面试常见问题01 - C++相关(施工ing)

目录

一. C、C++的区别和优缺点
二. C++的语言特性
三. 多种关键字的作用和用法
四. 内联函数
五. 虚函数
六. 函数调用
七. 动态编译和静态编译
八. 引用和指针的区别
九. RTTI(Runtime Type Information,运行时类型信息)
十. 类型转换:static_cast、dynamic_cast、const_cast、reinterpret_cast
十一. 智能指针:shared_ptr、weak_ptr、unique_ptr
十二. 标准模版库 STL
十三. C++11 的新特性
十四. 内存泄漏
十五. C++中类会自动生成哪些函数
十六. 左值与右值
十七. new和malloc的区别

一. C、C++的区别和优缺点

1. 区别:C++是C的超集
  • 编程范式角度: C语言是一种遵循面向过程的程序设计语言,其关注点在于对解决问题的算法的抽象C++允许使用各种编程范式,包括面向过程、面向对象、泛型编程、函数式编程等
  • 函数重载:C语言不支持函数重载,其函数签名不包含参数列表
  • 函数参数传递:C语言不从语言层面支持传引用,需要通过指针类型的值传递实现传引用的功能
  • 模版:C语言不支持模版编程
2. C语言的特点
  • 作为一种面向过程的结构化语言,易于调试和维护
  • 表现能力和处理能力极强,可以直接访问内存的物理地址
  • 实现了对硬件的编程操作,也适合于应用软件的开发
  • 效率高,可移植性强
3. C++语言的特点
  • 在C语言的基础上进行扩充和完善,支持多种编程范式
  • 可以使用抽象数据类型进行基于对象的编程
  • 可以使用多继承、多态进行面向对象的编程
  • 可以担负起以模版为特征的泛型化编程

二. C++的语言特性

  • 封装性是基础,继承性是关键,多态性是补充,并且多态性存在于继承的环境中
1. C++的三大特性
  • 封装性:C++语言中支持数据封装,类是支持数据封装的工具,对象是数据封装的实现。在封装中,提供了三种访问限定符来实现访问控制机制,使得一些数据被隐藏在封装体内,封装体与外界进行信息交换是通过操作接口进行的
  • 继承性:C++允许单继承和多继承,是面向对象语言的重要特性。一个类可以根据需要生成派生类,派生类也可以继续派生。派生类能够继承基类的成员,还可以定义自己的成员
  • 多态性:多态指的是不同类的对象执行相同的动作会有不同的实现,也可以理解为在一般类中定义的数据成员或成员函数呗继承后,可以具有不同的数据类型或不同的实现,多态性与继承性相关联。简单来说,多态性是指发出同样的消息被不同数据类型的对象接收后表现出不同的行为
2. 重载、重写(覆盖)和隐藏
  • 重载:相同范围(在同一个类中)内函数名字相同,参数列表不同,与返回值类型没有关系 virutal 关键字可有可无
  • 重写:派生类覆盖基类中的同名函数,要求基类函数必须是虚函数,且与基类的虚函数有相同的参数列表和返回类型
  • 隐藏: 函数参数列表不同基类函数为虚函数时,派生类的同名函数屏蔽了基类中的同名函数(两个函数参数列表不同,无论基类函数是否虚函数,基类函数都将被覆盖)
3. 多态的实现原理
  • 早绑定:编译器在编译时确定对象调用的非虚函数的地址
  • 晚绑定:对于有虚函数的类,编译器在类的构造函数中创建虚表和初始化虚指针,运行时通过虚表和虚指针在运行时确定对象调用的函数地址
  • 在基类的函数前加上virtual关键字,在派生类中重写该函数,运行时将会根据对象的实际类型来调用相应的函数。如果对象类型是派生类,就调用派生类的函数;如果对象类型是基类,就调用基类的函数 。存在虚函数的类都有一个一维的虚函数表叫做虚表,类的对象有一个指向虚表开始的虚指针。虚表是和类对应的,虚表指针是和对象对应的
