栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > C/C++/C#

【C语言重点难点精讲】关键字精讲

C/C++/C# 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

【C语言重点难点精讲】关键字精讲

必读:

  • C语言关键字是一个非常重要的话题,因为它能在相当的程度上将C语言的核心内容串联起来,起到一种提纲挈领的效果
  • 下面的内容重点提及的是相应关键字特别值得注意的地方,这些地方是我们经常忽略的,而且考试也会经常涉及到
  • 讲解这些关键字时默认大家都有C语言的基础,因此不会从0开始谈起

文章目录
  • 一:auto关键字
  • 二:register关键字
    • (1)存储器分级
    • (2)register修饰变量
  • 三:static关键字
    • (1)修饰全局变量和函数
    • (2)修饰局部变量
  • 四:sizeof关键字
  • 五:signed、unsigned关键字
  • 六:if、else
    • (1)关于C语言中bool类型
    • (2)float与“零值”的比较
    • (3)if和else的匹配问题
  • 七:switch-case组合
  • 八:do 、while 、for关键字
  • 九:goto关键字
  • 十:void关键字
  • 十一:return关键字
  • 十二:const关键字
  • 十三:volatile关键字
  • 十四:extern关键字
  • 十五:struct关键字
  • 十六:Union关键字
  • 十七:enum关键字
  • 十八:typedef关键字
  • 总结
    • (1)关键字分类

一般来讲,C语言一共有32个关键字(C90标准),当然C99后又新增了5个关键字,不过我们还是重点讨论这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开关语句分支
for一种循环语句
do循环语句的循环体
while循环语句的循环条件
goto无条件跳转语句
continue结束当前循环,开始下一轮循环
break跳出当前循环
default开关语句中的“其它”分支
sizeof计算数据类型长度
return子程序返回语句,循环条件
一:auto关键字

一般来说,在代码块中定义的变量(也即局部变量),默认都是auto修饰的,不过会省略。但是一定要注意:不是说默认的所有变量都是auto的,它只是一般用来修饰局部变量

当然在C语言中,我们已经不再使用auto了,或者称其为过时了,但是在C++中却赋予了auto新的功能,它变得更加强大了。有兴趣请点击2-6:C++快速入门之内联函数,auto关键字,C++11基于范围的for循环和nullptr

二:register关键字

register意味寄存器

(1)存储器分级

这个概念我们在计算机组成原理中讲得已经非常详细了,请点击:(计算机组成原理)第三章存储系统-第一节:存储器分类、多级存储系统和存储器性能指标

(2)register修饰变量

可以看出,如果将变量放到寄存器中,那么效率就会提高。可以用register修饰的变量有以下几种

  • 局部的(全局变量会导致CPU寄存器长时间被占用)
  • 不会被写入的(写入的话就需要被写回内存,要是这样的话register就没有意义的)
  • 高频需要被读取的

如果要使用,不要大量使用,因为寄存器的数量有限。

另外还需要注意的一点是:被register修饰的变量,是不能取地址的,因为它已经放在了寄存器中,地址会涉及到内存,但是可以被写入
当然这个register关键字现在也基本不会用了,因为如今的编译器优化已经很智能了,不需要你自己手动优化

三:static关键字 (1)修饰全局变量和函数

我们知道全局变量(加入关键字extern声明)和函数都可以跨文件使用的

但是有一些应用场景中,我们不想让全局变量或函数跨文件访问应该怎么办呢?那么就可以使用static关键字

static int g_value=100;//修饰staic后全局变量将不能跨文件使用


可以看出被static修饰的全局变量是不能被外部其他文件直接访问的,而只能在本文件内使用

  • 需要注意这里说的是直接访问,那意味着可以间接访问,比如通过函数的方式实现

同样,被static修饰的函数只能在本文件内访问,而不能在外部其它文件中直接访问

  • 还是需要注意,这里是不能直接访问,并不是不能访问,比如可以通过函数嵌套的方式

static这种功能本质为了封装,因为我们可以把一些不需要或者不想要暴露的细节保护起来了,只提供一个功能函数个,该函数在内部调用它们即可,这样的话代码安全性也比较高

(2)修饰局部变量

我们知道全局变量仅在当前代码块内有效,代码块结束之后局部变量会自动释放空间,因此下面代码的结果就会是这样

如果使用static修饰局部变量,会更改其生命周期,但其作用域不变,如下当用static修饰后,变量i地址不变,且结果累加

