目录

一、类的默认成员函数

二、构造函数

1. 构造函数的概念

三、构造函数的特性

1. 函数名与类名相同

2. 没有返回值

3. 对象创建时自动调用

4. 构造函数可以重载

四、this指针

空指针调用成员函数问题

五、默认构造函数

1. 什么是默认构造函数?

2. 编译器自动生成默认构造

3. 用户写了构造函数,编译器就不再生成

六、默认构造函数的行为

1. 内置类型

2. 自定义类型

七、C++11中的缺省值

八、无参构造与全缺省构造

无参构造

全缺省构造

默认构造函数只能有一个

九、空类大小问题

十、析构函数

1. 析构函数的概念

十一、析构函数的特性

1. 函数名是 ~类名

2. 无参数无返回值

3. 一个类只能有一个析构函数

4. 生命周期结束自动调用

十二、默认析构函数

默认析构函数会做什么?

对内置类型

对自定义类型

十三、为什么需要析构函数?

1. 不需要写析构函数的情况

2. 必须写析构函数的情况

十四、成员对象的构造与析构顺序

十五、对象析构顺序

十六、RAII思想

RAII思想


一、类的默认成员函数

在 C++ 中,即使一个类什么都不写,编译器也会自动帮我们生成一些函数,这些函数被称为默认成员函数。

所谓默认成员函数,就是:

用户没有显式实现,编译器自动生成的成员函数。

一个类在默认情况下,编译器会自动生成 6 个默认成员函数:

  • 构造函数

  • 析构函数

  • 拷贝构造函数

  • 赋值运算符重载

  • 取地址重载

  • const 取地址重载

其中最核心的是:

  • 构造函数

  • 析构函数

  • 拷贝构造

  • 赋值重载


二、构造函数

1. 构造函数的概念

构造函数是一种特殊的成员函数。

很多初学者会误以为:

构造函数的作用是“创建对象”。

实际上并不是。

对象的空间在定义对象时就已经开好了,构造函数真正负责的是:

对对象进行初始化。

它相当于替代了 C 语言中的 Init 初始化函数。

例如:

STInit(&st);
DateInit(&d);

在 C++ 中可以通过构造函数自动完成初始化。


三、构造函数的特性

1. 函数名与类名相同

class Date
{
public:
    Date()
    {

    }
};

构造函数名字必须与类名一致。


2. 没有返回值

构造函数不需要写返回值类型。

错误写法:

void Date()
{

}

正确写法:

Date()
{

}

这是 C++ 语法规定。


3. 对象创建时自动调用

class Date
{
public:
    Date()
    {
        cout << "调用构造函数" << endl;
    }
};

int main()
{
    Date d;

    return 0;
}

运行结果:

调用构造函数

对象创建后,构造函数会自动执行。


4. 构造函数可以重载

构造函数本质也是函数,因此支持函数重载。

class Date
{
public:
    // 无参构造
    Date()
    {
        _year = 1;
        _month = 1;
        _day = 1;
    }

    // 带参构造
    Date(int year, int month, int day)
    {
        _year = year;
        _month = month;
        _day = day;
    }

private:
    int _year;
    int _month;
    int _day;
};

使用:

Date d1;
Date d2(2025, 1, 1);

四、this指针

在 C++ 中:

非静态成员函数内部都会隐藏一个 this 指针。

例如:

d1.Print();

编译器实际会处理成:

Print(&d1);

所以成员函数内部才能访问:

this->_year

本质上:

this

指向当前调用函数的对象。

例如:

class Date
{
public:
    void Print()
    {
        cout << this->_year << endl;
    }

private:
    int _year;
};

这里的 this 指针本质就是当前对象地址。


空指针调用成员函数问题

class A
{
public:
    void Print()
    {
        cout << "A::Print()" << endl;
    }

private:
    int _a;
};

int main()
{
    A* p = nullptr;

    p->Print();

    return 0;
}

这段代码不会报空指针解引用问题,会正常运行。

因为:

p->Print();

本质是:

Print(p);

函数地址存在代码段中,不依赖对象空间。

但是如果访问成员变量:

cout << p->_a;

程序就会崩溃。

因为空指针无法访问对象数据。


五、默认构造函数

1. 什么是默认构造函数?

只要:

不传参数就能调用的构造函数

都叫默认构造函数。

包括:

  • 无参构造

  • 全缺省构造

  • 编译器自动生成的构造函数


2. 编译器自动生成默认构造

如果类中没有写构造函数:

class Date
{
private:
    int _year;
    int _month;
    int _day;
};

编译器会自动生成默认构造函数。


3. 用户写了构造函数,编译器就不再生成

class Date
{
public:
    Date(int year, int month, int day)
    {
        _year = year;
        _month = month;
        _day = day;
    }

private:
    int _year;
    int _month;
    int _day;
};

