【c++】this 指针及类的六个默认函数

this指针
一.this指针特性

  • this指针的类型:类类型 * const
  • this指针并不是对象的一部分,不影响sizeof的结果
  • this的作用域在类的“成员函数”的内部
  • this指针是“非静态类成员函数”的第一个默认隐含参数,编译器自动维护传递,类编写者不能显示传递
  • 只有在类的非静态成员函数中才可以使用this指针。其他函数都不可以
二._thiscall调用约定特点

  • _thiscall只能够用在类的成员函数上
  • 参数从右向左压栈
  • 如果参数个数确定,this指针通过ecx传递给被调用者;如果参数不确定,this指针在所有参数被压栈后压入堆栈
  • 对参数个数不定的,调用者清理堆栈,否则函数自己清理堆栈
类的六个默认函数

这里的六个默认的成员函数是在c++98中,而在c++11中默认成员函数已经增加到了八个(新增移动构造函数和移动赋值操作符重载)

一.构造函数

1.基本概念

构造函数是一个特殊的成员函数,名字与类名相同,创建类类型对象时,由编译器自动调用,在对象的生命周期只调用一次,保证每个数据成员都有一个合适的初始值

class Date
{
public:
	Date(int year,int month,int day)
		:_year(year)//初始化列表,可以不用
		,_month(month)
		,_day(day)
	{
		cout<<"Date(int,int,int)"<<this<<endl;
	}
	
	Date(int year,int month)
		:_year(year)//初始化列表,可以不用
		,_month(month)
	{
		cout<<"Date(int,int) "<<this<<endl;
	}
	Date()
	{
		cout<<"Date() "<<this<<endl;
	}
private:
	int _year;
	int _month;
	int _day;
};

int main()
{
	Date d3(2018,8,15);
	Date d1(2018,8);
	Date d2;
	return 0;
}

2.构造函数的特性

  • 函数名与类名相同
  • 没有函数值
  • 新对象被创建时,由编译器自动调用,且在对象的声明周期内仅调用一次
  • 构造函数可以重载,实参决定了决定了调用哪个构造函数
  • 无参构造函数和带有缺省值的构造函数都认为是缺省的构造函数,并且缺省的构造函数只能有一个
  • 有初始化列表(可以不用)
  • 如果没有显示定义时,编译器会合成一个默认的构造函数(在右必要时才会构造,像该类中有类类型的成员变量,且该类类型的成员变量有缺省的构造函数)
class Time
{
public:
	Time()
	{}

private:
	int _hour;
	int _minute;
	int _second;
};

class Date
{
private:
	int _year;
	int _month;
	int _day;
	Time _t;
};

int main()
{
	Date d1;
	return 0;
}

如上,在Date类中,并没有给出构造函数,但是在定义d1时调用了Date类的构造函数,该函数就是由编译器合成的

  • 构造函数不能被const修饰(因为构造函数中可能会为成员变量赋值)
  • 构造函数不能为虚函数(因为不能生成对象)
3.初始化列表

基本概念:以一个冒号开始,接着是一个以逗号分隔的数据成员列表,每个成员变量后面跟一个放在括号中四位初始值或表达式

注意:

  • 每个成员在初始化列表只能出现一次。
  • 初始化列表仅用于初始化类的数据成员,并不指定这些成员的初始化顺序,数据成员在类中定义的顺序就是在参数列表中的初始化顺序
  • 尽量避免使用成员初始化成员,成员的初始化顺序最好和成员的定义保持一致
