类与对象(二)

文章详细介绍了C++中的构造函数、析构函数、默认成员函数,以及拷贝构造和赋值运算符的重载,强调了它们在对象初始化、内存管理以及浅拷贝的重要性。

构造函数

1.引入

class stu
{
public:
	void Init_Num()
	{
		_num = 0;
	}
	int _num;
};

int main()
{
	stu a;
	a.Init_Num();
	a._num++;
	cout << a._num << endl;
	return 0;
}

类似代码,初始化是必须的,忘了初始化会引起不必要的麻烦,为保证数据成员都有一个初始值,c++创立构造函数.构造函数并非是开辟空间,而是初始化对象,在对象的生命周期里只会调用一次

class stu
{
public:
	stu()
	{
		_num = 0;
	}
	int _num;
};

int main()
{
	stu a;
	a._num++;
	cout << a._num << endl;
	return 0;
}

构造函数名字与类名相同,无返回值类型,这里调用是在创建对象的同时已经进行初始化,为什么不用如下形式呢?

stu a();

上面形式也可以是定义函数,加上括号这种会使编译器无法辨别你在定义函数a还是在创建对象a
构造函数传参和调用如下:

class stu
{
public:
	stu(int n)
	{
		_num = n;
	}
	int _num;
};

int main()
{
	stu a(0);
	a._num++;
	cout << a._num << endl;
	return 0;
}

2.特性

(1)函数名与类名相同
(2)无返回值
(3)对象实例化时编译器会自动调用构造函数
(4)构造函数可以重载
如下:

using namespace std;

class stu
{
public:
	stu(int n)
	{
		_num = n;
	}
	stu()
	{
		_num = 0;
	}
	//这里可以用缺省参数来简化
	/*
	stu(int n = 0)
	{
		_num = n;
	}
	*/
	int _num;
};

int main()
{
	stu a(3);//含参构造函数调用
	stu b;//含无参构造函数调用
	cout << a._num << endl;
	cout << b._num << endl;
	return 0;
}

析构函数

析构函数: 并非是对对象的销毁,而是在销毁时调用析构函数完成对对象资源的清理

1.特性

(1)析构函数名是在类名前加"~"
(2)无参数无返回值
(3)一个类只有一个析构函数,不能重载
(4)对象生命周期结束,会自动调用析构函数


class room
{
public:
	room(int n)
	{
		_name = (char*)malloc(sizeof(char) * n);
		_num = 0;
		_space = 0;
	}
	~room()
	{
		free(_name);
	}
private:
	int _num;
	int _space;
	char* _name;
};

int main()
{
	room a(10);

	return 0;
}

上图所示,申请一个动态的_name数组,可是申请空间最害怕忘记释放而内存泄漏,或者觉得释放麻烦,析构函数就会在对象生命周期结束之前,进入析构函数(上图的~room函数(析构函数)对申请空间进行释放)

默认成员函数

默认成员函数:用户不用实现,编译器会自动生成的成员函数叫作默认成员函数
拿构造函数来说,编译器却不总会初始化,如下:

class room
{
public:
	void Print()
	{
		cout << _num << endl;
		cout << _space << endl;
	}
	int _num;
	int _space;
};

int main()
{
	room a;
	a.Print();//此处打印皆是随机值,可是编译器不是初始化了嘛
	return 0;
}

c++把类型分为内置类型和自定义类型,内置类型可以理解为自带的类型,如int,char…也包括指针,对于内置类型,在默认构造函数中不进行初始化,而对于自定义类型会自动调用他的默认构造函数(不用传参数的构造->无参的,全缺省的,编译器自动生成的),如下:

class room
{
public:
	room(int num = 0,int space = 0)
	{
		_num = num;
		_space = space;
	}
	int _num;
	int _space;
};

class tem
{
	room a;
	room b;
};

int main()
{
	tem t;//实例化类tem,tem内部的类room a和room b(属于自定义类型),会调用类room的默认构造函数,初始化a和b
	//如下图
	return 0;
}

在这里插入图片描述

析构函数在这里与构造函数类似,默认生成析构函数,对于内置类型成员不做处理,对于自定义类型成员会调用他的析构函数


class room
{
public:
	room(int num = 0,int space = 0)//构造函数
	{
		_num = num;
		_space = space;
	}
	~room()//析构函数
	{
		cout << "~room" << endl;
	}
	int _num;
	int _space;
};

class tem
{
	room a;
	room b;
};

int main()
{
	tem t;
	return 0;
}

此处会打印两个~room,调用两次析构函数,在tem的默认析构函数会对类对象a和b调用他们的析构函数
上文提过对于内置类型,编译器是不进行处理的,如下:


class room
{
public:
	room(int num = 0,int space = 0)
	{
		_num = num;
		_space = space;
	}
	~room()
	{
		cout << "~room" << endl;
	}
	int _num;
	int _space;
	int s;//此处新添的变量s没有在构造函数被初始化
};

