对象内存分配方案
我们在前面C/C++区别最后提出,如果我们创建三个对象分别为c1,c2,c3
当我们对c1、c2、c3如果都进行完全独立的安排内存方案,如下:
这样分配全套的空间会使得当对象数量较多,内存会被消耗殆尽
所以我们使用的方案为,各对象的代码区公用:
此处c1,c2,c3我们只给它分配数据区,代码区被所有对象共享,继而出现一个重要的问题:既然对象只有属性而没有方法,当我们c1去调用方法时,方法如何去辨别处理的是c1对象的成员,所以我们引入了this指针的概念
编译器对类型的编译
编译器针对程序员自己设计的类型分三次编译
- 第一:识别和记录类体中属性的名称,类型和访问限定,与属性在类体中的位置无关(如class CGoods 中的 Name,Amount,Price,Total_value)
- 第二:识别和记录类体中函数原型(返回类型+函数名+参数列表),形参的默认值,访问限定,不识别函数体
- 第三:改写在类定义中函数的参数列表和函数体,改写对象调用成员函数的形式
识别数据成员
对类的数据(属性)成员记录在案
private:
char Name[21];
int Amount;
float Price;
float Total_value;
识别函数声明
对类中函数的声明进行记录在案,仅识别函数声明,不识别函数体,对函数原型的识别
public:
void RegisterGoods(const char* name, int amount, float price); //输入数据
void CountTotal(void); //计算商品总价值
void GetName(char[]); //读取商品名
int GetAmount(void); //读取商品数量
float GetPrice(void); //读取商品单价
float GetTotal_value(void); //读取商品总价值
函数改写
void CGoods::RegisterGoods(CGoods * const this,const char* name, int amount, float price);
//void RegisterGoods(const char* name, int amount, float price);
我们在这里添加了 CGoods * const this
指针,在对函数体编译时,如果函数体中的标识符与属性名称一样,就会在前面加一个this指针
void CGoods::RegisterGoods(const char *name, int amount, float price)
{
strcpy_s(Name,21, name);
Amount = amount;
Price = price;
}
修改后
void CGoods::RegisterGoods(CGoods * const this, const char *name, int amount, float price)
{
strcpy_s(this->Name,21, name);
this->Amount = amount;
this->Price = price;
}
- 每一个类的成员函数都要加上this指针
- 在识别函数体时候遇到与类属性名称一样的标识符时在前面加上this指针
且当我们在定义对象时,调用成员函数的时候会进行改写,会将我们当前对象的地址给予this指针,继而当我们操作函数是就会对相应的对象属性进行操作
CGoods c1, c2, c3;
c1.RegisterGoods("c++", 12, 98.99);
//c1.RegisterGoods(&c1,"c++", 12, 98.99);
c1.CountTotal();
//c1.CountTotal(&c1);
c2.RegisterGoods("java", 23, 12.23);
//c2.RegisterGoods(&c2,"java", 23, 12.23);
c2.CountTotal();
//c2.CountTotal(&c2);
c3.RegisterGoods("tea", 23, 34);
c3.CountTotal();
当我们调用c1的成员方法时,this指针的值就是c1的地址,而去调用c2方法时,this指针指向c2的地址;并且this指针并不在对象里,而在类的成员函数里
其他关于this指针的问题
-
在我们改写时,实际上是
CGoods * const this
,这样会约束this指针不能进行改变,使得this指针失去作用 -
this指针存在于成员方法,而不是存在于对象属性,只有调用成员函数才会有this指针
-
当我们在成员函数后面加上const,
float GetTotal_value(void) const
,const 在返回类型之前,代表放回的数值为常量,若是在方法之后代表我们的方法是一个常方法,也就是我们只能对对象的属性进行读取,而不能将对象的属性进行修改
float GetTotal_value(void) const
{
int x = this->Amount;
//this->Amount = 1000; 错误!!! 不能进行改写
}
这里也会进行一次改写
原本我们对float GetTotal_value(void)
的改写为float GetTotal_value(CGoods * const this)
而我们将const写在函数后面float GetTotal_value(void) const
,改写后的内容为float GetTotal_value(const CGoods * const this)
const修饰*this,也就是它的指向能力,this指针指向为常性,继而不能对类的属性进行修改
- 如果我们在定义对象时,我们对下面两种代码的成员方法重写如下:
class CGoods
{
private:
char Name[21];
int Amount;
float Price;
float Total_value;
public:
float GetPrice() //读取商品单价
{
return this->Price;
}
float GetTotal_value() const //读取商品总价值
{
return Total_value;
}
};
void main()
{
CGoods c1 = {"c++",12,23};
const CGoods c2 = {"java",12,23};
c1.GetPrice();
c2.GetPrice(); //错误!!!
c1.GetTotal_value();
c2.GetTotal_value();
}
我们来研究为什么c1.GetPrice();
是正确的,而c2.GetPrice();
是错误的;
这是因为我们使用const修饰过 c2 后,使得不允许对 c2 属性进行修改,继而当我们在调用GetPrice()
时候,使用普通指针指向c2会致使this指针能力扩展,所以是错误的,也就是说我们的常对象只能调用常方法,而普通对象既可以调用普通方法也可以调用常方法
- this指针的传递,通过thiscall的函数调用约定;this指针存放于ECX寄存器,参数从右到左压;当我们进行
c1.RegisterGoods("c++",12,23);
c1的地址通过ecx进行传递,到达函数内部时将ecx的值交给 this
假如我们通过添加_cdecl
关键字,使用c的调用约定
则会直接将地址入栈push传入给参数,与其他参数是一样的,而默认C++的调用约定thiscall并没有push这一过程