首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >【C/C++】类和对象(上):(二)实例化——类实例化出对象,对象大小,this指针,对比C++/C两种语言实现Stack

【C/C++】类和对象(上):(二)实例化——类实例化出对象,对象大小,this指针,对比C++/C两种语言实现Stack

作者头像
艾莉丝努力练剑
发布2025-11-13 10:21:04
发布2025-11-13 10:21:04
1740
举报
文章被收录于专栏:C / C++C / C++

🔥个人主页:艾莉丝努力练剑 ❄专栏传送门:《C语言》《数据结构与算法》C语言刷题12天IO强训LeetCode代码强化刷题C/C++干货分享&学习过程记录 🍉学习方向:C/C++方向 ⭐️人生格言:为天地立心,为生民立命,为往圣继绝学,为万世开太平

前言:本专栏记录了博主C++从初阶到高阶完整的学习历程,会发布一些博主学习的感悟、碰到的问题、重要的知识点,和大家一起探索C++这门程序语言的奥秘。这个专栏将记录博主C++语法、高阶数据结构、STL的学习过程,正所谓“万丈高楼平地起”,我们话不多说,继续进行C++阶段的学习。上篇文章我们介绍了类的定义等要点,本文我们则来介绍对象。


C++的两个参考文档:

老朋友(非官方文档):cplusplus 官方文档(同步更新):cppreference

一、实例化(类实例化出对象)

(一)实例化的一些概念

1、类实例化出对象:

用类类型在物理内存空间中创建对象的过程就叫做类实例化出对象;

2、类和对象的关系:

类是对象进行一种抽象描述,像是个模型似的东西,来限定类有哪些成员变量。

注:这些成员变量仅仅只是声明,并没有实际分配空间,只有在类实例化出对象时才会分配空间。

3、类可以实例化出多个对象

一个类可以实例化出多个对象,实例化出多个对象占用实际的物理空间,来存储类成员变量。

可以这样理解:

类实例化出对象很像是我们在现实中利用建筑设计图来建造房子,这个类就类似于设计图,它规划了我们要造的这个房子会有多少个房间,这些房间有多大、都具备什么功能等等,但是这个阶段还只是纸上谈兵,还停留在纸面上,并没有把实体的房子建筑建造出来,人又不是纸片人,设计图又不能住人,也就没有实际的物理空间,只有当我们使用设计图把房子造好了才可以住人。 同理,类就像设计图一样,不能存储数据,只有当它实例化出对象分配了物理空间之后才能存储数据(房子实际存在了,才能住人)。 如下图所示——

(二)代码演示

代码语言:javascript
复制
#include<iostream>
using namespace std;

class Stack
{
public:
	//成员函数
	void Init(int n = 4)
	{
		//...
	}

private:
	//成员变量,声明
	int* array;
	size_t capacity;
	size_t top;
};

int main()
{
	//定义,类实例化对象
	Stack s1;
	s1.top = 0;
	s1.Init();

	Stack s2;
	s2.top = 1;
	s2.Init(100);

	cout << sizeof(s1) << endl;
	cout << sizeof(Stack) << endl;

	return 0;
}

二、对象大小

(一)理解对象大小的概念

我们先来分析一下类对象中有哪些成员——类可以实例化出的每个对象都有独立的数据存储空间,因此对象中肯定包含成员变量,那么成员变量是否包含呢?

首先,函数被编译后是一段指令,对象中没办法存储,这些指令会被存储在一个单独的区域(即代码段中),那么对象中非要存储的话,只能是成员函数的指针。我们再再分析一下,对象中是否有存储指针的必要呢?Date实例化d1和d2两个对象,d1和d2都有各自独立的成员变量,_year、_month、_day存储各自的数据,但是d1和d2的成员函数Init/Print指针却是一样的,这要是存储在对象中可就浪费了。如果用Date实例化100个对象,那么成员函数指针就重复存储100次,实在是太浪费了。这里博主需要再额外解释一下,其实函数指针是不需要存储的,函数指针是一个地址,调用函数被编译成汇编指令[call 地址],其实编译器在编译链接时,就要找到函数的地址,不是在运行时找,只有动态多态是在运行时找,就需要存储函数地址,多态我们之前也说过,之后会详细介绍。

