C++面经

1. vector是怎么实现的?

新增元素:Vector通过一个连续的数组存放元素,如果集合已满,在新增数据的时候,就要分配一块更大的内存,将原来的数据复制过来,释放之前的内存,在插入新增的元素。插入新的数据分在最后插入push_back和通过迭代器在任何位置插入,这里说一下通过迭代器插入,通过迭代器与第一个元素的距离知道要插入的位置,即int index=iter-begin()。这个元素后面的所有元素都向后移动一个位置,在空出来的位置上存入新增的元素。
删除元素:删除和新增差不多,也分两种,删除最后一个元素pop_back和通过迭代器删除任意一个元素erase(iter)。通过迭代器删除还是先找到要删除元素的位置,即int index=iter-begin();这个位置后面的每个元素都想前移动一个元素的位置。同时我们知道erase不释放内存只初始化成默认值。
删除全部元素clear:只是循环调用了erase,所以删除全部元素的时候,不释放内存。内存是在析构函数中释放的。

2. 使用vector有什么要注意的?

3. 一个程序的内存模型是怎么样的?他们都有什么用?

C++分为五个区:堆,栈,静态全局变量去,常量区,自由存储区。
根据c/c++对象生命周期不同,c/c++的内存模型有三种不同的内存区域,即自由存储区,动态区、静态区。
自由存储区:局部非静态变量的存储区域,即平常所说的栈
动态区: 用operator new ,malloc分配的内存,即平常所说的堆
静态区:全局变量 静态变量 字符串常量存在位置
而代码虽然占内存,但不属于c/c++内存模型的一部分

4. 四次挥手的time_wait有什么用?

保证TCP协议的全双工连接能够可靠关闭
保证这次连接的重复数据段从网络中消失

5. 如果出现了非常多的time_wait,会有什么影响?怎么解决?

什么影响?
当大量的连接处于 time_wait 时,新建立 TCP 连接会出错,address already in use : connect 异常
解决办法?
1、客户端,HTTP 请求的头部,connection 设置为 keep-alive,保持存活一段时间:现在的浏览器,一般都这么进行了
2、服务器端
允许 time_wait 状态的 socket 被重用
缩减 time_wait 时间,设置为 1 MSL(即,2 mins)

6. c++多态如何实现?

静态多态:在编译期间就已经确定的,比如函数重载,函数模板。
动态多态:也称为运行时的多态,是指程序运行时根据所引用对象的实际类型调用相应的方法。通常所说的动态绑定也是这个意思。下文中指的多态,默认是动态多态。
静态多态就是重载,因为在编译期决议确定,所以称为静态多态。在编译时就可以确定函数地址。
动态多态就是通过继承重写基类的虚函数实现的多态,因为实在运行时决议确定,所以称为动态多态。运行时在虚函数表中寻找调用函数的地址。
在基类的函数前加上virtual关键字,在派生类中重写该函数,运行时将会根据对象的实际类型来调用相应的函数。
如果对象类型是子类,就调用子类的函数;如果对象类型是父类,就调用父类的函数,(即指向父类调父类,指向子类调子类)此为多态的表现。

7. c++vector迭代器失效场景?

Vector插入元素之后首地址改变,当迭代器还是之前的位置时,会报错

8. c++如何避免内存泄漏?

内存泄露 memory leak,是指程序在申请内存后,无法释放已申请的内存空间,一次内存泄露危害可以忽略,但内存泄露堆积后果很严重,无论多少内存,迟早会被占光。最终的结果就是导致内存溢出OOM(out of memory)。
(1) 确保没有在访问空指针。
(2) 每个内存分配函数都应该有一个 free 函数与之对应,alloca 函数除外。
(3) 每次分配内存之后都应该及时进行初始化,可以结合 memset 函数进行初始化,calloc 函数除外。
(4) 每当向指针写入值时,都要确保对可用字节数和所写入的字节数进行交叉核对。
(5) 在对指针赋值前,一定要确保没有内存位置会变为孤立的。
(6) 每当释放结构化的元素(而该元素又包含指向动态分配的内存位置的指针)时,都应先遍历子内存位置并从那里开始释放,然后再遍历回父节点。
(7) 始终正确处理返回动态分配的内存引用的函数返回值
内存泄露检测工具valgrind

