C++面经(简洁版)

1. 谈谈C和C++的认识

  • C++在C的基础上添加类,C是一种结构化语言,它的重点在于数据结构和算法
  • C语言的设计首要考虑的是如何通过一个过程,对输入进行运算处理得到输出
  • 而对C++,首先要考虑的是如何构造一个对象,通过封装一下行为和属性,通过一些操作将对象的状态信息输出

2. 对象

2.1 什么是面向对象

  • 就是一种对现实世界的理解和抽象,将问题转换成对象进行解决需求处理的思想。

2.2 如何限制一个类对象只能在堆上分配空间 

2.2.1 方法一: 使用delete禁掉默认析构函数 

#include <iostream>
using namespace std;class HeapOnly
{
public:HeapOnly(){_str = new char[10];}~HeapOnly() = delete;void Destroy(){delete[] _str;operator delete(this);}private:char* _str;//...
};int main()
{HeapOnly* ptr = new HeapOnly;ptr->Destroy();return 0;
}
  •  只能在堆上申请空间,可以直接使用delete把析构函数禁掉就行了 
  • 自己再实现一个释放空间的函数(Destory)

 2.2.2  方法二: 将析构函数私有化 

#include <iostream>
#include <stdlib.h>
using namespace std;
class HeapOnly
{
public:/*static void Delete(HeapOnly* p){delete p;}*/void Delete(){delete this;}private:// 析构函数私有~HeapOnly(){cout << "~HeapOnly()" << endl;}
private:int _a;
};int main()
{//HeapOnly hp1;// error//static HeapOnly hp2;// errorHeapOnly* ptr = new HeapOnly;ptr->Delete();return 0;
}
  • 只能在堆上申请空间,可以直接将析构函数私有化
  • 自己再实现一个调用析构函数的函数(Delete)

2.2.3 方法三: 将构造函数私有化(禁掉拷贝) 

#include <iostream>
#include <stdlib.h>
using namespace std;
class HeapOnly
{
public:// 提供一个公有的,获取对象的方式,对象控制是new出来的static HeapOnly* CreateObj(){return new HeapOnly;}// 防拷贝HeapOnly(const HeapOnly& hp) = delete;HeapOnly& operator=(const HeapOnly& hp) = delete;
private:// 构造函数私有HeapOnly():_a(0){}
private:int _a;
};int main()
{/*HeapOnly hp1;static HeapOnly hp2;HeapOnly* hp3 = new HeapOnly;delete hp3;*/HeapOnly* hp3 = HeapOnly::CreateObj();//HeapOnly copy(*hp3);delete hp3;return 0;
}
  • 只能在堆上申请空间,可以直接使用构造函数私有化
  • 并禁止掉2个拷贝构造和赋值重载
  • 自己再实现提供一个公有的,获取对象的方式*(CreatObj),返回值是static

注意: 如果需要派生该类,就不能将构造函数设为私有或删除。如果需要限制派生类对象只能在堆上构造,可以在派生类中重载 new 和 delete 运算符,强制所有派生类对象都通过堆来创建和销毁 

2.3 如何限制一个类对象只能在栈上分配空间

2.3.1  方法一: 构造函数私有化(禁掉new)  

#include <iostream>
#include <stdlib.h>
using namespace std;
class StackOnly
{
public:static StackOnly CreateObj(){StackOnly st;return st;}// 不能防拷贝//StackOnly(const StackOnly& st) = delete;//StackOnly& operator=(const StackOnly& st) = delete;void* operator new(size_t n) = delete;
private:// 构造函数私有StackOnly():_a(0){}
private:int _a;
};int main()
{/*StackOnly st1;static StackOnly st2;StackOnly* st3 = new StackOnly;*/StackOnly st1 = StackOnly::CreateObj();// 拷贝构造static StackOnly copy2(st1); // 不好处理,算是一个小缺陷//StackOnly* copy3 = new StackOnly(st1);return 0;
}
  • 只能在堆上申请空间,可以直接使用构造函数私有化
  • 并且使用delete禁止掉new
  • 自己再实现提供一个公有的,获取对象的方式*(CreatObj),返回值是static

2.4 private  protected public

第一: private,public,protected的访问范围: 

类中的函数 友元函数 子类函数 类的对象 

  • private: 只能由该类中的函数、其友元函数访问,不能被任何其他访问,该类的对象也不能访问.

  • protected: 可以被该类中的函数、子类的函数、以及其友元函数访问,但不能被该类的对象访问

  • public: 可以被该类中的函数、子类的函数、其友元函数访问,也可以由该类的对象访问

 第二:类的继承后方法属性变化:

  • 使用private继承,父类的所有方法在子类中变为private;
  • 使用protected继承,父类的protected和public方法在子类中变为protected,private方法不变;
  • 使用public继承,父类中的方法属性不发生改变;

2.5 拷贝构造函数参数中为什么有时候要加const

class test{public:test(const test& a){cout<<"拷贝构造函数"<<endl;}
};test get_test()
{test a;return a;
}
int main()
{    test  b=get_test();	
}
  • 有时候时候需要加上const修饰符的原因是为了确保被拷贝的对象在拷贝过程中不会被修改

  • 可以提高代码的可靠性安全性(特别是在多线程的情况下)

3.多态

3.1 什么是多态 

就是多种形态,具体点就是去完成某个行为,当不同的对象去完成时会

产生出不同的状态

  • 派生类对象的地址可以赋值给基类指针。对于通过基类指针调用基类和派生类中都有的同名、同参数表的虚函数的语句,编译时并不确定要执行的是基类还是派生类的虚函数;
  • 当程序运行到该语句时,如果基类指针指向的是一个基类对象,则基类的虚函数被调用,
  • 如果基类指针指向的是一个派生类对象,则派生类的虚函数被调用。这种机制就叫作“多态(polymorphism)

静态多态(编译阶段确定)

  • 函数重载 和 函数模板 的使用

 动态多态(运行阶段确定)

  • 派生类 和 虚函数 的使用

3.2 继承和多态区别与联系?

  • 不同点: 继承是子类使用父类的方法,而多态则是父类使用子类的方法。 
  • 继承是为了重用代码,有效实现代码重用,减少代码冗余
  • 多态是一种接口继承,增强接口的扩展性

