zoukankan      html  css  js  c++  java
  • 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;
        result = addtwonum();
        printf("result 为: %d
    ",result);
        return 0;
    }

    执行:

    $ 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
    ",result);
        return 0;
    }

    补充:声明和定义的故事

    声明和定义
    源码执行经过编译器这座桥梁。
    用文件去编写程序,一个大型程序会被组织成多个文件,这就给编译带来了难题。
    这些文件最终是要被翻译成程序的,可是它们的数量却是变化的。
    较小的程序可能有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,一个编译逻辑
  • 相关阅读:
    java在线预览txt、word、ppt、execel,pdf代码
    java读写文本文件大全
    经典SQL语句大全
    javascript和jQuery知识点总结
    30条经典的SQL语句
    js 树菜单
    一天一记
    笔记本驱动的安装事项[acer]
    拒绝买房的八大理由
    javascript 常用类
  • 原文地址:https://www.cnblogs.com/straybirds/p/14744009.html
Copyright © 2011-2022 走看看