C++基础三类和对象(中篇)

Posted 大家好我叫张同学

tags:

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

文章目录


1.类的6个默认成员函数

如果一个类中什么成员都没有,简称为空类。空类中什么都没有吗?并不是的,任何一个类在我们不写的情况下,都会自动生成下面6个默认成员函数。

 class Date  ;

2.构造函数

2.1 概念

构造函数:在对象构造时调用的函数,这个函数完成初始化工作

#include<iostream>
using namespace std;
class Date

public:
	//void Display(Date* this)
	void Display()
	
		cout << _year << "-" << _month << "-" << _day << endl;
		//cout << this->_year << "-" << this->_month << "-" <<this-> _day << endl;
	
	//void SetDate(Date* this,int year,int month,int day)
	void SetDate(int year, int month, int day)
	
		_year = year;//this->_year = year;
		_month = month;//this->_month = year;
		_day = day;//this->_day = year;
	
private:
	int _year;//年
	int _month;//月
	int _day;//日
;
int main()

	Date d1, d2;
	d1.SetDate(2021, 8, 19);//d1.SetDate(&d1,2021,8,19)
	d2.SetDate(2021, 8, 20);//d2.SetDate(&d2,2021,8,19)
	d1.Display();//d1.Display(&d1)
	d2.Display();//d2.Display(&d2)
	return 0;

对于Date类,可以通过SetDate公有的方法给对象设置内容,但是如果每次创建对象都调用该方法设置信息,未免有点麻烦,那能否在对象创建时,就将信息设置进去呢?

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

2.2 特性

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

其特征如下:

1.函数名与类名相同

2.无返回值。

3.对象实例化时编译器自动调用对应的构造函数

4.构造函数可以重载。

#include<iostream>
using namespace std;
class Date

public:
	//void Display(Date* this)
	void Display()
	
		cout << _year << "-" << _month << "-" << _day << endl;
		//cout << this->_year << "-" << this->_month << "-" <<this-> _day << endl;
	
	//void SetDate(Date* this,int year,int month,int day)
	void SetDate(int year, int month, int day)
	
		_year = year;//this->_year = year;
		_month = month;//this->_month = year;
		_day = day;//this->_day = year;
	
	//构造函数 相当于SetDate,初始化,但是由程序自动调用,不需要我们去调用
	//1.无参数构造函数
	Date() 
		_year = 0;
		_month = 1;
		_day = 1;
	
	//2.带参数构成函数    无参和带参两种方式均可
	Date(int year, int month, int day) 
		_year = year;
		_month = month;
		_day = day;
	
private:
	int _year;//年
	int _month;//月
	int _day;//日
;
int main()

	Date d1(2021, 8, 21);
	d1.Display();
	d1.SetDate(2021, 10, 1);
	d1.Display();

	Date d2;
	d2.Display();

	//Date d1;//调用无参构造函数 注意后面不能带(),否则变成函数声明了
	//Date d2(2015, 1, 1);//调用带参的构造函数

	//d1.SetDate(2021, 8, 19);//d1.SetDate(&d1,2021,8,19)
	//d2.SetDate(2021, 8, 20);//d2.SetDate(&d2,2021,8,19)
	//d1.Display();//d1.Display(&d1)
	//d2.Display();//d2.Display(&d2)
	return 0;

Date d2;//后面不能加(),d2(),这是语法规定的

5.如果类中没有显式定义构造函数,则C++编译器会自动生成一个无参的默认构造函数,一旦用户显式定义编译器将不再生成。

这句话的意思就是”如果我们定义了构造函数,那么C++编译器就会使用我们定义的构造函数来初始化新建的对象;如果我们没有定义构造函数,那么C++编译器会自动生成一个没有参数的默认构造函数,然后用这个默认构造函数去初始化新建的对象。”

看起来编译器默认构造函数啥也没干,还是随机值,那这个默认构造函数有啥用呢?

#include<iostream>
using namespace std;
class Time 
public:
	Time() 
		_hour = 0;
		_minute = 0;
		_second = 0;
		cout << "Time()" << endl;
	
