第四章
4.1 预处理
预处理是指通过预处理的内建功能对一个资源进行等价替换,常见的预处理包括:文件包含#include,条件编译: #ifdef,#ifndef,#else,以及布局控制#progma,还有宏替换#define。
#progma 用法较为复杂,可参照http://baike.baidu.com/link?url=707UZxhAcIKODivQADGQHMTd9dyQ8oELZ122PJ6siDvkQUt_DVatnG2SvqdkD6ONL9va5TKVV0dJAJe76ptb7q
例子1:
使用宏定义计算最大值:
#defineMAX(x,y) (((x)>(y))?(x):((y))
由于宏定义只是简单的替换,所以x 和 y 分别为j加上括号,避免产生错误
例如:#define Sq(x)(x*x) ,当我们调用时,如果是x 是表达式,a+3,那么计算结果就是a+3*a+3,而不是a+3的平方,所以改为(x)*(x)可以消除这种错误。
例子2,连接宏参数:
#defineSTR(s) #s
#defineCon(a,b) (int)(a##e##b)
cout<<STR(abc)<<endl;
cout<<Con(2,3)<<endl;
输出 :
abc
2000
例子2:
使用宏定义去高低位,数组长度
#defineHigh(x) ((x)&255)
#define Arr(a) (sizeof(a)/sizeof(a[0]))
4.2const 常量
const是C语言的关键字,表示不可更改。
例子3:
constint a=2;
a=3;//error
constint *b=&a;
*b=3;//error
intconst *c=b;
c=&a;//error
constint const *d=&a;
*d=3;//error
d=b;//error
例子3
#define和const区别:
#define 只是进行文本替换,不会分配空间,编译器也不会进行检查。
Const 会在堆栈中分配空间,并会进行安全检查。
例子4:const的用处
1、const定义的常量,编译器会对其进行数据静态类型检查。
2、在函数形参定义中,用const可以提高效率。尤其是自定义类型或是对象类型,如f(A a)和f(const A &a),后者省去了赋值构造等过程。
3、const修饰函数返回值,不能被修改,如果返回指针,则不能直接修改,且必须为同类型指针才能接收。例如 const A operator *(A &a,A&b){},这样就不会出现 a*b=c这样的错误了。
4、const修饰成员函数。如果成员函数不需要改变数据成员,那么应该将其定义为const,这样如果不小心修改了数据成员,或者是调用了nonconst函数,编译器就会报错,定义格式如下:int get() const;
4.3static 变量
Static是关键字,表示静态,可以修饰变量和函数。在类中,如果成员定义为static,那么该成员将不再属于类的对象,只属于类。
例子4:static有什么作用
1、在函数体内,被定义为static的变量,在调用过程中,其值保持不变。
2、在模块内,定义为static的变量,只能在模块内被使用,不能被模块外使用。
3、在模块内,定义为static的函数,也只能被模块内的函数调用,不能被模块外使用。
例子5:全局变量、局部变量和函数被定义为static后有什么不同?
对于全局变量来说,改为static后,存储方式没有改变,适用范围改变了。但对于局部变量来说,改变了其生命周期,存储方式发生了改变。
1、static的全局变量,只能在当前文件中使用,只能初始化一次。
2、static局部变量只初始化一次,下一次依照上一次结果
3、static 函数在内存中只保留一份,而普通函数在每个被调用中都维持一份复制品。其只能在当前文件中使用,成为内部函数。
例子6:
在类内部定义的static成员,为各类实例共享,而分单属于某一个实例。
4.4sizeof 操作符
例子7:
void ff(char s[10])
{
cout<<sizeof(s)<<endl;
}
void main()
{
char str[]="abcd";
char *p=str;
void *q=(void *)malloc(100);
cout<<sizeof(str)<<”,”<<sizeof(&str[0])<<","<<sizeof(p)<<","<<sizeof(q)<<endl;
}
结果为 4,5,4,4,4
在不把数组传入函数内时,其与指针有区别,传入后,作用与指针相同。
例子8:字符对齐的问题
字符对齐与编译器有关系,但是一般有以下三个原则。
1、结构体变量的首地址能够被其最宽的基本类型成员的大小整除;
2、结构体每个成员相对于首地址的偏移量都是成员大小的整数倍,如果有需要,会在成员之间加上填充字节。
3、结构体的总大小为其最宽基本类型成员的整数倍,如有需要,在末尾添加填充字节。
注意:如果想让结构体按照给定字节数对齐,可以使用 #progma pack(n);
例子9:
1、对于类的sizeof 函数。空类的存储空间是一个字节,编译器会安插一个char类,标记其每一个对象。
2、类内部数据依然遵循对其原则
3、对于虚函数,因为需要一个指针指向虚函数表,因此存储空间多了一个指针,与虚函数个数无关。
4、对于虚继承,编译器会自动为其添加一个指向父类的指针,若是空类,则不再需要char型
5、static数据成员放在静态存储区域中,不占用类的存储空间。
例子10:sizeof 和strlen区别:
1、sizeof 是运算符,而strlen是函数,sizeof的返回值为size_t,在头文件中typedef为unsigned int 型。
2、sizeof 可以用类型做参数,而strlen只能是char* 且以\0结尾的字符串。
3、数字传递给sizeof不退化,但是给strlen就退化为指针 例如
charstr[]="abcd";
cout<<sizeof(str)<<" "<<strlen(str)<<endl;
结果为 5 4
4、大部分程序在编译阶段,就已经计算好了sizeof,而strlen必须要到程序运行时在可以知道,这就是为什么sizeof 可以用来定义数组。Strlen计算的是字符串的长度,而不是占用内存的大小.例如 str[20]=”0123”;sizeof(str) 和strlen(str)就会不同
5、sizeof 如果是类型,必须要加括号,如果是变量,可以不加。
4.5inline 与宏定义
内联函数用来解决宏定义的缺点。他可以像宏定义一样展开,但编译器会对他进行安全检查,不想宏定义一样只是简单的替换,因此可靠性更强。缺点是会增加代码量。
在类内部定义的函数,默认为inline,如果内部声明,外部定义,则需声明为inline
例子9:二者区别
1、宏定义是在预编译阶段展开,而inline是在编译阶段展开;
2、宏定义是简单的文本替换,而inline是在调用位置展开。
3、inline函数本质上还是函数,而宏定义不是,因此inline函数会有基本的安全检查,宏定义没有
第四章结束