static为什么可以更改局部变量的生命周期呢?因为被static修饰的变量会将其从栈区移动到数据段,当然这就涉及到了C/C++地址空间的问题了

查看实际地址

#include 
#include 

int gobal_val=100;//全局变量已经初始化
int gobal_unval;//全局变量未初始化
int main(int argc,char* argv[],char* env[])
{
	printf("main函数处于代码段,地址为:%p,十进制为:%dn",main,main);
	printf("n");
	printf("全局变量gobal_val,地址为:%p,十进制为:%dn",&gobal_val,&gobal_val);
	printf("n");
	printf("全局变量未初始化gobal_unval,地址为:%p,十进制为:%dn",&gobal_unval,&gobal_unval);
	printf("n");
	
	char* mem=(char*)malloc(10);
	
	printf("mem开辟的堆空间,mem是堆的起始地址,是%p,十进制为:%dn",mem,mem);
	printf("n");	
	printf("mem是指针变量,指针变量在栈上开采,其地址为%p,十进制为:%dn",&mem,&mem);
	printf("n");	
	printf("命令行参数起始地址:%p,十进制为:%dn",argv[0],argv[0]);
	printf("n");
	printf("命令行参数结束地址:%p,十进制为:%dn",argv[argc-1],argv[argc-1]);
	printf("n");
	printf("第一个环境变量的地址:%p,十进制为:%dn",env[0],env[0]);
	printf("n");
}

四:sizeof关键字

sizeof用于确定一种类型对应在开辟空间的时候的大小,注意它是关键字而不是函数

它的基本用法就是下面这样,这我就不再多说了(注意Windows32位平台)

