当前位置: 首页 > web >正文

C++讲解---如何设计一个类

之前我们使用已经设计好的类学习对应的知识点

这一节,我们讲一下怎么设计一个类

这里以一个生成数组类为示例,这个类能够实现下面的功能:
1. 初始化数组;
2. 输入数组的元素个数和值生成类;
3. 返回元素个数;
4. 重写下标运算符;
5. 析构函数;
6. 限制隐式转化;
7. 可以复制数组;
8. 可以重新改变数组的个数;
9. 检查数组数据是否溢出

好,为了完成上面的功能,我们一步一步的完善代码。


1、初始版本

//初始版本
#ifndef ___Class_IntArray
#define ___Class_IntArray//==== 整数数组类====//
class IntArray {int nelem;       // 数组的元素个数int* vec;        // 指向第一个元素的指针public://--- 构造函数 ---//IntArray(int size) : nelem(size) { //格式参照成员初始化器vec = new int[nelem]; //创建动态数组}//--- 返回元素个数 ---//int size() const { return nelem; }//--- 下标运算符 ---//int& operator[](int i) { return vec[i]; }
};#endif

这里用到的知识点有:
1. 成员初始化器
2. 动态创建数组

在这里如果我使用IntArrary x[5]这个命令的话,那么在内存中,会执行下面的动作,如图所示:
数组的建立
使用成员初始化器 nelem(size) 把数据成员 nelem 初始化为 5。然后,执行构造函数体,把指向由 new 运算符分配的 nelem 个元素的存储空间中的第一个元素的指针赋给 vec。

在这里,新的知识点有:

1.1 重载下标运算符

现在我们来重点讲解一下您提供的代码中​​下标运算符 [ ]的重载​​。

这是整个 IntArray类实现能够像内置数组一样使用的关键所在。

核心代码行

int& operator[](int i) { return vec[i]; }

​​operator[ ]​​: 这是C++中用于重载下标运算符的固定语法。当编译器看到对一个对象使用[ ]时,就会自动寻找并调用这个成员函数。

​​参数 (int i)​​: 这是 [ ]括号内的索引值。例如,当你写 arr[5]时,5就会作为参数 i传递给这个函数。

​返回值 int&​​: 这是最关键的部分。它表示这个函数返回一个​​指向数组中第 i个元素的引用​​。(在复数类中,返回引用的是类名,在这里不是,返回类名是为了实现链式调用​​ (Chaining),而我们直接返回) 这里的 &符号是​​类型修饰符​​,它和前面的 int结合在一起,共同构成了一个新的类型——​​“对整数的引用”​​ (int&)。


2. 显式创建对象与销毁对象

在上个版本中,如果使用这个数组类创建的对象,在声明周期结束后,需要手动销毁数组主体。如下图所示:
初始版本&是生命周期
为了解决这个问题,我们可以使用以下方案:

  1. 显式构造函数: 添加explicit关键字,防止编译器进行隐式类型转换,避免意外的构造行为,提高代码安全性。
  2. 析构函数:释放动态分配的内存,防止内存泄漏。

代码如下:

// 整数数组类 IntArray (第2版)
#ifndef ___Class_IntArray
#define ___Class_IntArray//===== 整数数组类 ======
class IntArray {int nelem;     // 数组的元素个数int* vec;      // 指向第一个元素的指针public://--- 显式构造函数 ---//explicit IntArray(int size) : nelem(size) { vec = new int[nelem]; }//--- 析构函数 ---//~IntArray() { delete[] vec; }//--- 返回元素个数 ---//int size() const { return nelem; }//--- 下标运算符 ---//int& operator[](int i) { return vec[i]; }
};#endif

2.1 显式构造函数

在 C++ 中,explicit关键字用于修饰​​单参数构造函数​​(或可以仅用一个参数调用的构造函数),它的主要作用是​​防止编译器进行隐式类型转换​​,从而提高代码的安全性和可读性。
如果没有显示构造函数,那么这句代码可能会被误解为使用整数初始化数组,其实这句代码实际运行中是创建了5个元素的数组,这并不符合c++实际使用习惯。

IntArray a = 5;//// 看起来像赋值,实际上是构造

如果使用了显式构造函数,就只能使用清晰的语义建立数组:

IntArray arr(5);   // 明确调用构造函数
IntArray arr = IntArray(5);  // 显式构造

2.2. 析构函数

​​作用​​:在对象被销毁时自动调用,用于​​释放资源​​(如内存、文件句柄、网络连接等)。

​​语法​​:~类名(),没有返回值,不能带参数。