3.3 什么是重载、重写(覆盖)、重定义(隐藏)? 

  • 重载 : a. 两个函数在同一作用域,b.函数名相同,参数不同(类型,顺序,个数)
  • 重写(覆盖):
    两个函数分别在基类派生类的作用域
    函数名/参数/返回值都必须相同(协变除外) -> 简称三同
    特殊点1: 两个函数都必须是虚函数(其实子类可以不用加virtual)
    特殊点2: 如果返回值不同,则必须是父类的指针或引用
  •  重定义(隐藏)
    两个函数分别在基类派生类的作用域
    函数名相同
    两个基类和派生类的同名函数不构成重写,那么就是重定义(隐藏)

 3.4 多态的实现原理? 

  •  每一个类中都有一个vfptr,它是一张虚函数的表,本质就是一个函数指针数组
  • 多态调用:运行时去指向对象的虚表中找到函数地址,并进行调用(在符合多态的两个条件时,)

 3.5 产生多态的2个条件

  • 父类的指针或引用去调用
  • 满足虚函数的重写

3.6 inline函数可以是虚函数吗?

  • 不可以,但是inline只是一个建议,当一个函数是虚函数以后,多态调用中,inline会直接失效

3.7 静态成员可以是虚函数吗? 

  • 不可以 ,因为 静态成员函数没有this指针 ,使用类型::成员函数的调用方式无法访问虚函数表,所以静态成员函数无法放进虚函数表

3.8  构造函数可以是虚函数吗

  • 不可以,virtual函数为了实现多态,运行时去虚表中找对应虚函数进行调用
  • 而对象中虚表指针都是构造函数初始化列表阶段才初始化的
  • 所以构造函数的虚函数是没有意义的

 3.9 析构函数可以是虚函数吗

  • 可以,建议基类的析构函数定义成虚函数
  • 析构函数名都会被处理成destructor,所以这里析构函数完成了虚函数重写
  • 当基类的指针指向派生类对象的时候,发生多态,然后基类和派生类就会统一析构
    如果不将基类的析构函数定义为虚函数的话,那么派生类的析构函数就无法执行。

3.10  拷贝构造 和 operator=可以是虚函数?

  •  拷贝构造不可以,拷贝构造也是构造函数,同上
  • operator=可以但是没有什么实际价值

3.11  对象访问普通函数快还是虚函数更快?

  • 不构成多态, 是一样快的。
  • 构成多态,则调用的普通函数快
  • 因为构成多态,运行时调用虚函数需要到虚函数表中去查找

3.12 虚函数是在什么阶段生成的,存在哪的?

  • 编译阶段就生成好的,存在代码段(常量区)

3.13 什么是抽象类?抽象类的作用? 

  • 在虚函数的后面写上 =0 ,则这个函数为纯虚函数。包含纯虚函数的类叫做抽象类
  • 抽象类强制重写了虚函数,另外抽象类体现出了 接口继承关系

4. 内存管理

4.1 C++的内存分配

  • 堆区:一般由程序员自动分配,如果程序员没有释放,程序结束时可能有OS回收。其分配类似于链表。

  • 栈区:由编译器自动分配和释放,存放为运行函数分配的局部变量,函数参数,返回数据,返回地址等,其操作类似于数据结构中的栈。

  • 全局区(静态区static):存放全局变量,静态变量,常量。结束后由系统释放

  • 常量区(字符串常量区):存放常量字符串,程序结束后有系统释放

  • 代码区:存放函数体(类成员函数和全局区)的二进制代码。

4.2 简述c、C++程序编译的内存分配情况

  • 从静态存储区域分配
  • 在栈上分配
  • 在堆上分配

5. 关键字

5.1 extern 和 static 的区别,什么情况用前者什么情况用后者

  •  extern外部变量: 叫做外部声明,被extern修饰的变量,会告诉编译器这个变量的定义需要再其他文件中查找
  • static静态变量: 被static修饰的变量,将会被改变生命周期,直到程序运行结束的时候,系统才会释放,所以无需手动释放

5.2 声明和定义的区别

  • 声明是告诉编译器名字的存在

  • 而定义是为名字分配内存并实现其功能

  • 在使用变量或函数之前,必须先进行声明或定义。 

5.4 strcpy和memcpy的区别 

  • strcpy和memcpy都是在C语言和C++语言中用于复制内存块的函数,但它们在使用和效率上有所不同。

  • 但他们两者在拷贝时,如果源地址不够时,都会出现内存越界和缓冲区溢出问题

  • strcpy用于将一个以null结尾的字符串从源地址复制到目标地址。它会复制整个字符串,包括null终止符,直到遇到null为止 
  • memcpy用于将一段内存块从源地址复制到目标地址,可以复制任意长度的内存块,而不仅限于字符串,且不关心null 
  • 但是strcpy具有更简单的语法和更高的可读性,因此在处理字符串时,通常首选strcpy函数

5.5 关于类模板是否可以定义虚函数 

  • 类模板可以定义成虚函数,但是需要注意一些细节。
    首先,需要明确的是,类模板本身是一个模板,不能直接定义为虚函数类模板的实例化才能被定义为虚函数。

 6. 运算操作符

6.1 x=x+1,x+=1,x++哪个效率高

  • 在大多数情况下,这两者是差不多的,编译器都会转换成相同的机器码,这意味着它们在程序运行时具有相同的性能和速度
  • 但是由于x++需要创建一个临时变量来保存x的旧值,所以它就会比其他两者的效率稍微慢点,但是由于CPU处理数据的速度太快了,所以可以忽略不记

7.编译内存相关

7.1 C++程序编译过程

