C++初阶第五篇——类和对象(中)(构造函数+析构函数+拷贝构造函数+赋值操作符重载)

Posted 呆呆兽学编程

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了C++初阶第五篇——类和对象(中)(构造函数+析构函数+拷贝构造函数+赋值操作符重载)相关的知识,希望对你有一定的参考价值。

⭐️今天我要和大家分享C++中类和对象中有关6个默认成员函数的相关知识。
⭐️博客代码已上传至gitee:https://gitee.com/byte-binxin/cpp-class-code

目录


🌏类的6个默认成员函数

在一个类中,会默认生成6个默认成员函数,即使是空类也是如此。
分类如下:

🌏构造函数

概念: 一个特殊的成员函数,名字与类名相同,创建类类型对象时由编译器自动调用,一般是用来初始化成员变量,且一个生命周期只调用一次。

构造函数特性:

  • 函数名和类名相同
  • 无返回值
  • 对象实例化是自动调用
  • 可以发生重载
class Date

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

	
	// 2. 有参构造
	Date(int year, int month, int day)
	
		_year = year;
		_month = month;
		_day = day;
	
	// 3.全缺省
	Date(int year = 1, int month = 1, int day = 1)
	
		_year = year;
		_month = month;
		_day = day;
	
private:
	int _year;
	int _month;
	int _day;
;

构造函数显示定义的三种方法:

  1. 无参构造函数(实例化对象时后面不跟括号,否则会变成函数的声明)
  2. 有参构造函数
  3. 全缺省构造函数

类中无构造函数时,编译器会默认生成一个无参的默认构造函数。
默认构造函数有三种:全缺省、不含参和编译器给的

在我们不实现构造函数时,编译器就会调用编译器默认生成的构造函数,看下面一串代码,编译器的默认构造函数能否实现成员变量的初始化呢?

class Date

public:
	void Print()
	
		cout << _year << "-" << _month << "-" << _day << endl;
	
private:
	int _year;
	int _month;
	int _day;
;


int main()

	Date  d1;
	d1.Print();

	return 0;

我们再看运行结果:

3个值都是随机值,编译器的默认构造函数并没有进行对成员变量进行初始化,是不是说这个函数就没有什么用呢?
当然不能这么说,C++中把类型分为内置类型(基础类型)和自定义类型(class,struct等),编译器的默认构造函数对内置类型不处理,对自定义类型会调用它自己的默认构造函数,如果不存在(调有参时),编译器会报错。
看下面的代码,我给大家演示一下:

class A

public:
	A()
	
		cout << "A()" << endl;
	
private:
	int _a;
;
class Date

public:
	void Print()
	
		cout << _year << "-" << _month << "-" << _day << endl;
	
private:
	int _year;
	int _month;
	int _day;
	
	A _a;
;


int main()

	Date  d1;

	return 0;


代码运行结果如下:

显然是调用了_a的默认构造函数。

🌏析构函数

概念: 对象在销毁时会自动调用析构函数,完成类的一些资源清理工作。

特性:

  • 函数名前加一个‘~’
  • 无参数,无返回值
  • 一个类中有且只有一个析构函数,对象的生命周期结束是,编译器会自动调用
class Array

public:
	Array(int length = 0)
	
		_length = length;
		_p = (int*)malloc(sizeof(int) * _length);
	

	// 析构函数
	~Array()
	
		if (_p)
		
			free(_p);
			_p = nullptr;
		
	
private:
	int _length;
	int* _p;
;

如果我们没有实现析构函数,编译器会自动生成一个析构函数,且这个析构函数堆内置类型不处理,对自定义类型会调用它的析构函数,这一点和编译器生成的默认构造函数有点相似。

一个小思考

看下面一串代码,他们的构造和析构顺序是怎么样的?

class A

public:
	A()
	
		cout << "A()" << endl;
	

	~A()
	
		cout << "~A()" << endl;
	
private:
	int _a;
;

class B

public:
	B()
	
		cout << "B()" << endl;
	

	~B()
	
		cout << "~B()" << endl;
	
private:
	int _b;
;

int main()

	A a;
	B b;

	return 0;

代码运行结果如下:

构造的顺序和对象的创建顺序相同,析构和对象的创建顺序相反。(其实就是一个压栈的问题,先进后出)

🌏拷贝构造函数

概念: 只有单个形参,该形参是对本类类型对象的引用(一般常用const修饰),在用已存在的类类型对象创建新对象时由编译器自动调用。

特性:

  • 拷贝构造函数是构造函数的一个重载形式
  • 参数有且只有一个,且必须使用引用传参,使用传值会引发无穷递归
class Date

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


	
	AA _a;
;

下面解释为什么传值会无穷递归

如果未定义拷贝构造函数,编译器会默认生成一个拷贝构造函数,默认的拷贝构造函数会对内置类型按内存存储的字节序完成拷贝,对于自定义类型编译器会调用他们的默认构造函数,这种拷贝是浅拷贝
浅拷贝有很大的潜在危险,看下面一串代码:

class Array

public:
	Array(int length = 0)
	
		_length = length;
		_p = (int*)malloc(sizeof(int) * _length);
	

	// 析构函数
 ~Array()
	
		if (_p)
		
			free(_p);
			_p = nullptr;
		
	
