C语言的关键字

C语言的关键字



前言

首先,C语言的关键字一共有32个,这是C90标准定义的关键字个数。其实后续的C99标准又新增了五个关键字,但是由于目前主流的编译器对C99的支持并不是特别好,所以我们默认是C90标准定义的关键字,即32个。


一、关键字的分类

关键字 说明
auto 声明自动变量
short 声明短整型
int 声明整型
long 声明长整型
float 声明单精度浮点型
double 声明双精度浮点型
char 声明字符型
struct 声明结构体类型
union 声明联合(共用)类型
enum 声明枚举类型
typedef 重命名数据类型
const 声明只读变量
unsigned 声明无符号类型
signed 声明有符号类型
extern 声明外部变量
register 声明寄存器变量
static 声明静态变量
volatile 说明变量在程序执行中可隐含的被改变
void 声明函数无返回值或者无参数,声明无类型指针
if 条件语句
else 条件语句否定分支(与if连用)
switch 用于开关语句
case 开关语句分支
default 开关语句中默认分支
for 循环语句
do 循环语句循环体
while 循环语句的循环条件
continue 结束当前循环,开启下一轮循环
break 跳出当前循环
goto 无条件跳转语句
sizeof 计算数据类型长度
return 子程序返回语句(可以带参数,也可以不带参数)循环条件

二、最默默无闻的关键字-auto

我们知道,在代码块内部定义的变量是局部变量,而局部变量默认是用auto修饰的,但是一般都会省略。

int main()
{
	for (int i = 0; i < 10; i++) {
		printf("i=%dn", i);
		if (1)
		{
			auto int j = 0; //自动变量
			printf("before: j=%dn", j);
			j += 1;
			printf("after : j=%dn", j);
		}
	}
	system("pause");
	return 0;
}

上述代码中的变量j就是一个局部变量,其用了auto修饰,但是如果去掉其同样也默认为auto变量。当然,这只限于局部变量。所以用auto修饰不是必要的,可以省略。


二、最快的关键字-register

在弄懂为什么register是最快的关键字之前,我们首先要简单的了解一些简单的知识。

其实,CPU是负责进行运算的硬件单元,如果要进行运算,就需要将数据读取到CPU中去,再进行运算。但是各种硬件的数据处理速度是不同的,而根据处理数据速度的不同可以对硬件进行分级:
在这里插入图片描述
这种存储分级自上而下速度越来越慢,而存储容量缺越来越大,这很好理解,因为速度变慢,单位成本也会降低不少,所以其存储容量也会随之变大。而这样分级的原因主要是为了能充分利用CPU的优良快速的性能。

所以,相较于内存而言,寄存器的访问速度相较于CPU是很快的,而内存就要比寄存器慢了不少,所以说,将数据存储在寄存器中会大大提高计算机的运行速度。

同时,register修饰变量会尽量将其放在寄存器中,因为如果寄存器都占用了或者编译器不同意你放进寄存器中,那就无法将想要放进寄存器的变量放入寄存器中。同时register变量是不能被取地址的。因为取地址这种寻址操作只能在内存中进行。

代码如下:

int main()
{
	register int a = 0;
	printf("&a = %pn", &a);
	//编译器报错:错误 1 error C2103: 寄存器变量上的“&”
	system("pause");
	return 0;
}

同时,使用寄存器变量时应该注意以下几点:

1.应当使用局部变量,因为全局变量会导致寄存器被长时间占用,可能会对计算机的状态造成影响。

2.应当在需要高频使用的时候用register关键字修饰局部变量。

3.如果要使用,也应当注意不能过多使用,因为寄存器数量是有限的,同时大都用来存储一些重要的状态信息。

三、最名不副实的关键字-static

1. 修饰全局变量

对于全局变量来说,定义了一个全局之后,就可以通过使用extern声明不在本文件内部的全局变量来进行使用。所以全局变量是可以跨文件被访问的。

而static修饰的全局变量只能够在本文件内部被使用。

2. 修饰局部变量

对于局部变量来说,其生命周期是在定义的代码块内部。但是如果用static修饰局部变量。其生命周期就会变成全局变量的生命周期。但是要注意,虽然其生命周期发生改变,但是其作用域却不发生改变。请看以下代码:

void fun1()
{
	int i = 0;
	i++;
	printf("no static: i=%dn", i);
}
void fun2()
{
	static int i = 0;
	i++;
	printf("has static: i=%dn", i);
}
int main()
{
	for (int i = 0; i < 10; i++)
	{
		fun1();
		fun2();
	}
	system("pause");
	return 0;
}

下面是代码的运行结果:
在这里插入图片描述
fun1函数中的i是一个局部变量,函数结束就会被销毁。而下一次调用又重新初始化,所以每一次的结果都是一样的。

