[C++] 类与对象(中)类中六个默认成员函数(1)

这篇具有很好参考价值的文章主要介绍了[C++] 类与对象(中)类中六个默认成员函数(1)。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

[C++] 类与对象(中)类中六个默认成员函数(1),C++,c++,开发语言

 目录

1、类的六个默认成员函数

2、构造函数

2.1 构造函数的概念

2.2 特性

2.2.1 构造函数的重载:

2.2.2 全缺省的构造函数:

3、析构函数

3.1 析构函数的概念

3.2 特性

4、拷贝构造函数

4.1 拷贝构造函数的概念

4.2 特征


1、类的六个默认成员函数

如果一个类中什么成员都没有,简称为空类。空类中真的什么都没有吗?并不是,任何类在什么都不写时,编译器会自动生成以下6个默认成员函数。

[C++] 类与对象(中)类中六个默认成员函数(1),C++,c++,开发语言

2、构造函数

2.1 构造函数的概念

我们这里来看看日期类的初始化:

class Date
{
public:
    void Init(int year, int month, int day)
    {
        _year = year;
        _month = month;
        _day = day;
    }
    void Print()
    {
    	cout << _year << "/" << _month << "/" << _day << endl;
    }

private:
    int _year;
    int _month;
    int _day;
};
int main()
{
    Date d1;
    d1.Init(2022, 7, 5);
    d1.Print();
    
    return 0;
}

运行结果:

[C++] 类与对象(中)类中六个默认成员函数(1),C++,c++,开发语言

我们刚接触C++,一定会这样初始化。

如果我们实例化的对象太多了,忘记初始化对象了,程序运行出来的结果可能就是随机值了,也可能出问题。

这里C++祖师爷想到了,为我们设计了构造函数。

我们先来看一下忘记初始化直接打印的结果:

[C++] 类与对象(中)类中六个默认成员函数(1),C++,c++,开发语言

这里是随机值,那这是为什么呢?我们接着往下看。

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

2.2 特性

构造函数是特殊的成员函数,需要注意的是,构造函数虽然名称叫构造,但是构造函数的主要任务并不是开空间创建对象,而是初始化对象。
其特征如下:
1. 函数名与类名相同。
2. 无返回值(不是void,是不用写)。
3. 对象实例化时编译器自动调用对应的构造函数。
4. 构造函数可以重载。

我们先写一个日期类的构造函数来看看:

class Date
{
public:
	Date()//构造函数,无参构造
	{
		cout << "Date()" << endl;
		_year = 1;
		_month = 1;
		_day = 1;
	}

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


private:
	int _year;
	int _month;
	int _day;
};

我们测试看一下:

[C++] 类与对象(中)类中六个默认成员函数(1),C++,c++,开发语言

我们main函数里没有调用构造函数,但是这里打印了我们做的标记,这里我们实验出来了实例化对象时构造函数是自动调用的。
我们再来看将我们写的构造函数注释掉会发生什么:

[C++] 类与对象(中)类中六个默认成员函数(1),C++,c++,开发语言

我们能看到,注释掉后,仍然能打印出来,只不过是随机值。因为当我们不写,编译器会自动生成默认的构造函数,并自动调用。

C++将类型分为内置类型(基本类型):如int,char,double,int*……(自定义类型*也是);

自定义类型:如class,struct,union……。

并且这里我们能看出来,对于内置类型的成员不会处理,在C++11,支持成员变量给缺省值,算是补漏洞了。

2.2.1 构造函数的重载:

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

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


private:
	int _year;
	int _month;
	int _day;
};

int main()
{
    Date d1;
	d1.Print();
	Date d2(2023, 8, 1);//这里初始化必须是这样写,这是语法
	d2.Print();

    return 0;
}

运行结果:

[C++] 类与对象(中)类中六个默认成员函数(1),C++,c++,开发语言

注意我们在实例化对象的时候,当调用的构造函数没有参数,不能在对象后加括号,语法规定。

[C++] 类与对象(中)类中六个默认成员函数(1),C++,c++,开发语言

如果这样写,编译器分不清这到底是函数声明还是在调用。d2不会混淆是因为有传值,函数声明不会出现那样的写法。

2.2.2 全缺省的构造函数:

我们其实可以将上面的两个构造函数合并为一个

class Date
{
public:
	Date(int year = 1, int month = 1, int day = 1)
	{
		_year = year;
		_month = month;
		_day = day;
	}

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


private:
	int _year;
	int _month;
	int _day;
};
int main()
{
    Date d1;
	d1.Print();

	Date d2(2023, 8, 1);
	d2.Print();

	Date d3(2023, 9);
	d3.Print();

    return 0;
}

