探究全自动化机器——构造与析构的奥秘

文章详细阐述了C++中的构造函数,包括其作用、特点和默认构造函数的生成规则。析构函数的作用在于清理工作,其唯一性和非重载性也被提及。对于内置类型和自定义类型的处理差异进行了说明,特别是自定义类型会调用构造函数。文章还讨论了初始化列表的重要性以及拷贝构造函数的必要性,特别是避免无穷递归的问题。最后,文章举例展示了如何在实践中应用这些概念,如Stack和Date类的实现。
摘要由CSDN通过智能技术生成

类的6个默认成员函数(我们不写,编译器自动生成):构造函数,析构函数,拷贝构造,赋值重载,普通对象和const对象取地址。

一、构造函数主要完成初始化工作,是特殊的成员函数
特点:
1、函数名和类名相同
2、无返回值(不需要写void)
3、对象实例化时编译器自动调用对应的构造函数。
4、构造函数可以重载
5、 如果类中没有显示定义构造函数,则C++编译器会自动生成一个无参的默认构造函数,一旦用户显示定义,编译器将不再生成
6、无参的构造函数和全缺省的构造函数都称为默认构造函数,并且默认构造函数只能有一个。注意:无参构造函数、全缺省构造函数,都可以认为是默认构造函数。

二、析构函数主要完成清理工作,是特殊的成员函数
特点
1、析构函数名是在类名前加上字符~
2、无参数返回值类型
3、一个类只能有一个析构函数。若未显示定义,系统会自动生成默认的析构函数。注意:析构函数不能重载。
4、对象生命周期结束时自动调用

三、内置类型与自定义类型
· 内置类型/基本类型、语言本身定义的基础类型int/char/double/指针等等
· 自定义、用struct/class等等定义的类型
1、再谈构造函数
我们不写,编译器默认生成的构造函数,内置类型不做处理(有些编译器也会处理),自定义类型会去调用他的默认构造。
 

#include<stdlib.h>
#include<iostream>
using namespace std;

class Stack
{
public:
	//构造函数
	Stack(int capacity = 4)
	{
		cout << " Stack(int capacity = 4) " << endl;
		_array = (int*)malloc(sizeof(int) * capacity);
		if (NULL == _array)
		{
			perror("malloc申请空间失败");
			return;
		}
		_capacity = capacity;
		_size = 0;
	}
private:
	int* _array;
	int _capacity;
	int _size;
};

class Date
{
public:
	void Print()
	{
		cout << _year << "-" << _month << "-" << _day << endl;
	}
private:
	int _year;
	int _month;
	int _day; //C++规定内置类型不做处理(有些编译器可能处理,但那是个性化行为)

	Stack _st;//自定义类型调用它的构造函数
};

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

· 一般条件下,有内置类型成员,就需要自己写构造函数,不能用编译器自己生成的。
· 可以考虑让编译器自己生成的情况:
a.内置类型成员都有缺省值且初始化,符合我们的要求
b.全是自定义类型的构造,且这些类型都定义默认构造。
(C++11标准发布的时候打了个补丁,在成员声明的时候可以给缺省值)

#include<iostream>
using namespace std;

class Date
{
public:
	/*Date()
	{
		int _year = 2023;
		int _month = 7;
		int _day = 21;
	}*/

	Date(int year=2023, int month=7, int day=21)
	{
		_year = year;
		_month = month;
		 _day = day;
	}

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

private:
	int _year = 2023;
	int _month = 7;
	int _day = 21;
};

//缺省参数
//没有传参时,使用参数的默认值传参时,使用指定的实参
//传参是从左向右依次,缺省值是从右向左依次,栈帧调用压栈顺序由编译器决定(不能跳着传/指定传参)

//声明和定义不能同时给缺省值
//声明给缺省值,定义不给缺省值

int main()
{
	Date d1;
	d1.Print();
	//Date d1();//不可以这样写,会和函数声明冲突,编译器不好识别
	Date d2(2022,8,22);
	d2.Print();
}

· 构造函数的调用跟普通函数也不一样:对象(参数),不可以空传,会跟函数声明有冲突,编译器不好识别
· 无参的构造函数和全缺省的构造函数都称为默认构造函数,并且默认构造函数只能有一个,所以无参构造函数、全缺省构造函数,我们没写编译器默认生成的构造函数都可以认为是默认构造函数,不传参就可以调用的,就是默认构造函数
· 无参和全缺省构成函数重载,但会产生调用歧义

2、初始化列表

创建对象调用构造函数虽然能给函数体赋初值,但算不上对其进行初始化。若要对其初始化,则需要使用初始化列表。
初始化列表:对象成员定义的位置(一个冒号开始接着是一个逗号分隔的成员列表,每个成员变量后面跟一个放在括号中的初始值或表达式数据)

//构造函数体赋值
class Date
{
public:
    Date(int year, int month, int day)
    {
        _year = year;
        _month = month;
        _day = day;
    }
private:
    int _year;
    int _month;
    int _day;
};

//初始化列表
class Date
{
public:
    Date(int year,int month,int day)
        :_year(year)
        ,_month(month)
        ,_day(day)
    {}
private:
    int _year;
    int _month;
    int _day;
};