int main()
{
    Date d1;
}

会报错:

没有默认构造函数

因为:

一旦用户自己写了构造函数,编译器就不会再自动生成默认构造。


六、默认构造函数的行为

很多同学会发现:

class Date
{
public:
    void Print()
    {
        cout << _year << endl;
    }

private:
    int _year;
};

int main()
{
    Date d;
    d.Print();
}

输出的是随机值。

原因是:

编译器默认生成的构造函数,对内置类型不做处理。


1. 内置类型

例如:

  • int

  • char

  • double

  • 指针

这些都属于内置类型。

默认构造不会初始化它们。


2. 自定义类型

如果成员变量是类对象:

class Time
{
public:
    Time()
    {
        cout << "Time构造" << endl;
    }
};

class Date
{
private:
    Time _t;
};

创建 Date 对象时:

Date d;

会自动调用:

Time()

因为:

默认构造会自动调用自定义类型成员自己的构造函数。


七、C++11中的缺省值

C++11 后,可以直接给成员变量默认值:

class Date
{
private:
    int _year = 2025;
    int _month = 1;
    int _day = 1;
};

这样即使不写构造函数,也能完成初始化。


八、无参构造与全缺省构造

无参构造

Date()
{

}

全缺省构造

Date(int year = 2025, int month = 1, int day = 1)
{

}

它们都属于默认构造函数。


默认构造函数只能有一个

下面代码会报错:

class Date
{
public:
    Date()
    {

    }

    Date(int year = 2025, int month = 1, int day = 1)
    {

    }
};

因为:

Date d;

调用时会产生歧义。

编译器不知道调用哪个构造函数。


九、空类大小问题

即使类中没有成员变量:

class C
{

};

它的大小也不是 0。

cout << sizeof(C) << endl;

结果:

1

原因是:

编译器需要给对象一个唯一地址进行标识。

所以空类至少占 1 个字节。


十、析构函数

1. 析构函数的概念

析构函数与构造函数相反。

它并不是负责销毁对象本身。

因为:

  • 局部对象存在栈区

  • 生命周期结束后系统自动回收空间

析构函数真正负责的是:

清理对象内部申请的资源。

例如:

  • malloc

  • new

  • fopen

等动态资源。

它类似于 C 语言中的:

Destroy()

函数。


十一、析构函数的特性

1. 函数名是 ~类名

~Date()
{

}

2. 无参数无返回值

~Date()
{

}

3. 一个类只能有一个析构函数

因为:

  • 名字固定

  • 参数固定

所以析构函数不能重载。


4. 生命周期结束自动调用

class Date
{
public:
    ~Date()
    {
        cout << "~Date()" << endl;
    }
};

int main()
{
    Date d;

    return 0;
}

程序结束时:

~Date()

会自动执行。


十二、默认析构函数

如果用户不写析构函数:

class Date
{

};

编译器会自动生成默认析构函数。


默认析构函数会做什么?

对内置类型

什么都不做。

对自定义类型

自动调用它们自己的析构函数。

例如:

class Time
{
public:
    ~Time()
    {
        cout << "~Time()" << endl;
    }
};

class Date
{
private:
    Time _t;
};

Date 对象销毁时:

~Time()

也会自动调用。


十三、为什么需要析构函数?

1. 不需要写析构函数的情况

例如:

class Date
{
private:
    int _year;
    int _month;
    int _day;
};

这里只是普通变量。

没有动态资源。

因此不需要写析构函数。


2. 必须写析构函数的情况

例如栈:

class Stack
{
public:
    Stack(int n = 4)
    {
        _a = (int*)malloc(sizeof(int) * n);
    }

    ~Stack()
    {
        free(_a);
        _a = nullptr;
    }

private:
    int* _a;
};

这里使用了:

malloc

申请堆空间。

如果不释放:

free(_a);

就会造成内存泄漏。


十四、成员对象的构造与析构顺序

例如:

class MyQueue
{
private:
    Stack pushst;
    Stack popst;
};

构造顺序:

pushst
popst

析构顺序:

popst
pushst

原因是:

构造按成员声明顺序进行,析构顺序与构造相反。

这和栈的“后进先出”思想类似。


十五、对象析构顺序

对于局部对象:

后定义的对象先析构。

例如:

int main()
{
    Date d1;
    Date d2;
}

析构顺序:

d2
d1

因为栈本身就是后进先出的结构。


十六、RAII思想

在 C++ 中:

  • 构造函数负责获取资源

  • 析构函数负责释放资源

对象生命周期结束时自动释放资源。

这就是:

RAII思想

即:

Resource Acquisition Is Initialization
资源获取即初始化

它是 C++ 管理资源最核心的思想之一。

相比 C 语言:

Init();
Destroy();

需要手动调用。

C++ 自动调用构造和析构,大大降低了资源泄漏风险。

更多推荐