C++基础——对象的构造和析构

创建一个对象时,常常需要作某些初始化的工作,例如对数据成员赋初值。注意,类的数据成员是不能在声明类时初始化的

C++编译器提供了构造函数(constructor)来处理对象的初始化。构造函数是一种特殊的成员函数,与其他成员函数不同,不需要用户来调用它,而是在建立对象时自动执行

3.1 构造和析构函数

1. 构造函数和析构函数的概念

有关构造函数
1 构造函数定义及调用
1)C++中的类可以定义与类名相同的特殊成员函数,这种与类名相同的成员函数叫做构造函数;
2)构造函数在定义时可以有参数;
3)没有任何返回类型的声明。
2 构造函数的调用
自动调用:一般情况下C++编译器会自动调用构造函数
手动调用:在一些情况下则需要手工调用构造函数

有关析构函数
3 析构函数定义及调用
1)C++中的类可以定义一个特殊的成员函数清理对象,这个特殊的成员函数叫做析构函数
语法:~ClassName()
2)析构函数没有参数也没有任何返回类型的声明
3)析构函数在对象销毁时自动被调用
4 析构函数调用机制
C++编译器自动调用

#define _CRT_SECURE_NO_WARNINGS
#include "iostream"
using namespace std;

class Test
{
public:
	Test()  //无参数  构造函数
	{
		a = 10;    //作用   完成对属性的初始化构工作
		p = (char *)malloc(100);
		strcpy(p,"aaaaffff");
		cout<<"我是构造函数"<<endl;
	}
	void print()
	{
		cout<<p<<endl;
		cout<<a<<endl;
	}
	~Test()   //析构函数
	{
		if (p!=NULL)
		{
			free(p);
		}
		cout<<"我是析构函数,被调用了"<<endl;
	}
private:
	int a;
	char *p;

};

//给对象搭建一个舞台,研究对象的行为
void objplay()
{
	//构造,先定义的先
	//析构 ,先定义的后释放
	Test t1;
	t1.print(); 
	printf("分隔符\n");
	Test t2;
	t2.print();
};
void main()
{
	//Test t1,t2;
	objplay();

	cout<<"hello..."<<endl;
	system("pause");
	return ;
}

2. C++编译器构造析构方案 PK 对象显示初始化方案

设计构造函数和析构函数的原因

面向对象的思想是从生活中来,手机、车出厂时,是一样的。
生活中存在的对象都是被初始化后才上市的;初始状态是对象普遍存在的一个状态的
普通方案:
为每个类都提供一个public的initialize函数;
对象创建后立即调用initialize函数进行初始化。
优缺点分析
1)initialize只是一个普通的函数,必须显示的调用
2)一旦由于失误的原因,对象没有初始化,那么结果将是不确定的
没有初始化的对象,其内部成员变量的值是不定的
3)不能完全解决问题

#include "iostream"
using namespace std;

class Test3
{
public:
	void init(int _a,int _b)
	{
		a=_a;
		b=_b;
	}
protected:
private:
	int a;
	int b;
};

void main()
{
	//类没有提供 构造函数,C++编译器会自动的提供一个默认的构造函数
	//类没有提供构造函数    copy构造函数,C++编译器会自动给 程序员提供一个默认的拷贝构造函数

	Test3 t1; 
	int a=10;
	int b=20;
	t1.init(a,b);

	Test3 tArray[3];
	tArray[0].init(1,2);
	tArray[1].init(1,2);
	tArray[2].init(1,2);

	Test3 t21;t21.init(1,2);
	Test3 t22;t22.init(1,2);
	Test3 t23;t23.init(1,2);

	//在这种场景下,显示的初始化方案,显得很蹩脚
	Test3 tArray2[3]={t21,t22,t23};
	
	//这种场景下,满足不了编程需求
	//Test3 tArray2[1999]={t21,t22,t23};
	
	cout<<"hello..."<<endl;
	system("pause");
	return ;
	
}

