对象的构造和析构1


构造函数主要作用在于创建对象时为对象的成员属性赋值,构造函数由编译器自动调用,无须手动调用。
析构函数主要用于对象 销毁前系统自动调用,执行一些清理工作。

1.构造和析构函数

1.1 构造函数和析构函数的概念

构造函数语法:

  1. 构造函数函数名和类名相同,没有返回值,不能有void,但可以有参数。
  2. ClassName(){}

析构函数语法:

  1. 析构函数函数名是在类名前面加”~”组成,没有返回值,不能有void,不能有参数,不能重载。
  2. ~ClassName(){}
class Person{
public:
	Person(){
		cout << "构造函数调用!" << endl;
		pName = (char*)malloc(sizeof("John"));
		strcpy(pName, "John");
		mTall = 150;
		mMoney = 100;
	}
	~Person(){
		cout << "析构函数调用!" << endl;
		if (pName != NULL){
			free(pName);
			pName = NULL;
		}
	}
public:
	char* pName;
	int mTall;
	int mMoney;
};

void test(){
	Person person;
	cout << person.pName << person.mTall << person.mMoney << endl;
}

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

//为什么对象需要初始化 有什么样的初始化方案
#include "iostream"
using namespace std;

/*
思考为什么需要初始化
	面向对象思想来自生活,手机、车、电子产品,出厂时有初始化
	怎么样进行初始化?

方案1:显示调用方法
缺点:易忘、麻烦;显示调用init,不能完全解决问题
*/
class Test21
{
public:
	int m;
	int getM() const { return m; }
	void setM(int val) { m = val; }

	int n;
	int getN() const { return n; }
	void setN(int val) { n = val; }

public:
	int init(int m,int n)
	{
		this->m = m;
		this->n = n;
		return 0;
	}
protected:
private:
};

int main()
{
	int rv =0;
	Test21 t1;  //无参构造函数的调用方法
	Test21 t2;

	//t1.init(100, 200);
	//t2.init(300, 400);
	cout<<t1.getM()<<" "<<t1.getN()<<endl;
	cout<<t2.getM()<<" "<<t2.getN()<<endl;

	//定义对象数组时,没有机会进行显示初始化
	Test21 arr[3];
	//Test arr_2[3] = {Test(1,3), Test(), Test()};

	system("pause");
	return rv;
}

2 构造函数的分类及调用

  1. 按参数类型:分为无参构造函数和有参构造函数
  2. 按类型分类:普通构造函数和拷贝构造函数(复制构造函数)
class Person{
public:
	Person(){
		cout << "no param constructor!" << endl;
		mAge = 0;
	}
	//有参构造函数
	Person(int age){
		cout << "1 param constructor!" << endl;
		mAge = age;
	}
	//拷贝构造函数(复制构造函数) 使用另一个对象初始化本对象
	Person(const Person& person){
		cout << "copy constructor!" << endl;
		mAge = person.mAge;
	}
	//打印年龄
	void PrintPerson(){
		cout << "Age:" << mAge << endl;
	}
private:
	int mAge;
};
//1. 无参构造调用方式
void test01(){
	
	//调用无参构造函数
	Person person1; 
	person1.PrintPerson();

	//无参构造函数错误调用方式
	//Person person2();
	//person2.PrintPerson();
}
//2. 调用有参构造函数
void test02(){
	
	//第一种 括号法,最常用
	Person person01(100);
	person01.PrintPerson();

	//调用拷贝构造函数
	Person person02(person01);
	person02.PrintPerson();

	//第二种 匿名对象(显示调用构造函数)
	Person(200); //匿名对象,没有名字的对象

	Person person03 = Person(300);
	person03.PrintPerson();

	//注意: 使用匿名对象初始化判断调用哪一个构造函数,要看匿名对象的参数类型
	Person person06(Person(400)); //等价于 Person person06 = Person(400);
	person06.PrintPerson();

	//第三种 =号法 隐式转换
	Person person04 = 100; //Person person04 =  Person(100)
	person04.PrintPerson();

	//调用拷贝构造
	Person person05 = person04; //Person person05 =  Person(person04)
	person05.PrintPerson();
}

b为A的实例化对象,A a = A(b) 和 A(b)的区别?

当A(b) 有变量来接的时候,那么编译器认为他是一个匿名对象,当没有变量来接的时候,编译器认为你A(b) 等价于 A b.

注意: 不能调用拷贝构造函数去初始化匿名对象,也就是说以下代码不正确:

class Teacher{
public:
	Teacher(){
		cout << "默认构造函数!" << endl;
	}
	Teacher(const Teacher& teacher){
		cout << "拷贝构造函数!" << endl;
	}
public:
	int mAge;
};
void test(){
	
	Teacher t1;
	//error C2086:“Teacher t1”: 重定义
	Teacher(t1);  //此时等价于 Teacher t1;
}

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

第1个调用场景

#include "iostream"
using namespace std;

class AA
{
public:
	AA() //无参构造函数 默认构造函数
	{	
		cout<<"我是构造函数,自动被调用了"<<endl;
	}
	AA(int _a) //无参构造函数 默认构造函数
	{	
		a = _a;
	}
 	AA(const AA &obj2)
 	{
 		cout<<"我也是构造函数,我是通过另外一个对象obj2,来初始化我自己"<<endl;
 		a = obj2.a + 10;
 	}
	~AA()
	{
		cout<<"我是析构函数,自动被调用了"<<endl;
	}
	void getA()
	{
		printf("a:%d \n", a);
	}
protected:
private:
	int a;
};
//单独搭建一个舞台
void ObjPlay01()
{
	AA a1; //变量定义
	
	//赋值构造函数的第一个应用场景
	//用对象1 初始化 对象2 
	AA a2 = a1; //定义变量并初始化 //初始化法

	a2 = a1; //用a1来=号给a2 编译器给我们提供的浅copy
}

