枚举类型
- 枚举是一种特殊的构造类型,它本身和int 是等价的,但它的值是有限个int 常量的合集,这些常量称为枚举符。
enum test{MON, TUE, WED, THU, FRI, SAT, SUN};
enum test a = MON; //定义枚举变量a,它的值是枚举常量MON。
- 用枚举定义的变量,其值只能取枚举列表里面的值,如果不在该列表中,则会报错或者警告
- 枚举符的值可以相同,但是这样做容易引起歧义,所以在设计枚举类型的时候要注意避免。
- 在枚举符的作用域内有与之同名的其他标识符时,该枚举符将被覆盖。
const关键字
- 如果希望程序中的某个变量具有只可读,不可写的属性,那么可将变量定义成只读变量。
- const 变量只能在定义时赋初值或是作为函数的形参时由实参赋初值。
- 使用const 修饰的变量,只是让变量具有可读不可写的属性——只能通过变量名读取变量,而不可以通过变量名去写入变量。如果我们不通过变量名,而是通过地址的形式,const 变量仍可以修改。
const int i = 1;
int *p = &i;
*p = 10; //通过指针p 修改只读变量i 的值
printf(“%d\n”, i); //i 的值变成 10
const 修饰指针
const 修饰指针的意义非常重大,需要重点掌握。const 修饰指针可以有以下几种情况:
const int * p; // p 的值可变,但不可以使用p 去修改p 指向的对象
int const * p; //同上
int * const p; //不可以通过p 去修改p 的值,但可以通过p 去修改p 指向的对象
const int * const p; //p 和p 指向的对象都不可以通过p 去修改
字符串常量中的const
char *p1 = “hello”;
const char *p2 = “hello”;
不管加不加const,”hello”这个字符串的内容都改不了,但是,加上const 有一个好处,那就是在编译期间就可以检查出错误。
const 与define
const 与define 在某种程度上都可以用于定义“常量”,但是const 与define 还是有本质区别的,从以下几点分析:
- 编译器处理方式不同
- define 宏是在预处理阶段进行文本替换
- const 只读变量是在编译运行阶段使用
- 类型和安全检查
- define 宏没有具体的类型,仅仅在预编译时进行替换,不做类型检查
- const 只读变量有具体的类型,在编译阶段会执行类型检查。
- 存储方式不同
- define 宏不占用存储空间
- const 只读变量会在内存中分配空间
const 推出的初始目的,正是为了取代预编译指令,消除它的缺点,同时继承它的优点。
volatile 关键字
- 概念:
volatile 是易变的、不稳定的意思。通过volatile 关键字可以定义易变变量。 - 编译器特点:
当一个数据需要频繁使用时,从内存把数据载入CPU 或是从CPU 将数据写回内存将消耗大量的时间。因此,可以把数据保存在寄存器里,因为寄存器的存取速度与CPU 的计算速度相同,CPU 计算的有多快,寄存器就可以以多快的速度进行存取。 - 优化:
编译器根据以上特点,在编译程序时,会对具有上述特点的一些变量(比如常见的循环控制变量)进行优化,将它们保存在寄存器中。准确说就是,每次要用到这个变量时,都必须从内存中直接读取这个变量的值,而不是使用在它在寄存器中的备份。
volatile int i = 0; //每次用于i 时都要从内存去取值
for(i = 0; i < 10000; i++)
{
////
}
volatile int i = 0;
i = 1; //这些代码不会被优化
i = 2; //这些代码不会被优化
i = 3; //这些代码不会被优化
i = 4;
- 应用场景:
- 中断服务程序中修改的供其他程序检测的变量需要加volatile
(一个中断服务子程序中会访问到的非自动变量) - 多任务环境下各任务间共享的变量应该加volatile
(多线程应用中被几个任务共享的变量) - 并行设备的硬件寄存器通常也要加volatile 说明,因为每次对它的读写都可能有不同的意义。(如状态寄存器、控制寄存器)
- 中断服务程序中修改的供其他程序检测的变量需要加volatile
注意:频繁地使用volatile很可能会增加代码尺寸和降低性能,因此要合理的使用volatile。
register关键字
- register 关键字与volatile 关键字作用刚好相反。使用register 修饰后,编译器会尽可能将这个变量存储在CPU 内存的寄存器中,而不是通过内存寻址去访问。
- 注意,CPU 的寄存器是数量有限的,一般也就那么几个或是几十个,如果用户定义了很多的register 变量,那么并不是所有的register 变量都会真的存储在寄存器中。
- 使用register 修饰的变量必须是CPU 寄存器所能接受的类型,这意味着register 变量必须是一个值,且长度小于或等于整型数据的长度。因为register 变量可能不存放在内存中,所以不能对register 变量使用“&”进行取地址操作。
typedef与#define的区别
- 处理阶段不一样,#define 在预处理阶段处理,而typedef 则在编译阶段处理。
- 功能不一样,typedef 只可以为类型重命名,而#define 的工作原理是文本替换,除了可以用来定义类型,还可以用于定义常量,编译开关等。
- 作用域不一样,typedef 有作用域,而#define 的作用域是整个程序。
- 对指针的操作不一样:
typedef char* String1;
#define String2 char*
String1 p1, p2; //p1 和p2 都是char*类型
String2 p1, p2; //这个宏会展开成char *p1, p2; p1 是char*类型,p2 的类型则是char
全局变量的extern
在一个源文件中定义的全局变量也可以被另一个源文件使用,但是和函数不一样,全局变量的引用需要强制使用extern 关键字进行声明,否则会出现变量未定义情况。
1.c
int global = 1;
void hello(void){
printf(“hello, world\n”);
}
2.c
extern int global; //使用extern 对全局变量进行声明
int main(){
hello();
printf(“global is %d\n”, global);
}
注意一点,使用extern 声明全局变量时,不可以对全局变量进行赋值,如下:
extern int global = 2; //不可以赋值,因为此处是对extern 进行声明作用
static静态变量
- static 可以修饰普通的局部变量,表示这个变量是一个静态变量,它和全局变量一样长驻于内存的数据段,但是作用域只限于定义处的那个函数。
- 当static 修饰全局变量时,这个全局变量在本文件的使用性质并没有改变,但是,不可以用extern 将该文件引用到别的文件中去使用。
- 当static 修饰一个函数时,这个函数将变成一个内部接口,它只能在该文件中被其他的函数调用,而不可以被其他文件所调用,即使用extern 进行声明也不行。
总结:
修饰局部变量:让局部变量的生存周期变为全局的。
修饰全局变量:让全局变量只能在本文件中使用
修饰函数:让函数只能在本文件中使用。