Skip to content

Latest commit

 

History

History
854 lines (587 loc) · 34.6 KB

Cpp.md

File metadata and controls

854 lines (587 loc) · 34.6 KB

简述 const 关键字?

const的作用是告诉编译器某个值是不变的,可以理解成只读,对变量起到保护作用。

const可以用于以下方面:

  1. 修饰普通变量 需要在一开始就进行初始化;

  2. 修饰指针 根据在 * 前后可以分为常量指针和指针常量,当然也可以前后都修饰,如const int* aint* const aconst int * const a

  3. 修饰函数中的参数与返回值

  • 修饰函数中的参数:const在函数的参数中可以保护指针指向内容不被修改,如strcpy(char* des, const char* src)
  • 修饰函数的返回值:也可以保证返回值指针的内容不被修改,如 const char* getStr() 中,接收类型就必须是 const char *
  1. 修饰类中的成员变量与成员函数
  • 修饰成员函数时,不能对其成员变量进行修改(本质是修饰this指针)。
  • 修饰成员变量时,必须在构造函数列表里进行初始化。

延伸用法:const + &

const string& s,在满足的引用传递的优点下,既可以保护别名不被修改,也可以 接收右值(接收右值的原因在于右值都是const属性且不可见,只有const传递能捕捉到)。



简述 static 关键字?

static 修饰的数据存放在全局数据区,限定了作用域,并且生命周期是直到程序结束。

C中的用法:

  1. 静态局部变量 一般用在函数中,当函数第一次被调用时会在全局数据区初始化,其作用域只存在于该函数中。
  2. 静态全局变量 静态全局变量不能为其他文件所用,作用域只在该文件中
  3. 静态函数 与静态全局变量的作用类似,静态函数不能为其他文件所用,作用域只在该文件中

C++中的用法(包含C中的用法):

  1. static修饰类成员变量 静态成员变量属于整个类,在类实例之间共享,也就是说无论创建多少个类的对象,该成员变量都只有一个副本。 同时由于静态成员变量属于整个类,所以只能在类内申明,在类外初始化。如果在类内就初始化,那么会导致每一个实例化的对象都拥有一个该成员变量,这是矛盾的。

  2. static修饰类成员函数 静态成员函数属于整个类,由于没有this指针,所以只能调用静态成员变量



new/delete 和 malloc/free 的区别?

  1. 最重要的一点 new/delete可以调用类的构造函数,析构函数等;而malloc/free只是简单的申请和释放内存。
  2. 本质类型 new属于运算符,可重载;而malloc属于库函数,不可重载。
  3. 参数 new不需要参数就能够自动分配空间大小,malloc则需要传入申请内存块的大小。
  4. 返回值 new的返回值是相应类型的指针,malloc返回值是void*类型。
  5. 申请内存失败 new申请失败会抛出bad_alloc 异常,而malloc申请失败则会返回NULL
  6. 申请区域 new操作从 自由存储区 申请内存, malloc 从堆区申请内存;自由存储区可以是堆区,也可以是全局静态数据区,这由operator new(size_t) 决定。
  7. 处理数组 new[]和delete[]必须配套使用来处理数组。

延伸1: 既然new/delete的功能已经完全覆盖了malloc/free,为什么还要保留malloc/free呢?

因为C++程序中经常会使用C,而C只能使用malloc/free进行动态内存申请和释放。


延伸2:写出重载new delete 的程序

  • 使用new运算符时,先调用 operator new(size_t)申请空间,再调用构造函数,然后返回指针;
  • 使用delete运算符时,先调用析构函数,再调用 operator delete(void*)释放空间。
class A
{
public:
    A() { cout << "A()" << endl; }
    ~A() { cout << "~A()" << endl; }

    void*  operator new(size_t n)
    {
        cout << "operator new(size_t)" << endl;
        void *ret = malloc(n);
        return ret;
    }

    void* operator new[](size_t n)
    {
        cout << "operator new[](size_t)" << endl;
        void *ret = malloc(n);
        return ret;    
    }