定义对象数组时,无法显示的初始化

3.2 构造函数的分类以及调用

#include "iostream"
using namespace std;

class Test2
{
public:
	Test2()    //无参构造函数
	{
		m_a=0;
		m_b=0;
		cout<<"无参数构造函数"<<endl;
	}
	Test2(int a,int b)    //有参数构造函数
	{
		m_a=a;
		m_b=b;
		cout<<"有参构造函数"<<endl;
	}

	Test2(int a)         //有参数构造函数
	{
		m_a=a;
		m_b=0;
		cout<<"有参构造函数"<<endl;
	}

	//赋值构造函数(copy 构造函数)
	Test2(const Test2& obj)
	{
		cout<<"我也是构造函数"<<endl;
	}
public:
	void printT()
	{
		cout<<"普通成员函数"<<endl;
	}

protected:
private:
	int m_a;
	int m_b;
};

void main21()
{
	Test2 t1;   //调用无参数构造函数
	cout<<"hello..."<<endl;
	system("pause");
	return ;
}


//调用有参构造函数的三种方法
void main()
{
	//1   括号法;
	Test2 t1(1,2);     //调用有参构造函数
	t1.printT();

	//2   =法
	Test2 t2=(3,4,5);   //此处C++对等号操作符进行了功能性增强
					  //C++编译器帮我们自动调用构造函数
	                  //逗号表达式最后一个值为表达式的值
	                  //调用Test2(int a)构造函数
	Test2 t3=5;

	//3   直接调用构造函数   手动调用构造函数

	Test2 t4 = Test2(6,7);  //匿名对象 (匿名对象的去和留)

	//t4对象的初始化
	//直接调用有参构造函数完成t4的初始化

	t1=t4;   //把t4拷贝给t1     赋值操作
	//对象的初始化 和 对象的赋值 是两个不同的概念

	cout<<"hello..."<<endl;

	system("pause");
	return ;
}

1. 无参数构造函数

调用方法: Test t1, t2;

2. 有参构造函数

有参构造函数的三种调用方法:

  1. c++编译器默认调用有参构造函数 括号法
    2)c++编译器默认调用有参构造函数 等号法
    3)程序员手工调用构造函数 产生了一个对象 直接调用构造构造函数法
class Test5
{
private:
	int a;
public:
	//带参数的构造函数
	Test5(int a)
	{
		printf("\na:%d", a);
	}
	Test5(int a, int b)
	{
		printf("\na:%d b:%d", a, b);
	}
public:
};

int main55()
{
	Test5 t1(10);  //c++编译器默认调用有参构造函数 括号法 
	Test5 t2 = (20, 10); //c++编译器默认调用有参构造函数 等号法
	Test5 t3 = Test5(30); //程序员手工调用构造函数 产生了一个对象 直接调用构造构造函数法

	system("pause");
	return 0;
}

3. 拷贝构造函数调用时机

赋值构造函数的四种调用场景(调用时机)

  1. 第1和第2个调用场景

#include "iostream"
using namespace std;

class Test4
{
public:
	Test4()    //无参构造函数
	{
		m_a=0;
		m_b=0;
		cout<<"无参数构造函数"<<endl;
	}
	Test4(int a,int b)    //有参数构造函数
	{
		m_a=a;
		m_b=b;
		cout<<"有参构造函数"<<endl;
	}

	Test4(int a)    //有参数构造函数
	{
		m_a=a;
		m_b=0;
		cout<<"有参构造函数"<<endl;
	}

	//赋值构造函数(copy 构造函数)
	Test4(const Test4& obj)
	{
		cout<<"我也是构造函数"<<endl;
		m_b=obj.m_b+100;
		m_a=obj.m_a+100;
	}
public:
	void printT()
	{
		cout<<"普通成员函数"<<endl;
		cout<<"m_a   "<<m_a<<"   m_a  "<<m_b<<endl;
	}

protected:
private:
	int m_a;
	int m_b;
};

