zoukankan      html  css  js  c++  java
  • c语言的extern与static与递归

    知识点:

    外部函数:定义的函数能被本文件和其他文件访问

     1> 默认情况下所有函数都是外部函数

     2> 不允许有同名的外部函数

    内部函数:定义的函数只能被本文件访问,其他文件不能访问

     1> 允许不同文件中有同名的内部函数

     static对函数的作用:

     1> 定义一个内部函数

     2> 声明一个内部函数

     extern对函数的作用:

     1> 完整地定义一个外部函数

     2> 完整地声明一个外部函数

     (extern可以省略,默认情况下声明和定义的函数都是外部函数)

        全局变量分2种:

     外部变量:定义的变量能被本文件和其他文件访问

     1> 默认情况下,所有的全局变量都是外部变量

     1> 不同文件中的同名外部变量,都代表着同一个变量

     内部变量:定义的变量只能被本文件访问,不能被其他文件访问

     1> 不同文件中的同名内部变量,互不影响

     static对变量的作用:

     定义一个内部变量

     extern对变量的作用:

     声明一个外部变量

     static对函数的作用:

     定义和声明一个内部函数

     extern对函数的作用:

     定义和声明一个外部函数(可以省略)

     一、extern与函数

      如果一个程序中有多个源文件(.c),编译成功会生成对应的多个目标文件(.obj),这些目标文件还不能单独运行,因为这些目标文件之间可能会有关联,比如a.obj可能会调用c.obj中定义的一个函数。将这些相关联的目标文件链接在一起后才能生成可执行文件。

    先来理解2个概念:

    • 外部函数:如果在当前文件中定义的函数允许其他文件访问、调用,就称为外部函数。C语言规定,不允许有同名的外部函数。
    • 内部函数:如果在当前文件中定义的函数不允许其他文件访问、调用,只能在内部使用,就称为内部函数。C语言规定不同的源文件可以有同名的内部函数,并且互不干扰。

    接下来就演示在一个源文件中调用另外一个源文件定义的函数,比如在main.c中调用one.c中定义的one函数。

    1.首先在one.c中定义了一个one函数

    如果你想让这个one函数可以被main.c访问,那么one函数就必须是外部函数。完整的定义是要加上extern关键字。

    不过这个extern跟auto关键字一样废,完全可以省略,因为默认情况下,所有的函数就是外部函数。我们可以简化一下:

    2.接下来,我想在main.c的main函数中,调用one.c中的one函数

    怎样才能调用one.c中的one函数呢?你可能会产生2个想法:

    想法1:直接在main函数中写上one();

    这个做法肯定不行,因为main函数根本不知道one函数的存在,怎么调用呢?这个在标准C编译器里面会报错的,但是在Xcode中只是个警告。

    想法2:在main.c中包含one.c文件

    大家都知道#include的作用纯粹就是内容拷贝,所以又相当于

    哎,这么一看好像是对的哦,在main函数前面定义了个one函数,然后在main函数中调用了这个one函数。从语法上看是对的,所以编译是没问题的。但是这个程序不可能运行成功,因为在链接的时候会报错。我们已经在one.c中定义了one函数,现在又在main.c中定义one函数,C语言规定不允许有同名的外部函数,链接的时候链接器会发现在one.obj和main.obj中定义了同一个函数,会直接报错,Xcode中的错误信息是这样的:

    duplicate symbol _one是说one这个标识符重复了,linker是指链接器。

    上面的2种想法都是不可行的,其实思路是一致的:让main函数知道one函数的存在。正确的做法应该是在main函数前面对one函数进行提前声明(看清楚,是声明,不是定义,定义和声明是两码事)。

    3.在main函数前面对one函数进行提前声明

    你想要把其他源文件中定义的外部函数拿过来声明,完整的做法,应该使用extern关键字,表示引用别人的"外部函数"

    运行程序,从控制台输出可以发现 "one.c中定义的one函数" 已经被 "main.c的main函数" 成功调用了。

    也有人可能会马上冒出一个想法:假如除开one.c,还有其他源文件也有定义这个one函数怎么办?那main函数调用的究竟是谁的one函数啊?放心,绝对不会有这种情况,刚才不是说了么,不允许重复定义同一个外部函数,不然链接器会报错的,所以只会有一个外部one函数。

    上述就是extern关键字对函数的作用:用来定义和声明一个外部函数。其实extern又跟auto一样废,完全可以省略。于是,我们可以简化成这样:

    为了模块化地开发,在正规的项目里面,我们会把one函数的声明写到另一个头文件中,当然,这个头文件的命名最好有意义、规范一点,比如叫one.h。以后,谁想调用这个one函数,包含one.h这个头文件就行了。于是最后的代码结构是这样的:

      

    二、static与函数

    1.定义内部函数

    从上面的例子可以看出,one.c中定义的one函数是可以被其他源文件访问的。其实有时候,我们可能想定义一个"内部函数",也就是不想让其他文件访问本文件中定义的函数。这个非常简单,你只需要在定义函数的时候加个static关键字即可。

    (我们就在上面例子的代码基础上进行修改)

    我在void one()的前面加了个static,代表one函数是个内部函数。

    然后你会发现程序运行不起来了,在链接的时候就报错了。报错的原因很简单:我们在main.c中调用了one.c中定义的one函数,但是现在one.c的one函数是个"内部函数",不允许其他文件访问。我们来看看错误信息:

    第1个红框中的Undefined symbols...意思是one这个标识符没有被定义,也就是找不到one;第2个红框的linker表明是链接器报错了。

    但这个程序是可以编译成功的,因为我们在main函数前面声明了one函数(函数的声明和定义是两码事),这个函数声明可以理解为:在语法上,骗一下main函数,告诉它one函数是存在的,所以从语法的角度上main函数是可以调用one函数的。究竟这个one函数存不存在呢,有没有被定义呢?编译器是不管的。在编译阶段,编译器只会检测单个源文件的语法合不合理,并不检测函数有没有定义,只有在链接的时候才会检测这个函数存不存在,也就是有没有被定义。

      

    我们再来讨论一个问题,为什么好多情况下都是可以成功编译,但是链接的时候报错呢?只要你理解编译和链接的作用就好办了。

    所谓编译,就是单独检查每个源文件的语法是否合理,并不会检查每个源文件之间的关联关系,一个源文件编译成功就生成一个目标文件。

    所谓链接,就是检查目标文件的关联关系,将相关联的目标文件组合在一起,生成可执行文件。

    看完这2个概念,再回去思考下前面报的错,应该可以完全明白了。

    2.声明内部函数

    我们还可以用static声明一个内部函数

     1 #include <stdio.h>
     2  
     3  static void test();
     4  
     5  int main(int argc, const char * argv[])
     6  {
     7      test();
     8      return 0;
     9  }
    10  
    11  static void test() {
    12      printf("调用了test函数");
    13  }

    在第11行定义了一个test函数,这是一个内部函数,接着在第3行对test函数进行提前声明,然后就可以在第7行可以调用test()函数了

    三、static、extern与函数的总结

    1.static

    * 在定义函数时,在函数的最左边加上static可以把该函数声明为内部函数(又叫静态函数),这样该函数就只能在其定义所在的文件中使用。如果在不同的文件中有同名的内部函数,则互不干扰。

    * static也可以用来声明一个内部函数

    2.extern

    * 在定义函数时,如果在函数的最左边加上关键字extern,则表示此函数是外部函数,可供其他文件调用。C语言规定,如果在定义函数时省略extern,则隐含为外部函数。

    * 在一个文件中要调用其他文件中的外部函数,则需要在当前文件中用extern声明该外部函数,然后就可以使用,这里的extern也可以省略。

    一、在Java中,全局变量的定义没有严格的位置规定

    全局变量可以定义在类的最前面,也可以定义在类的最尾端,也就说一个方法可以访问在它之后定义的变量。

    可以看到,第4行定义的test方法可以访问第8行定义的变量a,这是完全没有问题的。

    二、在C语言中,全局变量定义的位置是有限制的

    默认情况下,一个函数不可以访问在它后面定义的全局变量

    在第4行定义的main函数中尝试访问第9行定义的变量a,编译器直接报错了。

    解决这个错误的话,有2种办法:

    第1种办法:将变量a定义在main函数的前面

    这样做编译器就不会找你麻烦了。

    第2种办法:在main函数前面对变量a进行提前声明

    也就是让main函数知道变量a的存在就行了,至于变量a定义在哪个位置,main函数不用管。

    * 完整的变量声明需要用extern关键字

    第3行是对变量a进行声明,第10行是定义变量a,再次强调,声明和定义是两码事。在第6行操作的就是第10行定义的变量a。

    注意:你不能省略第10行的定义,只留下第3行的声明,因为extern是用来声明一个已经定义过的变量。

    三、重复定义同一个变量

    * 其实,你也可以直接在main函数前面再定义一次a

    看到这一幕,你可能很惊讶,但编译器是不会报错的。在这种情况下,第3行和第10行的变量a代表着同一个变量。

    * 以此类推,如果我们写了无数遍全局变量int a;,它们代表的都是同一个变量。

    第3到第6行、第13到第17行的变量a都代表着同一个变量。

    * 还要注意的一点是,我们也可以将全局变量a声明为局部变量后再使用!!!

    注意:第2、第5、第6、第10行都代表着同一个变量。其实,从第6行a的颜色(浅蓝色)都可以看出,这个a依然是个全局变量。

    (这是Xcode的特性,如果在函数内部访问了全局变量,全局变量就会显示浅蓝色,如果函数内部访问的是局部变量,局部变量就显示普通的黑色。当然,不同的开发工具有不同的显示方案)

    * 但是,如果你将第5行的extern去掉,那情况就完全不一样了,相信有编程经验的你都懂得这是什么情况了

    第2、第10行代表着同一个全局变量,而第5、第6行则是一个局部变量,跟外面的那个全局变量没有半毛钱的关系。其实从第5、6行a的颜色(黑色)都可以看出是个局部变量。

    四、不同源文件中的同名变量

    前面讲到,你在一个源文件中无论写多少遍全局变量int a;,它们代表的都是同一个变量。还有一个事实,假如在另一个源文件中也有全局变量int a;,那么这两个源文件的所有全局变量int a;都代表着同一个变量。

       

    注意:main.c和test.c中的全局变量a都代表着同一个变量。

    我们可以证明一下:

    首先,在test.c中定义一个函数来查看a的值

    然后在main.c的第9行修改a的值为10,然后调用test.c的test函数看看test.c中a的值

    控制台的输出已经证明了一切。

    * 当然,extern关键字还是适用的,比如:

    或者是:

    上面的两种情况下,test.c和main.c中使用的全局变量a都还是代表着同一个变量

    注意了,不可以两个文件的所有全部变量a都用extern,下面的做法是错误的:

    因为extern是用来声明一个已经定义过的变量,这两个文件都是在声明变量,没有人定义变量,在链接的时候肯定报错:

    大致错误意思是:标示符a未定义

    五、static关键字

    但很多时候,我们并不想让源文件中的全局变量跟其他源文件共享,相当于私有的全局变量,那么你就得用static关键字来定义变量。

    这样写完,test.c和main.c的变量a分别代表着不同的变量,它们是没有联系的、互不干扰的。也就是说,main.c无法访问test.c中的变量a,因此在main.c中将a修改为10后,test.c中的a依然为0。输出结果:

     其实static还可以用来修饰局部变量,这个在《变量类型》中说过,不再阐述了。

    * 因为main.c已经没有权限访问test.c中的变量a了,所以下面的写法是错误的:

     

    extern是用来声明已经定义过而且能够访问的变量,虽然test.c中有定义过变量a,但是test.c中变量a的作用域是只限于test.c文件,main.c没有访问权限,所以main.c中的extern是废的。

    链接的时候报错:标示符a未定义

    除非main.c自己定义一个变量a,这样子extern才是有效的,不过这时候main.c和test.c中的变量a是分别代表着不同变量

    六、static和extern的总结

    1.extern可以用来声明一个全局变量,但是不能用来定义变量

    2.默认情况下,一个全局变量是可以供多个源文件共享的,也就说,多个源文件中同名的全局变量都代表着同一个变量

    3.如果在定义全局变量的时候加上static关键字,此时static的作用在于限制该全局变量的作用域,只能在定义该全局变量的文件中才能使用,跟其他源文件中的同名变量互不干扰

     1 #include <stdio.h>
     2 
     3 /*
     4  static修饰局部变量的使用场合:
     5  1.如果某个函数的调用频率特别高
     6  2.这个函数内部的某个变量值是固定不变的
     7  */
     8 
     9 void test()
    10 {
    11     static double pi = 3.14;
    12     
    13     double zc = 2 * pi * 10;
    14     
    15     int a = 0;
    16     a++;
    17     printf("a的值是%d
    ", a); // 1
    18     
    19     /*
    20      static修饰局部变量:
    21      1> 延长局部变量的生命周期:程序结束的时候,局部变量才会被销毁
    22      2> 并没有改变局部变量的作用域
    23      3> 所有的test函数都共享着一个变量b
    24      */
    25     static int b = 0;
    26     b++;
    27     printf("b的值是%d
    ", b); // 3
    28 }
    29 
    30 int main()
    31 {
    32     for (int i = 0; i<100; i++) {
    33         test();
    34     }
    35     
    36     
    37     test();
    38     
    39     test();
    40     
    41     test();
    42     
    43     
    44     return 0;
    45 }

       递归:

     设计一个函数,用来计算b的n次方

      递归的2个条件:

     1.函数自己调用自己

     2.必须有个明确的返回值

     1 #include <stdio.h>
     2 int pow2(int b, int n);
     3 
     4 int main()
     5 {
     6     int c = pow2(3, 2);
     7     
     8     printf("%d
    ", c);
     9     return 0;
    10 }
    11 
    12 /*
    13  pow2(b, 0) == 1
    14  pow2(b, 1) == b == pow2(b, 0) * b
    15  pow2(b, 2) == b*b == pow2(b, 1) * b
    16  pow2(b, 3) == b*b*b == pow2(b, 2) * b
    17  
    18  1> n为0,结果肯定是1
    19  2> n>0,pow2(b, n) == pow2(b, n-1) * b
    20  */
    21 
    22 int pow2(int b, int n)
    23 {
    24     if (n <= 0) return 1;
    25     return pow2(b, n-1) * b;
    26 }
     
     
  • 相关阅读:
    现代软件工程 第一章 概论 第3题——韩婧
    现代软件工程 第一章 概论 第2题——韩婧
    小组成员邓琨、白文俊、张星星、韩婧
    UVa 10892 LCM的个数 (GCD和LCM 质因数分解)
    UVa 10780 幂和阶乘 求n!中某个因子的个数
    UVa 11859 除法游戏(Nim游戏,质因子)
    Codeforces 703C Chris and Road 二分、思考
    Codeforces 703D Mishka and Interesting sum 树状数组
    hdu 5795 A Simple Nim SG函数(多校)
    hdu 5793 A Boring Question 推公式(多校)
  • 原文地址:https://www.cnblogs.com/zhangxiaomeng1991/p/4158268.html
Copyright © 2011-2022 走看看