首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >《一篇拿下!C++:类和对象(上)、封装、实例化和this指针详解》

《一篇拿下!C++:类和对象(上)、封装、实例化和this指针详解》

作者头像
用户11915063
发布2025-11-20 09:26:17
发布2025-11-20 09:26:17
30
举报

一、类的定义

类的定义格式
  • class为定义类的关键字,Stack为类的名字,{}中为类的主体,注意类定义结束时后面分号不能省略。类体中内容称为类的成员:类中的变量称为类的属性或成员变量; 类中的函数称为类的方法或者成员函数。
  • 为了区分成员变量,⼀般习惯上成员变量会加⼀个特殊标识,如成员变量前面或者后面加_ 或者m开头,注意C++中这个并不是强制的,只是⼀些惯例,具体看公司的要求。
  • C++中struct也可以定义类,C++兼容C中struct的用法,同时struct升级成了类,明显的变化是struct中可以定义函数,但⼀般情况下我们还是推荐用class定义类。
  • 定义在类里面的成员函数默认为inline(写上或者不写都是默认inline)

--上述要点会在接下来的例子中给大家详细介绍

访问限定符
  • C++⼀种实现封装的方式,用类将对象的属性与方法结合在一块,让对象更加完善,通过访问权限选择性的将其接口提供给外部的用户使用。
  • public修饰的成员在类外可以直接被访问;protected和private修饰的成员在类外不能直接被访问,protected和private是⼀样的,后续学到继承才能体现出他们的区别。
  • 访问权限作用域从该访问限定符出现的位置开始直到下一个访问限定符出现时为止,如果后面没有访问限定符,作用域就到 }即类结束。
  • class定义成员没有被访问限定符修饰时默认为private,struct默认为public。
  • 一般成员变量都会被限制为private/protected,需要给别人使用的成员函数会放为public。
举例说明(注释):
class类(注释):
代码语言:javascript
复制
// C++中数据和方法封装放到了一起,都在类里面
// 封装的本质体现了更严格的规范管理
//我们直接大概定义一个看一看
#include<iostream>
using namespace std;
 
class Stack 
{
	//访问限定符,class中没给的话里面默认是私有
public://公有,一般是把成员函数公有,在类外也可以直接访问
	//成员函数
	void Init(int capacity = 4)//可以不用像之前一样前面加个类似ST的区分
	{
		_arr = nullptr;//这里实际上是要malloc的
		_top = 0;
		_capacity = 0;
	}
	void Push(int x)
	{
 
	}
//public这个访问限定符到这里结束
private://私有,一般是把成员变量私有,在类外不能直接访问,更规范。
	//成员变量
	int* _arr;
	int _top;
	int _capacity;
 
};//分号不能掉
//private后面没有访问限定符了,到}这里结束
 
//以前C语言中只能向上找,类中向上向下都可以。
//所以我们前面先实现公有的成员函数,再定义的私有里面的成员变量也是可以的
 
int main()
{
	Stack s1;
	s1.Init();
 
	//像之前C语言中这样不规范的写发,我们通过C++的访问限定符规范了
	//s1.top++;//私有成员,类外无法直接访问
 
	return 0;
}
struct类(注释):
代码语言:javascript
复制
#include<iostream>
using namespace std;
 
//兼容C中struct的用法,升级成了类,就算不typedef也可以不用带前面的struct了
typedef struct A
{
	void func()
	{}
 
	int a1;
	int a2;
}AA;
 
// 升级成了类
struct B //class B
{
	void Init()
	{}
 
private:
	int b1;
	int b2;
};
 
int main()
{
	//struct A aa1;//不需要这样写了
	AA aa2;
 
	B bb1;
	bb1.Init();
}

一般情况下我们更喜欢用class,但是像链表定义节点这种还是比较喜欢用struct的(默认公有):

