C++ 类的构造函数和析构函数

上一篇函数重载做好了铺垫,这篇就正式开始介绍类的构造函数和析构函数

如有侵权,请联系删除,如有错误,欢迎大家指正,谢谢

构造函数

  • 因只有对象创建时,才会分配空间,类中非静态数据成员不能够在类内直接初始化或赋值,C++提供了构造函数对类的数据成员进行初始化,或者是赋值
  • C++中类的默认构造函数是一个空函数,什么也不做,如果用户在类中声明了构造函数,默认构造函数就不再起作用了
  • 构造函数没有返回值,名字与类名相同

注意

  1. 类的构造函数支持函数重载(重载的用法参考上篇文章)
  2. 类的构造函数一般作为类的公有( public )成员函数,在创建对象时可成功调用构造函数,若作为私有( private )或( protected )成员函数,创建对象时是无法访问的
  3. 类的构造函数有形参时可指定默认值,用法跟普通函数设置默认值一样,形参可全部指定默认值,也可部分默认值,部分有默认值也是从右向左连续指定,随意给形参指定默认值会报错,这与普通的函数给形参指定默认值用法一致
  4. 使用没有形参的构造函数时,定义对象时不需要加括号,使用有形参的构造函数,如果形参全部有默认值,也可以不传参数,也是不用加括号
  5. 构造函数除了对成员进行赋值的作用外,还可以利用参数列表对数据成员进行初始化,参数列表只需要在定义的时候写上就行了,初始化和赋值的区别在于,初始化是数据成员在定义的时候完成的( 像 int a = 10; 这是初始化 ),赋值是数据成员定义之后进行的( 像 int b; b = 12; 这是赋值 ),在重载的情况中,执行哪个构造函数就执行哪个初始化列表
class Test {
public:
	Test(int nn, double dd, char cc) :
		d(dd),
		n(nn),
		c(cc) 
	{
		n = 10;
		d = 3.4;
		c = 'b';
	}

	void show() {
		cout << n << " " << d << " " << c << endl;
	}

private:
	int n;
	double d;
	char c;
};

Test test;
test.show();
// 运行结果:10 3.4 b
// 构造函数可以理解为,定义时先用参数列表对数据成员进行初始化,然后又对数据成员进行赋值,最后的值是赋值的结果
  1. 初始化列表顺序对数据初始化的顺序是没有影响的,数据初始化的顺序与类中声明的顺序有关
  2. 类中数据成员有引用或者是const类型必须进行初始化,这两种类型不支持赋值操作
class Test {
public:
	Test(int& nn, const double dd) :
		d(dd),
		n(nn)
	{ }

	void show() {
		cout << n << " " << d << endl;
	}

private:
	int& n;
	const double d;
};

int n = 5;
Test test(n, 3.5);
test.show();
// 运行结果:5 3.5

拷贝构造

  • 拷贝构造本质上也是构造函数
  • 参数是本类的常引用的构造函数
  • 类中默认的拷贝构造函数,实现的是逐个复制非静态成员(成员的复制称为浅复制)值,复制的是成员的值,这种类中默认的拷贝构造函数实现的过程被称为浅拷贝
// ====== 浅拷贝示例代码 ======
class Test {
public:
	Test(int nn, double dd) :
		n(nn),
		d(dd)
	{ }

	Test(const Test& t) :   // 定义拷贝构造函数
		n(t.n),
		d(t.d)
	{ }

	void show() {
		cout << n << " " << d << endl;
	}

private:
	int n;
	double d;
};

// 调用:实例化一个对象,并用这个对象去初始化另一个对象时就会调用类的拷贝构造函数
Test t(5, 2.5); // 实例化一个对象
Test test1(t);  // 用对象t初始化另一个对象,调用拷贝构造函数
Test test2 = t; // 通过重载的 "=" 初始化对象,调用拷贝构造函数
Test test4 = Test(6, 10.5); // 通过临时变量初始化对象,先调用构造函数生成临时变量,再调用拷贝构造函数初始化对象test4
Test* test3 = new Test(t);  // 调用拷贝构造函数初始化对象
delete test3;
  • 浅拷贝方式对于一般的数据成员是"OK"的,当遇到实例化对象时在构造函数中为其申请了堆区的空间,在析构函数中对申请的堆区空间进行释放,不调用拷贝构造函数也是"OK"的,但系统默认的拷贝构造函数进行的是浅拷贝,它会把指针的值也同样复制给另一个对象的同一个成员,这样两个对象同时指向的是一块堆区空间,在对象生命周期结束时,它们都会调用各自的析构函数释放同一块空间,这就导致了空间的重复释放,这是浅拷贝存在的问题
  • 针对浅拷贝存在的问题,出现了深拷贝来解决这个问题,在深拷贝构造函数中,它不是再进行简单的给指针变量复制地址,而是给指针变量同样申请一块空间,这样在对象生命周期结束的时候调用析构函数就不会出现重复释放空间的问题了,这就是深拷贝的主要作用
// ====== 浅拷贝 ======
class Test {
public:
	Test(int n) :
		p(new int(n))
	{ }

	Test(const Test& t) :
		p(t.p)
	{ }

	~Test() {
		delete p;
	}

private:
	int* p;
};

Test t(10);
Test test(t);
// 程序运行出错,重复释放内存

// ====== 深拷贝 ======
class Test {
public:
	Test(int n) :
		p(new int(n))
	{ }

	Test(const Test& t) :
		p(new int(*t.p))
	{ }

	~Test() {
		delete p;
	}

private:
	int* p;
};

Test t(10);
Test test(t);
// 程序正常运行

临时变量

  • 临时变量的作用域是当前行
// 下面这条语句的执行过程是,OS创建一个临时变量假设是 int temp = 12; 然后c = temp; 
// 这个temp就是一个临时变量,它的作用域就是所在的这行语句
int c = int(12);

注意

  1. 类成员有指针变量时,通过拷贝构造给同类的对象初始化可以考虑使用深拷贝避免空间的重复释放,若没有指针变量使用浅拷贝就可以满足要求了
  2. 另外需要注意的一点是函数的返回值是一个临时变量
class Test {
public:
	Test(int n) :
		n(n)
	{ }

	Test(const Test& t) :
		n(t.n)
	{ }

	void show() {
		cout << n << endl;
	}

private:
	int n;
};

Test fun() {
	Test t(10);
	return t;
}

Test test(9);
// 相当于 test = Test(t); 临时变量的形式,如果不是有临时变量的话对象赋值是不会调用拷贝构造函数的,但是这条语句它调用了拷贝构造函数,说明它产生了临时变量,所以返回对象一般是用传堆区指针的方式
test = fun();

析构函数

  • 形式是构造函数名字前面加一个 “~”
  • 析构函数只有一个没有重载
  • 析构函数也没有形参
  • 析构函数是在对象生命周期结束时自动调用的,它负责清理工作
  • 与构造函数相同,类中都包含一个默认的析构函数,若类中声明了析构函数,默认的析构函数就失去了作用
发布了25 篇原创文章 · 获赞 3 · 访问量 639

猜你喜欢

转载自blog.csdn.net/xiao_ma_nong_last/article/details/104061806