C++基础3 类:构造 拷贝 析构函数,

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了C++基础3 类:构造 拷贝 析构函数,相关的知识,希望对你有一定的参考价值。

为什么会出现构造函数 与 析构函数

[email protected]:~/c++$ cat main.cpp 
#include <iostream>
#include <stdlib.h>
#include <string.h>
using namespace std;

class Test
{
public:
	void init()
	{
		a = 1;
		b = 2;
	}
private:
	int a;
	int b;
};

int main()
{
	Test arr[3] ;
	arr[0].init();	//显式的执行初始化
	arr[1].init();
	arr[2].init();
	
	Test haha[19999] ;	//请问这个怎么去初始化

	return 0;
}

[email protected]:~/c++$ g++ -g main.cpp  && ./a.out




类的构造函数 与 析构函数

[email protected]:~/c++$ cat main.cpp 
#include <iostream>
using namespace std;
class Test
{
public:
	Test()
	{
		cout << "构造函数 \n";
	}
	~Test()
	{
		cout << "析构函数 \n";
	}
};

int main()
{
	Test t1;
	Test t2;
	cout << "Hello World \n";
	return 0;
}

[email protected]:~/c++$ g++ -g -Wall main.cpp   && ./a.out 
构造函数 
构造函数 
Hello World 
析构函数 
析构函数




构造函数初始化变量:

析构函数释放内存

[email protected]:~/c++$ cat main.cpp 
#include <iostream>
#include <stdlib.h>
#include <string.h>
using namespace std;
class Test
{
public:
	Test()
	{
		a = 10;
		p = (char *)malloc(100);
		strcpy(p,"Hello Linux!");
		cout << "构造函数 \n";
	}
	void printf_var()
	{
		cout << a << endl;
		cout << p << endl;
	}
	~Test()
	{
		if(p != NULL)
		{
			free(p);
		}
		cout << "析构函数 \n";
	}
private:
	char *p;
	int a;
};
void fun()
{
	Test t1;
	Test t2;
	t1.printf_var();
}

int main()
{
	fun();
	cout << "-------------\n";
	return 0;
}

[email protected]:~/c++$ g++ -Wall -g main.cpp  && ./a.out 
构造函数 
构造函数 
10
Hello Linux!
析构函数 
析构函数 
-------------
[email protected]:~/c++$




类的构造函数3种初始化方式:

[email protected]:~/c++$ cat main.cpp 
#include <iostream>
#include <stdlib.h>
#include <string.h>
using namespace std;

class Test
{
public:
	Test()
	{
		cout << "没有默认参数的构造函数 \n";
	}

	Test(int _a)
	{
		a = _a;
		cout << "a=" << a <<"带有1个默认参数的构造函数 \n";
	}

	Test(int _a,int _b)
	{
		a = _a;
		b = _b;
		cout << "a=" << a << "  b=" << b<< "带有2个默认参数的构造函数 \n";
	}

	Test(const Test &OBJ)	//用一个对象初始化另一个对象
	{
		cout << "赋值构造函数构造函数 \n";
	}
	~Test()
	{
		cout << "析构函数 \n";
	}
private:
	int a;
	int b;
};
void fun()
{
	Test t1;	//调用无参数的构造函数

	//===== 	对象的初始化	==========
	/*1*/Test t2(1,2);
	//逗号表达式的最后一个值是整个表达式的值  g++ -Wall 编译有警告:逗号操作符的左操作数没有效果
	/*2*/Test t3 = (3,4,5,6,7);	//这个等号C++编译器做了增强
	/*3*/Test t4 = Test(1,4);/*匿名对象*/
	t1 = t4;	//对象的赋值,这个等号是普通的赋值操作

	Test t5(t2);
}

int main()
{
	fun();
	cout << "-------------\n";
	return 0;
}

正常编译:
[email protected]:~/c++$ g++  -g main.cpp  && ./a.out 
没有默认参数的构造函数 
a=1  b=2带有2个默认参数的构造函数 
a=7带有1个默认参数的构造函数 
a=1  b=4带有2个默认参数的构造函数 
赋值构造函数构造函数 
析构函数 
析构函数 
析构函数 
析构函数 
析构函数 
-------------


g++ -Wall编译
[email protected]:~/c++$ g++ -Wall -g main.cpp  && ./a.out 
main.cpp: In function ‘void fun()’:
main.cpp:44:20: warning: left operand of comma operator has no effect [-Wunused-value]
  /*2*/Test t3 = (3,4,5,6,7);
                    ^
没有默认参数的构造函数 
a=1  b=2带有2个默认参数的构造函数 
a=7带有1个默认参数的构造函数 
a=1  b=4带有2个默认参数的构造函数 
赋值构造函数构造函数 
析构函数 
析构函数 
析构函数 
析构函数 
析构函数 
-------------
[email protected]:~/c++$



