C++ 类的 6个默认成员函数

一、构造函数

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

        特征:

        1. 函数名与类名相同。

        2. 无返回值。

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

        4. 构造函数可以重载。

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

        6. C++11 中针对内置类型成员不初始化的缺陷,又打了补丁,即:内置类型成员变量在类中声明时可以给认值。

        7. 无参的构造函数和全缺省的构造函数都称为认构造函数,并且认构造函数只能有一个。(注:无参构造函数、全缺省构造函数、没有显式写出编译器生成的构造函数,都可以认为是认构造函数。)

        代码测试:

#include <iostream>
using namespace std;

class Custom {
public:
	Custom(int ca = 0, int cb = 0) : _ca(ca), _cb(cb) {}
private:
	int _ca;
	int _cb;
};

class Test {
public:
	// 1.无参构造函数,若没有显式定义,则编译器自动生成
	// 内置类型仍为随机值,但自定义类型会调用对应的认构造函数
	test() {};
	
	// 2.带参构造函数
	// 带参无缺省函数体内初始化,
	// 无缺省 -- 调用时需逐一输入对应形参值,
	// 函数体内初始化 -- 变量经过定义后在函数体内重新赋值,因而不可赋值初始化常量
	Test(int a, int b, Custom& c) {
		_a = a;
		_b = b;
		_c = c;
	}
	// 带参全缺省函数体内初始化,
	// 全缺省 -- 若无输入形参值,则认使用缺省值,输入的变量值按对应顺序排列
	Test(int a = 0, int b = 0, Custom c = {0, 0}) {
		_a = a;
		_b = b;
		_c = c;
	}
	// 带参全缺省初始化列表初始化
	// 初始化列表初始化 -- 在定义变量时即定义值,可初始化定义常量
	Test(int a = 0, int b = 0, int ca = 0, int cb = 0) {
		_a = a;
		_b = b;
		_c = { ca, cb };
		_c = Custom(ca, cb); // 经编译器优化后,与上式等价
		//_c(ca, cb); // 错误写法,不能编译通过
	}
	// 带参非缺省函数体内初始化
	// 前几个形参可以不必缺省,但定义类对象时必须补齐
	Test(int a, int b = 0, int ca = 0, int cb = 0, const int d = 0)
		: _a(a)
		, _b(b)
		, _c(ca, cb)
		, _d(d)
	{}
	// 带参全缺省初始化列表初始化
	// 用另一种方式输入缺省的类
	Test(int a = 0, int b = 0, Custom c = {0, 0}, const int d = 0)
		: _a(a)
		, _b(b)
		, _c(c)
		, _d(d)
	{}
	
private:
	int _a;
	int _b;

	Custom _c;
	const int _d = 0;
};

int main() {

	//Test test_1; // 调用无参或全缺省构造函数
	//Test test_2(0, 0, 0, 0, 0); // 调用带参的构造函数(非全缺省)
	//Test test_error(); // 这是一个错误的写法
	//Test test_4(0, 0, 0, 0, 0);
	//Test test_5(0, 0, { 1, 1 }, 0);

	return 0;
}

二、析构函数 

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

        特征:

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

        2. 无参数无返回值类型。

        3. 一个类只能有一个析构函数。若未显式定义,系统会自动生成认的析构函数。注意:析构函数不能重载。

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

        5. 编译器生成认析构函数,对自定类型成员调用它的析构函数

        代码测试:

#include <iostream>
using namespace std;

class Test {
public:
	Test(int a, int b, int c) {
		_a = a;
		_b = b;

		_c = (int*)malloc(c * sizeof(int));
		if (_c == nullptr) {
			perror("error");
			exit(-1);
		}
	}
	~test() {
		free(_c);
		_c = nullptr;
	}
private:
	int _a;
	int _b;

	int* _c;
};

int main() {
	Test test(0, 0, 1);
	return 0;
}

三、拷贝构造函数

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

        特征:

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

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

        3. 若未显式定义,编译器会生成认的拷贝构造函数认的拷贝构造函数对象按内存存储按字节序完成拷贝,这种拷贝叫做浅拷贝,或者值拷贝。(注:在编译器生成认拷贝构造函数中,内置类型是按照字节方式直接拷贝的,而自定义类型是调用其拷贝构造函数完成拷贝的。)

        代码测试:

#include <iostream>
#include <assert.h>
using namespace std;