成员函数的指针没存进对象,只考虑成员变量,不考虑成员函数;成员函数不在对象里面。

(二)代码实现

下面是计算对象大小的程序的演示:

代码语言:javascript
复制
#include<iostream>
using namespace std;

class A
{
public:
	void Print()
	{
		cout << _ch << endl;
	}
private:
	char _ch;
	int _i;
};

class B
{
public:
	void Print()
	{
		//...
	}
};

class C
{ };

int main()
{
	cout << sizeof(A) << endl;
	//开1个byte的空间是为了占位,占位不存储实际数据,表达对象存在过(占位标识对象存在)
	cout << sizeof(B) << endl;
	cout << sizeof(C) << endl;

	B b1;
	B b2;
	cout << &b1 << endl;
	cout << &b2 << endl;

	return 0;
}

(三)补充说明

1、对象存成员函数是个浪费

s1和s2成员函数调用的都是同一个(地址都一样),成员函数都一样,每个对象存成员函数是个浪费(编译链接时地址就有了——转换成call 地址),函数指针不会存。

2、为什么只开1个byte的空间

如下图所示——

运行结果如下——

只开1个byte的空间是为了占位,占位不存储实际数据,表达对象存在过(占位标识对象存在) 。


三、内存对齐

我们之前在C语言的结构体部分介绍过内存对齐的这一规则,链接如下——

【自定义类型:结构体】:类型声明、结构体变量的创建与初始化、内存对齐、传参、位段

这里博主截了一下这篇博客的目录和结构体对齐原则的概念——

上面我们分析了对象中只存储成员变量,C++规定类实例化的对象也要符合内存对齐的规则。

3.1 内存对齐规则

1、第一个成员在与结构体偏移量为0的地址处; 2、其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处; 3、注意:对齐数 = 编译器默认的⼀个对齐数与该成员大小的较小值; 4、VS中默认的对齐数为8; 5、结构体总大小为:最大对齐数(所有变量类型最大者与默认对齐参数取最小)的整数倍; 6、如果嵌套了结构体的情况,嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体的整体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍。

具体样例参考【对象大小】中的【为什么只开1个byte的空间】。

程序运行后,我们看到没有成员变量的B和C类对象的大小是1,为什么没有成员变量还要给1个字节呢?因为如果一个字节都不给,如何表示对象存在过呢!因此这里给1字节,纯粹是为了占位标识对象存在,就是标识一下。


四、this指针

this指针是隐含的(注意,不能显式地写出来)。

(一)理解this指针概念

(二)代码实现

代码语言:javascript
复制
class Date
{
public:
	//void Init(int* const year, int month, int day)
	void Init(int year, int month, int day)
	{
		/*cout << this << endl;*/

		//const保护this不能修改
		//this = nullptr;
		
		//this->_year = year;
		this->_year = year;
		this->_month = month;
		_day = day;
	}

	void Print()
	{
		cout << _year << "/" << _month << "/" << _day << endl;
	}
private:
	//这里只是声明,没有开空间
	int _year;
	int _month;
	int _day;
};

int main()
{
	Date d1;
	Date d2;
	//d1.Init(&d1, 2025, 7, 31);
	d1.Init(2025, 7, 31);

	//d2.Init(&d2, 2025, 7, 31);
	d2.Init(2025, 7, 31);

	d1.Print();
	d2.Print();

	return 0;
}

d1和d2成员函数地址都一样,那怎么区分d1和d2?C++看上去是不用传地址的——区分访问的是谁,其实地址还是要传的,只不过这个工作不用我们做,编译器会做的。

不要和前面const相关的权限放大缩小混到一起去。

(三) 题目测验

我们通过三道选择题来测试一下前面的知识掌握得如何——

大家做对了吗?

第二题解析(结合汇编):


五、对比C++/C两种语言实现Stack

(一)封装的概念

面向对象的三大特性:封装、继承、多态。

三大特性之中,我们现在只是了解了一下封装,剩下两个后面再介绍。

现在的C++虽然也比C语言方便了一些,但也没方便多少,还没有什么实质性的变化,等我们继续深入地学习下去,到STL(C++的标准库)学了之后,我们会明显感觉到C++更方便。

