C++面试内容总结

        常言说的好,人终有一招,不是秋招就是春招。经历过漫长的刷题、笔试、面试,实习、秋招,也算是摸出了一些门道。想把自己整理的一些知识,以及遇到的一些问题做一个总结(几乎涵盖了我遇到的所有C++的知识点,当然我面试的公司也不多)。说实话,通常在面试过程中,如果知识点不是很熟练,就很难答得比较全面,或者干脆不知道。以下的内容也只是做一个抛砖引玉,其中详细的内容还需要再深入探索。

一、各种区别

1.1 指针和引用的区别

从定义来看:1)指针是指向一块内存的变量,其占有内存,初始化可以为空。

                      2)引用是变量的别名,在初始化时必须和变量绑定。

从传值来看:1)指针传递是直接对指针指向的内存进行操作。

                      2)引用是直接对穿递过来变量的内容进行操作。

                      3)这两种传参方式都会改变实参和形参的值。

注意:在函数中对指针进行移动,并在主函数需要获得此指针改变后的值,则需要以指针的引用方式传参。值传递用于对象时,整个对象会拷贝一个副本,这样效率低;而引用传递用于对象时,不发生拷贝行为,只是绑定对象,更高效;指针传递同理,但不如引用传递安全。

1.2 指针常量和常量指针

指针常量:其本质是一个常量,形式如:int*const p,这里const修饰的是常量,则变量的值不可以改变,而指针的指向可以改变。

常量指针:其本质是一个指针,形式如:const int *p,这里const修饰的int型的指针,则指针的指向不可以改变,而指向的值可以改变。这也是引用的本质。

1.3 函数指针和指针函数

函数指针:形如:类型说明符 (*函数名)(参数):int (*FunPointName)(int a,int b)。这是一个指向函数的指针,包含了函数的地址,可以直接用它来调用函数,本质是一个指针变量。

指针函数:是一个函数,返回一个指针,实际上就是返回一个地址给调用函数。

注意:函数指针的应用场景回调(callback)。

1.4 数组指针和指针数组

数组指针:本质是指针,指向数组的地址,形如:int (*p)[10]。

指针数组:本质是数组,里面存放的是指针。

1.5 new和malloc的区别

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

2)new在分配内存的时候怒需要指定内存大小,malloc需要显示的指出内存的大小;

3)new操作符内存分配成功后,返回的是对象类型的指针,无需进行类型转换;malloc申请返回的是void*,需要进行强制类型转换;

4)new在申请内存后,会调用类型的构造函数,初始化成员变量;malloc只能申请内存。

1.6 堆和栈的区别

1)堆是程序员自己申请的内存,需要手动释放;栈是系统自动提供的内存,不需要释放;

2)堆是不连续的内存;栈是连续的内存;

3)堆的分配效率较低;栈的分配效率高。

1.7 union和struct的区别

1)union每个成员会用同一个存储空间;

2)struct可以存储多个成员信息。

注意:这里又涉及到struct和class的区别。

二、操作系统内存

2.1 操作系统内存分配

C++的内存分为全局数据区,代码区,堆,栈。

1)全局数据区:存放的是静态数据,去哪聚变量,常量;

2)代码区:存放代码;

3)栈区:存放函数返回地址,形参,局部变量,返回类型;

4)堆区:存放剩下的内存。

2.2 内存泄漏和野指针出现的情况

内存泄漏:1)使用new申请的空间没有用delete释放;

                  1)析构函数未释放匹配的内存;

                  2)基类的析构函数没有设为虚函数(在实现多态时,使用delete删除指向派生类的基类指针时,派生类的析构函数被覆盖,而调用了基类的析构函数,派生类对象没有被析构,导致内存泄露。如果设为虚函数,则先析构派生类对象,然后自动调用基类的析构函数)

野指针:1)指针被释放后,没有设置为NULL;

               2)指针操作超越了变量的作用范围,比如返回指向栈内存的指针就是野指针;

               3)指针变量没有被初始化;

               4)浅拷贝中二次释放指针,会出现野指针。

2.3 指针怎么分配内存

有动态内存分配和静态内存分配。

1)静态内存分配指的是从静态内存区分配,从栈和堆空间分配。

2)动态内存分配值的是利用new或malloc申请空间。

2.4 内存对齐

概念:为了提高程序的性能,数据结构应尽可能地在自然边界上对齐。

目的:1)便于移植。对于硬件只能在特定地址取址。

           2)提高处理器访问速度:对于未对齐的内存,处理器可能需要两次才可以完全读出数据,对齐的话只需要读取一次。

