c++对象模型和this指针

成员属性和成员函数分开储存

在c++中,成员属性和成员函数是分开储存的,只有成员属性属于对象。但在静态成员属性是不属于对象的,它和成员函数一样属于一个类而不属于某个对象。

静态成员属性在编译阶段分配内存,类内声明,类外初始化,并且所有对象共享同一个静态成员属性。

我们创建一个空类和一个测试用例

class person
{
};
void test01()
{
	person p;
	cout << "size of (p) = " << endl;
}

运行test01后我们可以知道空对象在内存中分配的空间为一字节,这是因为即使是空对象,不同的空对象也不能有相同的内存地址,所以编译器会分配一个字节的空间区分不同的空对象。

在类中添加一个非静态成员属性

class person
{
private:
	int age;
};

这时再次运行test01可以得到p对象在内存中占用的内存为4个字节,即一个整型的大小。因为不同的对象的属性也是不同的,所以类中的属性属于每一个对象;

在类中添加一个静态成员属性

class person
{
private:
	int age;
	static int ID;
};
int person::ID = 10;

这时运行test01发现p的大小未发生改变,这是因为静态成员属性并不是属于某个对象的,而是属于整个类,所以也不会改变对象的大小。

为类中添加一个行为

class person
{
private:
	int age;
	static int ID;
public:
	int showAge()
	{
		return age;
	}
};
int person::ID = 10;

同样的,运行test01后发现p的内存并未改变,因为行为同样是属于一个类的,而不是属于某个对象。编译器会通过某些方式来区分哪一个对象正在调用成员函数,这与this指针有关。

this指针

引言

通过前面的学习我们知道,每一个非静态成员函数只会生成一份函数实例,在调用时会自己区分哪一个对象在调用它。这一步骤的实现依靠的就是this指针。

this指针的概念

this指针是一种特殊的指针,this指针包含在每一个非静态的成员函数中,它指向的是正在调用函数的对象。
this不需要我们定义,直接使用即可。

this指针的作用

避免名称冲突

class number
{
public:
	number(int num)
	{
		num = num;
	}
	int num;
};
void test01
{
	number n1(10);
	cout << n1.num << endl;
}

如果我们运行test01会发现编译器并没有正确的输出10,而是输出了一个随机数。

这是因为编译器认为我们的有参构造函数中的形式参数num和等号左边的num即对象中的num是相同的,所以我们并没有为对象中的num正确的赋值。

使用this指针就可以解决这个问题;

class number
{
public:
	number(int num)
	{
		*this->num = num;
	}
	int num;
};

将代码做以上修改之后test01就可以正确的输出。

在类的非静态成员函数中返回对象本身

我们在number对象中加入add操作

class number
{
public:
	number(int num)
	{
		num = num;
	}
	void add(const number &p)
	{
		*this.num += p.num;
	}
	int num;
};
void test02()
{
	number n1(10);
	number n2(20);
	n2.add(n1);
	cout << n2.num << endl;
}

这时运行test02,可以正确的输出相加结果为30;

如果我们想通过链式操作进行连加将测试用例02改为:

void test02()
{
	number n1(10);
	number n2(20);
	n2.add(n1).add(n1).add(n1);
	cout << n2.num << endl;
}

我们发现编译器会报错,因为这时我们的add函数的返回类型为void,相当于除了第一次相加,后面执行的都是 “.add(n1).add(n1)”。这是错误的语句。

所以我们将add的返回类型改为number&,并利用this指针返回对象本身

class number
{
public:
	number(int num)
	{
		num = num;
	}
	number& add(const number &p)
	{
		*this.num += p.num;
		return *this;
	}
	int num;
};

这样链式操作的每一次调用都可以正确的执行"n1.add(n2)"

为什么要返回引用类型? 有以下两个原因:

如果不返回引用类型,通过前面的对拷贝构造函数的调用时机的学习我们知道,直接通过值返回时,函数会拷贝一个对象返回,这样会增大程序运行时的内存占用。

只有返回引用类型才可以进行链式操作,因为返回引用类型返回的是对象的本身,那么每一次链式操作都是对对象本身操作。如果返回的不是引用类型,那么链式操作第一次执行之后的操作都是对对象的一个拷贝的操作,所以这是链式操作就只有第一次是有效的,其他都是无效的。 所以为了可以进行有效的链式操作,我们应该返回引用类型。

利用空指针访问成员函数

c++中空指针也可以直接访问类的成员函数,但是否可以成功访问则与成员函数是否只用了this指针有关。以下面的代码为例:

class person
{
public:
	void showTest()
	{
		cout << "this is tset" << endl;
	}
	void showAge()
	{
		cout << "age:" << M_age << endl;
	}
	int M_age;
}

void test01()
{
	person *p = NULL;
	p->showTest;
	p->showAge;
}

如果运行test01会发现在在 “p->showAge” 处报错说this指针为空指针,而上一行调用的函数不会报错,这是因为showAge函数中为了正确的输出M_age会使用this指针,而showTest函数不需要输出任何成员变量,不用使用this指针,所以不会出现上述错误。

在实际应用中,为了避免这种错误可以将使用了this指针的函数做如下的修改,增强程序的健壮性

void showAge()
{
	if(this == NULL)
	{
		return;
	}
	cout << "name:" << M_age << endl;
}

const修饰:常函数和常对象

常函数:

被const修饰的成员函数叫常函数;

常函数不能修一般的改成员属性;

被mutable关键字修饰的成员属性可以被常函数修改;

常对象:

声明对象时用const修饰得到常对象;

常对象只能调用常函数;

常对象中只有被mutable修饰的属性才能被修改;

以以下代码为例:

class person
{
public:
	void setAge(int age) const
	{
		m_age = age;
	}
	mutable int m_age;
}

void test02()
{
	const person p;
	p.setAge(17);
}

首先我们要知道this指针的实质:this指针是一个常量指针,即person * const this;this指针的指向不能修改,但是this指针指向的对象的属性可以修改。
再次用const修饰this指针后:const person* const p,this指针指向的对象将会变成常对象。

上述代码中setAge函数的实质是:

this->m_age = age;

当函数被const修饰后,位于函数内部的this指针也会被const再次修饰,所以this指针指向的对象也会变成常对象,所以此时只有m_age属性被mutable修饰后程序才能正确运行。

为什么常对象只能调用常函数:
首先我们知道,常对象中只有被mutable修饰的属性才能被修改,如果常对象可以调用常函数以外的函数,那么没有被mutable修饰的属性也可以被修改,所以常对象只能调用常函数。

注意静态成员属性和常对象的区别:
静态成员属性的值是可以被静态成员函数修改的,但是常对象中只有被mutable修饰的属性才能被常函数修改
并且常对象中的属性都是属于自己的,而静态成员属性不属于某个对象,而是属于这个类

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值