C语言之关键字
C语言随读笔记 2013.11.09 小雨
一. C语言的标示符与关键字
关键字:编程语言保留的特殊标示符,有时又称为保留字;
标示符:表示源程序中某个对象的名字,最长255个字符;
关键字分类
32个关键字每个都有不同的意义,大体上根据其意义可以分为以下几类(下划线表示不同分类中有交集):
1) 非常见:auto、register、volatile、goto
2) 存储相关:const、extern、register、volatile、static、auto、signed、unsigned
3) 数据类型:char、short、int、float、long、double、struct、union、enum、void
4) 逻辑控制:if、else、for、while、do、break、continue、return、default、switch、case、goto
5) 特殊用途:sizeof、typedef
1. 隐形刺客:auto (auto--用以说明局部变量,默认值为此)
描述:auto关键字在我们写的代码里几乎看不到,但是它又无处不在,它是如此的重要,又是如此的与世无争,
默默的履行着自己的义务,却又隐姓埋名。
作用:C程序是面向过程的,在C代码中会出现大量的函数模块,每个函数都有其生命周期(也称作用域),在
函数生命周期中声明的变量通常叫做局部变量,也叫自动变量。例如:
例如:int fun()
{
int a = 10; //auto int a = 10;
//do someting
return 0;
}
说明:整型变量a在fun函数内声明,其作用域为fun函数内,出来fun函数,不能被引用,a变量为自动变量。也就
是说编译器会有int a = 10之前会加上auto的关键字。
2. 闪电飞刀:register
描述:register就和它的名字一样,很少出现在代码世界中,因为敢称为闪电飞刀的变量,通常只会在一些特定场
合才能出现。它是如此的快,以致于CPU都对其刮目相看,但是它有一个致命的缺点,它的速度“看心情”而定,
不是每一次都能让人满意。
作用:如果一个变量被register来修辞,就意味着,该变量会作为一个寄存器变量,让该变量的访问速度达到最快。
比如:一个程序逻辑中有一个很大的循环,循环中有几个变量要频繁进行操作,这些变量可以声明为register类型。
例如:#ifdef NOSTRUCTASSIGN
memcpy (d, s, l)
{
register char *d;
register char *s;
register int i;
while (i--)
*d++ = *s++;
}
#endif
说明:需要注意的地方
1) register变量必须是能被CPU寄存器所接受的类型,这通常意味着register变量必须是一个单个的值,并且其长度应小于
或等于整型的长度。但是,有些机器的寄存器也能存放浮点数
2) register变量可能不存放在内存中,所以不能用取址符运算符“ & ”
3) 只有局部变量和形参可以作为register变量,全局变量不行
4) 静态变量不能定义为register
3. volatile 该变量在程序执行中可被隐含地改变
volatile提醒编译器它后面所定义的变量随时都有可能改变,因此编译后的程序每次需要存储或读取这个变量的时候,
都会直接从变量地址中读取数据。如果没有volatile关键字,则编译器可能优化读取和存储,可能暂时使用寄存器中的值,
如果这个变量由别的程序更新了的话,将出现不一致的现象。下面举例说明。在DSP开发中,经常需要等待某个事件的触发,所
以经常会写出这样的程序:
short flag;
void test()
{
do1();
while(flag==0);
do2();
}
这段程序等待内存变量flag的值变为1(怀疑此处是0,有点疑问,)之后才运行do2()。变量flag的值由别的程序更改,这个程
序可能是某个硬件中断服务程序。例如:如果某个按钮按下的话,就会对DSP产生中断,在按键中断程序中修改flag为1,这样上
面的程序就能够得以继续运行。但是,编译器并不知道flag的值会被别的程序修改,因此在它进行优化的时候,可能会把flag的
值先读入某个寄存器,然后等待那个寄存器变为1。如果不幸进行了这样的优化,那么while循环就变成了死循环,因为寄存器的
内容不可能被中断服务程序修改。为了让程序每次都读取真正flag变量的值,就需要定义为如下形式:
volatile short flag;
需要注意的是,没有volatile也可能能正常运行,但是可能修改了编译器的优化级别之后就又不能正常运行了。因此经常会出
现debug版本正常,但是release版本却不能正常的问题。所以为了安全起见,只要是等待别的程序修改某个变量的话,就加上volatile关键字。
4. goto 构成goto转移结构
描述:在所有的编程语言里,恐怕没有哪个关键字可以和goto相比,它可以指哪打哪,完全不用去顾忌编码规则,在代码世界里游刃有余,
混得代码海洋里的浪里白条美誉,也正是由于其放荡不羁的特性,被编码规则牢牢划死在编程准则不允许之首。
作用:正如其名,go to everywhere,它可以在代码逻辑中任意穿梭,只要给我定义一个靶心(标签),我就可以打破逻辑直接到达,如下面示例。
例子:01.if(网卡未初始化){
02. // 初始化网卡
03.if(初始化出错)
04. goto error;
05.}
06.
07.char * buf = (char*)malloc(20);
08.if(接受网卡数据){
09. if(数据较验错误)
10. goto checkNumError;
11. //写入到buf中
12.}else{
13. goto timeupError;
14.}
15.
16.checkNumError:
17. // 处理较验出错
18. goto freeMem;
19.timeupError:
20. // 处理超时出错
21.freeMem:
22. free(buf);
23.error:
24. // 其它善后处理
通过上面的代码可以看出,使用goto关键字,程序逻辑非常的自由,网卡初始化出错时,直接跳到23行执行,第9行,数据较验出错,
直接跳到16行,然后处理完后跳到21行,执行buf的内存释放。虽然可以看到代码逻辑很自由,但是还是会发现有点混乱,如果程序员没有
足够的代码经验,很容易出现逻辑问题,因此很多派系的编码规范中规定,禁止或尽量不使用goto关键字,很容易让程序员范迷糊。但是
在很多场合下,使用goto关键字可以更方便快捷,比如:错误处理时,其实上面的例子就是一个简单的驱动错误处理例子。
注意:1)标签后面的代码会被依次执行,如上述代码18行,如果不使用goto,那么就会去执行19行后面的错误处理代码了。
2)在含有大量goto语句时,应该按照“先跳后出”的准则去设计逻辑,因为通常在标签处要做一些前面逻辑处理,越在前面声明的变量
或内存空间,越应该在最后去释放,如前面的例子。其大概逻辑如下比如:
01.goto error:
02.……
03.goto checkNumError:
04.……
05.goto timeupError:
06.……
07.
08.timeupError:
09.……
10.checkNumError:
11.……
12.error:
13.……
5. static 静态变量
在全局数据区分配内存;未经初始化的静态全局变量会被程序自动初始化为0(自动变量的值是随机的,除非它被显式初始化)作用域仅限于
变量被定义的文件中。
全局变量和全局静态变量的区别:
(1)全局变量是不显式用static修饰的全局变量,但全局变量 默认是静态的,作用域是整个工程,在一个文件内定义的全局变量,在另一个
文件中, 通过extern 全局变量名的声明,就可以使用全局变量。
(2)全局静态变量是显式用static修饰的全局变量,作用域是所在的文件,其他的文件即使用extern声明也不能使用。
静态函数:
在函数的返回类型前加上static关键字,函数即被定义为静态函数。静态函数与普通函数不同,它只能在声明它的文件当中可见,不能被其
它文件使用。其它文件中可以定义相同名字的函数,不会发生冲突。
6. const 在程序执行过程中不可更改的常量值
(1)欲阻止一个变量被改变,可以使用const关键字。在定义该const变量时,通常需要对它进行初始化,因为以后就没有机会再去改变它了;
(2)对指针来说,可以指定指针本身为const,也可以指定指针所指的数据为const,或二者同时指定为const;
(3)在一个函数声明中,const可以修饰形参,表明它是一个输入参数,在函数内部不能改变其值;
(4)对于类的成员函数,若指定其为const类型,则表明其是一个常函数,不能修改类的成员变量;
(5)对于类的成员函数,有时候必须指定其返回值为const类型,以使得其返回值不为“左值”。例如:
const classA operator*(const classA& a1,const classA& a2);
例如:#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定义的常量是在内存中有若干个拷贝。
提高效率
编译器通常不为普通const常量分配存储空间,而是将它们保存在符号表中,这也使得它成为一个编译期间的常量,没有了存储与读内存的操作,
使得它的效率提高。
7. sizeof 计算表达式或数据类型的字节数
例如:sizeof(char) = 1
int a[20];
sizeof(a) = 20
8. typedef 重新进行数据类型定义
1)typedef的最简单实用
定义一种类型的别名,而不只是简单的宏替换。可以用作同时声明指针型的多个对象。比如:
typedef unsigned long OT_U32;
typedef unsigned short OT_U16;
typedef unsigned char OT_U8;
2)typedef与结构结合使用
typedef struct tagMyStruct
{
int iNum;
long lLength;
} MyStruct;
这语句实际上完成两个操作:
a. 定义一个新的结构类型
struct tagMyStruct
{
int iNum;
long lLength;
};
分析:tagMyStruct称为“tag”,即“标签”,实际上是一个临时名字,struct 关键字和tagMyStruct一起,构成
了这个结构类型,不论是否有typedef,这个结构都存在。
b. typedef为这个新的结构起了一个名字,叫MyStruct typedef struct tagMyStruct MyStruct;
---------------
将一个结构体定义为全局变量;
typedef struct
{
unsigned char Unit; //单位 --
unsigned int Weight; //重量数据 --
unsigned char Dp; // .
unsigned char H1; // bluetooth icon
unsigned char H2; // :
unsigned char H3; // bluetooth icon
}LED_Display;
c文件中定义:LED_Display Hs4Led;
H文件中定义:extern LED_Display Hs4Led;
8. union 联合类型数据
在一个结构(变量)里,结构的各个成员顺序排列存储,每个成员都有自己的存储位置,联合变量的所有成员共享一片
存储区,因此,一个联合变量在每个时刻只能保存它的一个成员的值。
typedef union
{
unsigned long int dword;
unsigned int word[2];
unsigned char byte[4];
}Bytes4;
分析:
第一步:定义一个共同体
union
{
unsigned long int dword;
unsigned int word[2];
unsigned char byte[4];
}Bytes4;
第二步:为此共同体起个名字
typedef union Bytes4;
第三步:使用时,直接用Bytes4即可
9. asm 汇编类型
#define NOP() asm("nop")
10.enum 枚举
一般的定义方式如下:
enum enum_type_name
{
ENUM_CONST_1,
ENUM_CONST_2,
...
ENUM_CONST_n
} enum_variable_name;
注意:enum_type_name 是自定义的一种数据数据类型名,而enum_variable_name 为enum_type_name类型的一个变量,也就是我们
平时常说的枚举变量。实际上enum_type_name类型是对一个变量取值范围的限定,而花括号内是它的取值范围,即enum_type_name
类型的变量enum_variable_name 只能取值为花括号内的任何一个值,如果赋给该类型变量的值不在列表中,则会报错或者警告。
ENUM_CONST_1、ENUM_CONST_2、...、ENUM_CONST_n,这些成员都是常量,也就是我们平时所说的枚举常量(常量一般用大写)。
enum 变量类型还可以给其中的常量符号赋值,如果不赋值则会从被赋初值的那个常量开始依次加1,如果都没有赋值,它们的值从0
开始依次递增1。如分别用一个常数表示不同颜色:
enum Color
{
GREEN = 1,
RED,
BLUE,
GREEN_RED = 10,
GREEN_BLUE
}ColorVal;
其中各常量名代表的数值分别为:
GREEN = 1
RED = 2
BLUE = 3
GREEN_RED = 10
GREEN_BLUE = 11
二、枚举与#define 宏的区别
下面再看看枚举与#define 宏的区别:
1)#define 宏常量是在预编译阶段进行简单替换。枚举常量则是在编译的时候确定其值。
2)一般在编译器里,可以调试枚举常量,但是不能调试宏常量。
3)枚举可以一次定义大量相关的常量,而#define 宏一次只能定义一个。
10. if# else#
if(条件表达式)
语句1;
else
语句2;
if()
语句1;
else if()
语句2;
else if()
……………………
else 语句n
说明:else总是与最近的if相配对
11. switch/case语句
switch(表达式)
{
case 常量表达式1: {语句1;} break;
case 常量表达式2: {语句2;} break;
case 常量表达式3: {语句3;} break;
…………………………………………
case 常量表达式n: {语句n;} break;
default: {语句d;} break;
}
说明:a.case常量表达式可以是整形或字符型,也可以是枚举型数据
b.case和default的先后顺序不影响执行结果
c.没有break则控制执行下一个case语句,有break则跳出switch语句
12. while语句
while(条件表达式)
{
语句;
}
说明:当条件表达式为真时,则重复执行括号里面的语句,一直执行到表达式的结果为假时为止
如果条件表达式从一开始为假,则后面的语句一次也不会被执行
先判断,后执行
do
{
语句;
}
while(表达式);
说明:先执行一次,然后再判断
任何条件下,循环体语句至少会被执行一次
13. for语句
for(初值表达式1;循环条件表达式;更新表达式)
{
语句;
}
说明:先求表达式初值,判断是否成立,成立则执行语句,然后更新表达式,再判断;
不成立则推出for循环;
11. break 和 continue
break只能跳出它所处的那一层循环,结束整个循环;
continue则只结束本次循环下面的语句,而不终止整个循环的执行;