编译过程分为四个过程:编译(编译预处理编译、优化),汇编链接 

  • 预处理:头文件的展开,宏替换,去注释
  • 编译:把C/C++语言变成汇编代码;
  • 汇编:通过汇编变成以.o结尾的目标二进制文件(不可执行)
  • 链接:多个目标文件连接库进行链接的,从而生成可执行的程序 .exe 文件。

 7.2 栈和堆的区别

  • 申请方式:栈是系统自动分配,堆是程序员主动申请
  • 栈在内存中是连续的一块空间(向低地址扩展)最大容量是系统预定好的,堆在内存中的空间(向高地址扩展)是不连续的

  • 申请效率:栈是有系统自动分配,申请效率高,但程序员无法控制;堆是由程序员主动申请,效率低,使用起来方便但是容易产生碎片
  • 存放的内容:栈中存放的是局部变量,函数的参数;堆中存放的内容由程序员控制

7.3  全局变量定义在头文件中有什么问题

  • 如果在头文件中定义全局变量,当该头文件被多个文件 include 时,该头文件中的全局变量就会被定义多次,导致重复定义,因此不能再头文件中定义全局变量。 

7.4  内存对齐

什么是内存对齐?内存对齐的原则?为什么要进行内存对齐,有什么优点?

内存对齐编译器将程序中的每个“数据单元”安排在字的整数倍的地址指向的内存之中

内存对齐的原则:

  • 结构体变量的首地址 = min(最宽基本类型大小,对齐基数)的整除
  • 其他成员的地址偏移量 = min(成员大小,对齐基数)的整数倍 + 填充字节
  • 总大小 = min(最宽基本类型大小,对齐基数) + 填充字节

进行内存对齐的原因:(主要是硬件设备方面的问题)

  • 某些硬件设备只能存取对齐数据,存取非对齐的数据可能会引发异常;
  • 某些硬件设备不能保证在存取非对齐数据的时候的操作是原子操作
  • 相比于存取对齐的数据,存取非对齐的数据需要花费更多的时间
  • 某些处理器虽然支持非对齐数据的访问,但会引发对齐陷阱(alignment trap);
  • 某些硬件设备只支持简单数据指令非对齐存取,不支持复杂数据指令的非对齐存取。

7.5  类的大小 

说明:类的大小是指类的实例化对象的大小,用 sizeof 对类型名操作时,结果是该类型的对象的大小。

计算原则:

  • 遵循结构体的对齐原则
  • 与普通成员变量有关,与成员函数和静态成员无关。即普通成员函数,静态成员函数,静态数据成员,静态常量数据成员均对类的大小无影响。因为静态数据成员被类的对象共享,并不属于哪个具体的对象。
  • 虚函数对类的大小有影响,是因为虚函数表指针的影响。
  • 虚继承对类的大小有影响,是因为虚基表指针带来的影响。
  • 空类的大小是一个特殊情况,空类的大小为 1,当用 new 来创建一个空类的对象时,为了保证不同对象的地址不同,空类也占用存储空间。

 7.6 什么是内存泄漏

  • 内存泄漏:由于疏忽或错误导致的程序未能释放已经不再使用的内存

7.7  智能指针有哪几种?智能指针的实现原理?

  •  unique_ptr: 禁掉了拷贝构造和赋值重载

  • shared_ptr: 共同管理一段空间,引用计数 记录管理者 

  • weak_ptr: 为了解决shared_ptr循环引用的问题,是它的nextprev不增加计数 

 8. 语言对比

8.1 C++11 引入了什么新增加的内容

8.1.0 智能指针

  • auto_ptr  资源管理权转移,不负责任的拷贝,会导致被拷贝对象悬空
  • unique_ptr: 禁掉了拷贝构造和赋值重载

  • shared_ptr: 共同管理一段空间,引用计数 记录管理者 

  • weak_ptr: 为了解决shared_ptr循环引用的问题,是它的nextprev不增加计数 

8.1.1 auto类型推导

  • auto 关键字:自动类型推导,编译器会在 编译期间 通过初始值推导出变量的类型,通过 auto 定义的变量必须有初始值。

 8.1.2 lambda表达式

[捕捉列表](参数列表)mutable->返回值类型{函数体实现}

  • mutable:默认情况下,lambda函数总是一个const函数,mutable可以取消其常量性。使用该修饰符时参数列表不可省略 (即使参数为空)。

8.1.3 右值引用 

不能取地址的叫常量,只能用右值引用

    // 对右值的左值引用// double& r1 = 1.1 + 2.2;// errorconst double& r1 = 1.1 + 2.2;// 对左值的右值引用//int&& rr5 = b;// errorint&& rr5 = move(b);

出现的原因 : 

  • 左值引用: 中的引用返回,只能解决出了作用域还存在的
    且无法解决string中的to_string的返回值,以及有些函数返回值是二维数的问题

具体使用是:

  •  右值引用+ 移动构造/移动赋值 (用swap, 交换将亡值)

8.2 C和C++的区别

  • 语言自身:
     C 语言是面向过程的编程,它的主要特点是函数
    C++ 是面向对象的编程, 它的主要特点是

  • 应用领域:
    C 语言主要用于嵌入式领域,驱动开发等与硬件直接打交道的领域
    C++ 可以用于应用层开发,用户界面开发等与操作系统打交道的领域

  • C++ 对 C 的“增强”,表现在以下几个方面:
    类型检查更为严格。增加了面向对象的机制、泛型编程的机制(Template)、异常处理、运算符重载、标准模板库(STL)、命名空间(避免全局命名冲突)

8.3 面向对象

面向对象:对象是指具体的某一个事物,这些事物的抽象就是类,类中包含成员变量成员方法

  • 封装:将具体的实现过程和数据封装成一个函数,只能通过接口进行访问,降低耦合性。

  • 继承:子类使用父类的方法

  • 多态:去完成某个行为,当不同的对象去完成时会产生出不同的状态

9. 关键字库函数 

9.1 sizeof 和 strlen 的区别

  1. strlen 是头文件 中的函数sizeof 是 C++ 中的运算符
  2. strlen 测量的是字符串的实际长度(其源代码如下),以 \0 结束。
    而 sizeof 测量的是字符数组的分配大小
  3. sizeof会计算\0

9.2  explicit 的作用 

#include <iostream>
#include <cstring>
using namespace std;class A
{
public:int var;explicit A(int tmp){var = tmp;cout << var << endl;}
};
int main()
{A ex(100);A ex1 = 10; // error: conversion from 'int' to non-scalar type 'A' requestedreturn 0;
}
  • 作用: 避免编译器进行隐式类型转换

