当前位置: 首页 > news >正文

类的默认成员函数

默认成员函数就是用户没有显式实现,编译器会⾃动⽣成的成员函数称为默认成员函数。⼀个类,我 们不写的情况下编译器会默认⽣成以下6个默认成员函数。

两点要求:

第⼀:我们不写时,编译器默认⽣成的函数⾏为是什么,是否满⾜我们的需求。
第⼆:编译器默认⽣成的函数不满⾜我们的需求,我们需要⾃⼰实现,那么如何⾃⼰实现?

六个默认成员函数

  • 1.构造函数

  • 2.析构函数

  • 3.拷贝构造函数

  • 4.赋值运算符重载

  • 5.取地址运算符重载

  • 6.const成员函数

构造函数

构造函数是特殊的成员函数,需要注意的是,构造函数虽然名称叫构造,但是构造函数的主要任务并 不是开空间创建对象(我们常使⽤的局部对象是栈帧创建时,空间就开好了),⽽是对象实例化时初始化 对象。

 构造函数的特点

  • 函数名与类名相同。
  • 无返回值。 (返回值啥都不需要给,也不需要写void,不要纠结,C++规定如此)
  • 对象实例化时系统会⾃动调⽤对应的构造函数
  • 构造函数可以重载
  • 如果类中没有显式定义构造函数,则C++编译器会⾃动⽣成⼀个⽆参的默认构造函数,⼀旦用户显式定义编译器将不再生成。
  • 无参构造函数、全缺省构造函数、我们不写构造时编译器默认⽣成的构造函数,都叫做默认构造函 数。但是这三个函数有且只有⼀个存在,不能同时存在。⽆参构造函数和全缺省构造函数虽然构成 函数重载,但是调⽤时会存在歧义。要注意很多同学会认为默认构造函数是编译器默认⽣成那个叫 默认构造,实际上⽆参构造函数、全缺省构造函数也是默认构造,总结下就是不传实参就可以调用的构造就叫默认构造。

  •  我们不写,编译器默认⽣成的构造,对内置类型成员变量的初始化没有要求,也就是说是是否初始化是不确定的,看编译器。对于⾃定义类型成员变量,要求调⽤这个成员变量的默认构造函数初始化。如果这个成员变量,没有默认构造函数,那么就会报错,我们要初始化这个成员变量,需要⽤初始化列表才能解决,初始化列表,我们下个章节再细细讲解。

内置类型构造函数代码:

#include<iostream>
using namespace std;
class Date{
public:

	//无参构造函数
	Date()
	{
		_year = 2024;
		_month = 1;
		_day = 1;
	}


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

	//全缺省构造函数
	Date(int year = 2024, int month = 1, int day = 1)
	{
		_year = year;
		_month = month;
		_day = day;
	}
private:
	int _year;
	int _month;
	int _day;
};

对于内置类型,编译器不要求手动实现其构造函数,若无构造函数,编译器会自动生成构造函数,但不会初始化。 

 自定义类型构造函数:

#include<iostream>
using namespace std;
typedef int STDataType;
class Stack
{
public:
	Stack(int n = 4)
	{
		_a = (STDataType*)malloc(sizeof(STDataType) * n);
		if (nullptr == _a)
		{
			perror("malloc申请空间失败");
			return;
		}
		_capacity = n;
		_top = 0;
	}
private:
	STDataType * _a;
	size_t _capacity;
	size_t _top;
};
// 两个Stack实现队列
class MyQueue
{
public:
	//编译器默认⽣成MyQueue的构造函数调⽤了Stack的构造,完成了两个成员的初始化
private:
	Stack pushst;
	Stack popst;
};

对于自定义类型Stack ,需要显示写出构造函数,对于用Stack实现的MyQueue,它会自动调用Stack的构造函数,不需要手动实现构造函数。


析构函数

析构函数与构造函数功能相反,析构函数不是完成对对象本⾝的销毁,比如局部对象是存在栈帧的, 函数结束栈帧销毁,他就释放了,不需要我们管,C++规定对象在销毁时会自动调用析构函数,完成对象中资源的清理释放工作。析构函数的功能类比我们之前Stack实现的Destroy功能,而像Date没有 Destroy,其实就是没有资源需要释放,所以严格说Date是不需要析构函数的。