    void operator delete(void* p)
    {
        if (p)
        {
            cout << "operator delete(void*)" << endl;
            free(p);
            p = nullptr;
        }
    }

    void operator delete[](void* p)
    {
        if (p)
        {
            cout << "operator delete[](void*)" << endl;
            free(p);
            p = nullptr;
        }
    }
};


简述extern关键字?

extern关键字有两个作用:

  1. 跟”C”连用时 告诉编译器用C的规则进行编译。因为如果使用C++的规则,由于C++支持函数的重载,所以会将函数名进行修改,导致程序报错。
  2. 不与”C”进行连用时 对于变量或函数只进行申明而不是定义,提示编译器该变量或函数的定义需要在另一个文件中寻找。在编译阶段,目标模块A虽然找不到extern 修饰的变量或者函数,但不会报错,而是在链接时从模块B中找到该函数或变量。


volatile关键字?

volatile关键字的作用是让CPU取内存单元中的数据而不是寄存器中的数据。 如果没有volatile,那么经过编译器优化,CPU会首先访问寄存器中的数据而不是内存单元中的数据(因为访问寄存器会更加快速),这样在多线程环境可能会读取脏数据。

延伸1:一个参数可以既是const 又是 volatile吗?

可以,const修饰代表变量只读,volatile修饰代表变量每次都需要从内存中读取。

延伸2:volatile 可以修饰指针吗?

可以,代表指针指向的地址是volatile的。



mutable关键字?

mutable是const的反义词,用来突破const的限制。const修饰的成员函数可以修改mutable修饰的成员变量。

程序实例:

class A
{
    mutable int _val = 10;
public:
    void display()const
    {
        _val = 20;
        cout << _val << endl;
    }
};


指针与引用的区别?(C++中引用的特点?)

  1. 引用是变量的别名,本身不具有单独的内存空间,属于直接访问;指针是指向地址的变量,有单独的内存空间,属于间接访问。

  2. 引用在创建时就必须初始化,且不能更改绑定;指针可以不初始化,可以更改指向。

总的来说,引用既有指针的效率,同时也更加方便直观。



C++内存的五大分区?(内存的分配方式有哪几种?)

  1. 栈区 通常存放局部变量,形参,函数调用等,其操作方式类似于数据结构中的栈。
  2. 堆区 通过new malloc 等申请的内存块,其操作方式类似于数据结构中的链表。
  3. 全局变量与静态变量区 分为BSS段和Data段,BSS段存储未初始化的变量,Data段存储已经初始化的变量。
  4. 常量区 存放只读常量。
  5. 代码区 存放代码以及函数。

在这里插入图片描述



堆和栈的区别?

  • 栈是由编译器自动分配释放的,存放在高地址处,往下进行存储,通常存放局部变量,形参,函数调用等,其操作方式类似于数据结构中的栈,不会产生外部碎片。

  • 堆是由程序员手动分配释放的,存放在低地址处,往上进行存储,通常为new malloc 等申请的内存块,其操作方式类似于数据结构中的链表,会产生外部碎片。

  • 堆属于动态分配,没有静态分配的堆;栈由静态分配与动态分配两种方式,但是栈的动态分配由编译器控制。



静态内存分配与动态内存分配的区别?

  1. 时间:静态内存分配在编译时期完成;动态内存分配在程序运行时期完成。
  2. 空间:堆属于动态分配,没有静态分配的堆;栈由静态分配与动态分配两种方式,但是栈的动态分配由编译器控制完成,因为栈只能由编译器分配释放。

(该概念和静态联编,动态联编有些相似)



深拷贝与浅拷贝

  • 浅拷贝 只是对指针的拷贝,拷贝后会有两个指针指向同一个内存空间;

  • 深拷贝 对指针指向的内容进行拷贝,拷贝后会有两个指针指向不同的内存空间;

浅拷贝可能会出现问题,因为两个指针指向同一块内存区域,一个指针的修改会造成另一个指针错误,如出现两个对象析构,两次delete内存的情况。