类中如果有以下成员,必须放到初始化列表位置进行初始化

  • 引用成员变量
  • const成员变量
  • 类类型成员(该类没有缺省的构造函数,因为不初始化,编译器不知道传什么值
4.构造函数的作用
  • 构造&初始化对象
  • 类型转换:对于单个参数的构造函数,可以将其接收参数转化为类类型对象,用explicit修饰构造函数,抑制由构造函数定义的隐式转换,explicit关键字内部的构建申明上,在类的定义体外部的定义上不在重复

二.拷贝构造函数

1.基本概念

只有单个形参,而且该形参是对本类类型对象的引用(常用const修饰),这样的构造函数称为称为拷贝构造函数,创建对象时使用已存在的同类对象来进行初始化,由编译器自动调用

class Date
{
public:
	Date(int year,int month,int day)
		:_year(year)//初始化列表,可以不用
		,_month(month)
		,_day(day)
	{}

	Date(const Date& d )
		:_year(d._day )//初始化列表,可以不用
		,_month(d._month )
		,_day(d._year )
	{}
	
private:
	int _year;
	int _month;
	int _day;
};

int main()
{
	Date d1(2018,8,15);
	Date d2(d1);	
	return 0;
}

2.特征

  • 构造函数的重载,构造函数的性质拷贝构造均满足
  • 参数必须使用类类型对象的引用传递(因为传值会创建临时变量,创建临时变量也会调用拷贝构造函数,会无限循环下去)
  • 如果没有显示定义,系统会自动合成一个默认的拷贝构造函数。默认的拷贝构造函数会依次拷贝类的数据成员完成初始化(不一定合成,但是一定会完成拷贝构造函数)
class Date
{
public:
	Date(int year,int month,int day)
		:_year(year)//初始化列表,可以不用
		,_month(month)
		,_day(day)
	{}	
private:
	int _year;
	int _month;
	int _day;
};

int main()
{
	Date d1(2018,8,15);
	Date d2(d1);	
	return 0;
}

上面的代码中,没有显示定义拷贝构造,编译器也没有合成,但是依然完成了拷贝工作

class Time
{
public:
	Time()
	{}

	Time(const Time& t)
	{}

private:
	int _hour;
	int _minute;
	int _second;
};
class Date
{
public:
private:
	Time _t;
	int _year;
	int _month;
	int _day;
};

int main()
{
	Date d1;
	Date d2(d1);	
	return 0;
}

以上代码中,编译器合成了拷贝构造函数,合成条件与构造函数一致

三.析构函数

1.基本概念

析构函数:在类名(即构造函数名)加上字符~ ,它与构造函数功能相反,在对象被销毁时,由编译器自动调用,完成类的一些资源清理工作

class Array
{
public:
	Array(int capacity)
		:_arr(NULL)
		,_capacity(capacity)
		,_size(0)
	{
		cout<<"Array(int)"<<endl;
		_arr=(int *)malloc(sizeof(int)*_capacity);
	}

	~Array()//析构函数
	{
		if(_arr){
			cout<<"~Array()"<<endl;
			free(_arr);
			_capacity=_size=0;
		}
	}

private:
	int* _arr;
	int _capacity;
	int _size;
};

int main()
{
	Array arr(10);
	return 0;
}

2.特性

  • 析构函数即构造函数名)加上字符~
  • 析构函数无参数无返回值
  • 一个类有且只有一个析构函数,若未显示定义,系统会自动生成缺省的析构函数。
  • 对象生命周期结束时,C++编译系统会自动调用析构函数
  • 注意析构函数体内并不是删除对象,而是做一些清理工作
四.赋值运算符重

1.基本概念

重载操作符是具有特殊函数名的函数,关键字operator后面接需要定义的操作符符号。操作符重载也是一个函数,具有返回值和形参表。它的形参数目与操作符数目相同使用运算符重载可以提高代码的可读性

注:不是所有的运算符都可以重载

2不可以重载的运算符

3.注意

  • 不能通过连接其他符号来创建新的操作符:比如operator@
  • 重载操作符必须有一个类类型或者枚举类型的操作数(操作数不能为内置类型,没有意义)
  • 用于内置类型的操作符,其含义不能改变,例如:内置的整形,不能改变其含义
  • 作为类成员的重载函数,其形参看起来比操作数数目少1成员函数的操作符有一个默认的形参this,限定为第一个形参
  • 一般将算数操作符定义为非成员函数,将赋值运算符定义成员函数(在VS2008下,如果赋值运算符定义为非成员函数会报错)
  • 操作符定义为非类的成员函数时,一般将其定义为类的友元(因为成员变量一般为private)
  • ==和!=操作符一般要成对重载
  • 下标操作符[]:一个非const成员并返回引用,一个是const成员并返回引用
  • 解引用操作符*和->操作符,不显示任何参数
  • 前置事++/-必须返回被增量或者减量的引用,后缀式操作符必须返回旧值,并且应该是值返回而不是引用返回
  • 输入操作符>>和输出操作符<<必须定义为类的友元函数