【拷贝构造函数应用的4种时机】

1,抛题 -- 乱码的出现:

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

class Test
{
public:
	Test(int _a,int _b)
	{
		cout << "2个参数\n";
		a = _a;
		b = _b;
	}
	Test(Test &OBJ)
	{
		cout << "赋值构造函数被调用 \n";
	}
	void fun1()
	{
		cout << "a="<< a << "  ";
		cout << "b="<< b << "  \n";
	}
private:
	int a;
	int b;
};

int main()
{
	Test t1(1,5);	t1.fun1();
	Test t2 = t1;	
	//用t1 的参数初始化t2.会调用赋值构造函数,
	//如果没有编译器自己造,如果自定义了赋值构造函数,就直接调用
	t2.fun1();	//此时 输出属性值是乱码。因为我们自定义的构造函数什么也没有做
	return 0;
}

[email protected]:~/c++$ g++ -g main.cpp  && ./a.out 
2个参数
a=1  b=5  
拷贝构造函数被调用 
a=-336889536  b=32764





拷贝构造函数的使用:

第1种调用方法: Test t2 = t1;

[email protected]:~/c++$ cat main.cpp 
//拷贝构造函数应用的4种时机
#include <iostream>
#include <stdlib.h>
#include <string.h>
using namespace std;

class Test
{
public:
	Test(int _a,int _b)
	{
		cout << "2个参数\n";
		a = _a;
		b = _b;
	}
	Test(const Test &OBJ)
	{
		a = OBJ.a + 100;
		b = OBJ.a + 150;
		cout << "拷贝构造函数被调用 \n";
	}
	void fun1()
	{
		cout << "a="<< a << "  ";
		cout << "b="<< b << "  \n";
	}
private:
	int a;
	int b;
};

int main()
{
	Test t1(1,5);	t1.fun1();
	Test t2 = t1;	
	t2.fun1();	
	return 0;
}

[email protected]:~/c++$ g++ -g main.cpp  && ./a.out 
2个参数
a=1  b=5  
拷贝构造函数被调用 
a=101  b=151


拷贝构造函数的第2 中调用方式:Test t2(t1);

[email protected]:~/c++$ cat main.cpp 
//拷贝构造函数应用的4种时机
#include <iostream>
#include <stdlib.h>
#include <string.h>
using namespace std;

class Test
{
public:
	Test(int _a,int _b)
	{
		cout << "2个参数\n";
		a = _a;
		b = _b;
	}
	Test(const Test &OBJ)
	{
		a = OBJ.a + 100;
		b = OBJ.a + 150;
		cout << "拷贝构造函数被调用 \n";
	}
	void fun1()
	{
		cout << "a="<< a << "  ";
		cout << "b="<< b << "  \n";
	}
private:
	int a;
	int b;
};

int main()
{
	Test t1(1,5);	t1.fun1();
	Test t2(t1);	t2.fun1();
	return 0;
}

[email protected]:~/c++$ g++ -g main.cpp  && ./a.out 
2个参数
a=1  b=5  
拷贝构造函数被调用 
a=101  b=151


【答疑】对象的赋值操作t2 = t1 ,不会调用赋值构造函数:

[email protected]:~/c++$ cat main.cpp 
[email protected]:~/c++$ cat main.cpp 
//拷贝构造函数应用的4种时机
#include <iostream>
#include <stdlib.h>
#include <string.h>
using namespace std;

class Test
{
public:
	Test(int _a,int _b)
	{
		cout << "2个参数\n";
		a = _a;
		b = _b;
	}
	Test(const Test &OBJ)
	{
		a = OBJ.a + 100;
		b = OBJ.a + 150;
		cout << "赋值构造函数被调用 \n";
	}
	void fun1()
	{
		cout << "a="<< a << "  ";
		cout << "b="<< b << "  \n";
	}
private:
	int a;
	int b;
};

int main()
{
	Test t1(1,5);	t1.fun1();
	Test t2(99,99);	t2.fun1();
	t2 = t1;	t2.fun1();
	return 0;
}

[email protected]:~/c++$ g++ -g main.cpp  && ./a.out 
2个参数
a=1  b=5  
2个参数
a=99  b=99  
a=1  b=5



赋值构造函数的第3中应用:函数

[email protected]:~/c++$ cat main.cpp 
//拷贝构造函数应用的4种时机
#include <iostream>
#include <stdlib.h>
#include <string.h>
using namespace std;