4. 构造函数和析构函数的调用顺序
  • 构造函数:基类构造函数->数据成员构造函数->本类构造函数
  • 析构函数:本类析构函数->数据成员析构函数->基类构造函数

三. C++ 关键字

1. extern
  • 作用:extern可以置于变量或函数前,以在别的文件中标识变量或函数的定义,并提示编译器遇到此变量或函数时在其他模块中寻找其定义。extern表示将变量或函数声明为外部链接,变量默认是内部链接,函数默认是外部链接。通常,在模块的头文件中,对本模块提供给其他模块引用的函数和全局变量以关键字extern声明。
2. static
  • static主要定义静态变量、静态函数、静态数据成员和静态成员函数。
  • 静态变量和静态数据成员在全局数据区分配内存,始终驻留在全局数据区,直到程序运行结束。
  • 静态成员函数与类相联系,不与类的对象相联系,静态成员函数不能访问非静态数据成员。
3. const
  • 作用:声明常量、常引用、常对象、常数据成员、常成员函数和修饰指针、形参、数组、函数返回值
  • 特点
1)常对象只能访问常成员函数
2) 常成员函数中能够修改有mutable关键字修饰的数据成员
3)常数据成员只能在类构造函数的初始化列表中进行
4)const 数据类型* 指针名:表示不允许修改该指针指向的对象,但指针可以指向其他对象
   数据类型* const 指针名:表示指针不可以指向其他对象,但可以修改所指向的对象
5)const修饰函数返回值时表示不允许对返回值进行修改
4. explict
  • 作用:修饰类的构造函数,表示该构造函数只能够进行显示调用,无法进行隐式转换
  • 特点:explicit 只能用于修饰只有一个参数或除了第一个参数外其他参数均有默认值的构造函数
5. volatile
  • 作用:提醒编译器它后面所定义的变量随时都有可能改变(尤其是多线程),因此编译后的程序每次需要存储或读取这个变量的时候,都会直接从内存中读取数据,而不是使用保存在寄存器里的备份。
  • 特点
1)易变性:所谓的易变性,在汇编层面反映出来,就是两条语句,下一条语句不会直接使用上一条语
   句对应的volatile变量的寄存器内容,而是重新从内存中读取
2)不可优化:告诉编译器不要对这个变量进行各种激进的优化,甚至将变量直接消除,保证程序员写
   在代码中的指令,一定会被执行
3)顺序性:保证Volatile变量间的顺序性,编译器不会进行乱序优化,但Volatile变量与非Volatile
   变量之间的操作,是可能被编译器交换顺序的
  • 在多线程中的应用:无锁的共享数据
    在多线程的程序中,共同访问的内存当中,多个线程操纵一个变量,但无法判定何时这个变量会发生变化。例如A线程将变量复制到寄存器中,然后进入循环反复检测寄存器中的值是否满足一定的条件(它期待B线程改变变量的值),此时B改变了变量的值,但是这个改变对于A线程已经复制到寄存器中的值并没有影响,于是A就永远处于死循环状态。
struct SharedDataStructure gSharedStructure; 
int gFlag;

// 线程A
gSharedStructure.foo = ...; 
gSharedStructure.bar = ...; 
gSharedStructure.baz = ...; 
gFlag = 1; 

// 线程B
 if (gFlag) 
        UseSharedStructure(&gSharedStructure); 

在上面的这一段代码当中,结构体和flag之间因为不存在依赖关系,执行的顺序可能会被编译器修改(compiler reorder)。线程A的代码可能是flag先被置为1,然后处理结构体,这样线程B的if语句内就是不安全的 。这种情况下,可以将gFlag和gSharedStructure 都加上 volatile 关键字,保证编译器按照代码顺序产生机器码。但在加了volatile关键字之后,上述代码在实际执行时仍不能保证安全,因为CPU会激进的reorder以加快执行速度, 因此内部执行机器码的顺序仍然是未知的,只保证最终结束时的结果与原顺序一致。
结论:不要预期在多线程中使用volatile来解决数据的同步问题,该加锁时就加锁