//1    赋值构造函数  用1个对象初始化另一个对象
void main()
{
	Test4 t1(1,2);
	Test4 t0(3,4);
	
	Test4 t2(t1);//2 第二种   t1 初始化t2
	t2.printT();
	
	//t0=t1;   //用t1给t0赋值  到操作 和 初始化是连个不同的概念
	//1  第一种
	//Test4 t2=t1;  //用t1初始化t2
	//t2.printT();

	//赋值操作,会不会调用构造函数
	//不会调用  operator()

	cout<<"hello..."<<endl;
	system("pause");
	return ;

}

第3个调用场景

#include "iostream"
using namespace std;

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

	//拷贝构造函数,完成对象的初始化
	Location(const Location& obj)  //拷贝构造函数
	{
		X=obj.X+1;
		Y=obj.Y+1;
	}

	~Location()
	{
		cout<<X<<","<<Y<<"Object destoryed."<<endl;
	}
	int GetX() {return X;}
	int GetY() {return Y;}
private:
	int X,Y;
};

//业务函数  形参是一个元素
void f(Location p)
{
	cout<<p.GetX()<<endl;
}

void playobj()
{
	Location a(1,2);//构造
	Location b=a;//拷贝
	cout<<"b对象已经初始化完毕"<<endl;

	f(b);//b实参取初始化形参,会调用copy函数 
}

void main()
{
	playobj();
	cout<<"hello..."<<endl;
	system("pause");
	return ;
}

在这里插入图片描述
第4个调用场景

#include "iostream"
using namespace std;

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

	//拷贝构造函数,完成对象的初始化
	Location1(const Location1& obj)  //拷贝构造函数
	{
		X=obj.X;Y=obj.Y;
	}

	~Location1()
	{
		cout<<X<<","<<Y<<"Object destoryed."<<endl;
	}
	int GetX() {return X;}
	int GetY() {return Y;}
private:
	int X,Y;
};

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

//2  匿名对象的去和留 

//若用匿名对象初始化另外一个同类型的对象,那么匿名对象  转成有名对象
//若用匿名对象  赋值给  另外一个同类型的对象,匿名对象,被析构

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

void objplay1()
{
	g();
}

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

void objplay4()
{
	//用匿名对象 赋值给m2后,从匿名对象被析构
	Location1 m2(1,2);
	m2=g();
	printf("因为匿名对象=给m2, ,匿名对象被析构掉  \n");
	cout<<m2.GetX()<<endl;
}

void main()
{
	objplay1();
	objplay3();
	objplay4();
	cout<<"hello..."<<endl;
	system("pause");
	return ;
} 

在这里插入图片描述
1 函数的返回值是一个元素(复杂类型的),返回的是一个新的匿名对象(所以会调用匿名对象类的copy函数)
2 匿名对象的去和留
若用匿名对象初始化另外一个同类型的对象,那么匿名对象 转成有名对象
若用匿名对象 赋值给 另外一个同类型的对象,匿名对象,被析构

在这里插入图片描述

4.默认构造函数

二个特殊的构造函数
1)默认无参构造函数
当类中没有定义构造函数时,编译器默认提供一个无参构造函数,并且其函数体为空
2)默认拷贝构造函数
当类中没有定义拷贝构造函数时,编译器默认提供一个默认拷贝构造函数,简单的进行成员变量的值复制

3. 构造函数调用规则

1)当类中没有定义任何一个构造函数时,c++编译器会提供默认无参构造函数和默认拷贝构造函数
2)当类中定义了拷贝构造函数时,c++编译器不会提供无参数构造函数
3) 当类中定义了任意的非拷贝构造函数(即:当类中提供了有参构造函数或无参构造函数),c++编译器不会提供默认无参构造函数
4 )默认拷贝构造函数成员变量简单赋值

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

