C++学习第三天----构造函数、析构函数、拷贝构造函数、赋值构造函数、取地址运算符重载


一、构造函数的定义和使用

  1、函数名和类名相同

  2、构造函数无函数返回类型说明。即什么也不写,实际上构造函数有返回值,返回的就是构造函数所创建的对象

class Test
{
public:
	Test()
	{
		cout << "Creat Test Object:" << this << endl;
		data = 0;
	}
private:
	int data;
};
void main()
{
	Test t1;
	printf("%p",&t1);
}

  3、在程序运行时,当新的对象被建立,该对象所属的类的构造函数自动被调用,在该对象的生存周期中也只被调用一次。

#include "Test.h"
class Test
{
public:
	Test()
	{
		cout << "Creat Test Object:" << this << endl;
		data = 0;
	}
private:
	int data;
};
void main()
{
	Test t1;
	Test t2;
	Test t3;
}
//output
//Creat Test Object : 004FF914
//Creat Test Object : 004FF908
//Creat Test Object : 004FF8FC

  4、构造函数可以重载。

class Test
{
public:
	Test()
	{
		cout << "Creat Test Object:" << this << endl;
		data = 0;
	}
	Test(int d)
	{
		data = d;
	}

	Test(int d,int x, int y)
	{
		data = d;
		this->x = x;
		this->y = y;
	}
private:
	int data;
	int x;
	int y;
	int d;
};
void main()
{
	Test t1;//这里的t1后面没有()
	Test t2(10);
	Test t3(10,2,4);
}

  5、构造函数可以在类中定义,也可以在类外定义。

#include "Test.h"
class Test
{
public:
	Test()
	{
		cout << "Creat Test Object:" << this << endl;
		data = 0;
	}
	Test(int x, int y);

	Test(int d)
	{
		data = d;
	}

	Test(int d, int x, int y)
	{
		data = d;
		this->x = x;
		this->y = y;
	}
private:
	int data;
	int x;
	int y;
	int d;
};
Test::Test(int x, int y)
{
	this->x = x;
	this->y = y;
}
void main()
{
	Test t1;
	Test t2(10);
	Test t3(10,2,4);
	Test t4(20, 30);
}

  6、如果类中没有给构造函数,则C++编译器自动给出一个缺省的构造的函数:

      只要构造函数是无参的或者只要各参数均有缺省值的,C++编译器都都认为是缺省的构造函数,并且缺省的构造函数只有一个。

#include "Test.h"
class Test
{
public:
	
	Test(int x=1, int y=2);

private:
	int data;
	int x;
	int y;
	int d;
};
Test::Test(int x, int y)
{
	this->x = x;
	this->y = y;
	cout << "Creat Test Object:" << this << endl;
}
void main()
{
	Test t1;
}

  7、如果对象的数据成员全为公有的,也可以在对象名后加 “=” 加 “{ }”。在花括号中顺序填入全体数据成员的初始值。

二、析构函数定义和使用

当一个对象定义时,C++自动调用构造函数建立该对象并进行初始化,那么当一个对象的生命周期结束时,C++也会自动调用一个函数注销该对象并进行善后工作,这个特殊的成员函数就是析构函数。

  1、函数名和类名相同,但在前面要加‘~’

  2、无函数返回类型,不带任何参数

  3、一个类有一个也只有一个析构函数(相当于java中的static函数,所有对象共用这个函数,只需要把各自对象地址传递过去即可,实现原理就是用的this指针)

  4、对象注销时,系统自动调用析构函数

#include "Test.h"
class Test
{
public:
	
	Test()
	{
		cout << "Creat Test Obj:" << this << endl;
	}
	~Test()
	{
		cout << "Free Test Obj:" << this << endl;
	}

public:
	int data;
	int x;
	int y;
	int d;
};

void main()
{
	Test t1 ;
	Test t2;
	Test t3;
	Test t4;
}

 上图构造顺序是1 2 3 4  析构顺序是4 3 2 1

因为构造是入栈的过程,析构是出栈的过程

三、自动装箱和自动拆箱

  

#include "Test.h"
class Test
{
public:
	
	Test(int d=0)
	{
		cout << "Creat Test Obj:" << this << endl;
		data = d;
		cout << d << endl;
	}
	~Test()
	{
		cout << "Free Test Obj:" << this << endl;
	}
	operator int()
	{
		return data;
	}
public:
	int data;
	int x;
	int y;
};