析构函数的特点: 

  • 析构函数名是在类名前加上字符 ~。 
  • 无参数无返回值。 (这里跟构造类似,也不需要加void)
  • ⼀个类只能有⼀个析构函数。若未显式定义,系统会自动生成默认的析构函数。 
  •  对象生命周期结束时,系统会自动调用析构函数。
  • 跟构造函数类似,我们不写编译器自动生成的析构函数对内置类型成员不做处理,⾃定类型成员会调用他的析构函数。
  •  还需要注意的是我们显示写析构函数,对于自定义类型成员也会调用他的析构,也就是说⾃定义类型成员无论什么情况都会自动调用析构函数。
  •  
    如果类中没有申请资源时,析构函数可以不写,直接使用编译器生成的默认析构函数,如Date;如 果默认生成的析构就可以用,也就不需要显示写析构,如MyQueue;但是有资源申请时,一定要自己写析构,否则会造成资源泄漏,如Stack。
  •  ⼀个局部域的多个对象,C++规定后定义的先析构。

内置类型的析构函数 

#include<iostream>
using namespace std;
class Date{
public:
	~Date()
	{
		_year = 0;
		_month = 0;
		_day = 0;
	}
private:
	int _year;
	int _month;
	int _day;
};

内置类型即使不写,编译器也会自动生成一个。 


  自定义类型的析构函数

#include<iostream>
using namespace std;
typedef int STDataType;
class Stack
{
public:
	Stack(int n = 4)
	{
		_a = (STDataType*)malloc(sizeof(STDataType) * n);
		if (nullptr == _a)
		{
			perror("malloc申请空间失败");
			return;
		}
		_capacity = n;
		_top = 0;
	}

  // 析构函数,释放资源
 ~Stack()
{
	cout << "~Stack()" << endl;
	free(_a);
	_a = nullptr;
	_top = _capacity = 0;
}
private:
	STDataType * _a;
	size_t _capacity;
	size_t _top;
};
// 两个Stack实现队列
class MyQueue
{
public:


	//编译器默认⽣成MyQueue的析构函数调⽤了Stack的析构,释放的Stack内部的资源
		// 显⽰写析构,也会⾃动调⽤Stack的析构
		/*~MyQueue()
		{}*/

	//编译器默认⽣成MyQueue的构造函数调⽤了Stack的构造,完成了两个成员的初始化
private:
	Stack pushst;
	Stack popst;
};

对于自定义类型的析构函数,像Stack类的有资源进行释放,因此需要手动实现。

MyQueue类,由Stack类和内置类型组成,不需要手动实现,进行析构时会直接调用Stack类的析构函数。


 拷贝构造函数

 如果⼀个构造函数的第⼀个参数是自身类类型的引用,且任何额外的参数都有默认值,则此构造函数 也叫做拷贝构造函数,也就是说拷贝构造是⼀个特殊的构造函数。

拷贝构造函数的特点 

  • 拷贝构造函数是构造函数的一个重载。
  • 拷贝构造函数的第⼀个参数必须是类类型对象的引用,使用传值方式编译器直接报错,因为语法逻 辑上会引发无穷递归调用。 拷贝构造函数也可以多个参数,但是第⼀个参数必须是类类型对象的引用,后面的参数必须有缺省值。
  • C++规定自定义类型对象进行拷贝行为必须调用拷贝构造,所以这里自定义类型传值传参和传值返回都会调用拷贝构造完成。
  • 若未显式定义拷贝构造,编译器会生成自动生成拷贝构造函数。自动生成的拷贝构造对内置类型成员变量会完成值拷贝/浅拷贝(⼀个字节⼀个字节的拷贝),对自定义类型成员变量会调用他的拷贝构造。
  •  像Date这样的类成员变量全是内置类型且没有指向什么资源,编译器自动生成的拷贝构造就可以完 成需要的拷贝,所以不需要我们显示实现拷贝构造。像Stack这样的类,虽然也都是内置类型,但 是_a指向了资源,编译器自动生成的拷贝构造完成的值拷贝/浅拷贝不符合我们的求,所以需要 我们自己实现深拷贝(对指向的资源也进行拷贝)。像MyQueue这样的类型内部主要是自定义类型 Stack成员,编译器自动生成的拷贝构造会调⽤Stack的拷贝构造,也不需要我们显示实现 MyQueue的拷贝构造。这⾥还有⼀个小技巧,如果⼀个类显示实现了析构并释放资源,那么他就 需要显示写拷贝构造,否则就不需要。
  • 传值返回会产生一个临时对象调用拷贝构造,传值引用返回,返回的是返回对象的别名(引用),没 有产生拷贝。 但是如果返回对象是⼀个当前函数局部域的局部对象,函数结束就销毁了,那么使用引用返回是有问题的,这时的引用相当于⼀个野引用,类似一个野指针⼀样。 传引用返回可以减少拷贝,但是一定要确保返回对象,在当前函数结束后还在,才能用引用返回。

 内置类型的拷贝构造:

#include<iostream>
using namespace std;
class Date{
public:
	// 拷贝构造函数
	Date(const Date& d)

	{
		_year = d._year;
		_month = d._month;
		_day = d._day;
		}

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


void Func1(Date d)
{
cout << &d << endl;
d.Print();
}


Date& Func2()
{
Date tmp(2024, 7, 5);
tmp.Print();
return tmp;
}


int main()
{
    // C++规定⾃定义类型对象进⾏拷⻉⾏为必须调⽤拷⻉构造,所以这⾥传值传参要调⽤拷⻉构造
    Date d1(2024, 7, 5);




    // 所以这⾥的d1传值传参给d要调⽤拷⻉构造完成拷⻉,传引⽤传参可以较少这⾥的拷⻉
    Func1(d1);
    cout << &d1 << endl;


    // 这⾥可以完成拷⻉,但是不是拷⻉构造,只是⼀个普通的构造
    Date d2(&d1);
    d1.Print();
    d2.Print();


    //这样写才是拷⻉构造,通过同类型的对象初始化构造,⽽不是指针
    Date d3(d1);
    d2.Print();


    // 也可以这样写,这⾥也是拷⻉构造
    Date d4 = d1;
    d2.Print();


    // Func2返回了⼀个局部对象tmp的引⽤作为返回值
    // Func2函数结束,tmp对象就销毁了,相当于了⼀个野引⽤
    Date ret = Func2();
    ret.Print();

    return 0;    
}

自定义类型的拷贝构造函数

Stack(const Stack& st)
{
    // 需要对_a指向资源创建同样⼤的资源再拷⻉值
    _a = (STDataType*)malloc(sizeof(STDataType) * st._capacity);

    if (nullptr == _a)
    {
        perror("malloc申请空间失败!!!");
        return;
    }
    memcpy(_a, st._a, sizeof(STDataType) * st._top);
    _top = st._top;
    _capacity = st._capacity;
}

 为什么进行传值传参时会无限递归

C++规定自定义类型对象进行拷贝行为必须调用拷贝构造,所以这里自定义类型传值传参和传值返回都会调用拷贝构造完成。

 深\浅拷贝

 

 浅拷贝时改变一个内容的值时会影响另一个对象,因此自定义类型应采用深拷贝,不能靠编译器自己生成。

相关文章:

  • 滤波---卡尔曼滤波
  • 《HarmonyOS Next开发进阶:打造功能完备的Todo应用华章》
  • USB有驱ID卡读卡器C#小程序开发
  • 【区块链安全 | 第二十三篇】单位和全局可用变量(一)
  • 基于Python的Django框架的手机购物商城管理系统
  • xcode项目配置只能竖屏显示
  • 【论文阅读】Dynamic Adversarial Patch for Evading Object Detection Models
  • Mac 本地化部署 dify
  • 031-valgrind
  • 栈迁移学习
  • 【Music】读取文件夹及子文件夹所有mp3按照bpm拷贝到不同文件夹
  • reuseport socket 查找的一致性 hash
  • CET-4增量表
  • 产教融合|暴雨技术专家执裁江苏省职业院校技能大赛
  • Android学习总结之RecyclerView补充篇
  • 项目中静态通讯方案
  • KingbaseES之列级强访问控制
  • 【LeetCode回溯】17. 电话号码的字母组合
  • Scikit-learn使用指南
  • C语言实现查表8位SAE J1850 CRC
  • WordPress文章开启评论/网站功能优化
  • 动态网站用什么做的/推广网站的方法
  • 建站之星导出网站/北京seo
  • 网站主服务器地址/网络营销策划书步骤
  • 高端网站建设公司有哪些项目/百度竞价推广什么意思
  • 淄博网站建设0533cl/中国优秀网页设计案例