【C++】类与对象——六个默认成员函数、构造函数的概念和特征,析构函数的概念和特征

文章详细介绍了C++中类的六个默认成员函数,重点讨论了构造函数和析构函数的概念、特性和使用示例。构造函数用于初始化类对象,可以有多个重载形式,并且在对象创建时自动调用。析构函数则在对象生命周期结束时自动调用,用于清理资源。文章通过代码示例展示了如何使用构造函数初始化对象,以及析构函数在内存管理中的作用。
摘要由CSDN通过智能技术生成

1.类的六个默认成员函数

  如果一个类中什么成员都没有,简称为空类。
  空类中真的什么都没有吗?

  并不是,任何类在什么都不写时,编译器会自动生成以下6个默认成员函数。

class Date {};

默认成员函数:用户没有显式实现,编译器会生成的成员函数称为默认成员函数。

在这里插入图片描述

2.构造函数

2.1构造函数的概念

  构造函数是C++中的一个特殊函数,用于初始化类对象的数据成员,为对象分配内存并完成一些初始化工作。一个类可以有多个构造函数,但必须满足函数名相同、参数列表不同的条件,称为函数重载
  构造函数有以下特点:

(1)构造函数的函数名与类名相同,并且不需要返回类型的声明,在函数体中也不需要指定 return 语句。

(2)构造函数可以具有参数,用于传递初始值给对象的数据成员

(3)构造函数可以进行重载,支持多个构造函数的存在。

(4)如果一个类没有定义自己的构造函数,编译器会自动生成一个默认的构造函数,该函数不带任何参数并且什么也不做,它会自动初始化类的成员变量并分配内存。

(5) 构造函数可以使用初始化列表进行初始化,这种方式可以提高效率。初始化列表是用冒号:跟在构造函数名后的成员初始化语句,以逗号隔开数据成员的名称和初始值。初始化列表的执行顺序与成员在类中声明的顺序一致。

(6) 如果一个类需要在离开作用域时,自动释放在堆内存上分配的资源,必须定义类的析构函数(Destructor)

总结:构造函数是一个特殊的成员函数,名字与类名相同,创建类类型对象时由编译器自动调用,以保证每个数据成员都有 一个合适的初始值,并且在对象整个生命周期内只调用一次。

例子一(Person类):

class Person {
public:
    string name;
    int age;
    Person(string n, int a) { // 带参数的构造函数
        name = n;
        age = a;
    }
};

int main() {
    Person p1("Alice", 20); // 使用构造函数创建对象
    cout << p1.name << ", " << p1.age << endl; // 输出对象的成员变量

    return 0;
}

  我们定义了一个 Person 类,并创建了一个带有参数的构造函数来初始化对象的成员变量。
  在 main() 函数中,我们使用类定义了一个 Person 对象,编译器在创建对象时会自动调用构造函数,初始化对象并输出该对象的成员变量值。

例子二(Date类):

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

void Print()
{
	cout << _year << "-" << _month << "-" << _day << endl;
}

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

int main()
{
	Date d1;
	d1.Init(2023, 5, 29);
	d1.Print();
	Date d2;
	d2.Init(2023, 5, 29);
	d2.Print();
	return 0;
}

  这段代码定义了一个名为 Date 的类。Date 类包含了三个私有的数据成员:_year(年份)、_month(月份)和 _day(日期)。该类提供了两个公有的成员函数: Init 和 Print。
  Init 函数用于初始化 Date 对象的年、月、日信息,Print 函数则用于打印出 Date 对象的年、月、日信息。在主函数中,创建了两个 Date 对象,并通过 Init 函数初始化了其年、月、日的信息。

  我们可以使用构造函数来代替Init函数的作用,使创建的数据成员初始化。这两段代码实现的功能完全一样。

#include <iostream>
using namespace std;

class Date
{
public:
    // 默认构造函数
    Date() {
        _year = 1949;
        _month = 10;
        _day = 1;
    }
    // 带参数的构造函数
    Date(int year, int month, int day) {
        _year = year;
        _month = month;
        _day = day;
    }
    // 输出日期方法
    void Print()
    {
        cout << _year << "-" << _month << "-" << _day << endl;
    }

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

int main()
{
    // 使用构造函数初始化对象
    Date d1(2023, 5, 29);
    d1.Print();
    Date d2(2023, 5, 29);
    d2.Print();
    return 0;
}

2.2构造函数的特性

  综上所述:构造函数是特殊的成员函数,需要注意的是,构造函数虽然名称叫构造,但是构造函数的主要任务并不是开空间创建对象,而是初始化对象

  其特征如下:

(1)函数名与类名相同。

(2)无返回值。

(3)对象实例化时编译器自动调用对应的构造函数。

(4)构造函数可以重载。