四. 内联函数

1. 作用
  • 函数调用在开始和结束阶段需要进行很多工作,效率较低,使用内联函数能够减少函数调用,提高代码的执行效率。内联函数通常就是编译时在每个调用点上展开内联函数,即用内联函数的代码段替代原本的函数调用
2. 特点
  • inline关键字必须写在内联函数的定义前
  • 内联函数应该在头文件中定义,若在源文件中定义,则只在当前文件中可见
3. 优缺点
  • 优点:减少了函数调用,提高了代码的执行效率
  • 缺点:
    1)函数体内代码过长时不应使用内联,否则会导致可执行代码过大
    2)函数体内出现循环或其他复杂的控制结构时,使用内联函数的开销比函数调用大

五. 虚函数

1. 作用:C++中虚函数的作用主要是实现了多态
2. 底层机制
  • 虚函数表:拥有虚函数的类都会有对应的虚函数表,虚函数表中存放类中的虚函数地址
  • 虚指针:拥有虚函数的类在实例化对象时,在构造函数中初始化对象的虚指针
3. 工作原理
  • 没有虚函数的类实例化对象时,对象的内存中只包含类的数据成员
  • 有虚函数的类实例化对象时,对象的内存中开始的4字节为指向虚函数表的虚指针
  • 同一个类的不同实例共用同一个虚函数表
  • 多继承情况下,实例对象内会有多个虚指针
  • 虚函数表在编译时创建和初始化,虚指针在构造函数中创建和初始化
  • 虚函数表中还包含有类的type_info类型的对象
4. 析构函数与虚函数
  • 析构函数不一定必须是虚函数,是否为虚函数取决于该类的使用,一般该类为基类产生继承和多态时,才会是虚函数,单独使用可以不是虚函数。在继承和多态时设计为虚函数是因为当new派生类并且用基类指针指向这个派生类,在销毁基类指针时只会调用基类的析构函数,不会调用派生类的析构函数,因为基类无法操作派生类中非继承的成员,这样就造成派生类只new无法delete造成内存泄露。
  • 默认不是虚析构函数是因为如果析构函数为虚函数就需要编译器在类中增加虚函数表来实现虚函数机制,这样所需内存空间就更大了,因此没有必要默认为虚析构函数。如果父类或者祖先类中函数为虚函数,则子类及后代类中,函数是否加virtual关键字,都将是虚函数。
5. 类里面还有什么函数不能为虚
  • 不需要进行重写的普通函数
  • 静态成员函数:类的所有对象共享一份代码,与对象无关
  • 内联成员函数:内联函数编译时被展开,而虚函数运行时才动态绑定
  • 构造函数
  • 友元函数

六. 函数调用

  1. 程序内存区域
1)堆区:动态分配内存,向地址增大方向增长
2)栈区:存放局部变量、函数参数、当前状态、函数调用信息,向地址减小方向增长
3)全局区:存放全局变量和静态变量
4)常量区:存放常量
5)代码区:存放程序代码
  1. 函数调用过程


    压栈过程

    返回过程

七. 动态编译和静态编译

  1. 区别
    1)动态编译的可执行文件需要附带一个的动态链接库,在执行时,需要调用其对应动态链接库中的命令
    2)静态编译就是编译器在编译可执行文件的时候,将可执行文件需要调用的对应动态链接库中的部分提取出来,链接到可执行文件中,使可执行文件在运行的时候不依赖于动态链接库
  2. 静态链接的特点
  • 优点
    1)静态库链接是放在编译时期完成的
    2)程序在运行时与函数库再无瓜葛,移植方便
  • 缺点
    1)浪费空间和资源,使可执行文件体积变大
    2)静态库的变化会导致整个程序需要重新编译
  1. 动态链接的特点
  • 优点
    1)动态库链接是在运行时完成的
    2)多个程序可以使用同一个动态库
    3)动态库发生变化,只需要更新动态库而不用改变程序
  • 缺点
    1)应用程序需要附带一个动态库
    2)运行时需要进行动态链接,程序加载速度变慢

