C++常见经典面试题及详解

C++常见经典面试题及详解

  • 一 .static 的使用
  • 二、const的使用
  • 三、指针与引用的区别
  • 四、#define
  • 五 、结构体和类、联合体的区别
  • 六、C++的四种类型转换
  • 七、new-delete 与malloc-free的区别
  • 八、多线程 进程直接的通讯
  • 九、C++三大特性
  • 十、socket通信
  • 十一、拷贝构造函数
  • 十二、内存分配方式以及它们的区别
  • 十三、数据结构与算法
  • 十四、设计模式
  • 十五、STL

一 .static 的使用

c语言中:

  1. 限制全局变量与函数只能在本文件中使用
  2. 默认初始化为0,只执行一次初始化
  3. 从程序运行起就给全局变量分配空间,直到程序结束后释放

c++中 :

  1. 作用于类的成员变量—实现同类的不同对象之间数据的共享,必须在类外初始化;在类实例化之前就已经存在了,并且分配了内存。不属于任何对象,而非静态类成员变量则是属于对象所有的。
    类型 类名::静态成员变量=初始值 ,例: int point::count=2;
  2. 作用于类的成员函数–使其成为静态函数成员,专门用于处理静态成员变量 ,
    static 成员只能类外初始化。
    调用形式:类名::静态成员函数名();

二、const的使用

  1. const修饰普通类型变量:
    const int a;和int const a;意义一样,常整型数a的值保持不变。
  2. const 修饰指针
    A: const 修饰指针指向的内容,则内容为不可变量。 cosnt int a;常量指针
    B: const 修饰指针,则指针为不可变量。 int * const a;常量指针
    C: const 修饰指针和指针指向的内容,则指针和指针指向的内容都为不可变量。 const int
    const a;
  3. const修饰函数参数传递和函数返回值。
    –修饰函数参数
    A:当 const 参数为值时,一般这种情况不需要 const 修饰,因为函数会自动产生临时变量复制实参值。
    B:当 const 参数为指针时,可以防止指针被意外篡改。
    C:当 const 参数为自定义类型时,需要临时对象复制参数,对于临时对象的构造,需要调用构造函数,比较浪费时间,因此我们采取 const 外加引用传递的方法。并且对于一般的 int、double 等内置类型,我们不采用引用的传递方式。
    —修饰返回值
    A:const 修饰内置类型的返回值,修饰与不修饰返回值作用一样。
    B: const 修饰自定义类型的作为返回值,此时返回的值不能作为左值使用,既不能被赋值,也不能被修改。
    C: const 修饰返回的指针或者引用,是否返回一个指向 const 的指针,取决于我们想让用户干什么。
  4. c++ 函数前面和后面 使用const 的作用:
    前面使用const 表示返回值为const
    后面加 const表示函数不可以修改class的成员
    请看这两个函数
    const int getValue();
    int getValue2() const;

三、指针与引用的区别

  1. 指针是一个变量,存储的是一个地址,指向内存的一个存储单元;而引用仅是个别名;
  2. 引用只能在定义时被初始化一次,之后不可变;指针可变;
  3. 引用没有 const,指针有 const;
  4. 引用不能为空,指针可以为空;
  5. “sizeof 引用”得到的是所指向的变量(对象)的大小,而“sizeof 指针”得到的是指针本身的大小;
  6. 指针可以有多级,但是引用只能是一级(int **p;合法 而 int &&a是不合法的)
  7. 从内存分配上看:程序为指针变量分配内存区域,而引用不需要分配内存区域。

四、#define

  1. #define–预处理指令 定义一个标识符来表示一个常量,定义的标识符不占内存,只是一个临时符号,预编译后这个符号就不存在了
    #define 标识符 常量 //注意, 最后没有分号,如果要终止其作用域可以使用 #undef 命令,无法对宏定义中的变量进行类型检查
  2. #ifdef和 #define #endif组合
    一般用于头文件中,用以实现防止多个文件对此同一个头文件的重复引用.实际使用中,即使你的头文件暂时没有被多个文件所引用,为了增加程序可读性,移植性,健壮性等,还是最好都加上。其用法一般为:
    #ifndef <标识>
    #define <标识>
    ……… // include or define sth.
    #endif