代码语言:javascript
复制
//一般情况下我们更喜欢用class,但是像链表定义节点这种还是比较喜欢用struct的(默认公有)
struct ListNode
{
	//访问限定符,struct中没给的话里面默认是公有
public://所以这里其实给不给这个公有都行
	int data;
	//struct ListNode* next;//不需要这样写了
	ListNode* next;
};
类域:
  • 类定义了⼀个新的作用域,类的所有成员都在类的作用域中,在类体外定义成员时,需要使用 :: 作用域操作符指明成员属于哪个类域。
  • 类域影响的是编译的查找规则,下面程序中Init如果不指定类域Stack,那么编译器就把Init当成全局函数,那么编译时,找不到array等成员的声明/定义在哪里,就会报错。指定类域Stack,就是知道Init是成员函数,当前域找不到的array等成员,就会到类域中去查找。

举例说明(注释):

Stack.h:

代码语言:javascript
复制
#pragma once
#include<iostream>
 
class Stack
{
public:
	void Init(int capacity = 4);
	void Push(int x);
private:
	int* _a;
	int _top;
	int _capacity;
};

Stack.cpp:

代码语言:javascript
复制
#define _CRT_SECURE_NO_WARNINGS 1
#include"Stack.h"
 
//一定要带Stack::去找
void Stack::Init(int capacity)
{
	_a = nullptr; // malloc
	_top = 0;
	_capacity = capacity;
}
 
void Stack::Push(int x)
{
	//...
}

test.cpp:

代码语言:javascript
复制
#include"Stack.h"
 
int main()
{
	Stack st;
	st.Init();
	return 0;
}

二、实例化

实例化概念:
  • 用类类型在物理内存中创建对象的过程,称为类实例化出对象
  • 类是对象进行一种抽象描述,是一个模型一样的东西,限定了类有哪些成员变量,这些成员变量只是声明,没有分配空间,用类实例化出对象时,才会分配空间
  • ⼀个类可以实例化出多个对象,实例化出的对象占用 实际的物理空间,存储类成员变量。打个比方:类实例化出对象就像现实中使用建筑设计图建造出房子,类就像是设计图,设计图规划了有多少个房间,房间大小功能等,但是并没有实体的建筑存在,也不能住人,用设计建出房子,房子才能住人。同样类就像设计图⼀样,不能存储数据,实例化出的对象分配物理内存存储数据
举例说明:(看注释)
代码语言:javascript
复制
#include<iostream>
using namespace std;
 
class Stack 
{
public:
	//成员函数
	void Init(int capacity = 4)
	{
		_arr = nullptr;
		_top = 0;
		_capacity = 0;
	}
	void Push(int x)
	{
 
	}
private:
	//成员变量,这里是声明,不开空间
	int* _arr;
	int _top;
	int _capacity;
};
 
int main()
{
	//类实例化出对象s1,s2,这时才分配了空间
	Stack s1;
	Stack s2;
 
	//成员函数不存在对象里面,而是在代码段里,通过调地址访问,这个在下面还会详细解释
	s1.Init();//call 地址
	s1.Init(100);
 
	//但是成员变量是存在对象里面的
 
	//就像s1._top和s2._top肯定不是同一个,但是s1.Init和s2.Init调的是同一个
 
	//可以传对象也可以传类型,遵守内存对齐规则
	cout << sizeof(s1) << '\n';
	cout << sizeof(Stack) << '\n';
 
	return 0;
}
对象大小:
分析类对象中的成员:

分析: 类实例化出的每个对象,都有独立的数据空间,所以对象中肯定包含成员变量,那么成员函数是否包含呢?首先函数被编译后是⼀段指令,对象中没办法存储,这些指令存储在⼀个单独的区域(代码段),那么对象中非要存储的话,只能是成员函数的指针。再分析⼀下,对象中是否有存储指针的必要呢,Date实例化d1和d2两个对象,d1和d2都有各自独立的成员变量_year/_month/_day存储各自的数据,但是d1和d2的成员函数Init/Print指针却是⼀样的,存储在对象中就浪费了。如果用Date实例化100个对象,那么成员函数指针就重复存储100次,太浪费了。这里需要再额外哆嗦⼀下,其实函数指针是不需要存储的,函数指针是⼀个地址,调用函数被编译成汇编指令[call 地址], 其实编译器在编译链接时,就要找到函数的地址,不是在运行时找,只有动态多态是在运行时找,就需要存储函数地址,这个我们以后会讲解