void main()
{
	Test t1 = 200;
	t1 = 100;//自动装箱,调用Test(int d=0)构造函数 给data赋值
     cout <<"t1.data="<     int value;
     value = t1; //自动拆箱,调用operator int() 返回data值
     cout << "value=" << value << endl; }

 这是我表面理解成自动装箱和自动拆箱,实际上这跟java的自动拆装箱原理还是不太一样的

t1 = 100;  t1是Test类型 100是整型,要想进行赋值操作,就得找个中间的Test类型的变量用来接收100
同理double a = 22.14;int b = a;实际上是找了中间变量c保存22,再赋值给b

三、引用
  1、对变量引用
int a = 10;
int &b = a;

  2、对指针引用

int a = 10;
int *p = &a;
int *&q = p;//指针q引用指针p

  3、对数组引用

int arr[10] = {1,2,3,4,5,6,7,8,9,10};
int(&brr)[10] = arr;

  4、常引用

const int x = 100;
const int &y = x;

int n = 20;
const int &m = n;
	

  5、诡异的引用

const double d = 12.34;
const int &f = d;

  debug查看d和f的地址,以及值如下:

 &d  != &f 说明类型不一样时,d截取12保存在临时空间,再把临时空间起个别名叫f

四、拷贝构造函数

 同一个类的对象在内存中有完全相同的解构,如果进行整体复制是完全可行的,这个拷贝过程只需要拷贝数据成员,而函数成员是公用的。所以在创建对象时,可以用同一个类的另一个对象来初始化该对象,这时所用到的构造函数称为拷贝构造函数,当不编写拷贝构造函数时,默认编译器会有一个拷贝构造函数

拷贝构造函数使用的三个场景:

  1、直接赋值

Test t2(t1);//拷贝构造
Test t3 = t1;//拷贝构造

  2、当函数参数是对象时,会调用拷贝构造

