C++面试一百问

201 阅读8分钟

全局数组未初始化会全部置0,保存在堆中,局部数组未初始化可能包含垃圾值,保存在栈中。

今天开始记录一下C++面试一百问,B站上的一个视频,其实也没什么特别的目的,多了解一下也好,每天看几问就当消遣了,其实也很快。

C++面试100问

include双引号与尖括号的区别

include会把文件的内容 包含到当前文件中,尖括号表示是一个文件或标准头文件,搜索路径环境变量。双引号是用户提供的头文件,从当前目录查找

const

优点

  1. 便于类型检查,保护实参
  2. 类似于宏定义,方便参数修改
  3. 节省空间 const max=100,这时a=max不用再分配空间
  4. 为函数重载提供参考

常量指针和指针常量

谁在前读谁*(指针)const(常量)

*const p2 = &b指针常量 (指针指向的地址不变)从右向左看,p2先和const结合说明p2是内容然后是指针

const* p2 = &c常量指针(内容不变)从右向左先结合指针说明p是指针然后指针是常量

C++11新特性

1.初始化列表vectora1,1vector a{1,1} 2.auto关键字(1.定义变量时必须初始化2.不能用于函数参数3.不能定义数组4.不能用于类的非静态成员变量) 3.decltype从表达式推断出类型 4.for循环for (auto n:a) 5.nullptr关键字(取代NULL) 6.lambda表达式[capture list](parameter list)->return type {function body} eg. auto sum = [](int a,int b)->int {return a+b;} 7.智能指针

智能指针

auto_ptr,unique_ptr,share_ptr,weak_ptr 1.为什么用智能指针? 更安全更方便地管理内存 2.如何工作? unique_ptr独占,只能用move移交控制权

share_ptr内部有计数功能,当指向的指针个数等于0时会自动释放

weak_ptr指向share_ptr但是是弱指针不会引起计数改变只是用来观察

内存泄漏

分配内存后由于设计失误,失去对内存的控制

分类

堆内存泄漏

系统资源泄漏,系统分配给程序的资源

没有将基类的析构函数设置为虚函数

未使用delte[]而是delete

缺少拷贝构造函数导致浅拷贝(被拷贝对象未释放)

linux下可用valgrind检查

解决方法

可以使用智能指针

new/delete与malloc/free的区别

new 和delete 是关键字,是一种操作符,可以被重载

malloc 和free是C语言库的函数,不能重载

malloc使用时需要自己显示地计算内存大小,new是使用编译器自动计算

malloc分配成功返回void* 指针,需要强制类型转换,new直接返回对应类型指针

new 和delete分别调用构造函数和析构函数,malloc和free只能申请和释放内存,不会调用构造函数和析构函数

delete和free被调用后,内存不会立即回收,指针也不会指向空,会出现野指针情况,应该把指针指向NULL。

运行时多态

多态主要分为静态多态和动态多态,静态多态主要是重载,编译时已经确定,动态多态通过虚函数机制实现,运行时动态绑定

动态多态

动态多态是指基类的指针指向其派生类的对象,通过基类的指针调用派生类成员函数(必须是虚函数) ,如果不是虚函数,执行的则是基类函数

Base a;
Derived b;
Base* p1;
Base* p2l
p1->f1();
p2->f2();

多态是想让基类指针具有多种形态,能够尽量少写代码的情况下让基类实现更多的功能

静态函数在编译时已经确定,虚函数实在运行时动态绑定的,会增加内存开销

哈希表

哈希表关键字key和值value访问的数据结构

关键字k存储位置p,p=f(K),f称为哈希函数

哈希函数构造原则

1.便于计算 2.地址分布均匀,不易冲突

常见构造方法

1.数字分析法

2.平方取中法

3.分段叠加法

4.除留余数法

5.伪随机数法

哈希冲突

哈希函数产生的值有限,当不同的key映射到相同的位置时,产生冲突

解决方法

开放地址法

H(H(key))不断以输出作为输入直到不冲突

1.线性探测

d_i =1,2,3,...

发生冲突时,顺序查看表中下一个单元,直到找出一个不冲突的位置

