C++面试题总结
堆、栈、自由存储区、全局/静态存储区、常量存储区 自由存储区存储malloc申请的内存 (1)从静态存储区域分配 。内存在程序编译的时候就已经分配好,这块内存在程序的整个运行期间都存在。例如 全局变量, static 变量 。 (2)在栈上创建 。在执行函数时, 函数内局部变量的存储单元都可以在栈上创建 ,函数执行结束时这些存储单元自动被释放。栈内存分配运算内置于处理器的指令集。 (3)从堆上分配 , 亦称动态内存分配 。程序在运行的时候用 malloc 或 new 申请任意多少的内存,程序员自己负责在何时用 free 或 delete 释放内存。动态内存的生存期由程序员决定,使用非常灵活,但问题也最多。
extern “C”的主要作用就是为了能够正确实现C++代码调用其他C语言代码。加上extern “C”后,会指示编译器这部分代码按C语言的进行编译,而不是C++的。 (http://www.cnblogs.com/yc_sunniwell/archive/2010/07/14/1777431.html)
void * ( * (*fp1)(int))[10]; //fp1是一个指针,指向一个函数,函数参数为int,函数返回参数是一个指针,指针指向一个数组,数组中有10个元素,每个元素是一个void* 指针。
float (*(* fp2)(int,int,int))(int); //fp2是一个指针,指向一个函数,函数参数为3个int,函数的返回值是一个指针,指针指向一个函数,函数的参数是1个int,返回float。
int (* ( * fp3)())[10](); //fp3是一个指针,指向一个函数,函数没有参数,函数返回值为一个指针,指针指向一个数组,数组中有10个元素,每个元素是一个函数指针,函数没有参数,返回int。
区别与联系:
https://blog.csdn.net/21aspnet/article/details/1539951
1.在存储多个成员信息时,编译器会自动给struct第个成员分配存储空间,struct 可以存储多个成员信息,而Union每个成员会用同一个存储空间,只能存储最后一个成员的信息。
2.都是由多个不同的数据类型成员组成,但在任何同一时刻,Union只存放了一个被先选中的成员,而结构体的所有成员都存在。
3.对于Union的不同成员赋值,将会对其他成员重写,原来成员的值就不存在了,而对于struct 的不同成员赋值 是互不影响的。
未特殊说明时,按结构体中size最大的成员对齐(若有double成员),按8字节对齐。 eg:
struct sTest
{
int a; //sizeof(int) = 4
char b; //sizeof(char) = 1
short c; //sizeof(short) = 2
}x; #最终实际占用不止4+1+2,因为要考虑内存对齐的问题
union uTest
{
int a; //sizeof(int) = 4
double b; //sizeof(double) = 8
char c; //sizeof(char) = 1
}x; #分配的内存 size 就是8 byte
pragma pack () 取消指定对齐,恢复缺省对齐
总之,一般尽量用const比较好。
一般来说,const的分辨可以直接通过看const的最左侧,如果是指针,则指针是const,若为类型,则变量为const。当const在最左侧时,看const右侧。
int const *p
或const int *p
。
指针常量是一个常量,指针的值可以改变。如int *const p
。 int *p[10]
int (*p)[10]
int *p(int)
int (*p)(int)
```
- int *p[10]表示指针数组,强调数组概念,是一个数组变量,数组大小为10,数组内每个元素都是指向int类型的指针变量。
- int (*p)[10]表示数组指针,强调是指针,只有一个变量,是指针类型,不过指向的是一个int类型的数组,这个数组大小是10。
- int *p(int)是函数声明,函数名是p,参数是int类型的,返回值是int *类型的。
- int (*p)(int)是函数指针,强调是指针,该指针指向的函数具有int类型参数,并且返回值是int类型的。
##### 指针与数组名
- 二者均可通过增减偏移量来访问数组中的元素。
- 数组名不是真正意义上的指针,可以理解为常指针,所以数组名没有自增、自减等操作。
- 当数组名当做形参传递给调用函数后,就失去了原有特性,退化成一般指针,多了自增、自减操作,但sizeof运算符不能再得到原数组的大小了。
##### 野指针
空悬指针,不是指向null的指针,是指向垃圾内存的指针。
- 产生原因及解决办法:
- 指针变量未及时初始化 => 定义指针变量及时初始化,要么置空。
- 指针free或delete之后没有及时置空 => 释放操作后立即置空。
##### 指针和数组的区别
数组要么在静态存储区被创建(如全局数组),要么在栈上被创建。指针可以随时指向任意类型的内存块。
1. 修改内容上的差别:指针可能指向一块内存,但是指向的常量却无法通过下标计算。
char a[] = “hello”; a[0] = ‘X’; char *p = “world”; // 注意p 指向常量字符串,指向的是常量区 p[0] = ‘X’; // 编译器不能发现该错误,运行时错误
1 | 2. 用运算符sizeof 可以计算出数组的容量(字节数)。sizeof(p),p 为指针得到的是一个指针变量的字节数,而不是p 所指的内存容量。C++/C 语言没有办法知道指针所指的内存容量,除非在申请内存时记住它。注意当数组作为函数的参数进行传递时,该数组自动退化为同类型的指针。 |
---|
char a[] = “hello world”; char *p = a; cout<< sizeof(a) << endl; // 12 字节 cout<< sizeof(p) << endl; // 4 字节 //计算数组和指针的内存容量 void Func(char a[100]) { cout<< sizeof(a) << endl; // 4 字节而不是100 字节 }
### volatile
- volatile定义变量的值是易变的,每次用到这个变量的值的时候都要去重新读取这个变量的值,而不是读寄存器内的备份。
- 多线程中被几个任务共享的变量需要定义为volatile类型。
### 堆栈
#### 区别
1. 申请方式不同:栈由系统自动分配,堆由程序员手动分配
2. 申请大小不同:栈顶和栈底都是设定好的,大小固定,可以通过`ulimit -a`查看,通过`ulimit -s`修改。堆向高地址扩展,是不连续的内存区域,大小可以调整。
3. 申请效率不同:栈由系统分配,速度快,没有碎片。堆速度慢,且有碎片。
#### 内存分配
https://blog.csdn.net/nkguohao/article/details/8771867
## 面向对象
### 面向对象三大特性
- 封装性:数据和代码捆绑在一起,避免外界干扰和不确定性访问。
- 继承性:让某种类型对象获得另一个类型对象的属性和方法。
- 多态性:同一事物表现出不同事物的能力,即向不同对象发送同一消息,不同的对象在接收时会产生不同的行为(重载实现编译时多态,虚函数实现运行时多态)。
### 构造函数和析构函数
1.构造函数、析构函数中都不要调用虚函数
我们知道,构造函数一般不能是虚函数,而析构函数一般必须是虚函数。原理也很清晰,构造函数,由于构造顺序是从基类到派生类,所以调用虚函数,可能派生类还没有构造出来,没有意义。而对于析构函数来说,又必须是虚函数,因为只有先从子类对象进行销毁,才能保证资源不泄露。
在构造函数和析构函数中都不要调用虚函数也是这个道理。
### 成员变量和成员函数
1.静态成员变量是需要初始化
其实这样说的是有点问题的,应该是静态成员是需要定义的。
因为静态成员属于整个类,而不属于某个对象,如果在类内初始化,会导致每个对象都包含该静态成员,这是矛盾的。
《c++primer》里面说在类外定义和初始化是保证static成员变量只被定义一次的好方法。 但static const int就可以在类里面初始化
class Base{ public: static int class_p; //只有声明,而没有定义,不能直接调用 }; int Base::class_p=3; //进行定义 https://blog.csdn.net/qq_16209077/article/details/52602601 ```
调用情况:
一般会默认生成类的拷贝构造函数,但是当涉及动态分配存储空间时,默认的拷贝构造函数就会有问题,因此需要重写拷贝构造函数,并且采用深拷贝。 浅拷贝和深拷贝:
多态:对于不同对象接收相同消息时产生不同的动作。C++的多态性具体体现在运行和编译两个方面: 编译时多态:函数和运算符的重载。 运行时多态:继承和虚函数。
特性:单向的,传递性,不能继承
预处理->编译->汇编->链接
.a
结尾 .so
结尾
当静态库和动态库同名时, gcc命令将优先使用动态库.为了确保使用的是静态库, 编译时可以加上 -static 选项,因此多第三方程序为了确保在没有相应动态库时运行正常,喜欢在编译最后应用程序时加入-static
优缺点:
1.动态库运行时会先检查内存中是否已经有该库的拷贝,若有则共享拷贝,否则重新加载动态库(C语言的标准库就是动态库)。静态库则是每次在编译阶段都将静态库文件打包进去,当某个库被多次引用到时,内存中会有多份副本,浪费资源。
2.动态库更新很容易,当库发生变化时,接口没变只需要用新的动态库替换掉就可以。静态库需要重新编译。
3.静态库静态库一次性完成了所有内容的绑定,运行时就不必再去考虑链接的问题了,执行效率会高一些。 类型安全很大程度上可以理解为内存安全。类型安全的代码不会试图去访问自己没有被授权的内存区域。
对于C语言来说,很多操作都不是类型安全的。例如打印的时候:printf("%f\n",10) //编译通过,没有报错,结果为0.000000
.
对于C++来说,有些操作也不是类型安全的,比如不同类型指针之间可以强制转换(reinterpret cast) 注:C#、Java是类型安全的
C++使用得当,可以远比C更有类型安全性。
(1)操作符new返回的指针类型严格与对象匹配,而不是void;
(2)C中很多以void为参数的函数可以改写为C++模板函数,而模板是支持类型检查的;
(3)引入const关键字代替#define constants,它是有类型、有作用域的,而#define constants只是简单的文本替换;
(4)一些#define宏可被改写为inline函数,结合函数的重载,可在类型安全的前提下支持多种类型,当然改写为模板也能保证类型安全;
(5)C++提供了dynamic_cast关键字,使得转换过程更加安全,因为dynamic_cast比static_cast涉及更多具体的类型检查。
如果代码在多线程运行和单线程运行具有相同的结果,那就是线程安全的。 线程安全问题都是由全局变量及静态变量引起的。若每个线程中对全局变量、静态变量只有读操作,而无写操作,一般来说,这个全局变量是线程安全的;若有多个线程同时执行写操作,一般都需要考虑线程同步,否则的话就可能影响线程安全。
当异常抛出时,带有异常安全的函数会: (1)不泄露任何资源 一般采用RAII技术,即以对象管(智能指针)理资源来防止资源泄漏。 (2)不允许数据被破坏(例如正常指针变野指针) (3)少些try catch,因为大量的try catch会影响代码逻辑。导致代码丑陋混乱不优雅 解决异常安全的问题: 1.多使用RAII,使用智能指针来管理内存。由于unwind机制的保证,当异常发生时,函数栈内已构造的局部对象的析构函数会被一一调用,在析构函数内释放资源,也就杜绝了内存泄漏的问题。 2.做好程序设计。特别是异常发生时的回滚机制的正确使用,copy-and-swap是有效的方法。 3.注意需要异常保证的函数内部的调用函数,异常安全等级是以有最低等级异常保证的函数确定的。一个系统即使只有一个函数不是异常安全的,那么系统作为一个整体就不是异常安全的。 4.流对象,资源对象,new对象,不应该直接作为参数,一旦抛出异常,就可能会导致严重的问题,函数也许会被错误的执行,资源也许会泄漏。 5.减少全局变量的使用。 6.如果不知道如何处理异常,就不要捕获异常,直接终止比吞掉异常不处理要好。 7.保证构造、析构、swap不会失败
1,元素的话,一个个比咯:if(p1->age==p2->age)…有一个元素不等,即是两个实例不相等!没什么效率高的方法吧!
2,指针直接比较,如果保存的是同一个实例地址,则(p1==p2)为真!
3,重载==运算符;
###
常见C++面试题