9. share_ptr是线程安全的吗?

同一个shared_ptr对象可以被多线程同时读取。
不同的shared_ptr对象可以被多线程同时修改
shared_ptr本身不是线程安全的。比如多线程读写同一个shared_ptr可能会读到一个错误的信息(写线程刚刚写入一个成员然后读线程就开始读取,读到了一半旧一半新值)。
解决方案之一就是加锁。

10. 多线程

一个volatile变量自身具有以下三个特性:
可见性:即当一个线程修改了声明为volatile变量的值,新值对于其他要读该变量的线程来说是立即可见的。而普通变量是不能做到这一点的,普通变量的值在线程间传递需要通过主内存来完成。
有序性:volatile变量的所谓有序性也就是被声明为volatile的变量的临界区代码的执行是有顺序的,即禁止指令重排序。
受限原子性:这里volatile变量的原子性与synchronized的原子性是不同的,synchronized的原子性是指只要声明为synchronized的方法或代码块儿在执行上就是原子操作的。而volatile是不修饰方法或代码块儿的,它用来修饰变量,对于单个volatile变量的读/写操作都具有原子性,但类似于volatile++这种复合操作不具有原子性。所以volatile的原子性是受限制的。并且在多线程环境中,volatile并不能保证原子性。
#include
#include
#include

using namespace std;

mutex mu;
volatile int a=0;

void ic() {
for (int i = 0; i < 10; i++) {
mu.lock();
a++;
cout << a << endl;
mu.unlock();
}
}

int main(int argc, char* argv) {
thread t1(ic);
thread t2(ic);
t1.join();
t2.join();
return 0;
}

11. 解决hash冲突的办法

(1) 开放定址法(线性探测再散列,二次探测再散列,伪随机探测再散列)
(2) 再哈希法
(3) 链地址法
(4) 建立一个公共溢出区

12. C++11 最常用的新特性如下/标准模板库,哪些数据结构

(1) auto关键字:编译器可以根据初始值自动推导出类型。但是不能用于函数传参以及数组类型的推导
(2) nullptr关键字:nullptr是一种特殊类型的字面值,它可以被转换成任意其它的指针类型;而NULL一般被宏定义为0,在遇到重载时可能会出现问题。
(3) 智能指针:C++11新增了std::shared_ptr、std::weak_ptr等类型的智能指针,用于解决内存管理的问题。
(4) 初始化列表:使用初始化列表来对类进行初始化
(5) 右值引用:基于右值引用可以实现移动语义和完美转发,消除两个对象交互时不必要的对象拷贝,节省运算存储资源,提高效率
(6) atomic原子操作用于多线程资源互斥操作
(7) 新增STL容器array以及tuple

13. 进程和线程关系及区别

(1) 进程是具有一定独立功能的程序关于某个数据集合上的一次运行活动,进程是系统进行资源分配和调度的一个独立单位.
(2) 线程是进程的一个实体,是CPU调度和分派的基本单位,它是比进程更小的能独立运行的基本单位.线程自己基本上不拥有系统资源,只拥有一点在运行中必不可少的资源(如程序计数器,一组寄存器和栈),但是它可与同属一个进程的其他的线程共享进程所拥有的全部资源.

14. 进程间的通信方式