而fun2函数中的i是一个由static修饰的局部变量,其生命周期变为全局变量的生命周期。所以函数结束也不会被销毁。才会是我们看到的递增的打印。

同时,被static修饰的全局变量和局部变量都会存放在内存中的静态区。

2. 修饰函数

没有被static修饰的函数是可以在被别的文件调用的。但是一但加了static修饰,就只能够在本文件内部使用。这个功能常用来隐藏一些不想被其他人看到的函数细节。

四、最冤枉的关键字-sizeof

首先看一下sizeof的用法:

int main()
{
//sizeof简单介绍,下个主题就讲
printf("%dn", sizeof(char)); //1
printf("%dn", sizeof(short)); //2
printf("%dn", sizeof(int)); //4
printf("%dn", sizeof(long)); //4
printf("%dn", sizeof(long long)); //8
printf("%dn", sizeof(float)); //4
printf("%dn", sizeof(double)); //8
return 0;
}

sizeof可以求出对应类型在内存开辟空间的大小。
注意:sizeof可不是函数,也不是宏,它是关键字或者说操作符。

五、signed、unsigned关键字

signed表示有符号数,即最高位为符号位表示数据的正负。

unsigned表示无符号数,则没有符号位。

默认情况下整型变量都是有符号数,即signed int。其他情况则要视编译器情况而定。

六、if,else关键字

if (表达式)
语句1;
else
语句2;

//多分支
if (表达式1)
语句1;
else if (表达式2)
语句2;
else
语句3;

//嵌套
if (表达式1) {
	语句1;
	if (表示式x) {
		语句x;
	}
	else {
		语句y;
	}
}
else if (表达式2) {
	语句2;
}
else {
	语句3;
}

要注意,else总是与离它最近的未被匹配的if相匹配。

在if语句的判定表达式用,我们经常使用表达式结果和0比较或者用整型数据和想比较,但是如果是浮点数呢,如何比较?

现在看一个简单的代码:

int main()
{
	double d = 3.6;
	printf("%.50lf", d);
	return 0;
}

下面是它的运行结果:
在这里插入图片描述
如果我们打印一个浮点数后面很多位的话,就会发现它的结果也不是那么精确。这种情况叫做精度损失,虽然很接近3.6这个数,但是能说它们相等吗?明显是不能的。

那浮点数究竟怎么比较呢?

//应该进行范围精度比较
//
if((x-y) > -精度 && (x-y) < 精度){
//TODO
}

if(fabs(x-y) < 精度){ //fabs是浮点数求绝对值
//TODO
}

精度可以自己设置,也可以使用系统的默认精度。

七、switch、case、break、default关键字

我们从一段简单的代码来分析这四个关键字:

int main()
{
	int day = 1;
	switch (day) {
	case 1:
		printf("星期一n");
		break;
	case 2:
		printf("星期二n");
		break;
	case 3:
		printf("星期三n");
		break;
	case 4:
		printf("星期四n");
		break;
	case 5:
		printf("星期五n");
		break;
	case 6:
		printf("星期六n");
		break;
	case 7:
		printf("星期日n");
		break;
	default:
		printf("bug!n");
		break;
	}
	system("pause");
	return 0;
}

首先switch括号内表达式应该是整形表达式或者一个整型值

case则是对应的整型表达式的值或者对应的整型值,根据表达式的值转到对应的case语句。

break是用来跳出switch语句。记得在每个case语句后都加上break,除非你知道你在做什么。

而default是为了处理case语句中没有的特殊情况。

八、do、while、for、continue关键字

C语言中有三种循环语句:while循环、do-while循环和for循环。continue和break的不同之处在于其是跳过本次循环进入下一次循环,而break则是直接跳出循环。

九、goto关键字

goto语句可以实现跳转,跳转到标签所在位置,但是不能跨代码块实现:

int main()
{
	int i = 0;
START:
	printf("[%d]goto running ... n", i);
	Sleep(1000);
	++i;
	if (i < 10) {
		goto START;
	}

	printf("goto end ... n");
	system("pause");
	return 0;
}

这是利用goto语句实现的循环。这样看来,goto语句的功能似乎很强大,但是我们应该慎用或者不用,因为它可能引发一些意想不到的错误。

十、void关键字

void关键字如果你认为没有,那就没有,如果你认为有,那就有。

那能不能用void去定义一个变量呢?

答案是不行,因为定义变量的本质是要为其在内存中开辟空间,而void作为一个空类型,理论上是无法为其开辟空间的。但是其可以用来修饰函数返回值和函数参数。

先来看函数返回值:

