1 C语言中的const 关键字
1 )const 修饰的只读变量
定义const只读变量,具有不可变性。
例如:
const int Max=100;
intArray[Max];
这里请在Visual C++6.0里分别创建.c文件和.cpp文件测试一下。你会发现在.c文件中,编译器会提示出错,而在.cpp文件中则顺利运行。为什么呢?我们知道定义一个数组必须指定其元素的个数。这也从侧面证实在C语言中,const修饰的Max仍然是变量,只不过是只读属性罢了;而在C++里,扩展了const的含义。
2 )节省空间,避免不必要的内存分配,同时提高效率
编译器通常不为普通const只读变量分配存储空间,而是将它们保存在符号表中,这使得它成为一个编译期间的值,没有了存储与读内存的操作,使得它的效率也很高。例如:
#define M 3 //宏常量
const int N=5; //此时并未将N放入内存中
......
int i=N; //此时为N分配内存,以后不再分配!
int I=M; //预编译期间进行宏替换,分配内存
int j=N; //没有内存分配
int J=M; //再进行宏替换,又一次分配内存!
const定义的只读变量从汇编的角度来看,只是给出了对应的内存地址,而不是象#define一样给出的是立即数,所以,const定义的只读变量在程序运行过程中只有一份拷贝(因为它是全局的只读变量,存放在静态区),而#define定义的宏常量在内存中有若干个拷贝。#define宏是在预编译阶段进行替换,而const修饰的只读变量是在编译的时候确定其值。#define宏没有类型,而const修饰的只读变量具有特定的类型。
3)修饰一般变量
一般常量是指简单类型的只读变量。这种只读变量在定义时,修饰符const可以用在类型说明符前,也可以用在类型说明符后。例如:int const i=2; 或 const int i=2;
4)修饰数组
定义或说明一个只读数组可采用如下格式:
int const a[5]={1, 2, 3, 4, 5};或 const int a[5]={1, 2, 3, 4, 5};5)修饰指针
const int *p; // p可变,p指向的对象不可变
int const *p; // p可变,p指向的对象不可变
int*constp; //p不可变,p指向的对象可变
const int *const p; //指针p和p指向的对象都不可变
6)修饰函数的参数
const修饰符也可以修饰函数的参数,当不希望这个参数值被函数体内意外改变时使用。例如:void Fun(const int i);
告诉编译器i在函数体中的不能改变,从而防止了使用者的一些无意的或错误的修改
7)修饰函数的返回值
const修饰符也可以修饰函数的返回值,返回值不可被改变。例如:const int Fun (void);
在另一连接文件中引用const只读变量:
extern const int i; //正确的声明
extern const int j=10; //错误!只读变量的值不能改变
2 void关键字
void的字面意思是“空类型”,void *则为“空类型指针”,void *可以指向任何类型的数据。
void几乎只有“注释”和限制程序的作用,因为从来没有人会定义一个void变量
void真正发挥的作用在于:
(1) 对函数返回的限定:如果函数没有返回值,那么应声明为void类型
(2) 对函数参数的限定:如果函数无参数,那么应声明其参数为void
如果函数的参数可以是任意类型指针,那么应声明其参数为void *。
典型的如内存操作函数memcpy和memset的函数原型分别为:
void * memcpy(void *dest, const void *src, size_t len);
void * memset ( void * buffer, int c, size_t num );
//例子:memset接受任意类型指针
int IntArray_a[100];
memset (IntArray_a, 0, 100*sizeof(int) ); //将IntArray_a清0
//例子:memcpy接受任意类型指针
int destIntArray_a[100], srcintarray_a[100];
//将srcintarray_a拷贝给destIntArray_a
memcpy (destIntArray_a, srcintarray_a, 100*sizeof(int) );
//有趣的是,memcpy和memset函数返回的也是void *类型,
3 union 关键字
结构体所占的内存大小是其成员所占内存之和
编译器理所当然的认为你构造一个结构体数据类型是用来打包一些数据成员的,而最小的数据成员需要1个byte,编译器为每个结构体类型数据至少预留1个byte的空间。
所以,空结构体的大小就定位1个byte
union维护足够的空间来置放多个数据成员中的“一种”,而不是为每一个数据成员配置空间,在union中所有的数据成员共用一个空间,同一时间只能储存其中一个数据成员,所
有的数据成员具有相同的起始地址
union StateMachine
{
char character;
int number;
char *str;
double exp;
};
一个union只配置一个足够大的空间以来容纳最大长度的数据成员,以上例而言,最大长度是double型态,所以StateMachine的空间大小就是double数据类型的大小
在C++里,union的成员默认属性页为public。union主要用来压缩空间。如果一些数据不可能在同一时间同时被用到,则可以使用union
大小端模式对 union 类型数据的影响
union
{
int i;
char a[2];
}*p, u;
p=&u;
p->a[0] = 0x39;
p->a[1] = 0x38;
p.i的值应该为多少呢? //3839
这里需要考虑存储模式:大端模式和小端模式。
大端模式(Big_endian):字数据的高字节存储在低地址中,而字数据的低字节则存放在高地址中。
小端模式(Little_endian):字数据的高字节存储在高地址中,而字数据的低字节则存放在低地址中。
union型数据所占的空间等于其最大的成员所占的空间。对union型的成员的存取都是相对于该联合体基地址的偏移量为0处开始,也就是联合体的访问不论对哪个变量的存取都
是从union的首地址位置开始.
请写一个C函数,若处理器是Big_endian的,则返回0;若是Little_endian的,则返回1。
先分析一下,按照上面关于大小端模式的定义,假设int类型变量i被初始化为1。
以大端模式存储,其内存布局如下图:
以小端模式存储,其内存布局如下图:
变量i占4个字节,但只有一个字节的值为1,另外三个字节的值都为0。如果取出低地址上的值为0,毫无疑问,这是大端模式;如果取出低地址上的值为1,毫无疑问,这是
小端模式
int checkSystem( )
{
union check
{
int i;
char ch;
}c;
c.i = 1;
return (c.ch ==1);
}
建议在真正需要用空语句时写成这样:
NULL;
而不是单用一个分号
case 后面只能是整型或字符型的常量或常量表达式(想想字符型数据在内存里是怎么存的)
在多重循环中,如果有可能,应当将最长的循环放在最内层,最短的循环放在最外层,以减少 CPU 跨切循环层的次数
建议 for 语句的循环控制变量的取值采用“半开半闭区间”写法
for(n=0;n<10;n++)
break关键字很重要,表示终止本层循环。现在这个例子只有一层循环,当代码执行到break 时,循环便终止。
continue表示终止本次(本轮)循环。当代码执行到 continue 时,本轮循环终止,进入下一轮循环
return 语句不可返回指向“栈内存”的“指针”,因为该内存在函数体结束时被自动销毁。
1)#define宏常量是在预编译阶段进行简单替换。枚举常量则是在编译的时候确定其值。
2)一般在编译器里,可以调试枚举常量,但是不能调试宏常量。
3)枚举可以一次定义大量相关的常量,而#define宏一次只能定义一个
typedef的真正意思是给一个已经存在的数据类型(注意:是类型不是变量)取一个别名,而非定义一个新的数据类型
//typedef与#define 的区别
#define INT32 int
unsigned INT32 i = 10;//正确,因为在预编译的时候INT32被替换为int,而unsigned int i = 10;
typedef int int32;
unsigned int32 j = 10;//错误,用typedef取的别名不支持这种类型扩展
#define PCHAR char*
PCHAR p3,p4; //p3 是char型指针,p4却不是指针,仅仅是一个char类型的字符
typedef char* pchar;
pchar p1,p2; // p1, p2 都是char型指针