运行结果:

[C++] 类与对象(中)类中六个默认成员函数(1),C++,c++,开发语言

全缺省构造函数才是最适用的。无参构造与全缺省可以同时存在,但是不建议这样写,虽然不报错,但是在调用全缺省时我们不想传参,编译器不知道我们到底想调用哪个构造,会产生二义性。

我们再看用两个栈实现队列的问题:

class Stack
{
public:
	Stack(int n = 4)
	{
		if (n == 0)
		{
			_a = nullptr;
			_size = -1;
			_capacity = 0;
		}
		else
		{
			int* tmp = (int*)realloc(_a, sizeof(int) * n);
			if (tmp == nullptr)
			{
				perror("realloc fail");
				exit(-1);
			}
			_a = tmp;

			_size = -1;
			_capacity = n;
		}
	}

	void Push(int n)
	{
		if (_size + 1 == _capacity)
		{
			int newcapacity = _capacity == 0 ? 4 : 2 * _capacity;
			int* tmp = (int*)realloc(_a, sizeof(int) * newcapacity);
			if (nullptr == tmp)
			{
				perror("realloc fail:");
				exit(-1);
			}
			_a = tmp;
			_capacity = newcapacity;
		}

		_a[_size++] = n;
	}

	int Top()
	{
		return _a[_size];
	}

	void Pop()
	{
		assert(_size > -1);
		_size--;
	}

	void Destort()
	{
		free(_a);
		_a = nullptr;
		_size = _capacity = 0;
	}

	bool Empty()
	{
		return _size == -1;
	}

private:
	int* _a;
	int _size;
	int _capacity;
};
class MyQueue
{
private:
	Stack _pushst;
	Stack _popst;

};

一般情况下都需要我们自己写构造函数,决定初始化方式,成员变量全是自定义类型,可以考虑不写构造函数。会调用自定义类型的默认构造函数。

总结:无参构造函数、全缺省构造函数、我们不写编译器默认生成的构造函数,都可以认为是默认构造函数,并且默认构造函数只能存在一个(多个并存会产生二义性)。

3、析构函数

3.1 析构函数的概念

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

3.2 特性

析构函数是特殊的成员函数,其特征如下:
1. 析构函数名是在类名前加上字符 ~。
2. 无参数无返回值类型。
3. 一个类只能有一个析构函数。若未显式定义,系统会自动生成默认的析构函数(对内置类型不做处理,自定义类型会调用它自己的析构函数)。注意:析构函数不能重载。
4. 对象生命周期结束时,C++编译系统会自动调用析构函数。

我们先来看日期类的析构:

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

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

	~Date()
	{
		cout << "~Date()" << endl;
		_year = 0;
		_month = 0;
		_day = 0;
	}

private:
	int _year;
	int _month;
	int _day;
};
int main()
{
	Date d1;
	Date d2;

    return 0;
}

运行结果:

[C++] 类与对象(中)类中六个默认成员函数(1),C++,c++,开发语言

我们这里可以看出析构函数也是自动调用的。

我们不写,编译器自动生成默认的析构函数。

析构函数的调用顺序跟栈类似,后实例化的先析构。

如果类中没有申请资源时,析构函数可以不写,直接使用编译器生成的默认析构函数,比如Date类;有资源申请时,一定要写,否则会造成资源泄漏,比如Stack类。

我们画图来看一下:

[C++] 类与对象(中)类中六个默认成员函数(1),C++,c++,开发语言

栈中的析构函数就代替了栈的销毁:

[C++] 类与对象(中)类中六个默认成员函数(1),C++,c++,开发语言

[C++] 类与对象(中)类中六个默认成员函数(1),C++,c++,开发语言

class Stack
{
public:
	Stack(int n = 4)
	{
		if (n == 0)
		{
			_a = nullptr;
			_top = -1;
			_capacity = 0;
		}
		else
		{
			int* tmp = (int*)realloc(_a, sizeof(int) * n);
			if (tmp == nullptr)
			{
				perror("realloc fail");
				exit(-1);
			}
			_a = tmp;

			_top = -1;
			_capacity = n;
		}
	}

	~Stack()
	{
		free(_a);
		_a = nullptr;
		_top = _capacity = 0;
	}
	//void Destort()
	//{
	//	free(_a);
	//	_a = nullptr;
	//	_top = _capacity = 0;
	//}