private:
	int _hour;
	int _minute;
	int _second;
;
class Date

public:
	//void Display(Date* this)
	void Display()
	
		cout << _year << "-" << _month << "-" << _day << endl;
		//cout << this->_year << "-" << this->_month << "-" <<this-> _day << endl;
	
	//void SetDate(Date* this,int year,int month,int day)
	void SetDate(int year, int month, int day)
	
		_year = year;//this->_year = year;
		_month = month;//this->_month = year;
		_day = day;//this->_day = year;
	
	//我们没有显式定义构造函数,这里编译器生成无参默认构造函数

	构造函数 相当于SetDate,初始化,但是由程序自动调用,不需要我们去调用
	1.无参数构造函数
	//Date() 
	//	_year = 0;
	//	_month = 1;
	//	_day = 1;
	//
	2.带参数构成函数    无参和带参两种方式均可
	//Date(int year, int month, int day) 
	//	_year = year;
	//	_month = month;
	//	_day = day;
	//
private:
	int _year;//年
	int _month;//月
	int _day;//日
	Time _t;//自定义类型
;
int main()

	//Date d1(2021,8,21);
	Date d1;//调用了编辑器生成的默认构造函数
	d1.Display();
	d1.SetDate(2021, 10, 1);
	d1.Display();

	Date d2;
	d2.Display();

	//Date d1;//调用无参构造函数 注意后面不能带(),否则变成函数声明了
	//Date d2(2015, 1, 1);//调用带参的构造函数

	//d1.SetDate(2021, 8, 19);//d1.SetDate(&d1,2021,8,19)
	//d2.SetDate(2021, 8, 20);//d2.SetDate(&d2,2021,8,19)
	//d1.Display();//d1.Display(&d1)
	//d2.Display();//d2.Display(&d2)
	return 0;

添加一个自定义类型Time后,可以发现Date的编译器生成的默认构造函数实际上调用了Time的构造函数。也就是说:

默认生成的无参构造函数(语法坑:双标狗)

1、针对内置类型的成员变量没有做处理(比如int、char等类型)

2、针对用户自定义类型的成员变量,调用它的构造函数初始化(比如Time类型)

为了解决上面内置类型不初始化的问题,C++11打了一个补丁,允许给内置类型设置缺省值。

int _year = 0;
int _month = 1;
int _day = 1;
//注意这里是成员变量的声明,不是定义,没有开空间

**6.无参的构造函数和全缺省的构造函数都称为默认构造函数,并且默认构造函数只能有一个。**注意:无参构造函数、全缺省构造函数、我们没写编译器默认生成的构造函数,都可以认为是默认构造函数。

#include<iostream>
using namespace std;
class Date

public:
	无参构造函数
	//Date()   
	//	_year = 0;
	//	_month = 1;
	//	_day = 1;
	//
	带参构成函数
	//Date(int year, int month, int day)  
	//	_year = year;
	//	_month = month;
	//	_day = day;
	//
	//上面两种方式的合二为一 全缺省构造函数
	Date(int year = 0, int month = 1, int day = 1) 
		_year = year;
		_month = month;
		_day = day;
	
	void Display()
	
		cout << _year << "-" << _month << "-" << _day << endl;
		//cout << this->_year << "-" << this->_month << "-" <<this-> _day << endl;
	
private:
	int _year;//年
	int _month;//月
	int _day;//日
;
int main()

	Date d1(2021, 10, 1);
	Date d2;
	d1.Display();
	d2.Display();
	return 0;

注意:无参的构造函数和全缺省的构造函数,只能存在一个;如果两个同时存储,那么调用的时候会产生歧义,因为编译器不知道该调用哪个了。

7.关于编译器生成的默认成员函数,很多童鞋会有疑惑∶在我们不实现构造函数的情况下,编译器会生成默认的构造函数。但是看起来默认构造函数又没什么用?