解释:CPU按照2^n个字节来读取内存,CPU只能从这些倍数开始的位置读取内存,那么我们按照其读取规律,将数据从其读取处存放,可以提高效率。

2.5 大小端存储模式

因为一个字节无法存储完一个变量,按照数据高低位和内存高低地址可以分为大端存储模式和小端存储模式。

1)大端存储:一个数据的低字节内容存放在高地址中,高字节的内容存放在低地址中。

2)小端模式:一个数据的低字节内容存放在低地址中,高字节的内容存放在高地址中。

在面试过程中被问到如何查询自己电脑是大端存储还是小端存储:可以通过指针和共同体的方式来查询,具体如何操作也要知道。

三、指针(智能指针,this指针)

3.1 智能指针

智能指针展开来说,也是一个很大的模块,要了解智能指针的本质。在我面试中,甚至被要求让自己实现一个shared_ptr指针:)。之前的文章有讲过这部分的内容:https://blog.csdn.net/matlab16081101/article/details/124610290?spm=1001.2014.3001.5501

简单来说,智能指针有:shared_ptr,unique_ptr,weak_ptr,auto_ptr。其本质是模板类。

1)shared_ptr:共享指针。多个智能指针指相同对象,对象的最末一个拥有者负责销毁对象的责任,其实现原理是内部使用了一个计数器。

2)unique_ptr:独占指针。确保一个对象和对应资源只被一个指针拥有,且该指针被销毁时,他所指向的对象也被销毁。

3)weak_ptr:弱指针。配合shared_ptr使用,允许共享但不拥有对象,共享指针销毁后,weak_ptr也自动为空。

3.2 this指针

1)this指针是一个隐含于每一个非静态成员函数中的特殊指针,它指向调用该成员函数的对象;

2)当一个对象调用成员函数时,编译程序先将对象的地址赋给this指针,然后调用成员函数;

3)this指针并不是常规变量,而是一个右值,不能取地址;

4)当一个成员函数被调用时,自动向它传递一个隐含的参数,该参数就是一个指向这个成员函数所在的对象的指针。

四、 关键字的作用

4.1 const的作用

const可以修饰变量,指针,引用,成员函数。

1)const修饰变量时,值不可变;

2)修饰指针时,见常量指针;

3)修饰引用,有常量引用,没有引用常量;

4)修饰成员函数,不能改变成员变量的值。

注意:这里涉及到const和#define的区别,可自行查阅资料。另外宏定义和内联函数的区别也要搞清楚。

4.2 static的作用

static可以修饰普通变量、普通函数、成员变量、成员函数。

1)修饰普通变量,则改变了存储区和生命周期;

2)修饰普通函数时,表明函数的作用范围:仅在定义函数的文件中使用;

3)修饰成员变量时,多有对象共享,只能在类外定义;

4)修饰成员函数,不能访问非静态成员。

五、强制类型转换运算符

1)static_cast:用于数据类型的强制转换。

     用法:(1)用于类层次结构中积累和派生类之间指针和引用的转换。进行上行转换(派生类->基类)是安全的;进行下行转换(基类->派生类)是不安全的,因为没有动态类型检查。

                (2)把空指针转换成目标类型的空指针。

                (3)用于基本类型(数据)之间的强转。

2)const_cast:用于删除不能被修饰的常量类型,但不是改变变量的常量性,而是去除指向常数对象的指针或引用的常量性。作用于const,volatile修饰的对象,引用或指针。

3)reinterpret_cast:改变指针或引用的类型,将指针或引用转换为一个足够长度的整型,将整型转换为指针或引用类型,但是是比特位的拷贝:产生一个新的值,这个值与原始参数有相同的比特位。一般用于指针之间的转换,因为指针的长度是一致的,按照比特位拷贝不会缺失数据。

4)dynamic_cast:主要用于将父类的指针或引用转换为子类的指针或者引用(下行转换),相比于static_cast更安全,因为其进行了类型检查,如果类型不一致,则返回一个空指针。注意:使用时,基类必须有虚函数,这是因为在运行时类型检查需要运行时类型信息,而这个信息存储在虚函数表中。

写累了休息一下:

六、类

6.1 复制构造函数和移动构造函数

复制构造函数:形参是对象的const引用,使用已存在的对象初始化同类新对象,是一种浅拷贝。当直接定义,函数传参,函数返回值时可以调用复制构造。

移动构造函数:形参是“右值引用”,当右值(临时对象)即将消亡,并且他所拥有的资源是需要其他对象再使用的,就会触发移动构造函数,然后这块内存就会被调用他的对象接手,延长了生命周期。

6.2 深拷贝和浅拷贝