  在函数 TestDate 中,分别通过无参构造函数和带参构造函数创建了三个 Date 类型的对象(d1、d2 和 d3)。其中,d1 使用了无参构造函数,d2 使用了带参构造函数,并传入年月日参数进行初始化。而 d3 也是通过无参构造函数创建的对象,但使用了错误的语法,即在对象后添加了一对空括号,则编译器会将其解析为函数声明,而不是对象的创建;正确的写法应该是 Date d3;

class Date
{
public:
// 1.无参构造函数
Date()
{}

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

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

void TestDate()
{
	Date d1; // 调用无参构造函数
	Date d2(2015, 1, 1); // 调用带参的构造函数
	// 注意:如果通过无参构造函数创建对象时,对象后面不用跟括号,否则就成了函数声明
	// 以下代码的函数:声明了d3函数,该函数无参,返回一个日期类型的对象
	// warning C4930: “Date d3(void)”: 未调用原型函数(是否是有意用变量定义的?)
	Date d3();
}


(5)如果类中没有显式定义构造函数,则C++编译器会自动生成一个无参的默认构造函数,一旦用户显式定义编译器将不再生成。

  在这段代码中,如果将 Date 类中的构造函数屏蔽掉,那么编译器将会自动生成一个默认的无参构造函数,此时主函数中的代码可以正常编译。但如果取消屏蔽,那么编译器将不再生成默认构造函数,主函数中的代码将会编译失败。
  所以这个构造函数Date是错误的,因为在使用默认参数时,必须在函数声明或定义中为这些参数提供默认值,否则编译器会报错。

class Date
{
public:
/*
// 如果用户显式定义了构造函数,编译器将不再生成
Date(int year, int month, int day)
{
_year = year;
_month = month;
_day = day;
}
*/
void Print()
{
	cout << _year << "-" << _month << "-" << _day << endl;
}

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

int main()
{
	// 将Date类中构造函数屏蔽后,代码可以通过编译,因为编译器生成了一个无参的默认构造函数
	// 将Date类中构造函数放开,代码编译失败,因为一旦显式定义任何构造函数,编译器将不再生// 无参构造函数,放开后报错:error C2512: “Date”: 没有合适的默认构造函数可用
	Date d1;
	return 0;
}

(6)C++把类型分成内置类型(基本类型)和自定义类型。内置类型就是语言提供的数据类型,如:int / char …,自定义类型就是我们使用 class / struct /union 等自己定义的类型,编译器生成默认的构造函数会对自定类型成员_t调用的它的默认成员函数。

  Date 类包含了三个整型类型的基本数据成员 _year、_month、_day 和一个 Time 类型的私有数据成员 _t,其中 Time 类由于没有显式定义构造函数,因此会有一个默认的无参构造函数。

  在主函数中,创建对象 d 时将调用 Date 类的默认构造函数,并在其中对其基本数据成员 _year、_month、_day 进行了初始化,Time 类型的成员 _t 也会调用其默认构造函数,将 _hour、 _minute、 _second 分别初始化为 0。

class Time
{
public:
Time()
{
	cout << "Time()" << endl;
	_hour = 0;
	_minute = 0;
	_second = 0;
}
private:
	int _hour;
	int _minute;
	int _second;
};

class Date
{
private:
	// 基本类型(内置类型)
	int _year;
	int _month;
	int _day;
	// 自定义类型
	Time _t;
};

int main()
{
	Date d;
	return 0;
}

  注意:C++11 中针对内置类型成员不初始化的缺陷,又打了补丁,即:内置类型成员变量在类中声明时可以给默认值。

class Time
{
public:
Time()
{
	cout << "Time()" << endl;
	_hour = 0;
	_minute = 0;
	_second = 0;
}
private:
	int _hour;
	int _minute;
	int _second;
};

class Date
{
private:
	// 基本类型(内置类型)
	int _year = 1970;
	int _month = 1;
	int _day = 1;
	// 自定义类型
	Time _t;
};

int main()
{
	Date d;
	return 0;
}

(7)无参的构造函数和全缺省的构造函数都称为默认构造函数,并且默认构造函数只能有一个。注意:无参构造函数、全缺省构造函数、我们没写编译器默认生成的构造函数,都可以认为是默认构造函数。

  在这段代码中,Date 类显式声明了两个构造函数——其中一个是默认构造函数,另一个是带有三个参数的构造函数。在 Test() 函数中,没有提供任何参数来创建 Date 类型的对象 d1,因此将会使用默认构造函数进行初始化。

  所有这个测试函数能够通过编译,且会成功创建一个 Date 类型的对象 d1,该对象的年月日属性分别为默认值 1900 年 1 月 1 日。

class Date
{
public:
Date()
{
	_year = 1900;
	_month = 1;
	_day = 1;
}

Date(int year = 1900, int month = 1, int day = 1)
{
	_year = year;
	_month = month;
	_day = day;
}

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

void Test()
{
	Date d1;
}

3.析构函数

3.1析构函数的概念

