类与对象(一)

embedded/2025/3/13 15:14:15/

目录

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

2. 类的引入

3. 类的定义

4. 类的访问限定符及封装

4.1 访问限定符

4.2 封装

5. 类的作用域

6. 类的实例化

7. 类对象模型

7.1 类对象的存储方式

7.2 结构体内存对齐规则

7.3 特殊情况:空类的大小

8. this 指针

8.1 this 指针的引出

8.2 this 指针的特性

9. C 语言和 C++ 实现 栈(Stack) 的对比

💬 :如果你在阅读过程中有任何疑问或想要进一步探讨的内容,欢迎在评论区畅所欲言!我们一起学习、共同成长~!

👍 :如果你觉得这篇文章还不错,不妨顺手点个赞、加入收藏,并分享给更多的朋友噢~!


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

  • 面向过程(C 语言):关注解决问题的过程,分析求解步骤,通过函数调用逐步解决问题。
  • 面向对象(C++):基于面向对象编程,关注对象,将事情拆分成不同对象,依靠对象间的交互完成任务。

2. 类的引入

  • C 语言结构体:只能定义变量。
  • C++ 结构体:不仅能定义变量,还能定义函数。

示例:

#include <iostream>  
#include <cstdlib>   // 用于使用 malloc、free 和 perror 函数
#include <cassert>   typedef int DataType;// 定义栈结构体
struct Stack 
{void Init(size_t capacity) {_array = (DataType*)malloc(sizeof(DataType) * capacity);if (nullptr == _array) {perror("malloc申请空间失败");return;}_capacity = capacity;_size = 0;}// 接受一个常量引用参数,避免不必要的拷贝void Push(const DataType& data) {if (_size == _capacity) {size_t newCapacity = _capacity * 2;DataType* newArray = (DataType*)realloc(_array, sizeof(DataType) * newCapacity);if (newArray == nullptr) {perror("realloc申请空间失败");return;}_array = newArray;_capacity = newCapacity;}// 将元素存入栈中_array[_size] = data;++_size;}DataType Top() {// 确保栈不为空assert(_size > 0);// 返回栈顶元素return _array[_size - 1];}void Destroy() {if (_array) {free(_array);_array = nullptr;_capacity = 0;_size = 0;}}// 栈数组指针,用于存储栈中的元素DataType* _array;// 栈最多能存储的元素数量size_t _capacity;// 栈当前实际存储的元素数量size_t _size;
};int main() 
{// 创建一个栈对象Stack s;// 初始化栈,指定初始容量为 10s.Init(10);s.Push(1);s.Push(2);s.Push(3);std::cout << s.Top() << std::endl;s.Destroy();return 0;
}

C++ 中,更常用 class 代替 struct 来定义类

思考:为什么是 ++_size; 而非 _size++; ?

性能方面:现代编译器中两者在性能上差异微乎其微,但理论上前置自增性能更优。后置自增需要额外内存空间保存原值,并且操作结束后还要进行一次赋值;前置自增直接对变量进行自增并返回结果,没有这些额外开销。

代码语义方面:前置自增更直观地表达“先进行自增,再更新栈元素数量”的操作顺序。


3. 类的定义

  • 定义格式class 是定义类的关键字,ClassName 是类名,类定义结束后分号不能省略。
class className 
{// 类体:由成员函数和成员变量组成};  // 一定不要忘记分号int main()
{classname 对象名;对象名.成员函数名();
}

  • 类的成员:类中的变量称为类的属性或成员变量,类中的函数称为类的方法或成员函数。
  • 两种定义方式
    • (1)声明和定义全放类体中:成员函数在类中定义,编译器可能将其当作内联函数处理。
    • (2)类声明放 .h 文件,成员函数定义放 .cpp 文件:成员函数名前需加类名 :: 。一般推荐这种方式。
  • 成员变量命名规则建议:为避免成员变量和函数形参混淆,建议给成员变量加前缀或后缀,如 _year 或 mYear

4. 类的访问限定符及封装

4.1 访问限定符

访问限定符类外访问性作用域默认访问权限
public(公有)可直接访问从出现位置开始,到下一个访问限定符出现为止;若无下一个访问限定符,则到类结束struct 定义的类默认访问权限是 public
protected(保护)不能直接访问从出现位置开始,到下一个访问限定符出现为止;若无下一个访问限定符,则到类结束
private(私有)不能直接访问从出现位置开始,到下一个访问限定符出现为止;若无下一个访问限定符,则到类结束class 定义的类默认访问权限是 private

注意:访问限定符主要是在编译阶段发挥作用,用于保证代码的安全性和规范性,而在内存中,对象的成员变量并没有访问限定符的区分。

【面试题】C++ 中 struct 和 class 的区别?

C++需要兼容C语言,所以C++ 中 struct 可当作结构体使用。C++ 中 struct 也可定义类,与 class 定义类类似,区别在于 struct 定义的类默认访问权限是 publicclass 定义的类默认访问权限是 private。在继承和模板参数列表位置也有区别。

4.2 封装