	void Push(int n)
	{
		if (_top + 1 == _capacity)
		{
			int newcapacity = _capacity == 0 ? 4 : 2 * _capacity;
			int* tmp = (int*)realloc(_a, sizeof(int) * newcapacity);
			if (nullptr == tmp)
			{
				perror("realloc fail:");
				exit(-1);
			}
			_a = tmp;
			_capacity = newcapacity;
		}

		_a[_top++] = n;
	}

	int Top()
	{
		return _a[_top];
	}

	void Pop()
	{
		assert(_top > -1);
		_top--;
	}

	bool Empty()
	{
		return _top == -1;
	}

private:
	int* _a;
	int _top;
	int _capacity;
};
class MyQueue
{
private:
	Stack _pushst;
	Stack _popst;

};

对于栈这样的,我们析构函数代替了销毁函数,析构函数会自动调用,以前我们需要手动调用销毁函数的接口,现在不用调用了。

因此,构造函数和析构函数最大的优势是自动调用。

4、拷贝构造函数

4.1 拷贝构造函数的概念

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

4.2 特征

拷贝构造函数也是特殊的成员函数,其特征如下:
1. 拷贝构造函数是构造函数的一个重载形式。
2. 拷贝构造函数的参数只有一个且必须是同类型对象的引用,使用传值方式编译器会引发无穷递归调用。
3. 若未显式定义,编译器会生成默认的拷贝构造函数。 默认的拷贝构造函数对象按内存存储按字节序完成拷贝,这种拷贝叫做浅拷贝,或者值拷贝。

拷贝构造就像是复制粘贴一样。

拷贝构造函数的参数只有一个且必须是类类型对象的引用,使用传值方式编译器会引发无穷递归调用。

传值拷贝会发生无穷递归,我们来写一个拷贝构造函数。

class Date
{
public:
	Date(int year = 1, int month = 1, int day = 1)
	{
		_year = year;
		_month = month;
		_day = day;
	}

	//拷贝构造
	Date(Date d)
	{
		_year = d._year;
		_month = d._month;
		_day = d._day;
	}

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

	~Date()
	{
		_year = 0;
		_month = 0;
		_day = 0;
	}

private:
	int _year;
	int _month;
	int _day;
};
void func(Date d)
{
	d.Print();
}

int main()
{
	Date d1(2023, 8, 2);
	func(d1);

    return 0;
}

内置类型的拷贝是直接拷贝,自定义类型的拷贝要调用拷贝构造完成。

[C++] 类与对象(中)类中六个默认成员函数(1),C++,c++,开发语言

在vs2019中,传值传参编译器会报错:

[C++] 类与对象(中)类中六个默认成员函数(1),C++,c++,开发语言

因此,我们要是写拷贝构造函数,形参必须是同类型的引用:

[C++] 类与对象(中)类中六个默认成员函数(1),C++,c++,开发语言

引用是给变量起别名,析构自动调用的顺序是后定义先析构,拷贝的时候d1还没有析构,因此是可以使用引用的,这样就不会导致递归拷贝了。

我们将写的拷贝构造函数屏蔽掉,看看会如何:

class Date
{
public:
	Date(int year = 1, int month = 1, int day = 1)
	{
		_year = year;
		_month = month;
		_day = day;
	}

	//拷贝构造
	//Date(Date& d)
	//{
	//	_year = d._year;
	//	_month = d._month;
	//	_day = d._day;
	//}

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

	~Date()
	{
		_year = 0;
		_month = 0;
		_day = 0;
	}

private:
	int _year;
	int _month;
	int _day;
};

int main()
{
	Date d1(2023, 8, 2);
	Date d2(d1);
	d2.Print();
	
    return 0;
}

运行结果:

[C++] 类与对象(中)类中六个默认成员函数(1),C++,c++,开发语言

 我们发现,如果我们不写,还是能实现拷贝,这是因为我们不写,编译器默认生成一个拷贝构造函数,对于日期类这样的浅拷贝,默认生成的构造函数是可以实现拷贝的。

我们再来看栈的拷贝构造:

typedef int DataType;
class Stack
{
public:
	Stack(int n = 4)
	{
		if (n == 0)
		{
			_a = nullptr;
			_size = -1;
			_capacity = 0;
		}
		else
		{
			int* tmp = (int*)realloc(_a, sizeof(int) * n);
			if (tmp == nullptr)
			{
				perror("realloc fail");
				exit(-1);
			}
			_a = tmp;

			_size = -1;
			_capacity = n;
		}
	}