int main()
{
	cout <<"char:" < 


特别注意,sizeof求一种类型大小的写法共有三种,特别第三种很多人认为是错误的,而考试就爱给你整这些犄角旮旯的东西

int main()
{
	int a = 10;
	第一种:cout << sizeof(a) << endl;
	第二种:cout << sizeof(int) << endl;
	第三种:cout << sizeof a << endl;//这种写法其实也证明了sizeof不是函数
	cout << sizeof int << endl;//注意这种写法是错误的
}

五:signed、unsigned关键字

这一部分需要涉及数据存储及原码反码等基础概念,请参照以下章节

  • (计算机组成原理)第二章数据的表示和运算-第二节1:定点数的表示(原码、反码、补码和移码)
  • (计算机组成原理)第二章数据的表示和运算-第二节2:原码、反码、补码和移码的作用

第一点: 需要深刻理解signed和unsigned只是对数据的一种解读方式,其中signed会把首位数据解读为符号位,符号位用于标识其正负,unsigned的首位也算作数据位,也就是说类型决定了其读写的时候的解释方式
因此像下面的这样一句代码,看似不合适,但是它是没有问题的,因为存储时对于变量a它只关心我所开辟的空间上的二进制数据放进了没有,并不关心你之前是怎么样的

unsigned int b=-10;
-10的原码:1000 0000 0000 0000 0000 0000 0000 1010
-10的反码:1111 1111 1111 1111 1111 1111 1111 0101
-10的补码:1111 1111 1111 1111 1111 1111 1111 0110

也就是说b里面的存储的内容会按照不同的解释方式而变化

第二点: signed和unsigned也是相关C语言考试的重点,下面代码可以帮助你很好的理解

#include 
#include 
#include 


int main()
{
	unsigned int i;
	for (i = 9; i >= 0; i--)
	{
		printf("%un", i);
		Sleep(100);
	}

}

由于变量i是无符号整形,因此在与0比较的时候,不会小于0,所以会死循环,并且打印时从9开始减小到0,然后接着是42亿多,然后依次减小,最后再到0

第三点:使用unsigned时初始化变量时,建议带上u,也即

unsigned int b=10u;
六:if、else

if和else如果简单点学其实也很简单,主要就是以下内容

  1. 0为表示假,非0表示真
  2. if语句执行时,必然是先执行“()”里面的表达式或者是函数,得到真假后,然后进行判定,再进行分支功能
(1)关于C语言中bool类型

在C99之前C语言是没有bool类型的,在C00之后引入了_Bool类型,它处于头文件stdbool.h中

#include 
#include 
#include 


int main()
{
	bool ret = false;
	ret = true;
	printf("%dn", sizeof(ret));//在vs中为1
	return 0;
}

源码中显示就是一个宏定义

//
// stdbool.h
//
//      Copyright (c) Microsoft Corporation. All rights reserved.
//
// The C Standard Library  header.
//
#ifndef _STDBOOL
#define _STDBOOL

#define __bool_true_false_are_defined	1

#ifndef __cplusplus

#define bool	_Bool
#define false	0
#define true	1

#endif 

#endif 




(2)float与“零值”的比较

使用if进行浮点数比较时,下面的代码正确吗?按照道理1.0-0.9=0.1,应该是正确的

int main()
{
	double x = 1.0;
	double y = 0.1;
	if ((x - 0.9) == y)
	{
		printf("correctn");
	}
	else
	{
		printf("wrongn");
	}
	return 0;
}

但实际结果却是:

为什么会这样呢,其实这涉及到的的浮点数如何在计算机中存储的问题,详细细节请移步:

  • (计算机组成原理)第二章数据的表示和运算-第三节1:浮点数的表示

其实如果你打印出来后,你会发现两者根本不相等,精度丢失


既然浮点数比较时不能直接使用“==”,那么应该怎么办呢?

比较时,有点像高等数学中的取极限, δ delta δ可以被视为一个误差范围,这个 δ delta δ需要你自己定义,当两者的绝对值之差小于该范围时,C语言就认定他们相等,否则不相等

int main()
{
	double x = 1.0;
	double y = 0.1;
	if (fabs((1.0 - 0.9)-0.1) < CMP)
	{
		printf("correctn");
	}
	else
	{
		printf("wrongn");
	}

	return 0;
}

这里的 δ delta δ其实C语言已经帮我们定义好了,处在float.h头文件之下

#define DBL_EPSILON 2.2204460492503131e-016 
#define FLT_EPSILON 1.192092896e-07F 

回归到主题,如果0被定义为了浮点数,我们要判断某个数是否是0的话可以这样写

int main()
{
	double x = 0;
	if (fabs(x) < DBL_EPSILON)//注意不要写成<=
	{
		printf("x是0n");
	}
	else
	{
		printf("x不是0n");

	}
	return 0;
}

(3)if和else的匹配问题

这是一个老生常谈的话题。下面代码看似会输出“2”,但实际什么都不会输出

int main()
{
	int x = 0;
	int y = 1;
	if (10 == x)
		if (11 == y)
			printf("1n");
		else
			printf("2n");

	return 0;

}

这属于代码风格问题,else匹配采用的是就近原则

七:switch-case组合

第一: switch case的基本语法结构

switch(整型变量/常量/整型表达式)//注意只能这三种
{
	case var1://判断在这里
		break;
	case var2:
		break;
	case var3:
		break;
	default:
		break;
}

其中case完成的判断功能,break完成的是分支功能,所以如果忘记写break,就会导致击穿现象

第二: 注意一个语法细节,就是case里面如果要定义变量的话,必须加花括号

int main()
{
	int num = 0;
	scanf("%d", &num);
	switch (num)
	{
	case 1:
	{
		int a = 1;//注意花括号
		printf("firstn");
		break;
	}
	case 2:
		printf("secondn");
		break;
	case 3:
		printf("thirdn");
		break;
	default:
		printf("othern");
		break;
	}

}

第三: 多条件匹配时可以这样写

int main()
{
	int num = 0;
	scanf("%d", &num);
	switch (num)
	{
	case 1:
	case 2:
	case 3:
		printf("firstn");
		break;
	case 4:
	case 5:
		printf("secondn");
		break;
	default:
		printf("othern");
		break;
	}
}

第四: 注意default可以放在任意位置

第五: switch中可以使用return语句,但不建议使用

八:do 、while 、for关键字

第一: 这三种循环基本语法如下

//while
条件初始化
while(条件判定){
	//业务更新
	条件更新
}

//for
for(条件初始化; 条件判定; 条件更新){
	//业务代码
}

//do while
条件初始化
do{
	条件更新
  }while(条件判定)

第二: 三种循环对应的死循环写法如下

while(1){
}

for(;;){
}

do{
}while(1);

第三: break是跳出该循环,continue是结束一次循环

int main()
{
	while (1)
	{
		int c = getchar();
		if (c == '#')
		{
			break;//表示接受到“#”就结束
		}
		putchar(c);
	}
}

int main()
{
	while (1)
	{
		int c = getchar();
		if (c == '#')
		{
			continue;//表示接受到“#”略过
		}
		putchar(c);
	}
}


这里需要注意for循环的continue,经常爱考察。for循环在continue时是跳到循环更新处

int main()
{
	int i = 0;
	for (; i < 10; i++)
	{
		printf("continue before:%dn", i);
		if (i == 5) {
			printf("continue语句之前n");
			continue;
			printf("continue语句之后n");

		}
		printf("continue after:%dn", i);
	}
}


第四: for循环区间建议是前闭后开

for(int i=0;i<10;i++)
{
	//循环10次
}
for(int i=6;i<10;i++)
{
	//循环10-6=4次
}


九: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");
	return 0;
}