(二)对比C/C++实现Stack

这两个代码的实现都比较长。

接下来,我们就通过对比下面两份代码,感受C++实现Stack形态上发生的变化——

1、C实现Stack代码
代码语言:javascript
复制
#define  _CRT_SECURE_NO_WARNINGS  1

#include<stdio.h>
#include<stdlib.h>
#include<stdbool.h>
#include<assert.h>
typedef int STDataType;
typedef struct Stack
{
	STDataType* a;
	int top;
	int capacity;
}ST;
void STInit(ST* ps)
{
	assert(ps);
	ps->a = NULL;
	ps->top = 0;
	ps->capacity = 0;
}
void STDestroy(ST* ps)
{
	assert(ps);
	free(ps->a);
	ps->a = NULL;
	ps->top = ps->capacity = 0;
}
void STPush(ST* ps, STDataType x)
{
	assert(ps);
	// 满了, 扩容
	if (ps->top == ps->capacity)
	{
		int newcapacity = ps->capacity == 0 ? 4 : ps->capacity * 2;
		STDataType* tmp = (STDataType*)realloc(ps->a, newcapacity *
			sizeof(STDataType));
		if (tmp == NULL)
		{
			perror("realloc fail");
			return;
		}
		ps->a = tmp;
		ps->capacity = newcapacity;
	}
	ps->a[ps->top] = x;
	ps->top++;
}

bool STEmpty(ST* ps)
{
	assert(ps);
	return ps->top == 0;
}

void STPop(ST* ps)
{
	assert(ps);
	assert(!STEmpty(ps));
	ps->top--;
}

STDataType STTop(ST* ps)
{
	assert(ps);
	assert(!STEmpty(ps));
	return ps->a[ps->top - 1];
}

int STSize(ST* ps)
{
	assert(ps);
	return ps->top;
}

int main()
{
	ST s;
	STInit(&s);
	STPush(&s, 1);
	STPush(&s, 2);
	STPush(&s, 3);
	STPush(&s, 4);
	while (!STEmpty(&s))
	{
		printf("%d\n", STTop(&s));
		STPop(&s);
	}
	STDestroy(&s);
	return 0;
}

运行一下,结果如下所示——

2、C++实现Stack代码
代码语言:javascript
复制
#define  _CRT_SECURE_NO_WARNINGS  1

#include<iostream>
#include<assert.h>
using namespace std;
typedef int STDataType;
class Stack
{
public:
	// 成员函数 
	void Init(int n = 4)
	{
		_a = (STDataType*)malloc(sizeof(STDataType) * n);
		if (nullptr == _a)
		{
			perror("malloc申请空间失败");
			return;
		}
		_capacity = n;
		_top = 0;
	}

	void Push(STDataType x)
	{
		if (_top == _capacity)
		{
			int newcapacity = _capacity * 2;
			STDataType* tmp = (STDataType*)realloc(_a, newcapacity *
				sizeof(STDataType));
			if (tmp == NULL)
			{
				perror("realloc fail");
				return;
			}
			_a = tmp;
			_capacity = newcapacity;
		}
		_a[_top++] = x;
	}
	void Pop()
	{
		assert(_top > 0);
		--_top;
	}
	bool Empty()
	{
		return _top == 0;
	}
	int Top()
	{
		assert(_top > 0);
		return _a[_top - 1];
	}
	void Destroy()
	{
		free(_a);
		_a = nullptr;
		_top = _capacity = 0;
	}
private:
	// 成员变量 
	STDataType* _a;
	size_t _capacity;
	size_t _top;
};
int main()
{
	Stack s;
	s.Init();
	s.Push(1);
	s.Push(2);
	s.Push(3);
	s.Push(4);
	while (!s.Empty())
	{
		printf("%d\n", s.Top());
		s.Pop();
	}
	s.Destroy();
	return 0;
}

运行一下,结果如下——

3、总结

通过上面的C/C++这两份代码之间的对比,我们不难看出:C++实现Stack在形态上还是发生了挺多的变化,底层和逻辑上没有什么变化。


结尾

本文的完整代码