void show()
{
	printf("no return value!n");
}
int main()
{
	show();
	system("pause");
	return 0;
}

再来看一下void修饰函数参数:

int test1() //函数默认不需要参数
{
return 1;
}
int test2(void) //明确函数不需要参数
{
return 1;
}
int main()
{
printf("%dn", test1(10)); //依旧传入参数,编译器不会告警或者报错
printf("%dn", test2(10)); //依旧传入参数,编译器会告警(vs)或者报错(gcc)
system("pause");
return 0;
}

void变量让内存无法为其开辟空间,但是void却是可以的,因为其本质上是一个指针类型,在内存中为其开辟的空间大小也是一个指针变量的大小。同时void可以接受任何的指针类型,同时其也能被任何的指针类型接收。

//void* 能够接受任意指针类型
#include <stdio.h>
#include <windows.h>
int main()
{
	void* p = NULL;
	int* x = NULL;
	double* y = NULL;
	p = x; //虽然类型不同,但是编译器并不报错
	p = y; //同上
	system("pause");
	return 0;
}

但是,对void指针变量进行运算是不可以的,比如让指针加一或者让指针减一,因为无法确定类型,不知道指针变量指向的究竟是什么类型,所以无法进行运算。所以也无法对void指针进行解引用。

十一、return关键字

return用于终止一个函数,并返回其后面跟随的值。例如:

char* show()
{
	char str[] = "hello bit";
	return str;
}
int main()
{
	char* s = show();
	printf("%sn", s);
	system("pause");
	return 0;
}

show函数返回一个指向字符的指针,在函数中指针指向一个字符串,通过寄存器返回给调用方。但如果我们在主函数中打印,会是原先show函数中的字符串吗?

请看打印结果:
在这里插入图片描述
这是因为每个函数在调用时都要创建一个新的函数栈帧的原因,若要详细理解,请看函数栈帧一节。

十二、const关键字或许叫它readonly更合适

const修饰的变量,不可直接被修改。但是却可以通过指针的解引用来进行修改。

先来看一下直接修改const修饰的变量:

int main()
{
	const int a = 10;
	int* p = &a;

	a = 11; 
	//*p = 11;
	return 0;
}

在这里插入图片描述
再来看一下通过指针间接修改:

int main()
{
	const int a = 10;
	int* p = &a;

	//a = 11; 
	*p = 11;
	return 0;
}

在这里插入图片描述
这次编译器就没有报错。

const修饰指针变量分为几种情况:

  1. const int *p; // p 可变,p 指向的对象不可变
  2. int const *p; // p 可变,p 指向的对象不可变
  3. int *const p; // p 不可变,p 指向的对象可变
  4. const int *const p; //指针 p 和 p 指向的对象都不可变

同时const也可以修饰函数参数,const 修饰符也可以修饰函数的返回值,返回值不可被改变。例如:
const int Fun (void);

也可以在另一连接文件中引用 const 只读变量:
extern const int i; //正确的声明
extern const int j=10; //错误!只读变量的值不能改变

十三、最易变的关键字-volatile

volatile 是易变的、不稳定的意思。很多人根本就没见过这个关键字,不知道它的存在。也有很多程序员知道它的存在,但从来没用过它。我对它有种“杨家有女初长成,养在深闺人未识” 的感觉。

volatile 关键字和 const 一样是一种类型修饰符,用它修饰的变量表示可以被某些编译器未知的因素更改,比如操作系统、硬件或者其它线程等。遇到这个关键字声明的变量,编译器对访问该变量的代码就不再进行优化,从而可以提供对特殊地址的稳定访问。

嗯,先来看一个例子:
int i=10;
int j = i;//(1)语句
int k = i;//(2)语句

这时候编译器对代码进行优化,因为在(1)、(2)两条语句中,i 没有被用作左值。这时候编译器认为 i 的值没有发生改变,所以在(1)语句时从内存中取出 i 的值赋给 j 之后,这个值并没有被丢掉,而是在(2)语句时继续用这个值给 k 赋值。这是一种内存被“覆盖”的情况。编译器不会生成出汇编代码重新从内存里取 i 的值,这样提高了效率。但要注意:(1)、(2)语句之间 i 没有被用作左值才行。

再看另一个例子:
volatile int i=10;
int j = i; //(3)语句
int k = i; //(4)语句

volatile 关键字告诉编译器 i 是随时可能发生变化的,每次使用它的时候必须从内存中取出i的值,因而编译器生成的汇编代码会重新从 i 的地址处读取数据放在k中。这样看来,如果 i 是一个寄存器变量或者表示一个端口数据或者是多个线程的共享数据,就容易出错,所以说 volatile 可以保证对特殊地址的稳定访问。