第2个应用场景

//单独搭建一个舞台
void ObjPlay02()
{
	AA a1(10); //变量定义

	//赋值构造函数的第一个应用场景
	//用对象1 初始化 对象2 
	AA a2(a1); //定义变量并初始化 //括号法

	//a2 = a1; //用a1来=号给a2 编译器给我们提供的浅copy
	a2.getA();
} 
//注意:初始化操作 和 等号操作 是两个不同的概念

第3个调用场景

#include "iostream"
using namespace std;

class Location 
{ 
public:
	Location( int xx = 0 , int yy = 0 ) 
	{ 
		X = xx ;  Y = yy ;  cout << "Constructor Object.\n" ; 
	}
	Location( const Location & p ) 	    //复制构造函数
	{ 
		X = p.X ;  Y = p.Y ;   cout << "Copy_constructor called." << endl ;  
	}
	~Location() 
	{ 
		cout << X << "," << Y << " Object destroyed." << endl ; 
	}
	int  GetX () { return X ; }		int GetY () { return Y ; }
private :   int  X , Y ;
} ;

//alt + f8 排版
void f ( Location  p )   
{ 
	cout << "Funtion:" << p.GetX() << "," << p.GetY() << endl ; 
}

void mainobjplay()
{  
	Location A ( 1, 2 ) ;  //形参是一个元素,函数调用,会执行实参变量初始化形参变量
	f ( A ) ;
} 

void main()
{  
	mainobjplay();
	system("pause");
}

第4个调用场景

#include "iostream"
using namespace std;
class Location 
{ 
public:
	Location( int xx = 0 , int yy = 0 ) 
	{ 
		X = xx ;  Y = yy ;  cout << "Constructor Object.\n" ; 
	}
	Location( const Location & p ) 	    //复制构造函数
	{ 
		X = p.X ;  Y = p.Y ;   cout << "Copy_constructor called." << endl ;  
	}
	~Location() 
	{ 
		cout << X << "," << Y << " Object destroyed." << endl ; 
	}
	int  GetX () { return X ; }		int GetY () { return Y ; }
private :   int  X , Y ;
} ;

//alt + f8 排版
void f ( Location  p )   
{ 
	cout << "Funtion:" << p.GetX() << "," << p.GetY() << endl ; 
}

Location g()
{
	Location A(1, 2);
	return A;
}

//对象初始化操作 和 =等号操作 是两个不同的概念
//匿名对象的去和留,关键看,返回时如何接
void mainobjplay()
{  
	//若返回的匿名对象,赋值给另外一个同类型的对象,那么匿名对象会被析构
	//Location B;
	//B = g();  //用匿名对象 赋值 给B对象,然后匿名对象析构

	//若返回的匿名对象,来初始化另外一个同类型的对象,那么匿名对象会直接转成新的对象
	Location B = g();
	cout<<"传智扫地僧测试"<<endl;
} 

void main()
{  
	mainobjplay();
	system("pause");
}
  1. 对象以值传递的方式传给函数参数
  2. 函数局部对象以值传递的方式从函数返回(vs debug模式下调用一次拷贝构造,qt不调用任何构造)
  3. 用一个对象初始化另一个对象
class Person{
public:
	Person(){
		cout << "no param contructor!" << endl;
		mAge = 10;
	}
	Person(int age){
		cout << "param constructor!" << endl;
		mAge = age;
	}
	Person(const Person& person){
		cout << "copy constructor!" << endl;
		mAge = person.mAge;
	}
	~Person(){
		cout << "destructor!" << endl;
	}
public:
	int mAge;
};
//1. 旧对象初始化新对象
void test01(){

	Person p(10);
	Person p1(p);
	Person p2 = Person(p);
	Person p3 = p; // 相当于Person p2 = Person(p);
}

//2. 传递的参数是普通对象,函数参数也是普通对象,传递将会调用拷贝构造
void doBussiness(Person p){}

void test02(){
	Person p(10);
	doBussiness(p);
}

//3. 函数返回局部对象
Person MyBusiness(){
	Person p(10);
	cout << "局部p:" << (int*)&p << endl;
	return p;
}
void test03(){
	//vs release、qt下没有调用拷贝构造函数
	//vs debug下调用一次拷贝构造函数
	Person p = MyBusiness();
	cout << "局部p:" << (int*)&p << endl;
}

编译器存在一种对返回值的优化技术,RVO(Return Value Optimization).在vs debug模式下并没有进行这种优化,所以函数MyBusiness中创建p对象,调用了一次构造函数,当编译器发现你要返回这个局部的对象时,编译器通过调用拷贝构造生成一个临时Person对象返回,然后调用p的析构函数。

我们从常理来分析的话,这个匿名对象和这个局部的p对象是相同的两个对象,那么如果能直接返回p对象,就会省去一个拷贝构造和一个析构函数的开销,在程序中一个对象的拷贝也是非常耗时的,如果减少这种拷贝和析构的次数,那么从另一个角度来说,也是编译器对程序执行效率上进行了优化。

所以在这里,编译器偷偷帮我们做了一层优化:

   当我们这样去调用: Person p = MyBusiness();

编译器偷偷将我们的代码更改为:

 void MyBussiness(Person& _result){
       _result.X:X(); //调用Person默认拷贝构造函数
       //.....对_result进行处理
       return;
   }
int main(){
   Person p; //这里只分配空间,不初始化
   MyBussiness(p);
}
  • 29
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值