(1) 无名管道( pipe ):管道是一种半双工的通信方式,数据只能单向流动,而且只能在具有亲缘关系的进程间使用。进程的亲缘关系通常是指父子进程关系。
(2) 高级管道(popen):将另一个程序当做一个新的进程在当前程序进程中启动,则它算是当前程序的子进程,这种方式我们成为高级管道方式。
(3) 有名管道 (named pipe) : 有名管道也是半双工的通信方式,但是它允许无亲缘关系进程间的通信。
(4) 消息队列( message queue ) : 消息队列是由消息的链表,存放在内核中并由消息队列标识符标识。消息队列克服了信号传递信息少、管道只能承载无格式字节流以及缓冲区大小受限等缺点。
(5) 信号量( semophore ) : 信号量是一个计数器,可以用来控制多个进程对共享资源的访问。它常作为一种锁机制,防止某进程正在访问共享资源时,其他进程也访问该资源。因此,主要作为进程间以及同一进程内不同线程之间的同步手段。
(6) 信号 ( sinal ) : 信号是一种比较复杂的通信方式,用于通知接收进程某个事件已经发生。
(7) 共享内存( shared memory ) :共享内存就是映射一段能被其他进程所访问的内存,这段共享内存由一个进程创建,但多个进程都可以访问。共享内存是最快的 IPC 方式,它是针对其他进程间通信方式运行效率低而专门设计的。它往往与其他通信机制,如信号量,配合使用,来实现进程间的同步和通信。
(8) 套接字( socket ) : 套解字也是一种进程间通信机制,与其他通信机制不同的是,它可用于不同机器间的进程通信

15. static关键字用法详解

(1) 局部变量
普通局部变量是再熟悉不过的变量了,在任何一个函数内部定义的变量(不加static修饰符)都属于这个范畴。编译器一般不对普通局部变量进行初始化,也就是说它的值在初始时是不确定的,除非对其显式赋值。
[1] 普通局部变量存储于进程栈空间,使用完毕会立即释放。

静态局部变量使用static修饰符定义,即使在声明时未赋初值,编译器也会把它初始化为0。且静态局部变量存储于进程的全局数据区,即使函数返回,它的值也会保持不变。
[1] 变量在全局数据区分配内存空间;编译器自动对其初始化
[2] 其作用域为局部作用域,当定义它的函数结束时,其作用域随之结束
(2) 静态全局变量仅对当前文件可见,其他文件不可访问,其他文件可以定义与其同名的变量,两者互不影响。在定义不需要与其他文件共享的全局变量时,加上static关键字能够有效地降低程序模块之间的耦合,避免不同文件同名变量的冲突,且不会误使用。
(3) 静态数据成员
在类内数据成员的声明前加上static关键字,该数据成员就是类内的静态数据成员。其特点如下:
[1] 静态数据成员存储在全局数据区,静态数据成员在定义时分配存储空间,所以不能在类声明中定义
[2] 静态数据成员是类的成员,无论定义了多少个类的对象,静态数据成员的拷贝只有一个,且对该类的所有对象可见。也就是说任一对象都可以对静态数据成员进行操作。而对于非静态数据成员,每个对象都有自己的一份拷贝。
[3] 由于上面的原因,静态数据成员不属于任何对象,在没有类的实例时其作用域就可见,在没有任何对象时,就可以进行操作
[4] 和普通数据成员一样,静态数据成员也遵从public, protected, private访问规则
[5] 静态数据成员的初始化格式:<数据类型><类名>::<静态数据成员名>=<值>
[6] 类的静态数据成员有两种访问方式:<类对象名>.<静态数据成员名> 或 <类类型名>::<静态数据成员名>
[7] 同全局变量相比,使用静态数据成员有两个优势:
[8] 静态数据成员没有进入程序的全局名字空间,因此不存在与程序中其它全局名字冲突的可能性
[9] 可以实现信息隐藏。静态数据成员可以是private成员,而全局变量不能
(4) 静态成员函数
与静态数据成员类似,静态成员函数属于整个类,而不是某一个对象,其特性如下:
[1] 静态成员函数没有this指针,它无法访问属于类对象的非静态数据成员,也无法访问非静态成员函数,它只能调用其余的静态成员函数
[2] 出现在类体外的函数定义不能指定关键字static
[3] 非静态成员函数可以任意地访问静态成员函数和静态数据成员

