【C++类和对象】类有哪些默认成员函数呢?(上)

目录

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

2. 构造函数(*^▽^*)

2.1 概念

2.2 特性

3. 析构函数(*^▽^*)

3.1 概念

3.2 特性

4. 拷贝构造函数(*^▽^*)

4.1 概念

4.2 特性

5. 赋值运算符重载(*^▽^*)

5.1 运算符重载

5.2 赋值运算符重载


ヾ(๑╹◡╹)ノ"人总要为过去的懒惰而付出代价!ヾ(๑╹◡╹)ノ"


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

如果一个类中什么成员都没有,简称为空类。
空类中并不是什么都没有,任何类在什么都不写时,编译器会自动生成以下6个默认成员函数。

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

2. 构造函数(*^▽^*)

2.1 概念

对于data类:

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(2022, 7, 5);//初始化
	d1.Print();
	Date d2;
	d2.Init(2022, 7, 6);//初始化
	d2.Print();
	return 0;
}

对于上面的这个代码,可以通过 Init 公有方法给对象设置日期,每次创建对象时都调用该方法设置信息。

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

2.2 特性

构造函数 是特殊的成员函数,需要注意的是,构造函数虽然名称叫构造,但是构造函数的主要任务 并不是开 空间创建对象,而是初始化对象
特征:
1. 函数名与类名相同。
2. 无返回值。
3. 对象实例化时编译器 自动调用 对应的构造函数。
4. 构造函数可以重载。【但是我们大部分写的是全缺省的构造函数,一般不用函数重载【写一个默认的构造函数+别的函数重载】】
class Date
{
public:
	// 1.无参构造函数
	Date()//构造函数:函数名与类名相同,无返回值
	{
		_year = 1;
		_month = 1;
		_day = 1;
	}
	// 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 d1();
	Date d2(2015, 1, 1); // 调用带参的构造函数
	// 注意:如果通过无参构造函数创建对象时,对象后面不用跟括号,否则就成了函数声明
}
5. 如果类中没有显式定义构造函数,则 C++ 编译器会自动生成一个无参的默认构造函数,一旦用户显式定义编译器将不再生成。【即没有实现构造函数,C++编译器就会自动生成一个无参的默认构造函数】

 默认构造函数:(1)无参的构造函数(2)全缺省的构造函数(3)C++编译器生成的无参的构造函数【即三种必须要有一种,如果没有默认的构造函数【写的构造函数不是无参的,也不是全缺省的】就会报错】

6. 关于编译器生成的默认成员函数,在不实现构造函数的情况下,编译器会生成默认的构造函数。但是看起来默认构造函数又没什么用? 对象调用了编译器生成的默认构造函数,但是 对象 _year/_month/_day,依旧是随机值【内置类型仍然是随机值】。也就说在这里 编译器生成的默认构造函数并没有什么用?
答: C++ 把类型分成内置类型 ( 基本类型 ) 和自定义类型。内置类型就是语言提供的数据类型,如:int/char...,自定义类型就是我们使用 class/struct/union 等自己定义的类型。当不实现构造函数的情况下,编译器生成默认的构造函数会对自定类型成员 调用的它的默认成员函数。

如果一个类中的成员全是自定义类型,我们就可以用默认生成的函数。如果有内置类型函数成员,或者需要显示传参初始化,那么都要自己实现构造函数。(需要传参初始化,需要自己实现构造函数)【大部分都是自己写构造函数】

C++编译器默认生成构造函数内置类型函数成员变量不作处理,自定义类型成员会去调用它自己的默认构造函数。

注意: C++11 中针对内置类型成员不初始化的缺陷,又打了补丁,即: 内置类型成员变量在类中声明时 可以给默认值 。【注意:这里是声明,所以是一个缺省值,并不是初始化】
7. 无参的构造函数和全缺省的构造函数都称为默认构造函数,并且默认构造函数只能有一个。
注意:无参构造函数、全缺省构造函数、我们没写编译器默认生成的构造函数,都可以认为是默认构造函数。
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;//这里会发生错误,默认构造函数只能有一个
}

运行时都会发生错误,出现冲突。

一般情况一个C++类,都要自己写构造函数。只有极少数情况下可以让编译器自动生成:

