关键字
- 1. 前言
- 2. 什么是关键字
- 3. extern-声明外部符号
- 4. auto-自动
- 5. typedef-类型重定义(类型重命名)
- 6. register-寄存器
- 6.1 存储器
- 6.2 register关键字的作用
- 7. static-静态
- 7.1 static修饰局部变量
- 7.1.1 代码对比
- 7.1.2 原理分析
- 7.2 static修饰全局变量
- 7.2.1 代码对比
- 7.2.2 原理分析
- 7.3 static修饰函数
- 7.3.1 代码对比
- 7.3.2 原理分析
1. 前言
大家好,我是努力学习游泳的鱼。关键字,这名字一听,就很关键。而有些关键字,你可能不是很了解,更别谈使用。所以,这篇文章将带你见识常见的关键字,一起领略它们的风采吧。
2. 什么是关键字
C语言提供了丰富的关键字,这些关键字都是语言本身预先设定好的,
用户自己是不能创造关键字的。
大部分关键字会在其他章节介绍,这里仅介绍一些稍微有点难度的关键字。
3. extern-声明外部符号
extern
可以用来声明外部符号,如外部的全局变量和函数。
如我们在test1.c
里定义了全局变量a
,int a = 2022;
我们想在test2.c
里使用,就得先用extern
声明一下extern int a;
注意:一般extern
是用来声明外部的全局变量的。因为如果直接写int a;
就不是声明了,而是定义,会直接创建一个变量a
。只有写extern int a;
才是声明变量a
。如果是声明外部的函数,可以省略掉extern
。如直接写int Add(int, int);
和写extern int Add(int, int);
效果是相同的。
4. auto-自动
C语言里的局部变量,进入局部范围时自动创建,出局部范围时自动销毁。这种自动创建,自动销毁的特性,其实是由于前面省略了关键字auto
。比如,int a = 0;
其实编译器会处理为auto int a = 0;
一般来说,auto
会被省略掉。
5. typedef-类型重定义(类型重命名)
typedef
关键字用于给类型起别名,相当于起了个外号。
比如unsigned int num = 10;
如果我们嫌unsigned int
这个类型写起来太麻烦了,可以给它起个别名叫做uint
:typedef unsigned int uint;
这样上面的代码就等价于uint num = 10;
6. register-寄存器
6.1 存储器
数据的存储,需要存储器。常见的存储器有:
网盘,硬盘,内存,高级缓存,寄存器。
从左到右,速度越快,从而造价越高,从而空间越小。
早期,CPU处理的数据都来自内存。当时,CPU的处理速度和内存的读写速度是差不多的。随着技术的迭代,内存的读写速度逐渐跟不上CPU的处理速度,CPU在很大程度上被闲置了。
于是就有了这么一层设计。在内存之上设置读写速度更快的高级缓存和寄存器。CPU从寄存器中拿数据,与此同时,寄存器从高级缓存中拿数据,高级缓存从内存中拿数据。如果CPU想要的数据在寄存器中没有,那就直接从高级缓存中拿数据,如果还没有再从内存中拿。由于大部分数据都能在寄存器中命中,整体上,处理数据的速度就提升了。
以上,我们能明白一点:
寄存器的读写速度是非常快的!
6.2 register关键字的作用
如果我们写int num = 10;
,num
是放在内存中的。如果我们加了个register
:register int num = 10;
此时register
的作用是建议把num
放在寄存器中。注意只是建议,实际是否放在寄存器中取决于编译器的处理。
7. static-静态
在C语言中,static
有3种用法,分别修饰局部变量,全局变量和函数。
1.修饰局部变量-称为静态局部变量
2.修饰全局变量-称为静态全局变量
3.修饰函数-称为静态函数
7.1 static修饰局部变量
7.1.1 代码对比
下面代码的输出结果是多少呢?
#include <stdio.h>void test()
{int a = 5;a++;printf("%d ", a);
}int main()
{int i = 0;while (i < 10){test();i++;}return 0;
}
输出结果:10个6
为什么呢?test函数被调用了10
次,每次都做了同样一件事,创建a
并初始化为5
,a
自增变成6
,打印a
(即6
)。本质上,每次进入test
函数都会创建a
,出test
函数时都会销毁a
。这是由于局部变量的特性:进入局部范围创建,出局部范围销毁。那么,每次进入test
函数创建的都是一个新的a
,和之前创建的a
没有任何关系。
明白这点后,再看下面这段代码,输出的结果又是多少?
#include <stdio.h>void test()
{static int a = 5;a++;printf("%d ", a);
}int main()
{int i = 0;while (i < 10){test();i++;}return 0;
}
答案:输出6~15
。
分析一下:第一次调用test
函数时和没有static
相同,创建a
并初始化,自增,打印(此时a
是6
),但第二次调用怎么就打印7
了呢?这说明,第二次调用时,a
还是上次调用留下来的6
,才会自增变成7
!也就是说,第一次调用结束后,a
并没有销毁,第二次调用时依然存在。同理,第二次调用后a
也没有销毁,第三次调用时a
仍是第二次调用留下来的7
,然后自增变成8
后打印,以此类推。
static
修饰局部变量的时候,局部变量就变成了静态的局部变量,出了局部的范围,不会销毁,下一次进入函数依然存在。
7.1.2 原理分析
内存可以分为:栈区,堆区,静态区,等等。
栈区存储的是局部变量,函数参数,等等。
堆区是用来动态内存开辟的,与之相关的函数有malloc
,realloc
,calloc
和free
等等。
静态区存储的是静态变量和全局变量。
静态的局部变量出了作用域依然存在,是因为它是存储在静态区的。
同样存储在静态区的全局变量,生命周期也很长。
static
修饰局部变量时,实际改变的是变量的存储位置,本来一个局部变量是放在栈区的,被static修饰后放在了静态区,从而导致,出了作用域依然存在,生命周期并没有结束。
注意:放在静态区的变量出了作用域不销毁,相当于生命周期变长了,但是作用域并没有发生变化,也就是说,静态的局部变量仍然只能在它的局部范围内使用!
静态区中的数据的生命周期和程序的生命周期是一致的。程序结束,静态数据的生命周期也就到了。
7.2 static修饰全局变量
7.2.1 代码对比
我们创建两个源文件,test1.c
和test2.c
在test1.c
里定义一个全局变量g_val
// test1.c
int g_val = 2022; // 全局变量,定义在test1.c中
在test2.c
内部使用这个全局变量,由于全局变量的作用域是整个工程,所以可以跨源文件使用。但是在使用前需要使用extern
声明,否则会报编译错误。
// test2.c
extern int g_val;int main()
{g_val = 2023;return 0;
}
如果我们在g_val
的定义前面加上static
会发生什么呢?
// test1.c
static int g_val = 2022; // 全局变量,定义在test1.c中// test2.c
extern int g_val;int main()
{g_val = 2023;return 0;
}
此时会报链接错误,因为g_val
是定义在test1.c
里的静态全局变量,不能在test2.c
内部使用。看来静态的全局变量不能跨文件使用了。
7.2.2 原理分析
一个全局变量本来是具有外部链接属性的,既能在自己所在的源文件内部使用,也能在其他文件内部使用。
但是被static
修饰之后外部链接属性就变成了内部链接属性,只能在自己所在的源文件内部使用,不能在其他文件内部使用了。
使用上感觉作用域变小了。
7.3 static修饰函数
7.3.1 代码对比
我们在test1.c
里定义一个函数
// test1.c
int Add(int x, int y)
{return x + y;
}
在test2.c
内部使用,同理要先声明(此时可以省略extern
),否则会报一个警告。
// test2.c
#include <stdio.h>extern int Add(int, int); // extern可以省略int main()
{int sum = Add(10, 20);printf("sum = %d\n", sum);return 0;
}
如果在函数定义前加上static
会发生什么呢?
// test1.c
static int Add(int x, int y)
{return x + y;
}// test2.c
#include <stdio.h>extern int Add(int, int); // extern可以省略int main()
{int sum = Add(10, 20);printf("sum = %d\n", sum);return 0;
}
此时会报链接错误,因为Add
函数是定义在test1.c
内部的静态函数,不能在test2.c
内部使用。看来static
修饰函数和修饰全局变量类似,静态的函数也不能跨文件调用。
7.3.2 原理分析
static
修饰函数的作用:一个函数本来是具有外部链接属性的,但是被static
修饰之后,外部链接属性就变成了内部链接属性,这时这个函数只能在自己所在的源文件内部使用,其他文件是无法使用的。
使用上的感觉好像是作用域变小了。