class Date
{
	friend Date operator+(Date& d1,const Date& d2);//将加法操作符重载定义为类的友元,这样就能访问类的私有成员
public:
	Date(int year=2018,int month=4,int day=3)
		:_year(year)//初始化列表,可以不用
		,_month(month)
		,_day(day)
	{}

	Date(const Date& d )
		:_year(d._day )//初始化列表,可以不用
		,_month(d._month )
		,_day(d._year )
	{}

	Date& operator=(const Date& d)//赋值运算符重载
	{
		_year=d._year ;
		_month=d._month ;
		_day=d._day;

		return *this;//返回引用
	}

private:
	int _year;
	int _month;
	int _day;
};

Date operator+(Date& d1,const Date& d2)
{
	Date d3;
	d3._day =d1._day +d2._day ;
	return d3;
}

int main()
{
	Date d1;
	Date d2(2018,4,3);	

	Date d3;
	d3=d1+d2;

	return 0;
}

4.++操作符重载

class Date
{
	friend Date operator+(const Date& d1,const Date& d2);//将加法操作符重载定义为类的友元,这样就能访问类的私有成员

public:
	

	Date(int year=2018,int month=4,int day=3)
		:_year(year)//初始化列表,可以不用
		,_month(month)
		,_day(day)
	{}

	Date(const Date& d )
		:_year(d._day )//初始化列表,可以不用
		,_month(d._month )
		,_day(d._year )
	{}

	Date& operator=(const Date& d)//赋值运算符重载
	{
		_year=d._year ;
		_month=d._month ;
		_day=d._day;

		return *this;//返回引用
	}
	
	Date operator++()//前置++
	{
		_day+=1;
		return *this;  //返回引用
	}

	const Date operator++(int)//后置++,这里是为了区别前置与后置函数的区别,形成函数重载
	{
		Date d(*this);//利用当前对象拷贝构造临时变量d
		_day+=1;

		return d;//返回值,因为后置++是先使用值,再++
	}

private:
	int _year;
	int _month;
	int _day;
};

Date operator+(const Date& d1,const Date& d2)
{
	Date d3;
	d3._day =d1._day +d2._day ;
	return d3;
}

int main()
{
	Date d1;
	Date d2(2018,4,9);	

	Date d3;
	d3=d2++;
	d3=++d1;

	return 0;
}
五.址操作符重载和const修饰的取地址操作符重载
class Date
{
public:
	Date(int year=2018,int month=4,int day=3)
		:_year(year)//初始化列表,可以不用
		,_month(month)
		,_day(day)
	{}

	Date(const Date& d )
		:_year(d._day )//初始化列表,可以不用
		,_month(d._month )
		,_day(d._year )
	{}

	Date& operator=(const Date& d)//赋值运算符重载
	{
		_year=d._year ;
		_month=d._month ;
		_day=d._day;

		return *this;//返回引用
	}
	
	Date* operator&()//取地址操作符重载.this指针为Date* const this(指向不能改变)
	{
		return this;
	}

	const Date* operator&()const//取地址操作符重载.this指针为
				//const Date* const this(除了指向不能改变,指针所指向的内容也不能改变)
	{
		return this;
	}
private:
	int _year;
	int _month;
	int _day;
};

int main()
{
	Date d1;
	Date d2(2018,4,9);	

	cout<<&d1<<endl;
	cout<<&d2<<endl;

	return 0;
}

说明:取地址操作符重载和const修饰的取地址操作符重载这两个默认成员函数并没有什么意义



猜你喜欢

转载自blog.csdn.net/lw__sunshine/article/details/80803425