第6章 预处理、const与sizeof
1.宏定义
格式:
<pre name="code" class="html">#define pi (365*24*3600)
说明:宏定义末尾没有“;”,允许使用括号,使用时直接进行替换,不进行任何的类型检查,使用带参数宏时尽量用
括号将参数括起来防止出错;
宏定义是C语言的重要部分,在C++语言中,尽量使用inline替代,后面讲内联函数。
#define area(x) x*x //这样容易出问题,正确应该为<span style="font-family: Arial, Helvetica, sans-serif;"> #define area(x) (x)*(x)</span>
/*这在使用中是很容易出现问题的,看如下的程序*/
void main()
{
int y = area(2+2);
printf(“%d”,y);
}
上例子中,按理说给的参数是2+2,所得的结果应该为4*4=16,但是错了,因为该程序的实际结果为8,仍然是没能遵循纯粹的简单替换的规则,又是先计算再替换了,在这道程序里,2+2即为area宏中的参数,应该由它来替换宏定义中的x,即替换成2+2*2+2=8了。那如果遵循(1)中的解决办法,把2+2 括起来,即把宏体中的x括起来,是否可以呢?#define area(x) (x)*(x),对于area(2+2),替换为(2+2)*(2+2)=16,可以解决。同样:
#define N (2+2) //如果是#define N 2+2 ,则容易出现问题;
void main()
{
int a=N*N;
printf(“%d”,a);
}
2.const
指针常量、和常量指针。先看下面例子:
int b = 500;
const int* a = &b; //情况1;
int const *a = &b; //情况2;
int* const a = &b; //情况3;定义的时候必须初始化:
const int* const a = &b; //情况4;
关键:const位置在*的左边还是右边;如果const在星号左侧,则const就是用来修饰指针指向的变量,即指针指向的变量是常量(情况1和情况2);const在星号右侧,则const修饰指针本身,为常量指针(情况3), 此时定义的时候必须初始化;情况4两者都是常量,不能修改。
2.1类中的常量
类中const数据成员的初始化,只能在构造函数的初始化参数列表中进行初始化,如下:
class A
{
A(int size); // 构造函数
const int SIZE ;
};
A::A(int size) : SIZE(size) // 构造函数的
{
}
A a(100); // 对象 a 的 SIZE 值为 100
A b(200); // 对象 b 的 SIZE 值为 200
不能再类的声明中初始化const数据成员,因为类的对象未被创建,不知道SIZE的值是多少,如下是错误的:
class A
{
const int SIZE = 100; // 错误,企图在类声明中初始化 const 数据成员
int array[SIZE]; // 错误,未知的 SIZE
};
有一种比较好的的方法解决这类问题:类中用枚举来实现(枚举常量不会占用对象的存储空间,在编译时全部求值,缺点:隐含数据类型为int,有最大最小的界限,不能表示浮点数),如下:
class A
{
enum { SIZE1 = 100, SIZE2 = 200}; // 枚举常量
int array1[SIZE1];
int array2[SIZE2];
};
格式如下:
const成员函数是“只读”函数,它既不能更改数据成员的值,也不能调用能引起数据成员值变化的成员函数,只能调用const成员函数。如果在成员变量声明时有mutable关键字,如 mutable int a;则a在任何地方都能修改;
class A
{
public:
int get(int value_in) const; //const关键在一定要在后面,如果在前面,意味着返回值是const类型;
};
int A::get(int value_in) const
{
return 1;
}
说明:
const成员函数是“只读”函数,它既不能更改数据成员的值,也不能调用能引起数据成员值变化的成员函数,只能调用const成员函数。如果在成员变量声明时有mutable关键字,如 mutable int a;则a在任何地方都能修改;
2.3 const和宏定义#define的区别
C++两者可以使用,但是const明显有更多的优势:
其一:const有数据类型,编译的时候就可以检查错误,#define没有。
其二:某些工具可以对const进行调试,在C++编程中,应该尽量多用const,少用#define;
3.内联函数inline
将函数指定为内联函数,就是将它在程序中每个调用点上“内联地”展开,在程序中避免函数调用时用到的栈的开销。关键字:inline。内联说明对于编译器来说只是一个建议,编译器可以选择忽略这个建议(比如函数是复杂的递归函数,则编译器并不会内联展开,即使有关键字inline),仅当编译器的成本/效益分析显示它是有益的时候,引发插入(称为内联展开或内联)。 内联展开缓和较大代码尺寸的潜在成本的函数调用开销。对于内联函数应该放在头文件中.
类中内联函数有两种,在类中有函数的定义,该函数隐式转化为内联函数,其二,有关键字,并且在头文件中,例子如下:
class CTest
{
public:
CTest(void);
~CTest(void);
int GetValue() //隐式转化为内联函数;
{
return 0;
<span style="white-space:pre"> </span>}
};
或者:
class CTest
{
public:
CTest(void);
<span style="white-space:pre"> </span>~CTest(void);
int GetValue();//注意:此处不能用关键则inline,在函数定义中使用“内联”会导致它成为一个内联函数。但是,在调用该函数后,重新将该函数声明为“内联”是非法的,所以inline不能出现在函数的声明中
};
inline int CTest::GetValue()
{
return 0;
}
3.1 内联函数与宏定义
差别:宏定义只是简单的替换,内联函数在编译时将内联函数直接嵌入到目标代码中,这样可以加快速度,减少栈的花销。一般将函数语句少,调用次数多的函数申明为内联函数;
内联函数需要做类型检查;宏不是函数,叫做宏定义;
4.sizeof
作用:求数据内存空间的大小;指针的大小时钟为4个字节,在前面文章中分析过原因。
sizeof是操作符,不是函数,对于变量,可以不用括号.首先看下面的基础类型的大小;
cout << sizeof (char) << endl; // 结果=1
cout << sizeof (short) << endl;// 结果=2
cout << sizeof (float) << endl;// 结果=4
cout << sizeof (unsigned int) << endl;// 结果=4,所以有符号和无符号并不影响;
cout << sizeof (int) << endl;// 结果=4
cout << sizeof (long) << endl;// 结果=4
cout << sizeof (double) << endl;// 结果=8
cout << sizeof (double long) << endl;// 结果=8
cout << sizeof (char*) << endl;// 结果=4
4.1数组和指针的sizeof
见如下代码:
char a[100];
cout << sizeof a << endl; // 结果=100
char b[] = "asc";
cout << sizeof b << endl; // 结果=4,结束符站一个字节;
//指针
char *p = "ascde";
cout << sizeof p << endl;// 结果=4
cout << strlen(p) << endl;// 结果=5
注意:
sizeof计算的是栈中分配的大小,静态变量存放在全局数据区域,不会计算在内。
sizeof计算数组时,数组名不会转化为指针。
sizeof计算函数时,已函数的返回类型计算大小
sizeof是运算符,strlen()是函数。
strlen(char*)输入参数必须是char*,并且以“\0”结尾的。
数组作为函数的输入参数时,永远会转化为指向首元素的指针,所以无法传入数组的大小,数组的大小只能由另外的参数传入。
4.2 结构体,类的对齐以及求sizeof
4.2 结构体,类的对齐以及求sizeof
空类的大小为1,类的实例化是在内存中分配一块地址,每个实例在内存中都有独一无二的二地址。同样,空类也会实例化,所以编译器会给空类隐含的添加一个字节,这样空类实例化后就有独一无二的地址了。所以,空类的sizeof为1,而不是0.
class A{ virtual void f(){} };
class B:public A{}
此时,类A和类B都不是空类,其sizeof都是4,因为它们都具有虚函数表的地址。C++标准规定类的大小不为0,空类的大小为1,当类不包含虚函数和非静态数据成员时,其对象大小也为1,请看:
class A{};
class B:public virtual A{};
此时,A是空类,其大小为1;B不是空类,其大小为4.因为含有指向虚基类的指针。
多重继承的空类的大小也是1.
class Father1{}; class Father2{};
class Child:Father1, Father2{};
它们的sizeof都是1.
类和结构体数据对齐:
当结构体中元素的长度都小于处理器的位数(32位-4字节)时,按结构体内最长的数据对齐,结构体的长度一定是最长的数据的整数倍。