C++程序员面试宝典——预处理、counst与sizeof

来源:互联网 发布:苹果电脑mac初始密码 编辑:程序博客网 时间:2024/06/06 06:44

-------------------------------------------------基本原理----------------------------------------------------

C++程序设计三大难点:预处理、counst与sizeof

1. 预处理

C++从C语言那里把C语言预处理器继承过来(C语言预处理器,被Bjarne博士简称为Cpp)。

主要作用就是:    把通过预处理的内建功能对一个资源进行等价替换.

最常见的预处理有: 文件包含,条件编译、布局控制和宏替换4种。 

文件包含:    #include 是一种最为常见的预处理,主要是做为文件的引用组合源程序正文。 
条件编译:    #if,#ifndef,#ifdef,#endif,#undef等也是比较常见的预处理,主要是进行编译时进行有选择的挑选,注释掉一些指定的代码,以达到版本控制、防止对文件重复包含的功能。 
布局控制:    #progma,这也是我们应用预处理的一个重要方面,主要功能是为编译程序提供非常规的控制流信息。 
宏替换:    #define,这是最常见的用法,它可以定义符号常量、函数功能、重新命名、字符串的拼接等各种功能。

 (1)简单的宏定义

#define <宏名>  <字符串>

例: #define PI 3.1415926

(2) 带参数的宏定义

#define <宏名> (<参数表>) <宏体>

例: #define A(x) x

常见的预处理指令: 

     #define         宏定义 
     #undef          取消宏 
     #include        文本包含 
     #ifdef            如果宏被定义就进行编译 
     #ifndef          如果宏未被定义就进行编译 
     #endif           结束编译块的控制 
     #if                表达式非零就对代码进行编译 
     #else            作为其他预处理的剩余选项进行编译 
     #elif              这是一种#else和#if的组合选项 
     #line             改变当前的行数和文件名称 
     #error            输出一个错误信息 
     #pragma        为编译程序提供非常规的控制流信息 

2. const

Const 是C++中常用的类型修饰符,常类型是指使用类型修饰符const说明的类型,常类型的变量或对象的值是不能被更新的。

关于C++ const 的全面总结

Const主要有几点优势:

保护功能:便于进行类型检查;可以保护被修饰的东西;为函数重载提供了一个参考

时空效率:相比宏常量的反复分配内存空间,const常量只分配一次内存空间;编译器将它们保存在符号表中,成为编译常量,没有了存储与读取的操作。

指针使用CONST

(1)指针本身是常量不可变

     (char*) const pContent; 

     const (char*) pContent; 

(2)指针所指向的内容是常量不可变

     const (char) *pContent; 

     (char) const *pContent; 

(3)两者都不可变

      const char* const pContent; 

(4)还有其中区别方法,沿着*号划一条线

如果const位于*的左侧,则const就是用来修饰指针所指向的变量,即指针指向为常量;(const在左—>内容常量)

如果const位于*的右侧,const就是修饰指针本身,即指针本身是常量。(const在右—>指针常量)

函数中使用CONST

任何不修改数据成员的函数都应该被定义为const成员函数。

常量函数结尾使用const,表明该函数不会修改调用的实参。

像一般的输出函数display(),print(),show()等都可定义为常量函数。

如:int size()  const;

如果const放在函数开头,一般用来表示返回一个常量。

a.传递过来的参数在函数内不可以改变(无意义,因为Var本身就是形参)

void function(const int Var);

b.参数指针所指内容为常量不可变

void function(const char* Var);

c.参数指针本身为常量不可变(也无意义,因为char* Var也是形参)

void function(char* const Var);

d.参数为引用,为了增加效率同时防止修改。修饰引用参数时:

void function(const Class& Var); //引用参数在函数内不可以改变

将Const类型转化为非Const类型的方法

采用const_cast 进行转换。  

用法:const_cast <type_id>  (expression) 

类里面的成员变量加mutable修饰const成员函数,就可以修改它了。

对比学习内容:volatile

与const一样,volatile是一个类型修饰符。如果不加入volatile:要么无法编写多线程程序,要么编译器失去大量优化的机会。

volatile的作用是: 作为指令关键字,确保本条指令不会因编译器的优化而省略,且要求每次直接读值

使用该关键字的例子如下:

