C++ 面试

C++ 面试

1. TCP和UDP的区别

  1. TCP是面向连接的可靠传输;而UDP是面向无连接的不可靠传输;
  2. TCP确保所传输的数据一定会到达目的地,但时间却不能保证;而UDP不确保数据会按照原来的顺序到达,但具有实时性;
  3. TCP的使用场景是不怎么在意传输速度,但在意数据的准确性;而UDP的使用场景则是对实时性要求较高;
  4. TCP的首部开销大,占用20字节;而UDP的首部开销小,只占用8字节;
  5. TCP只支持点对点的数据传输;而UDP支持一对一、一对多、多对多等各种场景
  6. TCP需要校验和、序列号、超时重传、确认机制、连接管理(三次握手和四次挥手)、流量控制和拥塞控制来确保传输的可靠性,当网络拥塞时,速度会比较慢;UDP没有拥塞控制和流量控制这些。

2. new和malloc的区别

  1. new/delete 是C++的操作符,malloc/free 是C中的函数;
  2. new 产生的是一个对象,而malloc 分配的是一块内存;new 产生的对象可以用成员函数访问,而malloc分配的是一块内存区域,用指针访问,可以在里面移动指针;malloc申请的空间不能初始化,new可以初始化;
  3. malloc 申请空间时,需要手动计算空间大小并传递;new 只需在其后跟空间的类型,然后是多个对象,就只需要new类型的数组;
  4. malloc 返回值类型为void*,在使用时必须强转,new不需要,因为new 后面跟的是空间的类型;
  5. malloc 申请空间失败时,返回的是NULL,因此使用时必须判空;而new 不需要,但是new 需要捕获异常。

3. delete和free的区别

  1. delete是操作符,而free是函数;
  2. delete用于释放 new 分配的空间,free 用于释放 malloc 分配的空间;
  3. delete会调用析构函数(析构函数中会把指针修改为NULL),而 free 不会;
  4. 使用 delete 释放内存则不需要检查指针是否为NULL;而调用 free 之前需要检查需要释放的指针是否为空。

4. struct和class的区别

  1. 在C++中两者的唯一区别就是默认的访问权限不同
  2. struct默认权限为共有。
  3. class默认权限为私有。

拓展: 在C中,struct是用户自定义数据类型,是一些变量的集合体。没有权限设置,不能包含函数;在C++中,struct是抽象数据类型,能给用户提供接口,能定义成员函数,能继承也能实现多态,有权限设置。

5. struct和union的区别

  1. 在存储多个成员信息时,struct占用的空间由每个成员的大小加起来;union占用的空间取决于它所有的成员中,占用空间最大的一个成员的大小,并且它的大小要能被其他的成员的大小整除;
  2. 在使用时,struct中所有成员都有占用空间,所以都可以使用;而union中在一个时刻只能使用一个成员;

6. 多态是怎么实现的

多态分为静态多态(编译时多态)和动态多态(运行时多态)。

  1. 静态多态通过重载来实现的。重载是在一个类里面,方法名字相同,而参数不同。返回类型可相同也可不同,每个重载的方法(或构造函数)都必须有一个独一无二的参数类型列表。
  2. 动态多态通过继承、重写、向上转型实现的。使得父类指针或引用在同一个继承结构中使用统一的逻辑实现代码处理不同的对象,从而执行不同的行为(调用虚函数)。也就是在运行时会根据对象的实际类型来调用相应的函数。

    继承:在多态中必须存在有继承关系的子类和父类。
    重写:子类对父类中虚函数进行重写(重写就是子类父类都有这个虚函数,且子类的虚函数与父类虚函数的函数名、参数和返回值都相同,但需要注意的是,参数只看类型是否相同,不看缺省值,相对于只重写了父类虚函数中的实现部分,就是如果子类父类参数中有缺省值,当用多态时,会使用父类的缺省值),重写后,在调用这些虚函数时就会调用子类的虚函数。
    向上转型:在多态中需要将子类的指针或引用赋给父类对象,只有这样该指针或引用才既能调用父类的方法,又能调用子类的方法。

一篇把多态讲得很全面的博文分享给大家

7. 指针和引用的区别

  1. 定义和性质不同。指针是一种数据类型,用于保存地址类型的数据,而引用可以看成是变量的别名;
  2. 指针变量可以为空,在任何时候初始化,而引用不可以为空,当被创建时就必须初始化;
  3. 指针可以有多级,但引用只能是一级;
  4. 引用使用时无需解引用(*),指针需要解引用;
  5. 指针的值在初始化后可以改变,即指向其他的存储单元,而引用在进行初始化后就不会再改变了;
  6. 当调用sizeof时,指针的得到的时指针变量本身的大小,而引用得到的是所指向的变量(对象)的大小;
  7. 指针作为函数参数传递时传递的是指针变量(地址)的值(值传递),而引用作为函数参数传递时传递的时实参本身,而不是拷贝副本;
  8. 指针和引用进行++运算意义不一样。

