手动实现类中的默认函数【构造函数,析构函数,复制构造函数、赋值操作符重载】

目录

类中的默认函数

1. 构造函数

2. 析构函数

3. 复制构造函数

4. 赋值操作符重载函数

5和6. 重载取址运算符函数和重载取址运算符const函数

7和8. 移动构造函数和重载移动赋值操作符函数

样例代码全部


类中的默认函数

在C++中,一个类有八个默认函数:

  1. 默认构造函数;
  2. 默认析构函数;
  3. 默认复制构造函数;
  4. 默认重载赋值运算符函数;
  5. 默认重载取址运算符函数;
  6. 默认重载取址运算符const函数;
  7. 默认移动构造函数(C++11);
  8. 默认重载移动赋值操作符函数(C++11)。

假设我们现在有如下一个学生类。

  • string类型的名字变量
  • int类型的年龄
  • int类型的科目数量
  • 指针类型的成绩
  • 还有一个成员函数show(),可以输出这个学生的基本信息。
class stu{
public:
	void show();  //输出学生的基本信息
private:
	string name;  //姓名
	int age;   //年龄
	int num;   //科目数量
	int *src;  //num科的成绩分别为src[i]
};

void stu::show(){
	cout<<name<<" "<<age<<endl;
	cout<<num<<" : ";
	for (int i=0; i<num; i++) 
		cout<<src[i]<<" ";
	cout<<endl;
}

我们用这个例子来手动实现类中的默认函数。

 

1. 构造函数

  • 构造函数是一种特殊的成员函数,与其他成员函数不同,不需要用户调用它,在建立对象时自动执行。
  • 构造函数的名字与类名相同,没有类型,没有返回值。构造函数一般声明为public。
  • 构造函数可以重载,提倡用带默认参数的构造函数,比较省事。

构造函数的主要作用是初始化,我们为学生类定义如下构造函数:其含有默认参数,并且对src进行内存空间的动态分配。

	stu(const char* a="std",int b=20, int n=3){//默认名字是std 默认20岁 科目数量为3
		name=a;
		age=b;
		num=n;
		src=new int[n];
		for (int i=0; i<n; i++) src[i]=100;  //这里假设每科成绩都为100(当然也可以自己写入)
	}

利用构造函数对类对象进行初始化及构造函数的重载

 

2. 析构函数

析构函数和构造函数一样:没有返回值,且不带参数。在类名的函数名前加一个取反运算符~即可。

析构函数可以重载吗?

  • 不能,因为无参无返回值无类型

什么时候需要析构函数?

  • 当我们的构造函数中有动态内存分配时,就一定要自己实现析构函数来释放这些空间,否则会造成内存泄漏。
  • 文件占用
  • 网络断开

在继承中,如果子类有申请内存,父类无法调用子类的析构函数,这时父类需要使用虚析构。(有的人也习惯把所有的析构函数写为虚函数)

哪些函数不可以成为虚函数?构造函数和析构函数可以是虚函数吗?

在本例中,因为src是动态分配的内存,所以必须实现析构函数来释放申请的空间。

	~stu(){
		if (src!=NULL) 
			delete []src;
		src=NULL;
	}

注意:delete指针之后,要把指针设为NULL。因为我们在删除一个指针之后,编译器只会释放该指针所指向的内存空间,而不会删除这个指针本身。空间被回收再分配,会出现两个指针指向同一地址的情况。

解析C++中 new和delete 以及 malloc和free 的使用方法和区别

 

3. 复制构造函数

复制构造函数的调用分以下三种情况:

  • 一个对象以值传递的方式传入函数体
  • 一个对象以值传递的方式从函数返回
  • 一个对象需要通过另外一个对象进行初始化。

默认的复制构造函数是浅拷贝的,它把对象里的值完全复制给另一个对象。

在本例中,浅拷贝不能实现我们的需求,因为src是一个指针,如果仅仅是拷贝地址的话,会让两个对象指向同一堆中的内存。在析构时,会产生错误。

所以我们需要自己写复制构造函数来实现深拷贝。

深拷贝和浅拷贝 默认拷贝构造函数和自定义拷贝构造函数【有代码实例】

	stu(const stu& A){
		name=A.name;
		age=A.age;
		num=A.num;
		src=new int[num];
		for (int i=0; i<num; i++)
			src[i]=A.src[i];
	}