八. 引用和指针的区别

  1. 引用是变量的别名,没有分配新的内存空间,引用必须进行初始化
  2. 指针本身是一个变量,拥有自己的内存空间,值为所指向的对象的地址,可以不进行初始化
  3. 指针更加自由,可以改变所指向的对象,而引用不行

十. RTTI(Runtime Type Information,运行时类型信息)

  • typeid() 函数:返回一个类或对象的type_info对象,用于比较类或对象是否为同一类以及获取类或对象的类名
1. 获取类、对象的类名
int i = 3;
cout << typeid(i).name() << endl;  // int
cout << typeid(string).name() << endl;   // class std::basic_string< ... >

2. 当类中不存在虚函数时,typeid()是静态的,在编译时确定类名
class A
{
public:
     void Print() { cout << "This is class A." << endl; }
};

class B : public A
{
public:
     void Print() { cout << "This is class B." << endl; }
};

int main()
{
     A *pA = new B();
     cout << typeid(pA).name() << endl; // class A *
     cout << typeid(*pA).name() << endl; // class A
     return 0;
}

3. 当类中存在虚函数时,typeid()是动态的,在运行时确定类名
class A
{
public:
     virtual void Print() { cout << "This is class A." << endl; }
};

class B : public A
{
public:
     virtual void Print() { cout << "This is class B." << endl; }
};

int main()
{
     A *pA = new B();
     cout << typeid(pA).name() << endl; // class A *
     cout << typeid(*pA).name() << endl; // class B
     return 0;
}
  • type_info 类:type_info 类中重载的 == 和 != 可以比较两个对象的类型是否相等
A *pA = new B();

if (typeid(*pA) == typeid(A))
     cout << "I am a A truly." << endl;  
else (typeid(*pA) == typeid(B))
     cout << "I am a B truly." << endl; //  I am a B truly.

十一. 类型转换:static_cast、dynamic_cast、const_cast、reinterpret_cast

  • static_cast:对技术类型、指针或引用进行转化,不进行类型检查
1. 基础类型之间互转,如:float转成int、int转成unsigned int等
2. 指针与void之间互转,如:float*转成void*、Bean*转成void*、函数指针转成void*等
3.子类指针/引用与父类指针/引用之间转换,向上转换安全,向下则不安全
class Parent {
public:
    void test() {
        cout << "Parent " << endl;
    }
};
class Child : public Parent {
public:
     void test() {
        cout << "Child " << endl;
    }
};
Parent  *p = new Parent;
Child  *c = static_cast(p);

c->test();   // Child( test 若为虚函数则输出 Parent )
  • dynamic_cast:在运行时刻在一个类层次结构中安全地进行类型转换,把基类指针(引用)转换为派生类指针(引用),如果转换失败则返回NULL
Parent  *p = new Child ();

if (dynamic_cast(p) )
     cout<<"I am a Child1 truly."<(p) )
     cout<<"I am a Child2 truly."<
  • const_cast:修改类型的const或volatile属性
const char* a;
char* b = const_cast(a);
const char* c = const_cast(b);
  • reinterpret_cast:对指针、引用进行原始转换,实际上是对指针、引用的比特位进行重解释,用来处理无关类型之间的转换,主要用法如下:
1. 从指针类型到一个足够大的整数类型
int* p = new int(1);  // p = 00C3D8A8 (0 0 1100 0011 1101 1000 1010 1000)
int i = reinterpret_cast(p); // i = 12834984
2. 从整数类型或者枚举类型到指针类型
void* p = reinterpret_cast(1024);  // p = 00000400 (..... 0100 0000 0000)
3. 从一个指向函数的指针到另一个不同类型的指向函数的指针
4. 从一个指向对象的指针到另一个不同类型的指向对象的指针
5. 从一个指向类函数成员的指针到另一个指向不同类型的函数成员的指针
5. 从一个指向类数据成员的指针到另一个指向不同类型的数据成员的指针

十二. 智能指针:shared_ptr、weak_ptr、unique_ptr

  • 特点:本质都是类模板,使用了RAII(资源分配即初始化)机制,所谓RAII就是创建对象时系统会调用constructor,释放对象时系统会调用destructor,这样就可以在构造函数中申请资源,在析构函数中释放资源;构造函数都是explicit的,即必须显式调用构造函数,不允许隐式转换
  • shared_ptr(C++11):共享式指针,是一个共享资源所有权的指针,在shared_ptr类内部维护着一个引用计数器,该引用计数器实际上就是指向该资源的指针的个数,当shared_ptr对象销毁时,在该对象的析构函数中判断引用计数器是否为0,若为0则执行delete操作,释放指向的内存,否则将引用计数器减1
shared_ptr sp1(new int(5));
cout << "sp1 引用计数:" << sp1.use_count() << endl; // sp1 引用计数:1
shared_ptr sp2 = sp1; // 指向内存new int(5)的指针数量加1
shared_ptr sp3(sp1); // 指向内存new int(5)的指针数量加1
cout << "sp1 引用计数:" << sp1.use_count() << endl; // sp1 引用计数:3
cout << "sp2 引用计数:" << sp2.use_count() << endl; // sp2 引用计数:3
cout << "sp3 引用计数:" << sp3.use_count() << endl; // sp3 引用计数:3
sp3.reset(); // 指向内存new int(5)的指针数量减1
cout << "sp1 引用计数:" << sp1.use_count() << endl; // sp1 引用计数:2
cout << "sp2 引用计数:" << sp2.use_count() << endl; // sp2 引用计数:2
cout << "sp3 引用计数:" << sp3.use_count() << endl; // sp3 引用计数:0
sp2.reset(new int(3)); // 指向内存new int(5)的指针数量减1,指向内存new int(3)
                       // 的指针数量加1
cout << "sp1 引用计数:" << sp1.use_count() << endl; // sp1 引用计数:1
cout << "sp2 引用计数:" << sp2.use_count() << endl; // sp2 引用计数:1
cout << "sp3 引用计数:" << sp3.use_count() << endl; // sp3 引用计数:0

*  shared_ptr在多线程的情况下不是线程安全的,指向的内存和本身的引用计数器会被多个线程访问和
   修改。多线程的条件线,每个线程需要互斥的访问和修改其指向的内存和shared_ptr的引用计数器
  • weak_ptr(C++11):weak_ptr是为了解决shared_ptr循环引用造成内存泄漏的问题,一般与shared_ptr搭配使用,是shared_ptr的助手。需要通过一个shared_ptr对象来创建一个weak_ptr对象,并且创建后并不会引起原来shared_ptr引用计数的改变。使用weak_ptr的lock()函数可以返回一个weak_ptr指针所指向的资源的shared_ptr,若weak_ptr所指向的资源已被释放,lock()返回一个“空”shared_ptr。weak_ptr类中没有重载operator *和operator->,如果想要访问weak_ptr指向的资源时,必须先使用lock()获取到该weak_ptr所指向资源的shared_ptr的对象,然后再去访问
shared_ptr sp(new int(5));
cout << "创建wp前sp的引用计数:" << sp.use_count() << endl;    // 创建wp前sp的引用计数:1

weak_ptr wp(sp);
cout << "创建wp后sp的引用计数:" << sp.use_count() << endl;    // 创建wp后sp的引用计数:1