class Test
{
public:
	Test(int _a,int _b)
	{
		cout << "2个参数\n";
		a = _a;
		b = _b;
	}
	Test(const Test &OBJ)
	{
		a = OBJ.a + 100;
		b = OBJ.a + 150;
		cout << "赋值构造函数被调用 \n";
	}
	~Test()
	{
		cout << "a=" << a <<" b =" << b<< "析构函数被调用\n";
	}
	void fun1()
	{
		cout << "a="<< a << "  ";
		cout << "b="<< b << "  \n";
	}
private:
	int a;
	int b;
};
void fun2(Test OBJ)	//会自动调用类的赋值构造函数
{
	OBJ.fun1();
}
void fun1()
{
	Test t1(1,5);	t1.fun1();
	Test t2 = t1;	t2.fun1();
	cout << "t2已经完成初始化\n";
	fun2(t2);	//t2实参会初始化形参OBJ,会调用赋值构造函数
}

int main()
{
	fun1();
	return 0;
}

[email protected]:~/c++$ g++ -Wall -g main.cpp  && ./a.out 
2个参数
a=1  b=5  
赋值构造函数被调用 
a=101  b=151  
t2已经完成初始化
赋值构造函数被调用 
a=201  b=251   
a=201 b =251析构函数被调用
a=101 b =151析构函数被调用
a=1 b =5析构函数被调用




【难点】赋值构造函数应用4 

【匿名对象的去和留】-- 去

1,没有对象接收函数的返回 

在VS编译下,返回的对象会调用一次析构函数,然后没人接收就析构掉

在GCC下,不会出现这种情况


【这第4种方法 VS环境 与 GCC 表现不一样】


//拷贝构造函数应用的4种时机
#include <iostream>
#include <stdio.h>
using namespace std;

class Test
{
public:
	Test(int _a, int _b)
	{
		a = _a;
		b = _b;
		cout << "a=" << a << " b =" << b << "构造函数初始化, 有2个参数\n";
	}
	Test(const Test &OBJ)
	{
		a = OBJ.a + 1;
		b = OBJ.b + 1;
		cout << "a=" << a << " b =" << b << "赋值构造函数被调用 \n";
	}
	~Test()
	{
		cout << "a=" << a << " b =" << b << "析构函数被调用\n";
	}
	void fun1()
	{
		cout << "a=" << a << "  ";
		cout << "b=" << b << "  \n";
	}
private:
	int a;
	int b;
};

//函数返回一个Test 对象 (复杂类型的)
//返回的是一个新的匿名对象
//所以会调用匿名对象类的copy构造函数
Test fun1()
{
	Test A(66, 77);
	return A;
	//新的对象作为匿名对象返回
}

//用这个函数观测对象的生命周期
void fun2()
{
	//微软VS环境有效:用b对象b来接返回的匿名对象,不会直接析构
	//GCC 既不执行copy构造函数,也不执行析构函数
	fun1();
}

int main()
{
	fun2();
	getchar();
	return 0;
}
GCC编译运行:
[email protected]:~/c++$ g++ -Wall -g main.cpp  && ./a.out 
a=66 b =77构造函数初始化, 有2个参数
a=66 b =77析构函数被调用


VS编译运行:
C:\Users\chunli\Documents\c_c++\ConsoleApplication2\Debug>ConsoleApplication2
a=66 b =77构造函数初始化, 有2个参数
a=67 b =78赋值构造函数被调用
a=66 b =77析构函数被调用
a=67 b =78析构函数被调用



【难点】赋值构造函数应用4 

【匿名对象的去和留】-- 留

1,创建一个对象来接收函数的返回 

在VS编译下,返回的对象会调用一次析构函数,然后被接收就不会直接析构掉

在GCC下,不会出现这种情况

//拷贝构造函数应用的4种时机
#include <iostream>
#include <stdio.h>
using namespace std;

class Test
{
public:
	Test(int _a, int _b)
	{
		a = _a;
		b = _b;
		cout << "a=" << a << " b =" << b << "构造函数初始化, 有2个参数\n";
	}
	Test(const Test &OBJ)
	{
		a = OBJ.a + 1;
		b = OBJ.b + 1;
		cout << "a=" << a << " b =" << b << "赋值构造函数被调用 \n";
	}
	~Test()
	{
		cout << "a=" << a << " b =" << b << "析构函数被调用\n";
	}
	void fun1()
	{
		cout << "a=" << a << "  ";
		cout << "b=" << b << "  \n";
	}
private:
	int a;
	int b;
};

//函数返回一个Test 对象 (复杂类型的)
//返回的是一个新的匿名对象
//所以会调用匿名对象类的copy构造函数
Test fun1()
{
	Test A(66, 77);
	return A;
	//新的对象作为匿名对象返回
}

