c++构造函数总结

1、使用构造函数的目的:为了在定义的时候,马上给变量进行赋值(防止多线程赋值出错)。

2、析构函数 : ~+类名  没有任何的参数,也没有返回值。

3、构造函数的几点总结:

3.1 构造函数名字跟类型是一样的,没有返回值,但可以重载。

3.2 如果显示的写了一个普通构造函数, 会隐藏默认的无惨构造函数,但不会隐藏默认的拷贝构造函数。

3.3 如果显示的写了一个拷贝构造函数 ,会隐藏默认的无参构造函数和默认的拷贝构造函数

3.4 如果显示的写了一个析构函数, 会隐藏默认的析构函数

3.5 不要在构造函数中嵌套构造函数


4、关于拷贝构造函数:

class Test
{
public:

	Test(int x, int y)
	{
		m_x = x;
		m_y = y;
	}
	
	//拷贝构造函数 ,想通过另一个Test对象 将本对象进行拷贝。
	Test(const Test & another)//注意这里不能写成指针 const Test * another 拷贝构造函数的形式是固定的。
	{
		m_x = another.m_x;
		m_y = another.m_y;
	}
	
	//等号操作符
	void operator = (const Test &t)
	{
		m_x = t.m_x;
		m_y = t.m_y;
	}

private:
	int m_x;
	int m_y;
};
//调用拷贝构造函数的两种方式
Test t2(10, 20);

Test t3(t2); //调用t3的拷贝构造函数  

Test t4 = t2; //调用t4的拷贝构造函数

Test t5; //先调用无惨构造。
t5 = t2; //不会调用拷贝构造函数,而是调用=号重载操作符,因为构造函数只有在初始化的时候会调用,赋值是不会调用构造函数的。

5、构造函数执行过程分析

class Test
{
public:
	Test(int x, int y)
	{

		m_x = x;
		m_y = y;
		cout << "调用了有参数的构造函数" << endl;
	}

	//无参数的构造函数
	Test(){
		m_x = 0;
		m_y = 0;
		cout << "调用了无参数的构造函数" << endl;
	}


	//拷贝构造函数 
	Test(const Test & another)
	{
		m_x = another.m_x;
		m_y = another.m_y;
		cout << "调用了拷贝构造函数" << endl;
	}

	//等号操作符
	void operator = (const Test &t)
	{
		cout << "调用了=号操作符" << endl;
		m_x = t.m_x;
		m_y = t.m_y;
	}

	void printT()
	{
		cout << "x : " << m_x << ", y : " << m_y << endl;
	}

	//提供一个析构函数
	~Test()
	{
		cout << "~Test()析构函数被执行了" << endl;
		cout << "(" << m_x << ", " << m_y << ")" << "被析构了" << endl;
	}

private:
	int m_x;
	int m_y;
};



Test func()
{
	cout << "func begin..." << endl;
	Test temp(10, 20); //调用temp的带参数构造函数  
	Test temp1(100, 200); //调用temp的带参数构造函数  
	cout << "func end.." << endl;
	return temp; //创建一个临时的匿名对象 = temp ,把temp的数据给到了临时的匿名对象。
				//调用这个临时匿名对象的拷贝构造函数, 将temp传进去。
}

void test1() 
{
	cout << "test1 begin " << endl;
	func();

	/*
	匿名对象在此被析构了, 如果一个临时的匿名对象,没有任何变量去接收它,
	编译器认为这个临时匿名对象没有用处。会立刻销毁这个临时的匿名对象。
	*/
	cout << "test1 end" << endl;
}

/*
调用test1的执行结果:
test1 begin 
func begin...
调用了有参数的构造函数
调用了有参数的构造函数
func end..
~Test()析构函数被执行了
(100, 200)被析构了
~Test()析构函数被执行了
(10, 20)被析构了
test1 end

*/

void test2()
{
	cout << "test2 begin ..." << endl;
	Test t1 = func();
	
	
	//如果有一个变量去接收这个临时的匿名对象, 编译器认为这个匿名对象有用,就不会立刻给他销毁。
	
	/*
	t1 = 匿名的临时对象 为什么不会发生拷贝构造??
	
	此时的t1 去接收这个匿名的临时对象,不是重新创建一个t1,而是给这个匿名对象起个名字就叫t1。
	一旦这个匿名对象有了自己的名字,编译器就不会立刻给这个匿名对象销毁了,就当普通局部变量处理了。
	
	可以理解为匿名变量只有灵魂,没有肉体,执行 Test t1 = func() 相当于让 t1 变成他的肉体。
	
	再说说拷贝构造:
	Test t2(10,20);
	Test t1 = t2; 
	这种情况是拷贝构造,通过一个对象给另一个对象赋值,t1,t2都是有灵魂有肉体的。
	*/
	cout << "test2 end..." << endl;

	//在此时析构的t1
}

/*
调用test2的执行结果:
test2 begin ...
func begin...
调用了有参数的构造函数
调用了有参数的构造函数
func end..
~Test()析构函数被执行了
(100, 200)被析构了
test2 end...
~Test()析构函数被执行了
(10, 20)被析构了

下面是分析内容:
为什么只被析构了两次??
因为 Test t1 = func(); 从宏观上看只有一个变量t1。
*/

void test3()
{
	cout << "test3 begin..." << endl;
	Test t1; //调用t1的无参数构造函数
	t1 = func(); 
	/*
	调用了t1的=号操作符 , t1 = 匿名对象。
	此时匿名对象并没有找到一个宿主,因为t1本身就已经存在了,
	所以编译器就会立刻销毁他。
	*/
	cout << "test3 end..." << endl;
}

