C和C++面试秘笈二——预处理、const、static与sizeof
【注】:本篇博客所使用的编译环境是32位的编译器
一、用#define实现宏并求最大值和最小值
#define MAX(x,y) (((x) > (y)) ? (x) : (y))
#define MIN(x,y) (((x) < (y)) ? (x) : (y))
我们可以这样写,加这么括号是因为宏只是简单的文本替换,如果不加括号很容易引起歧义。
下面的例子就会讲这一点。
二、宏定义的使用
我们想定义一个宏,让其实现计算一个数的平方的功能。也许有的人就会这样定义
#define SQR(x) (x * x)
我们在main()里使用这个宏
int a;
a = SQR(3);
这样算出来的答案是9,也是正确的。接下来我们让这个宏来计算一个表达式的平方。
int a;
a = SQR(3+2);
但是这个结果是11,而不是我们想要的25,注意了,因为宏定义的作用只是做一个简单的文本替换,所以上面的表达式会被替换成这样:
a = 3 + 2 * 3 + 2;
这个宏并不会自动的给我们添上一个括号,所以我们在写宏定义的时候一定要注意括号的使用,应该改成这样
#define SQR(x) ((x) * (x))
这样我们再使用上面的宏就不会有问题了。
三、const和#define的特点和区别
#define的生命周期止于编译期,存在于程序的代码段,它只是用来做文本替换的,在实际程序中只是一个常数 、一个命令中的参数,并没有实际的存在。宏常量没有数据类型。
const常量存在于程序的数据段,并在堆栈分配了空间。它在程序中是实际存在的,可以被调用、传递。const常量有数据类型,编译器可以对const常量进行类型的安全检查。
四、C++中const的作用
1、const用于定义常量,const定义的常量编译器可以对其进行类型的安全检查。
2、const修饰函数的形参,表示我们不能相应的实参进行修改
3、const修饰函数的返回值,如给“指针传递”的函数返回值加const,则返回值不能被直接修改,且该返回值只能被赋值给加const修饰的同类指针类型。
const char* func()
{
char *a = "nhao";
return a;
}
const char *b = func();
4、const修饰类的成员函数(函数定义体),任何不会修改数据成员的函数都应该用const修饰,这样如果在函数里修改了数据数据成员编译器就会报错。const修饰类的成员函数的形式为:
void func(int a) const;
五、static的作用
1、在函数体内,一个被声明为静态的变量在这一函数别调用的过程中其值维持不变。这句的意思是,在这个函数调用结束收,这个静态变量并不会被回收掉,还会继续存在。
2、在模块内(但在函数体外),一个被声明为静态的变量可以被模块内的所有函数访问,但是不能被模块外的其他函数访问。它只是一个本地的全局变量。说明这个变量只能在当前的文件中使用,但是不能被其他文件使用。
3、在模块内,一个函数被声明为静态的函数只可以被这一模块内的其他函数调用,但是不能被其他模块的函数调用。说明这个函数只能在本文件内使用。
六、static变量或者函数和普通的变量或者函数的区别
static全局变量和普通全局变量的区别
它们的存储方式都是静态存储方式!
普通的全局变量:当一个源程序由多个源文件组成的时候,普通的全局变量在各个源文件中都是有效的。
static全局变量:当一个源程序由多个源文件组成的时候,static全局变量只是在本文件中有效,其它源文件无法使用它。
static局部变量和普通的局部变量的区别
static局部变量只会被初始化一次,下一次的值会根据上一次的结果。我们来看一下这个函数。
int func()
{
static int a = 1;
return a++;
}
我们在main()函数中连续调用两次func()函数
cout << func() << " ";
cout << func() << endl;
得到的结果是 1 2 。因为我们将a定义成了一个static局部变量,a只会被初始化一次,所以它的值会随着调用的词数一直递增。
但是普通的局部变量每次调用的这个函数的时候都会被初始化。
int func()
{
int a = 1;
return a++;
}
我们也在main()函数中连续调用两次,得到的结果是 1 1.
static函数和普通的函数的区别
static函数的作用域仅在本文件,其他文件不能调用这个函数。static函数在内存中只有一份。
普通的函数的作用在整个源程序中,如果源程序有多个源文件,那么各个源文件都可以调用这个函数。普通函数在每个被调用中维持一份复制品。
七、使用sizeof计算普通变量所占空间的大小
void func(char str[100])
{
cout << "sizeof func str = " << sizeof(str) << endl;
}
int main()
{
char str[] = "hello";
char *p = str;
int n = 10;
cout << "sizeof(str) = " << sizeof(str) << endl;
cout << "sizeof(p) = " << sizeof(p) << endl;
cout << "sizeof(n) = " << sizeof(n) << endl;
func(str);
char *p1 = (char *)malloc(sizeof(char) * 100);
cout << "sizoef(p1)" << sizeof(p1) << endl;
system("pause");
return 0;
}
在32位编译环境下,指针变量的大小是4个字节,int类型的是4个字节。现在我们来分析这段代码。
cout << "sizeof(str) = " << sizeof(str) << endl;
这里的sizeof(str)是用来计算str这个数组的大小的,这个数组里有5个字符加一个'\0'组成。所以结果是6。
cout << "sizeof(p) = " << sizeof(p) << endl;
cout << "sizeof(n) = " << sizeof(n) << endl;
在32位编译环境下指针变量和int类型的变量都是4个字节的。所以这两个结果都是4.
void func(char str[100])
{
cout << "sizeof func str = " << sizeof(str) << endl;
}
调用这个函数的时候也是打印的4,因为传递的是一个数组,也就是一个地址,所以会被转换成一个指向这个地址的指针,所以这还是对指针变量计算大小。所以还是4。
char *p1 = (char *)malloc(sizeof(char) * 100);
cout << "sizoef(p1)" << sizeof(p1) << endl;
这个和上面一样也只是计算指针变量的大小,所以也是4。
八、使用sizeof计算类对象所占空间大小
class D
{
public:
int i;
short j;
char ch;
};
然后我们在main()函数中用sizeof计算D的空间大小。
我们会得出这样的结果:8,也许有很多人得出的结果是4 + 2 + 1 = 7。这就涉及到了一个考点:字节对齐。
一般而言,字节对齐需要满足的三个准则
- 结构体变量的首地址能够被其最宽基本类型成员的大小所整除
- 结构体每个成员相对于结构体首地址的偏移量都是成员大小的整数倍,如果有需要,编译器会在成员之间加上填充字节
- 结构体的中大小为结构体最宽基本类型成员大小的整数倍,如果有需要,编译器会在最末尾一个成员之后加上填充字节
以上面的结构体为例,int是4字节,short是2字节,char是1字节,加起来是7个字节,但是7不能被4整除,所以就会在最后一个成员之后填充一个字节,让这个结构体字节对齐。
九、使用sizeof计算含有虚函数的类的对象的空间大小
首先看这段代码
class A
{
public:
A(int x) : a(x)
{}
virtual void print()
{
cout << "A" << endl;
}
private:
int a;
};
然后我们用sizeof(A)来计算A所占的空间大小,会得到这样的结果: 8;因为这个类里含有一个virtual函数,所以它包含一个指向虚表的指针成员,再加上一个int类型的成员,一共是8字节。十、sizeof与strlen的区别
- sizeof是操作符,strlen是函数
- sizeof的结果类型是size_t的
- sizeof可以用类型做参数,但是strlen只能用char*做参数,而且参数是以'\0'结束的。
- 数组做sizeof的参数不退化,传递给strlen就会退化为指针了。
- sizeof计算的是整个数组的大小,而strlen计算的是数组中的字符串的长度并且不包含'\0'。
- sizeof后如果是类型,必须加括号,如果是变量名可以不加。
【注意】:如果要计算指针指向的字符串的长度,则一定要用strlen
十一、使用sizeof计算联合体的大小
联合体的大小取决于它所有成员中占用空间最大的一个成员的大小。所以下面的联合体的大小应该是8。
union u
{
double a;
int b;
};
十二、内联函数的作用
在C++中引入了内联函数,内联函数的作用就是解决函数调用的效率问题。
inline定义的类的内联函数,函数的代码被放入符号表中,在使用时直接进行替换(这点和宏定义相似),没有了调用的开销,效率也提高了。
因为内联函数也是一种函数,所以编译器会在调用它是对它进行相应的检查,确保正确调用。
内联函数可以作为某个类的成员函数,所以在其中就可以使用所在类的保护成员或者私有成员。
【注意】:并不是所有的函数都可以定义为内联函数,因为内联函数只是省去了调用的开销。如果这个函数很复杂或者有循环就不应该将其定义为内联函数,如果将其定义为内联函数的开销将会增加,因为每一处内联函数的调用都要复制代码,将会使程序的代码量增大,消耗更多的内存空间。
十三、内联函数和宏的区别
1、内联函数在编译时展开,宏在预编译是展开
2、在编译的时候,内联函数可以再接被镶嵌到目标代码中,而宏只是简单的文本替换
3、内联函数可以完成类型检测、语句是否正确等编译功能,宏不可以
4、内联函数是函数,而宏不是
5、内联函数不会出现二义性,但是宏如果不小心处理宏参数就会出现二义性。
上一篇: java小知识点