(1)类里面成员都是自定义类型成员, 并且这些成员都提供了默认构造函数。

(2)内置类型成员声明时给出缺省值。

3. 析构函数(*^▽^*)

3.1 概念

析构函数:与构造函数功能相反,析构函数不是完成对象的销毁,局部对象销毁工作是由编译器完成的。而 对象在销毁时会自动调用析构函数,完成类的一些 资源清理 工作。【destory】

3.2 特性

析构函数 是特殊的成员函数。
特征:
1. 析构函数名是在类名前加上字符 ~
2. 无参数无返回值。
3. 一个类有且只有一个析构函数。若未显式定义,系统会自动生成默认的析构函数【自定义类型的变量会去调用它自己的析构函数处理,内置类型的变量不作处理】。 注意:析构函数不能重
4. 对象生命周期结束时,C++ 编译系统系统自动调用析构函数。
5.  关于编译器自动生成的析构函数,编译器生成的默认 析构函数,对会自定义类型成员调用它自己的析构函数。
6 . 如果类中没有申请资源时,析构函数可以不写,直接使用编译器生成的默认析构函数,比如 Date 类;有 资源申请时,一定要写,否则会造成资源泄漏,比如 Stack 类【总之,有资源申请时,析构函数必须写】。

4. 拷贝构造函数(*^▽^*)

4.1 概念

 创建对象时,创建一个与一个对象一某一样的新对象。

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

4.2 特性

1. 拷贝构造函数是构造函数的一个重载形式

2. 拷贝构造函数的参数只有一个 必须使用引用传参 ,使用 传值方式会引发无穷递归调用
class Date
{
public:
	Date(int year = 1900, int month = 1, int day = 1)
	{
		_year = year;
		_month = month;
		_day = day;
	}
	Date(const Date& d)//拷贝构造函数
	{
		_year = d._year;
		_month = d._month;
		_day = d._day;
	}
private:
	int _year;
	int _month;
	int _day;
};
int main()
{
	Date d1;
	Date d2(d1);
	return 0;
}

传值方式:调用拷贝构造会调用拷贝构造函数,调用这个函数需要先传参,传参是一份临时拷贝,此时的状态是临时拷贝对象,相当于又是拷贝构造,又需要调用拷贝构造函数,此时就会引发无穷递归调用。

引用传参:就不会调用拷贝构造。

【一个对象初始化这个类的另一个对象,就是拷贝构造】【进行拷贝对象就是拷贝构造】

自定义类型对象,拷贝初始化要调用拷贝构造完成。

若未显示定义,系统生成默认的拷贝构造函数。 默认的拷贝构造函数 对象按内存存储按字节序完成拷贝,这种拷贝我们叫做浅拷贝,或者值拷贝。【即内置类型的成员会完成值拷贝(浅拷贝),自定义类型的成员,去调用这个成员的拷贝构造】
结论:一般的类,自己生成的拷贝构造就够用了,不需要自己写拷贝构造。但是像stack这样的类,直接自己管理资源,就需要自己实现深拷贝。
4. 那么编译器生成的默认拷贝构造函数已经可以完成字节序的值拷贝了 ,我们还需要自己实现吗?当然像日期类这样的类是没必要的。栈这样的类是不可以的。【栈里边有一个地址,那么两个栈就会执向同一块空间,增删查改的时候会互相影响以及析构的时候同一块空间会被析构两次,同一块空间释放两次,程序会崩溃 ——结局方案,自己实现拷贝构造-深拷贝】
类中如果没有涉及资源申请时,拷贝构造函数是否写都可以;一旦涉及到资源申请时,则拷贝构造函数是一定要写的,否则就是浅拷贝。

 为了提高程序效率,一般对象传参时,尽量使用引用类型,返回时根据实际场景,能用引用尽量使用引用。

5. 赋值运算符重载(*^▽^*)

5.1 运算符重载