对齐规则:
举例说明:(看注释)
代码语言:javascript
复制
#include<iostream>
using namespace std;
// 计算⼀下A/B/C实例化的对象是多大?
class A
{
public:
	void Print()
	{
		cout << _ch << endl;
	}
private:
	char _ch;
	int _i;
};
class B
{
public:
	void Print()
	{
		//...
	}
};
class C
{
};
int main()
{
	A a;
	B b;
	C c;
	cout << sizeof(a) << endl;
	//a的这个不用多说,根据内存对齐规则就可以算出来
 
	//因为函数不存在对象里,所以b,c都是空类
	//空类的大小为1,开1byte是为例占位,不存在实际数据,只是为了表示对象存在过
	cout << sizeof(b) << endl;
	cout << sizeof(c) << endl;
 
	//大家可以想一下不开空间怎么表示对象存在过呢,不开地址怎么给呢?
	//所以肯定是会给的
 
	return 0;
}

三.this指针

关键点:
  • Date类中有 Init 与 Print 两个成员函数,函数体中没有关于不同对象的区分,那当d1调用Init和Print函数时,该函数是如何知道应该访问的是d1对象还是d2对象呢?那么这里就要看到C++给了一个隐含的this指针解决这里的问题
  • 编译器编译后,类的成员函数默认都会在形参第⼀个位置,增加⼀个当前类类型的指针,叫做this指针。比如Date类的Init的真实原型为 void Init(Date* const this, int year, int month, int day)
  • 类的成员函数中访问成员变量,本质都是通过this指针访问的,如Init函数中给_year赋值就是用的this->_year = year;
  • C++规定不能在实参和形参的位置显示的写this指针(编译时编译器会处理),但是可以在函数体内显示使用this指针
举例说明:(看注释)
代码语言:javascript
复制
#include<iostream>
using namespace std;
 
class Date 
{
public:
	//成员函数
	//所以这里应该是这样的
	//void Init(Data* const this,int year,int month,int day)--也不能这样写出来
	void Init(int year,int month,int day)
	{
		//但是这里的this是可以写的
		this->_year = year;
		_month = month;
		_day = day;
	}
 
	void Print()
	{
		cout << _year << "/" << _month << "/" << _day << '\n';
	}
private:
	//成员变量,这里只是声明,没有开空间
	int _year;
	int _month;
	int _day;
};
 
int main()
{
	Date d1;
	Date d2;
 
	//函数体中没有关于不同对象的区分,那当d1调用Init和Print函数时,
	//该函数是如何知道应该访问的是d1对象还是d2对象呢?
	//其实这里就涉及到了this指针,它在函数实参和形参的位置是不可以显式出现的
	//但是在函数体内可以出现
 
	//d1.Init(&d1,2025,7,31);--但是这里不能这样写出来
	d1.Init(2025, 7, 31);
	//d2.Init(&d2,2025,9,1);--也不能写出来
	d2.Init(2025, 7, 9);
 
	//和Init同理
	d1.Print();
	d2.Print();
 
	return 0;
}
课堂检测:

题目1:下面程序编译运行结果是(C),修改后的结果是(C)

A、编译报错 B、运行崩溃 C、正常运行

代码语言:javascript
复制
//题目1--C.正常运行
#include<iostream>
using namespace std;
class A
{
public:
	void Print()
	{
		cout << this << endl;
		cout << "A::Print()" << endl;
	}
private:
	int _a;
};
int main()
{
	A* p = nullptr;
	p->Print();//call 地址
	//因为这里的函数地址并不存在对象之中(而是直接通过地址调用的),this传过去也没影响
	//所以这里不存在空指针的解引用
 
	//如果改成这样呢
	(*p).Print();
	//也是一样的可以正常运行,这里转成反汇编看比较好,实际上是直接调用函数的
	//注意:不要只看语法的表象。
 
	return 0;
}