五 、结构体和类、联合体的区别

结构体与类:

  1. 默认 struct的成员都是公开的,class的成员是私有的
  2. C语言的结构体是没有继承关系的,而C++的类却有丰富的继承关系
    结构体与联合体:
  3. 结构体 各成员各自拥有自己的内存,遵循内存对齐原则,一个struct变量的总长度等于所有成员的长度之和,
  4. 联合体各成员共用一块内存空间,一个union变量的总长度至少能容纳最大的成员变量,而且要满足是所有成员变量类型大小的整数倍

六、C++的四种类型转换

C风格的强制类型转换很简单,均用 Type b = (Type)a 形式转换。C++风格的类型转换提供了4种类型转换操作符来应对不同场合的应用,如下:

  1. const_cast去掉类型的const或volatile属性
  2. static_cast无条件转换,静态类型转换
  3. dynamic_cast有条件转换,动态类型转换,运行时检查类型安全(转换失败返回NULL)
  4. reinterpret_cast仅重新解释类型,但没有进行二进制的转换

七、new-delete 与malloc-free的区别

  1. malloc与free是C++/C语言的标准库函数,new/delete是C++的运算符。
  2. delete会调用对象的析构函数,和new对应free只会释放内存,new调用构造函数(由于malloc/free是库函数而不是运算符,不在编译器控制权限之内,不能够把执行构造函数和析构函数的任务强加于malloc/free)
  3. new分配内存按照数据类型进行分配,malloc分配内存按照大小分配;
  4. new返回的是指定对象的指针,而malloc返回的是void*,因此malloc的返回值一般都需要进行类型转化;

八、多线程 进程直接的通讯

进程是资源分配的基本单位,线程是cpu调度和程序执行的最小单位。同一个进程中并行运行多个线程,就是对在同一台计算机上运行多个进程的模拟。

进程之间的5种通讯方式 :

  1. 管道:速度慢,容量有限,只有父子进程能通讯
  2. FIFO:任何进程间都能通讯,但速度慢
  3. 消息队列:容量受到系统限制,且要注意第一次读的时候,要考虑上一次没有读完数据的问题
  4. 信号量:不能传递复杂消息,只能用来同步
  5. 共享内存区:能够很容易控制容量,速度快,但要保持同步,比如一个进程在写的时候,另一个进程要注意读写的问题,相当于线程中的线程安全,当然,共享内存区同样可以用作线程间通讯,不过没这个必要,线程间本来就已经共享了同一进程内的一块内存,共享内存就是映射一段能被其他进程所访问的内存,这段共享内存由一个进程创建,但多个进程都可以访问。共享内存是最快的 IPC 方式,它是针对其他进程间通信方式运行效率低而专门设计的。它往往与其他通信机制,如信号两,配合使用,来实现进程间的同步和通信。
  6. 套接字( socket ) : 套解口也是一种进程间通信机制,与其他通信机制不同的是,它可用于不同及其间的进程通信。

多线程之间的通讯:
a . 全局变量 即共享内存
b. 管道
c. 信号槽

九、C++三大特性

一、封装:
封装就是将数据或函数等集合在一个个的单元中(我们称之为类)。
封装的意义在于保护或者防止代码(数据)被我们无意中破坏。
定义一个对象时先调用基类的构造函数、然后调用派生类的构造函数;析构的时候恰好相反:先调用派生类的析构函数、然后调用基类的析构函数。
二、继承:
继承主要实现重用代码,节省开发时间。
子类可以继承父类的一些东西。
三、多态
多态:同一操作作用于不同的对象,可以有不同的解释,产生不同的执行结果。在运行时,可以通过指向基类的指针,来调用实现派生类中的方法。
编译时的多态性特点是运行速度快,运行时的多态性是高度灵活和抽象

  1. 编译时多态
    函数重载与运算符重载:定义多个同名函数,这些同名函数的参数类型、个数、返回值可以不一样。
  2. 运行时多态:
    函数重写:返回类型,函数名,参数列表全部相同
    虚函数重写:通过父类访问子类中定义的函数
    纯虚函数:后面加了 =0 的虚函数,纯虚函数没有实现;
    抽象类:包含了纯虚函数的类叫做抽象类,抽象类不能创建实例(new),需要由子类将它实现之后,才能创建实例。抽象类也就是所谓的“接口”。
    虚函数表,编译器为每一个类维护一个虚函数表,每个对象的首地址保存着该虚函数表的指针,同一个类的不同对象实际上指向同一张虚函数表。
    注:基类的析构函数应设置为虚析构函数,构造不能设置为虚的。