  在 C++ 中,析构函数(Destructor)是一种特殊的函数,其名称与类名称相同,但在名称前面加上一个波浪号(~)。析构函数与构造函数一样,也是一种特殊的成员函数,但它是在对象生命周期结束时自动调用的,并且只能有一个析构函数,且不能带有参数。

  析构函数通常用于清理对象所占用的资源,比如释放动态申请的内存、关闭打开的文件等。具体来说,析构函数的工作包括释放内存或资源、删除临时文件、清理诸如打开的文件和数据库连接等。

析构函数有一下特点:

(1)在析构函数中,一般要释放对象所使用的内存或资源,防止内存泄漏。

(2)在堆上分配内存的对象,必须在析构函数中释放。

(3)调用析构函数的顺序与调用构造函数的顺序相反,即先析构派生类对象,再析构基类对象。

(4) 如果一个类有成员变量是指针类型或者是其他类的对象,需要在析构函数中先释放这些成员变量占用的内存,然后再释放自己的内存。

(5) 如果一个类没有显示地声明析构函数,则编译器会为该类生成一个默认的析构函数。默认的析构函数什么也不做。

总结:析构函数:与构造函数功能相反,析构函数不是完成对对象本身的销毁,局部对象销毁工作是由编译器完成的。而对象在销毁时会自动调用析构函数,完成对象中资源的清理工作。

简单的析构函数使用例子:

#include<iostream>
using namespace std;

class A
{
public:
    A()
    {
        cout << "A constructor" << endl;
    }
    
    ~A()
    {
        cout << "A destructor" << endl;
    }
};

int main()
{
    A a;
    return 0;
}

3.2析构函数的特征

析构函数是特殊的成员函数,其特征如下:

(1)析构函数名是在类名前加上字符 ~。

(2)无参数无返回值类型。

(3)一个类只能有一个析构函数。 若未显式定义,系统会自动生成默认的析构函数。注意:析构函数不能重载。

(4)对象生命周期结束时,C++编译系统系统自动调用析构函数。


以栈的实现为例:

typedef int DataType;
class Stack
{
public:
Stack(size_t capacity = 3)
{
	_array = (DataType*)malloc(sizeof(DataType) * capacity);
	if (NULL == _array)
	{
		perror("malloc申请空间失败!!!");
		return;
	}
	_capacity = capacity;
	_size = 0;
}

void Push(DataType data)
{
	// CheckCapacity();
	_array[_size] = data;
	_size++;
}

// 其他方法...
~Stack()
{
	if (_array)
	{
		free(_array);
		_array = NULL;
		_capacity = 0;
		_size = 0;
	}
}

private:
	DataType* _array;
	int _capacity;
	int _size;
};

void TestStack()
{
	Stack s;
	s.Push(1);
	s.Push(2);
}

(5)编译器生成的默认析构函数,对自定类型成员调用它的析构函数。

  在main方法中根本没有直接创建Time类的对象,为什么最后会调用Time类的析构函数?

  因为:main方法中创建了Date对象d,而d中包含4个成员变量,其中_year, _month, _day三个是内置类型成员,销毁时不需要资源清理,最后系统直接将其内存回收即可;而_t是Time类对象,所以在d销毁时,要将其内部包含的Time类的_t对象销毁,所以要调用Time类的析构函数。

  但是:main函数中不能直接调用Time类的析构函数,实际要释放的是Date类对象,所以编译器会调用Date类的析构函数,而Date没有显式提供,则编译器会给Date类生成一个默认的析构函数,目的是在其内部调用Time类的析构函数,即当Date对象销毁时,要保证其内部每个自定义对象都可以正确销毁main函数中并没有直接调用Time类析构函数,而是显式调用编译器为Date类生成的默认析构函数。

注意:创建哪个类的对象则调用该类的析构函数,销毁那个类的对象则调用该类的析构函数。

class Time
{
public:
~Time()
{
	cout << "~Time()" << endl;
}
	
private:
	int _hour;
	int _minute;
	int _second;
};

class Date
{
private:
	// 基本类型(内置类型)
	int _year = 1970;
	int _month = 1;
	int _day = 1;
	// 自定义类型
	Time _t;
};

int main()
{
	Date d;
	return 0;
}
//程序运行结束后输出:~Time()

(6)如果类中没有申请资源时,析构函数可以不写,直接使用编译器生成的默认析构函数,比如Date类;有资源申请时,一定要写,否则会造成资源泄漏,比如Stack类。


这些就是C++中类和对象中构造函数和析构函数的简单介绍了😉
如有错误❌望指正,最后祝大家学习进步✊天天开心✨🎉

  • 2
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

鳄鱼麻薯球

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值