C++ 为了 增强代码的可读性 引入了运算符重载 运算符重载是具有特殊函数名的函数 ,也具有其返回值类型,函数名字以及参数列表,其返回值类型与参数列表与普通的函数类似。
函数名字为 :关键字 operator 后面接需要重载的运算符符号
函数原型 返回值类型  operator 操作符 ( 参数列表 )
返回值:运算法运算后结果
参数:运算法操作数
注意:
(1)不能通过连接其他符号来创建新的操作符:比如 operator@
(2)重载操作符必须有一个类类型参数
(3)用于内置类型的运算符,其含义不能改变,例如:内置的整型 + ,不 能改变其含义
(4)作为类成员函数重载时,其形参看起来比操作数数目少 1 ,因为成员函数的第一个参数为隐藏的this
(5).*    ::    sizeof    ?:     . 注意以上 5 个运算符不能重载。这个经常在笔试中出现。
//日期的判断是否相等
bool operator==(const Date& d1, const Date& d2)
{
	return d1._year == d2._year 
		&& d1._month == d2._month 
		&& d1._day == d2._day;
 }
//放到类里面 Date,此时是在类外面的写法
//1.
if (operator==(d1, d2))
{
	cout << "==" << endl;
}
//2/
if (d1 == d2)
{
	cout << "==" << endl;
}
//1.和2.是等价的,编译器会处理成1.

(1)内置类型是可以直接用各种运算符,但是自定义类型是不可以直接用各种运算法的。为了让自定义类型可以使用各种运算符,所以就有了运算符重载。

(2)有多少个操作数就有多少个函数参数。(==两个操作数;++一个操作数;)

在类里面的写法

//日期的判断是否相等
bool operator==(const Date& d)
{
	return _year == d._year
		&& _month == d._month
		&& _day == d._day;
}
//放到类里面 Date,此时是在类里面的写法
//1.
if (d1.operator==(d2))
{
	cout << "==" << endl;
}
//2/
if (d1 == d2)
{
	cout << "==" << endl;
}
//1.和2.是等价的,编译器会处理成对应重载运算法调用

如果两个代码都存在(编译器是可以通过的,因为符合函数重载),编译器优先使用类里面的运算符重载。

//判断日期小
bool operator<(const Date& d)
{
	//小的情况
	if (_year < d._year
		|| (_year == d._year && _month < d._month)
		|| (_year == d._year && _month == d._month && _day < d._day))
	{
		return true;
	}
	else
	{
		return false;
	}
}

这里注意细节,不能想当然。

5.2 赋值运算符重载

1. 参数类型
2. 返回值
3. 检测是否自己给自己赋值
4. 返回 *this
5. 一个类如果没有显式定义赋值运算符重载,编译器也会生成一个,完成对象按字节序的值拷贝
1. 赋值运算符重载格式
参数类型 const T& ,传递引用可以提高传参效率
返回值类型 T& ,返回引用可以提高返回的效率,有返回值目的是为了支持连续赋值
检测是否自己给自己赋值
返回 *this :要复合连续赋值的含义
//d2 = d1;-> d2.operator(&d2, d1)
Date& operator=(const Date& d)
{
	if (this != &d)
	{
		_year = d._year;
		_month = d._month;
		_day = d._day;
	}
	return *this;
}
int main()
{

	Date d1(2000, 8, 20);
	Date d2(2000, 9, 17);
	Date d3(d1);//拷贝构造,一个存在的对象去初始化另一个要创建的对象
	d2 = d1;//赋值重载(复制拷贝) 两个已经存在的对象之间赋值
}
2.赋值运算符只能重载成类的成员函数不能重载成全局函数
赋值运算符重载成全局函数,注意重载成全局函数时没有this指针了,需要给两个参数,但是
赋值运算符如果不显式实现,编译器会生成一个默认的。此时用户再在类外自己实现一个全局的
赋值运算符重载,就和编译器在类中生成的默认赋值运算符重载冲突了,故赋值运算符重载只能是类的成员函数
3.用户没有显式实现时,编译器会生成一个默认赋值运算符重载,以值的方式逐字节拷贝 。注意:内置类型成员变量是直接赋值的,而自定义类型成员变量需要调用对应类的赋值运算符重载完成赋值。
4.编译器生成的默认赋值重载函数已经可以完成字节序的值拷贝了 ,我们还需要自己实现吗?当然像日期类这样的类是没必要的。如果类中未涉及到资源管理,赋值运算符是否实现都可以;一旦涉及到资源管理则必须要实现。

补充知识:空指针是不存在的吗?是存在的,空地址是一个存在的地址。

猜你喜欢

转载自blog.csdn.net/m0_57388581/article/details/132179408