//用这个函数观测对象的生命周期
void fun2()
{
	//微软VS环境有效:用b对象b来接返回的匿名对象,不会直接析构
	//GCC 既不执行copy构造函数,也不执行析构函数
	//注意此时是一个新的对象,VS会调用copy构造函数,不会析构
	Test b = fun1();
	b.fun1();
}

int main()
{
	fun2();
	getchar();
	return 0;
}
GCC下编译运行:
[email protected]:~/c++$ g++ -Wall -g main.cpp  && ./a.out 
a=66 b =77构造函数初始化, 有2个参数
a=66  b=77  
a=66 b =77析构函数被调用

VS下编译运行:
C:\Users\chunli\Documents\c_c++\ConsoleApplication2\Debug>ConsoleApplication2
a=66 b =77构造函数初始化, 有2个参数
a=67 b =78赋值构造函数被调用
a=66 b =77析构函数被调用
a=67  b=78
a=67 b =78析构函数被调用




【难点】赋值构造函数应用4 

【匿名对象的去和留】-- 去

1,创建一个对象来接收函数的返回 

在VS编译下,返回的对象会调用一次析构函数,然后被接收就不会直接析构掉

在GCC下,不会出现这种情况

[email protected]:~/c++$ cat main.cpp 
//拷贝构造函数应用的4种时机
#include <iostream>
#include <stdio.h>
using namespace std;

class Test
{
public:
	Test(int _a, int _b)
	{
		a = _a;
		b = _b;
		cout << "a=" << a << " b =" << b << "构造函数初始化, 有2个参数\n";
	}
	Test(const Test &OBJ)
	{
		a = OBJ.a + 1;
		b = OBJ.b + 1;
		cout << "a=" << a << " b =" << b << "赋值构造函数被调用 \n";
	}
	~Test()
	{
		cout << "a=" << a << " b =" << b << "析构函数被调用\n";
	}
	void fun1()
	{
		cout << "执行对象的成员函数  ";
		cout << "a=" << a << "  ";
		cout << "b=" << b << "  \n";
	}
private:
	int a;
	int b;
};

//函数返回一个Test 对象 (复杂类型的)
//返回的是一个新的匿名对象
//所以会调用匿名对象类的copy构造函数
Test fun1()
{
	cout << "fun1 start \n" ;
	//A  是局部变量,会被析构
	Test A(66, 77);
	return A;
	//新的对象作为匿名对象返回
	cout << "fun1 end \n" ;
}

//用这个函数观测对象的生命周期
void fun2()
{
	//微软VS环境有效:用b对象b来接返回的匿名对象,不会直接析构
	//GCC 既不执行copy构造函数,也不执行析构函数
	cout << "fun2 start \n" ;
	Test b(1,2);
	b.fun1();
	cout << "因为此时匿名对象并没有转换成新对象,匿名对象就会析构\n";
	b =  fun1();
	b.fun1();
	cout << "fun2 end \n" ;
}

int main()
{
	fun2();
	getchar();
	return 0;
}
[email protected]:~/c++$ 
GCC编译运行:
[email protected]:~/c++$ g++ -Wall -g main.cpp  && ./a.out 

fun2 start 
a=1 b =2构造函数初始化, 有2个参数
执行对象的成员函数  a=1  b=2  
因为此时匿名对象并没有转换成新对象,匿名对象就会析构
fun1 start 
a=66 b =77构造函数初始化, 有2个参数
a=66 b =77析构函数被调用
执行对象的成员函数  a=66  b=77  
fun2 end 
a=66 b =77析构函数被调用
[email protected]:~/c++$ 

VS编译运行:
C:\Users\chunli\Documents\c_c++\ConsoleApplication2\Debug>ConsoleApplication2
fun2 start
a=1 b =2构造函数初始化, 有2个参数
执行对象的成员函数  a=1  b=2
因为此时匿名对象并没有转换成新对象,匿名对象就会析构
fun1 start
a=66 b =77构造函数初始化, 有2个参数
a=67 b =78赋值构造函数被调用
a=66 b =77析构函数被调用
a=67 b =78析构函数被调用
执行对象的成员函数  a=67  b=78
fun2 end
a=67 b =78析构函数被调用


结论: 有关 匿名对象的去和留【VS环境】

如果用匿名对象  初始化 另外一个同类型的对象, 匿名对象 转成有名对象

如果用匿名对象  赋值给 另外一个同类型的对象, 匿名对象 被析构


老师的笔记与图:技术分享老师的程序:

#include <iostream>
using namespace std;


class Location 
{ 
public:
	Location( int xx = 0 , int yy = 0 ) 
	{ 
		X = xx ;  Y = yy ;  cout << "Constructor Object.\n" ; 
	}

