C语言中有32个关键字,如下:
关键字 | 说明 |
---|---|
auto | 声明一个自动存储的变量 |
break | 跳出当前循环 |
case | 开关语句分支 |
char | 声明字符型变量或函数返回值类型 |
const | 定义常量,如果一个变量被 const 修饰,那么它的值就不能再被改变 |
continue | 结束当前循环,开始下一轮循环 |
default | 开关语句中的“其他”分支 |
do | 循环语句的循环体 |
double | 声明双精度浮点型变量或函数返回值类型 |
else | 条件语句否定分支(与 if 连用) |
enum | 声明枚举类型 |
extern | 声明变量或函数是在其它文件或本文件的其他位置定义 |
float | 声明浮点型变量或函数返回值类型 |
for | 一种循环语句 |
goto | 无条件跳转语句 |
if | 条件语句 |
int | 声明整型变量或函数返回值类型 |
long | 声明长整型变量或函数返回值类型 |
register | 声明寄存器变量(只能修饰局部变量,不能修饰全局变量) |
return | 子程序返回语句(可以带参数,也可不带参数) |
short | 声明短整型变量或函数 |
signed | 声明有符号类型变量或函数 |
sizeof | 计算数据类型或变量长度(即所占字节数) |
static | 声明储类型是静态变量,或指定函数是静态函数 |
struct | 声明结构体类型 |
switch | 用于开关语句 |
typedef | 用以给数据类型取别名 |
unsigned | 声明无符号类型变量或函数 |
union | 声明共用体类型 |
void | 声明函数无返回值或无参数,声明无类型指针 |
volatile | 使编译器对该代码不在进行优化(防止编译器过度编译) |
while | 循环语句的循环条件 |
C99和C11 新增关键字
C99关键字 | C11关键字 |
---|---|
_Bool | _Alignas |
_Complex | _Alignof |
_Imaginary | _Atomic |
inline | _Generic |
restrict | _Noreturn |
_Static_assert | |
_Thread_local |
部分关键字说明:
break和continue
1. break可以在if-else中使用直接跳出当前循环。
2. 在多层循环中,一个break语句只向外跳一层。
3. 如果您使用的是嵌套循环(即一个循环内嵌套另一个循环),break 语句会停止执行最内层的循环,然后开始执行该块之后的下一行代码。
4. continue语句的作用是跳过循环体中剩余的语句并到循环末尾而强行执行下一次循环。
5. continue语句只用在for、while、do-while等循环体中, 常与if条件语句一起使用, 用来加速循环。
const
1. const修饰的变量的不允许改变的。也可称为只读变量。
2. 变量的空间可变,但不能通过变量名来修饰变量的值。
3. 用const修饰变量时,对变量做初始化,否则后面此变量不可改。
4. 修饰函数形参,在函数实现过程中,避免修改实参的值。
const int *p;---修饰的是*p,指针指向可以改变(地址可变),p所指向的空间内容不可以改变(内容不变)。
int const *p;---修饰的是*p,指针指向可以改变(地址可变),p所指向的空间内容不可以改变(内容不变)。
int* const p;---修饰的是p;指针指向不可以改变(地址不可以改变),p所指向的空间内容可以改变(内容可变)。
const int* const p;---指针指向和p所指的空间内容都不能改变。
作用
摘自百度百科
1. 可以定义const常量,具有不可变性。 如:const int Max=100; Max++会产生错误;
2. 便于进行类型检查,使编译器对处理内容有更多了解,消除了一些隐患。 如: void f(const int i) { .........} 编译器就会知道i是一个常量,不允许修改;
3. 可以避免意义模糊的数字出现,同样可以很方便地进行参数的调整和修改。 同宏定义一样,可以做到不变则已,一变都变! 如(1)中,如果想修改Max的内容,只需要它修改成:const int Max=you want;即可!
4. 可以保护被修饰的东西,防止意外的修改,增强程序的健壮性。 如: void f(const int i) { i=10;//error! }
5. 可以节省空间,避免不必要的内存分配。
如:
#define PI 3.14159 //常量宏
const double Pi=3.14159; //此时并未将Pi放入ROM中 ......
double i=Pi; //此时为Pi分配内存,以后不再分配!
double I=PI; //编译期间进行宏替换,分配内存
double j=Pi; //没有内存分配
double J=PI; //再进行宏替换,又一次分配内存!
const定义常量从汇编的角度来看,只是给出了对应的内存地址,而不是像#define一样给出的是立即数,所以,const定义的常量在程序运行过程中只有一份拷贝,而#define定义的常量在内存中有若干份拷贝。
a.下面分别用const限定不可变的内容是什么?
1. 在前面
const int n; //int是const
const char *p; //char是const, p可变
const char* const p; //p和*p都是const
2. 在后面,与上面的声明对等
int const n; //n是const
char const * p; //*p是const, p可变
char* const p; //p是const,*p可变
char const* const p; //p和*p都是const
分析:
const只修饰其后的变量,至于const放在类型前还是类型后并没有区别。如:const int a和int const a都是修饰a为const。
注意" * "不是一种类型,如果*pType之前是某类型,那么pType是指向该类型的指针。
判断方法:指针运算符*,是从右到左,那么如:char const * p,可以理解为char const (*p),即* p为const,而p则是可变的。
3. int const * p1,p2;
分析:p2是const;(*p1)是一整体,因此(*p1)是const,但p1是可变的。int * p1,p2只代表p1是指向整型的指针,要表示p1、p2都是指针是需写成int * p1,* p2。所以无论是* const p1,p2还是const * p1,p2,里面的*都是属于p1的。
4. int const * const p1,p2;
分析:p2是const,是前一个const修饰的,*p1也被前一个const修饰,而p1被后一个const修饰。
5. int * const p1,p2;
分析:p1是const,(* const p1)是整体,所以const不修饰p2。
b.指针指向及其指向变量的值的变化
const在*的左边,则指针指向的变量的值不可直接通过指针改变(可以通过其他途径改变);在*的右边,则指针的指向不可变。简记为“左定值,右定向”。
1. 指针指向的变量的值不能变,指向可变
int x = 1;
int y = 2;
const int* px = &x;
int const* px = &x; //这两句表达式一样效果
px = &y; //正确,允许改变指向
*px = 3; //错误,不允许改变指针指向的变量的值
2. 指针指向的变量的值可以改变,指向不可变
int x = 1;
int y = 2;
int* const px = &x;
px = &y; //错误,不允许改变指针指向
*px = 3; //正确,允许改变指针指向的变量的值
3. 指针指向的变量的值不可变,指向不可变
int x = 1;
int y = 2;
const int* const px = &x;
int const* const px = &x;
px = &y; //错误,不允许改变指针指向
*px = 3; //错误,不允许改变指针指向的变量的值
补充:
在c中,对于const定义的指针,不赋初值编译不报错,
int* const px;这种定义是不允许的。(指针常量定义的时候对其进行初始化)
int const *px;这种定义是允许的。(常指针可以在定义的时候不初始化)
但是,在C++中
int* const px;和const int* const px;会报错,const int* px;不报错。
必须初始化指针的指向int* const px = &x;const int* const px=&x;
强烈建议在初始化时说明指针的指向,防止出现野指针!
static
(1)static修饰局部变量叫静态局部变量。
作用:延长局部变量的生命周期,从定义到函数结束。
(2)static修饰全局变量:全局变量只在本文件可见,其他文件不可见,不可访问。
(3)static修饰函数:函数只能在本文件被调用,不能再其他文件调用。
(4)使其私有化,只能在本文件里使用。
使用说明:
(1):当static修饰局部变量时,可以改变局部变量的生命周期。
(2):static修饰函数的作用:只想让函数在本文件被调用,不想其他文件调用。
static全局变量与普通全局变量、static局部变量和普通局部变量以及static函数与普通函数有什么区别 ?
a. 全局变量之前加上static 就构成了静态的全局变量。
全局变量本身就是静态存储方式, 静态全局变量也是静态存储方式。 两者在存储方式上并无不同。
两者的区别在于非静态全局变量的作用域是整个源程序, 当一个源程序由多个源文件组成时,非静态的全局变量在各个源文件中都是有效的。 而静态全局变量则限制了其作用域, 即只在定义该变量的源文件内有效, 在同一源程序的其它源文件中不能使用它。由于静态全局变量的作用域局限于一个源文件内,只能为该源文件内的函数公用,因此可以避免在其它源文件中引起错误。
static全局变量只初使化一次,防止在其他文件单元中被引用;
b. 把局部变量改变为静态变量后是改变了它的存储方式即改变了它的生存期。把全局变量改变为静态变量后是改变了它的作用域,限制了它的使用范围。
static局部变量只被初始化一次,下一次依据上一次结果值;
c. static函数与普通函数作用域不同,仅在本文件。只在当前源文件中使用的函数应该说明为内部函数(static修饰的函数),内部函数应该在当前源文件中说明和定义。对于可在当前源文件以外使用的函数,应该在一个头文件中说明,要使用这些函数的源文件要包含这个头文件.
static函数在内存中只有一份,普通函数在每个被调用中维持一份拷贝。
函数分为内部函数和外部函数
当一个源程序由多个源文件组成时,根据函数能否被其它源文件中的函数调用,将函数分为内部函数和外部函数。
a.内部函数(又称静态函数)
如果在一个源文件中定义的函数,只能被本文件中的函数调用,而不能被同一程序其它文件中的函数调用,这种函数称为内部函数。
定义一个内部函数,只需在函数类型前再加一个“static”关键字即可,如:static+函数类型+函数名(函数参数表){……}
使用内部函数的好处是:不同的人编写不同的函数时,不用担心自己定义的函数,是否会与其它文件中的函数同名,因为同名也没有关系。
b.外部函数
外部函数的定义:在定义函数时,如果没有加关键字“static”,或冠以关键字“extern”,表示此函数是外部函数。如:[extern] 函数类型 函数名(函数参数表){……}
调用外部函数时,需要对其进行说明:
[extern] 函数类型 函数名(参数类型表)[,函数名2(参数类型表2)……];
作用
a. 隐藏
当同时编译多个文件时,所有未加 static 前缀的全局变量和函数都具有全局可见性。
/***************a.c**************/
char a = 'A'; //global variable
void msg()
{
printf("Hello\n");
}
/***************main.c**************/
int main(void)
{
extern char a; //extern variable must be declared before use
printf("%c ", a);
(void)msg();
return 0;
}
程序的运行结果是
A Hello
你可能会问:为什么在 a.c 中定义的全局变量 a 和函数 msg 能在 main.c 中使用?前面说过,所有未加 static 前缀的全局变量和函数都具有全局可见性,其它的源文件也能访问。
此例中,a 是全局变量,msg 是函数,并且都没有加 static 前缀,因此对于另外的源文件 main.c 是可见的。
如果加了 static,就会对其它源文件隐藏。例如在 a 和 msg 的定义前加上 static,main.c 就看不到它们了。利用这一特性可以在不同的文件中定义同名函数和同名变量,而不必担心命名冲突。static 可以用作函数和变量的前缀,对于函数来讲,static 的作用仅限于隐藏,而对于变量,static 还有下面两个作用。
b.保持变量内容的持久
存储在静态数据区的变量会在程序刚开始运行时就完成初始化,也是唯一的一次初始化。共有两种变量存储在静态存储区:全局变量和 static 变量,只不过和全局变量比起来,static 可以控制变量的可见范围,说到底 static 还是用来隐藏的
#include <stdio.h>
int fun(void){
static int count = 10; // 事实上此赋值语句从来没有执行过
return count--;
}
int count = 1;
int main(void)
{
printf("global\t\tlocal static\n");
for(; count <= 10; ++count)
printf("%d\t\t%d\n", count, fun());
return 0;
}
程序的运行结果是:
global local static
1 10
2 9
3 8
4 7
5 6
6 5
7 4
8 3
9 2
10 1
c. 是默认初始化为 0
全局变量也具备这一属性,因为全局变量也存储在静态数据区。在静态数据区,内存中所有的字节默认值都是 0x00,某些时候这一特点可以减少程序员的工作量。
比如初始化一个稀疏矩阵,我们可以一个一个地把所有元素都置 0,然后把不是 0 的几个元素赋值。如果定义成静态的,就省去了一开始置 0 的操作。
再比如要把一个字符数组当字符串来用,但又觉得每次在字符数组末尾加 \0 太麻烦。如果把字符串定义成静态的,就省去了这个麻烦,因为那里本来就是 \0 。
#include <stdio.h>
int a;
int main(void)
{
int i;
static char str[10];
printf("integer: %d; string: (begin)%s(end)", a, str);
return 0;
}
程序的运行结果如下:
integer: 0; string: (begin)(end)
struct
结构体定义
1.先定义结构体类型名,再定义结构体变量
struct test{
int a;
char ch;
int num[10];
};
struct test hello;
2.定义结构体类型名时,定义结构体变量
struct test{
int a;
char ch;
int num[10];
}hello;
3.省略结构体类型名,定义结构体变量
struct
{
int a;
char ch;
int num[10];
}hello;
注:在省略类型名时,变量必须同时定义。
4.typedef重命名
typedef struct test
{
int a;
char ch;
int num[10];
}TEST_TypeDef;
TEST_TypeDef hello;
结构体引用格式:
变量用" . “,指针用” -> "
一般情况下用“.”,只需要声明一个结构体。格式:结构体类型名+结构体名。然后结构体名加“.”加域名就可以引用域 了。因为自动分配了结构体的内存。如同 int a;一样。
而用“->”,则要声明一个结构体的指针,还要手动开辟一个该结构体的内存,然后把返回的指针给声明的结构体指针,才能用“->”正确引用。
否则内存中只分配了指针的内存,没有分配结构体的内存,导致想要的结构体实际上是不存在。这时候用“->”引用自然出错了,因为没有结构体,自然没有结构体的域了。
typedef struct
{
char num[5];
char ch;
int a;
}TEST_TypeDef;
TEST_TypeDef nest; //声明一个结构体变量
TEST_TypeDef *pnest; //声明一个指向结构体的指针
//访问数据操作如下:
nest.a = 5; //结构体变量通过点运算符( . )访问
pnest->a = 5; //指向结构体的指针通过箭头运算符( -> )访问
typedef
typedef vs #define
#define 是 C 指令,用于为各种数据类型定义别名,与 typedef 类似,但是它们有以下几点不同:
typedef 仅限于为类型定义符号名称,#define 不仅可以为类型定义别名,也能为数值定义别名,比如可以定义 1 为 ONE。
typedef 是由编译器执行解释的,#define 语句是由预编译器进行处理的。
参考这篇博客:https://blog.csdn.net/ameyume/article/details/6326278
volatile
volatile是一种类型修饰符,用它声明的类型变量表示可以被某些编译器未知的因素更改,比如:操作系统、硬件或者其它线程等。
遇到这个关键字声明的变量,编译器对访问该变量的代码就不再进行优化,从而可以提供对特殊地址的稳定访问。
声明时语法:int volatile vInt; 当要求使用 volatile 声明的变量的值的时候,系统总是重新从它所在的内存读取数据,即使它前面的指令刚刚从该处读取过数据。而且读取的数据立刻被保存。
volatile int i=10;
int a = i;
...
// 其他代码,并未明确告诉编译器,对 i 进行过操作
int b = i;
volatile 指出 i 是随时可能发生变化的,每次使用它的时候必须从 i的地址中读取,因而编译器生成的汇编代码会重新从i的地址读取数据放在 b 中。
而优化做法是,由于编译器发现两次从 i读数据的代码之间的代码没有对 i 进行过操作,它会自动把上次读的数据放在 b 中。而不是重新从 i 里面读。
这样以来,如果 i是一个寄存器变量或者表示一个端口数据就容易出错,所以说 volatile 可以保证对特殊地址的稳定访问。
注意,在 VC 6 中,一般调试模式没有进行代码优化,所以这个关键字的作用看不出来。下面通过插入汇编代码,测试有无 volatile 关键字,对程序最终代码的影响,输入下面的代码:
#include <stdio.h>
void main()
{
int i = 10;
int a = i;
printf("i = %d", a);
// 下面汇编语句的作用就是改变内存中 i 的值
// 但是又不让编译器知道
__asm {
mov dword ptr [ebp-4], 20h
}
int b = i;
printf("i = %d", b);
}
在 Debug 版本模式运行程序,输出结果:
i = 10
i = 32
在 Release 版本模式运行程序,输出结果:
i = 10
i = 10
输出的结果明显表明,Release 模式下,编译器对代码进行了优化,第二次没有输出正确的 i 值。下面,我们把 i 的声明加上 volatile 关键字,看看有什么变化:
#include <stdio.h>
void main()
{
volatile int i = 10;
int a = i;
printf("i = %d", a);
__asm {
mov dword ptr [ebp-4], 20h
}
int b = i;
printf("i = %d", b);
}
分别在 Debug 和 Release 版本运行程序,输出都是:
i = 10
i = 32
这说明这个 volatile 关键字发挥了它的作用。其实不只是内嵌汇编操纵栈"这种方式属于编译无法识别的变量改变,
另外更多的可能是多线程并发访问共享变量时,一个线程改变了变量的值,怎样让改变后的值对其它线程 visible。一般说来,volatile用在如下的几个地方:
1) 中断服务程序中修改的供其它程序检测的变量需要加 volatile;
2) 多任务环境下各任务间共享的标志应该加 volatile;
3) 存储器映射的硬件寄存器通常也要加 volatile 说明,因为每次对它的读写都可能由不同意义;
volatile 指针
和 const 修饰词类似,const有常量指针和指针常量的说法,volatile 也有相应的概念:
修饰由指针指向的对象、数据是 const 或 volatile 的:
const char* cpch;
volatile char* vpch;
指针自身的值——一个代表地址的整数变量,是 const 或 volatile 的:
char* const pchc;
char* volatile pchv;
注意:
(1) 可以把一个非volatile int赋给volatile int,但是不能把非volatile对象赋给一个volatile对象。
(2) 除了基本类型外,对用户定义类型也可以用volatile类型进行修饰。
(3) C++中一个有volatile标识符的类只能访问它接口的子集,一个由类的实现者控制的子集。
用户只能用const_cast来获得对类型接口的完全访问。此外,volatile向const一样会从类传递到它的成员。
多线程下的volatile
有些变量是用 volatile 关键字声明的。当两个线程都要用到某一个变量且该变量的值会被改变时,应该用 volatile 声明,该关键字的作用是防止优化编译器把变量从内存装入 CPU 寄存器中。如果变量被装入寄存器,那么两个线程有可能一个使用内存中的变量,一个使用寄存器中的变量,这会造成程序的错误执行。
volatile 的意思是让编译器每次操作该变量时一定要从内存中真正取出,而不是使用已经存在寄存器中的值,如下:
volatile BOOL bStop = FALSE;
(1) 在一个线程中:
while( !bStop ) { ... }
bStop = FALSE;
return;
(2) 在另外一个线程中,要终止上面的线程循环:
bStop = TRUE;
while( bStop ); //等待上面的线程终止,如果bStop不使用volatile申明,那么这个循环将是一个死循环,因为bStop已经读取到了寄存器中,寄存器中bStop的值永远不会变成FALSE,加上volatile,程序在执行时,每次均从内存中读出bStop的值,就不会死循环了
这个关键字是用来设定某个对象的存储位置在内存中,而不是寄存器中。
因为一般的对象编译器可能会将其的拷贝放在寄存器中用以加快指令的执行速度,例如下段代码中:
...
int nMyCounter = 0;
for(; nMyCounter<100;nMyCounter++)
{
...
}
...
在此段代码中,nMyCounter 的拷贝可能存放到某个寄存器中(循环中,对 nMyCounter 的测试及操作总是对此寄存器中的值进行),但是另外又有段代码执行了这样的操作:nMyCounter -= 1; 这个操作中,对 nMyCounter 的改变是对内存中的 nMyCounter 进行操作,于是出现了这样一个现象:nMyCounter 的改变不同步。
参考:
https://blog.csdn.net/qq_37858386/article/details/79064900
https://www.runoob.com/w3cnote/c-static-effect
https://blog.csdn.net/faihung/article/details/79190039