vlambda博客
学习文章列表

【连载】(学了这么多年C语言,你真的了解static关键词吗)乐创DIY C语言讲义​——4.4节


文 | Edward

【连载】(学了这么多年C语言,你真的了解static关键词吗)乐创DIY C语言讲义​——4.4节

4.4 变量的补充
前面内容中,我们已经讨论了变量的定义,但是由于函数的概念还没有引入,因而这是不全面的,本节开始,我们再来补充一些变量的其他内容,这就相当于对变量这个概念的重新认识。
我们之前讲的变量定义,都是定义在“main()“函数里面的,并且告诉大家了变量定义的一个原则,即变量先定义后使用。其实早在C90的时候,只允许函数定义完成之后,紧接着将所有的变量都定义好然后再使用,如图4-4-1所示。一旦发现有在程序语句后面定义变量,编译器就会报错。典型的就是51单片机的编程语言KeilC51,它是基于C90开发的一套特殊的C语言,因此相信大家做过51单片机的,都遇到过这种莫名其妙的错误,如图4-4-2所示。这个问题是早期的K&R C就遗留下来的。
                           

【连载】(学了这么多年C语言,你真的了解static关键词吗)乐创DIY C语言讲义​——4.4节

图4-4-1 C90的变量定义

【连载】(学了这么多年C语言,你真的了解static关键词吗)乐创DIY C语言讲义​——4.4节

图4-4-2 C51变量定义错误
 
而C99开始就已经修改了这种别扭的设定,从此以后,你可以在任何地方定义你想要定义的变量,只要这个变量是在使用之前被定义的即可。如图4-4-3所示。

【连载】(学了这么多年C语言,你真的了解static关键词吗)乐创DIY C语言讲义​——4.4节

图4-4-3 C99变量定义

但是,最根本的原则不能变,即变量要在使用之前被定义,如果先使用变量然后再定义变量,编译器就会报错。如图4-4-4所示。

【连载】(学了这么多年C语言,你真的了解static关键词吗)乐创DIY C语言讲义​——4.4节

图4-4-4 变量先使用后定义导致报错
 
以上是变量定义时的详细补充,下面的内容,我们将细致地讨论以下内容:
Ø 全局变量和局部变量;
Ø 静态变量和常变量。
 
(1)全局变量和局部变量
定义在函数内部的变量叫做局部变量,当我们将这个变量定义完成之后,即可以在当前函数内部使用。而一旦函数被调用完成之后,局部变量将会被编译器回收,即只有在使用时才分配存储空间的变量为局部变量。如图4-4-3中的“int a; int b; int c;”,都是局部变量。为了说明定义在函数中的局部变量每次运行完成之后会被回收,每次使用会被重新定义,我们可以设计一个程序来说明这个现象。如图4-4-5所示。

【连载】(学了这么多年C语言,你真的了解static关键词吗)乐创DIY C语言讲义​——4.4节

图4-4-5 局部变量验证

图4-4-5中,我们定义了一个函数“LoacalVarPrint”,这个函数的主要功能是,定义一个局部变量,对其自加,然后打印出来,而主函数写了一个“for(;;)”的空循环,这个循环和“while(1)”等价,即不断地调用而不退出,在这个循环里面,主函数不断地调用“LoacalVarPrint”函数,“Sleep(500)”的原型是Sleep(n),它是Windows中的系统函数(API),所以需要包含“windows.h”,其功能是延迟n毫秒。主函数中还有一个不断自加的变量,每调用一次就对其自加。如果加设局部变量退出时是不会被回收,重新调用函数时是不会被重新初始化,那么“LoacalVarPrint”函数中的变量“var_to_watch”应该被不断地自加,但是现在查看程序的输出结果,它永远为1,也就是说,主函数每次调用函数时,这个变量重新被初始化(定义变量缺省初始化时,编译器一般自动赋初值为0),然后自加1,因此这个程序的输出永远为1。退出这个循环程序使用“Ctrl + C”。
全局变量是指定义在函数外面的变量,它其实有个默认的属性,即“静态属性”,所谓静态属性和下面所讲的静态变量类似,一旦编译器分配存储空间给它便不会回收了,它将永远存在。
全局变量存在的形式有两种。
如果是单个.c文件中定义的全局变量,一般将其定义在程序的第一个函数之前。因为全局变量也遵循“先定义后使用”的基本变量定义原则,因而如要被越多的函数使用到,那就要定义在越多的函数前面,因而定义在.c文件第一个函数之前。如图4-4-6所示。我们可以看到,在main函数之前定义了全局变量,每次在GlobalVarPrint函数中自加并且打印,这个变量会一直保留着进行自加,并不会被重新初始化和回收释放。由于全局变量有这么好的特性,因此我们需要用到一些系统状态被工程中的其他函数读写,就可以使用全局变量来传递了。