	//拷贝构造
	Stack(Stack& s)
	{
		_a = s._a;
		_size = s._size;
		_capacity = s._capacity;
	}

	~Stack()
	{
		free(_a);
		_a = nullptr;
		_size = _capacity = 0;
	}

	void Push(int n)
	{
		if (_size + 1 == _capacity)
		{
			int newcapacity = _capacity == 0 ? 4 : 2 * _capacity;
			int* tmp = (int*)realloc(_a, sizeof(int) * newcapacity);
			if (nullptr == tmp)
			{
				perror("realloc fail:");
				exit(-1);
			}
			_a = tmp;
			_capacity = newcapacity;
		}

		_a[_size++] = n;
	}

	int Top()
	{
		return _a[_size];
	}

	void Pop()
	{
		assert(_size > -1);
		_size--;
	}

	bool Empty()
	{
		return _size == -1;
	}

private:
	int* _a;
	int _size;
	int _capacity;
};
int main()
{
	Stack s1;
	Stack s2(s1);
	
    return 0;
}

我们这里为栈写的拷贝构造,我们来试一下拷贝构造:

[C++] 类与对象(中)类中六个默认成员函数(1),C++,c++,开发语言

这里为什么引发了异常呢?

我们调试看看:

[C++] 类与对象(中)类中六个默认成员函数(1),C++,c++,开发语言

这里我们可以看到,s1的_a与s2的_a地址是一样的,当s2拷贝完后就会析构,s2的_a被释放掉后,s1还会再调用一次析构函数,这时再去释放_a,_a的空间已经被释放过了,就会引发空指针异常的问题。

因此,对于有空间申请的对象,在写拷贝构造的时候必须要深拷贝。

我们来改正代码:

typedef int DataType;
class Stack
{
public:
	Stack(int n = 4)
	{
		if (n == 0)
		{
			_a = nullptr;
			_size = -1;
			_capacity = 0;
		}
		else
		{
			int* tmp = (int*)realloc(_a, sizeof(int) * n);
			if (tmp == nullptr)
			{
				perror("realloc fail");
				exit(-1);
			}
			_a = tmp;

			_size = -1;
			_capacity = n;
		}
	}

	//拷贝构造
	Stack(Stack& s)
	{
		cout << "Stack(Stack& s)" << endl;
		//深拷贝
		_a = (DataType*)malloc(sizeof(DataType) * s._capacity);
		if (nullptr == _a)
		{
			perror("malloc fail:");
			exit(-1);
		}

		memcpy(_a, s._a, sizeof(DataType) * (s._size+1));
		_size = s._size;
		_capacity = s._capacity;
	}

	~Stack()
	{
		free(_a);
		_a = nullptr;
		_size = _capacity = 0;
	}

	void Push(int n)
	{
		if (_size + 1 == _capacity)
		{
			int newcapacity = _capacity == 0 ? 4 : 2 * _capacity;
			int* tmp = (int*)realloc(_a, sizeof(int) * newcapacity);
			if (nullptr == tmp)
			{
				perror("realloc fail:");
				exit(-1);
			}
			_a = tmp;
			_capacity = newcapacity;
		}

		_a[_size++] = n;
	}

	int Top()
	{
		return _a[_size];
	}

	void Pop()
	{
		assert(_size > -1);
		_size--;
	}

	bool Empty()
	{
		return _size == -1;
	}

private:
	int* _a;
	int _size;
	int _capacity;
};

运行结果:

[C++] 类与对象(中)类中六个默认成员函数(1),C++,c++,开发语言

总结:像Date这样不需要我们实现拷贝构造,默认生成的就可以用;Stack需要我们自己实现深拷贝的拷贝构造,默认生成的会出问题;对于成员全是自定义类型的也不需要写拷贝构造,会调用自定义类型的拷贝构造函数。

引申:

[C++] 类与对象(中)类中六个默认成员函数(1),C++,c++,开发语言

[C++] 类与对象(中)类中六个默认成员函数(1),C++,c++,开发语言文章来源地址https://www.toymoban.com/news/detail-654153.html