枚举(enum) 和 宏(#define),typedef,using 的区别?

  • 宏是在预编译阶段进行简单的替换操作,并不占用内存,一次性只能定义一个。

  • 枚举一次性可以定义多个,在编译阶段进行替换,需要占用内存。

  • typedef相当于起别名,在编译阶段进行,并不占用内存。

  • using 和 typedef 类似,都是相当于起别名,不占用内存,在编译阶段进行。且using比typedef更加简洁。



内联函数(inline)是什么?

  • 我们知道如果频繁的调用一个函数,那么函数多次压栈会消耗过多的栈空间。 当函数被申明为内联函数之后,编译器编译时会将其内联展开,而不是按照普通的函数调用机制进行压栈调用;少了多次的压栈操作,栈空间的消耗就减小了。

另外注意:

  1. 内联函数一般是不超过10行的小函数,内联函数中不允许使用循环和开关语句,因为如果内联函数过长或者过于复杂,那么内联展开之后同样会消耗许多栈空间,便得不偿失了。所以滥用内联函数反而可能会导致程序变慢。
  2. 类成员函数默认加上inline,但具体是否进行内联由编译器决定,类函数声明前加上inline是无效写法,只有在类函数定义前加上inline才有效。


struct 与 class 的区别?

struct和class的区别主要在于 默认访问级别 和 默认继承级别。

  1. 默认访问级别:struct中的成员默认是public,class中的成员默认是private。
  2. 默认继承级别:struct默认public继承,class默认private继承。

除了这两点外,struct 和 class 完全相同。



struct 和 union 的区别?

这两个的区别在于内存空间的分配。

  • struct 使用struct时,编译器会给每一个struct成员变量分配空间,并且每一个成员变量互不干扰;

  • union 使用union时,编译器会让union中的成员变量共享同一个空间,并且会根据定义顺序对之前的成员变量进行覆盖。当成员变量的相关性不强时,可以使用union节省内存空间。

注意:class,struct 和 union 都需要进行内存对齐。



内存对齐是什么?(字节对齐)

现代计算机中的内存空间都是按照字节划分的,CPU实际读取内存时,是按照k字节进行读取而不是一个字节一个字节读取,这就是内存对齐;有了内存对齐之后,CPU可以一次性读取k字节的数据,变得更加高效。

注意

  1. k通常为最大成员数据类型的大小,结构体的大小也应该为k的整数倍。

  2. 在union,class,struct中均有内存对齐;但是也可以通过 #pragma push(k)#pragma pop() 来设置内存对齐的方式。



右值引用是什么?移动语义是什么?move函数的作用?

右值: 左值是可以取到地址的值,右值是不能够取到地址的值。右值主要用于实现移动语义。

移动语义:以移动而非深拷贝的方式初始化含有指针成员的类对象。将对象(通常是右值)的内存资源移动为自己使用,这样减小了多次申请释放内存空间的开销。在类中,通常有专门的 移动构造函数移动赋值运算符 来实现移动语义。

move函数:将左值强制转化为右值,转换后就能够调用 移动构造函数移动赋值运算符 来减小多次申请释放内存空间的开销。



RVO返回值优化是什么?

返回值优化(Return value optimization,缩写为RVO)是C++的一项编译优化技术,即省略掉 两次 通过拷贝构造函数 创建临时对象 的过程。这样大大节省了开销。



简述智能指针?

智能指针的本质也是指针,只是它可以帮助我们自动释放空间,避免了内存泄漏和野指针的情况。

目前常用的智能指针有三种auto_ptr已经淘汰):

  1. unique_ptr 一个对象只能由一个unique_ptr引用,当指针不再引用该对象时,该对象自动析构并释放内存。

  2. shared_ptr 一个对象可以由多个shared_ptr引用,对象的被引数量可以用引用计数(use_count)来表示,当对象的引用计数为0时,将该对象自动析构并释放内存。

  3. weak_ptr weak_ptr是一种弱引用,不会增加对象的引用计数,是用来打破shared_ptr相互引用时的死锁问题。 例如现在有两个类,一个类A,一个类B,类A里面有一个shared_ptr指向B,B里面有一个shared_ptr指向A,这样的话就算程序结束了这两个类也不会进行析构,因为他们的引用计数都还是1,这样会造成内存泄漏。