8. C和C++的区别

  1. C是面向过程,而C++是面向对象;
  2. C++完全兼容C的内容;
  3. C++支持运算符重载、函数重载等编译时多态机制;
  4. C++支持泛型编程、模板机制;
  5. C++支持异常处理;

9. 动态库和静态库

静态库和动态库都是编程中常用的库文件,它们都包含了一些函数和数据,可以供程序调用。它们的主要区别在于编译时和运行时的不同

静态库(Static Library 是在编译时将库文件的代码和数据复制到可执行文件中,程序运行时不再需要该库文件。静态库的优点是使用简单,不需要额外的安装和配置,而且可以保证程序的稳定性和可靠性。缺点是占用的空间较大,当多个程序使用同一个静态库时,会导致重复的代码和数据,浪费系统资源。

动态库(Dynamic Library) 是在程序运行时才加载的库文件,程序启动时只需要加载动态库的链接信息,而不需要将整个库文件复制到内存中。动态库的优点是占用的空间较小,多个程序可以共享同一个动态库,从而节省系统资源。缺点是使用较为复杂,需要安装和配置动态库,同时也可能会产生一些运行时的问题,如版本不兼容、依赖关系等。

在使用静态库和动态库时,需要注意以下几点:

  • 静态库可以直接链接到可执行文件中,使用时需要将库文件的路径添加到编译器的链接选项中。动态库需要在运行时加载,使用时需要将库文件的路径添加到系统的动态库搜索路径中。

  • 静态库会将库文件的代码和数据复制到可执行文件中,因此可执行文件的大小会增加。动态库不会将库文件的代码和数据复制到可执行文件中,因此可执行文件的大小不会增加。

  • 静态库可以保证程序的稳定性和可靠性,因为库文件的代码和数据已经被复制到可执行文件中。动态库可能会有版本不兼容、依赖关系等问题,需要进行额外的配置和管理。

总之,静态库和动态库都有各自的优点和缺点,需要根据具体的需求和情况来选择使用哪种库文件。

10. 简述 DNS 查询服务器的基本流程是什么?(深信服)

DNS负责域名和IP的转换。
DNS查询方式有两种,一种是递归查询(本地去查根,根没有会一直查询域服务器、解析服务器),另一种是迭代查询(本地去查根,根没有会告诉本地该去那里查,要是还没有又是本地去另外的服务器查,反正就是本地自己去查),下面介绍的就是迭代查询。

  1. 打开浏览器,输入一个域名。比如输入www.baidu.com,这时,你使用的电脑会发出一个DNS请求到本地DNS服务器。 本地DNS服务器一般就是你的网络接入服务商提供,如中国移动、中国电信;
  2. DNS请求到达本地DNS服务器之后,本地DNS服务器会首先查询它的缓存记录,如果缓存中有此条记录,就可以直接返回结果。如果没有,本地DNS服务器还要向DNS根服务器进行查询;
  3. 根DNS服务器没有记录具体的域名和IP地址的对应关系,而是给出域服务器的地址,告诉本地DNS服务器,你可以到域服务器上去继续查询;
  4. 本地DNS服务器继续向域服务器发出请求,在这个例子中,请求的对象是.com域服务器。.com域服务器收到请求之后,也不会直接返回域名和IP地址的对应关系,而是告诉本地DNS服务器,你的域名的解析服务器的地址;
  5. 最后,本地DNS服务器向域名的解析服务器发出请求,这时就能收到一个域名和IP地址对应关系,本地DNS服务器不仅要把IP地址返回给用户电脑,还要把这个对应关系保存在缓存中,以备下次别的用户查询时,可以直接返回结果,加速网络访问。

11. 什么是泛型编程

泛型编程(Generic Programming)是一种编程方法论,它的目的编写可重用、通用的代码,以实现更高效、更灵活的软件开发。泛型编程的核心思想是将算法和数据结构的实现与其所操作的数据类型分离开来,从而实现代码的通用性和可重用性。
在 C++ 中,泛型编程的实现依赖于模板(Template)机制。模板是一种可以在编译时生成代码的机制,它允许我们编写通用的代码,可以适用于多种不同的数据类型。通过使用模板,我们可以将算法和数据结构的实现与具体的数据类型分离开来,从而实现代码的通用性和可重用性。
C++ 标准库中的许多容器、算法和函数都是使用泛型编程实现的。例如,std::vector、std::list、std::set 等容器都是使用模板实现的,可以适用于不同的数据类型。同样,std::sort、std::find、std::transform 等算法也是使用模板实现的,可以适用于不同的数据类型。

12. UDP怎么模拟TCP传输

UDP是一种不可靠的传输协议,而TCP是一种可靠的传输协议。UDP不保证数据传输的可靠性和完整性,因此不能像TCP那样保证数据的准确性和可靠性。但是,可以通过一些技术手段来模拟TCP传输,以保证数据传输的可靠性和完整性。

以下是一些常见的技术手段:

  1. 应用层协议:可以使用一些应用层协议来保证数据的可靠传输,如HTTP、FTP等。这些协议在传输数据时会对数据进行一些处理,以保证数据的完整性和可靠性。

  2. UDP包的确认和重传:可以通过在应用层实现UDP包的确认和重传机制,来模拟TCP传输。UDP包的确认和重传机制可以保证数据的可靠传输。

  3. 超时重传:当发送方发送数据包时,可以设置一个超时时间,如果在该时间内没有收到接收方的确认包,则认为数据包丢失,需要重新发送。

  4. 拥塞控制:可以使用拥塞控制算法来限制发送方的发送速率,以避免网络拥塞,从而保证数据的可靠传输。

总之,虽然UDP不是一个可靠的传输协议,但是可以通过一些技术手段来模拟TCP传输,以保证数据的可靠性和完整性。

13. MySQL索引数据结构

MySQL索引采用B+树作为数据结构,B+树是一种平衡树,它具有以下特点:

  1. 所有数据都存储在叶子节点上,非叶子节点只存储索引信息。

  2. 叶子节点之间通过指针连接起来,形成一个有序的链表,便于范围查询。

  3. 非叶子节点的子节点按照索引值大小排序,便于二分查找。

  4. B+树的高度相对较低,可以减少磁盘I/O的次数,提高查询效率。

14. B树和B+树的区别

B树和B+树都是针对磁盘存储而设计的索引结构,其中B+树是在B树的基础上进行了优化。

B树和B+树的主要区别如下:

  • 数据存储方式不同: B树的非叶子节点和叶子节点都存储数据,而B+树只有叶子节点存储数据。

  • 叶子节点的指针不同: B树的叶子节点存储数据,但是叶子节点之间是相互独立的,而B+树的叶子节点之间是通过指针串联起来的,因此支持范围查询更加高效。

  • 非叶子节点的指针不同: B树的非叶子节点存储数据和指向子节点的指针。而B+树的非叶子节点只存储指向子节点的指针,不存储数据,因此可以存储更多的指针(相对于B树,B+树的非叶子节点所占的大小比较小,因为非叶子节点不包含具体数据,索引一页可以存储更多的索引节点,可以减少磁盘I/O的次数),提高查询效率。

  • 查询效率不同: 由于B+树的叶子节点之间是通过指针串联起来的,因此支持范围查询更加高效。同时,由于B+树的非叶子节点只存储指针,可以存储更多的指针,因此查询效率更高。

综上所述,B+树相对于B树来说,在支持范围查询和查询效率方面更加优秀。但是,在插入和删除操作频繁的情况下,B树的效率可能会更高。

15. C++最左匹配原则

C++中的最左匹配原则指的是,在多重继承的情况下,如果一个类继承了多个父类,那么在访问该类的成员时,会按照继承顺序,从左往右查找。

例如,假设有如下代码:

class A {
public:
    int a;
};

class B {
public:
    int b;
};

class C : public A, public B {
public:
    int c;
};

C obj;
obj.a = 1;
obj.b = 2;
obj.c = 3;

在访问obj的成员时,如果使用obj.a,会先在C类中查找是否有a成员,如果没有则会继续在A类中查找;如果使用obj.b,会先在C类中查找是否有b成员,如果没有则会继续在B类中查找。

因此,最左匹配原则确保了在多重继承的情况下,成员的访问顺序是确定的,不会出现歧义

16. 建立索引的场景

建立索引可以加快数据库的查询速度,提高查询效率,常见的建立索引的场景包括:

  • 主键和唯一性约束: 对于主键和唯一性约束,通常会自动创建索引,以保证数据的唯一性。

  • 经常使用的查询字段: 如果某个字段经常被用于查询,那么建立索引可以加快查询速度。例如,用户表中的用户名、邮箱等字段,订单表中的订单号、用户ID等字段。

  • 外键关联字段: 如果某个表与其他表有外键关联,那么对于外键关联字段建立索引可以加快关联查询的速度。

  • 经常用于排序和分组的字段: 如果某个字段经常用于排序和分组,那么建立索引可以加快排序和分组的速度。例如,订单表中的下单时间、订单金额等字段。

  • 大数据表的关键字段: 如果某个表的数据量很大,那么对于关键字段建立索引可以加快查询速度。例如,新闻网站的文章表、电商网站的商品表等。

需要注意的是,建立索引会增加数据库的存储空间和维护成本,并且在插入、更新和删除数据时也会影响性能,因此需要根据实际情况权衡利弊,避免过度索引。

17. C++中static关键字有什么作用

  1. 静态局部变量:定义在函数内部的变量前加上static关键字,使得该变量在程序执行期间只被初始化一次,即使函数被调用多次。

  2. 静态全局变量:定义在函数外部、文件内部的变量前加上static关键字,可以在当前文件中被访问,但在其他文件中不可见。

  3. 静态函数:在函数声明或定义前面加上static关键字,可以将该函数限制在当前文件的作用域内,其他文件无法访问该函数。

  4. 静态成员变量和函数:在类中使用static关键字定义的成员变量和函数,被所有该类的实例对象所共享,且不依赖于任何实例对象的存在。可以通过类名访问,也可以通过实例对象访问。

18. 自动锁

自动锁是C++11标准中一个新的特性,它使得在多线程编程时使用锁更加方便。在C++11标准中,引入了一个新的类std::lock_guard,它可以在构造函数中实现自动加锁,在析构函数中实现自动解锁。这样,在多线程环境下使用std::lock_guard可以避免忘记手动加锁或解锁的问题,从而提高了程序的稳定性和安全性。

19. 空类的大小

空类(没有成员变量和成员函数的类)的大小在C++中是1字节。这是因为C++要确保每个对象都有一个唯一的地址,因此空类的对象也需要有一个唯一的地址,即使没有任何成员变量和成员函数。因此,编译器会为每个空类的对象分配一个字节的内存空间,以确保它们在内存中占据一个唯一的位置。

20. 构造函数和析构函数可以是虚函数吗?

构造函数不可以,析构函数可以且常常是。

构造函数不能是虚函数的原因是因为虚函数的调用依赖于对象的创建,而构造函数在对象创建过程中执行,因此构造函数无法成为虚函数。在调用虚函数时,程序需要先创建一个对象并将其指针传递给函数,而构造函数在对象创建的过程中就已经被调用,因此构造函数不能是虚函数。

另一方面,析构函数可以是虚函数的原因是因为它在对象被销毁时执行,此时对象的类型已经确定,因此可以在对象销毁时调用正确的析构函数。如果析构函数不是虚函数,那么当使用基类指针删除派生类对象时,只会调用基类的析构函数,而不会调用派生类的析构函数,这可能导致内存泄漏和程序行为不正常。因此,将析构函数声明为虚函数可以确保在对象被销毁时调用正确的析构函数。

21. 进程线程之间的区别

进程与线程的区别在于,进程是一个独立的执行单位(资源分配的单位),具有完全独立的虚拟地址空间,线程是进程中的一个执行单元,一个进程可以包含多个线程。

进程拥有独立的堆、栈、数据区、代码区等系统资源,在创建进程时需要分配独立的内存空间,因此进程拥有独立的系统资源。而线程不拥有独立的系统资源,它们在进程内共享进程的系统资源。这意味着进程间的切换开销比较大,而线程的切换开销比较小。

另外,进程间通信需要依靠操作系统提供的 IPC(Inter-Process Communication)机制,而线程之间的通信则可以直接访问共享的进程资源,通信更加高效。但是,线程间的同步和互斥问题也需要更加注意和处理。

拓展:“为什么进程间的切换开销比较大,而线程的切换开销比较小?”
进程间的切换开销比较大,主要是因为进程间拥有独立的虚拟地址空间和系统资源,当系统需要切换进程时,需要进行上下文切换,即保存当前进程的状态、切换到新的进程的状态,并更新内核数据结构,这个过程需要耗费相对较大的时间和资源。
而线程的切换开销相对较小,主要是因为线程共享进程的虚拟地址空间和系统资源,当系统需要切换线程时,只需要保存当前线程的栈和寄存器等少量信息,并切换到新的线程,这个过程比进程切换开销小得多。线程切换的效率和实现方式有关,一般采用用户级线程(UCL)实现的线程切换开销较小,而内核级线程(KCL)实现的线程切换开销比较大。

  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值