9.3  static 的作用

作用: 定义静态变量,静态函数

  • 保持变量内容持久:改变了变量的生命周期
  • 隐藏:static 作用于全局变量和函数,改变了全局变量和函数的作用域,使得被static修饰的变量和函数只能在当前文件中访问
  • 类的静态成员函数中只能访问静态成员变量或者静态成员函数,不能将静态成员函数定义成虚函数

9.3.1 static在类中使用的注意事项(定义、初始化和使用 )

静态成员变量

  • 静态成员变量是在类内进行声明,在类外进行定义和初始化,在类外进行定义和初始化的时候不要出现 static 关键字和private、public、protected 访问规则。

  • 静态成员变量相当于类域中的全局变量,被类的所有对象所共享,包括派生类的对象。

静态成员函数

  • 静态成员函数不能调用非静态成员变量或者非静态成员函数,因为静态成员函数没有 this 指针。
  • 静态成员函数能做为类作用域的全局函数
  • 静态成员函数不能声明成虚函数(virtual)、const 函数和 volatile 函数。 

 9.4 static全局变量和普通全局变量的异同

相同点: 

  • 存储方式:普通全局变量和 static 全局变量都是静态存储方式

不同点:

  • 作用域: 普通全局变量的作用域是整个源程序,而static全局变量只在当前文件有效,且只能初始化一次

9.5 const 作用及用法

9.5.1 作用:

  • const 修饰成员变量,定义成 const 常量,相较于宏常量,可进行类型检查,节省内存空间,提高了效率。
  •  const 修饰函数参数,使得传递过来的函数参数的值不能改变
  • const 修饰成员函数,使得成员函数不能修改任何类型的成员变量(mutable 修饰的变量除外),也不能调用非 const 成员函数,因为非 const 成员函数可能会修改成员变量。

9.5.2 在类中的用法:

const 成员变量:

  • const 成员变量只能在类内声明、定义,在构造函数初始化列表中初始化
  • const 成员变量只在某个对象的生存周期内是常量,对于整个类而言却是可变的,因为类可以创建多个对象,不同类的 const 成员变量的值是不同的。因此不能在类的声明中初始化 const 成员变量,类的对象还没有创建,编译器不知道他的值

const 成员函数:

  • 不能修改成员变量的值,除非有 mutable 修饰;只能访问成员变量。
  • 不能调用非常量成员函数,以防修改成员变量的值

9.6 define 和 const 的区别

区别

  • 编译阶段:define 是在编译预处理阶段进行替换,const 是在编译阶段确定其值
  • 安全性:define 定义的宏常量没有数据类型,所以不会进行类型安全检查,而const 定义的常量是有类型的,所以会进行类型安全检查
  • 内存占用:define 定义的宏常量,是进行替换的,所以内存中有多个备份会占用的是代码段的空间;const 定义的常量占用静态存储区的空间,因为整个程序运行过程中只有一份
  • 调试:define不支持调试,而const支持调试,因为define在预编译阶段就已经进行替换

const 的优点:

  • 有数据类型,在定义式可进行安全性检查。
  • 可调式。
  • 占用较少的空间。

9.7  define 和 typedef 的区别

  • define不进行类型安全的检查,而typedef会进行类型安全的检查
  • define没有作用域的限制,而typedef有作用域的限制

9.8 用宏实现比较大小,以及两个数中的最小值 

#include <iostream>
#define MAX(X, Y) ((X)>(Y)?(X):(Y))
#define MIN(X, Y) ((X)<(Y)?(X):(Y))
using namespace std;int main ()
{int var1 = 10, var2 = 100;cout << MAX(var1, var2) << endl;cout << MIN(var1, var2) << endl;return 0;
}
/*
程序运行结果:
100
10
*/
  • 使用define模拟函数,但是需要记得加()

9.9 inline 作用及使用方法

作用:

  • inline是一个关键字,被inline修饰的函数叫做内联函数,内联函数不会进行函数跳转,而是直接展开(通过反汇编观察),但是具体展不展开还是要看编译器,
  • 这样可以大大减少由函数调用带来的开销,从而提高程序的运行效率
  • 去除函数只能定义一次的限制(内联函数可以在头文件中被定义,并被多个 .cpp 文件 include,而不会有重定义错误)

 使用方法:

  • 类内定义成员函数默认是内联函数
  • 而在类外定义就直接加 inline关键字就行了

9.10 宏定义(define)和内联函数(inline)的区别

  • 二者都是在编译阶段处理的,但是lnline是直接内嵌到目标代码中的,而宏只是一个简单的文本替换
  • 内联函数是真正的函数,而宏定义编写较为复杂,常需要增加一些括号来避免歧义。
  • 宏定义只进行文本替换,不会做各种安全检查。而内联函数是真正的函数,会做各种检查

9.11 new 的作用?

new 是 C++ 中的关键字,用来动态分配内存空间,实现方式如下:

int *p = new int[5]; 

9.12 new 和 malloc 如何判断是否申请到内存?

  • malloc :成功申请到内存,返回指向该内存的指针;分配失败,返回 NULL 指针
  • new :内存分配成功,返回该对象类型的指针;分配失败,抛出 bad_alloc 异常

9.13 delete 实现原理?delete 和 delete[] 的区别?

delete 的实现原理:

  • 首先执行该对象所属类的析构函数
  • 进而通过调用 operator delete 的标准库函数来释放所占的内存空间。

delete 和 delete [] 的区别:

  • delete 用来释放单个对象所占的空间,只会调用一次析构函数;
  • delete [] 用来释放数组空间,会对数组中的每个成员都调用一次析构函数。

9.14 new 和 malloc 的区别,delete 和 free 的区别 

  • 相同点:都是从堆上申请空间,并且需要用户手动释放。

  • 不同点:
    malloc和free是函数,new和delete是操作符
    malloc申请的空间不会初始化,new申请的空间会初始化,即调用构造函数
    malloc申请空间时,需要手动计算空间大小并传递,new只需在其后跟上空间的类型即可
    malloc的返回值是void*,在使用时必须强转,new不需要,因为new后跟的是空间的类型
    malloc申请失败时,返回的是NULL,因此使用时必须判空而new不需要,但是new需要捕获异常