	//copy构造函数  完成对象的初始化
	Location(const Location & obj) //copy构造函数 
	{
		X = obj.X; Y = obj.Y;
	}
	~Location() 
	{ 
		cout << X << "," << Y << " Object destroyed." << endl ; 
	}
	int  GetX () { return X ; }		int GetY () { return Y ; }
private :   int  X , Y ;
} ;

//g函数 返回一个元素 
//结论1 : 函数的返回值是一个元素 (复杂类型的), 返回的是一个新的匿名对象(所以会调用匿名对象类的copy构造函数)

//
//结论2: 有关 匿名对象的去和留
//如果用匿名对象  初始化 另外一个同类型的对象, 匿名对象 转成有名对象
//如果用匿名对象  赋值给 另外一个同类型的对象, 匿名对象 被析构

//
//你这么写代码,设计编译器的大牛们:
//我就给你返回一个新对象(没有名字 匿名对象)
Location g()
{
	Location A(1, 2);
	return A;
}

//
void objplay2()
{
	g(); 
}

//
void objplay3()
{
	//用匿名对象初始化m 此时c++编译器 直接把匿名对转成m;(扶正) 从匿名转成有名字了m
	Location m = g(); 
	printf("匿名对象,被扶正,不会析构掉\n");
	cout<<m.GetX()<<endl;;
}

void objplay4()
{
	//用匿名对象 赋值给 m2后, 匿名对象被析构
	Location m2(1, 2);
	m2 = g();
	printf("因为用匿名对象=给m2, 匿名对象,被析构\n");
	cout<<m2.GetX()<<endl;;
}
void main()
{
	//objplay2();
	//objplay3();
	objplay4();
	cout<<"hello..."<<endl;
	system("pause");
	return ;
}




【回顾上午】




构造函数初始化的三种方式:

[email protected]:~/c++$ cat main.cpp 
#include <iostream>
#include <stdio.h>
#include <stdlib.h>
using namespace std;
class Test
{
public:
	Test(int _a)
	{
		a = _a;
		cout << "a=" << a << "构造函数初始化, 有1个参数\n";
	}
	Test(int _a,int _b)
	{
		a = _a;
		b = _b;
		cout << "a=" << a  << "  b= " << b << "构造函数初始化, 有2个参数\n";
	}
	void fun()
	{
		cout << "执行对象的成员函数  ";
		cout << "a=" << a << "  ";
		cout << "b=" << b << "  \n";
	}
private:
	int a;
	int b;
};

int main()
{
	Test t1(1,2);		t1.fun();//C++编译器自动调用构造函数
	Test t2 = (1,5,6);	t2.fun();//C++编译器自动调用构造函数
	Test t3 = Test(4,5);		//程序员手动调用构造函数
	return 0;
}
[email protected]:~/c++$ g++ -g main.cpp  && ./a.out 
a=1  b= 2构造函数初始化, 有2个参数
执行对象的成员函数  a=1  b=2  
a=6构造函数初始化, 有1个参数
执行对象的成员函数  a=6  b=0  
a=4  b= 5构造函数初始化, 有2个参数




【回顾】拷贝构造函数的4种使用时机

GCC编译器 与 VS编译有些区别:

[email protected]:~/c++$ cat main.cpp 
#include <iostream>
#include <stdio.h>
#include <stdlib.h>
using namespace std;
int num = 0;
class Test
{
public:
	Test(int _a)
	{
		a = _a;
		cout << "a=" << a << "构造函数初始化, 有1个参数\n";
	}
	Test(int _a,int _b)
	{
		a = _a;
		b = _b;
		cout << "a=" << a  << " b=" << b << "构造函数初始化, 有2个参数\n";
	}
	Test(const Test &OBJ)
	{
		a = OBJ.a + 1;
		b = OBJ.b + 1;
		num++;
		cout << "第"<< num <<"次调用拷贝构造函数";
		cout << "a=" << a << "  b=" << b <<endl ;
	}
	~Test()
	{
		cout << "a=" << a << " b=" << b << "析构函数被调用\n";
	}
	void fun()
	{
		cout << "执行对象的成员函数   ";
		cout << "a=" << a << "  ";
		cout << "b=" << b << "  \n";
	}
private:
	int a;
	int b;
};

void fun1()
{
	cout << "拷贝函数的调用时机,第1种和第2种 \n";
	Test t1(1,2);		t1.fun();
	Test t2 = t1;		t2.fun();	//C++编译器会调用拷贝构造函数
	Test t3(t2) ;		t3.fun();	//C++编译器会调用拷贝构造函数
}
void fun2(Test OBJ)
{
	cout << "拷贝函数的调用时机,第3种\n";
}

Test fun3()	//返回一个Test类
{
	cout << "拷贝函数的调用时机,第4种\n";
	Test t1(7,8);
	return t1;
}