volatile int vint;

当要求使用volatile 声明的变量的值的时候,系统总是重新从它所在的内存读取数据,即使它前面的指令刚刚从该处读取过数据。而且读取的数据立刻被保存。volatile 指出 i是随时可能发生变化的,每次使用它的时候必须从i的地址中读取,因而编译器生成的汇编代码会重新从i的地址读取数据放在b中。

而优化做法是,由于编译器发现两次从i读数据的代码之间的代码没有对i进行过操作,它会自动把上次读的数据放在b中。如果i是一个寄存器变量或者表示一个端口数据就容易出错,所以说volatile可以保证对特殊地址的稳定访问

假设被面试者正确地回答了这是问题(嗯,怀疑是否会是这样),我将稍微深究一下,看一下这家伙是不是真正懂得volatile完全的重要性。

1). 一个参数既可以是const还可以是volatile吗?解释为什么。

2). 一个指针可以是volatile 吗?解释为什么。

3). 下面的函数被用来计算某个整数的平方,它能实现预期设计目标吗?如果不能,试回答存在什么问题:

int square(volatile int *ptr)
{
    return ((*ptr) * (*ptr));
}

下面是答案:

1). 是的。一个例子是只读的状态寄存器。它是volatile因为它可能被意想不到地改变。它是const因为程序不应该试图去修改它。

2). 是的。尽管这并不很常见。一个例子是当一个中断服务子程序修改一个指向一个buffer的指针时。

3). 这段代码是个恶作剧。这段代码的目的是用来返指针*ptr指向值的平方,但是,由于*ptr指向一个volatile型参数,编译器将产生类似下面的代码:

int square(volatile int* &ptr)//这里参数应该申明为引用,不然函数体里只会使用副本,外部没法更改
{
    int a,b;
    a = *ptr;
    b = *ptr;
    return a*b;
}

由于*ptr的值可能在两次取值语句之间发生改变,因此a和b可能是不同的。结果,这段代码可能返回的不是你所期望的平方值!正确的代码如下:

long square(volatile int*ptr)

{
    int a;
    a = *ptr;
    return a*a;
}

3. sizeof

C++ sizeof用法 .

sizeof不是函数,更像一个特殊的宏,它是在编译阶段求值的。

cout<<sizeof(int)<<endl; // 32位机上int长度为4cout<<sizeof(1==2)<<endl; // == 操作符返回bool类型,相当于 cout<<sizeof(bool)<<endl;
在编译阶段已经被翻译为:

cout<<4<<endl;cout<<1<<endl;

这里有个陷阱,看下面的程序:

int a = 0;cout<<sizeof(a=3)<<endl;cout<<a<<endl;
  输出为什么是4,0而不是期望中的4,3???就在于sizeof在编译阶段处理的特性。由于sizeof不能被编译成机器码,所以sizeof作用范围内,也就是()里面的内容也不能被编译,而是被替换成类型。=操作符返回左操作数的类型,所以a=3相当于int,而代码也被替换为:
int a = 0;cout<<4<<endl;cout<<a<<endl;

sizeof有两种用法:

(1)sizeof(object)

也就是对对象使用sizeof,也可以写成sizeof object 的形式。

(2)sizeof(typename)

也就是对类型使用sizeof,注意这种情况下写成sizeof typename是非法的。

任何时候,加括号总是对的。

sizeof的指针问题 

考虑下面问题:

cout<<sizeof(string*)<<endl; // 4cout<<sizeof(int*)<<endl; // 4cout<<sizof(char****)<<endl; // 4
可以看到,不管是什么类型的指针,大小都是4的,因为指针就是32位的物理地址。

结论:只要是指针,大小就是4(64位机变成8字节)。 但是其指向的变量占用不同大小的存储空间。

C++ STL vector:sizeof(vector)

向函数传递数组的问题

#include <iostream>using namespace std; int Sum(int i[]){ int sumofi = 0; for (int j = 0; j < sizeof(i)/sizeof(int); j++) //实际上,sizeof(i) = 4 {  sumofi += i[j]; } return sumofi;} int main(){ int allAges[6] = {21, 22, 22, 19, 34, 12}; cout<<Sum(allAges)<<endl; system("pause"); return 0;}
Sum的本意是用sizeof得到数组的大小,然后求和。但是实际上,传入自函数Sum的,只是一个int 类型的指针,所以sizeof(i)=4,而不是24,所以会产生错误的结果。解决这个问题的方法使是用指针或者引用。