shared_ptr sp2 = wp.lock();
cout << "创建 sp2后sp的引用计数:" << sp.use_count() << endl;    // 创建sp2后sp的引用计数:2

sp.reset();
sp2.reset();
if ( shared_ptr sp3 = wp.lock() )
    cout << *sp3 << endl;
else
    cout << "wp指向对象为空" << endl;   // wp指向对象为空
  • unique_ptr(C++11):资源所有权独占的智能指针,即两个 unique_ptr 不能指向一个对象,类似于c++99中的auto_ptr,但是要比auto_ptr更加强大,因此取代了auto_ptr。
1. unique_ptr不具备拷贝语义和赋值语义**,不能通过copy constructor和operator=转移资源所有
   权,而在auto_ptr中实现了拷贝语义和赋值语义
   unique_ptr ptr(new int(4));
   unique_ptr ptr1(ptr);//错误,因为没有实现拷贝语义
   unique_ptr ptr2 = ptr;//错误,因为没有实现赋值语义

2. C++中实现了“move语义”,使用“move语义”可以将资源所有权从一个对象转移到另一个对象
   unqiue_ptr ptr(new int(4));
   unique_ptr ptr1(std::move(ptr));//正确
   unique_ptr ptr2 = std::move(ptr);//正确

3. unique_ptr对象可以借助于“move语义”存放在容器中,而auto_ptr不允许存放在容器中
   vector >v;
   unique_ptrptr (new int(5));
   v.push_back(std::move(ptr));//v.push_back(ptr);是不对的

4. unique_ptr可以用于管理数组,auto_ptr不支持数组

十三. 标准模版库 STL

STL体系结构
  • 六大组件的关系:容器通过分配器获取系统资源(内存),算法通过迭代器操作容器中的内容,仿函数可以协助算法完成不同的策略变化,适配器可以修饰或套接仿函数
  • 通用容器
    1. 顺序性容器:维护一组排列有序、类型相同的元素,主要有vector、list、deque、array(C++ 11)、forward_list(C++ 11)
array(C++ 11):相对于数组,增加了迭代器等函数
vector:用连续内存存储元素,起始地址不变;当元素个数大于当前容量时,会分配原来2倍容量,从而
        将元素复制到新空间,原空间释放;可以快速随机访问元素,快速在末尾插入删除元素,此过程
        中不用移动内存;对中间元素插入删除时要移动内存

vector v;
v.push_back(1);
v.push_back(2);
v.push_back(3);
cout << v[0] << ": " << &(v[0]) << ", " << v[1] << ": " << &(v[1]) << ", " << v[2] << ": " << &(v[2]) << endl;
v.erase(v.begin());
cout << v[0] << ": " << &(v[0]) << ", " << v[1] << ": " << &(v[1]) << endl;
// 1: 000D9B20, 2: 000D9B24, 3: 000D9B28
// 2: 000D9B20, 3: 000D9B24
list:用双向链表而非连续内存存储元素,只能顺序访问,不支持随机访问,在任意位置插入或删除操作
      效率较高
forward_list(C++ 11):用单向链表实现
deque:用连续内存存储元素,起始地址可以改变;可以快速地随机访问元素,快速地在开始和末尾插
       入删除元素,随机插入比两端插入要慢;重新分配空间后,原空间保持,只是新建立了一个内存块,
       将新数据插入这个内存块,没有空间的复制删除过程,故deque是由多个分段连续的内存空间组成,
       各小片内存间用链表相连,还是可以使用[],只是没有vector快
