【C++】类和对象-对象的存储方式和this指针

对象的存储方式

通过类创建了一个对象之后,我们想要知道这个对象在内存中是如何进行存储的,因为这会影响我们计算这个对象的大小。
因为在类中既有成员变量,又有成员函数,那么通过这个类创建的对象中又包含了些什么内容呢?

实际上,对象中只包含了类中的成员变量,因为成员变量的值是不同的,而成员函数在调用的时候使用的是相同的代码,则只将成员变量放入对象中,而成员函数放在公共的代码段就可以减小空间的浪费。

通过代码也可以看到

// 类中既有成员变量,又有成员函数
class A1 {
public:
	void f1(){}
private:
	int _a;
};
// 类中仅有成员函数
class A2 {
public:
	void f2() {}
};
// 类中什么都没有---空类
class A3
{};

在这里插入图片描述

结论: 一个类的大小,实际就是该类中”成员变量”之和,当然要注意内存对齐,而空类比较特殊,编译器给了空类一个字节来唯一标识这个类的对象。

为什么需要将空类的大小置为1呢?
例如:int a, b, c在这里面,a b c是不同的变量,并且它们在栈上开辟的空间地址也是不同的,各自的地址偏移量是int 4个字节。
那么如果定义一个空类

class A
{}

并且通过这个空类创建了三个对象A a1, a2, a3,而我们认为a1 a2 a3也是不同的对象,但是如果空类的大小为0,则各自地址的偏移量为0,三个对象的地址就相同了,相同地址实际上表示的是相同的对象,这就和我们的代码含义矛盾了,

归根结底是空类的大小为0导致无法区分不同的对象。
为了区分空类创建出来的对象,需要将空类的大小置为1。

this指针

当我们创建出一个对象之后,因为这个对象中是没有存储成员函数代码的,那么当我们调用某个方法时,程序是如何知道我们需要操作的是哪个对象?

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

this指针的特性

  1. this指针的类型:类类型* const,即成员函数中,不能给this指针赋值
  2. 只能在“成员函数”的内部使用
  3. this指针本质上是“成员函数”的形参,当对象调用成员函数时,将对象地址作为实参传递给this形参。所以对象中中存储this指针。
  4. this指针是“成员函数”第一个隐含的指针形参,一般情况由编译器通过ecx寄存器自动传递,不需要用户传递

在这里插入图片描述
这样一个类,其中有一个SetData方法,当某个对象需要调用这个方法的时候,对于用户看起来是这样的调用
在这里插入图片描述

但是实际上,编译器会在这个方法中添加一个this指针,作为这个方法的第一个形参,通过this指针指向调用这个方法的对象,去操作对应对象中的成员变量。

在这里插入图片描述

通过查看汇编代码,我们发现this指针是存储在ecx寄存器中的,而这个寄存器相当于当前程序的全局变量,这个this指针就是通过这个寄存器进行传递的。

c语言和c++实现的stack代码

1.c语言实现stack

typedef int DataType;
typedef struct Stack
{
	DataType* array;
	int capacity;
	int size;
}Stack;
void StackInit(Stack* ps)
{
	assert(ps);
	ps->array = (DataType*)malloc(sizeof(DataType) * 3);
 	if (NULL == ps->array)
 	{
 		assert(0);
 		return;
 	}
 	ps->capacity = 3;
 	ps->size = 0;
}
void StackDestroy(Stack* ps)
{
 	assert(ps);
 	if (ps->array)
 	{
 		free(ps->array);
 		ps->array = NULL;
 		ps->capacity = 0;
 		ps->size = 0;
 	}
}
void CheckCapacity(Stack* ps)
{
 	if (ps->size == ps->capacity)
 	{
 		int newcapacity = ps->capacity * 2;
 		DataType* temp = (DataType*)realloc(ps->array, 
		newcapacity*sizeof(DataType));
 	if (temp == NULL)
 	{
 		perror("realloc申请空间失败!!!");
 		return;
 	}
 	ps->array = temp;
 	ps->capacity = newcapacity;
 	}
}
void StackPush(Stack* ps, DataType data)
{
 	assert(ps);
 	CheckCapacity(ps);
 	ps->array[ps->size] = data;
 	ps->size++;
}
int StackEmpty(Stack* ps)
{
 	assert(ps);
 	return 0 == ps->size;
}
void StackPop(Stack* ps)
{
 	if (StackEmpty(ps))
 	return;
 	ps->size--;
}
DataType StackTop(Stack* ps)
{
 	assert(!StackEmpty(ps));
 	return ps->array[ps->size - 1];
}
int StackSize(Stack* ps)
{
 	assert(ps);
 	return ps->size;
}
int main()
{
 	Stack s;
 	StackInit(&s);
 	StackPush(&s, 1);
 	StackPush(&s, 2);
 	StackPush(&s, 3);
 	StackPush(&s, 4);
 	printf("%d\n", StackTop(&s));
 	printf("%d\n", StackSize(&s));
 	StackPop(&s);
 	StackPop(&s);
 	printf("%d\n", StackTop(&s));
 	printf("%d\n", StackSize(&s));
 	StackDestroy(&s);
 	return 0;
}

在使用c语言实现stack时,我们注意到,struct中只定义了变量,而使用时需要调用struct外部的函数,这些函数的第一个参数都是stcak*,由于传第一个参数为指针,而很有可能传的是空,因此需要进行判空,传参必须传stack结构体变量的地址。这样的方法涉及到大量的指针操作,稍不注意就会出错。

2.c++实现stack

typedef int DataType;
class Stack
{
public:
 	void Init()
 	{
 		_array = (DataType*)malloc(sizeof(DataType) * 3);
 		if (NULL == _array)
 	{
 	perror("malloc申请空间失败!!!");
 	return;
  	}
 _capacity = 3;
 _size = 0;
 }
 void Push(DataType data)
 {
 	CheckCapacity();
 	_array[_size] = data;
 	_size++;
 }
 void Pop()
 {
 	if (Empty())
 		return;
 	_size--;
 }
 DataType Top(){ return _array[_size - 1];}
 int Empty() { return 0 == _size;}
 int Size(){ return _size;}
 void Destroy()
 {
 	if (_array)
 	{
 		free(_array);
 		_array = NULL;
 		_capacity = 0;
 		_size = 0;
 	}
 }
private:
 void CheckCapacity()
 {
 	if (_size == _capacity)
 	{
 		int newcapacity = _capacity * 2;
 		DataType* temp = (DataType*)realloc(_array, newcapacity *
		sizeof(DataType));
 		if (temp == NULL)
 		{
 			perror("realloc申请空间失败!!!");
 			return;
 		}
 		_array = temp;
 		_capacity = newcapacity;
 	}
 }
private:
 	DataType* _array;
 	int _capacity;
 	int _size;
};
int main()
{
 	Stack s;
 	s.Init();
 	s.Push(1);
 	s.Push(2);
 	s.Push(3);
 	s.Push(4);
 
 	printf("%d\n", s.Top());
 	printf("%d\n", s.Size());
 	s.Pop();
 	s.Pop();
 	printf("%d\n", s.Top());
 	printf("%d\n", s.Size());
 	s.Destroy();
 	return 0;
}

C++中通过类可以将数据 以及 操作数据的方法进行完美结合,通过访问权限可以控制那些方法在类外可以被调用,即封装,在使用时就像使用自己的成员一样,更符合人类对一件事物的认知。而且每个方法不需要传递Stack*的参数了,编译器编译之后该参数会自动还原,即C++中 Stack * 参数是编译器维护的,C语言中需要用户自己维护。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值