weak_ptr打破死锁的实例

#include <iostream>
#include <memory>
using namespace std;
class B;
class A
{
public:
    A()
    {
        cout << "构造函数" << endl;
    }
    ~A()
    {
        cout << "析构函数" << endl;
    }
    weak_ptr<B> _pb;//若为shared_ptr,那么析构时只会析构pA和pB,但A 和 B 的引用计数仍为1,所以不能析构并释放内存
};

class B
{
public:
    B()
    {
        cout << "构造函数" << endl;
    }
    ~B()
    {
        cout << "析构函数" << endl;
    }
    weak_ptr<A> _pa;
};

int main()
{
    shared_ptr<A> pA(new A());
    shared_ptr<B> pB(new B());
    pA->_pb = pB;
    pB->_pa = pA;
    cout << "A的引用计数:" << pA.use_count() << endl;
    cout << "B的引用计数:" << pB.use_count() << endl;
    return 0;
}
  • 延伸:智能指针是线程安全的吗?

首先智能指针的引用计数使用的是atomic原子操作,所以智能指针本身是线程安全的;但是对于智能指针托管的对象,在多线程环境下则需要加锁操作才行。



C++有哪四种强制类型转换?

  1. static_cast static_cast也就是用于比较自然的类型之间的转换,相比C中的转换有安全检查机制。
  2. const_cast const_cast主要用于去除变量的const属性。
  3. reinterpret_cast reinterpret_cast可以用于不同类型数据之间的转换,是按照逐个比特进行复制。该转换具有很强的灵活性,但并不保证转换的安全性。
  4. dynamic_cast dynamic_cast主要用于将 多态基类的指针或引用 转换为 派生类的指针或引用,并且能够检查安全性。如果转换不安全,对指针则返回nullptr;对引用则抛出bad_cast异常。(多态基类意味着必须由虚函数


动态数组的实现?

创建 m行n列 的二维动态数组,通过以下两种方法:

  1. 使用new
int **a = new int *[m];
   for (int i = 0; i < m; i++)
      a[i] = new int[n];
  1. 使用vector
vector<vector<int>> nums(m, vector<int>(n, 0));


简述STL 库?

STL库即标准模板库,是一个具有工业强度的高效C++库。

  1. 容器

分为序列式容器关联式容器以及容器适配器

  1. 算法

是一种常用的算法模板,可以对容器,数组,自定义结构体进行操作。

  1. 迭代器

是一种特殊的指针,作为容器库和算法库之间的粘合剂。可以将其看成一种泛型指针。 从实现角度看,迭代器是通过重载*,->, ++, - - 等方式实现的。可以将不同数据类型的访问逻辑抽象出来,在不暴露内部结构的情况下对元素进行遍历。

  1. 适配器

适配器分为 函数适配器容器适配器

  • 函数适配器 函数适配器通常通过bind,bind1st, bind2nd 实现,这三个函数都会返回一个新的函数。

  • 容器适配器 stack,queue,priority_queue既是序列式容器,也是容器适配器;stack,queue的标准模板是deque,priority_queue的标准模板是vector

  1. 配置器

配置器的功能在于定义类中内存的分配,也就是 allocator一系列的函数,在各种容器中均存在,只是我们在使用时,allocator对我们来说是完全透明的。

  1. 函数对象(仿函数 function object)

可以理解为一种重载了 () 运算符的结构体,使用时可以当做函数来使用。



简述STL库中的容器以及特点?

  1. 顺序容器
  • vector 底层由数组实现,支持快速随机访问,支持在尾部增删。适用于需要大量随机访问,且只需要在尾部增删的场景。

  • list 底层由双向链表实现,不支持快速随机访问,支持快速增删。适用于不考虑随机访问,且需要大量插入和删除的场景。

  • deque 底层由一个中央控制器和多个缓冲区实现,支持快速随机访问,支持在首尾进行快速增删。适用于需要大量随机访问,且需要在首尾进行快速增删的场景。

  1. 关联容器
  • map 底层由红黑树实现,元素有序且不可重复。以key-value 键值对方式存储,优点是元素有序,缺点是存储红黑树的节点需要消耗大量内存。

  • set 底层由红黑树实现,元素有序且不可重复。优点是元素有序,缺点是存储红黑树的节点需要消耗大量内存。

  • unordered_map 底层由hash表实现,元素无序且不可重复。以key-value键值对方式存储,优点是查询十分的高效。缺点是哈希表的建立需要消耗大量时间。

  • unordered_set 底层由hash表实现,元素无序且不可重复。优点是查询十分的高效。缺点是哈希表的建立需要消耗大量时间。

(若加上multi,则变为可重复)

  1. 容器适配器
  • stack 底层由list或deque实现。适用于先进后出的场景。

  • queue 底层由list或deque实现。适用于先进先出的场景。

  • priority_queue 底层由vector实现,逻辑实现方式为heap(最大堆或最小堆)。适用于设置优先级队列的场景。



简述vector的存储机制?

vector在STL源码中的实现主要是有三个指针:start,finish,end_of_storage start->finish:代表着存储的数据; start->end_of_storage:代表着容量(capacity)的大小 finish->end_of_storage:代表着剩余可存储空间的大小

当无法存储下所有元素时,进行三个步骤:

  1. 申请一个更大的空间,通常是原空间大小的2倍;
  2. 将原空间的数据拷贝到新的空间;
  3. 释放掉原空间内存。

由此可见,vector的动态扩容机制代价较高



简述list的存储机制?

list的底层实现是闭环双向链表,有一个指向尾部节点的空白头结点,只需要这个空白头结点便可以遍历整个链表 在STL源码中有两个指针prevnext,分别指向上一个节点和下一个节点 ,以及data存放数据。 如果是空链表,那么也只有一个空白头结点,prev和next都指向自己。



简述deque的存储机制?

deque是由一个中央处理器map作为主控,注意这个map并不是STL的map容器,而是一小块的连续存储空间。 该连续存储空间存放的每一个元素都是一个指针,指向一个较大的连续存储空间,我们把这个空间称为缓冲区,这个缓冲区才是deque进行实际存储的地方。

deque的内部结构除了有map以外,重要的还有 startfinish 两个迭代器,每个迭代器组成如下:

  • cur:指向缓冲区当前位置的元素位置
  • first:指向缓冲区头
  • last:指向缓冲区尾
  • node:指向中控器map

deque插入:

  • 在后端插入 如果空间大于一个那么就直接插入即可; 如果deque只剩下一个备用空间存储元素,那么进行存储时会先再配置一块新的缓冲区,在设置新的元素内容,最后修改finish指针。

  • 在前端插入 如果start指向的缓冲区还有剩余那么直接插入即可; 如果start指向的的缓冲区没有剩余,那么配置一块新的缓冲区,存储新的元素,最后修改start指针。

在了解了deque的插入方法之后, 那么deque 的删除也就很简单了。

注意:deque的最初状态(无任何元素时)都有一个缓冲区,即便是使用了clear()之后也有一个缓冲区。



什么情况下选择unorder_map 和 map?

选用map还是unordered_map,关键在于看关键字的查询操作次数

如果查询操作次数较多,要求平均查询时间短,那么就使用unordered_map

如果只有少次数的查询,unordered_map可能会造成不确定的O(N),且创建也需要大量时间,那么就不如选用单词处理时间恒定为O(logN)的map



什么情况下选择set和map?

这两种数据类型的底层均为红黑树。

map更适合用于作为数据字典,也就是关键字的查询

而set适用于判断关键字是否在集合中



简述迭代器失效的情况?

迭代器失效分为三种情况。

  1. 数组型数据结构

由于该类型的元素都是存储在连续的内存空间中,进行插入和删除后没有重新分配空间,那么会导致该 位置以及之后元素移位,也就导致该位置以及之后的迭代器失效;如果重新分配空间了的话,那么所有的迭代器都会失效。

  1. 链表型数据结构

由于链表的特点,删除某一位置的元素只会让该位置的迭代器失效,不会影响其他迭代器。

  1. 红黑树型数据结构

由于树本身也是一种链表,删除某一位置的元素只会让该位置的迭代器失效,不会影响其他迭代器。



没有迭代器的容器有哪些?

queue, stack, priority_queue。



简述public ,protected,private 访问修饰符的区别?

  1. 修饰成员时

类的内部(定义该类的代码内部),无论成员被public,protected,private修饰,都可以随意访问。

类的外部(定义该类的代码外部),只有public修饰的成员能够被访问,protected和private修饰的成员均不能被访问。

class中如果不写则默认是private修饰

  1. 在继承时

在这里插入图片描述

class中如果不写则默认是private继承

注意:不论哪一种方式,基类的private成员均不能在派生类中使用;但并不是基类的private成员没有被派生类继承,实质上是继承了并占用内存了的,只是不能使用。



派生类不能继承基类的哪些东西?

派生类可以继承基类的大部分资源,但是 构造函数析构函数不能够继承。

延伸:赋值运算符可以继承吗? 可以继承,只是在继承了之后由于派生类本身也拥有赋值运算符,那么就会将基类的赋值运算符进行隐藏。当然,也可以通过域限定符(::)调用基类的赋值运算符。

延伸:友元关系可以继承吗? 不能,友元关系不具有传递性。



友元是什么?

友元可以分为友元函数以及友元类,在某一个类中申明了友元函数或者友元类之后,该友元函数或友元类就可以访问该类的所有public,protected和 private 成员。

注意

  1. 友元关系没有传递性,也不能够被继承。
  2. 友元并不属于类成员(也就没有this指针,通常是将对象作为参数),所以放在public, protected, private 均可,没有关系。


this指针是什么?

this实质上是成员函数的一个隐形形参,在通过对象调用成员函数时会将该对象的地址赋值给this指针

注意

  1. this指针是加了const修饰的,所以无法修改指针的指向。
  2. 友元由于本质上不属于类成员,所以没有this指针。
  3. (static)静态成员函数没有this指针,因为this指针的的赋值是需要将对象的地址传入的。


简述C++中的多重继承?(菱形继承)

多继承会让程序变得复杂,同时可能会继承一些不必要的数据。 多继承容易出现命名冲突的问题,可以加上域限定符(::),或是采用虚继承来消除二义性。



什么情况下需要使用初始化列表初始化成员变量?

  1. 类中有const 修饰 或者 引用类型 的成员变量;

  2. 类中有必须用参数初始化的对象;

  3. 派生类需要初始化基类的成员变量;



类成员变量初始化的顺序?

是按照 类中的声明顺序 进行初始化的,并不是按照初始化列表的顺序进行初始化。



简述C++中的多态机制?(虚函数,多态相关问题)

多态:是一个接口的多种形态。

C++中的多态机制是通过虚函数来实现的。 实现多态的条件有两个:

  1. 虚函数重写
  2. 调用虚函数时必须使用指针或引用

虚函数:虚函数是带有 virtual 关键字的 类成员函数。实现多态需要进行 虚函数重写,也就是派生类有一个和基类 函数名,参数,返回值完全相同 的成员函数,也就称为虚函数重写(覆盖)。这就是虚函数实现多态的方式。

虚函数表:有虚函数的类在编译时期都会生成一个 虚函数表,虚函数表实质上是一个 指针数组,存放 指向虚函数的指针,通过该指针我们可以调用虚函数;另外虚函数表是类对象之间共享的,在各个类对象之间不会存储整个虚函数表,只存放一个指向该 虚函数表的指针,该指针通常是放在类内存的最前面。这就是虚函数表。该虚函数表存放在全局静态数据区DATA段

在生成派生类过程中,对虚函数表的操作有三个步骤

  1. 将基类中的虚函数表指针拷贝到派生类中;
  2. 派生类对基类虚函数进行覆盖(重写);
  3. 派生类将自己新增的虚函数依次添加在虚函数表后。

延伸1:虚函数表中的虚函数指针是如何实现偏移的?

通常编译器会将虚函数表指针放在对象内存的最前面,我们可以通过取该对象的地址得到该虚函数表指针,进而得到虚函数表,也就是一个指针数组(指针属于函数指针,指向虚函数),遍历这个数组,就可以得到我们想要的虚函数。


延伸2:每个实例化对象的虚函数表是否相同?

相同,因为虚函数表属于类对象之间共享的,只会有一个,每一个实例化对象都只会存放一个虚函数表指针,指向共享的虚函数表。



虚函数与纯虚函数的区别?

纯虚函数在基类中只申明不定义(如virtual void func() = 0),必须在派生类中进行覆盖重写虚函数表;拥有纯虚函数的基类被称为抽象类,抽象类不能实例化,只能被继承。



C++中哪些函数不能是虚函数?

  1. 构造函数不能是虚函数

如果将构造函数设置为虚函数,那么派生类将无法创建,因为无法调用基类的构造函数。

  1. inline内联函数不能是虚函数

因为内联函数会在编译时内联展开,而虚函数需要在运行时动态联编。

  1. 友元函数不能是虚函数

因为友元函数不属于类成员函数,虚函数必须是类成员函数。

  1. 静态成员函数不能是虚函数

因为静态成员函数是整个类共享的,虚函数无法进行覆盖。



静态联编与动态联编?(静态绑定与动态绑定)

  • 静态联编 是编译阶段就能确定的程序行为。

  • 动态联编 是程序运行时进行的确定的程序行为,实质上是运行时虚函数的实现。

编译时多态通过重载函数实现,运行时多态通过虚函数实现。



重载,隐藏,覆盖的区别?

  • 重载同一个类中,函数名相同参数类型(或个数) 不同则为函数重载;如果只是 返回值不同 则不能称为重载。重载也称为编译时多态。

  • 隐藏 若派生类的函数名与基类的 函数名相同,参数不同,无论该函数是否有virtual,派生类的函数则会吧基类的函数隐藏起来。 若派生类的函数名与基类的 函数名相同,参数相同,函数没有virtual,派生类的函数则会吧基类的函数隐藏起来。

  • 覆盖 派生类中的函数与基类中的虚函数完全相同(函数名,参数,返回值均相同),那么称为覆盖。覆盖也称为运行时多态。



构造函数与析构函数能否为虚函数?

  • 构造函数不能为虚函数

若构造函数为虚函数,那么派生类生成的过程中将会无法调用基类的构造函数。

  • 析构函数可以为虚函数

如果不将析构函数设置为虚函数,只能调用基类的析构函数,将无法调用派生类的析构函数从而造成内存泄漏。



析构函数可以抛出异常吗?

析构函数不能抛出异常,原因如下:

  1. 如果析构函数抛出异常,那么异常点之后的程序并不会执行,那么就会造成内存泄漏的问题。

  2. 严格来说,析构函数也是处理异常的一部分;如果之前发生异常,调用析构函数来释放内存,若是析构函数也抛出异常,将会让程序崩溃。



空类中自带哪些函数?

六个函数:

  • 构造函数
  • 析构函数
  • 拷贝构造函数
  • 赋值运算符
  • 取址运算符
  • 取址运算符const。

补充: 取址运算符:T* operator&() 取址运算符const: const T* operator&() const



抽象类和接口的区别?

  • 抽象类即是拥有纯虚函数的基类,不能够被实例化;
  • 接口是一种特殊的抽象类,并且满足:1)类中没有任何成员变量;2)所有的成员函数都是公有且都是纯虚函数。