这里说明一下复制构造函数的参数问题。

为什么要用引用?
可能你的第一反应用引用是为了减少一次内存拷贝。其实不然。
我们都知道参数的值传递需要用到复制构造函数,试想如果复制构造函数的参数传递也是值传递方式,那么就会无限递归下去,所以必须使用地址传递(也就是引用)。
学会了引用,再也不用担心令人烦恼的C++指针了!!!

为什么要用const?
不加const,编译器也不会报错。但是为了防止对引用类型参数值的意外修改,一般都加上const。

 

4. 赋值操作符重载函数

如果自定义了复制构造函数,那也必须重载赋值操作符。

复制构造函数是用已存在的对象来初始化新对象。而赋值运算符是使用已存在的对象赋值给相同类型的已存在对象

其流程是:

  1. 判断是否为自赋值
  2. 释放掉旧的堆空间
  3. 开辟新的空间
  4. 将要赋值的内容拷贝到被赋值的对象中

其参数为const引用,防止修改实参;返回值也为引用,目的是实现链式表达式。

	stu& operator=(const stu& A){
		if (this!=&A){
			name=A.name;
			age=A.age;
			num=A.num;
			if (src!=NULL) delete src;  //释放旧的堆空间
			src=new int[num];
			for (int i=0; i<num; i++)
				src[i]=A.src[i];
		}
		return *this;
	} 

 

5和6. 重载取址运算符函数和重载取址运算符const函数

重载取址运算符函数没有参数

如果没有显式定义,编译器会自动生成默认的重载取址运算符函数,函数内部直接return this

这两个运算符一般不需要重载,使用编译器生成的默认取地址的重载即可,只有特殊情况,才需要重载(比如想让别人获取到指定的内容

	stu* operator&(){//重载取址运算符函数
        return this ;
    }
    
    const stu* operator&()const{//重载取址运算符const函数
        return this ;
    }

 

7和8. 移动构造函数和重载移动赋值操作符函数

  1.C++11 新增move语义:源对象资源的控制权全部交给目标对象,可以将原对象移动到新对象, 用于a初始化b后,就将a析构的情况;

  2.移动构造函数的参数和拷贝构造函数不同,拷贝构造函数的参数是一个左值引用,但是移动构造函数的初值是一个右值引用;

  3.临时对象即将消亡,并且它里面的资源是需要被再利用的,这个时候就可以使用移动构造。移动构造可以减少不必要的复制,带来性能上的提升。

左值右值,左值引用和右值引用及其用途

 

样例代码全部

#include<iostream>
using namespace std;

class stu{
public:
	void show();
	
	//构造函数 
	stu(const char* a="std",int b=20, int n=3){
		name=a;
		age=b;
		num=n;
		src=new int[n];
		for (int i=0; i<n; i++) src[i]=100;
	}
	
	//析构函数 
	~stu(){
		if (src!=NULL) 
			delete []src;
		src=NULL;
	}
	
	//复制构造函数 
	stu(const stu& A){
		name=A.name;
		age=A.age;
		num=A.num;
		src=new int[num];
		for (int i=0; i<num; i++)
			src[i]=A.src[i];
	}
	
	//重载赋值操作符函数 
	stu& operator=(const stu& A){
		if (this!=&A){
			name=A.name;
			age=A.age;
			num=A.num;
			if (src!=NULL) delete src;
			src=new int[num];
			for (int i=0; i<num; i++)
				src[i]=A.src[i];
		}
		return *this;
	} 
	
	stu* operator&(){//重载取址运算符函数
        return this ;
    }
    
    const stu* operator&()const{//重载取址运算符const函数
        return this ;
    }
	
private:
	string name;
	int age;
	int num;
	int *src;
};

void stu::show(){
	cout<<name<<" "<<age<<endl;
	cout<<num<<" : ";
	for (int i=0; i<num; i++) 
		cout<<src[i]<<" ";
	cout<<endl;
}

int main(){
	
	stu A("Sam",16,10); //构造函数 
	A.show();
	
	stu B=A; //复制构造函数 
	B.show();
	
	stu C; //构造函数 
	C.show(); 
	C=B=A; //赋值操作符重载函数 
	B.show();
	
	//对象消亡自动执行析构函数 
	return 0; 
}

 

 

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值