一. 变量的存储方式
——>>静态存储方式,对应存放在内存中的静态存储区(程序执行就分配存储区,程序执行完毕才释放)
——>>动态存储方式,对应存放在内存中的动态存储区or CPU中的寄存器(函数调用时才分配存储区,调用结束就释放)
C语言的存储类别包括4种:auto(自动)、static(静态)、register(寄存器)、extern(外部)
1.局部变量
局部变量既可能是静态存储,也可能是动态存储
局部变量只能被auto、static、register修饰,不能被extern修饰
不同的存储类型来修饰局部变量会导致局部变量的存储方式不同,而不改变局部变量的作用域,具体三种如下:
(1)自动变量(auto修饰的局部变量)
auto修饰的局部变量会被分配到动态存储区,属于动态存储方式
auto int b = 5;
但实际上,局部变量未特殊声明均默认为自动变量,在敲代码时可以省略 auto 如:
int b = 5;
(2)静态局部变量(static修饰的局部变量)
static修饰的局部变量会被分配到静态存储区,在函数调用结束后保留原值,占用的存储单元不释放,直到整个程序结束才释放,书写格式如下:
static int n = 5;
举个栗子:
首先我们要明白static修饰的局部变量只会被赋予一次初值,然后我们来看这段代码
当i==0进入第一次循环时,m被赋予一次初值5,经过第六行计算后m==6被返回打印
当i==1进入第二次循环时,m不被赋予初值(可以理解为不再经过第五行)仍然等于6,经过第六行计算后m==7被返回打印
当i==2进入第三次循环时,m不被赋予初值,等于7,经过第六行计算后m==8被返回打印
聪明的你明白了吗?
(3)寄存器变量(register修饰的局部变量)
register修饰的局部变量会被分配到CPU中的寄存器中
一般来说,变量是存放在内存中的,当程序中用到某一变量时,会被从内存送到CPU中的运算器中,结束后再从运算器被送回到内存中
如果某一变量频繁被使用(来回在CPU和内存中存取),就要花费不少时间在路上,这时为了提高效率,允许将局部变量放在CPU中的寄存器中,需要时直接取出参加运算,效率大大提升,具体格式如下:
register int a = 5;
但是如今的编译系统可以自动识别频繁使用的变量,会自动地将变量放在寄存器中,实际上不必要使用register来修饰变量
2.全局变量
全局变量都是静态存储
全局变量只能被static、extern修饰,不能被auto、register修饰
与局部变量不同,全局变量被不同的存储类型修饰,仍然存放在静态存储区,只是对全局变量的作用域有影响,所以先不讨论static与extern修饰全局变量的不同,接下来谈到 “extern与static对变量和函数的用法 ” 时,再详细阐述。
3.变量的定义与声明
对于局部变量:
auto、static、register这三个存储类型只能在定义时才可以使用,不可以用于声明
如上,如果用于局部变量的声明,就会出现重定义的报错
对于全局变量:
static、extern这两个存储类型不仅可以用于定义,而且可以用于声明
二. extern与static对变量和函数的用法
1.extern的用法
(1)对于全局变量
1)在同一文件中扩展全局变量作用域
我们知道程序的读取是由上至下的,如果全局变量A的使用在它的定义之前,这时编译器就会报错,如:
这时我们有三种解决办法
1.将 int A = 10; 移动到主函数的上面
2.在主函数上面加一个对全局变量A的声明
3.在主函数内部加一个对全局变量A的声明
第一种方法:Ctrl+X,Ctrl+V直接搞定
第二种方法:
这时有两种写法,加不加extern均可,意思相同
extern声明全局变量可以将全局变量的作用域进行扩展,此代码A的作用域从第11行往下扩展到了从第4行往下
第三种方法:
如果在主函数内部想要对全局变量A进行声明,那么只能用extern修饰,不能像第二种方法一样直接用 int A; ,否则会报错如下:
只有加上extern才能运行,如下:
2)将全局变量作用域扩展到其他文件
全局变量本身是具有外部链接属性的,在M文件中定义的全局变量,我们可以在N文件中通过链接使用
但是我们可以直接使用在另一个文件中的全局变量吗?我们来看代码:
答案是不能直接使用,会显示 A 这个变量还没有声明
那我们怎样在另一个文件中声明A变量呢?
这时就要用extern来声明全局变量A,它的作用是将“test_01.c”文件中的全局变量A的作用域扩展到“test_02.c”文件中,让“test_02.c”文件也可以使用全局变量A
建议将extern修饰的声明放在引用它的所有函数之前,因为程序的读取是由上至下的,可能会出现程序走到A变量的时候,A变量却没有声明这种尴尬处境,如下代码:
(2)对于函数
extern对于函数的作用和将全局变量作用域扩展到其他文件的作用类似,所以简单提一下,就不再赘述
函数本身是具有外部链接属性的,在M文件中定义的函数,我们可以在N文件中通过链接使用
我们不能直接在“test_02.c”文件中直接调用“test_01.c”文件中的 test() 函数(部分编译器可以,不推荐),需要在调用前先用extern进行声明,目的是将 test() 函数的作用域扩展到“test_02.c”文件中,能让主函数调用它
2.static的用法
(1)对于局部变量
与刚刚我们提到的静态局部变量的用法一致
我们现在来总结一下并再次举出一个简单的栗子来巩固知识点
总结:
1)只将局部变量赋予一次初值
2)每次使用完将数据保留在静态存储区,下次直接调用,整个程序结束才销毁
3)局部变量的存储位置从动态存储区放到静态存储区
栗子:(输入一个整数k,输出1!+2!+3!+······(k-1)!+k!)
首先我们来思考拆分问题:
1!= 1!
1! * 2 = 2!
2! * 3 = 3!
3! * 4 = 4!
以此类推,我们可以看出每次计算后的结果对下一次计算有帮助,只需要乘上n,就能得到n!
(如果我们每一次计算阶乘时都从1开始乘起,虽然便于理解,但是效率相对较低)
然后我们容易想到,在函数调用结束的时候,函数内的局部变量所在内存会被释放,每次计算的结果不会被保留,那怎么办呢?
这时就要用到static修饰的局部变量来将数据保留下来,等待下一次函数调用时使用,直到整个程序结束为止
思路有了最后我们来看代码:
第一次循环进入 fac 函数时,将 i 赋予初值1,计算完n *= 1时,n==1被返回(1!)
第二次循环进入 fac 函数时, i 仍==1,计算完n *= 2时,n==2被返回(2!)
第三次循环进入 fac 函数时, i 仍==2,计算完n *= 3时,n==6被返回(3!)
第四次循环进入 fac 函数时, i 仍==6,计算完n *= 4时,n==24被返回(4!)
每次循环由sum += fac将所有返回值相加得到结果33
聪明的你明白了吗?
(2)对于全局变量
刚刚我们在讲extern的用法时提到过,全局变量本身是具有外部链接属性的,在M文件中定义的全局变量,我们可以在N文件中通过链接使用
也就是说,全局变量在定义时不加任何修饰,默认为extern类型
而我们在讲全局变量时知道,全局变量只能被static、extern修饰,不能被auto、register修饰
那我们给全局变量加上static修饰了会怎样呢?请看如下代码:
我们可以看到,这时即使我们在“test_01.c”文件中用extern声明了全局变量A,但是它也会报错,因为static对于全局变量的作用就是将全局变量的作用域限制在它本身的文件中,不允许其他文件通过extern来调用它
(3)对于函数
static对于函数的作用和对于全局变量的作用类似,所以简单提一下,就不再赘述
函数和全局变量类似,函数也是在定义时不加任何修饰,默认为extern类型(外部函数)
在函数定义时加上static修饰,就会将函数的作用域局限在它本身的文件中,不允许其他文件通过extern来调用它,代码如下: