探索C++面向对象:从抽象到实体的元规则(上篇)

**前引:**在计算机科学的浩瀚星空中,面向对象编程(OOP) 无疑是照亮现代软件开发的核心范式。而 C++ 作为一门兼具高性能与抽象能力的系统级语言,其类与对象的语法设计更是开发者构建复杂系统的"元规则"。你是否曾困惑于 封装与暴露的边界?是否在继承与多态的迷宫中迷失方向?又是否理解一个简单的 new 操作符背后,内存如何为对象分配生命?本文将深入 C++ 类与对象的语法内核,从底层原理到实战设计,带你揭开面向对象编程的神秘面纱!

目录

认识:面向过程与面向对象

面向过程

面向对象

类的引入

为何访问类里面的成员函数不需要传址

类的定义

类的两种定义方式

声明+定义结合

[声明 定义分开](#声明 定义分开)

类定义的良好习惯

类的限定访问及封装

C++中的class与C中的struct区别

类的封装

类的作用域

限定范围别人如何访问

类的实例化

不同的实例化

类的对象模型

计算类对象的大小

结构体对齐规则


认识:面向过程与面向对象

面向过程

C语言是面向过程的,关注的是过程,分析出求解问题的步骤,通过函数调用逐步解决问题

例如:

面向对象

C++是基于面向对象的,关注的是对象,将一件事情拆分成不同的对象,靠对象之间的交互完成

类的引入

类在C++中其实是由结构体进化而来的!在C中结构体是用来放成员变量的,而在C++结构体进化为类之后,可以放函数了!这样对比C更加的快捷、方便,下面我们来体会一下!

cpp 复制代码
struct structspace
{
	//定义成员变量
 
	int size;
	int* newnode;
 
	//定义成员函数
 
	int Arr(int data = 10)
	{
		return data *= 2;
	}
};

现在我们来调用类里面的函数,看看效果

现在我们来用 类 简单实现一个栈看看,加深对成员函数的使用与理解

cpp 复制代码
struct structspace
{
	//定义成员变量
 
	int size;
	int* newnode;
 
	//定义成员函数
 
	void Preliminary()
	{
		//初始化
		newnode = (int*)malloc(sizeof(int) * MAX);
		if (newnode == NULL)
		{
			perror("空间开辟失败\n");
			return;
		}
 
		size = 0;
	}
 
	void Push(int data = 1)
	{
		assert(newnode);
		//存储进栈
		newnode[size++] = data;
	}
 
	void Read()
	{
		//打印栈顶元素
		assert(newnode);
		if (size == MAX)
		{
			perror("栈满\n");
			return;
		}
 
		std::cout << "栈顶元素: " << newnode[size - 1] << std::endl;
	}
 
	void Destory()
	{
		//销毁栈
		assert(newnode);
 
		size = 0;
 
		free(newnode);
		newnode = nullptr;
 
		std::cout << "销毁成功" << std::endl;
	}
};

我们来看看使用成员函数的效果!

在C++中,我们习惯将关键字 struct 替换为关键字 class ,其它属于扩展,我们下面细说!

为何访问类里面的成员函数不需要传址

大家看下面两幅图,看看用C语言写和用C++写一个栈的区别!

函数是通过传参去访问变量的,通过函数单独实现某个功能,这个函数是独立存在的,而函数的参数是进行拷贝还是传址是根据需求来看的,这里我们需要改变结构体的成员,需要传址访问

而类是直接访问成员的,固不用去在成员函数里面去设置传址操作!

类的定义

类的组成:

(1)在C++中,class 为定义类的关键字,后面接类的名字,形成类型,同样可以使用 typedef

(2){ } 中的成员变量、成员函数依旧为主体

(3){ }后面依旧需要一个分号以示结尾

(4)类中的变量成为类的属性 或者 成员变量;类中的函数称为类的方法 或者 成员函数

类的两种定义方式

一般情况下我们是尽量选择第二种的,这种其实就是把之前C中的声明写入类里面,函数的定义再加一个类名即可,并不难!

声明+定义结合

顾名思义就是将成员函数的声明与定义全部写在一起,例如:

cpp 复制代码
struct structspace
{
	//定义成员变量
 
	int size;
	int* newnode;
 
	//定义成员函数
 
	int Arr(int data = 10)
	{
		return data *= 2;
	}
};

如果将声明与定义写在一起,编译器可能将其当做内联函数处理!

因此我们可以将这种情况用内联函数的要求来进行规定:对于递归、太长的函数我们不要这么做!

声明 定义分开

分开倒是很好理解,就跟之前C中写函数一样,那么如何让它成为成员函数呢?

解决方案: 分开定义的成员函数加上类名,例如:

cpp 复制代码
class Structspace
{
	//成员变量
	int data;
	int* newnode;
 
	//成员函数
	void Arr(int a = 5);
};

下面我们在另一个文件中实现定义:

cpp 复制代码
void Structspace::Arr(int a)
{
	std::cout << a << std::endl;
}

**注意:**函数缺省声明与定义只能存在一个,否则会进行报错

类定义的良好习惯

相信大家在上面已经有一种感觉了,就是:如果两个变量一模一样,看起来没那么的流程,例如:

cpp 复制代码
class Structspace
{
	//成员变量
	int data;
	int* newnode;
 
	//成员函数
	void Arr(int data = 5)
	{
		data = data;
	}
};

因此,我们推荐一个好的习惯:成员变量 和 函数形参 作一个区分,也是为了以后快速适应工作!下面我们看看改进版是如何看起来赏心悦目的!

cpp 复制代码
class Structspace
{
	//成员变量
	int _data;
	int* newnode;
 
	//成员函数
	void Arr(int data = 5)
	{
		_data = data;
	}
};

类的限定访问及封装

如何理解呢?

例如一部电脑,它给你的是USB接口等等,你只能通过固定的位置去给它充电、关机等一系列操作,而不是将里面的构造直接展现给你操作,那样估计买完一个星期内就出问题了.......

这里的类也是一样

以后如果进行工程合并,别人直接访问你自己定义的类里面的全部成员吗?那样安全性不高

所以我们针对这个情况,C++对类的使用通过以下三个限定符对定义的类来进行保护:

(1)public修饰的成员在类外可以直接被访问

(2)protected和private 修饰的成员在类外不能直接被访问(此处protected和private是类似的)

(3) 访问权限作用域从该访问限定符出现的位置开始直到下一个访问限定符出现时为止

(4)如果后面没有访问限定符,作用域就到 } 即类结束