构造析构阶段性总结:
1)构造函数是C++中用于初始化对象状态的特殊函数
2)构造函数在对象创建时自动被调用
3)构造函数和普通成员函数都遵循重载规则
4)拷贝构造函数是对象正确初始化的重要保证
5)必要的时候,必须手工编写拷贝构造函数

#include "iostream"
using namespace std;

class Text
{
public:
	//Text()
	//{
	//	a=0;
	//	b=0;
	//	cout<<"无参数构造函数,自动被调用"<<endl;
	//} 
	Text(int _a)  //有参构造函数
	{
		a=_a;
		b=0;
	}
	Text(const Text& obj)  //copy构造函数作用:用一个对象初始另一个对象
	{
		a=obj.a+100;
		b=obj.b+100;
	}
	void printT()
	{
		cout<<"a: "<<a<<" b: "<<endl;
	}
	~Text()
	{
		cout<<"我是析构函数,对象生命周期结束时,会被C++编译器自动调用"<<endl;
	}
protected:
private:
	int a;
	int b;
};


//在定义类时,只要写了构造函数,则必须要用 
void main81()
{
	//Text t1;  //调用无参构造函数

	cout<<"hello..."<<endl;
	system("pause");
	return ;
}

4. 深拷贝和浅拷贝

  • 默认复制构造函数可以完成对象的数据成员值简单的复制
  • 对象的数据资源是由指针指示的堆时,默认复制构造函数仅作指针值复制
    在这里插入图片描述
    在这里插入图片描述
  • 显示提供copy构造函数
  • 显示操作重载=号操作,不使用编译器提供的浅copy
#define _CRT_SECURE_NO_WARNINGS
#include "iostream"
using namespace std;

class Name
{
public:
	Name(const char *myp)
	{
		m_len=strlen(myp);
		m_p=(char *)malloc(m_len+1);  //字符串后的0
		strcpy(m_p,myp);
	}

	//解决方法,手工的编写拷贝构造函数,使用深拷贝
	Name(const Name& obj1)
	{
		cout<<obj1.m_p<<endl;
		m_len = obj1.m_len;
		m_p=(char *)malloc(m_len+1);
		strcpy(m_p,obj1.m_p);
	}

	~Name()
	{
		if (m_p!=NULL)
		{
			free(m_p);
			m_p=NULL;
			m_len=0;
		}
	}
protected:
private:
	char *m_p;
	int m_len;
};

void objplaymain()
{
	Name obj1("abcdefg");

	Name obj2=obj1;

	Name obj3("obj3");

	obj3=obj1;   //等号操作
}

void main()
{
	objplaymain();
	cout<<"hello..."<<endl;
	system("pause");
	return ;
}

5. 多个对象构造和析构

5.1 对象初始化列表

1)对象初始化列表出现原因
1.必须这样做:
如果我们有一个类成员,它本身是一个类或者是一个结构,而且这个成员它只有一个带参数的构造函数,没有默认构造函数。这时要对这个类成员进行初始化,就必须调用这个类成员的带参数的构造函数,
如果没有初始化列表,那么他将无法完成第一步,就会报错。

2、类成员中若有const修饰,必须在对象初始化的时候,给const int m 赋值
当类成员中含有一个const对象时,或者是一个引用时,他们也必须要通过成员初始化列表进行初始化,
因为这两种对象要在声明后马上初始化,而在构造函数中,做的是对他们的赋值,这样是不被允许的。
2)C++中提供初始化列表对成员变量进行初始化
语法规则
Constructor::Contructor() : m1(v1), m2(v1,v2), m3(v3)
{
// some other assignment operation
}
3)注意概念
初始化:被初始化的对象正在创建
赋值:被赋值的对象已经存在

4)注意:
成员变量的初始化顺序与声明的顺序相关,与在初始化列表中的顺序无关
初始化列表先于构造函数的函数体执行

#include "iostream"
using namespace std;

