C++初阶之类与对象(中)——六个默认函数详细解析

个人主页:点我进入主页

专栏分类:C语言初阶  C语言进阶  数据结构初阶    Linux    C++初阶    

欢迎大家点赞,评论,收藏。

一起努力,一起奔赴大厂

目录

一.前言

二.构造函数

2.1构造函数的语法和特性

2.1.1语法

2.1.2特性

2.2代码演示与验证

2.2.1构造函数的多种写法(语法部分验证)

2.2.1.1无参

2.2.1.2传参数

2.2.1.3全缺省 

2.2.1.4默认生成

2.2.1.5三种只能存在一种

2.2.2类嵌套类(特性部分验证)

2.3C++11新写法

三.析构函数

3.1析构函数的语法

3.2代码演示与验证

3.3析构函数应用场景

3.4析构函数的调用顺序

四.拷贝构造

4.1特性

4.2代码解析

4.2.1只有一个拷贝后构造的日期类出现的问题

4.2.2正确的代码

4.3深拷贝

4.4解决方案

4.5传值引发的死递归

五.总结



一.前言

        在本次博客中我将给大家带来6个默认成员函数,主要包括构造函数,析构函数,拷贝构造,赋值重载,符号重载,在这次文章中我们需要记住一个关键点自动调用,其中构造函数是对数据进行初始化,析构函数是完成清理工作,拷贝构造是对同同类对象进行初始化 ,赋值重载是把一个对象给另一个对象。其中重要的是前四个,我们这次对前三个进行讲解。

C++初阶之类与对象(中)——六个默认函数详细解析_第1张图片

二.构造函数

2.1构造函数的语法和特性

2.1.1语法

  • 构造函数函数名和类名相同。
  • 构造函数支持函数重载。
  • 构造函数无返回值。
  • 构造函数在对象实例化的时候自动调用。   

2.1.2特性

  • C++分为内置类型和自定义类型,内置类型不会处理,定义类型自动调用它的默认构造。
  • 如果我们没有写默认构造编译器会自动生成一个默认构造。
  • 默认构造会自动调用,其中默认构造包括无参形式,全缺省形式以及编译器自动生成的默认构造,这三个只能存在一个。

2.2代码演示与验证

2.2.1构造函数的多种写法(语法部分验证)

2.2.1.1无参

我们分文件进行编写,我们先创建一个类,头文件的代码为:

#pragma once
#include
using namespace std;

class Data {
public:
	Data()
	{
		_year = 1;
		_month = 1;
		_day = 1;
	}
	void Print();
private:
	int _year;
	int _month;
	int _day;
};

我们的打印函数在另一个.cpp文件,主函数在另外一个.cpp文件中

#include"class.h"

void Data::Print()
{
	cout << _year << "/" << _month << "/" << _day << endl;
}

主函数的代码为:

#include"class.h"

int main()
{
	Data d1;
	d1.Print();
	return 0;
}

我们运行代码就会看到d1的年月日都被初始化为1

2.2.1.2传参数

我们将构造函数改为

	Data(int year, int month, int day)
	{
		_year = year;
		_month = month;
		_day =day;
	}

我们按照上面的代码继续运行就会出现问题:

编译器显示我们没有默认构造函数可用,这是为什么呢?我们不是写了吗?我们写了,所以编译器不会生成,只能选择我们写的,我们需要对其进行传参,那我们应该如何修改呢?我们需要知道构造函数时什么时候调用的呢?构造函数是在生成实例化对象的时候自动调用的,所以我们在生成实例化对象的时候进行传参,

//Data d1;
Data d1(1, 1, 1);

看到这里有人就会想到为什么无参的为什么不写成Data da();这主要就是为了和我们的函数声明区分开

2.2.1.3全缺省 
	Data(int year=1, int month=1, int day=1)
	{
		_year = year;
		_month = month;
		_day = day;
	}