到了这里,关于[C++] 类与对象(中)类中六个默认成员函数(1)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 【是C++,不是C艹】 类与对象 | 默认成员函数 | 构造函数 | 析构函数

    💞💞 欢迎来到 Claffic 的博客 💞💞  👉  专栏: 《是C++,不是C艹》👈 前言: 在完成类与对象的认识后,我们接着学习类与对象的第二部分:默认成员函数,它包括构造函数,析构函数,拷贝构造,赋值重载,普通对象取地址和const对象取地址重载,放心,这一期不会都

    2024年02月09日
    浏览(41)
  • 【C++初阶】类与对象:6个默认成员函数-----构造函数和析构函数

        我们在写代码的时候经常会忘记初始化和销毁,C++的构造函数和析构函数就能避免这个问题。 默认成员函数:用户没有显式实现,编译器会生成的成员函数称为默认成员函数。 1.构造函数是一个特殊的成员函数; 2. 名字与类名相同 ,创建类类型对象时由 编译器自动调用

    2024年02月05日
    浏览(46)
  • 【C++初阶】类与对象:6大默认成员函数------拷贝构造和赋值运算符重载

      拷贝构造函数: 只有单个形参,该形参是对本类类型对象的引用(一般常用const修饰) ,在用已存在的类类型对象创建新对象时由编译器自动调用。 1. 拷贝构造函数是 构造函数的一个重载形式 ; 2. 拷贝构造函数的 参数只有一个且必须是类类型对象的引用 ,使用传值方式编

    2024年02月03日
    浏览(46)
  • C++:类的六个默认成员函数

    个人主页 : 个人主页 个人专栏 : 《数据结构》 《C语言》《C++》 本篇博客作为C++知识总结,我们来认识类的六个默认成员函数。 下面我主要以日期类作为示例显示。 构造函数 是一个特殊的成员函数,名字与类名相同,创建类类型对象时(实例化类)由编译器自动调用,以保

    2024年02月08日
    浏览(54)
  • 【浅尝C++】继承机制=>虚基表/菱形虚继承/继承的概念、定义/基类与派生类对象赋值转换/派生类的默认成员函数等详解

    🏠专栏介绍:浅尝C++专栏是用于记录C++语法基础、STL及内存剖析等。 🎯每日格言:每日努力一点点,技术变化看得见。 我们生活中也有继承的例子,例如:小明继承了孙老师傅做拉面的手艺。继承就是一种延续、复用的方式。C++为了提高代码的可复用性,引入了继承机制,

    2024年04月10日
    浏览(48)
  • 在 Qt 的文本编辑类中,document() 是一个成员函数,用于获取文档对象

    在 Qt 的文本编辑类中, document() 是一个成员函数,用于获取文档对象。它返回与文本编辑器关联的 QTextDocument 对象的指针。 QTextDocument 类是 Qt 中用于处理富文本内容的类。它包含了文本内容以及相关的格式、样式和布局信息。通过 document() 函数,可以获取到当前文本编辑器

    2024年02月04日
    浏览(43)
  • 互联网开发中六个思维模型

    邓宁-克鲁格效应表明,没有经验的人往往会高估自己的能力,而有经验的人往往会低估自己的能力。 你不擅长某件事,但你会认为你擅长它。如果你擅长某事,你认为你不擅长 - 这可能导致冒名顶替综合症,这让你怀疑自己的能力,以至于你在其他具有相似技能的人中感到

    2024年02月05日
    浏览(51)
  • 【类和对象(中)】六大默认成员函数

    本文继类和对象上,开始讲述默认成员函数。 默认成员函数是:我们不具体写,编译器会自动生成的函数叫默认成员函数。 构造函数是类的一个默认成员函数,它虽然叫构造函数,但它的作用并不是构造一个对象,而是初始化一个对象。 它与Init函数不同, 每次实例化一个

    2024年02月03日
    浏览(40)
  • C++ ------类和对象详解六大默认成员函数

    如果我们定义一个类,然后这个类中什么也没有。那么这里的类就什么也没有吗?其实不然,任何类在里面什么都不写时,编译器都会生成6个默认成员函数。 用户没有显式实现,编译器会生成的成员函数称为默认成员函数。 六个默认成员函数 我们来看一个Date类 观察上述代

    2024年02月15日
    浏览(55)
  • 【C++】类和对象(中篇)----->六大默认成员函数

    目录 一、类的6个默认成员函数 二、构造函数  1、概念   2、特性 三、析构函数  1、概念  2、特性 四、拷贝构造函数  1、概念  2、特征 五、赋值运算符重载  1、运算符重载  2、值运算符重载    2.1 赋值运算符重载格式    2.2 赋值运算符只能重载成类的成员函数不能

    2024年02月12日
    浏览(42)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包