class A
{
public:
	A(int _a)
	{
		a=_a;
		cout<<"A构造函数"<<"a  "<<a<<endl;
	}
	~A()
	{
		cout<<"A析构函数"<<endl;
	}
protected:
private:
	int a;
};


//2   构造函数的初始化列表   解决:在B类中 组合了一个A类对象(A类设计了构造函数)
//根据构造函数的调用规则,设计A的构造函数,必须要用,没有机会初始化A
//新的用法  Constructor::Comstructor():m1(v1),m2(v1,v2),m3(v3)

//3  被组合对象的构造顺序   与定义的顺序有关系,与初始化列表的顺序没有关系

class B
{
public:
	B(int _b1,int _b2) : a1(1),a2(2),c(0)
	{ 
		
	}
	B(int _b1,int _b2,int m,int n):a1(m),a2(n),c(0)
	{
		b1=_b1;
		b2=_b2;
		cout<<"B的构造函数"<<endl;
	}
	~B()
	{

		cout<<"B的析构函数"<<endl;
	}
protected:
private:
	int b1;
	int b2;

	A a1;
	A a2;

	const int c;
	
};

//先执行被组合对象的构造函数
//若组合对象有多个 ,按照定义顺序,而不是按照初始化列表顺序

//析构函数:和构造函数的调用顺序相反

//4   初始化列表   用来给const  属性赋值
void obj10play()
{
	A a1(10);
	B ojbB(1,2);

	//1   参数传递
	B ojbB2(1,2,3,4);

	//调用顺序
	
}

void main()
{
	obj10play();

	cout<<"hello..."<<endl;
	system("pause");
	return ;
}

1 C++中提供了初始化列表对成员变量进行初始化

2 使用初始化列表出现原因:

  • 1).必须这样做:
    如果我们有一个类成员,它本身是一个类或者是一个结构,而且这个成员它只有一个带参数的构造函数,
    而没有默认构造函数,这时要对这个类成员进行初始化,就必须调用这个类成员的带参数的构造函数,
    如果没有初始化列表,那么他将无法完成第一步,就会报错。

  • 2)类成员中若有const修饰,必须在对象初始化的时候,给const int m 赋值
    当类成员中含有一个const对象时,或者是一个引用时,他们也必须要通过成员初始化列表进行初始化,
    因为这两种对象要在声明后马上初始化,而在构造函数中,做的是对他们的赋值,这样是不被允许的。

6. 构造函数和析构函数的调用顺序研究

构造函数与析构函数的调用顺序
1)当类中有成员变量是其它类的对象时,首先调用成员变量的构造函数,调用顺序与声明顺序相同;之后调用自身类的构造函数
2)析构函数的调用顺序与对应的构造函数调用顺序相反

7. 类对象的动态建立与释放

7.1 new和delete基本语法

1)在软件开发过程中,常常需要动态地分配和撤销内存空间,例如对动态链表中结点的插入与删除。在C语言中是利用库函数malloc和free来分配和撤销内存空间的。C++提供了较简便而功能较强的运算符new和delete来取代malloc和free函数。
注意: new和delete是运算符,不是函数,因此执行效率高。

2)虽然为了与C语言兼容,C++仍保留malloc和free函数,但建议用户不用malloc和free函数,而用new和delete运算符。

new int;  //开辟一个存放整数的存储空间,返回一个指向该存储空间的地址(即指针)
new int(100);  //开辟一个存放整数的空间,并指定该整数的初值为100,返回一个指向该存储空间的地址
new char[10];  //开辟一个存放字符数组(包括10个元素)的空间,返回首元素的地址
new int[5][4];  //开辟一个存放二维整型数组(大小为5*4)的空间,返回首元素的地址
float *p=new float (3.14159); 
//开辟一个存放单精度数的空间,并指定该实数的初值为//3.14159,将返回的该空间的地址赋给指针变量p