十、socket通信

Socket是应用层与TCP/IP协议族通信的中间软件抽象层,它是一组接口。
C++常见经典面试题及详解_第1张图片

TCP通信过程:
C++常见经典面试题及详解_第2张图片

先从服务器端说起。服务器端先初始化Socket,然后与端口绑定(bind),对端口进行监听(listen),调用accept阻塞,等待客户端连接。在这时如果有个客户端初始化一个Socket,然后连接服务器(connect),如果连接成功,这时客户端与服务器端的连接就建立了。客户端发送数据请求,服务器端接收请求并处理请求,然后把回应数据发送给客户端,客户端读取数据,最后关闭连接,一次交互结束。
udp通讯:
server:socket ->bind ->recvform ->sendto ->close
client : socket ->sendto ->recvform ->close
udp与tcp的区别:

  1. TCP面向连接, UDP面向无连接的
  2. TCP有保障的,UDP传输无保障的
  3. TCP是效率低的,UDP效率高的
  4. TCP是基于流的,UDP基于数据报文
  5. TCP传输重要数据,UDP传输不重要的数据
    三次握手:
    C++常见经典面试题及详解_第3张图片

当客户端调用connect时,触发了连接请求,向服务器发送了SYN J包,这时connect进入阻塞状态;服务器监听到连接请求,即收到SYN J包,调用accept函数接收请求向客户端发送SYN K ,ACK J+1,这时accept进入阻塞状态;客户端收到服务器的SYN K ,ACK J+1之后,这时connect返回,并对SYN K进行确认;服务器收到ACK K+1时,accept返回,至此三次握手完毕,连接建立。

四次挥手:
C++常见经典面试题及详解_第4张图片

某个应用进程首先调用close主动关闭连接,这时TCP发送一个FIN M;
另一端接收到FIN M之后,执行被动关闭,对这个FIN进行确认。它的接收也作为文件结束符传递给应用进程,因为FIN的接收意味着应用进程在相应的连接上再也接收不到额外数据;
一段时间之后,接收到文件结束符的应用进程调用close关闭它的socket。这导致它的TCP也发送一个FIN N;
接收到这个FIN的源发送端TCP对它进行确认。

为什么连接的时候是三次握手,关闭的时候却是四次握手?
答:因为当Server端收到Client端的SYN连接请求报文后,可以直接发送SYN+ACK报文。其中ACK报文是用来应答的,SYN报文是用来同步的。但是关闭连接时,当Server端收到FIN报文时,很可能并不会立即关闭SOCKET,所以只能先回复一个ACK报文,告诉Client端,“你发的FIN报文我收到了”。只有等到我Server端所有的报文都发送完了,我才能发送FIN报文,因此不能一起发送。故需要四步握手。

十一、拷贝构造函数

是一种特殊的构造函数,函数的名称必须和类名称一致,它必须的一个参数是本类型的一个引用变量。
.用一个已存在的对象来构造一个新对象,称为对象的拷贝或者复制

  1. 拷贝构造函数调用的时机:
    a、当函数的参数为类的对象时
    b、当函数的返回值为类的对象时
    c、对象用另一个已存在对象进行初始化
  2. 深拷贝与浅拷贝
    a、浅拷贝:默认拷贝构造函数 ,传递对象给函数参数或者函数返回对象都能很好的进行,这是因为编译器会给我们自动产生一个拷贝构造函数,这就是“默认拷贝构造函数”,用“老对象”的数据成员的值对“新对象”的数据成员一一进行赋值,
    b、深拷贝:对于对象中动态成员,应该重新动态分配空间。当数据成员中有指针时和静态成员,必须要用深拷贝。
    为什么拷贝构造函数必须是引用传递,不能是值传递?
    防止递归引用。
    具体:
    当 一个对象需要以值方式传递时,编译器会生成代码调用它的拷贝构造函数以生成一个复本。如果类A的拷贝构造函数是以值方式传递一个类A对象作为参数的话,当 需要调用类A的拷贝构造函数时,需要以值方式传进一个A的对象作为实参; 而以值方式传递需要调用类A的拷贝构造函数;结果就是调用类A的拷贝构造函数导 致又一次调用类A的拷贝构造函数,这就是一个无限递归。