9.15 malloc 的原理?malloc 的底层实现?

malloc 的原理:

  • 当开辟的空间小于 128K 时,调用 brk() 函数,通过移动 _enddata 来实现;
  • 当开辟空间大于 128K 时,调用 mmap() 函数,通过在虚拟地址空间中开辟一块内存空间来实现

9.16 C 和 C++ struct 的区别?

  • 一个是自定义数据类型,而另一个是抽象数据类型
  • C语言中的struct不能包含成员函数,C++中的struct可以包含成员函数,区别在于访问控制 

9.17 为什么有了 class 还保留 struct?

  • C++ 是在 C 语言的基础上发展起来的,为了与 C 语言兼容,C++ 中保留了 struct

9.18 struct 和 union 的区别

// 大小 = 所有变量类型最大的那个 的 整数倍
typedef union
{char c[10];int i;double d; // double 8 字节,按该类型的倍数分配大小
} u22;// 需要遵循内存对齐规则
typedef struct s2
{char c;   // 1 字节char cc;  // 1(char)+ 1(char)= 2 字节double d; // 2 + 6(内存对齐)+ 8(double)= 16 字节
} s22;
  • 联合体和结构体都是由若干个数据类型不同的数据成员组成。
    使用时,联合体只有一个有效的成员;
    而结构体所有的成员都有效。

  • 对联合体的不同成员赋值,将会对覆盖其他成员的值
    而对于结构体的对不同成员赋值时,相互不影响。

  • 联合体的大小为其内部所有变量的最大值,按照最大类型的倍数进行分配大小
    结构体分配内存的大小遵循内存对齐原则。

9.19 truct 和 class 区别

  • struct的成员默认是公有的,而类的成员默认是私有的 

  • C语言中的struct不能包含成员函数,C++中的struct和class都可以包含成员函数,
    区别在于访问控制 
  • class可以用于定义模板参数,struct不能用于定义模板参数。

9.20  volatile 的作用?是否具有原子性,对编译器有什么影响?

  • volatile 的作用:
    当对象的值可能在程序的控制或检测之外被改变时,应该将该对象声明为 violatile,
    告知编译器不应对这样的对象进行优化。
  • volatile不具有原子性
  • volatile 对编译器的影响:
    使用该关键字后,编译器不会对相应的对象进行优化,应该在真实的物理地址空间中拿数据
    不会将变量从内存缓存到寄存器中
    防止多个线程有可能使用内存中的变量,也有可能使用寄存器中的变量,从而导致程序错误。

9.21 什么情况下一定要用 volatile, 能否和 const 一起使用?

使用 volatile 关键字的场景:

  • 多个线程都会用到某一变量,并且该变量的值有可能发生改变时,需要用 volatile 关键字对该变量进行修饰;
  • 中断服务程序中访问的变量或并行设备的硬件寄存器的变量,最好用 volatile 关键字修饰。

volatile 关键字和 const 关键字可以同时使用,某种类型可以既是 volatile 又是 const ,同时具有二者的属性。

在C++多线程中,volatile不具有原子性;无法对代码重新排序实施限制。
能干什么:告诉编译器不要在此内存上做任何优化。如果对内存有只写未读的等非常规操作,如

x=10;
x=20;

编译器会优化为:

x=20;

volatile 就是阻止编译器进行此类优化。

9.22 extern C 的作用?

当 C++ 程序 需要调用 C 语言编写的函数

// 可能出现在 C++ 头文件<cstring>中的链接指示
extern "C"{int strcmp(const char*, const char*);
}
  • C++ 和 C语言编译函数签名方式不一样
  • extern关键字可以让两者保持统一,这样才能找到对应的函数

9.23 sizeof(1==1) 在 C 和 C++ 中分别是什么结果?

  • C语言没有布尔类型,因此按整数处理,有可能是4字节,也有可能是8字节
  • 而C++有布尔类型,占1字节

10. 类相关

10.1 什么是虚函数?什么是纯虚函数? 

  • 虚函数:被 virtual 关键字修饰的成员函数,就是虚函数。

  • 纯虚函数:纯虚函数在类中声明时,加上 =0;

10.2 虚函数和纯虚函数的区别?

  • 使用方式不同:虚函数可以直接使用,纯虚函数必须在派生类中实现后才能使用;
  • 定义形式不同:虚函数在定义时在普通函数的基础上加上 virtual 关键字,纯虚函数定义时除了加上virtual 关键字还需要加上 =0;

10.3 对虚函数表的理解

  •  虚函数表存放的内容:类的虚函数的地址
  • 虚函数表建立的时间:编译阶段,即程序的编译过程中会将虚函数的地址放在虚函数表中。

  • 虚表指针保存的位置:虚表指针存放在对象的内存空间中最前面的位置,这是为了保证正确取到虚函数的偏移量。

10.4 如何禁止构造函数的使用?

  • 为类的构造函数增加 = delete 修饰符,可以达到虽然声明了构造函数但禁止使用的目的。

10.5 什么是类的默认构造函数?

  • 编译器自动生成的,且未提供任何实参的构造函数 -> 默认构造函数

 10.6 如何禁止一个类被实例化

  • 在类中定义一个纯虚函数,使该类成为抽象类,因为不能创建抽象类的实例化对象
  • 构造函数私有化

10.7 为什么用成员初始化列表会快一些?

  • C++ 规定,对象的成员变量的初始化动作发生在进入构造函数本体之前
  • 所以初始化列表在没有进入函数体之前,就将成员变量设为初始值了
  • 总之就是一个进入了函数体,另一个没有进入函数体

10.8 实例化一个对象需要哪几个阶段

  • 1.分配空间  2.初始化  3.赋值

10.9 友元函数的作用

  • 作用: 让普通的成员函数可以访问类中的私有成员和保护成员

10.10 深拷贝和浅拷贝的区别 

  • 深拷贝:该对象和原对象占用不同的内存空间
  • 浅拷贝:该对象和原对象占用同一块内存空间

10.11 如何让类不能被继承?

