C语⾔extern的概念(声明和定义的区别)在java语⾔中,没有注意,C语⾔还专门有个关键词 extern来标⽰声明,在这记录⼀下:
extern
adj. 外⾯的;外来的;对外的
外部变量的意思
最简单的说法:
声明就是没有分配值空间
定义就是分配了值空间
这样说貌似也没错,但⼀些场景有点说不清,⽐如下⾯
extern int i;
int i;
extern int d = 3, f = 5; // d 和 f 的声明与初始化
int d = 3, f = 5; // 定义并初始化 d 和 f
这两种情况:要么都没有赋值,要么都赋值。那么这样还有什么意义,有什么区别。
具体来说就是:
extern int i; //声明,不是定义
int i; //声明,也是定义
区别就是定义包括了声明,声明只是声明。
意思就是变量使⽤前都需要声明然后定义,
但写法可以⼀步到位(声明就定义),
也可分开写(不仅可以在同⼀个⽂件不同地⽅,还可以声明和定义分别在不同⽂件)。
声明的使⽤有两种情况:
1、⼀种是声明需要建⽴存储空间的。例如:int a 在声明的时候就已经建⽴了存储空间。
2、另⼀种只声明不需要建⽴存储空间的,通过使⽤extern关键字声明变量名⽽不定义它。
例如:extern int a,其中变量 a 可以在别的⽂件中定义的。
除⾮有extern关键字,否则都是变量的定义。
int i;这种写法和java⼀样都是,
⾸先编译器初始化(分配空间值为0),再有赋值语句,就修改内存空间的值。
extern int d = 3, f = 5; // d 和 f 的声明与初始化
int d = 3, f = 5; // 定义并初始化 d 和 f
个⼈理解这两种的区别:
extern int d = 3, f = 5; 分配空间的直接赋值。
int d = 3, f = 5;分配空间先指定⼀个默认值再赋值。
补充:初始化的意思有两种情况
1,定义时指定值,第⼀次主动赋值。
2,定义不指定值,编译器默认赋值
⼀个声明和赋值不在同⼀个⽂件的例⼦:
addtwonum.c ⽂件代码:
#include <stdio.h>
/*外部变量声明*/
extern int x ;
extern int y ;
int addtwonum()
{
return x+y;
}
test.c ⽂件代码:
#include <stdio.h>
/*定义两个全局变量*/
int x=1;
int y=2;
int addtwonum();
int main(void)
{
int result;text函数什么意思
result = addtwonum();
printf("result 为: %d\n",result);
return0;
}
执⾏:
$ gcc addtwonum.c test.c -o main
$ ./main
result 为: 3
注意点:C语⾔的⽅法定义不同java语⾔(略微有点java抽象⽅法的影⼦,这⾥先声明⽅法体再⽤抽象⽅法(个⼈的⼀种⾮正规表达⽅式))
/*外部变量声明*/
extern int x ;
extern int y ;
int addtwonum()
{
return x+y;
}
/*定义两个全局变量*/
int x=1;
int y=2;
int addtwonum(); //别的⽂件有⽅法体,这⾥竟然可以这样写。⽽且不⽤传参数就赋值(从java⾓度看,挺别扭的!)
int main(void)
{
int result;
result = addtwonum();
printf("result 为: %d\n",result);
return0;
}
补充:声明和定义的故事
声明和定义
源码执⾏经过编译器这座桥梁。
⽤⽂件去编写程序,⼀个⼤型程序会被组织成多个⽂件,这就给编译带来了难题。
这些⽂件最终是要被翻译成程序的,可是它们的数量却是变化的。
较⼩的程序可能有2个⽂件,较⼤的程序可能有⼏百万个⽂件,
代码是组织在多个⽂件中的,编译器为了解决这个问题,
提出了声明和定义这两个概念声明 Declarations 定义 Definitions
⼀个变量或函数在内存中只能存在⼀份,所以在代码中它只能在⼀个地⽅被定义,这就是定义。
⽽这个变量或函数可能被多个⽂件使⽤,
使⽤的时候需要知道它的类型,
可是它却只能有⼀个定义,怎么解决这个⽭盾呢?-- ⽤声明。
如果程序不是存放在多个⽂件中,那么根本就不需要声明,直接定义对象就够了。
如果程序虽然放在多个⽂件中,可是它们能相互间⾃由引⽤(考,那和⼀个⽂件有什么分别),那么也不需要声明。
可是,你知道这些假设都是不可能的,因为⼈类是⽤⼀个⼀个的⽂件去表达的。
看待⼀个⽂件的时候,某个事物在它的上下⽂中意义更明确并与众不同。
编译器把它定义为作⽤域,并⽤在了函数中。造成了编译器必须去这么设计,必须有声明和定义这种语法。
赋值和初始化的故事:
赋值和初始化现在的编译器已经“聪明”到超出你的想象。
即使去看⼏⼗年前的⽼ c 编译器,它的聪明程度也会令你惊叹。
初始化就是这样的⼀个“聪明”的⾏为。
可是偏偏初始化使⽤了和赋值⼀样的语法,形如int foo = 123;结果,导致了这个编译时⾏为有点耍“⼩聪明”的味道。
如果我告诉你,在c语⾔本来的设计中,初始化和赋值是两种截然不同的语法。你就会恍然⼤悟了。
int foo 123; /* 初始化,只能⽤在全局变量 */
int foo = 123; /* 赋值,只能⽤在局部变量 */
这两种语法出现的场景、作⽤的对象和含义都不相同,很好区分。
初始化完全是编译器的⾏为,赋值则是运⾏时的⾏为。
标准 C 后来统⼀了初始化这个概念,全局变量的初始化和⾃动变量的默认值赋值都叫初始化。
这确实更“⾼级”了,但是其实这两个初始化差别却存在,全局变量的初始化值只能是常量。所以这也是 C 的⼀个遗憾。
故事会:
⼀、未声明
1.c:
int main() {
a = 1;
}
$ cc 1.c1.c:2:5: error: use of undeclared identifier 'a' a = 1; ^1 error generated.
undeclared:declare 是【对外宣告】,
undeclared形容词 --【没有对外宣告过的】,
叫【未声明】
identifier: identify是【⾝份证】,也叫ID。
*ier是什么⼈,identifier就是【有⾝份的⼈】,
叫【标识符】
合起来是,
a 标识符没有声明,不知道它是个什么东西。这就叫未声明 undeclared
⼆、未定义
1.c:
extern int a;
int main() {
a = 1;
}
$ cc 1.c/tmp/ccxhuV7j.o: In function `main':1.c:(.text+0x6): undefined reference to `a'collect2: error: ld returned 1 exit status undefinedundefined: 【没有定义过的】意思
reference:【介绍信】的意思
叫【引⽤】
to 'a': 对于 a
合起来是,对于 a 的【介绍信】,是【没有定义过的】
这句话是说,a 是个名称,它引⽤的内存实体是不存在的。
这就叫未定义 undefined
三、不能赋值
1.c:
int main() {
main = 123;
}
$ cc 1.c1.c:2:8: error: non-object type 'int ()'is not assignable main = 123; ~~~~ ^1 error generated.
assignable这句话是说non-object type: ⾮对象类型
'int ()': 返回值为int的函数类型
is not assignable: 不可以被赋值合起来就是,⾮对象类型的函数类型不可以被赋值main 是返回值为int的函数类型,
它为什么不能被赋值呢?
要从对象说起,对象是⼀块可以操作的内存块。
⾔外之意,内存中还存在不能被操作的内存?
是的,
内存有向量区禁⽌⼊内⽂本区禁⽌乱涂乱画数据区⾃⼰的可以随便玩,不是⾃⼰的禁⽌拍照IO区只开放给专家学者对象!
本例中,main 是⼀块⽂本区的内存,不是可操作的内存,所以不能被赋值。
这就叫不能赋值 not assignable
四、不能初始化
1.c:
int a = "foo";
int main() {}
$ cc -w 1.c1.c:1:9: error: initializer element is not computable at load time int a = "foo";
initializerelement:是常量"foo",是个字符串地址
is not computable:不是算数at load time:
在程序运⾏的时候合起来就是,字符串地址在运⾏时不能被计算。
a 这个位置是int,字符串就是⼀个地址,也是int。
因此,从原理上来说上⾯程序没有问题。
事实上,在⽼ c 语⾔中,上述程序正常。
但是,后来语法变了。为什么?
为了更加规范和安全,这种⾏为被禁⽌了。
编译器给出的理由是,初始化的元素在运⾏时是算不了的。
其实,这是⼀个善意的谎⾔,指针当然可以计算。
但是为了规范有⼈阻⽌了你,阻⽌你的⼈正是初始化。
初始化是编译器这个⼤程序的⼀个⼦程序。
程序分编译时,运⾏时。
⽬前看来,编译时越来越庞⼤,越来越聪明。
各种思想⽅法论被发明出来,典型如c++。
事实上,编译器初始化的⼀些⼩聪明展现了他的可怜⽗母⼼,巴不得把所有后事都料理完,脏活累活全都不让孩⼦⼲。
⽐如下⾯的代码
1.c:
char *s = "bar";
int days[] = {31,28,31,30,31,30,31,31,30,31,30,31};
long hour = 60*60*1000;
int main() {}
编译器为"bar"分配字符串存储,把days变成数组,hour算成600000,⽽不是在运⾏时再算。
这就是初始化器 initializer,⼀个编译逻辑
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论