说明:
- 面试题来源于网络书籍,公司题目以及博主原创或修改(题目大部分来源于各种公司);
- 文中很多题目,或许大家直接编译器写完,1分钟就出结果了。但在这里博主希望每一个题目,大家都要经过认真思考,答案不重要,重要的是通过题目理解所考知识点,好应对题目更多的变化;
- 博主与大家一起学习,一起刷题,共同进步;
- 写文不易,麻烦给个三连!!!
1.用预处理指令#define声明一个常数,用以表明1年中有多少秒?
解析:
通过这道题面试官想考以下几个知识点:
- #define语法的基本知识(例如,不能以分号结束,括号的使用,等等)
- 要懂得预处理器将为你计算常数表达式的值,因此,写出你是如何计算一年中有多
少秒而不是计算出实际的值,会更有意义 - 意识到这个表达式将使一个16位机的整型数溢出,因此要用到长整型符号L,告诉
编译器这个常数是长整型数
如果在表达式中用到UL(表示无符号长整型),那么你就有了一个好的起点。记住,
第一印象很重要。
答案:#define SECOND_EVERY_YEAR (606024*365)UL
2.写一个“标准”宏MIN,这个宏输入两个参数并返回较小的一个
解析:
这个测试是为下面的目的而设的
- 标识#define在宏中应用的基本知识。这是很重要的,因为直到嵌入(inline)操作符
变为标准C的一部分,宏都是方便地产生嵌入代码的唯一方法。对于嵌入式系统来说,为了
能达到要求的性能,嵌入代码经常是必须的方法 - 三重条件操作符的知识。这个操作符存在C语言中的原因是它使得编译器能产生比
if-then-else更优化的代码,了解这个用法是很重要的 注意点
:一定要带好括号,不要嫌麻烦,因为宏操作是直接替换,如果不带括号会出现一些问题,请看下面的例子
#define SQUARE(x) x * x
int a = 4;
int b = SQUARE(a + 1);
在预处理阶段,SQUARE(a + 1) 会被替换成 a + 1 * a + 1,由于乘法的优先级高于加法,所以实际上会计算成 a + (1 * a) + 1,结果为 9 而不是 25。
答案:#define MIN(x, y) ((x) <= (y) ? (x) : (y))
3.说说const修饰指针?
解析:
关于const修饰指针的情况,一般分为如下4种情况:
int main()
{
int b = 500;
const int* a = &b; // 情况1
int const* a = &b; // 情况2
int* const a = &b; // 情况3
const int* const a = &b; // 情况4
return 0;
}
- 先看情况1
如果const位于星号的左侧,则const就是用来修饰指针所指向的变量,即指针指向为常
量;如果const位于星号的右侧,const就是修饰指针本身,即指针本身是常量。因此,1和2的
情况相同,都是指针所指向的内容为常量(const放在变量声明符的位置无关),这种情况下
不允许对内容进行更改操作。
换句话来说,如果a是一名仓库管理员的话,他所进入的仓库,里面的货物(*a)是他没权
限允许动的,仓库里面的东西原来是什么就是什么;所以
int b = 500;
const int* a = &b;
*a = 600; // 错误
但是也有别的办法去改变*a的值,一个是通过改变b的值:
int b = 500;
const int* a = &b;
b = 600;
cout << *a << endl; // 得到600
还有一种改变*a办法就是a指向别处(管理员换个仓库):
int b = 500, c = 600;
const int* a = &b;
a = &c;
cout << *a << endl; // 得到600
- 情况2与情况1相同。
- 情况3为指针本身是常量,这种情况下不能对指针本身进行更改操作,而指针所指向
的内容不是常量
举例来说:如果a是一名仓库管理员的话,他只能进入指定的某仓库,而不能去别的仓
库(所以a++是错误的);但这个仓库里面的货物(*a)是可以随便动的,(*a=600是正确
的)。
此外,对于情况3:定义时必须同时初始化。
int b = 500, c = 600;
// int *const a; // 错误 没有初始化
int *const a = &b; // 正确,必须初始化
*a = 600; // 正确,允许改值
a = &c; // 错误,指针是常量
cout << a++ << endl; // 错误
- 对于情况4为指针本身和指向的内容均为常量。那么这个仓库管理员只能去特定的仓库,并且仓库里面所有的货物他都没有权限去改变。
int b = 500, c = 600;
const int* const a = &b;
a = &c; // 错误
*a = 600; // 错误
4.const与#define相比有什么不同?
答案:
- const常量有数据类型,而宏常量没有数据类型。编译器可以对前者进行类型安全检查,而对后者只进行字符替换,没有类型安全检查,并且在字符替换中可能会产生意料不到的错误(边际效应)。
- 有些集成化的调试工具可以对const常量进行调试,但是不能对宏常量进行调试。在C++程序中只使用const常量而不使用宏常量,即const常量完全取代宏常量。
- 存储方式不同: 定义的常量存储在程序的数据段中,有固定的地址。而 #define 定义的常量只是简单的文本替换,在编译时会直接替换成对应的值,不会分配内存空间
- 作用域不同: 常量的作用域与其他变量类似,通常只在定义它的文件或函数内部可见。而 #define 定义的常量没有作用域限制,可以在整个程序中使用。
扩展:常量的引进是在早期的C++版本中,当时标准C规范正在制订。那时,常量被看做一个好的思想而被包含在C中。但是,C中的const的意思是“一个不能被改变的普通变量”。在C中,它总是占用内存,而且它的名字是全局符。C编译器不能把const看成一个编译期间的常量。在C中,如果写:
const bufsize = 100;
char buf[bufsize];
尽管看起来好像做了一件合理的事,但这将得到一个错误的结果。因为bufsize占用内存的某个地方,所以C编译器不知道它在编译时的值。在C语言中可以选择这样书写:
const bufsize;
这样写在C++中是不对的,而C编译器则把它作为一个声明,这个声明指明在别的地方有内存分配。因为C默认const是外部连接
的,C++默认const是内部连接
的,这样,如果在C++中想完成与C中同样的事情,必须用extern把内部连接改成外部连接
extern const bufsize;
这种方法也可用在C语言中。在C语言中使用限定符const不是很有用,即使是在常数表达式里(必须在编译期间被求出)想使用一个已命名的值,使用const也不是很有用的。C迫使程序员在预处理器里使用 #define
5.下列程序的输出结果是什么?
#include <iostream>
#include <stdio.h>
#include <string.h>
using namespace std;
struct {
short a1;
short a2;
short a3;
}A;
struct {
long a1;
short a2;
}B;
int main()
{
char *ss1 = "0123456789";
char ss2[] = "0123456789";
char ss3[100] = "0123456789";
int ss4[100];
char q1[] = "abc";
char q2[] = "a\n";
char *q3 = "a\n";
char *str1 = (char *)malloc(100);
void *str2 = (void *)malloc(100);
cout << sizeof(ss1) << " ";
cout << sizeof(ss2) << " ";
cout << sizeof(ss3) << " ";
cout << sizeof(ss4) << " ";
cout << sizeof(q1) << " ";
cout << sizeof(q2) << " ";
cout << sizeof(q3) << " ";
cout << sizeof(A) << " ";
cout << sizeof(B) << " ";
cout << sizeof(str1) << " ";
cout << sizeof(str2) << " ";
return 0;
}
解析:
- ss1是一个字符指针,指针的大小是一个定值,就是4字节,所以sizeof(ss1)是4字节
- ss2是一个字符数组,这个数组最初未定大小,由具体填充值来定。填充值
是“0123456789”。1个字符所占空间是1字节,10个就是10字节,再加上隐含的“\0”,所以一
共是11字节 - ss3也是一个字符数组,这个数组开始预分配100,所以它的大小一共是100字节
- ss4也是一个整型数组,这个数组开始预分配100,但每个整型变量所占空间是4,所以
它的大小一共是400字节 - q1与ss2类似,所以是4字节
- q2里面有一个“\n”,“\n”算做一位,所以它的空间大小是3字节
- q3是一个字符指针,指针的大小是一个定值,就是4,所以sizeof(q3)是4字节
- 结构体A中有3个short类型变量,各自以2字节对齐,结构体对齐参数按默认的8字节对齐,则a1、a2、a3都取2字节对齐,sizeof(A)为6,其也是2的整数倍
- B中a1为4字节对齐,a2
为2字节对齐,结构体默认对齐参数为8,则a1取4字节对齐,a2取2字节对齐;结构体大小为6字节,6不为4的整数倍,补空字节,增到8时,符合所有条件,则sizeof(B)为8 - str1与str2与ss1类似,都是指针的大小,所以为4
答案: 4 11 100 400 4 3 4 6 8 4 4
6.以下代码为32位机器编译,数据是以4字节为对齐单位,这两个类的输出结果是什么,为什么?
#include <iostream>
using namespace std;
class A {
private:
bool m_bTemp;
int m_nTemp;
bool m_bTemp2;
};
class B {
private:
int m_nTemp;
bool m_bTemp;
bool m_bTemp2;
};
int main()
{
cout << sizeof(A) << endl;
cout << sizeof(B) << endl;
return 0;
}
解析:
在访问内存时,如果地址按4字节对齐,则访问效率会高很多。这种现象的原因在于访问内存的硬件电路。一般情况下,地址总线总是按照对齐后的地址来访问的。例如你想得到0x00000001开始的4字节内容,系统首先需要以0x00000000读4字节,从中取得3字节,然后再用0x00000004作为开始地址,获得下一个4字节,再从中得到第一个字节,两次组合出你想得到的内容。但是如果地址一开始就是对齐到0x00000000,则系统只要一次读写即可。
答案:12 8
7.说明sizeof和strlen之间的区别
解析:
由以下几个例子我们说明sizeof和strlen之间的区别。
第1个例子:
char * ss = "0123456789";
sizeof(ss)结果为4,ss是指向字符串常量的字符指针。
sizeof(*ss)结果为1,*ss是第一个字符。
第2个例子:
char ss[] = "0123456789";
sizeof(ss)结果为11,ss是数组,计算到“\0”位置,因此是(10+1)。
sizeof(*ss)结果为1,*ss是第一个字符。
第3个例子:
char ss[100] = "0123456789";
sizeof(ss)结果为100,ss表示在内存中预分配的大小,100×1。
strlen(ss)结果为10,它的内部实现是用一个循环计算字符串的长度,直到“\0”为止。
第4个例子:
int ss[100] = "0123456789";
sizeof(ss)结果为400,ss表示在内存中的大小,100×4。
strlen(ss)错误,strlen的参数只能是char*,且必须是以“\0”结尾的。
第5个例子:
class X
{
int i;
int j;
char k;
};
X x;
cout<<sizeof(X)<<endl;结果为12,内存补齐。
cout<<sizeof(x)<<endl;结果为12,理由同上。
答案:
通过对sizeof与strlen的深入理解,得出两者区别如下:
- sizeof操作符的结果类型是size_t,它在头文件中的typedef为unsigned int类型。该类型保证能容纳实现所建立的最大对象的字节大小。
- sizeof是运算符,strlen是函数。
- sizeof可以用类型做参数,strlen只能用char*做参数,且必须是以“\0”结尾的。sizeof还可以用函数做参数。
- 数组做sizeof的参数不退化,传递给strlen就退化为指针。
- 大部分编译程序在编译的时候就把sizeof计算过了,是类型或是变量的长度。这就是sizeof(x)可以用来定义数组维数的原因。
- strlen的结果要在运行的时候才能计算出来,用来计算字符串的长度,而不是类型
占内存的大小。 - sizeof后如果是类型必须加括号,如果是变量名可以不加括号。这是因为sizeof是
个操作符而不是个函数。 - 当使用了一个结构类型或变量时,sizeof返回实际的大小。当使用一静态的空间数
组时,sizeof返回全部数组的尺寸。sizeof操作符不能返回被动态分配的数组或外部的数组的
尺寸。 - 数组作为参数传给函数时传的是指针而不是数组,传递的是数组的首地址,如
fun(char [8])、fun(char [])都等价于fun(char *)。在C++里传递数组永远都是传递指向数组首
元素的指针,编译器不知道数组的大小。如果想在函数内知道数组的大小,需要这样做:进
入函数后用memcpy将数组复制出来,长度由另一个形参传进去。代码如下:
void fun (unsigned char *p1, int len)
{
unsigned char* buf = new unsigned char[len+1];
memcpy(buf, p1, len);
}
- sizeof操作符不能用于函数类型、不完全类型或位字段。不完全类型指具有未知
存储大小数据的数据类型,如未知存储大小的数组类型、未知内容的结构或联合类型、void
类型等。
8.说明sizeof的使用场合
答案:
- sizeof操作符的一个主要用途是与存储分配和I/O系统那样的例程进行通信。例如:
void *malloc(size_t size);
size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream);
- 用它可以看看某种类型的对象在内存中所占的单元字节
- 在动态分配一对象时,可以让系统知道要分配多少内存
- 便于一些类型的扩充。在Windows中有很多结构类型就有一个专用的字段用来存放该类型的字节大小
- 由于操作数的字节数在实现时可能出现变化,建议在涉及操作数字节大小时用sizeof代替常量计算。
- 如果操作数是函数中的数组形参或函数类型的形参,sizeof给出其指针的大小
9. 这个数组占据多大空间?
int **a[3][4] = ?
A. 64
B. 12
C. 48
D. 128
解析:sizeof问题,3×4×4=48。
答案:C
10.一个空类占多少空间?多重继承的空类呢?
解析:我们用程序来实现一个空类和一个多重继承的空类。看看它们的大小是多少。代
码如下:
#include <iostream>
#include <memory.h>
#include <assert.h>
using namespace std;
class A {};
class A2 {};
class B : public A {};
class C : public virtual B {};
class D : public A, public A2 {};
int main()
{
cout << "sizeof(A): " << sizeof(A) << endl;
cout << "sizeof(B): " << sizeof(B) << endl;
cout << "sizeof(C): " << sizeof(C) << endl;
cout << "sizeof(D): " << sizeof(D) << endl;
return 0;
}
空类所占空间为1,单一继承的空类空间也为1,多重继承的空类空间还是1。
但是虚继承涉及虚表(虚指针),所以sizeof©的大小为4。
答案:1 1 4 1
11.内联函数和宏的差别是什么?
答案:
- 内联函数和普通函数相比可以加快程序运行的速度,因为不需要中断调用,在编译的时候内联函数可以直接被镶嵌到目标代码中。而宏只是一个简单的替换。
- 内联函数要做参数类型检查,这是内联函数跟宏相比的优势。
- inline是指嵌入代码,就是在调用函数的地方不是跳转,而是把代码直接写到那里去。对于短小的代码来说inline增加空间消耗换来的是效率提高,这方面和宏是一模一样的,但是inline在和宏相比没有付出任何额外代价的情况下更安全。至于是否需要inline函数,就需要根据实际情况来取舍了。
- inline一般只用于如下情况:
一个函数不断被重复调用。
函数只有简单的几行,且函数内不包含for、while、switch语句。
一般来说,我们写小程序没有必要定义成inline,但是如果要完成一个工程项目,当一个简单函数被调用多次时,则应该考虑用inline。
- 宏在C语言里极其重要,而在C++里用得就少多了。关于宏的第一规则是绝不应该去使用它,除非你不得不这样做。几乎每个宏都表明了程序设计语言里、程序里或者程序员的一个缺陷,因为它将在编译器看到程序的正文之前重新摆布这些正文。宏也是许多程序设计工具的主要麻烦。所以,如果你使用了宏,就应该准备只能从各种工具(如排错系统、交叉引用系统、轮廓程序等)中得到较少的服务。
- 宏是在代码处不加任何验证的简单替代,而内联函数是将代码直接插入调用处,而减少了普通函数调用时的资源消耗。
- 宏不是函数,只是在编译前(编译预处理阶段)将程序中有关字符串替换成宏体。
- 关键字inline必须与函数定义体放在一起才能使函数成为内联,仅将inline放在函数声明前面不起任何作用。如下风格的函数Foo不能成为内联函数:
inline void Foo(int x, int y); // inline仅与函数声明放在一起
void Foo(int x, int y) {}
而如下风格的函数Foo则成为内联函数:
void Foo(int x, int y);
inline void Foo(int x, int y) {} // inline与函数定义放在一起
所以说,inline是一种“用于实现的关键字”,而不是一种“用于声明的关键字”。内联能提高函数的执行效率,至于为什么不把所有的函数都定义成内联函数?如果所有的函数都是内联函数,还用得着“内联”这个关键字吗?内联是以代码膨胀(复制)为代价,仅仅省去了函数调用的开销,从而提高函数的执行效率。如果执行函数体内代码的时间,相比于函数调用的开销较大,那么效率的收获会很少。另一方面,每一处内联函数的调用都要复制代码,将使程序的总代码量增大,消耗更多的内存空间。
以下情况不宜使用内联:
1 如果函数体内的代码比较长,使用内联将导致内存消耗代价较高。
2 如果函数体内出现循环,那么执行函数体内代码的时间要比函数调用的开销大。类的构造函数和析构函数容易让人误解成使用内联更有效。要当心构造函数和析构函数可能会隐藏一些行为,如“偷偷地”执行了基类或成员对象的构造函数和析构函数。所以不要随便地将构造函数和析构函数的定义体放在类声明中。一个好的编译器将会根据函数的定义体,自动地取消不值得的内联(这进一步说明了inline不应该出现在函数的声明中)。