浅拷贝:再用一个对象初始化另一个对象时,只复制了成员,并没有复制资源,使两个对象同时指向了同一块资源。当对这块内存进行释放时,可能会出现二次释放,导致崩溃。

深拷贝:就是自己定义了一个复制构造函数,则可以重新申请一块内存,在内存中存入要复制的值,则不会出现浅拷贝的问题。

6.3 多态以及多态的实现原理

概念:多态是指同一接口具有多种形态的能力。

类型:多态分为静态多态和动态多态。静态多态指的是函数重载;动态多态指的是类的多态。

动态多态的实现原理:实现方式是虚函数,派生类可以重写基类的虚函数以实现多态。其中具体的细节问题:多态实现中的虚表以及虚表指针,这两个的生成时间是当虚基类被构造时,会自动生成一个虚表和指向虚表的指针。虚表中存放的是虚函数,子类和父类都有一个虚表,当调用虚函数时,会在虚表中查询。

6.4 重载,重写,重定义

重载:函数名相同,参数列表中的个数、类型、顺序不同,是一种静态绑定;

重写:子类对父类中的虚函数重新定义,是一种动态绑定;

重定义:子类和父类的成员函数变量相同或者函数名相同,子类隐含父类的对应成员。

(不懂是吗?不懂就对了!详细知识点还需要查资料)

6.5虚函数可以有哪些

1)析构函数可以是且常常是虚函数;

2)构造函数不能是虚函数:虚函数的调用是通过虚函数表来查找的,而虚函数表由类的实例化对象的智能指针指向,该指针存放在对象的内部空间中,需要调用构造函数完成初始化。两者矛盾。

3)构造函数以及析构函数中不能有虚函数:由于类的构造函数是有基类到派生类,所以在构造函数中调用虚函数,派生类还没有完全构造,虚函数是不会出现多态的。类的析构是从派生类到基类,当调用继承层次中某一层次的类的析构函数时意味着其派生类已经析构掉,也不会出现多态。

4)内联函数不能是虚函数:inline函数是编译器将函数内容替换到调用内联函数处,是静态编译的;而虚函数是动态编译的,编译器不知道需要调用的是父类还是子类的,所以无法展开。

5)static成员函数不能是虚函数:静态成员函数不在类中,没有this指针,不能通过vptr指针访问。

6.6 空类会有哪些函数

默认构造函数、析构函数、拷贝构造函数、赋值构造函数

七、数据结构

7.1 STL组成

STL六大组件:容器、迭代器、算法、仿函数、适配器、分配器

1)容器:序列式容器:vector\deque\list

                关联式容器:set\multiset\map\multisetmap

                容器适配器:stack\queue\priority queue

2)仿函数:使类的使用看起来像是一个函数,其实现就是累中实现一个operator(),这个类就有了类似函数的行为,就是一个仿函数类了;

3)谓词:普通函数或重载的operator()返回值是bool类型的函数对象。如果operator接受一个参数,那么叫做一元谓词,如果接受两个参数,叫做二元谓词。

7.2 vector扩容机制

vector底层是动态数组,把数据存放在连续的内存里,如果size>capacity,会找到一个更大的内存,把原来的元素拷贝过去,并释放旧空间。

7.3 数组和链表的区别

1)数组是连续内存,链表是不连续的内存;

2)数组是事先固定数组长度,在栈上分配;链表使用new申请,在堆上分配;

3)数组访问效率高,插入删除效率低;链表访问效率低,插入删除效率高。

7.4 哈希表和哈希碰撞

概念:哈希是一种可以将键和值进行映射的数据结构。

哈希冲突:当两个数通过哈希函数被映射到同一块内存时,会发生冲突。

解决方法:1)链地址法:将所有哈希地址相同的记录在同一个链表中;

                  2)线性探测法,平方探测法;

7.5 迭代器失效的问题

本质:迭代器是类模板,表现的像指针。可以遍历STL容器内存全部或部分元素的对象。迭代器返回的是对象的引用而不是对象的值,只能输出迭代器使用*取值后的值,而不能输出其本身。

迭代器失效:迭代器失效指的是迭代器底层对应指针所指向的空间被销毁了,导致使用了一块已经被释放的空间。

分类:1)顺序容器迭代器失效:添加使其扩容,存储空间重新分配,原内存被释放;

                                                    删除会导致元素次序发生改变导致迭代器失效。

           2)关联容器迭代器失效:map\set删除某个元素仅仅当前元素的迭代器失效,其他正常。

八、计算机网络

8.1 OIS七层模型和TCP/IP五层模型

OSI七层参考模型有:物理层、数据链路层、网络层、传输层、会话层、表示层、应用层。