d对象调用了编译器生成的默认构造函数,但是d对象的_year/_month/_day,依旧是随机值。也就说在这里编译器生成的默认构造函数并没有什么作用??

解答:C++把类型分成内置类型(基本类型)和自定义类型。内置类型就是语法已经定义好的类型:如int/char…,。自定义类型就是我们使用class/struct/union自己定义的类型,看看下面的程序

就会发现编译器生成默认的构造函数会对自定类型成员_t调用的它的默认成员函数

(这个前面已经说的很清楚了,语法坑:双标狗)

8.成员变量的命名风格

建议成员变量名称前面加上下划线_

int _year;
int _month;
int _day;
//或者也可以这样
int m_year;
int m_month;
int m_day;
//m-member 成员

3.析构函数

3.1 概念

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

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

析构:对象生命周期到了以后,自动调用。完成对象里面的资源清理(资源:对象创建时向系统申请的空间,比如所 malloc 出来的空间)

3.2 特性

析构函数是特殊的成员函数。其特征如下:

1.析构函数名是在类名前加上字符~

2.无参数无返回值

3.一个类有且只有一个析构函数。若未显式定义,系统会自动生成默认的析构函数

4.对象生命周期结束时,C++编译系统系统自动调用析构函数

#include<iostream>
using namespace std;
class Date

public:
	//构造函数
	Date(int year = 0, int month = 1, int day = 1) 
		_year = year;
		_month = month;
		_day = day;
		cout << "自动调用构造函数" << endl;
	
	//析构函数
	~Date() 
		cout << "自动调用析构函数~" << endl;
	
	void Display()
	
		cout << _year << "-" << _month << "-" << _day << endl;
		//cout << this->_year << "-" << this->_month << "-" <<this-> _day << endl;
	

private:
	int _year;//年
	int _month;//月
	int _day;//日
;
int main()

	Date d1(2021, 10, 1);
	Date d2;
	d1.Display();
	d2.Display();
	return 0;

析构函数什么时候使用呢?

当我们动态开辟数组,比如malloc/colloc/realloc出来的空间,使用之后需要将这些堆上的空间释放掉,就可以利用析构函数将资源清理掉。

#include<iostream>
using namespace std;
class Stack 
public:
	//构造函数,完成初始化
	Stack(int n = 10) 
		_array = (int*)malloc(sizeof(int) * n);
		_size = 0;
		_capacity = 10;
	
	//析构函数,完成资源清理
	~Stack() 
		free(_array);
		_array = NULL;
		_size = 0;
		_capacity = 0;
	
private:
	int* _array;
	int _size;
	int _capacity;
;
int main() 
	Stack st;
	return 0;

调用构造函数完成初始化

调用析构函数完成资源清理

一个类要有什么样的成员变量和成员函数,需要根据类的实际使用场景来确定。

5.关于编译器自动生成的析构函数,是否会完成一些事情呢?

下面的程序我们会看到,编译器生成的默认析构函数,对会自定类型成员调用它的析构函数。

(类似于构造函数的语法坑:双标狗)

跟构造函数类似,编译器默认生成的析构函数做了偏心的处理:

1)内置类型不处理

2)自定义类型会去调用它的析构函数

#include<iostream>
using namespace std;
class Time 
public:
	~Time() 
		cout << "~Time()" << endl;
	
private:
	int _hour;
	int _minute;
	int _second;
;
class Date

public:
	//构造函数
	Date(int year = 0, int month = 1, int day = 1) 
		_year = year;
		_month = month;
		_day = day;
		cout << "自动调用构造函数" << endl;
	
	析构函数
	//~Date() 
	//	cout << "自动调用析构函数~" << endl;
	//
	void Display()
	
		cout << _year << "-" << _month << "-" << _day << endl;
		//cout << this->_year << "-" << this->_month << "-" <<this-> _day << endl;
	

private:
	int _year;//年
	int _month;//月
	int _day;//日
	Time _t;//自定义类型
;
int main()

	Date d1(2021, 10, 1);
	Date d2;
	d1.Display();
	d2.Display();
	return 0;