3)new和delete运算符使用的一般格式为:
在这里插入图片描述
用new分配数组空间时不能指定初值。如果由于内存不足等原因而无法正常分配空间,则new会返回一个空指针NULL,用户可以根据该指针的值判断分配空间是否成功。

4) 应用举例
在这里插入图片描述

7.2 类对象的动态建立和释放

使用类名定义的对象都是静态的,在程序运行过程中,对象所占的空间是不能随时释放的。但有时人们希望在需要用到对象时才建立对象,在不需要用该对象时就撤销它,释放它所占的内存空间以供别的数据使用。这样可提高内存空间的利用率。
C++中,可以用new运算符动态建立对象,用delete运算符撤销对象

Box *pt;  //定义一个指向Box类对象的指针变量pt
pt=new Box;  //在pt中存放了新建对象的起始地址
              在程序中就可以通过pt访问这个新建的对象。如
cout<<pt->height;  //输出该对象的height成员
cout<<pt->volume( );  //调用该对象的volume函数,计算并输出体积
				C++还允许在执行new时,对新建立的对象进行初始化。如
Box *pt=new Box(12,15,18);
		这种写法是把上面两个语句(定义指针变量和用new建立新对象)合并为一个语句,并指定初值。这样更精炼。 
		新对象中的height,width和length分别获得初值12,15,18。调用对象既可以通过对象名,也可以通过指针。

在执行new运算时,如果内存量不足,无法开辟所需的内存空间,目前大多数C++编译系统都使new返回一个0指针值。只要检测返回值是否为0,就可判断分配内存是否成功。

ANSI C++标准提出,在执行new出现故障时,就“抛出”一个“异常”,用户可根据异常进行有关处理。但C++标准仍然允许在出现new故障时返回0指针值。当前,不同的编译系统对new故障的处理方法是不同的。

在不再需要使用由new建立的对象时,可以用delete运算符予以释放。如
delete pt; //释放pt指向的内存空间
这就撤销了pt指向的对象。此后程序不能再使用该对象。
如果用一个指针变量pt先后指向不同的动态对象,应注意指针变量的当前指向,以免删错了对象。在执行delete运算符时,在释放内存空间之前,自动调用析构函数,完成有关善后清理工作。

#include "iostream"
using namespace std;

//1 
//     malloc   free    
//     new delete 操作符 c++ 语法

//2    new基础类型变量   分配数组变量 分配类对象

//3    

void main1301()
{
	//
	int *p=(int *)malloc(sizeof(int));
	*p=10;
	free(p);

	int *p2=new int;   //分配基础类型
	*p2=20;
	free(p2);

	int *p3=new int(30);
	printf("*p3:%d \n",*p3); 
	delete p3;

	cout<<"hello..."<<endl;
	system("pause");
	return ;
}

//分配数组

void main1302()
{
	//c语言分配数组
	int *p=(int *)malloc(sizeof(int)*10);    //int array[10];
	p[0]=1;
	free(p);

	//C++分配数组
	int *pArray=new int[10];
	pArray[1]=2;

	delete [] pArray;   //数组不要忘记[]

	char *pArray2=new char[25];
	delete [] pArray2;

	cout<<"hello..."<<endl;
	system("pause");
	return ;
}


//分配对象
class Test
{
public:
	Test(int _a)
	{

		a=_a;
		cout<<"构造函数执行"<<endl;

	}
	~Test()
	{
		cout<<"析构函数执行"<<endl;

	}
protected:
private:
	int a;
};

//分配对象 new  delete
//相同 和 不同的地方   new能执行类型构造函数
                   //  delete操作符 能执行类的析构函数
void main()
{
	//c语言
	Test *pT1=(Test *)malloc((sizeof(Test)));
	free(pT1);

	//c++
	Test *pT2=new Test(10);
	delete pT2;

	cout<<"hello..."<<endl;
	system("pause");
	return ;
}

new能执行类型构造函数
delete操作符 能执行类的析构函数

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值