2.再平方探测

d_i = 1^2,-1^2,2^2,-2^2,... 左右表跳跃试探

3.伪随机探测

建立伪随机发生器产生序列

链式地址法

冲突值缀在相同地址上

公共溢出区

哈希表分为基本表和溢出表两部分,凡是冲突的放入溢出表

再哈希表

冲突的哈希值,构造另一个哈希函数处理,直到没有哈希冲突

strlen/sizeof/length/size

strlen是一个函数,只能用char* 作为参数,计算字符串长度,不包括结束字符'\0'

sizeof是单目运算符,参数可以是数组,指针,字符串,对象等,在统计字符串长度时,包含结束字符'\0'

eg.

char* s1="0123456789";
sizeof(s1)=8;指针大小
sizeof(*s1)=1;指针指向的第一个字符
strlen(s)=10;字符串长度

char s2[]="0123456789";
sizeof(s2)=11;
sizeof(*s2)=1;
strlen(s2)=10;

结构体为了CPU存取速度快,会进行数据对齐,但可能浪费内存

string 的length()和size()作用一样,为了兼容,length()是C语言习惯保留,size()是STL容器属性

虚函数

1.在基类内部声明的成员函数,添加关键字virtual指明的函数

2.虚函数存在的意义是为了实现多态,让派生类鞥能够重写基类成员函数

3.派生类重写基类时可添加virtual关键字但不必须

4.动态绑定,运行时确定

5.必须是非静态成员函数,静态成员函数需要在编译时确定

6.构造函数不能是虚函数,虚函数动态绑定,但构造函数创建时需要确定对象类型

7.析构函数一般是虚函数

8.虚函数一旦声明,一直是虚函数

9.virtual int fun()=0;纯虚函数

工作机制

虚函数表+虚表指针

1.编译器创建虚函数表vtable,以及虚表指针vptr

2.派生类继承基类时,也会继承基类的虚函数表

3.派生类重写虚函数会将虚函数地址替换掉

4.派生类没有重写,则直接保存继承来的

类的所有对象共享虚函数表,而非每个对象都有一个虚函数表

左值引用和右值引用

左值:可以取地址的,有名字的,非临时的

右值:不能取地址的,没有名字的,临时的

左值引用要求右边的值必须能够取地址,如果无法取地址,可以用常引用。但使用常引用后,只能通过引用来读取数据,无法去修改数据。

右值引用时C++11新特性,用来绑定到右值,绑定或,本来会被销毁的右值的生存期会延长到与绑定到它的右值引用的生存期。

定义: int &&c =10;

std:move()可以把右值引用指向左值

const 左值引用可以指向右值

声明出来的左右值引用都是左值

都可以避免拷贝

右值引用更灵活,可以修改值

深拷贝与浅拷贝

浅拷贝时通过拷贝构造函数实现的,如果程序员不主动编写,缺省函数只是简单地赋值某个对象的指针

深拷贝必须显式地提供拷贝构造函数和赋值运算符,新旧对象不共享内存

深拷贝情况:

1.对象以值传递地方式传入函数体

2.对象以值传递地方式从函数体返回

3.对象需要通过另外一个对象初始化

堆和栈地区别

管理方式:栈是有编译器自动申请和释放空间,堆需要手动申请和释放

空间大小: 栈的空间有限,默认1M,堆最大可到4G

碎片:栈先进后出,不会产生碎片,如果不断调用malloc,则会造成内存碎片

生长方向:堆向上,栈向下

分配方式:堆都是动态分配的,栈有静态和动态分配

分配效率:栈的效率比堆高的多

静态全局变量与全局变量:

1.都属于常量区

2.静态全局区只在本文件有效,全局变量可在别的文件中使用

3.如果别的文件定义该全局变量相同变量名,报错

静态局部变量和局部变量:

1.静态局部属于常量区,函数内部的局部变量属于栈区

2.静态局部变量在该函数调用结束时,不会销毁,局部变量则不同

3.如果没有初始值,静态局部变量初始化为0,局部变量为随机值

4.静态局部变量编译期赋值一次,局部变量每调用一次赋值一次