Test.c:
代码语言:javascript
复制
#define  _CRT_SECURE_NO_WARNINGS  1

#include<iostream>
using namespace std;

class Stack
{
public:
	//成员函数
	void Init(int n = 4)
	{
		//...
	}

//private:
	//成员变量,声明
	int* array;
	size_t capacity;
	size_t top;
};

int main()
{
	//定义,类实例化对象
	Stack s1;
	s1.top = 0;
	s1.Init();

	Stack s2;
	s2.top = 1;
	s2.Init(100);

	cout << sizeof(s1) << endl;
	cout << sizeof(Stack) << endl;

	return 0;
}

class A
{
public:
	void Print()
	{
		cout << _ch << endl;
	}
private:
	char _ch;
	int _i;
};

class B
{
public:
	void Print()
	{
		//...
	}
};

class C
{ };

int main()
{
	cout << sizeof(A) << endl;
	//开1个byte的空间是为了占位,占位不存储实际数据,表达对象存在过(占位标识对象存在)
	cout << sizeof(B) << endl;
	cout << sizeof(C) << endl;

	B b1;
	B b2;
	cout << &b1 << endl;
	cout << &b2 << endl;

	return 0;
}

class Date
{
public:
	//void Init(int* const year, int month, int day)
	void Init(int year, int month, int day)
	{
		/*cout << this << endl;*/

		//const保护this不能修改
		//this = nullptr;
		
		//this->_year = year;
		this->_year = year;
		this->_month = month;
		_day = day;
	}

	void Print()
	{
		cout << _year << "/" << _month << "/" << _day << endl;
	}
private:
	//这里只是声明,没有开空间
	int _year;
	int _month;
	int _day;
};

int main()
{
	Date d1;
	Date d2;
	//d1.Init(&d1, 2025, 7, 31);
	d1.Init(2025, 7, 31);

	//d2.Init(&d2, 2025, 7, 31);
	d2.Init(2025, 7, 31);

	d1.Print();
	d2.Print();

	return 0;
}

class A
{
public:
	void Print()
	{
		cout << this << endl;
		cout << "A::Print()" << endl;
		cout << _a << endl;
	}
private:
	int _a;
};

int main()
{
	A* p = nullptr;
	//p->Print(); // call 函数地址

	(*p).Print();

	// 00C3210C  mov       ecx,dword ptr[p]
	// 00C3210F  call      A::Print(0C31447h)

	return 0;
}
往期回顾:

【C/C++】类和对象(上):(一)类和结构体,命名规范——两大规范,新的作用域——类域

【C/C++】初识C++(三):C++入门内容收尾——const引用,指针和引用关系梳理,inline(内联函数),nullptr替代NULL

【C/C++】初识C++(二):深入详解缺省参数(默认参数)函数重载、引用(重头戏)

【C/C++】初识C++(一):C++历史的简单回顾+命名空间、流插入、命名空间的指定访问、展开问题等概念整理

结语:本文内容到这里就全部结束了, 本文我们在上一篇文章的基础上,继续学习了实例化——类实例化出对象,对象大小,this指针,对比C++/C两种语言实现Stack等知识点,从现在一直到学习到模版初阶学完之后,都是些晦涩的概念,还用不起来,到后面我们就能像之前那样,结合起来介绍。

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2025-08-29,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 一、实例化(类实例化出对象)
    • (一)实例化的一些概念
      • 1、类实例化出对象:
      • 2、类和对象的关系:
      • 3、类可以实例化出多个对象
    • (二)代码演示
  • 二、对象大小
    • (一)理解对象大小的概念
    • (二)代码实现
    • (三)补充说明
      • 1、对象存成员函数是个浪费
      • 2、为什么只开1个byte的空间
  • 三、内存对齐
    • 3.1 内存对齐规则
  • 四、this指针
    • (一)理解this指针概念
    • (二)代码实现
    • (三) 题目测验
  • 五、对比C++/C两种语言实现Stack
    • (一)封装的概念
    • (二)对比C/C++实现Stack
      • 1、C实现Stack代码
      • 2、C++实现Stack代码
      • 3、总结
  • 结尾
    • 本文的完整代码
      • Test.c:
      • 往期回顾:
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档