十:void关键字

第一: void是不能用来定义变量的。因为定义变量的本质就是开辟内存空间,而void作为空类型,理论上将是不应该开辟空间的,即使开辟了空间,也仅仅作为一个占位符来看待,所以这种行为直接就会被编译器禁止

第二: 首先先说明一点,在C语言中函数是可以不带返回值的,返回类型为整型

还在有些场景中我们时不需要函数的返回值的,如果采用上面的那种方式书写,很容易产生阅读上的歧义,因此如果函数不想让其返回,可以用void,这里一定要将其理解为一种占位符,它是告知用户和编译器的

第二: 在如下情形中,编译器是不会报错的,因此会有很大的安全隐患

而如果限制void后,编译器将会报警。因此void可以充当函数的形参列表,用于告知编译器和用户该函数不需要传入参数

第四: void的确不可以定义变量,但是void*可以,因为指针变量的大小是明确的(Windows32位下为4个字节大小)

void* p=nullptr;

第五: void*可以被任何类型的指针接受,void*也可以接受任意指针类型

int main()
{
	void* p = NULL;
	int* x = NULL;
	double* y = NULL;
	p = x;//void*接受int*
	p = y;//void*接受double*
}
  • 尤其注意 void*也可以接受任意指针类型,这一点通常用作一些通用接口的设计

第六: 我们知道,普通类型的指针可以进行位运算

int* p=NULL;
p++;
p--;

而对于void*呢?它要是视平台而定,一般VS下不可以,Linux下可以(Linux认为void是1)

十一:return关键字

第一:return不可以返回指向“栈内存”,因为在函数体结束时会被自动销毁

因此下面的语句会出现乱码

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

第二: 函数的返回值,通过寄存器的方式,返回给函数的调用方(注意区别上面,上面不能那样做,因为那是指向栈的指针)

int GetData()
{
	int x = 0x11223344;
	printf("runningn");
	return x;
}

int main()
{
	int y = GetData();
	printf("return value:%xn", y);
	return 0;
}

return x对应的汇编代码为:

十二:const关键字

第一: const修饰的变量不可以直接被修改

const int a=10;
a=20;//错误

但间接可以修改

int main()
{
	const int a = 10;
	int* p = &a;
	printf("change before:%dn", a);
	*p = 20;
	printf("change after:%dn", a);
	return 0;
}

那么既然这样其意义何在呢?其实const修饰变量主要有下面两个目的

  1. 让编译器进行直接修改式检查
  2. 告诉其他人这个变量不要改动,属于“自描述”含义

真正意义上的不可修改如C语言中的常量字符串

int main()
{
	char* str = "hello worldn";//常量字符串
	*str ='E';
	return 0;
}

第二: const int i 和int const i是等价的

第三: const修饰的变量同样不能作为数组定义的一部分(标准C不可以,但是在Linux可以)

int main()
{
	const int n = 100;
	int arr[n];//错误
	return 0;
}

第四: const在定义时必须初始化

第五: 建立只读数组可以这样写

int const a[5]={1,2,3,4,5};
或
const int a[5]={1,2,3,4,5};


第六 :const放在谁后面就修饰谁,因此它与指针的关系如下

①:const int* i 与int const* i等价
其中i是指针,const修饰了int,表示指针可以变化,但是指针指向内容不能被修改

②:int* const i

const修饰的是指针,所以指针不可变,但是指向的内容可变

③:const int* const i=&a

表示指针不可以变,指向的内容也不可以变

第七: const 也可以用来修饰函数参数,表明不可更改

void show(const int* _p)//防止指针指向内容被修改
{
	printf("value:%dn", *_p);
	*_p = 20;//非法操作
}

