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值并没有赋值成功。
这就是引用引来的弊端
使用引用的条件:若函数返回的变量是局部变量,则别用引用返回,否则可以
六、函数的调用优化