  • 面向对象三大特性:封装、继承、多态。类和对象阶段主要研究封装特性。
  • 封装定义:将数据和操作数据的方法有机结合,隐藏对象属性和实现细节,仅对外公开接口与对象交互。
  • C++ 实现封装:通过类将数据和操作方法结合,利用访问权限隐藏内部实现细节,控制类外可直接使用的方法。

5. 类的作用域

 类定义了新的作用域,类的所有成员都在类的作用域中。在类体外定义成员时,需用  ::  作用域操作符指明成员所属类域。

#include <iostream>
#include <string>
using namespace std;class Person 
{
public:// 声明成员函数void PrintPersonInfo();void SetName(const char* name);void SetGender(const char* gender);void SetAge(int age);
private:char _name[20];char _gender[3];int  _age;
};void Person::PrintPersonInfo() 
{cout << _name << " " << _gender << " " << _age << endl;
}void Person::SetName(const char* name) 
{// 将传入的姓名拷贝到成员变量_name中strncpy(_name, name, sizeof(_name) - 1);// 确保字符串以'\0'结尾_name[sizeof(_name) - 1] = '\0';
}void Person::SetGender(const char* gender) 
{strncpy(_gender, gender, sizeof(_gender) - 1);_gender[sizeof(_gender) - 1] = '\0';
}void Person::SetAge(int age) 
{_age = age;
}int main() 
{Person p;p.SetName("Alice");p.SetGender("女");p.SetAge(25);p.PrintPersonInfo();return 0;
}

6. 类的实例化

  • 定义:用类创建对象的过程称为类的实例化。
  • 特点
    • 类是对对象的描述,是模型,定义类时未分配实际内存空间。例如学生信息表可看成类,描述具体学生信息。
    • 一个类可实例化多个对象,实例化的对象占用实际物理空间,存储类成员变量。

7. 类对象模型

7.1 类对象的存储方式

在C++中,类对象的存储方式遵循以下规则:

类对象包含成员变量,但不包含成员函数。成员函数不属于某个具体的对象,而存放在公共的代码段,所有该类的对象共享同一份成员函数代码。

综上所述,类对象的大小本质上是其成员变量所占内存空间之和。但需要考虑内存对齐规则。

7.2 结构体内存对齐规则

  • 第一个成员的起始位置:第一个成员变量存放在与结构体偏移量为0的地址处。

  • 其他成员的对齐位置:其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处。对齐数等于编译器默认的对齐数和该成员大小的较小值。在Visual Studio中,默认的对齐数为8。

  • 结构体的总大小:结构体总大小必须是最大对齐数(所有成员变量类型的最大者与默认对齐参数取最小)的整数倍。

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

7.3 特殊情况:空类的大小

空类:没有任何成员变量和成员函数的类,或者只有成员函数而没有成员变量的类。

编译器会给空类分配1字节的空间,目的是为了让空类的对象能够拥有唯一的地址。


8. this 指针

8.1 this 指针的引出

C++ 中,当一个类有多个对象时,每个对象都有自己独立的成员变量。但是类的成员函数是所有对象共享的。

这就产生一个问题:当一个成员函数被调用时,如何知道是哪个对象在调用它呢?

#include <iostream>
using namespace std;class Date 
{
public:void Init(int year, int month, int day) {_year = year;_month = month;_day = day;}void Print() {std::cout << _year << "-" << _month << "-" << _day << std::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;
}

以上述代码为例,d1.Init(2022, 1, 11)d2.Init(2022, 1, 12)被调用时,Init函数将无法确定将这些值赋给d1的成员变量还是d2的成员变量。

为了解决以上问题,使用 this 指针。

C++中,每个非静态的成员函数都有一个隐藏的指针参数,这个指针就是 this 指针。它的主要作用是指向当前正在调用该成员函数的对象(不可为空)。

8.2 this 指针的特性

  • this指针的类型是 类类型 * const ,这意味着 this 指针是一个指向常量的指针,不能在成员函数中给this指针赋值。例如,对于一个Student类,this指针的类型就是 Student * const 

  • this 指针只能在类的成员函数内部使用,不能在类的外部单独使用。

  • this 指针是成员函数的第一个隐含的指针形参,不需在调用成员函数时显式传递。当对象调用成员函数时,编译器会自动将对象的地址作为实参传递给 this 指针。例如调用s1.display()时,编译器会自动将其转换为Student::display(&s1)。

  • this 指针不存储于对象本身中。在函数调用期间,this 指针的值保存在函数的栈帧中。函数执行完毕后,this 指针的值就会被销毁。


9. C 语言和 C++ 实现 栈(Stack) 的对比

#include <stdio.h>
#include <stdlib.h>
#include <assert.h>typedef int DataType;typedef struct Stack 
{DataType* array;  // 用于存储栈中元素的数组指针int capacity;    int size;      // 栈中当前元素的数量   
} Stack;// 初始化栈
void StackInit(Stack* ps) 
{assert(ps); // 为栈的数组分配初始容量为3的空间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;  // 如果栈中元素数量为0,返回1表示空,否则返回0
}// 出栈
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;
}

#include <iostream>
#include <cstdlib>
#include <cstring>
using namespace std;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);  cout << s.Top() << endl;  cout << s.Size() << endl;  s.Pop();  s.Pop();  cout << s.Top() << endl;  cout << s.Size() << endl;  s.Destroy(); return 0;
}
比较维度C 语言实现C++ 实现
数据与操作的组织方式结构体中只能定义存放数据的结构,数据和操作数据方式分离通过类将数据与操作数据方式完美结合
函数参数每个函数的第一个参数都是 Stack*不需显式传递 Stack * 参数,编译器自动维护
空指针检测函数中必须要对第一个参数(Stack*)检测是否为 NULL无需手动对类似指针参数进行空指针检测,编译器自动维护
调用方式调用时必须传递 Stack 结构体变量的地址使用时如同使用自身成员
访问控制通过访问权限(public、protected、private)控制哪些方法在类外可被调用,实现封装
实现复杂度涉及大量指针操作,容易出错,实现相对复杂代码结构更清晰,一定程度上降低了因指针操作不当导致的错误风险