int main()
{
	int a = 10;
	int* p = &a;
	show(p);
}
十三:volatile关键字

有关volatile关键字的作用在下面这篇文章中欧诺个有详细介绍,请移步

  • Linux系统编程34:进程信号之可重入函数,volatile关键字的作用和SIGHLD

volatile关键字的作用:volatile将保持内存的关键字,一个变量一旦被volatile修饰,那么系统总是会从内存中读取数据,而不是从寄存器

需要注意const和volatile的区别,两者并不矛盾

  • const要求你不要进行写入
  • volatile意思是你读的时候每次要从内存读
十四:extern关键字

extern关键字这里就多说了,非常简单

十五:struct关键字

第一: struct基本介绍

定义

初始化(不能初始化后整体赋值)


成员访问

结构体传参

第二: 在Linux中空结构体的大小为0

第三: 柔性数组

我们知道C语言中是不能有这样的操作的,就是用变量对数组进行初始化

int main()
{
	int i=0;
	scanf("%d",&i);
	int arr[i];
}

在C语言中如果要完成动态数组,可以借助柔性数组。使用柔性数组时,我们采用结构体的方式,将一个数组作为结构体成员放置于其中,但注意该数组不初始化,什么都不写

在上述结构体中,有两个结构体变量,数组似乎不占空间,但其实不然。实则,该结构体将其所占空间划分为两部分,一部分就是那个整形,一部分用于动态开辟,以此满足数组的动态变化
既然是柔性,那就可以修改,使用realloc修改

十六:Union关键字

第一: Union是什么
联合也是一种特殊的自定义类型 这种类型定义的变量也包含一系列的成员,特征是这些成员公用同一块空间,联合体内所有成员的起始地址都是一样的,每个成员都认为它是联合体的第一个成员

第二: 根据内存地址分布,如下,b永远定义在相对于a的低地址处

union Un
{
	int a;
	int b;
};

根据这一性质我们可以利用联合体来判断机器是大端机还是小端机,如下

union Un
{
	int a;
	char b;
};
int main()
{
	union Un u;
	u.a = 1;
	if (u.b == 1){
		printf("小端机n");
	}
	else {
		printf("大端机n");
	}
	return 0;
}

这是因为

十七:enum关键字

enum用于枚举一堆常量,就像Excel中的数据有效性,它规定了整个数据它只能是这几个取值,比如说男性它只有男或女

定义

enum color是枚举类型,括号中的内容是枚举类型的可能取值,也叫做枚举常量。这些可能取值实际上是有值的,默认是从0开始的

当然是可以修改的

枚举的这样的写法其实和宏的写法在代码的逻辑上是相似的

十八:typedef关键字

第一: typedef的作用就是为类型重新命名

typedef unsigned int u_int;

int main()
{
	u_int a = 10;
	return 0;
}


typedef 经常会在结构体重命名里

typedef struct stu
{
	int a;
	int b;
}Student;

int main()
{
	Student student1;
}

第二: 大家一定要对typedef理解到位,如下

int main()
{
	int* a, b;
	//a是指针类型
	//b是整形
}

typedef从某种方面可以理解一种全新的类型,因此下面的*就不存在和谁结合的问题了

typedef int* int_p;

int main()
{
	int_p a, b;
	//a是指针类型
	//b也是指针类型
}

而对于#define而言它就是一种文本替换了,因此

#define int_p int*

int main()
{
	int_p a, b;
	//a是指针类型
	//b是整形
}

第三:使用typedef定义后的新类型,不能配合其他关键字使用

#define INT_DE int
typedef int INT_TY;

int main()
{
	unsigned INT_DE a;//正确
	unsigned INT_TY b;//错误
}

总结 (1)关键字分类

数据类型关键字

  • char
  • short
  • int
  • long
  • signed
  • unsigned
  • float
  • double
  • struct
  • union
  • enum
  • void

控制语句关键字
1:循环控制

  • for
  • do
  • while
  • break
  • continue

2:条件语句

  • if
  • else
  • goto

3:开关语句

  • switch
  • case
  • default

4:返回语句

  • return

存储类型关键字

  • auto
  • extern
  • register
  • static
  • typedef

这里需要补充一点:使用typedef时不能同时出现多个存储关键字

typedef static int//错误
typedef register int//错误

其他关键字

  • const
  • sizeof
  • volatile
转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/316025.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号