int main()
{
	fun1();	//拷贝函数的调用时机,第1种和第2种
	Test t1(4,5);	fun2(t1);	//拷贝函数的调用时机,第3种
	Test t2 = fun3();		//弄了一个新的对象接受了匿名对象,这个不会直接执行析构函数
	Test t3(11,12);	t3 =  fun3();	//t3不是一个新的对象,匿名类会执行析构函数
	return 0;
}
1 GCC编译执行:
[email protected]:~/c++$ g++ -Wall -g main.cpp  && ./a.out 
拷贝函数的调用时机,第1种和第2种 
a=1 b=2构造函数初始化, 有2个参数
执行对象的成员函数   a=1  b=2  
第1次调用拷贝构造函数a=2  b=3
执行对象的成员函数   a=2  b=3  
第2次调用拷贝构造函数a=3  b=4
执行对象的成员函数   a=3  b=4  
a=3 b=4析构函数被调用
a=2 b=3析构函数被调用
a=1 b=2析构函数被调用
a=4 b=5构造函数初始化, 有2个参数
第3次调用拷贝构造函数a=5  b=6
拷贝函数的调用时机,第3种
a=5 b=6析构函数被调用
拷贝函数的调用时机,第4种
a=7 b=8构造函数初始化, 有2个参数
a=11 b=12构造函数初始化, 有2个参数
拷贝函数的调用时机,第4种
a=7 b=8构造函数初始化, 有2个参数
a=7 b=8析构函数被调用
a=7 b=8析构函数被调用
a=7 b=8析构函数被调用
a=4 b=5析构函数被调用
[email protected]:~/c++$ 

2 VS环境编译执行:
C:\Users\chunli\Documents\c_c++\ConsoleApplication2\Debug>ConsoleApplication2
拷贝函数的调用时机,第1种和第2种
a=1 b=2构造函数初始化, 有2个参数
执行对象的成员函数   a=1  b=2
第1次调用拷贝构造函数a=2  b=3
执行对象的成员函数   a=2  b=3
第2次调用拷贝构造函数a=3  b=4
执行对象的成员函数   a=3  b=4
a=3 b=4析构函数被调用
a=2 b=3析构函数被调用
a=1 b=2析构函数被调用
a=4 b=5构造函数初始化, 有2个参数
第3次调用拷贝构造函数a=5  b=6
拷贝函数的调用时机,第3种
a=5 b=6析构函数被调用
拷贝函数的调用时机,第4种
a=7 b=8构造函数初始化, 有2个参数
第4次调用拷贝构造函数a=8  b=9
a=7 b=8析构函数被调用
a=11 b=12构造函数初始化, 有2个参数
拷贝函数的调用时机,第4种
a=7 b=8构造函数初始化, 有2个参数
第5次调用拷贝构造函数a=8  b=9
a=7 b=8析构函数被调用
a=8 b=9析构函数被调用
a=8 b=9析构函数被调用
a=8 b=9析构函数被调用
a=4 b=5析构函数被调用



【关于默认构造函数】

默认构造函数:

二个特殊的构造函数

1)默认无参构造函数

当类中没有定义构造函数时,编译器默认提供一个无参构造函数,并且其函数体为空

2)默认拷贝构造函数

当类中没有定义拷贝构造函数时,编译器默认提供一个默认拷贝构造函数,简单的进行成员变量的值复制






【构造函数调用规则研究 】

1)当类中没有定义任何一个构造函数时,c++编译器会提供默认无参构造函数和默认拷贝构造函数

2)当类中定义了拷贝构造函数时,c++编译器不会提供无参数构造函数

3) 当类中定义了任意的非拷贝构造函数(即:当类中提供了有参构造函数或无参构造函数),c++编译器不会提供默认无参构造函数

4 )默认拷贝构造函数成员变量简单赋值

总结:只要你写了构造函数,那么你必须用。





1,【当你定义了构造函数,你必须使用】:

先演示没有任何构造函数的情景:

[email protected]:~/c++$ cat main.cpp 
#include <iostream>
#include <stdio.h>
#include <stdlib.h>
using namespace std;
int num = 0;
class Test
{
public:
	void fun()
	{
		cout << "执行对象的成员函数   ";
		cout << "a=" << a << " b=" << b << "  \n";
	}
private:
	int a;
	int b;
};
int main()
{
	Test t1;	t1.fun();
	return 0;
}

编译运行OK:
[email protected]:~/c++$ g++ -Wall -g main.cpp  && ./a.out 
执行对象的成员函数   a=2071403600 b=32765




当类中定义了构造函数,如果不使用,对象初始化就会报错!


示范:

[email protected]:~/c++$ cat main.cpp 
#include <iostream>
#include <stdio.h>
#include <stdlib.h>
using namespace std;
int num = 0;
class Test
{
public:
	Test(int _a,int _b)
	{
		a = _a;
		b = _b;
		cout << "a=" << a  << " b=" << b << "构造函数初始化, 有2个参数\n";
	}
	void fun()
	{
		cout << "执行对象的成员函数   ";
		cout << "a=" << a << " b=" << b << "  \n";
	}
private:
	int a;
	int b;
};
int main()
{
	Test t1;	t1.fun();//定义一个对象
	return 0;
}

编译运行:直接报错
[email protected]:~/c++$ g++ -Wall -g main.cpp  && ./a.out 
main.cpp: In function ‘int main()’:
main.cpp:43:7: error: no matching function for call to ‘Test::Test()’
  Test t1; t1.fun();
       ^



手动定义一个空的构造函数,编译通过!

[email protected]:~/c++$ cat main.cpp 
#include <iostream>
#include <stdio.h>
#include <stdlib.h>
using namespace std;
int num = 0;
class Test
{
public:
	Test()
	{

	}
	void fun()
	{
		cout << "执行对象的成员函数   ";
		cout << "a=" << a << " b=" << b << "  \n";
	}
private:
	int a;
	int b;
};
int main()
{
	Test t1;	t1.fun();
	return 0;
}


[email protected]:~/c++$ g++ -Wall -g main.cpp  && ./a.out 
执行对象的成员函数   a=89452272 b=32765  
[email protected]:~/c++$




【结论】

在类的定义时,只要写了构造函数,编译器就不会再自动为你定义无参的构造函数,这个构造函数必须要使用!


构造析构阶段性总结 

1)构造函数是C++中用于初始化对象状态的特殊函数

2)构造函数在对象创建时自动被调用

3)构造函数和普通成员函数都遵循重载规则

4)拷贝构造函数是对象正确初始化的重要保证

5)必要的时候,必须手工编写拷贝构造函数 




【浅拷贝 与 深拷贝】


技术分享




浅拷贝  示范,程序编译通过,运行就死掉,

因为释放已经释放过的内存,所以死掉

[email protected]:~/c++$ cat main.cpp 
#include <iostream>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
using namespace std;
int num = 0;
class Test
{
public:
	Test(const char *str)
	{
		cout << "I‘m init....\n";
		len  =  strlen(str);
		p = (char *)malloc(len +1);
		strcpy(p,str);
		*(p + len) = ‘\0‘;
		
	}
	~Test()
	{
		if(p != NULL)
		{
			free(p);		
			p = NULL;
			len = 0;
		}
		cout << "I‘m Free!\n";
	}
	void show()
	{
		cout << p <<"\n";
	}
private:
	char *p;
	int len;
};

void fun1()
{
	Test t1("hello world");	t1.show();
	Test t2(t1);		t2.show();
	
}
int main()
{
	fun1();
	return 0;
}
编译运行:
[email protected]:~/c++$ g++ -Wall -g main.cpp  && ./a.out 
I‘m init....
hello world
hello world
I‘m Free!
*** Error in `./a.out‘: double free or corruption (fasttop): 0x0000000001a93010 ***
Aborted (core dumped)
[email protected]:~/c++$ 

gdb调试:
[email protected]:~/c++$ gdb ./a.out 
(gdb) run
(gdb) where
#5  0x0000000000400b35 in Test::~Test (this=0x7fffffffead0, __in_chrg=<optimized out>) at main.cpp:23

会看到 at main.cpp:23
这一行就是 free(p);




手动写一个拷贝构造函数吗,完成深拷贝:

[email protected]:~/c++$ cat main.cpp 
#include <iostream>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
using namespace std;
int num = 0;
class Test
{
public:
	Test(const char *str)
	{
		cout << "I‘m init....\n";
		len  =  strlen(str);
		p = (char *)malloc(len +1);
		strcpy(p,str);
		*(p + len) = ‘\0‘;
		
	}
	Test(const Test &obj)	// 当外部调用 Test t2(t1);
	{
		//深度拷贝
		len = obj.len;
		p = (char *)malloc(len +1);;
		strcpy(p,obj.p);
		*(p + len) = ‘\0‘;
	}
	~Test()
	{
		if(p != NULL)
		{
			free(p);		
			p = NULL;
			len = 0;
		}
		cout << "I‘m Free!\n";
	}
	void show()
	{
		cout << p <<"\n";
	}
private:
	char *p;
	int len;
};

void fun1()
{
	Test t1("hello world");	t1.show();
	Test t2(t1);		t2.show();
        //Test t3 = t1;         =等号也是浅拷贝
	
}
int main()
{
	fun1();
	return 0;
}
编译运行:
[email protected]:~/c++$ g++ -Wall -g main.cpp  && ./a.out 
I‘m init....
hello world
hello world
I‘m Free!
I‘m Free!



还是会存在宕掉的可能:

Test t1("hello world!"); t1.show();

Test t2("hello Linux!"); t2.show();

t1 = t2; //=等号也是浅拷贝

这样会再次导致一片内存两次free,继续宕机!

[email protected]:~/c++$ cat main.cpp 
#include <iostream>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
using namespace std;
int num = 0;
class Test
{
public:
	Test(const char *str)
	{
		cout << "I‘m init....\n";
		len  =  strlen(str);
		p = (char *)malloc(len +1);
		strcpy(p,str);
		*(p + len) = ‘\0‘;
		
	}
	Test(const Test &obj)	// 当外部调用 Test t2(t1);
	{
		//深度拷贝
		len = obj.len;
		p = (char *)malloc(len +1);;
		strcpy(p,obj.p);
		*(p + len) = ‘\0‘;
		cout << "I‘m in copy \n";
	}
	~Test()
	{
		if(p != NULL)
		{
			free(p);		
			p = NULL;
			len = 0;
		}
		cout << "I‘m Free!\n";
	}
	void show()
	{
		cout << p <<"\n";
	}
private:
	char *p;
	int len;
};

void fun1()
{
	Test t1("hello world!");	t1.show();
	Test t2("hello Linux!");	t2.show();
	t1 = t2;			//=等号也是浅拷贝
	
}
int main()
{
	fun1();
	return 0;
}

编译通过,运行出错!
[email protected]:~/c++$ g++ -Wall -g main.cpp  && ./a.out 
I‘m init....
hello world!
I‘m init....
hello Linux!
I‘m Free!
*** Error in `./a.out‘: double free or corruption (fasttop): 0x000000000249b030 ***
Aborted (core dumped)