加上了析构函数之后,数组对象的生命周期如下图所示:
析构函数销毁对象


3. 实现数组复制功能和赋值运算符能

3.1复制构造函数

作用​​:用另一个同类型对象初始化新对象。

关键点​​:

  1. ​​检查自赋值​​:if(&x == this)。防止用自己初始化自己(虽然罕见,但需防范)
  2. 若非自赋值:
    分配新内存:vec = new int[nelem];
    深拷贝数据:for循环逐元素复制
//---- 复制构造函数 ----
IntArray::IntArray(const IntArray& x)
{if (&x == this) {// 如果初始化为自己nelem = 0;vec = NULL;} else {nelem = x.nelem;vec = new int[nelem];for (int i = 0; i < nelem; i++)vec[i] = x.vec[i];}
}

3.2赋值运算符

作用​​:将一个对象的值赋给另一个已存在的对象。

​​关键点​​:

  1. ​​检查自赋值​​:if(&x != this)。核心安全措施,防止delete[]自己的内存

  2. 若非自赋值:
    检查大小是否相同​​:if(nelem != x.nelem)
    若不同,先delete[]释放旧内存,再按新大小new分配
    深拷贝数据:for循环逐元素复制

  3. ​​返回*this​​:支持链式赋值(如a = b = c)

//---- 赋值运算符 ----//
IntArray& IntArray::operator=(const IntArray& x)
{if (&x != this) {//检查自赋值​​if (nelem != x.nelem) {//检查大小是否相同delete[] vec;//释放旧内存nelem = x.nelem;vec = new int[nelem];//分配数据}for (int i = 0; i < nelem; i++) {vec[i] = x.vec[i];//深拷贝数据}}return *this;
}

4.异常处理

如果我们想要给下标溢出进行警告,可以这样做:

// 代码清单14-8
// IntArray04/IntArray.h
// 整数数组类 IntArray(第4版:头文件)#ifndef ___Class_IntArray
#define ___Class_IntArray// 整数数组类
class IntArray {int nelem; // 数组的元素个数int* vec;  // 指向第一个元素的指针public:// 下标范围错误class IdxRngErr {private:const IntArray* ident;int idx;public:IdxRngErr(const IntArray* p, int i) : ident(p), idx(i) { }int index() const { return idx; }};//各类函数。。。。// 下标运算符int& operator[](int i) {if (i < 0 || i >= nelem)throw IdxRngErr(this, i); // 抛出下标范围错误return vec[i];}// const 版下标运算符const int& operator[](int i) const {if (i < 0 || i >= nelem)throw IdxRngErr(this, i); // 抛出下标范围错误return vec[i];}
};#endif

这样使用:

#include <new>
#include <iostream>
#include "IntArray.h"
using namespace std;//--- 对元素个数为size的数组的前num个数据赋值并显示 ---
void f(int size, int num) {try {IntArray x(size);for (int i = 0; i < num; i++) {x[i] = i;cout << "x[" << i << "] = " << x[i] << '\n';}} catch (IntArray::IdxRngErr& x) {cout << "下标溢出:" << x.index() << '\n';return;} catch (bad_alloc) {cout << "内存分配失败。\n";exit(1);}
}int main() {int size, num;cout << "元素个数:";cin >> size;cout << "数据个数:";cin >> num;f(size, num);cout << "main函数结束。\n";
}

发生异常的时候,代码是按照下面的步骤运行的:

1.​​抛出异常​​:当 operator[]发现下标越界,它立刻“按下警报按钮” —— throw IdxRngErr(this, i);。

this告诉警报系统:​​“数组A出现了问题!”​​i告诉警报系统:​​“下标5出现了问题!”​​

2.​​警报信息生成(构造函数)​​:构造函数就是那个​​生成警报信息的机器​​。它接收“数组位置”和“下标号”,然后把这些信息刻在一块牌子上(即初始化 ident和 idx)。

3.catch 块相应​​:catch块接到这块牌子,一看就知道是数组a和下标5出现了问题,然后他们就可以直接调用 x.index()去精准灭火(处理错误)。

其中,IdxRngErr错误是是由 IntArray类的 operator[](下标访问运算符)抛出的。​​​bad_alloc是由 C++ 标准库中的 new操作符抛出的异常​​。

5.全部代码

5.1 头文件

#ifndef ___Class_IntArray
#define ___Class_IntArray// 整数数组类
class IntArray {int nelem;      // 数组的元素个数int* vec;       // 指向第一个元素的指针public:// 下标范围错误class IdxRngErr {private:const IntArray* ident;int idx;public:IdxRngErr(const IntArray* p, int i) : ident(p), idx(i) { }int index() const { return idx; }};// 显式构造函数explicit IntArray(int size) : nelem(size) { vec = new int[nelem]; }// 复制构造函数IntArray(const IntArray& x);// 析构函数~IntArray() { delete[] vec; }// 返回元素个数int size() const { return nelem; }// 赋值运算符IntArray& operator=(const IntArray& x);// 下标运算符int& operator[](int i) {if (i < 0 || i >= nelem) throw IdxRngErr(this, i); // 抛出下标范围错误return vec[i];}// const 版下标运算符const int& operator[](int i) const {if (i < 0 || i >= nelem) throw IdxRngErr(this, i); // 抛出下标范围错误return vec[i];}
};#endif

5.2 源文件

// 整数数组类(第3版:源文件)#include <cstddef>
#include "IntArray.h"//--- 复制构造函数 ---
IntArray::IntArray(const IntArray& x)
{if (&x == this) {           // 如果初始化器是自己nelem = 0;              // 则将元素个数设为0vec = NULL;             // 将数组指针设为NULL} else {nelem = x.nelem;        // 使元素个数与x相同vec = new int[nelem];   // 分配空间for (int i = 0; i < nelem; i++)vec[i] = x.vec[i];  // 复制所有元素}
}//--- 赋值运算符 ---
IntArray& IntArray::operator=(const IntArray& x)
{if (&x != this) {           // 如果赋值源不是自己if (nelem != x.nelem) { // 如果元素个数不同delete[] vec;       // 就释放已经确保的空间nelem = x.nelem;    // 更新元素个数vec = new int[nelem]; // 重新分配空间}for (int i = 0; i < nelem; i++)vec[i] = x.vec[i];  // 复制所有元素}return *this;               // 返回赋值对象
}

5.3 测试用例

//整数数组类 IntArray(第4版)的使用例程
#include <new>
#include <iostream>
#include "IntArray.h"
using namespace std;//对元素个数为 size的数组的前 num个数据赋值并显示
void f(int size, int num) {try {IntArray x(size);for (int i = 0; i < num; i++) {x[i] = i;cout << "x[" << i << "] = " << x[i] << "\n";}}catch (IntArray::IdxRngErr & x) {cout << "下标溢出:" << x.index() <<'\n';return;}catch (bad_alloc) {cout << "内存分配失败。\n";exit(1);}
}int main()
{int size, num;cout << "元素个数:";cin >> size;cout << "数据个数:";cin >> num;f(size, num);cout << "main函数结束。\n";
}
http://www.xdnf.cn/news/19416.html

相关文章:

  • 【lua】模块基础及应用
  • LED灯带离线语音控制方案WTK6900P
  • fork详解(附经典计算题)
  • 苍穹外卖项目笔记day02
  • Rust 登堂 之 Sized和不定长类型 DST(七)
  • leetcode刷题记录08——top100题里的5道中等题
  • Vue基础知识-methods事件绑定(@事件名和v-on:事件名)和常用事件修饰(.prevent/.stop/.once/.enter)
  • Coze源码分析-API授权-删除令牌-后端源码
  • 【15】VisionMaster入门到精通——--通信--TCP通信、UDP通信、串口通信、PLC通信、ModBus通信
  • 鸿蒙ArkTS 核心篇-16-循环渲染(组件)
  • lvgl模拟器 被放大 导致显示模糊问题
  • Notepad++使用技巧1
  • 日志ELK、ELFK、EFK
  • 快速学习和掌握Jackson 、Gson、Fastjson
  • AI + 行业渗透率报告:医疗诊断、工业质检领域已进入规模化落地阶段
  • GD32入门到实战20--定时器
  • 【LeetCode】大厂面试算法真题回忆(122) —— 篮球比赛
  • react性能优化有哪些
  • SSR降级CSR:高可用容灾方案详解
  • Android中handler机制
  • 【Android】JSONObject和Gson的使用
  • HTTP的概念、原理、工作机制、数据格式和REST
  • 《C++——makefile》
  • 三重积分的性质
  • 【MATLAB绘图进阶教程】(2-6)动态绘图制作详解与例程,包括drawnow、pause、getframe、video write等命令
  • 机器学习时间序列算法进行随机划分数据是不合适的!
  • Dify1.8.0最新版本安装教程:Ubuntu25.04系统本地化安装部署Dify详细教程
  • 移动零,leetCode热题100,C++实现
  • IP-Guard支持修改安全区域密级文字和密级级数
  • 嵌入式学习日记(38)HTTP