class Test {
public:
	Test(int a, int b) {
		assert(b > 0);
		_a = a;
		_b = b;

		_c = (int*)malloc(b * sizeof(int));
		if (_c == nullptr) {
			perror("error");
			exit(-1);
		}
	}
	~test() {
		free(_c);
		_c = nullptr;
	}
	Test(const Test& t) {
		_a = t._a;
		_b = t._b;

		_c = (int*)malloc(t._b * sizeof(int));
		if (_c == nullptr) {
			perror("error");
			exit(-1);
		}
	}
private:
	int _a;
	int _b;

	int* _c;
};

int main() {
	Test test(0, 1);
	return 0;
}

四、赋值运算符重载

        C++为了增强代码的可读性引入了运算符重载,运算符重载是具有特殊函数名的函数,也具有其返回值类型,函数名字以及参数列表,其返回值类型与参数列表与普通的函数类似。

        函数名字:关键字operator后面接需要重载的运算符符号。

        函数原型:返回值类型 operator操作符(参数列表)

        注:

        1. 不能通过连接其他符号来创建新的操作符:比如operator@

        2. 重载操作符必须有一个类类型参数

        3. 用于内置类型的运算符,其含义不能改变,例如:内置的整型+,不 能改变其含义

        4. 作为类成员函数重载时,其形参看起来比操作数数目少1,因为成员函数的第一个参数为隐藏的this

        5.    .*   ::    sizeof    ? :    .   注意以上5个运算符不能重载。

        C++的认成员函数中存在赋值运算符重载。赋值运算符只能重载成类的成员函数而不能重载成全局。因为赋值运算符如果不显式实现,编译器会生成一个认的。此时用户再在类外自己实现一个全局的赋值运算符重载,就和编译器在类中生成认赋值运算符重载冲突了,故赋值运算符重载只能是类的成员函数

        用户没有显式实现赋值运算符重载时,编译器会生成一个认赋值运算符重载,以值的方式逐字节拷贝。但是内置类型成员变量是直接赋值的,而自定义类型成员变量需要调用对应类的赋值运算符重载完成赋值。因此如果类中未涉及到资源管理,赋值运算符是否实现都可以;一旦涉及到资源管理则必须要实现。

        对于 前置++和 后置++这两种单目运算符编译器运用函数重载实现区分。

        代码测试:

#include <iostream>
#include <assert.h>
#include <string.h>
using namespace std;

class Test {
public:
	Test(int a, int b) {
		assert(b > 0);
		_a = a;
		_b = b;

		_c = (int*)calloc(b, sizeof(int));
		if (_c == nullptr) {
			perror("error");
			exit(-1);
		}
	}
	~test() {
		free(_c);
		_c = nullptr;
	}
	Test(const Test& t) {
		_a = t._a;
		_b = t._b;

		_c = (int*)calloc(t._b, sizeof(int));
		if (_c == nullptr) {
			perror("error");
			exit(-1);
		}
	}
	Test& operator=(const Test& t) {
		if (this != &t) {
			_a = t._a;
			_b = t._b;
			memcpy(_c, t._c, sizeof(*t._c));
		}
		return *this;
	}
	// 前置++
	Test& operator++() {
		_a++;
		return *this;
	}
	// 后置++
	Test operator++(int) {
		Test ret(*this);
		_a++;
		return ret;
	}
private:
	int _a;
	int _b;

	int* _c;
};

int main() {

	//Test test_1(0, 1);
	//Test test_2(test_1);
	//test_1++;
	//++test_2;

	return 0;
}

五、取地址操作符和 const取地址操作符

        顾名思义,就是对类对象进行取地址操作,属于操作符重载。这两个认成员函数一般不用重新定义,编译器认会生成

#include <iostream>
using namespace std;

class Test {
public:
	Test* operator&() {
		return this;
	}
	const Test* operator&() const {
		return this;
	}
private:
	int _a;
};

int main() {
	Test test_1;
	const Test test_2;
	Test* p_test = &test_1;
	const Test* c_p_test = &test_1;
	c_p_test = &test_2;
	return 0;
}

相关文章

显卡天梯图2024最新版,显卡是电脑进行图形处理的重要设备,...
初始化电脑时出现问题怎么办,可以使用win系统的安装介质,连...
todesk远程开机怎么设置,两台电脑要在同一局域网内,然后需...
油猴谷歌插件怎么安装,可以通过谷歌应用商店进行安装,需要...
虚拟内存这个名词想必很多人都听说过,我们在使用电脑的时候...