1. i++和++i哪个效率更高
- 从内建数据类型来看,去除编译器优化的影响,两者都只是简单地用于增加一元操作数,没有任何区别。
- 考虑自定义数据类型(类等),++i可以返回对象的引用;i++必须返回对象的值(必须产生一个临时对象保存更改前对象的值并返回),导致在大对象的时候产生了较大的复制开销,引起效率降低。
2. 两个变量值的交换方法
- 中间变量
- 加减运算,可能导致溢出
void swap(int &a, int &b)
{
a = a + b;
b = a - b;
a = a - b;
}
- 异或运算
void swap(int &a, int &b)
{
a ^= b;
b ^= a;
a ^= b;
}
3. 头文件引用中<>与""的区别
- <>表表明括号中的文件是一个工程或标准头文件,会首先查找系统目录,找不到再找当前目录。
- ""表示是用户提供的头文件,会从当前文件目录或文件名指定的目录寻找,找不到再查找系统目录。
4. #define与const的区别
- define只是简单的文本替换,预处理的时候编译器会做文本替换,因此#define常量的生命周期止于预处理期,它存在于程序的代码段,在实际程序中只是一个常数,一个命令中的参数,没有实际的存在。
- const存在于程序的数据段,于堆栈中分配了空间,它在程序中确确实实的存在着,并可以被调用、传递。const常量有数据类型,可以进行类型安全检查。
5. const的作用
- 定义常量。
- 修饰函数形式参数。函数输入参数为自定义类型时,传引用比传值效率更高(临时对象)。但只用引用有可能改变原值,所以加const。
- 修饰函数的返回值。如果给“指针传递”的函数返回值加const,则返回值不能被直接修改,且只能被赋值给const修饰的同类型指针。
- 修饰类的成员函数。不需要修改数据成员的函数都应该用const修饰,这样不小心修改了数据成员或调用了非const成员函数,编译器会报错。
6. static的作用
- 在函数体,一个被声明为静态的变量在函数被调用的过程中维持其值不变。
- 模块内函数体外,被声明为静态的变量和函数可以被模块内所有函数访问,但不能被模块外访问。
- 类中的static数据成员,属于类所有,所有的对象使用同一份数据。
- 类中的static成员函数,不接受this指针,只能访问static数据成员。
7. sizeof和strlen的区别
- sizeof是操作符,strlen是函数。
- strlen只能用char*做参数,且必须"\0"结尾,sizeof可以计算任意类型。
- sizeof计算的是类型占内存的大小,strlen用来计算字符串的长度。
8. C++为什么引入内联函数
- 替代宏定义。宏定义的缺点:只是简单的文本替换,无法进行有效性检测和类型安全检查,另外它的返回值也不能被强制转换为可转换的合适类型。内联函数是一个真正的函数,可以进行一系列的相关检查,保证调用正确。
- 内联函数代码被放入符号表(我们把函数和变量统称为符号,每个目标文件都有一个符号表,记录了目标文件中用到的所有符号)中,使用时直接替换,没有调用的开销,效率很高。
- 作为某个类的成员函数,使用类的保护成员和私有成员。(在类内定义的成员函数默认为inline,当然,是否内联还是取决于编译器。宏定义做不到,因为无法将this指针放在合适的位置)
9. 什么情况下不宜使用内联函数
- 函数代码比较长。会导致内存消耗代价较高。
- 函数内出现循环。执行函数的时间要比函数调用的开销大。
- 构造和析构函数。可能会执行基类的构造析构函数,无限套娃。
10. 指针和引用的区别
- 初始化要求不同。引用创建时必须初始化,而指针不必初始化。
- 可修改性不同。引用一旦被初始化,就不能被另一个对象引用;而指针在任何时候都可以指向另一个对象。
- 不存在NULL引用,而指针可以是NULL。
11. 复杂指针的声明
- 定义一个整数:int a;
- 定义一个指向整数的指针:int *a;
- 定义一个指向指针的指针,它指向的指针指向一个整数:int **a;
- 定义一个有十个整数的数组:int a[10];
- 定义一个有十个指针的数组,指针指向整数:int *a[10]
- 定义一个指向有十个整数数组的指针:int (*a)[10]
- 定义一个指向函数的指针,该函数有一个整型参数并返回一个整数:int (*a)(int)
- 定义一个有十个指针的数组,指针指向函数, 该函数有一个整型参数并返回一个整数:int(*a[10])(int)
12. 指针运算
指针的运算就是地址的运算,因此指针运算不同于普通变量,它只允许有限的几种运算:
- 指针指向某一存储单元,静态存储区、栈、堆上有所不同:
https://blog.csdn.net/qq_29762941/article/details/80738129 - 指针与整数相加或相减,用来移动指针:对指针进行+1操作,得到的是下一个元素的地址,而不是原地址+1,所以类型为t的指针的移动是以sizeof(t)为移动单位。
int a[5] = {1,2,3,4,5};
//a与&a的地址是一样的,但含义不一样,a是数组首地址,也就是a[0]的地址,&a是数组的地址。
//a+1是数组下一元素的地址,即a[1],而&a+1是下一个数组的地址,即a[5],这时候已越界,一般需要强制类型转换。
- 指针比较,得到存储位置的先后。
- 指针间相加减,得到两个地址之间的数据个数。
13. 指针常量和常量指针
- 常量指针:指向常量的指针,本身可改,但不能改所指向的常量。
- 指针常量:指针是常量,本身不可改,但可改指向的对象。
#include<iostream>
using namespace std;
int main()
{
char *str[] = { "welcome", "to", "fortemedia", "nanjing" };
char **p = str + 1;
str[0] = (*p++) + 2;
str[1] = *(p+1);
str[2] = p[1] + 3;
str[3] = p[0] + (str[2]-str[1]);
cout << str[0] << endl;
cout << str[1] << endl;
cout << str[2] << endl;
cout << str[3] << endl;
return 0;
};
14. this指针
C++中,对象的this指针并不是对象本身的一部分,不会影响sizeof(对象)的结果。this指针的作用域是类内部,在类的非静态成员函数中访问类的非静态成员时,编译器会自动将对象地址作为一个隐含参数传递给函数。
15. typedef
在编程中使用typedef目的一般有两个,一个是给变量一个易记且意义明确的新名字,另一个是简化一些比较复杂的类型声明。
16. malloc/free和new/delete的区别
- malloc与free是C++/C语言的标准库函数,new/delete是C++的运算符。
运算符是语言自身的特性,它有固定的语义,而且编译器也知道意味着什么。就像 +-* 一样,由编译器解释语义,生成相应的代码;库函数是依赖于库的,没有库就没有它,也就是一定程度上独立于语言的。理论上,编译器不知道也不关心函数的作用,编译器只保证编译函数,以及调用该函数时参数和返回值符合语法,并生成相应 call 函数的代码。但实际中一些高级点的编译器,都会对标准库自带的一些函数进行特别处理。 - 对于非内部数据类型的对象而言,仅用maloc/free 无法满足动态对象的要求。对象在创建的同时要自动执行构造函数,对象在消亡之前要自动执行析构函数。由于malloc/free是库函数而不是运算符,不在编译器控制权限之内,因此不能把执行构造函数和析构函数的任务强加于malloc/free函数。
- new操作符从自由存储区(free store)上为对象动态分配内存空间,而malloc函数从堆上动态分配内存。
自由存储区是C++基于new操作符的一个抽象概念,凡是通过new操作符进行内存申请,该内存即为自由存储区。而堆是操作系统中的术语,是操作系统所维护的一块特殊内存,用于程序的内存动态分配。
自由存储区是否能够是堆,取决于operator new 的实现细节。 - new返回的是对象类型的指针,而malloc内存分配成功则是返回void *。
- new内存分配失败时,会抛出bac_alloc异常,它不会返回NULL;malloc分配内存失败时返回NULL。
- C++提供了new[]与delete[]来专门处理数组类型,malloc需要手动自定数组的大小。
参考:https://www.cnblogs.com/QG-whz/p/5140930.html
17. 各种标准内存分配函数的使用
- malloc 调用形式为(类型*)malloc(size)。在内存的动态存储区中分配长度为“size”字节的连续区域,返回该区域的首地址,此时内存中的值没有初始化,是一个随机数。
- calloc调用形式为(类型*)calloc(n,size)。在内存的动态存储区中分配n块长度为“size”字节的连续区域,返回首地址,此时内存中的值都被初始化为0。
- realloc调用形式为(类型)realloc(ptr,size)。将ptr内存大小增大到“size”,新增加的内存块没有初始化。
- free的调用形式为free(void*ptr):释放ptr所指向的内存空间。
- C++中用new和delete函数,调用类的构造函数和析构函数。
18. 使用库函数将数字转换为字符串
- itoa():将整型值转换为字符串。
itoa(num_int, str_int, 10) //把整数num_int转换为字符串str_int,10表示十进制
- ltoa():将长整型值转换为字符串。
- ultoa():将无符号长整型值转换为字符串。
- gcvt():将浮点型数转换为字符串,取四舍五入。
gcvt(num_double, 8, str_double) //把浮点数num_double转换为字符串str_double,8表示精确位数
- ecvt(double value, int ndigit, int *decpt, int *sign)
将双精度浮点型值转换为字符串,转换结果中不包含十进制小数点。
value:待转换的双精度浮点数。
ndigit:存储的有效数字位数。如果value中的数字个数超过ndigit,低位数字被舍入。如果少于ndigit个数字,该字符串用0填充。
*decpt:存储的小数点位置。0或负数指小数点在第一个数字的左边。
*sign:转换的数的符号。如果该整数为0,这个数为正数,否则为负数。 - fcvt():ndigit是小数点后面的位数,其余同ecvt()。
19. 使用库函数将字符串转换为数字
- atof():将字符串转换为双精度浮点型值。
- atoi():将字符串转换为整型值。
- atol():将字符串转换为长整型值。
- strtod():将字符串转换为双精度浮点型值,并报告不能被转换的所有剩余数字。
- strtol():将字符串转换为长整值,并报告不能被转换的所有剩余数字。
- strtoul():将字符串转换为无符号长整型值,并报告不能被转换的所有剩余数字。
20. 关键字volatile有什么含义
定义为volatile的变量可能会被意想不到地改变,这样,编译器就不会去假设这个变量的值。准确地说,优化器在用到volatile变量时必须小心地重新读取该变量的值,而不是使用保存在寄存器里的备份。
21. 临时对象
临时对象不是局部变量,它是不可见的,会执行构造和析构函数,对程序性能可能产生影响。三种常见的创建情况:
- 以值的方式给函数传参
- 类型转换
#include <iostream>
using namespace std;
class TempObj {
public:
TempObj(int m = 0, int n = 0);
TempObj(TempObj& to)
{
cout << "Copy Constructor" << endl;
m_Number = to.m_Number;
m_Size = to.m_Size;
}
~TempObj()
{
//cout << "Deconstructor" << endl;
}
int GetSum(TempObj &ts);
public:
int m_Number;
int m_Size;
};
TempObj::TempObj(int m, int n)
{
cout << "Default constructor" << endl;
m_Number = m;
m_Size = n;
cout << "m_Number=" << m_Number << " " << "m_Size=" << m_Size << endl;
}
int TempObj::GetSum(TempObj &ts)
{
int tmp = ts.m_Number + ts.m_Size;
ts.m_Number = 1000;
return tmp;
}
void main()
{
TempObj tm(10, 20), sum;
sum = 1000;
cout << "sum=" << tm.GetSum(sum) << endl;
}
main函数创建了两个对象,但输出却调用了三次构造函数。关键在sum = 1000;本身1000和sum类型不符,但编译器为了通过编译以1000为参数调用构造函数创建了一个临时对象。
- 函数需要返回对象时
22. 判断处理器使用Big_endian还是Little_endian模式
联合体union的存放顺序是所有成员都从低地址开始存放,利用该特性,轻松地了解CPU对内存采用的读写模式。若处理器使用Big_endian 模式存储数据,则返回 0;若是用Little_endian模式存储数据,则返回1。
int checkCPU()
{
union w
{
int a;
char b;
}c;
c.a = 1;
return(c.b == 1);
}
23. struct和class的区别
- C的struct与C++的class的区别:struct只是作为一种复杂数据类型定义,不能包含成员函数,不能用于面向对象编程。
- C++中的struct 和 class的区别:对于成员访问权限以及继承方式,class 中默认的是private,而struct中则是public。class还可以用于表示模板类型,struct则不行。
24. 类成员的初始化
class Obj
{
public:
Obj(int k) :j(k),i(j)
{
}
private:
int i;
int j;
};
这里很容易让人误认为先对j进行初始化,然后再用j对i进行初始化。实际上,初始化列表的初始化顺序与变量声明的顺序一致,而不是按照出现在初始化列表中的顺序。这里成员i比成员j先声明,因此正确的顺序是先用j对i进行初始化,然后再对j进行初始化。由于在对i进行初始化时j尚未被初始化,j的值为随机值,故i的值也为随机值。
25. 初始化列表和赋值的区别
当类中含有const、reference成员变量以及基类的构造函数都需要初始化列表。
26. 静态成员变量和静态成员函数
- 为了与非静态成员变量相区别,静态成员变量不能在类内部被初始化。可以把静态成员变量放在类定义外面初始化。
- 静态成员函数和静态成员变量一样,不属于类的对象,因此不含this指针,也就无法调用类的非静态成员。
27. 空类默认情况下会产生哪些类成员函数?
- 默认构造函数和复制构造函数,它们被用于类的对象的构造过程。
- 析构函数,它被用于类的对象的析构过程。
- 赋值函数,它被用于同类的对象间的赋值过程。
- 取值运算,当对类的对象进行取地址(&)时,此函数被调用。
28. explicit构造函数
普通构造函数能够被隐式调用,而explicit构造函数只能被显示调用。
29. 构造函数的执行顺序与构造函数相反。
30. 什么是复制构造函数,以及使用它的场合。
复制构造函数是一种特殊的构造函数,它由编译器调用来完成一些基于同一类的其他对象的构造及初始化。在C++中,3种对象需要复制,此时复制构造函数将会被调用:
- 一个对象以值传递的方式传入函数体。
- 一个对象以值传递的方式从函数返回。
- 一个对象需要通过另外一个对象进行初始化。
31. 一组重载函数中,只能有一个函数被指定为extern "C"。
32. C++虚函数实现细节
简单地说,虚函数是通过虚函数表实现的。事实上,如果一个类中含有虚函数,则系统会为这个类分配一个指针成员指向一张虚函数表(vtbl),表中每一项指向一个虚函数的地址,实现上就是一个函数指针的数组。
虚函数表既有继承性又有多态性。每个派生类的虚函数表继承了它各个基类的虚函数表,如果基类虚函数表中包含某一项,则派生类的虚函数表中也将包含同样的一项,但是两项的值可能不同。如果派生类覆盖(override)了该项对应的虚函数,则派生类虚函数表的该项指向重载后的虚函数,在没有重载的情况下,则沿用基类的值。
在类对象的内存布局中,首先是该类的虚函数表指针,然后才是对象数据。在通过对象指针调用虚函数时,编译器生成的代码将先获取对象类的虚函数表指针,然后调用虚函数表中对应的项。对于通过对象指针调用的情况,在编译期间无法确定指针指向,但是在运行期间执行到调用语句时,这一点已经确定,编译后的调用代码能够根据具体对象获取正确的虚函数表,调用正确的虚函数,从而实现多态性。
33. 在构造函数中,虚拟机制不会发生作用。因为基类的构造函数在派生类的构造函数之前执行,基类构造函数运行时,派生类的成员还没有初始化,向下匹配是危险的。
34. 多重继承的缺点
- 同名二义性。基类成员有相同的名称。
- 路径二义性。如果派生类所继承的多个基类有相同的基类,而派生类对象需要调用这个祖先类的接口方法,产生路径二义性。