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. 显式创建对象与销毁对象
在上个版本中,如果使用这个数组类创建的对象,在声明周期结束后,需要手动销毁数组主体。如下图所示:
为了解决这个问题,我们可以使用以下方案:
- 显式构造函数: 添加explicit关键字,防止编译器进行隐式类型转换,避免意外的构造行为,提高代码安全性。
- 析构函数:释放动态分配的内存,防止内存泄漏。
代码如下:
// 整数数组类 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复制构造函数
作用:用另一个同类型对象初始化新对象。
关键点:
- 检查自赋值:if(&x == this)。防止用自己初始化自己(虽然罕见,但需防范)
- 若非自赋值:
分配新内存: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赋值运算符
作用:将一个对象的值赋给另一个已存在的对象。
关键点:
-
检查自赋值:if(&x != this)。核心安全措施,防止delete[]自己的内存
-
若非自赋值:
检查大小是否相同:if(nelem != x.nelem)
若不同,先delete[]释放旧内存,再按新大小new分配
深拷贝数据:for循环逐元素复制 -
返回*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";
}