deque v;
v.push_back(1);
v.push_back(2);
v.push_back(3);
cout << v[0] << ": " << &(v[0]) << ", " << v[1] << ": " << &(v[1]) << ", " << v[2] << ": " << &(v[2]) << endl;
v.erase(v.begin());
cout << v[0] << ": " << &(v[0]) << ", " << v[1] << ": " << &(v[1]) << endl;
// 1: 00169B20, 2: 00169B24, 3: 00169B28
// 2: 00169B24, 3: 00169B28

  2. 关联式容器:根据键值key来快速高效的检索数据,容器中的元素初始化后都按一定的顺序排列,使用非连续的内存空间存储元素,主要有set、multiset、map、multimap、unordered_map(C++ 11)、unordered_set(C++ 11)

set:支持快速查找,不允许有重复值,用红黑树结构来存储;multiset支持快速查找,允许有重复值。
map:一对一映射,支持关键字快速查找,不允许有重复值,使用红黑树结构存储;multimap是一对多映
     射,基于关键字查找,允许有重复值
unordered_map(C++ 11):元素不进行排序,使用hash表实现,查找效率高,但需要增加内存开销
unordered_set(C++ 11):元素不进行排序,使用hash表实现,查找效率高,但需要增加内存开销

  3. 容器适配器:对已有容器进行某些特性的再封装,它不是一个新容器,主要有stack、queue

  • 适配器:绑定适配器、插入适配器
    1. 绑定适配器:适配器函数的第一个参数为二元函数对象或指针,第二个参数为需要绑定到函数参数的默认值,返回一个具有默认值的函数对象或指针;bind1st()是将指定值绑定到二元函数的第一个参数上,bind2nd()是将指定值绑定到二元函数的第二个参数上
    2. 插入适配器:主要有back_inserter()、inserter()、front_inserter(),函数的参数为容器,返回该容器的迭代器,使用容器对应的push_back()、insert()、push_front()函数取代赋值运算符
template
void Filter(Iter _itIndex)
{
    int arr[] = {1, 2, 3, 4, 5};
    for (auto val : arr)
        *_itIndex = val;
}

int main()
{
    vector vec;
    // Filter(vec.begin());  //  出错,vec的未分配内存,使用*_itIndex = val导
                             //  致内存错误,需要提前为vec分配内存
    Filter(back_inserter(vec));  // 相当于将Filter()中的 *_itIndex = val 替换
                                 // 为 vec.push_back(val)

    return 0;
}

十四. C++11 的新特性

  • C++11 常用新特性快速一览
  • 关键字和新语法
1. auto:编译器根据上下文情况,确定auto变量的真正类型,auto作为函数返回值
         时,只能用于定义函数,不能用于声明函数
2. nullptr:用于解决NULL数据类型实际为int导致的函数重载问题
3. override:可以显式的在派生类中声明,哪些成员函数需要被重写,如果没被重
             写,则编译器会报错
4. final:类被final修饰,不能被继承;基类虚函数被final修饰,不能被重写
5. for-each循环
6. 右值引用:右值引用标记为T&&,引用一个临时对象,减少内存拷贝,优化性能
9. 委托构造函数:使用它所属的类的其他构造函数执行自己的初始化过程,或者说
                它把自己的一些(或者全部)职责委托给了其他构造函数,被委
                托的构造函数需要能够完整地构造出这个对象
10. 继承构造函数:在派生类中声明 using BaseClass::BaseClass; 使派生类能
                 够使用基类的构造函数,
11. 移动构造函数:参数是一个右值引用类型的参数,避免深拷贝,移动构造函数只
                 是将临时对象的资源做了浅拷贝,不需要对其进行深拷贝,从而
                 避免了额外的拷贝,提高性能
  • STL 新增容器
1. std::array
2. std::forward_list:与list区别在于它是单向链表
3. std::unordered_map:插入时不会自动排序, 使用哈希表实现
4. std::unordered_set:插入时不会自动排序, 使用哈希表实现
  • 多线程