【连载】(学了这么多年C语言,你真的了解static关键词吗)乐创DIY C语言讲义​——4.4节

图4-4-6 全局变量验证

如果一个C语言工程项目中有多个文件,在其中的一个文件中定义一个全局变量是其可以被其他.c文件中的函数读写,那么这个变量和函数一样,不仅需要定义,还要在.h文件中被声明,声明全局变量用“extern”表示。如图4-4-7所示。我们一共定义了四个文件,一个是main.c,里面存放主函数,一个是increase.c里面实现了一个全局变量的自加,一个是increase.h,这是increase.c的头文件,主要存放了函数和全局变量声明,最后一个是它们的依赖文件Makefile。

【连载】(学了这么多年C语言,你真的了解static关键词吗)乐创DIY C语言讲义​——4.4节

图4-4-7 多文件全局变量定义和声明
 
(2)静态变量和常变量
静态变量是一种用“static”关键词声明的变量,它表示这个变量本身是一种静态的变量,我们前面说了,静态属性的变量,其好处就是变量不会被编译器回收释放,而用“static”关键词声明的变量不仅能做全局变量,也能做局部变量,做局部变量时,它在函数执行完成之后也不会被编译器回收。
静态变量的定义方式为:static 变量类型变量名;
可能现在大家就有疑问了,静态变量的这种功能岂不是和全局变量一样了?为什么还要再单独弄个静态变量?这真是多此一举。可是这真的是多次一举吗?
这里大家要注意,在C语言中,所有用“static”关键词修饰静态类型都有一个共同的特征,即不能放在.h文件中声明。这一个看似没啥用的特征却对C语言这种简单语言起了很大的作用,即使用static修饰的变量和函数,可以作为每个独立.c源文件的私有变量和函数。私有变量和函数就意味着,当我们将一个功能模块里面的函数和变量定义好之后,又不想被调用者随便更改而造成某些问题,那我们就可以使用私有函数和变量,仅供内部调用。而从C++以后的语言都增加了“private,public,protect”关键词来表示变量的权限。如图4-4-8所示,我们将图4-4-7代码中的GlobalIncNum全局变量静态化声明,并且在.h文件中声明它,结果编译器就抛出异常。

【连载】(学了这么多年C语言,你真的了解static关键词吗)乐创DIY C语言讲义​——4.4节

图4-4-8 静态变量声明报错
 
而图4-4-9中,我们“大胆”地定义了两个变量名完全相同的变量,并且都用static关键词做静态化修饰。main.c函数中的GlobalIncNum变量做自减,而increase.c中的GlobalIncNum变量做自加,结果发现编译正常通过,而且在main函数中输出的是其自己文件中定义的GlobalIncNum变量,因为这个变量是自减的。当然,你也可以在GlobalVarIncrease()函数中也将GlobalIncNum变量打印出来,看看有什么现象。

【连载】(学了这么多年C语言,你真的了解static关键词吗)乐创DIY C语言讲义​——4.4节

图4-4-9静态修饰变私有化
 
static修饰的静态变量还有一个功能,即它在函数退出时不会被编译器回收。如图4-4-10所示,在函数中,将局部变量静态化,那么它每次在函数结束调用后也不会被回收了。

【连载】(学了这么多年C语言,你真的了解static关键词吗)乐创DIY C语言讲义​——4.4节

图4-4-10 静态变量不会被编译器回收
 
C语言中const关键字是constant的缩写,它的意思是常量、常数等。C语言中const功能很强大。这里我们先说明它用来修饰变量时候的用法。Const关键词修饰变量的用法为:const 数据类型变量名。如“const intnumber = 5;”这就表面,定义了一个常变量,变量名为number,值为5。而在程序任何地方都不能修改number的值,因为无论你怎么修改,这个程序都编译不过。如图4-4-11所示。

【连载】(学了这么多年C语言,你真的了解static关键词吗)乐创DIY C语言讲义​——4.4节

图4-4-11 const变量非法修改

其实const修饰的常变量有点类似于只读变量的意思,事实上编译器处理这些变量的时候,都是将其直接编译进程序的代码里,而不作为数据来用,这么说可能做PC端开发的读者不明白。如果你用哈佛结构的计算机,如绝大多数单片机,你会知道,它们的程序和数据是分别存在两个不同的存储器上面的一个类似于PC的硬盘,一个类似于内存。不同的是哈佛架构的计算机,程序直接可以在硬盘里面运行,不需要像冯诺依曼架构计算机一样,拷贝到内存上,而运行过程中的数据(如一般变量)是存放在数据存储器上的。而如果用const关键词修饰的常变量,编译器会将它直接放到程序存储器里面,因为它是只读的。