类和对象中篇——默认成员函数总结

目录

默认成员函数

1. 构造函数

2. 析构函数

3. 拷贝构造函数

拷贝构造的无限递归问题

深拷贝与浅拷贝

运算符重载函数

前置运算符与后置运算符的重载

4. 赋值运算符重载

const成员

5/6. 取地址重载函数


默认成员函数

在类中有一些特殊的成员函数,称为默认成员函数它们用来执行常见的操作,例如对象的初始化、清理、拷贝和赋值等,默认成员函数区别于其他成员函数的地方在于就算我们不写编译器也会默认生成。

默认成员函数有6个:

  • 构造函数(用于完成初始化工作)
  • 析构函数(用于完成清理工作)
  • 拷贝构造(用于使用同类对象初始化创建对象)
  • 赋值重载(用于把一个对象赋值给另一个对象)
  • 取地址重载(用于普通对象取地址,这个很少会自己实现)
  • const取地址重载(用于const对象取地址,这个很少会自己实现)

注:默认成员函数只能是类成员,你猜它们为啥叫默认成员函数

1. 构造函数

介绍

构造函数是默认成员函数的一种,用于在创建对象时对其进行初始化操作。

特性

  • 构造函数的函数名与类名相同且没有返回值
Date() //构造函数
{
	_day = 0;
	_month = 0;
	_year = 0;
}
  • 构造函数可以有缺省值和重载函数
  • 对象实例化时编译器会自动调用对应的构造函数
  • 构造函数如果有参数需要在对象实例化时传入需要的参数,如果没有则不需要
void TestDate()
{
	Date d1(2023,6,1); //调用带参的构造函数
	Date d2; //调用无参构造函数
   //不能写成Date d2()否则编译器无法区分是函数声明还是变量定义
}
  • 如果类中没有显式定义构造函数,则编译器会自动生成一个无参的默认构造函数,一旦用户定义构造函数编译器将不再生成
  • 默认构造函数指的是不需要传入参数的构造函数,没有参数和全缺省都构成默认构造函数
Date(int year = 0, int month = 0, int day = 0) //全缺省
{
	_day = year;
	_month = month;
	_year = day;
}
Date() //无参
{
	_year = 0;
	_month = 0;
	_day = 0;
}
  • 默认构造函数从语法的角度可以定义多个,但在调用时会产生二义性,应只保留一个默认构造函数
  • 编译器自己生成的默认构造函数,对内置类型不做任何处理对自定义类型会去调用它的默认构造函数
  • 无论是自己实现的构造函数还是编译器默认生成的构造函数,对于自定义类型在初始化列表时会自动的去调用它的默认构造函数进行初始化
  • 对于内置类型成员不初始化的缺陷在C++11中打了补丁,即内置类型的成员变量在类中声明时可以给缺省值
class Date
{
public: 
	int _year = 0; //在声明时给一个缺省值
	int _month = 0;
	int _day = 0;
};
  • 缺省值的本质是个备胎,如果构造函数没有对成员变量进行初始化则使用缺省值进行初始化

2. 析构函数

介绍

构造函数是默认成员函数的一种,用于执行对象清理和资源释放的操作。

特性

  • 析构函数名是在类名前加上字符 ~
  • 析构函数无显示参数无返回值类型
~Date() //析构函数
{
	_year = 0;
	_month = 0;
	_day = 0;
}
  • 析构函数不能重载, 一个类只能有一个析构函数
  • 若未显式定义,编译器会自动生成默认的析构函数。编译器自己生成的析构函数,对内置类型不做任何处理对自定义类型会去调用它的析构函数
  • 无论是自己实现的析构函数还是编译器默认生成的析构函数对于自定义类型编译器会去调用它的析构函数进行清理工作
  • 在对象生命周期结束时,编译器会自动调用析构函数。栈内对象是在保存函数返回值之后调用的析构函数
  • 栈内对象析构函数调用的顺序是:先实例化的后调用、后实例化先调用,因为要符合栈的特性

3. 拷贝构造函数

概念

拷贝构造函数是构造函数的一种特殊重载形式,用于在创建对象时用已经存在的对象来初始化一个新对象

特性

  • 拷贝构造函数是构造函数的一种重载形式
  • 拷贝构造函数的参数只有一个且必须是类型对象的引用,如果使用传值方式编译器直接报错,因为会引发无限递归
	Date(Date& d) //必须是引用类型,普通对象编译器会直接报错
	{
		_year = d._year;
		_month = d._month;
		_day = d._year;
	}
  • 若类中未显示定义,编译器会自动生成一个拷贝构造函数。编译器自己生成的拷贝构造函数,对内置类型只能完成浅拷贝对自定义类型会去调用它的拷贝构造函数
拷贝构造的无限递归问题

首先要明确区分拷贝与赋值概念,拷贝指的是用一个已经存在的变量来初始化一个新变量赋值指的是将一个已经存在的变量的值赋给另一个已存在的变量

其次c++规定自定义类型的拷贝必须调用拷贝构造函数来完成,因为会有深浅拷贝的问题。

清楚了这两点之后,如下图假设拷贝构造函数的形参不是引用会发生什么呢?