所以,volatile可以忽略编译器优化,保持内存可见性。

十四、最会戴帽子的关键字-extern

extern是为了声明变量或者函数。

extern 可以置于变量或者函数前,以标示变量或者函数的定义在别的文件中,下面的代码用到的这些变量或函数是外来的,不是本文件定义的,提示编译器遇到此变量和函数时在其他模块中寻找其定义。就好比在本文件中给这些外来的变量或函数带了顶帽子,告诉本文件中所有代码,这些家伙不是本地人。

十五、struct关键字

struct是用来声明结构体类型的关键字。

struct 是个神奇的关键字,它将一些相关联的数据打包成一个整体,方便使用。在网络协议、通信控制、嵌入式系统、驱动开发等地方,我们经常要传送的不是简单的字节流(char 型数组),而是多种数据组合起来的一个整体,其表现形式是一个结构体。

经验不足的开发人员往往将所有需要传送的内容依顺序保存在 char 型数组中,通过指针偏移的方法传送网络报文等信息。这样做编程复杂,易出错,而且一旦控制方式及通信协议有所变化,程序就要进行非常细致的修改,非常容易出错。这个时候只需要一个结构体就能搞定。

十六、union关键字和enum关键字

union是联合体类型。

union关键字和struct结构较为类似。但是,union内部成员共享一个存储空间同一时间只能储存其中一个数据成员,所有的数据成员具有相同的起始地址。同时,联合体总空间大小要能整除其任意成员的空间大小。

enum是枚举类型。

枚举类型的成员都是常量。是具有相关性的一些常量,例如:

enum color
{
	RED,
	YELLOW,
	BLUE,
	WHITE,
	BLACK,
	GREEN
};

int main()
{

	enum color c = BLUE;

	return 0;
}

既然都是定义常量,那为什么不适用宏定义呢?

下面再看看枚举与#define 宏的区别:

1),#define 宏常量是在预编译阶段进行简单替换。枚举常量则是在编译的时候确定其值。

2),一般在编译器里,可以调试枚举常量,但是不能调试宏常量。

3),枚举可以一次定义大量相关的常量,而#define 宏一次只能定义一个。

十七、typedef关键字

typedef是用来对类型进行重命名的:

  1. 对一般类型进行重命名
  2. 对结构体类型进行重命名
  3. 对指针进行重命名
  4. 对复杂结构进行重命名

如果只是对类型的重命名的话,宏定义也能做到,那它们二者有什么区别呢?看下面的例子:

#define PTR_T int*
typedef int* ptr_t;

int main()
{
	ptr_t a, b;
	PTR_T c, d;

	return 0;
}

a,b,c,d四个变量前三个都是指针,而d是一个整型变量,因为宏只是起到一个替换的作用,而typedef重新定义了一个新的类型。

另外一个区别是:

#define INT32 int
typedef int int32;


int main()
{
	unsigned INT32 a;
	unsigned int32 b;   //这是不可以的
	return 0;
}

typedef是不支持上述这种类型的拓展的。

总结

数据类型关键字(12个)

char :声明字符型变量或函数
short :声明短整型变量或函数
int : 声明整型变量或函数
long :声明长整型变量或函数
signed :声明有符号类型变量或函数
unsigned :声明无符号类型变量或函数
float :声明浮点型变量或函数
double :声明双精度变量或函数
struct :声明结构体变量或函数
union :声明共用体(联合)数据类型
enum :声明枚举类型
void :声明函数无返回值或无参数,声明无类型指针

控制语句关键字(12个)

  1. 循环控制(5个)
    for :一种循环语句
    do :循环语句的循环体
    while :循环语句的循环条件
    break :跳出当前循环
    continue :结束当前循环,开始下一轮循环
  2. 条件语句(3个)
    if : 条件语句
    else :条件语句否定分支
    goto :无条件跳转语句
  3. 开关语句 (3个)
    switch :用于开关语句
    case :开关语句分支
    default :开关语句中的“其他”分支
  4. 返回语句(1个)
    return :函数返回语句(可以带参数,也看不带参数)

存储类型关键字(5个)

auto :声明自动变量,一般不使用
extern :声明变量是在其他文件中声明
register :声明寄存器变量
static :声明静态变量
typedef :用以给数据类型取别名(但是该关键字被分到存储关键字分类中,虽然看起来没什么相关性)

注意:在C语言中存储类型关键字在使用时只能存在一个,不能共存

其他关键字(3个)

const :声明只读变量
sizeof :计算数据类型长度
volatile :说明变量在程序执行中可被隐含地改变

本图文内容来源于网友网络收集整理提供,作为学习参考使用,版权属于原作者。
THE END
分享
二维码
< <上一篇
下一篇>>