4.拷贝构造函数

4.1 概念

在现实生活中,可能存在长得一模一样的事物,我们称其为双胞胎(孪生/双生)。

那在创建对象时,可否创建一个与一个对象一模一样的新对象呢?

拷贝构造函数:只有单个形参,该形参是对本类类型对象的引用(一般常用const修饰),在用已存在的类类型对象创建新对象时由编译器自动调用。

4.2 特性

拷贝构造函数也是特殊的成员函数,其特征如下:

1.拷贝构造函数是构造函数的一个重载形式。

2.拷贝构造函数的参数只有一个且必须使用引用传参,使用传值方式会引发无穷递归调用。

#include<iostream>
using namespace std;
class Date 

public:
	Date(int year = 0, int month = 1, int day = 1)
    
		_year = year;
		_month = month;
		_day = day;
	
	//拷贝构造函数
	Date(const Date& d) 
    
		_year = d._year;
		_month = d._month;
		_day = d._day;
	
private:
	int _year;
	int _month;
	int _day;
;
int main()

	Date d1(2021, 10, 1);
	Date d2(2021, 10, 1);
	Date d3(d1);
	return 0;

如果不使用引用,在调用拷贝构造函数之前需要先传参,传参的时候要先进行拷贝构造,拷贝构造之前又要先传参….语义上

#include<iostream>
using namespace std;
class Date

public:
	Date(int year = 0, int month = 1, int day = 1) 
    
		_year = year;
		_month = month;
		_day = day;
	
	//拷贝构造函数
	Date(const Date d) 
    
		_year = d._year;
		_month = d._month;
		_day = d._day;
	
private:
	int _year;
	int _month;
	int _day;
;
int main() 

	Date d1(2021, 10, 1);
	Date d2(2021, 10, 1);
	Date d3(d1);
	return 0;

传参的过程又是一个拷贝构造的调用?这句话怎么理解?

func1(int i) 

	//函数内部代码

int main() 

	int j = 0;
	func1(j);//调用func1的时候,要先将j传给i

实参传给形参是一个赋值的方式

换成class Date类的话

d1传给d的过程就是一个赋值的过程,但是这个自定义类的赋值不同于内置类型的直接赋值,而是再次通过拷贝构造函数给其赋值,然后就形成了语义上的无穷递归……

传值的方式,实参接收形参传过来的值时,需要创建一个临时对象,就会引发对象的拷贝构造。

而传引用的方式(引用是一个变量的别名)没有传参赋值的这个过程,也就不会形成无穷的递归。

拷贝构造也可以通过类似赋值的方式进行

Date d4 = d1;

Date(const Date &d)这里为什么要加上const的呢?

1)使用const可以保证对象d的内容不被修改

2)如果Date(const Date &d) ;花括号内部代码出错了,比如说写反了,编译器会查出来。

3.若未显示定义,系统生成默认的拷贝构造函数。默认的拷贝构造函数对象按内存存储按字节序完成拷贝,这种拷贝我们叫做浅拷贝,或者值拷贝。

默认的拷贝构造函数,跟默认的构造和析构函数不太一样,不会去区分内置类型和自定义类型,都会区处理

1)内置类型,字节序的浅拷贝

2)自定义类型,会去调用它的拷贝构造函数完成拷贝

字节序的浅拷贝:就像用 memcpy 函数完成拷贝一样(一个字节接着一个字节的拷贝复制)

Stack St1;
Stack st2(st1);
//同一块空间会释放两次

编译器默认生成的拷贝构造函数并不能解决所有问题,所以需要我们定义深拷贝构造来解决这个问题。

#include<iostream>
using namespace std;
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;
 ;
C++基础三类和对象(中篇)

C++类和对象(中篇)

C++从青铜到王者第三篇:C++类和对象(中篇)

C++初阶 —— 类和对象(中篇)

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

C++初阶:类和对象(中篇)构造函数 | 析构函数 | 拷贝构造函数 | 赋值运算符重载