类和对象中篇——默认成员函数总结_第1张图片

 我们想用d1来初始化d2,于是调用拷贝构造函数,再函数调过程中需要将实参d1拷贝给形参date,但自定义类型的拷贝只能调用拷贝构造函数完成,于是又要调用拷贝构造函数将d1拷贝给date,再调用过程中又需要将实参d1拷贝给形参date,但是自定义类型的拷贝只能调用拷贝构造函数.....子子孙孙无穷匮也

深拷贝与浅拷贝

浅拷贝

浅拷贝只是简单地将一个对象的值复制给另一个对象。

这种拷贝有一个问题,如果拷贝对象是指针那么拷贝指针和源指针就会指向同一块空间。但往往我们需要的是两块独立的空间存放着相同的数据,而且拷贝指针和源指针都指向同一块可能会导致一系列的内存问题。

深拷贝

如果拷贝对象是指针,就先为目标指针开辟与源指针相同的空间,在依次将数据依次拷贝到目标指针。

如下图,将一个栈对象s1将拷贝给s2

 类和对象中篇——默认成员函数总结_第2张图片

 浅拷贝

类和对象中篇——默认成员函数总结_第3张图片

只简单的复制s1对象中的数据,但这样会导致s1对象中的指针和s2对象中的指针指向同一块空间,进而引发一系列的内存问题。

 深拷贝

类和对象中篇——默认成员函数总结_第4张图片

会开辟与开辟与源指针相同的空间,在依次将数据依次拷贝到目标指针。

注:一旦涉及资源申请,就必须要使用深拷贝。

运算符重载函数

介绍

运算符重载是具有特殊函数名的函数,它的引入是为了解决自定义类型不能使用运算符的问题,具体方法是通过函数来模拟运算符功能,在使用运算符的时候就自动调用该函数。

特性

  • 函数名字为关键字operator后面接需要重载的运算符符号
  • 运算符重载函数的参数个数必须与该运算符的操作数对应
  • 运算符重载函数必须有一个类类型的参数
//+=运算符重载函数
Date& operator+=(Date& x, Date& y) //函数参数必须与运算符操作数对应
{
    x.a = x.a + y.a;
    x.b = x.b + y.b;
}
  • 运算符重载函数的符号不可以无中生有,如:operator@
  • 运算符重载函数可以函数重载
  • 作为类成员函数重载时,其形参看起来比操作数数目少1,因为成员函数的第一个参数为隐藏的this指针
//+=运算符重载函数
Date& operator+=(Date& y) //类成员
{
    a = x.a + y.a;
    b = x.b + y.b;
}
  • 运算符重载函数有两种调用方式:函数调用,运算符操作(编译器会自动转换成函数调用)
void TestDate()
{
	Date s1;
	Date s2;
	operator+(s1, s2);  //全局函数调用方式
	//s1.operator+(s2);   成员函数调用方式
	s1 + s2;  //运算符操作, 编译器自动转换成operator+(s1, s2)
}
  • 并不是所有运算符重载函数都是默认成员函数,只有赋值重载和取地址重载才是默认成员函数
  • 以下5个操作符不能被重载:
1. .* 
2. :: 
3. sizeof
4. ?: 
5. .
前置运算符与后置运算符的重载

为了在运算符重载时区分前置运算符和后置运算符,C++规定在定义后置运算符时多定义一个int类型的参数以构成函数重载来区分,这个int类型的参数仅作为占位符并不使用它

// 前置++
Date& operator++()
{
    (*this) += 1;
    return *this;
}
//后置++
Date operator++(int)
{
    Date d1(*this);
    (*this) += 1;
    return d1;
}

4. 赋值运算符重载

介绍

赋值重载是运算符重载的一种形式,同时也属于默认成员函数的一种,用于相同类型的赋值

特性

  • 它的函数名为关键字operator后面接赋值运算符(=)
Date& operator=(Date& x) //赋值重载函数
{
	if (this != &x)
	{
		a = x.a;
		b = x.b;
	}
	return *(this);
}
  • 拷贝构造与赋值重载的区别在于,拷贝构造是用一个已经存在的对象来初始化一个新对象,赋值重载是将一个已经存在的对象的值赋给另一个已存在的对象
  • 赋值重载函数只能是类成员,(默认成员函数都只能在类域里面定义
  • 若类中没有定义赋值重载函数,编译器会自动生成一个赋值重载函数。编译器自己生成的赋值重载函数,对内置类型只能完成浅拷贝对自定义类型会去调用它的赋值重载函数
const成员

const成员在调用成员函数时会报错,原因是权限的放大, const成员地址的实际类型是const 类名* this,但接受的形参却是类名* const this,这就造成了权限的放大。

类和对象中篇——默认成员函数总结_第5张图片

 解决方法是将形参的类型修饰成const 类名* this,但this不能显示传递所以只能在()括号后面加上const这样就表示const 类名* this。

类和对象中篇——默认成员函数总结_第6张图片

注:()括号后面加上const,形参的实际类型是const 类名* const this

5/6. 取地址重载函数

介绍

取地址重载是运算符重载的一种形式,同时也属于默认成员函数的一种,用于对象的取地址

特性

  • 取地址重载有两个,普通对象取地址和const对象取地址。
//普通对象&地址重载
Date* operator&()
{
	return this;
}
//const对象&地址
const Date* operator&() const
{
	return this;
}
  • 这两个函数一般不需要自己实现使用编译器默认生成的就好,只有特殊情况,才需要自己实现,比如想让别人获取到指定的内容

你可能感兴趣的:(C++,c++,开发语言)