16. new和malloc的区别

  1.   属性
    

new/delete是C++关键字,需要编译器支持。malloc/free是库函数,需要头文件支持。

  1.   参数
    

使用new操作符申请内存分配时无须指定内存块的大小,编译器会根据类型信息自行计算。而malloc则需要显式地指出所需内存的尺寸。
2. 返回类型
new操作符内存分配成功时,返回的是对象类型的指针,类型严格与对象匹配,无须进行类型转换,故new是符合类型安全性的操作符。而malloc内存分配成功则是返回void * ,需要通过强制类型转换将void*指针转换成我们需要的类型。
3. 分配失败
new内存分配失败时,会抛出bac_alloc异常。malloc分配内存失败时返回NULL。
4. 自定义类型
new会先调用operator new函数,申请足够的内存(通常底层使用malloc实现)。然后调用类型的构造函数,初始化成员变量,最后返回自定义类型指针。delete先调用析构函数,然后调用operator delete函数释放内存(通常底层使用free实现)。
malloc/free是库函数,只能动态的申请和释放内存,无法强制要求其做自定义类型对象构造和析构工作。
5. 重载
C++允许重载new/delete操作符,特别的,布局new的就不需要为对象分配内存,而是指定了一个地址作为内存起始区域,new在这段内存上为对象调用构造函数完成初始化工作,并返回此地址。而malloc不允许重载。
6. 内存区域
new操作符从自由存储区(free store)上为对象动态分配内存空间,而malloc函数从堆上动态分配内存。自由存储区是C++基于new操作符的一个抽象概念,凡是通过new操作符进行内存申请,该内存即为自由存储区。而堆是操作系统中的术语,是操作系统所维护的一块特殊内存,用于程序的内存动态分配,C语言使用malloc从堆上分配内存,使用free释放已分配的对应内存。自由存储区不等于堆,如上所述,布局new就可以不位于堆中。

17. delete的几种方式

18. c++具有哪些特点和性质,和c有什么区别

19. 面向对象的三个特点,简单总结

面向对象的三个基本特征是:封装、继承、多态
封装,也就是把客观事物封装成抽象的类,并且类可以把自己的数据和方法只让可信的类或者对象操作,对不可信的进行信息隐藏。
继承是指这样一种能力:它可以使用现有类的所有功能,并在无需重新编写原来的类的情况下对这些功能进行扩展。
多态性(polymorphisn)是允许你将父对象设置成为和一个或更多的他的子对象相等的技术,赋值之后,父对象就可以根据当前赋值给它的子对象的特性以不同的方式运作。简单的说,就是一句话:允许将子类类型的指针赋值给父类类型的指针。
实现多态,有二种方式,覆盖,重载。
覆盖,是指子类重新定义父类的虚函数的做法。
重载,是指允许存在多个同名函数,而这些函数的参数表不同(或许参数个数不同,或许参数类型不同,或许两者都不同)。

20. 子类和父类在构造和析构的时候有什么特点吗,顺序

构造子类对象时,先调用父类构造函数,再调用子类构造函数(构造函数没有虚函数这一说法)
析构子类对象时,先调用子类的析构函数,再调用父类析构函数(无论父类的析构函数是否为虚函数)
构造子类构造的父类对象时,先调用父类构造函数,再调用子类构造对象(构造函数没有虚函数这一说)
析构子类构造的父类对象时:
若父类是虚函数,则先调用子类析构函数,再调用父类析构函数
若父类不是虚函数,则只调用父类的析构函数

21. 纯虚函数和虚函数的区别

