c++基础篇——类与对象入门(中)

Posted 东条希尔薇

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了c++基础篇——类与对象入门(中)相关的知识,希望对你有一定的参考价值。

作者介绍:

关于作者:东条希尔薇,一名喜欢编程的在校大学生
主攻方向:c++和linux
码云主页点我
本系列仓库直通车
作者CSDN主页地址


类的默认成员函数之构造函数

构造函数定义及其特性

如果我们这儿有一个日期类

class Date

public:
 void SetDate(int year, int month, int day)
 
 _year = year;
 _month = month;
 _day = day;
 

 void Display()
 
 cout <<_year<< "-" <<_month << "-"<< _day <<endl;
 
private:
 int _year;
 int _month;
 int _day;
;
int main()

 Date d1,d2;
 d1.SetDate(2018,5,1);
 d1.Display();

 Date d2;
 d2.SetDate(2018,7,1);
 d2.Display();
 return 0;

我们可以观察到,我们专门设置了一个初始化函数来对我们实例化出的对象进行赋值,但这种初始化函数不仅设置麻烦,而且我们在每次可能会忘记调用初始化函数,所以c++类中,为了我们的使用方便,提供了一个默认成员函数:构造函数

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

构造函数的表示方式如下:

Date();//没有返回值,函数名与类名必须相同

我们当然可以设计程序来测试一下构造函数的调用

class Date

public:

	Date()
	
		cout << "构造函数调用" << endl;//看这句话打印的时机
		_year = 2022;
		_month = 1;
		_day = 15;
	

	void Display()
	
		cout << _year << "-" << _month << "-" << _day << endl;
	
private:
	int _year;
	int _month;
	int _day;
;
int main()

	Date d1;
	d1.Display();
	return 0;

输出结果如下:


可以看出,构造函数在对象实例化时就会被自动调用,然后将成员变量根据指定值来设置我们实例化出的对象

构造函数是特殊的成员函数,需要注意的是,构造函数的虽然名称叫构造,但是需要注意的是构造函数的主要任务并不是开空间创建对象,而是初始化对象。

构造函数一般分为默认构造含参构造

而默认构造又可分为无参构造全缺省构造,含参构造分为半缺省构造含参构造

使用示例

class Date

public:

	Date()//无参构造
	
		cout << "构造函数调用" << endl;
		_year = 2022;
		_month = 1;
		_day = 15;
	
	Date(int year,int month,int day)//含参构造
	
		_year = year;
		_month = month;
		_day = day;
	

	void Display()
	
		cout << _year << "-" << _month << "-" << _day << endl;
	
private:
	int _year;
	int _month;
	int _day;
;

注意:无参和含参函数构成函数重载

使用

Date d1(2022,1,15);//调用含参构造
Date d2;//调用默认构造
Date d3();//注意,这个是一个返回Date类型,名为d3的函数声明,并不是函数的无参构造

构造函数的其它特性

在我们没有定义构造函数时,编译器会默认传一个默认构造函数,在用户定义了析构函数时,将不再生成默认构造函数

默认函数对内置类型(int,char)等变量不做处理

对自定义类型(class)会调用此自定义类型的构造函数

我们设计一个程序来验证一下

class A

public:
	A()
	
		cout << "A的析构函数" << endl;
	
;

class Date

public:
	//不给构造函数
	void Display()
	
		cout << _year << "-" << _month << "-" << _day << endl;
	
private:
	int _year;
	int _month;
	int _day;
	A a;
;
int main()

	Date d1;
	d1.Display();
	return 0;

程序输出如下


第二行的随机值,表示了默认析构不对其做出处理

但如果A里面没有默认构造函数,而是含参构造函数,程序会报错

析构函数

前面通过构造函数的学习,我们知道一个对象时怎么来的,那一个对象又是怎么没呢的?

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

而什么是需要资源呢?

其实就是我们在堆上开辟的空间

析构函数同样无返回值,且无参数,定义方式是~加上函数名

例如:~Date();

我们一般在什么场景使用析构函数呢?

当我们需要动态释放堆上空间的时候

class SeqList//顺序表类

public :
 	SeqList (int capacity = 10)
 	
 		_pData = (DataType*)malloc(capacity * sizeof(DataType));//开辟在堆上的空间
 		assert(_pData);

 		_size = 0;
 		_capacity = capacity;
 		

 	~SeqList()
 	
 	
 		if (_pData)
 		
 			free(_pData ); // 释放堆上的空间
 			_pData = NULL; // 将指针置为空
 			_capacity = 0;
 			_size = 0;
 		
 	
 

