【C++】类和对象2(this指针、默认成员函数、构造函数)


一、this指针

1.this指针的引入

这里实现一个日期的类,之后的内容以这个类为例。

代码如下(示例):

class Date
{
public:

private:
	int _year;//年
	int _month;//月
	int _day;//日
};

但是想要修改或使用的成员变量都是private的,在类外无法访问,所以需要在public中定义成员函数对成员变量进行操作。

代码如下(示例):

class Date
{
public:
	void Init(int year = 1970, int month = 1, int day = 1) 
	{
		//C++类中的成员变量命名风格应与形参不同
		//这样才能分清楚哪个是成员变量,哪个是形参
		_year = year;
		_month = month;
		_day = day;
		//把形参赋值给成员变量
	}

	void Print()
	{
		cout << _year << "-" << _month << "-" << _day << endl;
	}

private:
	//C++类中的成员函数命名风格应与形参不同
	int _year;//年
	int _month;//月
	int _day;//日
};

int main()
{
	Date d1, d2;
	d1.Init(2021, 7, 8);
	d2.Init();
	d1.Print();
	d2.Print();
	return 0;
}

上面的代码调试查看反汇编代码可知:d1、d2调用的Init和Print函数是同一个(函数地址相同)。

结果如下:

在这里插入图片描述
那么这些函数是如何知道应该改变d1的成员变量还是应该改变d2的成员变量呢?

C++中通过引入this指针解决该问题,即:C++编译器给每个“非静态的成员函数“增加了一个隐藏的指针参数,让该指针指向当前对象(函数运行时调用该函数的对象),在函数体中所有成员变量的操作,都是通过该指针去访问。只不过不需要我们来传递,编译器自动完成。


2.this指针的特性

(1)只能在“成员函数”的内部使用。
(2)this指针本质上其实是一个成员函数的形参,是对象调用成员函数时,将对象地址作为实参传给this形参。所以对象中不存储this指针。
(3)this指针是成员函数第一个隐含的指针形参,一般情况由编译器通过ecx寄存器自动传递,不需要我们手动传递

所以,上面的代码经过编译器处理会转变成下面的代码,但是注意,下面代码不一定能全部编译通过,仅仅是帮助理解this指针。

代码如下(示例):

class Date
{
public:
	void Init(Date* this, int year = 1970, int month = 1, int day = 1) 
	{
		this->_year = year;
		this->_month = month;
		this->_day = day;
	}

	void Print(Date* this)
	{
		cout << this->_year << "-" << this->_month << "-" << this->_day << endl;
	}

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

int main()
{
	Date d1, d2;
	d1.Init(&d1, 2021, 7, 8);
	d2.Init(&d2);
	d1.Print(&d1);
	d2.Print(&d2);
	return 0;
}

上文多次强调this指针是隐含的,所以不能在函数调用或函数定义时显式地写出

代码如下(示例):

//		  error
void Init(Date* this, int year = 1970, int month = 1, int day = 1) 
{
	//...
}
//		error
d2.Init(&d2);

但是在成员函数中可以直接用this指针

代码如下(示例):

void Init(int year = 1970, int month = 1, int day = 1) 
{
	this->_year = year;//直接用
	this->_month = month;//可以写
	_day = day;//也可以不写
}

3.两个小问题

(1)this指针存储在哪里?

this指针是存在栈上的,因为形参都是存在栈上的(有时也被存在寄存器上)。

(2)this指针可以为空(nullptr)吗?

代码如下(示例):

class A
{
public:
	void Print1()
	{
		cout << _a << endl;
	}

	void Print2()
	{
		cout << "Print2()" << endl;
	}
private:
	int _a;
};

int main()
{
	A* a = nullptr;
	a->Print1();
	a->Print2();
	return 0;
}

上面的代码中,a->Print1();崩溃,a->Print2();正常运行。

下面的代码可以帮助理解(但是不能通过编译)

代码如下(示例):

//运行时,下面的this为nullptr
void Print1(Date* this)
	{
		cout << this->_a << endl;
	}