(5)class的默认访问权限为private,struct 为 public(因为struct要兼容C)

C++中的class与C中的struct区别

C++需要兼容C语言,所以C++中struct可以当成结构体使用。另外C++中struct还可以用来定类。和class定义类是一样的,

区别是struct定义的类默认访问权限是public,class定义的类 默认访问权限是private

**注意:**class 和 struct 没有本质区别,唯一区别在于:class默认为私有,struct默认为公有

类的封装

在C++语言中实现封装,可以通过类将数据以及操作数据的方法进行有机结合,通过访问权限来 隐藏对象内部实现细节,控制哪些方法可以在类外部直接被使用

下面我们来用访问限定符封装类

类的作用域

类定义了一个新的作用域,类的所有成员都在类的作用域中,我们通常是二者结合使用!

(1) 在类体外定义成员时,需要使用**::**作用域操作符指明成员属于哪个类域,这里和上面的跨文 件分开成员函数的声明和定义同理

cpp 复制代码
class Structspace
{
 
 
	//成员变量
	int _data;
	int* newnode;
 
 
	//成员函数
	void Arr(int data = 5);
};
 
//在类外定义函数(指明类区域)
void Structspace::Arr(int data)
{
	_data = data;
}

(2)使用限定符划分区域

cpp 复制代码
class Structspace
{
public:
 
	//成员变量
	int _data;
	int* newnode;
 
private:
	//成员函数
	void Arr(int data = 5);
};
限定范围别人如何访问