1. std::thread:创建一个std::thread的实例,线程在实例成功构造成时启动,可以通过
                std::thread的构造函数的参数将参数传递给线程函数,类成员函数做为
                线程入口时,把this做为第一个参数传递进去即可。
   1) 成员函数:get_id()、join()、detach()、joinable()
   get_id():获取线程id(native_handle()获取系统分配的线程id)
   join():等待线程运行结束
   detach():从 thread 对象分离执行的线程,允许执行独立地持续,thread 对象失去
             对线程的所有权,但线程仍会继续执行,直到线程退出并释放所有资源
   joinable():判断 thread 对象是否还有对线程的所有权,返回true则有,否则为无
   2) 操作函数:
   std::this_thread::sleep_for():使当前线程暂停一段时间再运行
   std::this_thread::sleep_until:使当前线程暂停到某一时间再运行
2. std::atomic:原子数据类型,原子数据类型不会发生数据竞争,能直接用在多线程中而
                不必用户对其进行添加互斥资源锁的类型
3. std::condition_variable:条件变量,用于线程同步
   成员函数:wait()、notify()
   1) wait():线程的等待动作,直到其它线程将其唤醒后,才会继续往下执行
   2) notify_xxx():唤醒wait在该条件变量上的线程,notify_one 唤醒等待的一个线程;
                 notify_all 唤醒所有等待的线程
  • 智能指针
1. std::shared_ptr:共享式指针,维护一个引用计数器,当引用计数器为0时自动释放内存
2. std::weak_ptr:与shared_ptr搭配使用,解决循环引用的问题
  • lamda表达式:创建一个匿名函数
1. 基本语法:[ caputrue ] ( params ) opt -> ret { body; };
   1) capture是捕获列表: []不捕获任何变量;
                         [&]捕获外部作用域中所有变量,按引用捕获
                         [=]捕获外部作用域中所有变量,按值捕获
   2) params是参数表(选填) 
   3) opt是函数选项;可以填mutable,exception,attribute(选填) 
      mutable说明lambda表达式体内的代码可以修改被捕获的变量,并且可以访问被捕获的
      对象的non-const方法。 
      exception说明lambda表达式是否抛出异常以及何种异常
      attribute用来声明属性
   4) ret是返回值类型(拖尾返回类型)
   5) body是函数体
  • std::function:可调用对象模板类,通过function模板类,可以将不同的可调用对象转化为相同类型的对象(function对象)
  • std::bind:通用的函数适配器,它接受一个可调用对象,生成一个新的可调用对象来“适应”原对象的参数列表
  • std::move:将对象的状态或者所有权从一个对象转移到另一个对象,只是转移,没有内存拷贝
  • std::forward:能够按照参数原来的类型转发到另一个函数,这种转发类型称为完美转发

十五. 内存泄漏

  • 内存错误
1. 使用分配未成功的内存
2. 内存分配成功,但未初始化
3. 操作超出内存边界
4. 内存使用结束未释放
5. 使用已释放的内存
  • 内存泄漏定位:使用mtrace来追踪内存使用

十六. C++中类会自动生成哪些函数

  1. 对于空类,声明时,编译器不会生成任何的成员函数,只会生成1个字节的占位符
  2. 空类定义时会生成6个成员函数:缺省的构造函数、拷贝构造函数、析构函数、赋值运算符、两个取址运算符
class Empty
{
public:
    Empty();                            //缺省构造函数
    Empty(const Empty &rhs);            //拷贝构造函数
    ~Empty();                           //析构函数 
    Empty& operator=(const Empty &rhs); //赋值运算符
    Empty* operator&();                 //取址运算符
    const Empty* operator&() const;     //取址运算符(const版本)
};
  1. 拷贝构造函数参数必须为引用,否则会造成拷贝构造函数无限递归;C++对于临时对象的引用只能是const,当使用临时变量(如函数的返回值)初始化对象时,拷贝构造函数的参数必须用const修饰

十七. 左值与右值

十八. new和malloc的区别

你可能感兴趣的:(面试常见问题01 - C++相关(施工ing))