private:
	int _length; 
	int* _p;
;

int main()

	Array a1(10);
	Array a2(a1);

	return 0;

程序运行结果如下:

代码直接就崩了,这就是浅拷贝带来的影响。

🌏赋值云运算符重载

🌰运算符载

概念: 运算符重载是具有特殊函数名的函数,也具有其返回值类型,函数名字以及参数列表,其返回值类型与参数列表与普通的函数类似。
函数原型:

返回值   operator操作符(参数列表)

需要注意的几点:

  • 不能通过重载操作符来创建新的操作符,如operator@
  • 必须含有一个类类型或枚举类型的操作数
  • 不能改变内置类型的含义
  • 作为类成员的重载操作符,隐含了一个Date* const this的形参,限定为第一个形参
  • .*,::,?:,sizeof,.这五个运算符不能重载
class Date

public:
	Date(int year = 1, int month = 1, int day = 1)
	
		_year = year;
		_month = month;
		_day = day;
	
	
	// operator==
	bool operator==(const Date& d)
	
		return _year == d._year
			&& _month == d._month
			&& _day == d._day;
	

	void Print()
	
		cout << _year << "-" << _month << "-" << _day << endl;
	
private:
	int _year;
	int _month;
	int _day;

;

int main()

	Date d1(2022, 1, 20);
	Date d2(2022, 1, 19);

	cout << (d1 == d2) << endl;// ==>d1.operator==(d2) 

	return 0;

d1 == d2 <==> d1.operator==(d2)
两种调用方式等价

🌰赋值运算符重载

原型: 返回值 operator=(参数列表)

特性:

  • 返回是*this
  • 如果没有显示定义,编译器也会生成一个,按字节序拷贝
Date& operator=(const Date& d)

	// 检测是否自己给自己赋值
	if (this == &d)
	
		_year = d._year;
		_month = d._month;
		_day = d._day;
	

	return *this;

🌰与拷贝构造函数的异同

相同:没有显示定义时,编译器都会默认生成一个,对于内置类型进行字节序的浅拷贝,对自定义类型会调用它自身的拷贝构造函数或operator=。
不同:
拷贝构造:用一个已经存在的对象初始化一个马上要创建的对象
赋值重载:两个已经存在的对象之间进行赋值。

🌰前置++和后置++的实现

先看下面一个类:

class Person

public:
	Person(string name, int age)
	
		_name = name;
		_age = age;
	

private:
	string _name;
	int _age;
;

前置++和后置++最大的区别就是返回值不同,前置是返回变化之后的值,后置是返回变化之前的值,两个在重载是,都是operator++,我们如何区分呢?
一般operator++默认是前置++,为了区分后置++,我们通常会在参数列表加一个占位参数,且这个参数必须是int类型的,从而构造成函数重载。实现如下:

class Person

public:
	Person(string name, int age)
	
		_name = name;
		_age = age;
	

	// 前置++
	Person& operator++()
	
		_age++;
		return *this;// 返回变化之后的值,传引用
	

	// 后置++
	Person operator++(int)
	
		Person ret = *this;
		_age++;

		return ret;// 返回变化之前的值,传值
	

	void Print()
	
		cout << _name << "-" << _age << endl;
	
private:
	string _name;
	int _age;
;

int main()

	Person p("wxj", 19);
	p.Print();

	cout << "前置++" << endl;
	// 前置++
	Person ret = ++p;
	ret.Print();
	p.Print();

	cout << "后置++" << endl;
	// 后置++
	ret = p++;
	ret.Print();
	p.Print();

	return 0;

运行结果如下:

🌰const修饰类的成员函数

概念: 将const修饰的类成员函数称之为const成员函数,const修饰类成员函数,实际修饰该成员函数隐含的this指针,表明在该成员函数中不能对类的任何成员进行修改。
一般我们再成员函数后面加const,就会使得this的类型变为 const Date* const ,也就是该成员函数中成员变量不能够被修改。

事例:

bool operator==(const Date& d) const // const Date* const this

	return _year == d._year
		&& _month == d._month
		&& _day == d._day;

🌏取地址及const取地址重载

这两个函数一般自己不定义,都是编译器自己生成。

Date* operator&()

	return this ;

const Date* operator&() const

	return this ;

🌐总结

类和对象的大体内容就差不多是这些了,类和对象(下)还会继续介绍一些有关的零碎知识。喜欢的话,欢迎大家点赞支持~

以上是关于C++初阶第五篇——类和对象(中)(构造函数+析构函数+拷贝构造函数+赋值操作符重载)的主要内容,如果未能解决你的问题,请参考以下文章

C++初阶:类和对象(中篇)构造函数 | 析构函数 | 拷贝构造函数 | 赋值运算符重载

C++初阶第六篇——类和对象(下)(初始化列表+explicit关键字+static成员+友元+内部类)

C++初阶类和对象

数据结构初阶第五篇——栈和队列(实现+图解)

C++初阶---类和对象(类的默认成员函数和其他)

C++初阶第四篇——类和对象(上)(类的定义+封装+this指针)