	void Print2(Date* this)
	{
		cout << "Print2()" << endl;
	}

很明显Print1中对空指针进行了解引用,所以崩溃;而Print2没有,所以正常运行。


二、类的6个默认成员函数

如果一个类中什么成员都没有,简称为空类。但空类中什么都没有吗?并不是的,任何一个类在我们自己不写的情况下,都会自动生成6个默认成员函数。
(1)构造函数:初始化对象
(2)析构函数:清理
(3)拷贝构造:使用同类对象初始化创建一个 (新的)对象
(4)赋值重载:把一个对象赋值给另一个 (已经存在的)对象
(5)普通对象取地址重载
(6)const对象取地址重载

其中(5)、(6)很少需要自己实现


三、构造函数

1.概念

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


2.特性

构造函数是特殊的成员函数,需要注意的是,构造函数的虽然名称叫构造,但是构造函数的主要任务并不是开空间创建对象,而是初始化对象

(1)函数名与类名相同。

(2)无返回值。

这里一定要注意与void返回值区分:构造函数没有返回值;返回值为void表示有返回值但返回值为空。

(3)对象实例化时编译器自动调用对应的构造函数。

在用C语言实现数据结构时,会因忘记调用初始化函数而产生很多bug;C++实现构造函数后编译器自动调用,这样就能够保证对象一定被初始化。


(4)构造函数可以重载。

代码如下(示例):

class Date
{
public:
	//函数重载

	// 1.无参构造函数
	Date()//无返回值!!!
	{
		_year = 1970;
		_month = 1;
		_day = 1;
	}

	// 2.带参构造函数
	Date(int year, int month, int day)//无返回值!!!
	{
		_year = year;
		_month = month;
		_day = day;
	}
	//上面两个Date函数也可以通过给出默认值合二为一
	
private:
	int _year;
	int _month;
	int _day;
};

int main()
{
	Date d1;//调用无参构造函数
	Date d2(2021, 7, 8);//调用含参构造函数,注意写法

	//注意下面的写法是声明了一个返回值为Date,函数名为d3的函数
	//而不是创建了一个名为d3的对象
	//所以通过无参构造函数创建对象时,对象后面不能有括号,否则就成了函数声明
	Date d3();
	
	return 0;
}

(5)类中没有显式给出构造函数,则编译器自动生成

如果类中显式写出了构造函数,则编译器不再生成。

代码如下(示例):

class Date
{
public:
	void print()
	{
		cout << _year << " " << _month << " " << _day << endl;
	}
private:
	int _year;
	int _month;
	int _day;
};

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

运行结果如下:

在这里插入图片描述

似乎编译器默认生成的默认构造函数没发挥什么作用,d1内的三个成员变量仍全部都是随机值。但事实并不是这样的。


代码如下(示例):

class A
{
public:
	A(int a = 0)
	{
		_aa = a;
	}

	void printA()
	{
		cout << "printA()" << endl;
		cout << _aa << endl;
	}
private:
	int _aa;
};

class Date
{
public:
	void print()
	{
		cout << _year << " " << _month << " " << _day << endl;
		_a.printA();
	}
private:
	int _year;
	int _month;
	int _day;
	A _a;
};

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

运行结果如下

在这里插入图片描述

可以得出,编译器生成的默认构造函数并不是什么都不做,只是对内置类型和自定义类型区别对待。

对内置类型(int,char……)及其指针不做处理。
对自定义类型(struct,class……定义的类型)去调用它们的构造函数来初始化。

上面第二段代码,编译器生成的默认构造函数对_year、_month、_day三个int型变量不做处理,而对_a这个A类型的变量调用了默认构造函数初始化。


(6)默认构造函数

无参构造函数、全缺省构造函数、编译器生成的构造函数,都可以认为是默认构造函数。

总之就是不需要传参即可调用的构造函数就是默认构造函数,默认构造函数有且只有一个。

代码如下(示例):

class Date
{
public:
	Date(int year)
	{}
	void print()
	{
		cout << _year << " " << _month << " " << _day << endl;
	}
private:
	int _year;
	int _month;
	int _day;
};

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

编译结果如下:

在这里插入图片描述

上面的代码中因为已经显式定义了构造函数,所以编译器不再生成,但是这个构造函数需要传一个参数才能调用,所以Date类没有合适的默认构造函数可用,编译不通过。


感谢阅读,如有错误请批评指正

评论 11
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

山舟

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值