#include <iostream>using namespace std;class Base final
{
};class Derive: public Base{ // error: cannot derive from 'final' base 'Base' in derived type 'Derive'};int main()
{Derive ex;return 0;
}
  • final关键字修饰的基类 不能被派生类继承 

11. 语言特性

11.0 谈谈STL各个容器

11.1 左值和右值的区别?两者之前如何转换 

#include <iostream>
#include <utility>
using namespace std;int main()
{// 左值: 能取地址int* p = new int(0);int b = 1;const int c = 2;// 对左值的左值引用int*& rp = p;int& rb = b;const int& rc = c;int& pvalue = *p;// 右值:不能取地址10;1.1 + 2.2;// 对右值的右值引用int&& rr1 = 10;double&& rr2 = 1.1 + 2.2;// 对右值的左值引用// double& r1 = 1.1 + 2.2;// errorconst double& r1 = 1.1 + 2.2;// 对左值的右值引用//int&& rr5 = b;// errorint&& rr5 = move(b);return 0;
}
  • 能取地址的叫左值,不能取地址的叫右值
  • 如果对右值使用左值引用,需要加上const
  • 如果对左值使用右值引用,需要是使用move函数

 11.2 什么是野指针和悬空指针?

void *p = malloc(size);
free(p); 
// 此时,p 指向的内存空间已释放, p 就是悬空指针。
  • 悬空指针: 这个指针指向的地址空间已经被释放了,但这个指针还是指向原来的那段地址空间
void *p; 
// 此时 p 是“野指针”。
  • 野指针: 不确定指向地址空间的指针,和未进行初始化的指针

11.3 C++ 11 nullptr 比 NULL 优势

  • 在使用nullptr表示指针空值时,不需要包含头文件,且能提高代码的健壮健壮性
  • NULL 本质上是 0,在函数调用过程中,若出现函数重载并且传递的实参是 NULL,可能会出现不知道调那个函数的问题

11.4 指针和引用的区别

11.4.1 从使用场景来说

  • 引用和指针使用场景基本一样,但是链表的链式结构是引用无法代替的,只能使用指针

11.4.2 从语法特性来说

  • 引用在定义时必须初始化,指针没有要求
  • 引用在初始化时引用一个实体后,就不能再引用其他实体,
    而指针可以在任何时候指向任何一个同类型实体。
  • 没有NULL引用但有NULL指针
  • 在sizeof中的含义不同:引用的结果为引用类型的大小
    但指针始终是4或者8字节
  • 引用进行自增操作就相当于实体增加1
    而指针进行自增操作是指针向后偏移一个类型的大小
  • 有多级指针,但是没有多级引用。
  • 访问实体的方式不同引用是编译器自己处理,指针需要显示解引用
  • 引用比指针使用起来相对更安全

11.5 说一说c++的强制类型转换

  • static_cast关键字 -> 隐式类型转换 

  • reinterpret_cast关键字 -> 强制类型转换

  • const_cast关键字->取消变量的const属性

  • dynamic_cast关键字->父类指针 转换 子类指针(保证安全性)

11.6 什么是模板?如何实现?

  • 模板:创建类或者函数的蓝图或者公式,分为函数模板类模板
  • 实现方式:模板定义以关键字 template 开始,后跟一个模板参数列表。
    模板参数列表不能为空;模板类型参数前必须使用关键字 class 或者 typename

11.7 函数模板和类模板的区别

  • 实例化: 函数模板实例化后是一个函数,类模板实例化后是一个类

  • 特化:函数模板只能全特化;而类模板可以全特化,也可以偏特化
  • 调用方式不同:函数模板可以隐式调用,也可以显式调用;类模板只能显式调用

11.8 什么是可变参数模板?

可变参数模板:接受可变数目参数的模板函数或模板类。将可变数目的参数被称为参数包,包括模板参数包和函数参数包。

  • 模板参数包:表示零个或多个模板参数;
  • 函数参数包:表示零个或多个函数参数。

11.9 什么是模板特化?为什么特化?

  • 原因: 当我们需要针对某些特定类型或条件进行特殊处理时,
    可以使用模板特化来定义专门的实现
  •  概念: 模板参数在某种特定类型下的具体实现。分为函数模板特化类模板特化

 特化分为全特化和偏特化:

  • 全特化:模板中的模板参数全部特例化。
  • 偏特化:模板中的模板参数只确定了一部分,剩余部分需要在编译器编译时确定。

函数模板只能全特化;而类模板可以全特化,也可以偏特化


 11.10  迭代器的作用

  • 无需知道容器底层原理的情况下,遍历容器中的元素

 11.11 泛型编程如何实现

  • 容器:涉及到 STL 中的容器,例如:vector、list、map 等,可选其中熟悉底层原理的容器进行展开讲解。

  • 迭代器:在无需知道容器底层原理的情况下,遍历容器中的元素。

  • 模板:可参考本章节中的模板相关问题。

12 多线程交替打印奇偶数 

#include<iostream>
#include<thread>
#include<mutex>
#include<condition_variable>
#include<vector>
#include<atomic>
using namespace std;int main()
{int i = 0;int n = 100;mutex mtx;condition_variable cv;// 条件变量bool ready = true;// t1打印奇数thread t1([&]() {while (i < n){{unique_lock<mutex> lock(mtx);cv.wait(lock, [&ready]() {return !ready; });// 等待线程cout << "t1--" << this_thread::get_id() << ":" << i << endl;i += 1;ready = true;cv.notify_one();// 解除线程等待}//this_thread::yield();this_thread::sleep_for(chrono::microseconds(100));}});// t2打印偶数thread t2([&]() {while (i < n){{unique_lock<mutex> lock(mtx);cv.wait(lock, [&ready]() {return ready; });cout << "t2--" << this_thread::get_id() << ":" << i << endl;i += 1;ready = false;cv.notify_one();}}});this_thread::sleep_for(chrono::seconds(3));cout << "t1:" << t1.get_id() << endl;cout << "t2:" << t2.get_id() << endl;t1.join();t2.join();return 0;
}
  •  cv.wait(lock, [&ready]() {return !ready; });

    ready返回的是false时,这个线程就会阻塞
    阻塞当前线程,并自动调用lock.unlock()允许其他锁定的线程继续执行

  •  cv.notify_one();
    唤醒当前线程并自动调用lock.lock();就只允许自己一个线程执行

13. 单例模式(饿汉模式 && 懒汉模式)

那两种模式都是将构造函数私有化,自己实现一个构造生成一个静态对象

  • 一个类只能创建一个对象,即单例模式,该模式可以保证系统中该类只有一个实例,并提供一个 访问它的全局访问点,该实例被所有程序模块共享

13.1  饿汉模式: 程序启动时就创建一个唯一的实例对象

class Singleton
{
public:static Singleton* GetInstance(){return &m_instance;}
private:// 构造函数私有Singleton() {};// C++11 : 防拷贝Singleton(Singleton const&) = delete;Singleton& operator=(Singleton const&) = delete;static Singleton m_instance;// 声明
};Singleton Singleton::m_instance;// 定义
  • 优点:简单

  • 缺点:可能会导致进程启动慢,且如果 有多个单例类对象实例启动顺序 不确定。

  • 总结: 如果这个单例对象在多线程高并发环境下频繁使用,性能要求较高,那么显然使用饿汉模式来避 免资源竞争,提高响应速度更好

13.2 饿汉模式->多线程下

#include<iostream>
#include<thread>
#include<mutex>
using namespace std;
class Singleton
{
public:static Singleton* GetInstance(){// 保护第一次,后续不需要加锁// 双检查加锁if (_pInstance == nullptr){unique_lock<mutex> lock(_mtx);if (_pInstance == nullptr){_pInstance = new Singleton;}}return _pInstance;}private:// 构造函数私有Singleton(){};// C++11Singleton(Singleton const&) = delete;Singleton& operator=(Singleton const&) = delete;static Singleton* _pInstance;static mutex _mtx;
};Singleton* Singleton::_pInstance = nullptr;
mutex Singleton::_mtx; int main()
{Singleton::GetInstance();Singleton::GetInstance();return 0;
}

 13.3 懒汉模式 : 第一次使用对象再创建实例对象

  • 如果单例对象构造十分耗时或者占用很多资源,比如加载插件啊, 初始化网络连接啊,读取 文件啊等等,而有可能该对象程序运行时不会用到,那么也要在程序一开始就进行初始化,
  • 就会导致程序启动时非常的缓慢。 所以这种情况使用懒汉模式(延迟加载)更好。
#include <iostream>
#include <stdlib.h>
using namespace std;
class MemoryPool
{
public:static MemoryPool* GetInstance(){if (_pinst == nullptr) {_pinst = new MemoryPool;}return _pinst;}void* Alloc(size_t n){void* ptr = nullptr;// ....return ptr;}void Dealloc(void* ptr){// ...}// 实现一个内嵌垃圾回收类    class CGarbo {public:~CGarbo(){if (_pinst)delete _pinst;}};private:// 构造函数私有化MemoryPool(){// ....}char* _ptr = nullptr;// ...static MemoryPool* _pinst; // 声明
};// 定义
MemoryPool* MemoryPool::_pinst = nullptr;// 回收对象,main函数结束后,他会调用析构函数,就会释放单例对象
static MemoryPool::CGarbo gc;int main()
{void* ptr1 = MemoryPool::GetInstance()->Alloc(10);MemoryPool::GetInstance()->Dealloc(ptr1);
}
  • 优点: 有控制顺序, 不影响启动速度
  • 缺点: 相对复杂, 存在线程安全问题

13.4 单例对象释放问题:

  1. 一般情况下,单例对象不需要释放的。因为一般整个程序运行期间都可能会用它。单例对象在进程正常结束后,也会资源释放。
  2. 有些特殊场景需要释放,比如单例对象析构时,要进行一些持久化(往文件、数据库写)操作。

原文出处: 整理的C++面经(较全)-CSDN博客

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/bicheng/4270.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

人工智能|推荐系统——推荐大模型最新进展

近年来,大语言模型的兴起为推荐系统的发展带来了新的机遇。这些模型以其强大的自然语言处理能力和丰富的知识表示,为理解和生成复杂的用户-物品交互提供了新的视角。本篇文章介绍了当前利用大型语言模型进行推荐系统研究的几个关键方向,包括嵌入空间的解释性、个性化推荐的知…

Amazon云计算AWS之[5]关系数据库服务RDS

文章目录 RDS的基本原理主从备份和下读写分离 RDS的使用 RDS的基本原理 Amazon RDS(Amazon Relational Database Service) 将MySQL数据库移植到集群中&#xff0c;在一定的范围内解决了关系数据库的可扩展性问题。 MySQL集群方式采用Share-Nothing架构。每台数据库服务器都是…

【C++】---STL容器适配器之stack

【C】---STL容器适配器之stack 一、什么是适配器&#xff1f;二、栈1、栈的性质2、栈类&#xff08;1&#xff09;栈的构造&#xff08;2&#xff09;empty()&#xff08;3&#xff09;push()&#xff08;4&#xff09;pop()&#xff08;5&#xff09;top()&#xff08;6&#…

yolov8 dll 编译

1. 每次用yolo v8 都要用python &#xff0c;对于我这种写软件的太不方便了&#xff0c;下面尝试编译dll 调用, 我已经有做好的模型.best.pt 参考视频方法: yolov8 TensorRT C 部署_哔哩哔哩_bilibili 【yolov8】tensorrt部署保姆级教程&#xff0c;c版_哔哩哔哩_bilibili 需…

面经总结(二)(数据库)

数据库常识&#xff1a; 1、数据库系统包含什么&#xff1f; 包含了数据库、数据库管理系统、数据库管理员和应用程序。 数据库&#xff08;DB)&#xff1a;顾名思义是存放数据的仓库&#xff0c;实现数据的持久化。 数据库管理系统&#xff08;DBMS)&#xff1a;类似于操作系…

rabbitmq下载安装最新版本--并添加开机启动图文详解!!

一、简介 RabbitMQ是一个开源的遵循AMQP协议实现的消息中间件支持多种客户端语言,用于分布式系统中存储和转发消息, 这是 Release RabbitMQ 3.13.0 rabbitmq/rabbitmq-server GitHub 二、安装前准备 1、查看自己系统 确认操作系统版本兼容性 uname -a2、下载Erlang依赖包…

记录浏览器打开网站拦截提示不安全解决方法

浏览器可能会因为多种原因显示“不安全”的警告,这通常是由于安全设置不当或配置错误造成的。以下是一些常见的原因和解决方法: 1. HTTPS未启用 原因:如果网站使用HTTP而不是HTTPS,浏览器可能会显示不安全的警告。 解决方法:配置SSL/TLS证书并使用HTTPS来加密数据传输…

MySQL数据库常见SQL语句宝典

一 、常用操作数据库的命令 1.查看所有的数据库 : show databases;2.创建一个数据库 : create database if not exists 数据库名;3.删除一个数据库 : drop database if exists 数据库名;4.选择一张表 (注意在建表之前必须要选择数据库) : use 表名;* --tab 键的上面&#x…

K8s 使用 CephFS 作为后端存储(静态供给、动态供给)

一、K8s 使用 CephFS CephFS是 Ceph 中基于RADOS&#xff08;可扩展分布式对象存储&#xff09;构建&#xff0c;通过将文件数据划分为对象并分布到集群中的多个存储节点上来实现高可用性和可扩展性。 首先所有 k8s 节点都需要安装 ceph-common 工具&#xff1a; yum -y ins…

Vue 组件分类、局部注册和全局注册

文章目录 背景知识组件分类安装 vue-cli示例设置组件局部注册设置组件全局注册 背景知识 开发 Vue 的两种方式&#xff1a; 核心包传统开发模式&#xff1a;基于 html / css / js 文件&#xff0c;直接引入核心包&#xff0c;开发 Vue。工程化开发模式&#xff1a;基于构建工…

ubuntu系统搭建pytorch环境详细步骤【笔记】

实践设备&#xff1a;华硕FX-PRO&#xff08;NVIDIA GeForce GTX 960M&#xff09; 搭建PyTorch环境的详细步骤如下&#xff1a; 安装Ubuntu系统&#xff1a; 下载Ubuntu的镜像文件并制作启动盘。将启动盘插入计算机&#xff0c;启动计算机并按照提示安装Ubuntu系统。 配置镜…

Python爬虫--Ajax异步抓取腾讯视频评论

在某些网站 &#xff0c;当我们滑下去的时候才会显示出后面的内容 就像淘宝一样&#xff0c;滑下去才逐渐显示其他商品 这个就是采用 Ajax 做的 然后我们现在就是要编写这样的爬虫。 规律分析&#xff1a; 这个时候就要用到我们的 Fiddler 了 我们需要分析加载评论的规律 …

Orange3数据可视化(组件概览)

概要 大家见过Orange3提供的丰富数据可视化组件吗&#xff1f; Orange3为您提供了一系列生动的图表工具&#xff0c;包括树图、箱线图、小提琴图、分布图、散点图、折线图、条形图、筛图、马赛克图、自由投影、线性投影、雷达图、热力图、韦恩图、轮廓图、毕达哥拉斯树、毕达哥…

编程学习系列(1):计算机发展及应用(1)

前言&#xff1a; 最近我在整理书籍时&#xff0c;发现了一些有关于编程的学习资料&#xff0c;我派蒙也不是个吝啬的人&#xff0c;从今天开始就陆续分享给大家。 计算机发展及应用&#xff08;1&#xff09; 1944 年美国数学家冯诺依曼&#xff08;现代计算机之父&#xff…

鹏哥C语言复习——字符函数与字符串函数

目录 一.字符函数 1.字符分类函数 2.字符转换函数 二.基础字符串函数 1.strlen函数 2.strcpy函数 3.strcat函数 4.strcmp函数 三.基础字符串函数优化 1.strncpy函数 2.strncat函数 3.strncmp函数 四.进阶字符串函数 1.strstr函数 2.strtok函数 3.strerror函数 一…

【Linux进程】守护进程

【Linux进程】守护进程 目录 【Linux进程】守护进程守护进程守护进程概念进程组和会话的概念 系统的守护进程函数 作者&#xff1a;爱写代码的刚子 时间&#xff1a;2024.4.27 前言&#xff1a;本篇博客将会介绍守护进程&#xff0c;以及进程组和会话的概念&#xff0c;如何变成…

《C++学习笔记---入门篇3》---内联函数,auto关键字,范围for,指针空值nullptr

1.内联函数 1.1 内联函数概念 1.2 特性 1.3 接下来说一道面试题&#xff1a; 2.auto关键字(C11) 2.1auto简介 2.2 auto的使用细则 3.3 auto不能推导的场景 3.基于范围的for循环(C11) 3.1范围for的语法 3.2 范围for的使用条件 4.指针空值---nullptr(C11) 4.1 C98中的…

25计算机考研院校数据分析 | 厦门大学

厦门大学&#xff0c;简称厦大&#xff08;XMU&#xff09;&#xff0c;地处福建厦门。由著名爱国华侨领袖陈嘉庚先生于1921年创办&#xff0c;是中国近代教育史上第一所华侨创办的大学&#xff0c;是国内最早招收研究生的大学之一&#xff0c;中国首个在海外建设独立校园的大学…

C++ 动态链接库DLL创建及使用

一、动态链接库DLL创建 使用VS2022 创建 1、创建新解决方案 创建即可 2、创建动态链接库新项目 右键解决方案 语言选择C&#xff0c;选择动态链接库 填入项目名称&#xff0c;勾选&#xff1a;将解决方案和项目放在同一目录中 点击创建 3、创建后&#xff0c;显示dllmai…

详解centos8 搭建使用Tor 创建匿名服务和匿名网站(.onion)

1 Tor运行原理&#xff1a; 请求方需要使用&#xff1a;洋葱浏览器&#xff08;Tor Browser&#xff09;或者Google浏览器来对暗&#xff0c;网网站进行访问 响应放需要使用&#xff1a;Tor协议的的Hidden_service 2 好戏来了 搭建步骤&#xff1a; 1.更新yum源 rpm -Uvh h…