项目场景:
黑马程序员c++教程中利用类模板实现数组封装案例
在黑马程序员c++教程中,老师在讲解类模板时,使用类模板创建了数组类。通过这一类模板,可以创建不同数据类型的数组,并对数组进行一系列操作。其中,我们可以对运算符“[]”进行重载,来实现通过“方括号内下标”的方式直接访问数组元素。
问题描述
在测试代码的过程中,如果通过下标的方式访问数组元素,会出现触发断点的bug
以下是数组类模板的实现代码:
#pragma once
#include <iostream>
#include <string>
using namespace std;
template<typename T>
class MyArray
{
public:
//构造函数(容量)
MyArray(int capacity);
//拷贝构造
MyArray(const MyArray& arr);
//operator=
MyArray& operator=(const MyArray& arr);
//尾插元素
void PushBack(T x);
//尾删元素
void PopBack(T x);
//利用[下标]访问数组元素
T& operator[](const int i);
//获取数组大小
int GetSize();
//获取数组容量
int GetCapacity();
//析构函数
~MyArray();
private:
T* m_arr;
int m_size;
int m_capacity;
};
//构造函数(容量)
template<typename T>
MyArray<T>::MyArray(int capacity)
{
this->m_arr = new T(capacity);
this->m_size = 0;
this->m_capacity = capacity;
cout << "MyArray的构造函数调用" << endl;
}
//拷贝构造函数
//需要深拷贝
template<typename T>
MyArray<T>::MyArray(const MyArray<T>& arr)//
{
this->m_capacity = arr.m_capacity;
this->m_size = arr.m_size;
this->m_arr = new T(m_capacity);
for (int i = 0; i < this->m_size; i++)
{
this->m_arr[i] = arr.m_arr[i];
}
cout << "MyArray的拷贝构造函数调用" << endl;
}
//operator=
//同样需要深拷贝
template<typename T>
MyArray<T>& MyArray<T>::operator=(const MyArray<T>& arr)//
{
//先检验原数组是否已经存在数据,若已存在,先删除再拷贝
if (this->m_arr != NULL)
{
delete[]this->m_arr;
this->m_size = 0;
this->m_capacity = 0;
this->m_arr = NULL;
}
this->m_capacity = arr.m_capacity;
this->m_size = arr.m_size;
this->m_arr = new T(m_capacity);
for (int i = 0; i < this->m_size; i++)
{
this->m_arr[i] = arr.m_arr[i];
}
cout << "MyArray的operator=函数调用" << endl;
return *this;
}
//尾插元素
template<typename T>
void MyArray<T>::PushBack(T x)
{
if (this->m_size == this->m_capacity)
{
cout << "当前数组已满,无法插入!" << endl;
}
else
{
this->m_arr[this->m_size] = x;
(this->m_size)++;
}
}
//尾删元素
template<typename T>
void MyArray<T>::PopBack(T x)
{
if (this->m_size == 0)
{
cout << "当前数组为空,无法删除!" << endl;
}
else
{
(this->m_size)--;
}
}
//利用[下标]访问数组元素
template<typename T>
T& MyArray<T>::operator[](const int i)
{
return this->m_arr[i];
}
//获取数组大小
template<typename T>
int MyArray<T>::GetSize()
{
return this->m_size;
}
//获取数组容量
template<typename T>
int MyArray<T>::GetCapacity()
{
return this->m_capacity;
}
//析构函数
template<typename T>
MyArray<T>::~MyArray()
{
delete[]this->m_arr;
this->m_size = 0;
this->m_capacity = 0;
this->m_arr = NULL;
cout << "MyArray的析构函数调用" << endl;
}
以下是测试代码:
int main()
{
MyArray<int> arr(5);
int capacity = arr.GetCapacity();
for (int i = 0; i < capacity; i++)
{
arr.PushBack(i);
}
int size = arr.GetSize();
for (int i = 0; i < size; i++)
{
cout << arr[i]<<endl;
}
}
以下是出现的断点bug
在通过“[下标]”的方式访问利用类模板创建的数组时,触发断点
最初笔者以为,是因为重载[]运算符的代码有误,导致无法通过方括号内下标的方式访问数组,但将这段代码注释掉,即不使用重载的运算符后,出现了更严重的bug:
原因分析:
在笔者将自己的代码与视频教程中的代码对比后,笔者发现了原因。
在有参构造函数、拷贝构造函数,以及重载赋值运算符函数这三个函数中,需要在堆区上开辟一段连续的内存,用来存储数组的元素。
要做到这一点,就需要使用new关键字
具体语法为:
接收指针p = new 数据类型 [元素个数]
其中,用来包含元素个数的是中括号“[]”
但笔者在代码中却使用了小括号“()”
以有参构造函数为例:
错误的写法:
template<typename T>
MyArray<T>::MyArray(int capacity)
{
this->m_arr = new T(capacity);//错误的写法
this->m_size = 0;
this->m_capacity = capacity;
}
正确的写法:
template<typename T>
MyArray<T>::MyArray(int capacity)
{
this->m_arr = new T[capacity];//正确的写法
this->m_size = 0;
this->m_capacity = capacity;
}
new T[capacity]
的含义是,在堆区开辟大小为capacity个T类型数据的内存,并返回第一个数据的指针。
而new T(capacity)
的含义则是,在堆区开辟大小为一个T类型数据的内存,将这个T类型数据的值初始化为capacity。
正是因为笔者写成了后者,所以才导致在测试时,以为创建了5个元素大小的数组,实际上只创建了一个值为5的元素。
而此时size又是5,自然会导致指针访问越界,从而触发断点。
int main()
{
MyArray<int> arr(5);
int capacity = arr.GetCapacity();
for (int i = 0; i < capacity; i++)
{
arr.PushBack(i);
}
int size = arr.GetSize();
for (int i = 0; i < size; i++)
{
cout << arr[i]<<endl;
}
}
解决方案:
虽然解决了这一bug,但笔者尚未明白的是,在原有的错误代码下,用尾插函数向数组中插入元素时,指针应该也是越界访问的,但却没有因此崩溃,在调试时甚至将该段连续的内存成功地依次赋值(包括越界访问的部分)。反倒是后面执行的打印操作导致程序崩溃。简单点说,两部分都存在越界访问,结果前面没崩,后面反而崩了。难道原因在于,前面使用的是普通成员函数,后面使用的是重载的运算符?