http://www.ppmy.cn/embedded/172280.html

相关文章

2025软件供应链安全最佳实践︱新能源汽车领域SCA开源风险治理项目

软件定义汽车”时代 开源威胁不容小觑 当前我国新能源汽车产业蓬勃发展&#xff0c;智能网联趋势持续深化。汽车技术与工程核心逐渐从传统硬件层面转移到软件层面&#xff0c;踏上软件定义汽车(SDV)的变革之路。 软件定义汽车意味着日益膨胀的代码量。据亿欧智库预计&#xff0…

Deepin通过二进制方式升级部署高版本 Docker

一、背景&#xff1a; 在Deepin系统中通过二进制方式升级部署高版本 Docker&#xff0c;下面将详细介绍二进制方式升级部署高版本 Docker 的具体步骤。 二、操作步骤 1.根据需求下载二进制文件&#xff0c;下载地址如下&#xff1a; https://mirrors.tuna.tsinghua.e…

【深度学习】多源物料融合算法(一):量纲对齐常见方法

目录 一、引言 二、量纲对齐常见方法 2.1 Z-score标准化Sigmoid归一化 2.2 Min-Max 归一化 2.3 Rank Transformation 2.4 Log Transformation 2.5 Robust Scaling 3、总结 一、引言 类似抖音、快手、小红书等产品的信息流推荐业务&#xff0c;主要通过信息流广告、信…

大型语言模型在工业应用中的局限性:事实性扩充与深入分析

大型语言模型在工业应用中的局限性&#xff1a;事实性扩充与深入分析 摘要 本文深入探讨了大型语言模型&#xff08;LLMs&#xff09;在工业应用中所面临的重大挑战&#xff0c;特别聚焦于其在机械图纸解读、可编程逻辑控制器&#xff08;PLC&#xff09;程序生成以及更广泛的…

Java进阶:Zookeeper相关笔记

概要总结&#xff1a; ●Zookeeper是一个开源的分布式协调服务&#xff0c;需要下载并部署在服务器上(使用cmd启动&#xff0c;windows与linux都可用)。 ●zookeeper一般用来实现诸如数据订阅/发布、负载均衡、命名服务、集群管理、分布式锁和分布式队列等功能。 ●有多台服…

Ubuntu-配置apt国内源

Ubuntu-配置apt国内源 安装vim apt-get update apt-get install -y vim备份 cp /etc/apt/sources.list /etc/apt/sources.list.bak编辑源数据 vim /etc/apt/sources.list deb http://mirrors.aliyun.com/ubuntu/ jammy main restricted universe multiverse deb-src http://m…

信号处理之插值、抽取与多项滤波

信号处理之插值、抽取与多项滤波 一、问题背景 插值(Interpolation)与抽取(Decimation)是数字信号处理中采样率转换的核心操作&#xff1a; 插值&#xff1a;在信号中插入新样本以提高采样率&#xff08; L L L倍&#xff09;抽取&#xff1a;按比例 M M M降低采样率&#xf…

AI自动化编程初探

先说vscodeclinemodelscope方案&#xff0c;后面体验trae或者cursor再写写其它的。vscode和trae方案目前来说是免费的&#xff0c;cursor要用claud需要付费&#xff0c;而且不便宜&#xff0c;当然效果可能是最好的。 vscode方案&#xff0c;我的经验是最好在ubuntu上&#xff…