#include "iostream"
using namespace std;
class Test
{
public:
	Test(int d = 0) 
	{
		cout << "Creat default construct" << endl;
		data = d;
	}
	Test(const Test& t)//拷贝构造函数
	{
		
		cout << "Creat copy construct" << this << endl;
		data = t.data;
	}
	Test& operator = (const Test& t)//赋值构造函数
	{
		if (this != &t)
		{
			data = t.data;
		}
		return *this;
	}
	~Test()
	{
		cout << this << ":free this obj" << endl;
	}
public:
	int getData()
	{
		return data;
	}
private:
	int data;
};
int fun(Test x)
{
	int value;
	value = x.getData();
	return value;
void main()
{
	Test t1;
	fun(t1);
	
}

  3、函数返回值是对象时,调用拷贝构造函数

#include "iostream"
using namespace std;
class Test
{
public:
	Test(int d = 0) 
	{
		cout << "Creat default construct" << endl;
		data = d;
	}
	Test(const Test& t)//拷贝构造函数,采用引用代码出现无穷拷贝构造函数的调用,其实不采用引用,编译会报错
	{
		
		cout << "Creat copy construct" << this << endl;
		data = t.data;
	}
	Test& operator = (const Test& t)//赋值构造函数,采用引用可以避免调用构造函数 
	{
		if (this != &t)
		{
			data = t.data;
		}
		return *this;
	}
	~Test()
	{
		cout << this << ":free this obj" << endl;
	}
public:
	int getData()
	{
		return data;
	}
private:
	int data;
};
Test fun(Test x)
{
	int value;
	value = x.getData();
	Test tmp(10);
	return tmp;
}
void main()
{
	Test t1;
	Test t2(t1);//拷贝构造
	Test t3 = t1;//拷贝构造
	Test t4;
	t4 = t1;//赋值构造
	Test t6 = fun(t1);//函数返回时已经拷贝构造了一个无名的对象,所以t6就不需要再调用拷贝构造函数了
	Test t5;
	t5 = fun(t1);//这里需要调用赋值构造函数
}

五、赋值构造函数

  C++中不编写赋值构造函数时,编译器会默认提供,功能就是把成员变量进行赋值

#include "Test.h"
class Test
{
public:
	Test(int d=0)
	{
		cout << "Creat Test Obj:" << this << endl;
		data = d;
	}
	~Test()
	{
		cout << "Free Test Obj:" << this << endl;
	}
public:
	int data;
};

void main()
{
	Test t1 = 10;
	Test t2;
	t2 = t1;
}

  上述代码就是把t1的成员变量值赋值给t2的成员变量,即t2里的data也是10。

自己定义赋值构造函数

  1、模板1 

#include "Test.h"
class Test
{
public:
	Test(int d=0)
	{
		cout << "Creat Test Obj:" << this << endl;
		data = d;
	}
	Test(const Test &t)
	{
		cout << "Creat copy construct" << this << endl;
		data = t.data;
	}
	void operator= (Test t)
	{
		data = t.data;
	}
	~Test()
	{
		cout << "Free Test Obj:" << this << endl;
	}
public:
	int data;
};

void main()
{
	Test t1 = 200;
	Test t2;
	t2 = t1;
}

 (1)这里在执行t2 = t1的时候会先执行一个拷贝构造函数(这样效率很低),再执行赋值构造函数,因为赋值构造函数里的形参是Test类对象

 (2)t2 = t1 其实就是t2.operate=(t1),就是t2调用operate()函数,

  2、模板2 对模板1的缺点:调用拷贝构造函数,效率低   优化,

#include "Test.h"
class Test
{
public:
	Test(int d=0)
	{
		cout << "Creat Test Obj:" << this << endl;
		data = d;
	}
	Test(const Test &t)
	{
		cout << "Creat copy construct" << this << endl;
		data = t.data;
	}
	void operator= (const Test &t)//追加1、const 防止被修改 2、&应用,可以避免调用构造函数
	{
		if(this != &t)//追加这个判断的作用是防止,对象自己给自己复制,避免效率低
          {
            data = t.data;
          } } ~Test() { cout << "Free Test Obj:" << this << endl; } public: int data; }; void main() { Test t1 = 200; Test t2; t2 = t1; }

 这里还有个缺点,就是返回值问题,这里是void

  3、模板3 当出现连等赋值时,模板二就会编译出错

#include "Test.h"
class Test
{
public:
	Test(int d=0)
	{
		cout << "Creat Test Obj:" << this << endl;
		data = d;
	}
	Test(const Test &t)
	{
		cout << "Creat copy construct" << this << endl;
		data = t.data;
	}
	Test& operator= (Test &t) //修改两点: 1、返回值追加Test类型,这样main函数就可以使用连等运算 2、返回以引用返回,避免调用构造函数
	{
		if(this != &t)
		{
			data = t.data;
		}
		return *this;
		
	}
	~Test()
	{
		cout << "Free Test Obj:" << this << endl;
	}
public:
	int data;
};

void main()
{
	Test t1 = 200;
	Test t2;
	Test t3;
	t3 = t2 = t1;//相当于t3.operator=(t2.operator=(t1)) 如果赋值构造函数无返回值,那么这个调用就会编译器报错
}

总结:模板3才是真正的模板,但也要注意,引用不能滥用,比如看下面的代码

#include "Test.h"
class Test
{
public:
	Test(int d=0)
	{
		cout << "Creat Test Obj:" << this << endl;
		data = d;
	}
	Test(const Test &t)
	{
		cout << "Creat copy construct" << this << endl;
		data = t.data;
	}
	Test& operator= (Test &t)
	{
		if(this != &t)
		{
			data = t.data;
		}
		return *this;
		
	}
	~Test()
	{
		cout << "Free Test Obj:" << this << endl;
	}
public:
	int GetData()
	{
		return data;
	}
private:
	int data;
};

Test& fun(Test x)
{
	int value;
	value = x.GetData();
	Test tmp(value);
	return tmp;
}

void main()
{
	Test t1 = 10;
	Test t2;
	t2 = fun(t1);
}

  debug时会发现,程序执行后t2里的data是个随机值,并不是10,说明t1给t2赋值失败,原因是什么呢?

  其实是因为调用fun函数时,返回的是引用且tmp是局部变量,出了fun函数就消失了,所以t2的data值并没有赋值成功。

  这就是引用引来的弊端

使用引用的条件:若函数返回的变量是局部变量,则别用引用返回,否则可以

六、函数的调用优化