C++数据结构与算法实现(目录)
前驱课程
C++ 精简教程 目录(必读)
堆数组 heap array
面相对象的堆数组
1 原始堆数组的缺点:
1) 原始堆数组 其长度是固定不变的。
2) 使用指针管理元素,数据和操作散落一地。这样的数组,使用起来也很不方便。
2 如何实现一个面向对象的动态数组
下面我们要实现的类叫 Vector。这个类有下面的一些功能。
现在,我们也开始考虑如何实现下面的这些功能。
1 返回元素数量 size
数组的元素数量,我们可以用一个成员变量来记录。
每当元素增加一个的时候,我们记得把这个成员变量的值加1。
class Vector
{
int size();// 返回元素数量
private:
int element_cout;// 存储元素数量
};
2 添加元素 push_back()
为了盛放元素,我们需要创建一个动态数组。但是一开始这个数组多大比较好呢?
我们把数组初始大小设为10. 当这些元素用完了以后再扩容。
第一次push_back的时候,容器的容量是10,元素个数为0,因为还没有存任何数据;
class Vector
{
private:
int element_cout=0;
int capacity=0;
int* data=nullptr;
};
3 添加元素到容器里
使用push_back(i)来把元素放到容器里的过程如下
添加整数11到容器中:
添加整数12到容器中:
一直添加,直到添加20到容器中:
此时容器再也没有剩余的空间可以添加元素了,需要扩容。
扩容如果要保持元素挨在一起存放,只能另起一个更大的炉灶,把现在的数据拷贝过去。
(1)先开辟更大的空间
(2)拷贝原来的数据到新地方:
(3)添加新元素到新空间的末尾,更新元素数量
(4)释放原来的空间,接管新空间
delete[] m_data;
m_data = p;
4 访问元素
自定义的类型class/struct Vector是不可以使用下标操作符[]的。
Vector a;
a[0];// 编译报错!!!!
为此需要使用操作符重载。
对操作符[]的重载是一个特殊的成员函数,有固定的格式。
给你的类添加一个下面的成员函数,就可以使用下标操作符了:
int& operator[](int n) { return data[n]; }
有了上面的成员函数,下面的代码就可以正常工作了:
Vector a;
a[0];// OK
5 清空元素 clear
清空元素会让数组回到初始状态,也就是capacity为0,size为0;
如果原来有元素,需要释放原来的全部动态内存。
此时 容量为0,大小为0。
注意:用户可以连续两次调用clear,而不应该出现问题。
完整代码和测试用例如下
#include<iostream>
#include <iomanip>
#include <cassert>
using namespace std;
//------下面的代码是用来测试你的代码有没有问题的辅助代码,你无需关注------
#include <algorithm>
#include <cstdlib>
#include <iostream>
#include <vector>
#include <utility>
using namespace std;
struct Record { Record(void* ptr1, size_t count1, const char* location1, int line1, bool is) :ptr(ptr1), count(count1), line(line1), is_array(is) { int i = 0; while ((location[i] = location1[i]) && i < 100) { ++i; } }void* ptr; size_t count; char location[100] = { 0 }; int line; bool is_array = false; bool not_use_right_delete = false; }; bool operator==(const Record& lhs, const Record& rhs) { return lhs.ptr == rhs.ptr; }std::vector<Record> myAllocStatistic; void* newFunctionImpl(std::size_t sz, char const* file, int line, bool is) { void* ptr = std::malloc(sz); myAllocStatistic.push_back({ ptr,sz, file, line , is }); return ptr; }void* operator new(std::size_t sz, char const* file, int line) { return newFunctionImpl(sz, file, line, false); }void* operator new [](std::size_t sz, char const* file, int line)
{ return newFunctionImpl(sz, file, line, true); }void operator delete(void* ptr) noexcept {
Record item{ ptr, 0, "", 0, false }; auto itr = std::find(myAllocStatistic.begin(), myAllocStatistic.end(), item); if (itr != myAllocStatistic.end()) {
auto ind = std::distance(myAllocStatistic.begin(), itr); myAllocStatistic[ind].ptr = nullptr; if (itr->is_array) { myAllocStatistic[ind].not_use_right_delete = true; }
else { myAllocStatistic[ind].count = 0; }std::free(ptr);
}
}void operator delete[](void* ptr) noexcept {Record item{ ptr, 0, "", 0, true }; auto itr = std::find(myAllocStatistic.begin(), myAllocStatistic.end(), item); if (itr != myAllocStatistic.end()) { auto ind = std::distance(myAllocStatistic.begin(), itr); myAllocStatistic[ind].ptr = nullptr; if (!itr->is_array) { myAllocStatistic[ind].not_use_right_delete = true; } else { myAllocStatistic[ind].count = 0; }std::free(ptr); }}
#define new new(__FILE__, __LINE__)
struct MyStruct { void ReportMemoryLeak() { std::cout << "Memory leak report: " << std::endl; bool leak = false; for (auto& i : myAllocStatistic) { if (i.count != 0) { leak = true; std::cout << "leak count " << i.count << " Byte" << ", file " << i.location << ", line " << i.line; if (i.not_use_right_delete) { cout << ", not use right delete. "; } cout << std::endl; } }if (!leak) { cout << "No memory leak." << endl; } }~MyStruct() { ReportMemoryLeak(); } }; static MyStruct my; void check_do(bool b, int line = __LINE__) { if (b) { cout << "line:" << line << " Pass" << endl; } else { cout << "line:" << line << " Ohh! not passed!!!!!!!!!!!!!!!!!!!!!!!!!!!" << " " << endl; exit(0); } }
#define check(msg) check_do(msg, __LINE__);
//------上面的代码是用来测试你的代码有没有问题的辅助代码,你无需关注------
//注意:禁止修改Vector的定义,包括禁止给Vector添加成员变量;
//可以添加私有成员函数,如果你认为需要的话
struct Vector
{
public:
Vector();
Vector(int n, int value);
Vector(const Vector& from);//deep copy
Vector& operator=(const Vector& from);//deep copy
~Vector();
int size() const;
//只读元素
//参考 https://zhuanlan.zhihu.com/p/539451614
const int& operator[](int n)const { return m_data[n]; }
//写入元素
int& operator[](int n) { return m_data[n]; }
void push_back(int value);
bool empty() const;
void clear();
private:
void copy(const Vector& from);
private:
int m_size = 0;
int m_capacity = 0;
int* m_data = nullptr;
//请忽略下面这个成员变量,这个成员变量不影响你的实现,当它不存在即可。
};
//默认构造函数什么也不需要做,只用来保证可以创建对象的时候无需提供参数
Vector::Vector()
{
}
Vector::Vector(int n, int value)
{
for (int i = 0; i < n; i++)
{
push_back(value);
}
}
Vector::Vector(const Vector& from)
{
copy(from);
}
Vector::~Vector()
{
//释放动态内存,需用用 delete[]
//(4) your code
}
int Vector::size() const
{
return m_size;
}
void Vector::push_back(int value)
{
//1 如果capacity为0,则一次性开辟10个元素
//2 如果capacity容量没有用完 追加到最后
//3 如果capacity容量已经用完,开辟两倍capacity大小的容量,拷贝老数据,追加新数据
if (m_capacity == 0)
{
//(1) your code
}
else if (m_size < m_capacity)
{
//给最后一个元素的后面赋值为新元素value
//增加元素数量
//(5) your code
}
else
{
//每次内存不够用就翻倍
int* p = new int[2 * m_capacity];
//先把原来的每个元素拷贝到新地方
int j;
for (int j = 0; j < m_size; j++)
{
p[j] = m_data[j];
}
//把新添加的元素也添加到新地方
//(6) your code
//记得元素数量加1
//(7) your code
//容量翻倍
//(8) your code
//释放原来的内存
//(9) your code
//成员变量接管新开普的内存
//(10) your code
}
}
bool Vector::empty() const
{
return m_size == 0;
}
void Vector::clear()
{
//(11) your code 参考 清空元素部分的介绍;如果原来已经有容量了,需要先释放原来的空间;
}
void Vector::copy(const Vector& from)
{
//(2) your code 先调用 clear
}
Vector& Vector::operator = (const Vector& from)
{
if (&from == this)
{
return *this;
}
copy(from);
return *this;
}
void test1(void)
{
Vector v;
int i;
check(v.size() == 0);
for (i = 0; i < 10; i++)
{
v.push_back(i);
}
check(v.size() == 10);
for (int i = 0; i < 10; i++)
{
check(v[i] == i);
}
check(v.size() == 10);
}
void test2(void)
{
int n = 100000;
Vector v;
int i;
check(v.size() == 0);
for (i = 0; i < n; i++)
{
v.push_back(i);
}
for (int i = 0; i < n; i++)
{
assert(v[i] == i);//这里必须用assert,否则会疯狂打印pass
}
check(v.size() == n);
}
void print(Vector& v, const std::string& msg)
{
std::cout << "The contents of " << msg.c_str() << " are:";
for (int i = 0; i != v.size(); ++i)
{
std::cout << ' ' << v[i];
}
std::cout << '\n';
}
void test3()
{
Vector a;
Vector first; // empty vector of ints
assert(first.empty() == true && first.size() == 0);
Vector second(4, 100); // four ints with value 100
assert(second.empty() == false);
assert(second.size() == 4);
Vector fourth(second); // a copy of third
assert(fourth.size() == second.size());
int myints[] = { 16,2,77,29 };
Vector fifth;
fifth.push_back(16);
fifth.push_back(2);
fifth.push_back(77);
fifth.push_back(29);
assert(fifth.empty() == false);
assert(fifth[0] == 16);
assert(fifth[3] == 29);
assert(fifth.size() == sizeof(myints) / sizeof(int));
print(fifth, "fifth");//The contents of fifth are:16 2 77 29
fifth.push_back(30);
assert(fifth[4] == 30);
assert(fifth.size() == 5);
print(fifth, "fifth");//The contents of fifth are:16 2 77 29 30
assert(fifth.size() == sizeof(myints) / sizeof(int) + 1);
first = fifth = fifth;
print(first, "first");//The contents of first are:16 2 77 29 30
assert(first.empty() == false && first.size() == fifth.size());
Vector a1;
a1.push_back(16);
a1.push_back(2);
a1.push_back(77);
a1.push_back(29);
{
Vector b(a1);
b.push_back(2);
check(b[4] == 2);
check(b[0] == 16);
}
{
Vector c;
c = a1;
std::cout << std::endl;
}
assert(a1.size() == sizeof(myints) / sizeof(int));
{
Vector c;
c = fifth;
c[0] = 1;
assert(c[0] == 1);
}
}
int main()
{
test1();
test2();
test3();
return 0;
}
正确完整输出如下
line:180 Pass
line:185 Pass
line:188 Pass
line:188 Pass
line:188 Pass
line:188 Pass
line:188 Pass
line:188 Pass
line:188 Pass
line:188 Pass
line:188 Pass
line:188 Pass
line:190 Pass
line:197 Pass
line:206 Pass
The contents of fifth are: 16 2 77 29
The contents of fifth are: 16 2 77 29 30
The contents of first are: 16 2 77 29 30
line:258 Pass
line:259 Pass
Memory leak report:
No memory leak.
答案在此
动态数组 Vector(难度1)(答案)