我们将构造函数改成全缺省的形式,的那我们在生成实例化对象的时候既可以传参也可以不传参

	Data d1(1, 1, 1);
	Data d2;

这两种都可以使用。

2.2.1.4默认生成

我们不写构造函数,我们的结果是什么样子的呢?

我们看大是这些是,这是由于编译器的不同,所以数据可能不同。

2.2.1.5三种只能存在一种

        如果我们写了构造函数,编译器不会生成构造函数,也就是说编译器生成的构造函数和我们写的不能共存,这一点是非常容易理解的,对于无参和全缺省的我们可以看下面的示例:

我们的构造函数为:

	Data()
	{
		_year = 1;
		_month = 1;
		_day = 1;
	}	

	Data(int year=1, int month=1, int day=1)
	{
		_year = year;
		_month = month;
		_day = day;
	}	

我们的测试代码为:

Data d2;

我们可以看到错误为:

这是因为我们的测试代码对于这两种都符合,编译器不知道运行哪一个,所以我们的无参和构造函数不能同时存在,传参数的和无参的虽热可以同时存在,但是他们两个的功能一个全缺省就可以解决,所以全缺省是最完美的。

2.2.2类嵌套类(特性部分验证)

我们将类进行修改,并且增加一个新的类:

class A {
public:
	A(int data1=1, int data2=1)
	{
		_data1 = data1;
		_data2 = data2;
	}
private:
	int _data1;
	int _data2;
};
class Data {
public:
    int num;
	Data(int year=1, int month=1, int day=1)
	{
		_year = year;
		_month = month;
		_day = day;
	}	
	void Print();
private:
	int _year;
	int _month;
	int _day;
	A _a;
};

我们进行调试就可以看到我们的内置类型不做任何处理,也就是我们的d2.num,内置类型会调用它的默认构造。

2.3C++11新写法

        在C++11中委员会对构造函数进行了新的更新,可以在声明时进行缺省,我们看下面的代码样例:

class Data {
public:	
	void Print();
private:
	int _year=2;
	int _month = 2;
	int _day=2;
	
};

三.析构函数

3.1析构函数的语法

  • 析构函数函数名是~类名
  • 析构函数不支持函数重载
  • 析构函数无参数无返回值
  • 对象的声明周期结束时自动调用,若没用显示定义编译器会自动生成。

3.2代码演示与验证

        我们在类里面加上我们析构函数(析构函数是对没用用的资源进行处理,由于我们写的是日期类,不用进行资源的清理,并且为了我们为了更详细的演示所以我们进行打印函数名)。

class Data {
public:
	Data(int year = 1, int month = 1, int day = 1)
	{
		cout << "Data" << endl;
	}
	~Data()
	{
		cout << "~Data" << endl;
	}
private:
	int _year;
	int _month;
	int _day;
	
};

我们运行后可以看到:

3.3析构函数应用场景

        说到我们的析构函数,我们应该知道祖师爷为什么设计出我们的析构函数,你是否有这样的经历,一个软件本来是挺快的但是当我们用了一段时间后就会变得很卡,当我们将后台杀了再打开就会变快,但是用一段时间后又会卡,这主要就是一些文件打开后就没有关闭,再我们下写程序的时候我们总会出现这样的情况开辟了一段空间后总会忘记释放,祖师爷也深受这些的困扰,所以出现了我们的析构函数,我们一个应用场景就是栈,我们写一个类,代码如下:

class Stack {
public:
	Stack()
    {
	    _a = new int[4];
        _size = 0;
	    _capacity = 4;
    }
	~Stack()
    {
	    delete _a;
    }
private:
	int* _a;
	int _size;
	int _capacity;
};

在这里就是我们对析构函数的一个应用。

3.4析构函数的调用顺序

我们看下面代码:

Data d4(4, 4, 4);
Data d5(5, 5, 5);
int main()
{
	Data d1(1, 1, 1);
	static Data d2(2, 2, 2);
	Data d3(3, 3, 3);
	return 0;
}