注意:
1.每个成员变量在初始化列表中最多只能出现一次(初始化只能初始化一次)
2. 类中包含以下成员,必须放在初始化列表位置进行初始化:
☐ 引用成员变量
☐ const成员变量
☐ 自定义类型成员(在该类没有默认构造函数时)
特征:必须在定义的时候初始化
成员变量在类中声明次序就是其在初始化列表中的初始化顺序,与其在初始化列表中的先后顺序无关
(声明顺序和定义顺序尽量保持一致)

class A
{
public:
    A(int a)//没有默认构造,只有带参构造
        :_a(a)
    {
        cout << "A(int a)" << endl;
    }
private:
    int _a;
};

class B
{
public:
    B(int a, int& ref)
        :_ref(ref)
        , _n(0)
        , _x(0)
        ,aobj(a)//显式初始化
    {
        //_n = 0;
        //_ref = ref;
    }
private:
    int& _ref;//引用
    const int _n = 1;//const
    int _x = 1;//缺省值给初始化列表
    A _aobj;//若没有默认构造函数
};

3、再谈析构函数

内置类型成员不做处理。自定义类型会去调用它的析构函数

· 一般情况下,有动态申请资源,就需要显示析构函数释放资源
· 没有动态申请的资源,不需要写析构,需要释放资源的成员,不需要写析构
· 需要释放资源的成员都是自定义类型,不需要写析构


四、拷贝构造函数
拷贝构造函数是构造函数的一个重载形式
拷贝构造函数的参数只有一个,且必须是类类型对象的引用,使用传值方式,编译器直接报错,因为会引发无穷递归调用

07a0ab41539d43c28ab830688d2ae206.png

无穷递归原因:C++规定:内置类型直接拷贝,自定义类型必须调用拷贝构造(建议加const引用)。可以用指针和引用解决问题
如下列代码:d1是d的别名,d传给了this

#include<iostream>
using namespace std;

class Date
{
public:
    //构造函数
	Date(int year = 2023, int month = 7, int day = 21)
	{
		_year = year;
		_month = month;
		_day = day;
	}

	//拷贝构造
	Date(const Date& d)
	{
		_year = d._year;
		_month = d._month;
		_day = d._day;
	}
	void Print()
	{
		cout << _year << "-" << _month << "-" << _day << endl;
	}

private:
	int _year = 2023;
	int _month = 7;
	int _day = 21;
};

int main()
{
	Date d1(2022, 8, 22);
	d1.Print();

	Date d2(d1);
	d2.Print();
}

内置类型成员完成值拷贝浅拷贝
自定义类型成员会调用它的拷贝构造

Date默认生成的拷贝构造可以用
但是stack默认生成的拷贝构造不可以用,因为同一块空间析构了两次(一个修改会影响另一个)(后定义的先析构),所以必须自己实现深拷贝(后续章节会详细解析深拷贝相关内容)

a805e19d54bf450a96ac2a7f25c0816c.png
内置类型可以直接比较自定义类型,不可以直接比较(运算符重载相关内容将在下一节详细论述)

以下代码作为对前几章节的应用,一并附上:

#include<iostream>

using namespace std;
typedef int Datatype;

class Stack
{
public:

	//手动初始化
	Stack(Datatype* a, int n)
	{
		cout << "Stack(Datatype * a, int n)" << endl;
		_array = (Datatype*)malloc(sizeof(Datatype) * n);
		if (NULL == _array)
		{
			perror("malloc申请空间失败");
			return;
		}
		memcpy(_array, a, sizeof(Datatype) * n);
		_capacity = n;
		_size = 0;
	}

	//构造函数
	Stack(int capacity = 4)
	{
		cout << " Stack(int capacity = 4) " << endl;
		_array = (Datatype*)malloc(sizeof(Datatype) * capacity);
		if (NULL == _array)
		{
			perror("malloc申请空间失败");
			return;
		}
		_capacity = capacity;
		_size = 0;
	}

	//析构函数
	~Stack()
	{
		cout << " ~Stack() " << endl;
		free(_array);
        _array = nullptr;
		_capacity = _size = 0;
	}

	//尾插
	void Push(Datatype data)
	{
		checkcapacity();
		_array[_size] = data;
		_size++;
	}

	//尾删
	void Pop()
	{
		if (Empty())
		{
			return;
		}
		_size--;

	}

	Datatype Top() { return _array[_size - 1]; }//取栈顶元素
	int Empty() { return 0 == _size; }//判空
	int Size() { return _size; }//栈大小

private:
	//检查容量
	void checkcapacity()
	{
		if (_size == _capacity)
		{
			int newcapacity = _capacity * 2;
			Datatype* temp = (Datatype*)realloc(_array, newcapacity * sizeof(Datatype));
			if (NULL == _array)
			{
				perror("realloc申请空间失败");
				return;
			}
			_array = temp;
			_capacity = newcapacity;
		}
	}
private:
	Datatype* _array;
	int _capacity;
	int _size;
};

int main()
{
	Stack s1;
}


 

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值