int Sum(int (*i)[6]){ int sumofi = 0; for (int j = 0; j < sizeof(*i)/sizeof(int); j++) //sizeof(*i) = 24 {  sumofi += (*i)[j]; } return sumofi;} int main(){ int allAges[] = {21, 22, 22, 19, 34, 12}; cout<<Sum(&allAges)<<endl; system("pause"); return 0;}
在这个Sum里,i是一个指向i[6]类型的指针,注意,这里不能用int Sum(int (*i)[])声明函数,而是必须指明要传入的数组的大小,不然sizeof(*i)无法计算。但是在这种情况下,再通过sizeof来计算数组大小已经没有意义了,因为此时大小是指定为6的。

此外,对函数使用sizeof,在编译阶段会被函数的返回值类型取代。

struct的sizeof问题 

例如,下面的结构各成员空间分配情况:

struct test  {       char x1;       short x2;       float x3;       char x4;  };  
结构的第一个成员x1,其偏移地址为0,占据了第1个字节。

第二个成员x2为short类型,其起始地址必须2字节对界,因此,编译器在x2和x1之间填充了一个空字节。

结构的第三个成员x3和第四个成员x4恰好落在其自然对界地址上,在它们前面不需要额外的填充字节。

在test结构中,成员x3要求4字节对界,是该结构所有成员中要求的最大对界单元,因而test结构的自然对界条件为4字节,编译器在成员x4后面填充了3个空字节。整个结构所占据空间为12字节。

因为对齐问题使结构体的sizeof变得比较复杂,看下面的例子:(默认对齐方式下) 

struct s1{ char a; double b; int c; char d; }; struct s2{ char a; char b; int c; double d;}; cout<<sizeof(s1)<<endl; // 24cout<<sizeof(s2)<<endl; // 16
对于s1,首先把a放到8的对界,假定是0,此时下一个空闲的地址是1,但是下一个元素d是double类型,要放到8的对界上,离1最接近的地址是8了,所以d被放在了8,此时下一个空闲地址变成了16,下一个元素c的对界是4,16可以满足,所以c放在了16,此时下一个空闲地址变成了20,下一个元素d需要对界1,也正好落在对界上,所以d放在了20,结构体在地址21处结束。由于s1的大小需要是8的倍数,所以21-23的空间被保留,s1的大小变成了24。

对于s2,首先把a放到8的对界,假定是0,此时下一个空闲地址是1,下一个元素的对界也是1,所以b摆放在1,下一个空闲地址变成了2;下一个元素c的对界是4,所以取离2最近的地址4摆放c,下一个空闲地址变成了8,下一个元素d的对界是8,所以d摆放在8,所有元素摆放完毕,结构体在15处结束,占用总空间为16,正好是8的倍数。

这里有个陷阱,对于结构体中的结构体成员,不要认为它的对齐方式就是他的大小,看下面的例子: 

struct s1{ char a[8];}; struct s2{ double d;}; struct s3{ s1 s; char a;}; struct s4{ s2 s; char a; }; cout<<sizeof(s1)<<endl; // 8cout<<sizeof(s2)<<endl; // 8cout<<sizeof(s3)<<endl; // 9cout<<sizeof(s4)<<endl; // 16;
s1和s2大小虽然都是8,但是s1的对齐方式是1,s2是8(double),所以在s3和s4中才有这样的差异。

所以,在自己定义结构体的时候,如果空间紧张的话,最好考虑对齐因素来排列结构体里的元素。 

-------------------------------------------- 易错题 ----------------------------------------------

1. 用预处理指令#define声明一个常数,用以表明一年中有多少秒。

这是一个简单宏定义,考察三点:

(1)#define不能以分号结尾,括号使用。

(2)预处理器将为你计算常数表达式的值,因此写入计算式子比计算结果重要。

(3)意识到16位机将有一个整形数溢出,因此使用长整形,因此用到长整形符号UL。

答案:#define SECORES_PRE_YEAR (60*60*24*365) UL

2. 写一个标准宏,返回两个数较小那个。

这是一个带参数的宏,需要用到三元运算符。

答案:#define MIN(a,b) (a<=b ? a : b)

3. Const与define的区别?

二者都可以定义常量,区别在两点:

const常量有数据类型,宏常量没有数据类型。前者可进行安全检查,后者只进行字符替换,可能产生边际效应。

Const节省内存空间,存在编译器中,省去多余存取时间。

C++程序中Const常量完全取代宏常量。

C中默认const是外部连接,C迫使程序员使用#define。C++中默认const是内部连接,如果要定义外部连接,需要用extern const bufsize; //只声称

例如:const buffsize=100; char buf[bufsize];  看起来是对的,但实际上buffsize占用了一部分内存,C编译器并不知道它在编译时候的值。可以选择这样写:

const buffsize;

内部连接和外部连接

4. sizeof与strlen的区别

sizeof度量内存空间大小,strlen度量char*指向的字符串长度而且必须以‘\0’结尾;

sizeof是运算符,strlen是函数;

当使用结构类型或者变量时,sizeof返回实际大小,使用静态空间时,返回全部数组的尺寸(计算结构变量大小必须考虑对齐问题);

sizeof不能用于函数类型、不完全类型(未知存储大小、void、未知内容)、或位字段。

例1: char ss[100]=“0123456789”;

sizeof(ss)结果为400;strlen(ss)结果为11。

例2:int ss[100]="0123456789";

sizeof(ss)结果为400;strlen(ss)错误,参数不是char类型。

例3:char* ss[100]=“0123456789”;//ss是指针

sizeof(ss)结果为4,ss是指针,指针在32位系统大小固定为4B。

sizeof(*ss)结果为1,ss指针指向的第一个字符。

例4:char ss[ ]=“0123456789”;//ss是数组

sizeof(ss)结果为11,ss是数组,计算到“\0”位置。

sizeof(*ss)结果为1,*ss是第一个字符。

5.sizeof用途:

与存储分配和I/O系统类似例程进行通信;

查看某种类型的对象在内存中所占的单元字节;

给出动态分配内存的大小;

便于类型扩充。

6. 内联函数和宏定义的区别.

(1)内联函数可以加快程序运行。因为不需要中断调用,而宏只是一个简单的替换。

(2)内联函数要做参数类型检查。

内联函数适用于:一个函数不断地被调用;函数只有简单的几行。

0 0
原创粉丝点击
热门问题 老师的惩罚 人脸识别 我在镇武司摸鱼那些年 重生之率土为王 我在大康的咸鱼生活 盘龙之生命进化 天生仙种 凡人之先天五行 春回大明朝 姑娘不必设防,我是瞎子 吃完辣的想吐怎么办 体育生暑假训练太累怎么办 高强度运动后性功能不好怎么办 运动后第二天全身酸痛怎么办 运动后心慌想吐怎么办 激素停后全身疼怎么办 运动完头晕想吐怎么办 跑了步全身酸痛怎么办 运动后全身没力气怎么办 学完游泳不会换气怎么办 猫不吃饭精神不好怎么办 运动完头晕目眩想吐怎么办 酒后第二天恶心想吐怎么办 剧烈运动后肌肉酸痛怎么办 剧烈运动后吐了怎么办 长跑后头晕想吐怎么办 运动后一直想吐怎么办 跑多了恶心想吐怎么办 剧烈运动恶心想吐怎么办 长跑之后 恶心想吐怎么办 喝咖啡后绞心痛怎么办 牛奶喝多了胃胀怎么办 运动后大量出汗头晕怎么办 屁股出汗淹的特别痛怎么办 运动完感觉很累怎么办 跑步后感觉很累怎么办 输液多了伤脾胃怎么办 运动完后特别晕怎么办 运动过量大腿肌肉酸痛怎么办 13岁发烧38.2度怎么办 头晕恶心想吐四肢无力怎么办 烧退了浑身疼怎么办 下午睡久了头疼怎么办 一天睡久了头疼怎么办 在家躺久了头疼怎么办 4周多儿童睡眠差怎么办 6岁儿童睡眠差怎么办 四年级的孩子不会写作文怎么办 四年级的孩子写不出作文怎么办? 欠了三万网贷怎么办 打完篮球浑身疼怎么办