本文总结C++中容易混淆的概念。

1 常见易混淆知识点

1.1 内存控制

1.1.1 malloc/free, new/delete的区别

  • malloc和free是C/C++语言的标准库函数,而new/delete是C++的运算符。它们都可以用于动态申请和释放内存。
  • new/delete运算符不仅可以申请/释放内存,还可以对申请的对象进行初始化和销毁操作。
  • C++既可以使用new/delete,也可以调用malloc/free。而C语言只能用malloc/free管理动态内存。

1.1.2 new/delete

  • new/delete就是C++的运算符。new的全称为new operator, delete的全称为delete operator。其作用是为分配并初始化一个对象数组。
    C++执行new操作实际上后台执行了以下3步操作:
  1. 调用operate new或者operate new []的标准库函数。该函数分配一块原始的、足够大的、未命名的存储特定类型对象的空间;
  2. 编译器运行相应的构造函数以构造这些特定对象(或对象数组);
  3. 对象被构造完成并分配的空间,返回一个指向该对象的指针。

C++执行delete操作实际上后台执行了以下2步操作:

  1. 对所指向的对象或者对象数组执行对应的析构函数;
  2. 编译器调用operate delete(或者operate delete[])的标准库函数释放内存空间。

使用示例:

1
2
3
4
5
int* p = new int;
delete p;

int* pa = new int[10];
delete [] pa;

1.1.3 operate new/delete

  • operate new/delte是C++的标准函数。用于用于为特定类型的对象分配或者释放内存空间。operate new/delete的函数原型定义如下:
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
void* operate new(size_t);   // 分配一个对象
void* operate new[](size_t); // 分配一个对象数组
void* operate delete(void*) noexpect;   // 释放一个对象
void* operate delete[](void*) noexpect; // 释放一个对象数组

// 不会抛出异常的函数,功能同上
void* operate new(size_t, nothrow_t&) noexpect;
void* operate new[](size_t, nothrow_t&) noexpect;
void* operate delete(void*, nothrow_t&) noexpect; 
void* operate delete[](void*, nothrow_t&) noexpect;

如果应用程序希望自己控制内存的分配和释放,就需要自己定义operate new和operate delete函数,从而让编译器使用自定义的版本替换标准库中定义的版本。 应用程序可以在全局作用域中定义oprerate new/delete函数或者定义为类的成员函数。
如果分配或者释放的的对象是类对象,则首先在类中查找是否有自定义的oprerate new/delete函数。若没查找到,再到全局作用域中查找。
我们也可以直接指定查找全局作用域的函数:使用::new ::delete可以指定查找全局作用域的函数。 因为operate new用在构造函数之前而operate delete用在对象销毁之后,所以这两个成员函数必须是静态函数,而且不能操作类的任何数据成员。

例外:不能重载以下形式的operate new函数:

1
void* operate new(size_t, void*);

这种形式只供标准库使用,不能被用户重新定义。

温馨提示
实际上C++也有和operate new/delete功能相似的标准库allocator类,用于为指定类型的对象分配内存。它非配的内存是原始、未构造的。

1.1.4 placement new

placement new用于对使用operate new的方式申请的内存上构造对象。placement new的几种形式:

1
2
3
4
new (place_address) type
new (place_address) type (initializers)
new (place_address) type [size]
new (place_address) type [size] {braced initializer list}

其中place_address必须是一个指针,同时在initializers中提供一个(可能为空)的逗号分隔的初始化列表,该初始值列表用于构造新分配的对象。

1.1.5 智能指针

1.1.5.1 shared_ptr

1.1.5.2 unique_ptr

1.1.5.3 weak_ptr

1.2 指针与引用的区别

  • 指针本身就是一个对象,其内容是某个内存单元的地址,允许对指针赋值和拷贝;而引用并非对象,它只是一个已经存在的对象的一个别名。
  • 指针在它的生命周期内可以指向不同的对象;而引用一旦初始化完成,将和它的初始值始终绑定在一起。
  • 指针无须在定义时赋初值;而引用必须初始化,绑定到一个已经存在的对象。指针可以为空,但引用不可以。
  • sizeof操作指针和引用的结果不同;sizeof操作引用得到的是引用所绑定的对象的大小;而sizeof操作指针得到的是指针本身的大小。
  • 引用是类型安全的,而指针不是。

1.3 struct和class的区别

  • struct的成员(成员函数和数据成员)的访问控制属性默认是共有的;而class的成员默认是私有的。
  • struct的继承默认是共有继承,而class的默认继承方式是私有的。

1.4 const修饰常量与define的区别

  • const定义的常量在编译阶段进行处理;#define定义的常量在预处理阶段进行处理;
  • const定义的常量要进行类型检查;#define定义的常量只是简单的文本替换,没有类型,不进行类型检查;
  • const定义的常量要分配内存;#define定义的常量为宏定义,不需要分配内存;
  • const定义的常量存放在数据区,只有一份拷贝;#define定义的常量不分配存储空间,使用过程中只是简单的执行文本替换;

1.5 重载覆盖隐藏的区别

1.5.1 重载

重载的特点:

  • 相同的范围(在同一个类中)
  • 函数名字相同
  • 参数不同
  • virtual 关键字可有可无

1.5.2 覆盖

覆盖是指派生类函数覆盖基类虚函数。其特征为:

  • 不同的范围(分别位于派生类与基类);
  • 函数名字相同
  • 参数相同
  • 基类函数必须有virtual 关键字

1.5.3 隐藏

“隐藏”是指派生类的函数屏蔽了与其同名的基类函数,规则如下:

  • 如果派生类的函数与基类的函数同名,但是参数不同。此时,不论有无virtual关键字,基类的函数将被隐藏(注意别与重载混淆)。
  • 如果派生类的函数与基类的函数同名,并且参数也相同而且基类函数没有virtual 关键字。此时,基类的函数被隐藏(注意别与覆盖混淆)

1.5.4 覆盖隐藏的示例

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
class Base {
public:
    virtual void fcn() {
        cout << "This is Base fcn()." << endl;
    }
};

class FristDerived : public Base {
public:
     // FristDerived继承Base中的虚函数fcn()
     // 形参与Base::fcn()中的不一致,隐藏基类的fcn()
    void fcn(int) {
        cout << "This is FristDerived fcn()." << endl;
    }
    // 新增一个虚函数,在Base中不存在
    virtual void display() { 
        cout << "This is FristDerived display()." << endl;
    }
};

class SecondDerived : public FristDerived {
public:
    // 隐藏FristDerived::fcn(int)
    void fcn(int a) { 
        cout << "This is SecondDerived fcn(" << a << ")." << endl;
    }
    void fcn() { // 覆盖Base中的虚函数fcn()
        cout << "This is SecondDerived fcn()." << endl;
    }
    void display() { // 覆盖FristDerived中的虚函数display()函数
        cout << "This is SecondDerived display()." << endl;
    }
};

int main()
{
    Base base_obj;
    FristDerived fd_obj;
    SecondDerived sd_obj;

    Base* bp1 = &base_obj;
    Base* bp2 = &fd_obj;
    Base* bp3 = &sd_obj;
    bp1->fcn(); // Base::fcn()
    bp2->fcn(); // Base::fcn()
    bp3->fcn(); // SecondDerived::fcn()
    cout << "----------" << endl;


    FristDerived* fd = &fd_obj;
    SecondDerived* sd = &sd_obj;
    // bp2->display(); // 错误,Base没有display()函数
    fd->display();  // FristDerived::display()
    sd->display();  // SecondDerived::display()

    return 0;
}