定义一个函数为虚函数,不代表函数为不被实现的函数。
定义他为虚函数是为了允许用基类的指针来调用子类的这个函数。
定义一个函数为纯虚函数,才代表函数没有被实现。
定义纯虚函数是为了实现一个接口,起到一个规范的作用,规范继承这个类的程序员必须实现这个函数。纯虚函数一定没有定义,纯虚函数用来规范派生类的行为,即接口。包含纯虚函数的类是抽象类,抽象类不能定义实例,但可以声明指向实现该抽象类的具体类的指针或引用。
C++纯虚函数
一、定义
纯虚函数是在基类中声明的虚函数,它在基类中没有定义,但要求任何派生类都要定义自己的实现方法。在基类中实现纯虚函数的方法是在函数原型后加“=0”
virtual void funtion1()=0
二、引入原因
1、为了方便使用多态特性,我们常常需要在基类中定义虚拟函数。
2、在很多情况下,基类本身生成对象是不合情理的。例如,动物作为一个基类可以派生出老虎、孔雀等子类,但动物本身生成对象明显不合常理。

22. 引用的作用

引用传值

23. 常引用

使用场景,修饰形参为只读,尤其是拷贝构造函数
const int &a=10;会分配内存
引用不产生新的变量,减少形参与实参传递时的开销
由于引用可能导致实参随形参改变而改变,将其定义为常量引用可以消除这种副作用
如果希望实参随着形参改变而改变,那么使用一般的引用,如果不希望实参随着形参改变,那么使用常引用

24. 结构体和联合体

一、结构体struct
各成员各自拥有自己的内存,各自使用互不干涉,同时存在的,遵循内存对齐原则。一个struct变量的总长度等于所有成员的长度之和。
#include<stdio.h>
struct u4
{
int a;
char b;
short c;
}U5;

struct u5
{
char b;
int a;
short c;
}U6;

//主函数
int main(){
printf("%d\n",sizeof(U5));
printf("%d\n",sizeof(U6));
return 0;
}

//输出为
//8
//12
二、联合体union
各成员共用一块内存空间,并且同时只有一个成员可以得到这块内存的使用权(对该内存的读写),各变量共用一个内存首地址。因而,联合体比结构体更节约内存。一个union变量的总长度至少能容纳最大的成员变量,而且要满足是所有成员变量类型大小的整数倍。不允许对联合体变量名U2直接赋值或其他操作。
union大小计算准则:1、至少要容纳最大的成员变量 2、必须是所有成员变量类型大小的整数倍

25. 对联合不同成员赋值,其它的值会改变吗

给联合中的成员赋值时,只会对这个成员所属的数据类型所占内存空间的大小覆盖成后来的这个值,而不会影响其他位置的值。

26. 重载和重写的特点?

  1. Override 特点
    [1] 覆盖的方法的标志必须要和被覆盖的方法的标志完全匹配,才能达到覆盖的效果;
    [2] 覆盖的方法的返回值必须和被覆盖的方法的返回一致;
    [3] 覆盖的方法所抛出的异常必须和被覆盖方法的所抛出的异常一致,或者是其子类;
    [4] 被覆盖的方法不能为private,否则在其子类中只是新定义了一个方法,并没有对其进行覆盖。
    2.Overload 特点
    [1] 在使用重载时只能通过不同的参数样式。例如,不同的参数类型,不同的参数个数,不同的参数顺序(当然,同一方法内的几个参数类型必须不一样,例如可以是fun(int, float), 但是不能为fun(int, int));
    [2] 不能通过访问权限、返回类型、抛出的异常进行重载;
    [3] 方法的异常类型和数目不会对重载造成影响;
    [4] 对于继承来说,如果某一方法在父类中是访问权限是priavte,那么就不能在子类对其进行重载,如果定义的话,也只是定义了一个新方法,而不会达到重载的效果。

27. const和define的区别,哪种更好