我们需要知道我们的析构函数符合栈的规则,符合后进先出,也就是说我们先调用后面的析构函数,我们运行后可以看到:

C++初阶之类与对象(中)——六个默认函数详细解析_第2张图片

符合我们的规则,先析构3再析构1,由于2是静态变量所以后出,出2,然后出我们的全局变量,出5再出4;我们将5和4定义的位置互换

Data d5(5, 5, 5);
Data d4(4, 4, 4);

int main()
{
	Data d1(1, 1, 1);
	static Data d2(2, 2, 2);
	Data d3(3, 3, 3);
	return 0;
}

我们运行后可以看到:

C++初阶之类与对象(中)——六个默认函数详细解析_第3张图片

四.拷贝构造

4.1特性

  • 拷贝构造是构造函数的重载
  • 拷贝构造在传址或传参时自动调用
  • 拷贝构造只有一个参数,必须是类的对象,传值时会发生错误出现死递归,所以我们采用引用。
  • 如果没有显式定义,编译器会自动生成浅拷贝的拷贝构造,如果需要深拷贝就不能完成。

4.2代码解析

4.2.1只有一个拷贝后构造的日期类出现的问题

class Data {
public:
	Data(const Data& d)
	{
		_year = d._year;
		_month = d._month;
		_day = d._day;
	}
private:
	int _year;
	int _month;
	int _day;

};

当我们运行

	Data d1;

我们看到的错误信息为

这时候我们想构造函数不是默认生成的吗?怎末会不存在默认构造?仔细看我们类的定义,你会发现我们拷贝构造的构造函数重载了,我们需要知道拷贝构造也是构造,所以我们在写拷贝构造时我们需要加上我们的构造函数。

4.2.2正确的代码

class Data {
public:
	Data(int year = 1, int month = 1, int day = 1)
	{
		_year = year;
		_month = month;
		_day = day;
	}
	Data(const Data& d)
	{
		_year = d._year;
		_month = d._month;
		_day = d._day;
	}
private:
	int _year;
	int _month;
	int _day;

};

我们需要将拷贝构造和构造函数一块写,我们只写拷贝构造的话编译器也不会生成构造函数,这是因为拷贝构造也是构造

4.3深拷贝

        在上面的拷贝都是一些浅拷贝,我们看下面的代码:

class Stack {
public:
	Stack(int capacity=4)
	{
		_a = new int[4];
		_size = 0;
		_capacity = capacity;
	}
	~Stack()
	{
		delete[] _a;
	}
	Stack(const Stack& s)
	{
		_a = s._a;
		_size = s._size;
		_capacity = s._capacity;
	}
private:
	int* _a;
	int _size;
	int _capacity;
};

我们运行的代码为:

	Stack s1(4);
	Stack s2(s1);

我们运行后可以看到:

C++初阶之类与对象(中)——六个默认函数详细解析_第4张图片

这个主要就是程序出现了对空间的二次free。

4.4解决方案

我们只需要对拷贝构造进行修改即可:

	Stack(const Stack& s)
	{
		int * _tmp = new int[4];
		_size = s._size;
		_capacity = s._capacity;
		memcpy(_tmp, s._a, 16);
		_a = _tmp;
	}

4.5传值引发的死递归

	Data( const Data d)
	{
		_year = d._year;
		_month = d._month;
		_day = d._day;
	}

这是我们的拷贝构造,我们需要知道当我们调用拷贝构造时会自动调用一次拷贝构造,也就是说传自定义类型时会调用拷贝构造,由于我们使用的是传值,这次调用,调用的会再次调用拷贝构造, 这样就会发生死递归,当我们改成传引用就会完美的解决我们的问题。

五.总结

        类是我们C++中的一个重点,大家可以多看看,最后希望大家可以一键三连。

你可能感兴趣的:(C++初阶,c++)