我们使用prevate修饰之后,虽然设置了区域保护,但是并不是完全只能自由访问,我们可以通过:(这里先只了解,后面会进行学习哦!)

(1)设置友元

(2)写一个函数,把成员变量引用返回,在类外进行访问

类的实例化

例如:

类的设置仿佛一个建筑图纸,实例化仿佛就是将图纸变成实实在在的房子

首先类的设置是没有去使用空间的,只有实例化之后才会具体开辟空间且占用空间

那么什么是实例化?我们直接上例子,这样更好理解:

创建变量时记得不要加 前面的前缀:class

cpp 复制代码
//类的实例化
Structspace St1;
不同的实例化

不同的实例化仿佛是根据图纸去创造出不同的房子,比如:

cpp 复制代码
	//类的实例化
	Structspace St1;
 
	//不同的实例化
	Structspace St2;

类的对象模型

计算类对象的大小

我们通过实例来进行探索(下面是类对象的三种形式):

(1)只有成员变量

cpp 复制代码
class Structspace1
{
	//成员变量
	int _data;
	int* newnode;
};

(2)只有成员函数

cpp 复制代码
class Structspace2
{
	//成员函数
	void Arr(int data = 5);
};

(3)既有成员变量也有成员函数

cpp 复制代码
class Structspace3
{
	//成员变量
	int _data;
	int* newnode;
 
	//成员函数
	void Brr(int data = 5);
};

下面我们来看看三个类对象大小的计算结果对比:

结论:

一个类的大小,实际就是该类中"成员变量"之和,当然要注意内存对齐

注意空类的大小,空类比较特殊,编译器给了空类一个字节来唯一标识这个类的对象

那么加上限定符之后会变化吗?

加上限定符之后也是同理,这里我们方便测试,只有成员变量:

cpp 复制代码
class Structspace3
{
	//成员变量
	int _data;
	int* newnode;
 
	int _pc;
};
 
class Structspace4
{
 
public:
	//成员变量
	int _data;
	int* newnode;
 
private:
	int _pc;
};
结构体对齐规则

(1) 第一个成员在与结构体偏移量为0的地址处

(2) 其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处。 注意:对齐数 = 编译器默认的一个对齐数 与 该成员大小的较小值。 VS中默认的对齐数为8

(3)结构体总大小为:最大对齐数(所有变量类型最大者与默认对齐参数取最小)的整数倍

(4) 如果嵌套了结构体的情况,嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体的整 体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍

面试题:

(1)结构体怎么对齐? 为什么要进行内存对齐?

(2)如何让结构体按照指定的对齐参数进行对齐?能否按照3、4、5即任意字节对齐?

(3)什么是大小端?如何测试某台机器是大端还是小端,有没有遇到过要考虑大小端的场景

【雾非雾】期待与你的下次相遇!预知后续如何精彩,请先关注+收藏!

相关推荐
再看扣你眼5 分钟前
系统安全及应用学习笔记
笔记·学习·系统安全
xuanshang_yutou6 分钟前
调研函模板可参考,以无人机职业技能调研为例
笔记
半青年9 分钟前
Qt读取Excel文件的技术实现与最佳实践
c语言·c++·python·qt·c#·excel
再看扣你眼10 分钟前
系统安全及应用深度笔记
linux·运维·笔记·安全·系统安全
AA-代码批发V哥13 分钟前
Java-List集合类全面解析
java·开发语言·list
John_ToDebug18 分钟前
Chromium 回调设计实战:BindOnce 与 BindRepeating 的最佳实践
c++·chrome·性能优化
羚羊角uou18 分钟前
【C++】map和multimap的常用接口详解
开发语言·c++
Q_Q196328847524 分钟前
python动漫论坛管理系统
开发语言·spring boot·python·django·flask·node.js·php
摄殓永恒25 分钟前
出现的字母个数
数据结构·c++·算法
举一个梨子zz26 分钟前
Java—— IO流 第一期
java·开发语言