一:区别
(1)就起作用的阶段而言: #define是在编译的预处理阶段起作用,而const是在 编译、运行的时候起作用。
(2)就起作用的方式而言: #define只是简单的字符串替换,没有类型检查。而const有对应的数据类型,是要进行判断的,可以避免一些低级的错误。
(3)就存储方式而言:#define只是进行展开,有多少地方使用,就替换多少次,它定义的宏常量在内存中有若干个备份;const定义的只读变量在程序运行过程中只有一份备份。
(4)从代码调试的方便程度而言: const常量可以进行调试的,define是不能进行调试的,因为在预编译阶段就已经替换掉了。
二:const优点
(1)const常量有数据类型,而宏常量没有数据类型。编译器可以对前者进行类型安全检查。而对后者只进行字符替换,没有类型安全检查,并且在字符替换可能会产生意料不到的错误。
(2)有些集成化的调试工具可以对const常量进行调试,但是不能对宏常量进行调试。
(3)const可节省空间,避免不必要的内存分配,提高效率

28. 指针和数组的区别

数组:数组是用于储存多个相同类型数据的集合。
指针:指针相当于一个变量,但是它和不同变量不一样,它存放的是其它变量在内存中的地址。
1.赋值
同类型指针变量可以相互赋值,数组不行,只能一个一个元素的赋值或拷贝
2.存储方式
数组:数组在内存中是连续存放的,开辟一块连续的内存空间。数组是根据数组的下进行访问的,多维数组在内存中是按照一维数组存储的,只是在逻辑上是多维的。
数组的存储空间,不是在静态区就是在栈上。
指针:指针很灵活,它可以指向任意类型的数据。指针的类型说明了它所指向地址空间的内存。
指针:由于指针本身就是一个变量,再加上它所存放的也是变量,所以指针的存储空间不能确定。
3.求sizeof
数组:
数组所占存储空间的内存:sizeof(数组名)
数组的大小:sizeof(数组名)/sizeof(数据类型)
指针:
在32位平台下,无论指针的类型是什么,sizeof(指针名)都是4,在64位平台下,无论指针的类型是什么,sizeof(指针名)都是8。

29. 基类的析构函数,不是虚函数会造成什么问题

[1] 基类的的析构函数不是虚函数的话,删除指针时,只有其类的内存被释放,派生类的没有。这样就内存泄漏了。
[2] 析构函数不是虚函数的话,直接按指针类型调用该类型的析构函数代码,因为指针类型是基类,所以直接调用基类析构函数代码。
[3] delete是删除指针p指向的实例,p指针本身依然存在,delete后将p置为空值是常用做法,空值一般写成NULL宏,其实就是0。因为内存0位置是不允许访问的,delete 0操作编译器可以判断是错误操作不会执行,因此将p置为空值0是很安全的做法。
[4] 当基类指针指向派生类的时候,如果析构函数不声明为虚函数,在析构的时候,不会调用派生类的析构函数,从而导致内存泄露。
[5] 子类对象创建时先调用父类构造函数然后在调用子类构造函数,在清除对象时顺序相反,所以delete p只清除了父类,而子类没有清除

30. 全局变量和局部变量的区别,操作系统和编译器是怎么知道的

全局变量和局部变量的区别是作用域不同,全局变量从定义位置开始到程序结束,而局部变量只限定义的函数内可使用,全局变量在数据段,而局部变量在栈,局部变量在函数结束时内存空间就被系统收回,所以要返回的数组或字符串不要用局部变量定义.extren和在main()函数外定义的变量都称为全局变量,操作系统和编译器从定义变量为变量分配内存时,从变量的定义和存储区域来分别局部变量和全局变量.

31. UDP和TCP有什么区别

[1] TCP 是面向连接的,UDP 是面向无连接的
[2] UDP程序结构较简单
[3] TCP 是面向字节流的,UDP 是基于数据报的
[4] TCP 保证数据正确性,UDP 可能丢包
[5] TCP 保证数据顺序,UDP 不保证

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值