C++篇:类和对象(上)

目录

1.面向过程和面向对象初步认识

2.类的引入

3.类的定义

[3.1 声明和定义分离](#3.1 声明和定义分离)

[3.2 声明和定义放在一块](#3.2 声明和定义放在一块)

4.类的访问限定符

4.1封装

5.类的实例化

6.类对象模型大小

[6.1 类大小计算](#6.1 类大小计算)

[6.2 结构体内存对齐规则](#6.2 结构体内存对齐规则)

7.this指针

[7.1 this指针的引入](#7.1 this指针的引入)

[7.2 this的注意事项](#7.2 this的注意事项)


这次算是真正踏入 C++ 的门槛了,之前都是大部分可以和 C 兼容,而类和对象是 C++ 的特性,需要认真学习,这一 part 也是比较难的。这次要学习的内容是把面向对象和面向过程有详细的认识,然后去了解什么是类,类的定义,如何去比喻一个类,类的访问限定符等等。什么是封装,什么是类的实例化,以及类的实例化怎么用等等,这些都是去学习讨论的,一点点来,C++ 就是如此,前期学习的会比较困难,稍微后面就会相对轻松一点。那么话不多说,立马开始吧。

**1.**面向过程和面向对象初步认识

明确一点,C 是面向过程的,它更在意的是过程 ,C++ 是面向对象,关注的是对象。举个例子,我们平常生活中肯定会有点外卖吧,从过程上来看是这样,app 上架商品,客户打开 app 在上架下单,然后系统派单,骑手接单,送餐,客户签收。这些过程有哪些呢,上架,下单,派单,接单,送餐,签收等等过程就是,而 C 语言就是面向过程的,注重步骤。

而 C++,是重点面向对象,即谁下单,谁送货,谁出货,谁搭建沟通平台。那就是商户,客户,骑手,企业(app)。将一个过程分成不同的对象,靠对象之间的交互来完成,人们不需要知道餐从哪里来,怎么炒的菜,系统派单逻辑,花了多久等等过程,这就是面向对象。类就是现实世界映射到虚拟计算机系统里。

2.类的引入

C++ 里类除了放着变量还可以放置函数,C 语言中的结构体中只能存放变量,而在 C++ 中不仅可以存放变量,还能存放函数体。比如 C 语言中的栈,结构体中只放变量;而在 C++ 中,不但能放变量,还能放函数。如下就是 C++ 里的类,会晓得原来 struct 里面也可以定义函数,而不是单单定义变量那么单一。

cpp 复制代码
struct stack
{
	void init(size_t capacity)
	{
		_a = (int*)malloc(sizeof(int) * capacity);
		if (_a == nullptr)
		{
			perror("defeat");
			return;
		}
		_size = 0;
		_capacity = capacity;
	}
	int* _a;
	size_t _capacity;
	size_t _size;
	void push(int& x)
	{
		_a[_size] = x;
		_size++;
	}
	int top()
	{
		return _a[_size - 1];
	}
	void Destroy()
	{
		if (_a)
		{
			free(_a);
			_a = nullptr;
			_capacity = 0;
			_size = 0;
		}
	}
};
cpp 复制代码
int main()
{
	stack s1;
	s1.init(10);
	s1.push(2);
	s1.push(3);
	s1.push(8);
	s1.push(9);
	s1.top();
	s1.Destroy();
}

当然要注意,现在你是可以运行的,编译可以通过,但是如果你把 struct 变成 class,就会报错。原因在后面会提,其实在 C++ 中,更喜欢用 class 来代替,啥是 class,马上就说。

3.类的定义

这里正式介绍一下什么是类。类有个关键字,还要有类名,{}里的是主体,可以包含函数,变量定义,类定义后面}有个分号不能省。类体中内容称为类的成员: 变量 称为成员变量 ; 函数 称为成员函数

cpp 复制代码
class wj
{
public:
	void Print(const int&a,const int&b)
	{
		cout << a << b;
	}
	int x;
	int y;
	char c;
};

这就是一个很普通的类,首先类体中要用成员函数和成员变量组成,最后有一个分号。然后需要注意的是要类可以两种定义方式,一种是声明和定义分离 ,一种是直接在类里面定义

3.1 声明和定义分离

即类体中成员函数的声明放在h文件中,类的成员函数放在cpp文件中,注意这样子成员函数前需要类名:

声明和定义分离

3.2 声明和定义放在一块

声明和定义合并

这是两种不同的实现方式,不过一般的我会采取第二种更为方便管理。

4.类的访问限定符

类的访问限定符有public,protected,private,看中文意思能够理解是公开的、受保护的以及私有的。这也涉及到封装的概念,C++ 就是把类将对象的属性和实现方法结合在一起,用封装的方式决定哪些部分对用户开放,这个封装的方式就叫访问限定符。

  1. public 是公共区域,它所修饰的成员可以在类外直接被访问。
  2. 而 protected 和 private 所修饰的成员是不能被类外直接访问的。这两个类似。
  3. 访问限定符的作用域从该限定符出现开始到另一个限定符出现为止,如果没有就是以 } 结束为标志代表类的结束,比如 public:开始.....到 private:出现,那么中间的就是 public 的区域。
  4. class 默认为私有(private),struct 默认是公开(public),因为要兼容 C,所以也解释了上面为何用 struct 会编译通过,而 class 就报错。

这里提问一个经典问题:C++中class和struct的区别

答:C++ 需要兼容 C 语言,所以 C++ 中 struct 可以当成结构体使用,还可以用来定义类,这个和 class 定义类是一样的,区别是 struct 定义的类默认访问权限是 public,class 定义的类默认访问权限是 private。

4.1封装

封装是指通过类将数据以及操作数据的方法有机结合,把对象的具体属性和实现方式关起门来,只让你调用接口来实现用户的目的。

封装的好处是减少不必要的麻烦,为了更好的服务于开发商和用户。比如玩一个游戏,所有的游戏实现逻辑你是看不到的,玩家只需要鼠标、键盘、耳机、一个显示屏就可以了,而里面的代码运行、CPU 处理玩家的鼠标键盘、服务器传输数据、传回画面进行反馈,这些都不是我们需要考虑的。再举个例子,对于计算机使用者而言,不用关心内部核心部件,用户只需要知道怎么开机、怎么通过键盘和鼠标与计算机进行交互即可**。不然使用者出于好奇心动了一些部件导致损坏,那就会造成很多麻烦,专业的事由专业的人来做。**

4.类的作用域

类域是新的作用域,在类里面,类的所有函数成员、成员变量都在类的作用域里 ,在类外定义成员,需要说明这是我这个类的成员,就是要类名 + :: 来表示主权,:: 是域作用操作符。

cpp 复制代码
class Person
{
public:
	void PrintPersonInfo();
private:
	char _name[20];
	char _gender[3];
	int _age;
};
// 这里指定PrintPersonInfo是属于Person这个类域
void Person::PrintPersonInfo()
{
	cout << _name << " " << _gender << " " << _age << endl;
}

5.类的实例化

实例化是用类创建实体对象的过程,称之为类的实例化,很形象的比喻就是图纸到实物的过程,图纸就是类,而实物:枪械零件,手办,汽车的实体,这些具象化的东西是实例化,我们的一切操作都是在实物上完成。

cpp 复制代码
class wj
{
public:
	void Print(const int&a,const int&b)
	{
		cout << a << b;
	}
	int x;
	int y;
	char c;
};
int main()
{
	wj  Wj;
    wj.Print(1,4);//错误,没有空间,编译失败,语法错误
	Wj.Print(1,4);//√
}

主函数的第一个代码就是类的实例化,就是要给我一个对象,其次在这上面进行一系列操作。时刻记住:类是对对象进行描述的,是一个图纸,但是你说图纸能不能放东西,能不能去实际使用,那显然是不能。但是实例化之后是可以的,就是你把图纸里的东西造出来的,那么就可以使用了。可以进行装饰,拆卸,加装零件等等;


在类里面,类实例化出的对象可以访问,可以存放类成员变量,占用实际的物理空间。

做个比方。类实例化出对象就像现实中使用建筑设计图建造出房子,类就像是设计图,只设 计出需要什么东西,但是并没有实体的建筑存在。类可以有多个实例化

ps:注意一个点,我们已经知道不能直接用类去访问,因为那个是声明,无法存储数据。那么我们实例化出来的对象1,对象2,他们的地址一样吗,我们每个对象的成员函数地址一样吗,即我们栈1要压栈和栈2要压栈操作,是用的同一个函数代码进行压栈操作吗,是的!push是公共区域

cpp 复制代码
stack s1;
stack s2;
s1.init(10);
s2.init(10);
cout <<"s1对象地址:"<< & s1 << endl;
cout <<"s2对象地址:"<<&s2<< endl;
void(stack:: * ppush)( const int&) = &stack::push;
cout << "push函数成员地址" << (void*&)ppush << endl;

6.类对象模型大小

6.1 类大小计算

类里面有类的成员变量,还有类的成员函数,那么如何计算类的大小呢,要不要把类的成员函数算进去呢,下面这个类大小是多少?其实就是在想到底是都算进去还是只算成员变量,把成员函数放在公共代码段不去计算。

cpp 复制代码
class A1
{
public:
void PrintA()
{
   cout<<_a<<endl;
}
private:
char _a;
};
class A2
{
public:
void PrintA(){ }
}

class A3
{

}

结论是:一个类的大小是该类中的成员变量的总和。(只保存成员变量,成员函数)当然你得注意一个规则,这个在c语言中struct一样规则,当有多个不同类型的变量时,需要遵照内存对齐规则。注意空类的大小,空类比较特殊,编译器给了空类一个字节来唯一标识这个类的对象。为啥,这就是规则,也可以理解为人没来,给book一个位置。

6.2 结构体内存对齐规则

  1. 第一个成员在与结构体偏移量为0的地址处。
  2. 其他成员变量要对齐到对齐数的整数倍地址数
  3. 对齐数:编译器默认的对齐数与该成员的较小值(vs默认对齐数是8)
  4. 结构体总大小:最大对齐数(结构体内最大变量类型的大小和默认对齐数取最小值)的整数倍

既然复习了一下结构体的内存对齐规则,就来看看类A1为什么是8:


首先char类型的字节是1,这个是毫无疑问的,int类型的字节大小是4,这个也没有意外。先是char,再是int的话。根据结构体内存对齐规则,char就直接占一个字节,然后int要对齐到对齐数(8和4比,较小值是4,所以对齐数是4)的整数倍,4或者8,那就是从5开始,占据4个字节,前面为了对齐就要浪费3个字节。加起来就是8

那么有个核心问题:为什么要进行内存对齐?

答:首先内存对齐是指在内存中按照硬件访问数据的要求,把数据放在特定的边界上。

如果不进行内存对齐,读取数据会很麻烦,对齐也许只要读1次,读2次就会读取完的数据,没有内存对齐后会增加读取次数,浪费时间。这在计算机喜欢以空间换时间的性格是相违背的。而现在cpu的寄存器决定了内存内访问的基本粒度。例如,64位cpu寄存器通常为8字节,这表示cpu会以8字节为单位进行内存访问,这是基于对硬件效率的考虑,是为了降低访问延迟。提高cpu的访问效率,如果不对齐,就要进行额外的读取操作,浪费时间

7.this指针

7.1 this指针的引入

来看一个date类的

cpp 复制代码
class Date
{
public:
	void Init(int year, int month, int day)
	{
		_year = year;
		_month = month;
		_day = day;
	}
	void Print()
	{
		cout << _year << "-" << _month << "-" << _day << endl;
	}
private:
	int _year; 
	int _month; 
	int _day; 
};
int main()
{
	Date d1, d2;
	d1.Init(2022, 1, 11);
	d2.Init(2022, 1, 12);
	d1.Print();
	d2.Print();
	return 0;
}

这里产生一个疑问,为什么我们调用的init和print是同一个函数,那编译器是怎么区分他们的数据,打印结果完全不同。函数调用都是同一个代码片段,你怎么知道一个要打印d1的数据,d2的数据?答案是:它会传递一个我们用户看不到也不需要看到的隐藏指针:this指针。是编译器自己改的,而我们看不到,让指针去指向调用函数时的对象。

这是我们看到的:

这是编译器做的:

但是this指针并不能在形参和实参中显示传递:

7.2 this的注意事项

  1. this指针存在栈里,因为它是隐式的作为形参和实参传递到函数。所以this指针在栈里,不在堆区也不在常量区
  2. 最多只能在成员内部使用,不能在别的地方显示。
  3. (vs中)对象地址存放在ecx寄存器中,而寄存器存储thsi指针的值。
  4. 针对第1点和3点,就是this指针是形参,类的实例化对象调用成员函数时,对象地址会作为参数传给this的形参,即对象本身不存this指针。this就是对象的地址。

d1和d2的对象地址不一样

再有一个问题就是this指针可以为空吗?this可以为空,但是不能去访问this的内容,会出错。
预告:后面会单开一个部分,专门学习一个比较难的地方,是类的默认成员函数,其中最主要构造函数和析构函数,然后拷贝构造和赋值重载,这里是块硬骨头,学会了这里,其实又是一个小进步。

相关推荐
许彰午30 分钟前
07-SqlBuilder六法
java·开发语言·低代码·架构
BS30813_vx34 分钟前
【编号:01652】Spring Boot村务管理系统:财务公开、党建活动与计生服务一体化实战
开发语言·python
Jul1en_36 分钟前
【Java 脚手架】封装通用工具类-3
java·开发语言·redis·缓存·ai·bootstrap·rabbitmq
学习星球44 分钟前
2026年AI Agent全栈开发实战——从Prompt到Production
开发语言·人工智能·prompt
_Narcissus_1 小时前
分治&递归
数据结构·c++·笔记·算法·leetcode·递归·分治
Fluxart.ai1 小时前
商品多角度图怎么做?Flux Art 从白底图到规格图、包装图的 10 步教程
开发语言·前端·javascript
牛油果子哥q2 小时前
C++ Socket精讲:TCP/UDP套接字、客户端服务端实现、字节序、粘包拆包、简单HTTP服务、网络踩坑与面试全解
网络·c++·tcp/ip
程与留2 小时前
12_常用控件速查(下):QSpinBox、QSlider、QProgressBar、QCheckBox
c++·qt