private :
 int* _pData ;
 size_t _size;
 size_t _capacity;
;

同样,析构函数有和构造函数类似的特性

  1. 系统会默认生成,默认的析构函数对内置类型不处理,对自定义类型调用其析构函数
  2. 对象生命周期结束时,C++编译系统系统自动调用析构函数

注意

因为变量进入栈区是按照先进先出的特性,所以后定义的变量会先会被析构

当某个对象的生命周期结束,将会自动调用其析构函数

拷贝构造入门

我们知道,我们在写作业的时候,如果写不出来的时候,我们就可以借鉴别人的作业,从而就有了两份一模一样的作业

那么在程序中我们能不能创建与某个对象一模一样的对象呢

就可以使用拷贝构造函数了

拷贝构造函数定义如下:

Date(const& date);

使用如下

Stu s1(...);
Stu s2(s1);

我们根据函数特征可以推断出,拷贝构造其实是构造函数的重载形式,参数类型必须是本类型的引用

那么,为什么要传引用呢

因为函数的传值拷贝要调用类的拷贝构造函数,如果我们传值的话,函数就会无限递归下去

而且,为了防止被复制的对象被意外修改,我们都要加一个const修饰我们的参数

与前面两个函数一样,若我们没有定义拷贝构造,系统将会自动生成一个拷贝构造函数

默认拷贝构造函数对内置类型做按字节拷贝(类似memcpy),对自定义类型调用其拷贝构造函数

注意,默认拷贝构造函数是执行浅拷贝
**需要深拷贝的例子:**动态开辟的资源

class String

public:
 String(const char* str = "jack")
 
 _str = (char*)malloc(strlen(str) + 1);
 strcpy(_str, str);
 
 ~String()
 
 cout << "~String()" << endl;
 free(_str);
 
private:
 char* _str;
;

深浅拷贝问题我们在后面的章节中介绍

运算符重载

为了我们人的使用和理解方便,c++允许我们将内置的运算符号重载到我们的自定义类型中,对某个自定义类型,赋予符号计算方法方便我们对自定义类型进行运算,本质是一种特殊的函数

内置符号不会直接计算我们的自定义类型
不能重载非内置符号,比如@,$等
对于二目运算符,如果我们少传了一个参数,将会自动传入this指针

定义方式:返回类型+operator+重载符号+参数列表

例如:

bool operator>(const Date& d);

例如我们有一个日期类,我们要进行比较大小的操作

class Date

public:
 Date(int year = 1900, int month = 1, int day = 1)
 
 _year = year;
 _month = month;
 _day = day;
 
private:
 int _year;
 int _month;
 int _day;
;

我们可以在类中进行这么定义

bool operator>(const Date& d)

	if (_year < d._year)
	
		return true;
	
	else if (_year == d._year && _month < d._month)
	
		return true;
	
	else if (_year == d._year && _month == d._month && _day < d._day)
	
		return true;
	
	return false;

在主函数中这两种使用方法等价

d1>d2;
d1.operator>(d2);

赋值运算符

我们怎么完成赋值的操作?

首先,如果我们要实现连续赋值,必须要返回Date&,&可减少拷贝

我们可以在函数体内返回*this

Date& operator=(const Date& d)

	this->day=d.day;
	this->month=d.month;
	this->yeat=d.year;
	return *this;

如果我们有时候写成了d=d(本身赋值怎么办?)可以不用进行操作

Date& operator=(const Date& d)

	if(this!=&d)
	
		this->day=d.day;
		this->month=d.month;
		this->yeat=d.year;
	

	return *this;

一个类如果没有显式定义赋值运算符重载,编译器也会生成一个,完成对象按字节序的值拷贝。

以上是关于c++基础篇——类与对象入门(中)的主要内容,如果未能解决你的问题,请参考以下文章

c++基础篇——类与对象入门(下)

C++入门篇之类和对象上

C++从入门到入土第四篇:类与对象(中篇)

C++从入门到入土第三篇:类与对象(上篇)

C++从入门到入土第五篇:类与对象(下篇)

C++基础总结:C++ 类与对象初探——继承和重载