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

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.dtcms.com/a/359251.html

相关文章:

  • 防火墙技术(三):状态检测和会话机制
  • 接口自动化测试框架
  • python pyqt5开发DoIP上位机【自动化测试的逻辑是怎么实现的?】
  • 深度解析Fluss LockUtils类的并发艺术
  • 手写MyBatis第43弹:插件拦截原理与四大可拦截对象详解
  • Agent实战教程:LangGraph结构化输出详解,让智能体返回格式化数据
  • Keil5 MDK_541官网最新版下载、安装
  • offsetof宏的实现
  • 线程池项目代码细节2
  • 互联网医院系统源码解析:如何从零搭建高效的在线问诊平台
  • SNMPv3开发--EngineID安全访问机制
  • 腾讯云的运维笔记——从yum的安装与更新源开始
  • 深入理解 Linux 驱动中的 file_operations:从 C 语言函数指针到类比 C++ 虚函数表
  • centos7中MySQL 5.7.32 到 5.7.44 升级指南:基于官方二进制包的原地替换式升级
  • 有个需求:切换车队身份实现Fragment的Tab隐藏显示(车队不显示奖赏)
  • SNMPv3开发--简单使用
  • 【Linux基础】深入理解Linux环境下的BIOS机制
  • Python - 机器学习:从 “教电脑认东西” 到 “让机器自己学规律”
  • 项目管理和产品管理的区别
  • docker,mysql安装
  • vector的学习和模拟
  • 揭秘表格推理的“思维革命”:RoT模型介绍
  • 【机器学习基础】机器学习中的容量、欠拟合与过拟合:理论基础与实践指南
  • Vue生命周期、工程化开发和脚手架、组件化开发
  • 学习日志41 python
  • 打工人日报#20250830
  • 内网后渗透攻击--跨域攻击
  • 给某个conda环境安装CUDA 12.4版本 全局CUDA不变
  • Mybatis 动态sql
  • 【树形数据结构】李超线段树 (Li-Chao Tree)