/*
调用test3的执行结果:
test3 begin...
调用了无参数的构造函数
func begin...
调用了有参数的构造函数
调用了有参数的构造函数
func end..
~Test()析构函数被执行了
(100, 200)被析构了
调用了=号操作符
~Test()析构函数被执行了
(10, 20)被析构了
test3 end...
~Test()析构函数被执行了
(10, 20)被析构了


下面是分析内容:
为什么被析构了三次??
因为从宏观上看有两个变量,一个是t1,一个是匿名的临时变量。
*/

int main(void)
{
	
	// test1();
	// test2();
	test3();


	return 0;
}

6、浅拷贝

class Teacher
{
public:
	//有参数的构造函数
	Teacher(int id, const char *name)
	{
		cout << "调用了Teacher 的构造函数" << endl;
		//是给id 赋值
		m_id = id;

		//给姓名赋值
		int len = strlen(name);
		m_name = (char*)malloc(len + 1);
		strcpy(m_name, name);
	}

	~Teacher() {
		//在构造函数中, 已经开辟了内存 所以为了防止泄露
		//在析构函数中,在对象销毁之前,把m_name的内存释放掉
		if (m_name != NULL) {
			free(m_name);
			m_name = NULL;
			cout << "释放掉了m_name" << endl;
		}
	}
private:
	int m_id;
	char *m_name;
};

int main(void)
{
	Teacher t1(1, "zhang3");

	//如果不提供一个显示的拷贝构造函数, 通过系统自带的默认拷贝构造函数
	Teacher t2(t1); //会调用t2的拷贝构造函数,将t1的值拷贝给t2
	
	return 0;
}

/*
执行结果:	
调用了Teacher 的构造函数
释放掉了m_name
*** Error in `./a.out': double free or corruption (fasttop): 0x08c79008 ***
Aborted (core dumped)

原因:调用了系统默认的拷贝构造函数,指针只是进行了简单的赋值,两个对象t1,t2
内部的指针成员变量指向的是同一个地址。当t2对象析构后,释放了这块内存。但t1
对象中这个指针值还不是null,所以又进行了一次释放,造成内存泄露。
*/

7、深拷贝

class Teacher
{
public:
	//有参数的构造函数
	Teacher(int id, const char *name)
	{
		cout << "调用了Teacher 的构造函数" << endl;
		//是给id 赋值
		m_id = id;

		//给姓名赋值
		int len = strlen(name);
		m_name = (char*)malloc(len + 1);
		strcpy(m_name, name);
	}

	//显示写一个拷贝构造函数
	//通过显示拷贝构造函数提供了深拷贝的动作
	Teacher(const Teacher &another)
	{
		m_id = another.m_id; //给id赋值

		int len = strlen(another.m_name);
		m_name = (char*)malloc(len + 1);

		strcpy(m_name, another.m_name);
	}
	~Teacher() {
		//在构造函数中, 已经开辟了内存 所以为了防止泄露
		//在析构函数中,在对象销毁之前,把m_name的内存释放掉
		if (m_name != NULL) {
			free(m_name);
			m_name = NULL;
			cout << "释放掉了m_name" << endl;
		}
	}
private:
	int m_id;
	char *m_name;
};

int main(void)
{
	Teacher t1(1, "zhang3");
	
	Teacher t2(t1); 
	
	return 0;
}
/*
执行结果:	
调用了Teacher 的构造函数
释放掉了m_name
释放掉了m_name
*/

总结:当类的成员变量有指针时,一定要手动写拷贝构造函数。否则会造成浅拷贝。


8、构造函数的初始化列表

class A
{
public:
	A(int a)
	{
		m_a = a;
		cout << "a = " << m_a << "调用了构造函数" << endl;
	}
	
	void printA()
	{
		cout << " a = " << m_a << endl;
	}
	
	~A()
	{
		cout << "a = " << m_a << "被析构了" << endl;
	}

private:
	int m_a;
};

class B
{
public:
	B(int b) :m_a1(10), m_a2(100) //在初始化B的时候通过初始化列表给内部对象a1 和a2 进行了初始化
	{
		m_b = b;
		/*
		m_a1(10);//这样子是不行的
		m_a2(20);
		*/
		cout << "b = " << m_b << "调用了构造函数" << endl;
	}
	
	B(A &a1,A &a2,int b) :m_a1(a1), m_a2(a2) //调用A的拷贝构造函数,可以在A中手动添加拷贝构造函数进行测试。
	{
		m_b = b;
		cout << "b = " << m_b << "调用了构造函数" << endl;
	}

	B(int aa1, int aa2, int b) : m_a1(aa1), m_a2(aa2), m_b(b) //通过初始化列表不仅能够初始化成员对象, 还可以初始化成员变量 
	{
		//其中	m_a1(aa1), m_a2(aa2) 会调用A类的有参构造函数
	}

	~B()
	{
		cout <<"b = " <<m_b << " 调用了析构函数" << endl;
	}

private:
	int m_b;
	// const int m_m; //常量成员变量不能够赋值,只能通过初始化列表进行初始化
	A m_a2;//这样写初始化列表就会先初始化 m_a2 再初始化 m_a1 。跟初始化列表中的顺序无关
	A m_a1;
};

使用场景:

1、类中包含其他类

2、类中存在常量

猜你喜欢

转载自blog.csdn.net/amumu_123/article/details/81395501