c++--this指针,构造函数,析构函数

时间:2021-08-09 19:26:44

一、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'、参数必须使用类类型对象引用传递(为什么?)

若是不使用引用函数的调用过程如下:传参的位置一直调用拷贝构造函数,导致栈溢出。所以拷贝构造函数的参数一定是类对象的引用,这样不会出现栈溢出。

c++--this指针,构造函数,析构函数

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;
}