TCP/IP五层模型有:应用层、传输层、网络层、数据链路层、物理层。

8.2 传输层协议有哪些,有什么区别

传输层的协议有:TCP、UDP。

1)TCP面向连接,USP无连接;

2)TCP提供可靠服务,会进行三次握手确保数据无差错,不丢失,不重复,且按序到达;UDP尽最大努力交付,不保证可靠交付;

3)TCP是全双工传输,UDP是不可靠传输;

4)TCP是面向字节流,UDP面向报文;

5)UDP没有拥塞控制,效率比较高;

6)TCP头部开销是20字节,UDP是8字节。

8.3 TCP如何解决粘包问题

可以发送定长包,或者在包头加上包体长度。

8.4 TCP保证可靠传输手段

三次握手,四次挥手。

8.5 TCP流量控制和拥塞控制的区别

1)流量控制解决的是发送方和接收方速率不匹配的问题,发送方发送过快,接收端来不及接收和处理,采用滑动窗口控制发送了但未被ACK的包的数量。

2)拥塞控制解决的是避免网络资源被耗尽的问题。

8.6 滑动窗口和拥塞窗口的区别

1)滑动窗口:发送端和接收端窗口,窗口有大小限制,大小是接收端用来告诉发送端目前接收端能接收的最大字节数;

2)拥塞窗口:指发送端在一个RTT内可以发送的最大的数据包数。

窗口本质是缓冲区。

8.7 socket客户端和服务端使用的函数

九、操作系统

9.1 进程和线程的区别

 1)调度:进程是操作系统资源分配的基本单位,而线程是处理器任务调度和执行的基本单位;

2)拥有资源:进程有自己独立的代码和数据空间;线程有线程ID,当前指令指针,寄存器集合和堆栈组成。线程拥有共享资源:代码资源、共有数据等

3)并发性:同一进程内的多个线程可以并发执行;

4)系统开销:创建和撤销进行时,系统需要回收I/O设备,内存空间等;而线程切换需要保存寄存器内容,开销少;

5)地址空间和其他资源:进程的地址空间互相独立,同一进程的各线程共享进程的资源;

6)通信方面:进程间通过操作系统,线程间可直接访问数据段。

9.2进程和线程的通信机制

进程通信:管道(在内存中开辟一个缓冲区用于通信),系统IPC:消息队列、信号量、信号、内存共享,套接字socket(不同主机之间通信);

线程通信:临界区,互斥量,信号量,条件变量,读写锁。

9.3 并发和并行

并发:对于单个CPU,一个时刻只能有一个进程在运行,但是线程切换时间很短,多个任务来回快速切换。

并行:多个CPU,多个进程同时运行。

9.4 线程的状态

创建,就绪,执行,阻塞,终止

9.5 多线程如何实现

主要是CPU通过给每个线程分配CPU时间片来实现多线程的。时间片是CPU分配给各个线程的时间,因为时间片非常短,所以CPU通过不停地切换线程执行,让我们感觉多个线程同时执行。

9.6 死锁以及死锁产生的条件

概念:是指两个或两个以上的进程在执行过程中,由于竞争资源,或者由于彼此通信而造成的一种阻塞的现象,若无外力推进,则无法进行。

必要条件:1)互斥条件:进程要求对所分配的资源进行排他性控制,一段时间内某块资源只能被一个线程占用;

                  2)请求保持条件:当线程因请求资源而阻塞时,对已获得的资源保持不放;

                  3)不剥夺条件:进程已获得的资源在未使用完整之前,不能剥夺,只能在使用完后释放;

                  4)环路等待条件:进程资源的环形链。

9.7 虚拟内存

操作系统会为每个进程分配一个独立的地址空间,但是一个虚拟内存。虚拟地址与物理地址存在映射关系,通过页表寻址完成虚拟地址和物理地址的转换。

9.8 多线程的条件变量,锁,互斥量,信号量

9.9 生产者-消费者模型

9.10 线程池的实现

9.8、9.9和9.10这三部分的内容在我下一篇内容将讲到,这里就不展开说了。

十、Linux操作系统

10.1 内核态和用户态

内核态是拥有最高权限,可以访问所有系统指令,但有些指令比较危险,为了安全考虑设置了用户态。

当系统调用,异常,设备中断则会进入内核态。

10.2 各种命令

比如.查看PID进程信息:PS,查看网络配置:ifconfig,等等。

十一、数据库

这部分内容我也是临阵磨枪,被问到的也不是很多。若面试岗位和数据库有很大关系,建议深入学习。

人生就是不断学习的过程!加油!

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值