我们在代码的注释中对题目进行了解释和分析,这题大部分人看到空指针可能都会直接选择B也就是认为这里是空指针的解引用,但大家结合我们上面学习的知识就会发现,这里调用函数(成员函数是不存于对象之中的),函数体内也没有成员变量,所以没有问题。

都可以正常运行,退出码为0

题目2:下面程序编译运行结果是(B)

A、编译报错 B、运行崩溃 C、正常运行

代码语言:javascript
复制
//题目2--B.运行崩溃
#include<iostream>
using namespace std;
class A
{
public:
	void Print()
	{
		cout << this << endl;
		cout << "A::Print()" << endl;
		cout << _a << endl;//调用函数执行到这里时就有问题了
		//_a是成员变量,是存在对象里的,所以会出现问题。
	}
private:
	int _a;
};
int main()
{
	A* p = nullptr;
	p->Print();
	return 0;
}

这题会出现运行崩溃的问题,是因为在函数体内存中成员变量,这个就会受到空指针的影响了

运行错误,退出码不为0

题目3:this指针存在内存哪个区域的 (A)

A. 栈 B.堆 C.静态区 D.常量区 E.对象里面

this是存在栈里面的,就像形参一样,类似于局部变量。但是this也是很有可能存在寄存器里的 ,所以这里这题其实是不太严谨的


四. C语言和C++实现Stack的对比

面向对象三大特性:封装、继承、多态,下面的对比我们可以初步了解⼀下封装。

通过下面两份代码对比,我们发现C++实现Stack形态上还是发生了挺多的变化,但是在底层和逻辑上没啥变化。

  • C++中数据和函数都放到了类里面,通过访问限定符进行了限制,不能再随意通过对象直接修改数据,这是C++封装的⼀种体现,这个是最重要的变化。这里的封装的本质是⼀种更严格规范的管理,避免出现乱访问修改的问题。当然封装不仅仅是这样的,我们后面还需要不断的去学习。
  • C++中有⼀些相对方便的语法,比如Init给的缺省参数会方便很多,成员函数每次不需要传对象地址,因为this指针隐含的传递了,方便了很多。使用类型不再需要typedef,用类名就很方便
  • 在我们这个C++阶段实现的Stack看起来变了很多,但是实质上变化不大。等着我们后面看STL中的用适配器实现的Stack,才能真正感受C++的魅力。
C实现Stack的代码:
代码语言:javascript
复制
//C实现Stack
#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;
}
C++实现Stack的代码:
代码语言:javascript
复制
//C++实现Stack
#include<iostream>
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;
}

完整源代码:

【CPP知识学习仓库】 - Gitee.com


往期回顾:

《C++起源与核心:版本演进+命名空间法》

《C++基础:输入输出、缺省参数,函数重载与引用的巧妙》

《C++进阶:引用补充、内联函数与nullptr 核心用法》

总结:这篇博客到这里就结束了,接下来会进入类和对象(中)的学习,类和对象是很重要的,我们在LeetCode等刷题软件都可以看到类和对象的存在,所以大家一定要琢磨透,不然其它内容的学习都会变得比较吃力,如果文章对你有帮助的话,欢迎评论,点赞,收藏加关注,感谢大家的支持。

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

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

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

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

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 一、类的定义
    • 类的定义格式
    • 访问限定符
    • 举例说明(注释):
      • class类(注释):
      • struct类(注释):
    • 类域:
  • 二、实例化
    • 实例化概念:
    • 举例说明:(看注释)
    • 对象大小:
      • 分析类对象中的成员:
    • 对齐规则:
    • 举例说明:(看注释)
  • 三.this指针
    • 关键点:
    • 举例说明:(看注释)
    • 课堂检测:
  • 四. C语言和C++实现Stack的对比
    • C实现Stack的代码:
    • C++实现Stack的代码:
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档