一、this指针:
this指针特性
this指针的类型:类类型* const this指针并不是对象本身的一部分,不影响sizeof的结果 this的作用域在类”成员函数”的内部 this指针是”类成员函数”的第一个默认隐含参数,编译器自动维护 传递,类编写者不能显式传递 只有在类的非静态成员函数中才可以使用this指针,其它任何函数都 不可以
__thiscall调用约定:
__thiscall只能够用在类的成员函数上 参数从右向左压栈 如果参数个数确定,this指针通过ecx传递给被调用者;如果参数不 确定(_cdecl),this指针在所有参数被压栈后压入堆栈 对参数个数不定的,调用者清理堆栈,否则函数自己清理堆栈
问题: 1. 引用底层也是指针,此处为什么不是引用,而使用this指针呢? 2. this指针是否可以为空?
一个成员函数看起来有n个参素,实际上有n+1个参数,还有一个隐藏的this指针,由编译器传,因为是编译器自动编译的。
this指针传参的方式:
(1)ecx寄存器: 用this_call这种调用约定。
(2)参数压栈:函数接受参数的个数是不确定的,使用_cdecl这种调用约定,而不是this_call
this指针可能为空,只要在当前函数里面没有访问成员变量或者数据,则程序一定不会崩溃
class Test { public: void FunTest() { cout << this << endl; //_data = 10; } int _data; }; int main() { Test t; t.FunTest(); Test *pt = &t;//mov ecx pt pt->FunTest();//call Test::FunTest()参数为编译器默认this指针的地址 pt = NULL; pt->FunTest();//把pt的内容当成函数的一个参数,//Test::FunTest(pt(NULL))this指针为空 return 0; }
二、构造函数:(编译器自己感觉需要时再去合成默认的构造函数)
1、构造函数是一个特殊的成员函数,名字与类名相同,创建类类型对象时,由编 译器自动调用,在对象的生命周期内只调用一次,保证每个数据成员都有 一个合适的初始值。
2. 构造函数特性
函数名与类名相同
没有返回值
新对象被创建时,由编译器自动调用,且在对象的声明周期内仅调用 一次
构造函数可以重载,实参决定了调用那个构造函数
无参构造函数和带有缺省值的构造函数(全缺省)都认为是缺省的构造函数,并 且缺省的构造函数只能有一个
有初始化列表(可以不用)
如果没有显式定义时,编译器会合成一个默认的构造函数
构造函数不能用const修饰(为什么?)
构造函数不能为虚函数(为什么?)
3. 对象初始化 初始化列表:以一个冒号开始,接着是一个以逗号分隔的数据成员列表, 每个”成员变量”后面跟一个放在括号中的初始值或表达式
注意:
每个成员在初始化列表中只能出现一次(为什么?)
初始化列表仅用于初始化类的数据成员,并不指定这些数据成员的初 始化顺序,数据成员在类中定义顺序就是在参数列表中的初始化顺序
尽量避免使用成员初始化成员,成员的初始化顺序好和成员的定义 顺序保持一致
类中包含以下成员,一定要放在初始化列表位置进行初始化:
引用成员变量
const成员变量
类类型成员(该类有非缺省的构造函数)
4. 构造函数作用
构造&初始化对象
类型转换 对于单个参数构造函数,可以将其接受参数转化成类类型对象。用 explicit修饰构造函数,抑制由构造函数定义的隐式转换,explicit 关键字类内部的构建声明上,在类的定义体外部的定义上不再重复
class Data { public: Data(int year) :_year(year) { cout << "Data(int):" << this << endl; } private: int _year; int _month; int _day; }; int main() { Data d1(2018); d1 = 2019;//左操作数为日期对象,右操作数为整型 //先把2019转化为临时对象,以2019作为构造函数的参数,用临时对象给d1赋值。 //转化的前提是类的构造函数是单参的。 return 0; }
尽量不要使用隐式类型转化,可能产生意向想不到的错误加上关键字explicit
class Data
{
public:
explicit Data(int year)
:_year(year)
{
cout << "Data(int):" << this << endl;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Data d1(2018);
d1 = 2019;//左操作数为日期对象,右操作数为整型
//先把2019转化为临时对象,以2019作为构造函数的参数,用临时对象给d1赋值。
//转化的前提是类的构造函数是单参的。
return 0;
}
加上exlicit后隐式转换时会报错。
合成构造函数的前提:(1)、如果A类中包含了B类的对象,B类有缺省的构造函数,A没有显式自己的构造函数。
三、拷贝构造函数:是构造函数的重载。
只有单个形参,而且该形参是对本类类型对象的引用(常用const修饰),这 样的构造函数称为拷贝构造函数。拷贝构造函数是特殊的构造函数,创建 对象时使用已存在的同类对象来进行初始化,由编译器自动调用
class Date { public: Date(int year,int month,int day) :_year(year) , _month(month) , _day(day) { cout << "Date(int):" << this << endl; } private: int _year; int _month; int _day; }; int main() { int a = 10; int b(10); int c(b); Date d1(1028, 1, 1); Date d2(d1);//用已经存在的d1创建一个新的实体。只要创建新的对象就要创造实体 return 0; }
要求:
(1)、函数参数是对类类型的引用并且只能有一个参数,不需要通过参数改变外部实参的话,最好加上const。函数体里面可以打印this的地址。this可以用于函数体中,不可以用于初始化列表。
class Date { public: Date(int year,int month,int day) :_year(year) , _month(month) , _day(day) { cout << "Date(int):" << this << endl; } Date(const Date &d) :_year(d._year) , _month(d._month) , _day(d._day) { cout << "Date(const Date &):" << this << endl; } private: int _year; int _month; int _day; }; int main() { int a = 10; int b(10); int c(b); Date d1(1028, 1, 1); Date d2(d1);//用已经存在的d1创建一个新的实体。只要创建新的对象就要创造实体 return 0; }
this可否用于初始化列表之中?
this不可以用于初始化列表中,以下会产生错误:
class Date
{
public:
Date(int year,int month,int day)
:_year(year)
, _month(month)
, _day(day)
{
cout << "Date(int):" << this << endl;
}
Date(const Date &d)
:_year(d._year)
, this->_month(d._month)
, _day(d._day)
{
cout << "Date(const Date &):" << this << endl;
}
产生错误的原因是:函数体里面是进行赋值,初始化列表是对类中的各个成员变量进行初始化,上述初始化的位置对象不完整,所以不能使用this。
(2)应用场景:调用构造函数和拷贝构造函数的相同点是都创建新的对象,不同点是:拷贝构造函数是用已经存在的对象创建新的对象,其他位置调用的都是构造函数。
1’对象实例化对象
2' 作为函数参数
3'作为函数返回值
引用和传值两种方式调用构造和拷贝构造函数的过程:
1'、传值
class Date { public: Date(int year=290,int month=11,int day=1) :_year(year) , _month(month) , _day(day) { cout << "Date(int):" << this << endl; } Date(const Date &d) :_year(d._year) ,_month(d._month) , _day(d._day) { cout << "Date(const Date &):" << this << endl; } void SetDay(int day) { _day = day; } int GetDay() { return _day; } private: int _year; int _month; int _day; }; Date TestDate(Date d) { Date tmp(d); tmp.SetDay(tmp.GetDay() + 1); return tmp; } void Test() { Date d1(299, 1, 4);//1、调构造函数,构造出d1; Date d2;//2、创建d2; d2 = TestDate(d1); //执行:1、调构造函数,构造出d1;2、创建d2; //d2 = TestDate(d1):3、因为Date TestDate(Date d)以值的方式传递,生成一个临时对象,用已经存在的 //对象产生新的对象,调用拷贝构造函数 //4、TestDate(Date d)函数体里面 Date tmp(d):调用拷贝构造函数 //5、 tmp.SetDay(tmp.GetDay() + 1); return tmp;tmp以值的方式返回需要创建临时对象 //再调用一次拷贝构造函数 }
2'、引用:
class Date { public: Date(int year=290,int month=11,int day=1) :_year(year) , _month(month) , _day(day) { cout << "Date(int):" << this << endl; } Date(const Date &d) :_year(d._year) ,_month(d._month) , _day(d._day) { cout << "Date(const Date &):" << this << endl; } void SetDay(int day) { _day = day; } int GetDay() { return _day; } private: int _year; int _month; int _day; }; Date &TestDate(Date &d) { Date tmp(d); tmp.SetDay(tmp.GetDay() + 1); return d;//引用的声明周期一定要比函数的声明周期长,引用变量的声明周期比函数长 //因为引用相当于对实参取别名,实参的声明周期比函数的声明周期长,所以不能直接return tmp //d的声明周期比tmp长,tmp会销毁。 } void Test() { Date d1(299, 1, 4);//1、调构造函数,构造出d1; Date d2;//2、创建d2; d2 = TestDate(d1); //执行:1、调构造函数,构造出d1;2、调用构造函数创建d2; //3、调用d2 = TestDate(d1): //Date tmp(d):因为该函数以引用接收,不需要创建临时对象,调用拷贝构造函数创建tmp //tmp.SetDay(tmp.GetDay() + 1);临时对象加1,return d;后返回d1,不需要创建临时对象 //d2 = TestDate(d1);赋值给d1 }用已经存在的对象创建新的对象,叫做拷贝构造函数,其他创建新对象的方式都为构造函数。
(3)特性:
1'、构造函数的重载,构造函数的性质拷贝构造函数均满足 (拷贝构造函数是构造函数的一种特殊形式,所以构造函数的特性拷贝构造函数都满足。除了拷贝构造函数不能重载)
2'、参数必须使用类类型对象引用传递(为什么?)
若是不使用引用函数的调用过程如下:传参的位置一直调用拷贝构造函数,导致栈溢出。所以拷贝构造函数的参数一定是类对象的引用,这样不会出现栈溢出。
3'、如果没有显式定义,系统会自动合成一个默认的拷贝构造函数。默认 的拷贝构造函数会依次拷贝类的数据成员完成初始化
class Time { public: Time(int hour=0, int minute=0, int second=0) :_hour(hour) , _minute(minute) , _second(second) {} //拷贝构造函数 Time(const Time& t) :_hour(t._hour) , _minute(t._minute) , _second(t._second) {} private: int _hour; int _minute; int _second; }; class Date { public: Date(int year,int month,int day) :_year(year) , _month(month) , _day(day) { cout << "Date(int):" << this << endl; } Date(const Date &d) :_year(d._year) ,_month(d._month) , _day(d._day) { cout << "Date(const Date &):" << this << endl; } private: int _year; int _month; int _day; Time _t; }; int main() { int a = 10; int b(10); int c(b); Date d1(1028, 1, 1); Date d2(d1);//d1拷贝构造d2,调用Date类的拷贝构造函数,为了调用Time类中的构造函数,用d1对象中的_t拷贝d2对象中的_t。编译器自己合成一个拷贝构造函数,(用已经存在的d1调用不存在的d2必须调用拷贝构造函数) // return 0; } //此时日期类虽然没有给拷贝构造函数,编译器自动合成
四、析构函数:
以下的程序,会产生空间无法销毁的情况:
typedef int DataType; class SeqList { public: SeqList(size_t capacity) :_pData((int *)malloc(sizeof(DataType)*capacity)) , _capacity(capacity) , _size(0) { } void PushBack(const DataType &data) { _pData[_size++] = data; } private: DataType *_pData; size_t _capacity; size_t _size; }; void TestSeqList() { SeqList s(10); s.PushBack(1); s.PushBack(2); s.PushBack(3); //1、free(s._pData);//不能采用这种方式释放动态内存空间,因为_pData是私有的 //2、采用Get,Set方法来把私有的成员变为公有的成员,在这里不安全,若是让自己的空间 //被不坏好意的人知道,会产生意想不到的错误 } int main() { TestSeqList(); return 0; }
我们可以引入析构函数,销毁空间:
typedef int DataType; class SeqList { public: SeqList(size_t capacity) :_pData((int *)malloc(sizeof(DataType)*capacity)) , _capacity(capacity) , _size(0) { } void PushBack(const DataType &data) { _pData[_size++] = data; } ~SeqList() { cout << "~SeqList"<<this<<endl; if (_pData) { free(_pData); _capacity = 0; _size = 0; } } private: DataType *_pData; size_t _capacity; size_t _size; }; void TestSeqList() { SeqList s(10); s.PushBack(1); s.PushBack(2); s.PushBack(3); } int main() { TestSeqList(); return 0; }
1、原理:析构函数:与构造函数功能相反,在对象被销毁时,由编译器自动调用,完成类 的一些资源清理和汕尾工作。(类中涉及到资源(打开文件、申请资源)一般会显示定义出析构函数,作用是释放资源)
2、特性:
(1)析构函数在类名(即构造函数名)加上字符~
(2)析构函数无参数无返回值 :析构函数没有参数(可以有void类型的参数),不能重载。
(3)一个类有且只有一个析构函数。若未显示定义,系统会自动生成缺省 的析构函数 (类中包含其他类型的对象,该对象包含析构函数,则会合成)
(4)对象生命周期结束时,C++编译系统系统自动调用析构函数
(5)注意析构函数体内并不是删除对象,而是做一些清理工作(不是删除对象的内存空间,仍在在个位置上,只有函数调用结束,才释放内存空间)
(6)编译器在一些情况下会自动合成拷贝构造函数,或者不合成拷贝构造函数,但是实现拷贝构造函数的功能。以下加入SeqList S2(S)会产生的问题:程序会崩溃。s与s2的内容相同,s与s2公用同一块空间,出了函数的时候会销毁,是栈上的变量,创建的晚先销毁,创建的早后销毁,所以销毁的时候,先销毁s2,s2指向的空间已被释放,s成为野指针。
typedef int DataType;
class SeqList
{
public:
SeqList(size_t capacity)
:_pData((int *)malloc(sizeof(DataType)*capacity))
, _capacity(capacity)
, _size(0)
{
}
void PushBack(const DataType &data)
{
_pData[_size++] = data;
}
~SeqList()
{
cout << "~SeqList"<<this<<endl;
if (_pData)
{
free(_pData);
_capacity = 0;
_size = 0;
}
}
private:
DataType *_pData;
size_t _capacity;
size_t _size;
};
void TestSeqList()
{
SeqList s(10);
s.PushBack(1);
s.PushBack(2);
s.PushBack(3);
SeqList s2(s);
}
int main()
{
TestSeqList();
return 0;
}
在当前日期的基础上加1000天:
方式一:会改变原先的值,相当于+=,不合适
class Date { public: Date(int year=290,int month=11,int day=1) :_year(year) , _month(month) , _day(day) { cout << "Date(int):" << this << endl; } Date(const Date &d) :_year(d._year) ,_month(d._month) , _day(d._day) { cout << "Date(const Date &):" << this << endl; } Date &Add(size_t days) { _day += days; return *this;//当前对象的声明周期比函数长 } private: int _year; int _month; int _day; }; int main() { Date d1(122, 3, 4); d1.Add(5);//此方式会改变左边的值,相当于加等,所以不合适 return 0; }
方式2:不改变左右操作数,用当前对象构造一个临时对象,将天数加到临时对象上,没有改变左右操作数的值,返回加后的结果。临时对象不能通过引用返回,声明周期不同。
class Date { public: Date(int year=290,int month=11,int day=1) :_year(year) , _month(month) , _day(day) { cout << "Date(int):" << this << endl; } Date(const Date &d) :_year(d._year) ,_month(d._month) , _day(d._day) { cout << "Date(const Date &):" << this << endl; } Date Add(size_t days) { Date tmp(*this); tmp._day += days; return tmp;//当前对象的声明周期比函数长 } private: int _year; int _month; int _day; }; int main() { Date d1(122, 3, 4); d1.Add(5); return 0; }
方式3:d1不能直接加上一个数字,因为有一个自定义的对象,运算符重载的方式,把加的规则告诉编译器:
class Date { public: Date(int year=290,int month=11,int day=1) :_year(year) , _month(month) , _day(day) { cout << "Date(int):" << this << endl; } Date(const Date &d) :_year(d._year) ,_month(d._month) , _day(d._day) { cout << "Date(const Date &):" << this << endl; } Date operator+(int days)//运算符为+,有两个操作数。是成员函数,所以参数位置有一个隐藏的 //this指针,相当于有两个参数.+左边的为左操作数,-为为右操作数 //不能改变左右操作数,所以加到临时空间上 { Date tmp(*this); tmp._day += days; return tmp; } private: int _year; int _month; int _day; }; int main() { Date d1(122, 3, 4); Date d2; d2 = d1 + 5; return 0; }