十二、内存分配方式以及它们的区别

  1. 从静态存储区域分配。内存在程序编译的时候就已经分配好,这块内存在程序的整个运行期间都存在。例如全局变量,static 变量。
  2. 在栈上创建。在执行函数时,函数内局部变量的存储单元都可以在栈上创建,函数执行结束时这些存储单元自动被释放。栈内存分配运算内置于处理器的指令集。
  3. 从堆上分配,亦称动态内存分配。程序在运行的时候用malloc 或new 申请任意多少的内存,程序员自己负责在何时用free 或delete 释放内存。动态内存的生存期由程序员决定,使用非常灵活,但问题也最多。

十三、数据结构与算法

每一种数据结构都有着独特的数据存储方式

  1. 数组 :可以再内存中连续存储多个元素的结构,在内存中的分配也是连续的
    优点:
    a、按照索引查询元素速度快
    b、按照索引遍历数组方便
    缺点:
    a、数组的大小固定后就无法扩容了
    b、数组只能存储一种类型的数据
    c、添加,删除的操作慢,因为要移动其他的元素。
  2. 栈:栈是一种特殊的线性表,仅能在线性表的一端操作,栈顶允许操作,先进后出
  3. 队列:队列与栈一样,也是一种线性表,不同的是,队列可以在一端添加元素,在另一端取出元素,也就是:先进先出
  4. 链表:链表是物理存储单元上非连续的、非顺序的存储结构,数据元素的逻辑顺序是通过链表的指针地址实现,每个元素包含两个结点,一个是存储元素的数据域 (内存空间),另一个是指向下一个结点地址的指针域。

十四、设计模式

参考https://www.cnblogs.com/jesonjason/p/5402627.html

  1. 单例模式
    保证一个类仅有一个实例,并提供一个访问它的全局访问点。
     所有类都有构造方法,不编码则系统默认生成空的构造方法,若有显示定义的构造方法,默认的构造方法就会失效。
  2. 观察者模式——老板回来,我不知道
      定义了一种一对多的依赖关系,让多个观察者对象同时监听某一个主题对象;
     这个主题对象在状态发生变化时,会通知所有观察者对象,使它们能够自动更新自己
  3. 工厂模式,
    定义一个用于创建对象的接口,让子类决定实例化哪一个类。Factory Method 使一个类的实例化延迟到其子类。
     简单工厂模式是由一个具体的类去创建其他类的实例,父类是相同的,父类是具体的。应该考虑用一个单独的类来做这个创造实例的过程,这就是工厂
  4. 策略模式——商场促销
      它定义了算法家族,分别封装起来,让它们之间可以相互替换,此模式让算法的变化,不会影响到使用算法的客户

十五、STL

主要包括算法和容器
在STL中,容器分为两类:序列式容器和关联式容器。

序列式容器,其中的元素不一定有序,但都可以被排序。如:vector、list、deque、stack、queue、heap、priority_queue、slist;
关联式容器,内部结构基本上是一颗平衡二叉树。所谓关联,指每个元素都有一个键值和一个实值,元素按照一定的规则存放。如:RB-tree、set、map、multiset、multimap、hashtable、hash_set、hash_map、hash_multiset、hash_multimap。

下面各选取一个作为说明。
vector:它是一个动态分配存储空间的容器。区别于c++中的array,array分配的空间是静态的,分配之后不能被改变,而vector会自动重分配(扩展)空间。
set:其内部元素会根据元素的键值自动被排序。区别于map,它的键值就是实值,而map可以同时拥有不同的键值和实值。

迭代器是STL的精髓,迭代器提供了一种方法,使它能够按照顺序访问某个容器所含的各个元素,但无需暴露该容器的内部结构。它将容器和算法分开,好让这二者独立设计。

你可能感兴趣的:(C++常见经典面试题及详解)