【gdb调试】:
[email protected]:~/c++$ gdb ./a.out 
(gdb) run
(gdb) where
#5  0x0000000000400b43 in Test::~Test (this=0x7fffffffead0, __in_chrg=<optimized out>) at main.cpp:32
32行就是    free(p);




构造函数 初始化 列表:

1,首先会执行被组合对象的构造函数

2,如果组成对象有多个,安照对象定义的顺序初始化,而不是按照列表的顺序

3,被组合对象的析构顺序与构造顺序相反

[email protected]:~/c++$ cat main.cpp 
#include <iostream>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
using namespace std;
int num = 0;

class Test1
{
public:
	Test1(int _a)
	{
		a = _a;
		cout << a << " Test1 I‘m init....\n";
	}
	~Test1()
	{
		cout << a << " Test1 I‘m Free!\n";
	}
private:
	int a;
};

class Test2
{
public:
	//在初始化参数的同时,将Test1对象完成初始化
	Test2(int _a):t11(1),t12(2)
	{
		a = _a;
		cout << "Test2  1 I‘m init....\n";
	}
	//经典用法,参数传递
	Test2(int _a,int _b,int _c,int _d):t11(_c),t12(_d)
	{
		a = _a;
		b = _b;
		cout << "Test2 2 I‘m init....\n";
	}
	~Test2()
	{
		cout << "Test2 I‘m Free!\n";
	}
private:
	int a;
	int b;
	Test1 t11;
	Test1 t12;
};

int main()
{
//1 构造函数的初始化列表  解决: 在B类中 组合了一个 A类对象 (A类设计了构造函数)
//根据构造函数的调用规则 设计A的构造函数, 必须要用;没有机会初始化A
//新的语法  Constructor::Contructor() : m1(v1), m2(v1,v2), m3(v3)
	Test2 t22(1,3,1,4);
	return 0;
}



[email protected]:~/c++$ g++ -Wall -g main.cpp  && ./a.out 
1 Test1 I‘m init....
4 Test1 I‘m init....
Test2 2 I‘m init....
Test2 I‘m Free!
4 Test1 I‘m Free!
1 Test1 I‘m Free!
[email protected]:~/c++$










本文出自 “魂斗罗” 博客,请务必保留此出处http://990487026.blog.51cto.com/10133282/1794395

以上是关于C++基础3 类:构造 拷贝 析构函数,的主要内容,如果未能解决你的问题,请参考以下文章

c++类大四个默认函数-构造函数 析构函数 拷贝构造函数 赋值构造函数

C++面试之 类string的构造函数拷贝构造函数和析构函数

C++类与对象(详解构造函数,析构函数,拷贝构造函数,赋值重载函数)

C++类与对象第二篇:(构造函数析构函数拷贝构造运算符重载取地址及const取地址操作符重载)

C++构造/析构/赋值函数

C++入门(拷贝)构造函数和析构函数