class tem
{
	room a;
	room b;
};

int main()
{
	tem t;
	return 0;
}

如下:
在这里插入图片描述
要想对这里的s进行初始化,可以在类room中s的声明位置给出缺省值,如下:

class room
{
public:
	room(int num = 0,int space = 0)
	{
		_num = num;
		_space = space;
	}
	~room() 
	{
		cout << "~room" << endl;
	}
	int _num;
	int _space;
	int s = 0;//给出缺省值
};

给出缺省值,s被初始化

默认生成拷贝构造和赋值重载:

  1. 内置类型浅拷贝,一个一个字节拷贝
  2. 自定义类型,去调用成员的拷贝构造或赋值重载

拷贝构造

拷贝构造函数是一种特殊的构造函数.只有一个形参,是这个类类型的对象的引用

1.特征

  1. 拷贝函数是构造函数一种重载形式
  2. 拷贝构造函数的参数只有一个且必须是这个类类型对象的引用,如果采用传值传参的方式会报错

class room
{
public: 
	room(int num = 0)
	{
		_num = num;
	}
	room(room tem)//这里采用传值传参会报错,会造成无限,
	{
		_num = tem._num;
	}
	int _num;
};

int main()
{
	room a(1);
	room b(a);
	return 0;
}
  1. 编译器对于内置类型,传参通常采用值传递的方式,在函数调用时,编译器会创建一个新的内存空间来存储传入的参数,并将实际参数的值复制到这个新的内存空间中;对于自定义类型,需要调用拷贝构造函数
    内置类型采用浅拷贝,如果自定义类型采用浅拷贝,会导致新数据与原来的数据公用一块区域,并非各自拥有独立的空间
    如果自定义类型采用传值传参的方式,如下:
class room
{
public:
	room(int num = 0)
	{
		_num = num;
	}
	room(room tem)//报错
	{
		_num = tem._num;
	}
	int _num;
};

int main()
{
	room a(1);
	room b(a);
	return 0;
}

出现上述情况会造成无穷递归,你在将实参传向形参的过程中,因为传的是自定义类型,采用拷贝构造,而拷贝构造又需要传参,传参又是自定义类型,又需要拷贝构造,而拷贝构造又需要传参,传参还是自定义类型,这样一直无穷拷贝,形成无限递归的情况
什么情况下需要实现拷贝构造?
自己实现析构函数释放空间,就需要实现拷贝构造

赋值运算符重载

1.运算符重载

函数声明:
返回值类型  operator操作符(参数)
如下,

class room
{
public:
	room(int num = 0)
	{
		_num = num;
	}
	room(room& tem)
	{
		_num = tem._num;
	}
	int _num;
};
bool operator>(const room& x, const room& y)
//bool值作为返回值,operator>,">"是操作符
{
	return x._num > y._num;//为真返回1(布尔类型性质,真为1,假为0),为假返回0
}
int main()
{
	room a(1);
	room b(0);
	cout << (operator>(a, b)) << endl;
	cout << (a > b) << endl;
	/*
	两种表示方式(operator>(a, b))和(a > b),个人喜欢第二种,第一种感觉就行正常调用函数,只是命名后可以加
	操作符,大家根据个人爱好选择
	*/
	return 0;
}
  1. operator后面加的是操作符,不要加其他的符号,如#,@
  2. 参数必须有一个类类型的参数
  3. 当数据对对外不能访问,只能在类内部实现时:
class room
{
public:
	room(int num = 0)
	{
		_num = num;
	}
	room(room& tem)
	{
		_num = tem._num;
	}
	bool operator>(const room& y)
	{
		return this->_num > y._num;//使用this指针更好的理解_num是当前对象
		//return _num > y._num;(省略this指针)
	}
private:
	int _num;
};
int main()
{
	room a(1);
	room b(0);
	cout << (a.operator>(b)) << endl;
	cout << (a > b) << endl;
	return 0;
}
.*
::
sizeof
?:
.

以上五个操作符不可重载

2.赋值运算符重载

class room
{
public:
	room(int num = 0)
	{
		_num = num;
	}
	room(room& tem)
	{
		_num = tem._num;
	}
	/*
	赋值运算符重载如下:
	参数类型和返回值类型建议都用引用,之前说过,引用可以避免传值传参开辟新空间接收而浪费资源,返回值同理
	*/
	room& operator=(const room& t)
	{
		_num = t._num;
		return *this;//返回值使用*this也就是该类room,是为了应对连续赋值的情况
		//例如:a=b=c,返回值与接收类型不同会使赋值失败
	}
	void Print()
	{
		cout << _num << endl;
	}
private:
	int _num;
};

int main()
{
	room a(1);
	room b(0);
	b.Print();
	b = a;
	b.Print();
	return 0;
}
评论 15
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

图灵的六月

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值