数据结构——数组

数组

github地址

数组基础

  • 数组最大的有点:快速查询。索引快
  • 数组最好应用于 “索引有语义” 的情况
  • 但并非所有有语义的索引都适用于数组(身份证号)
  • 数组也可以处理 ”索引没有语义“ 的情况

封装数组类

  • 数组类该具备的功能:增 删 改 查

使用泛型:

  • 让我们的数据结构可以放置 “任何” 数据类型

实现

构造函数

我们希望有两个构造函数,可以支持给定容量和默认容量,这里默认容量我们设置成 10 :

template<typename T>
MyArray<T>::MyArray() {size_ = 0;capacity_ = 10;data_ = new T[capacity_];std::cout << "调用 MyArray() 构造." << std::endl;
}template<typename T>
MyArray<T>::MyArray(int capacity) {if (capacity <= 0) {std::cout << "MyArray(int) error. Capacity is illegal." << std::endl;throw "MyArray(int) error. Capacity is illegal.";}size_ = 0;capacity_ = capacity;data_ = new T[capacity_];std::cout << "调用 MyArray(int capacity) 构造." << std::endl;
}

析构函数

template<typename T>
MyArray<T>::~MyArray() {if (nullptr != data_) {delete[] data_;data_ = nullptr;}size_ = 0;capacity_ = 0;std::cout << "调用 ~MyArray() 析构." << std::endl;
}

拷贝构造和拷贝赋值

首先添加拷贝构造测试:

// 初始化
class ArrayTest : public testing::Test {protected:void SetUp() override {for (int i = 0; i < 10; ++i) {my_array_.AddLast(i);}}void TearDown() override {}MyArray<int> my_array_;
};// 拷贝构造测试
TEST_F(ArrayTest, CopyConstructorTest) {MyArray<int> my_array(my_array_);EXPECT_EQ(10,my_array.GetSize());EXPECT_EQ(9,my_array.Get(9));
}

这里出现了 TEST_F,在TDD实例有例子。

拷贝构造函数和赋值函数:

template<typename T>
MyArray<T>::MyArray(const MyArray &arr) {this->size_     = arr.size_;this->capacity_ = arr.capacity_;this->data_     = new T[capacity_];for (int i = 0; i < size_; ++i) {this->data_[i] = arr.data_[i];}std::cout << "调用  MyArray(const MyArray &arr) 拷贝构造" << std::endl;
}template<typename T>
MyArray<T> &MyArray<T>::operator=(const MyArray<T> &arr) {assert(this != &arr);if (this->data_ != nullptr) {delete[] this->data_;this->data_  = nullptr;}//分配内存this->size_     = arr.size_;this->capacity_ = arr.capacity_;this->data_     = new T[capacity_];//拷贝数据for (int i = 0; i < size_; i++) {//如果是自定义的复杂数据类型,必须对 = 运算赋进行重载,  operator=this->data_[i] = arr.data_[i];}std::cout << "调用 = 赋值操作 " << std::endl;return* this;
}

移动构造和移动赋值

首先添加测试用例:

// 移动构造和移动赋值测试
TEST_F(ArrayTest, MoveTest) {// 移动拷贝构造MyArray<int> my_array(std::move(my_array_));// 移动赋值传入本身 会报错
//  my_array = std::move(my_array);
//  EXPECT_EQ(10,my_array.GetSize());
//  EXPECT_EQ(9,my_array.Get(9));MyArray<int> my_array1;// 移动赋值my_array1 = std::move(my_array);EXPECT_EQ(10,my_array1.GetSize());EXPECT_EQ(0,my_array.GetSize());EXPECT_EQ(0,my_array_.GetSize());
}

移动构造和移动赋值:

template<typename T>
MyArray<T>::MyArray(MyArray &&arr) noexcept {assert(this != &arr);capacity_ = std::exchange(arr.capacity_, 0);size_     = std::exchange(arr.size_, 0);    // 非类类型成员的显式移动data_     = std::move(arr.data_);           // 类类型成员的显式移动arr.data_ = nullptr;std::cout << "调用  MyArray(const MyArray &&arr) 移动构造函数" << std::endl;
}template<typename T>
MyArray<T> &MyArray<T>::operator=(MyArray<T> &&arr) noexcept {assert(this != &arr);if (this->data_ != nullptr) {delete[] this->data_;this->data_ = nullptr;}//分配内存this->size_ = std::exchange(arr.size_, 0);this->capacity_ = std::exchange(arr.capacity_, 0);this->data_ = new T[capacity_];//拷贝数据for (int i = 0; i < size_; i++) {//如果是自定义的复杂数据类型,必须对 = 运算赋进行重载,  operator=this->data_[i] = std::move(arr.data_[i]);}std::cout << "调用 = 移动赋值操作 " << std::endl;return * this;
}

<< 重载

测试用例:

// 测试 << 重载
TEST_F(ArrayTest, OperatorTest) {std::cout << my_array_ << std::endl;
}

实现:

// 内部友元函数实现 重载输出 << 操作符,这里必须定义成友元
friend std::ostream &operator<<(std::ostream &out, MyArray<T> &obj) {out << "MyArray size = " << obj.size_ << ", Capacity = " << obj.capacity_ << std::endl;out << "MyArray: [";for (int i = 0; i < obj.size_; ++i) {out << obj.data_[i];if (i != obj.size_ - 1)out << ", ";}out << "] ";return out;
}

[] 重载

测试用例:

// [] 测试
TEST_F(ArrayTest, SquareBracketsTest) {EXPECT_EQ(0,my_array_[0]);EXPECT_EQ(1,my_array_[1]);
}

实现:

template<typename T>
T &MyArray<T>::operator[](int index) {if (index < 0 || index >= size_) {std::cout << "[] fail. Index is illegal." << std::endl;throw "[] fail. Index is illegal.";}std::cout << "[] 调用" << std::endl;return this->data_[index];
}

添加元素

首先添加测试:

TEST(ArrayTestNoF, AddAndGet) {MyArray<int> my_array;my_array.Add(0,1);my_array.Add(1,2);my_array.Add(2,3);EXPECT_EQ(1,my_array.Get(0));EXPECT_EQ(2,my_array.Get(1));EXPECT_EQ(3,my_array.Get(2));my_array.AddFirst(4);EXPECT_EQ(4,my_array.Get(0));EXPECT_EQ(4,my_array.GetFirst());my_array.AddLast(5);EXPECT_EQ(5,my_array.Get(4));EXPECT_EQ(5,my_array.GetLast());
}

我们希望提供三个接口:在指定位置插入元素,在头部和尾部插入元素

后两个方法可以通过第一个方法实现,我们先实现第一个方法,如下:

template<typename T>
void MyArray<T>::Add(int index, T t) {if (index < -1 || index > size_) {std::cout << "Add fail. Index is illegal." << std::endl;throw "Add fail. Index is illegal.";}if (IsFull()) {  Resize(capacity_ * 2);}for (int i = size_; i > index; --i) {data_[i] = data_[i - 1];}data_[index] = t;size_++;
}

后两个方法在此基础上:

template<typename T>
void MyArray<T>::AddFirst(T t) {Add(0, t);
}template<typename T>
void MyArray<T>::AddLast(T t) {Add(size_, t);
}

判断空满

测试用例:

TEST(ArrayTestNoF, IsEmpty) {MyArray<int> my_array(10);EXPECT_TRUE(my_array.IsEmpty());
}
template<typename T>
bool MyArray<T>::IsFull() const {return size_ == capacity_;
}template<typename T>
bool MyArray<T>::IsEmpty() const {return size_ == 0;
}

获取容量和大小

测试用例:

TEST_F(ArrayTest, GetSizeAndGetCapacity) {EXPECT_EQ(10,my_array_.GetCapacity());EXPECT_EQ(10,my_array_.GetSize());
}

实现:

template<typename T>
int MyArray<T>::GetSize() const {return size_;
}template<typename T>
int MyArray<T>::GetCapacity() const {return capacity_;
}

对指定位置元素赋值

测试用例:

TEST_F(ArrayTest, Set) {my_array_.Set(0,11);EXPECT_EQ(11,my_array_.GetFirst());
}

实现:

template<typename T>
void MyArray<T>::Set(int index, T t) {if (index < 0 || index >= size_) {std::cout << "Set Fail. Index is illegal." << std::endl;throw "Set fail. Index is illegal.";}if (IsEmpty()) {std::cout << "Set Fail. Array is empty." << std::endl;throw "Set fail. Array is empty.";}data_[index] = t;
}

查找元素

测试用例:

TEST_F(ArrayTest, FindAndContain) {EXPECT_EQ(0,my_array_.Find(0));EXPECT_TRUE(my_array_.Contain(1));EXPECT_FALSE(my_array_.Contain(111));
}

实现:

template<typename T>
int MyArray<T>::Find(T t) const {if (IsEmpty()) {std::cout << "Find fail. Array is empty." << std::endl;throw "Find Fail. Array is empty";}for (int i = 0; i < size_; ++i) {if (data_[i] == t) {return i;}}return -1;
}template<typename T>
bool MyArray<T>::Contain(T t) const {return Find(t) != -1;
}

调整空间

测试用例:

TEST_F(ArrayTest, Resize) {EXPECT_EQ(10,my_array_.GetSize());EXPECT_EQ(10,my_array_.GetCapacity());my_array_.AddLast(10);EXPECT_EQ(11,my_array_.GetSize());EXPECT_EQ(20,my_array_.GetCapacity());for (int j = 0; j < 6; ++j) {my_array_.RemoveLast();}EXPECT_EQ(5,my_array_.GetSize());EXPECT_EQ(20,my_array_.GetCapacity());my_array_.RemoveLast();EXPECT_EQ(4,my_array_.GetSize());EXPECT_EQ(10,my_array_.GetCapacity());
}

全部的源码传送门

时间复杂度分析

  • 添加操作       O(n)
    • addLast(e)      O(1)
    • addFirst(e)      O(n)
    • add(index,e)   O(n/2)=O(n)

最坏情况:O(n) , resize() 时间复杂度 O(n)

  • 删除操作 O(n)
    • removeLast()     O(1)
    • removeFirst()     O(n)
    • remove(index)    O(n/2)=O(n)

最坏情况:O(n) , resize() 时间复杂度 O(n)

  • 修改操作 O(1)

    • set(index,e)    O(1)
  • 查找操作

    • get(index)    O(1)
    • contains(e)  O(n)
    • find(e)           O(n)

均摊复杂度分析

由于 resize() O(n) 的存在,所以每次 addLastremoveLast 时间复杂度依然是 O(n) ?

resize()复杂度分析

假设当前 capacity 为 8 ,并且每次添加操作都是 addLast , 9 次添加操作触发一次 resize(), 总共进行了 9 次基本操作, n 次操作才会触发一次时间复杂度为 O(n) 的操作,平摊到前 n 次操作,相当于前 n 次操作每次复杂度为 O(1+1)=O(1)

在这个例子中,这样均摊计算,比计算最坏情况更有意义。

但是会出现这么一种情况:

复杂度振荡

size 为 n 时,我们先后执行 addLast , removeLast , addLast , removeLast , 每一次的操作时间复杂度都是 O(n) 的,出现问题的原因在于 removeLastresize 过于着急 (Eager)

解决方案:Lazy

size == capacity / 4 时,才将 capacity 减半。

测试是否有内存泄露

使用 valgrind 测试测试用例是否存在内存泄露

valgrind --leak-check=full --show-reachable=yes --trace-children=yes ./ArraysTest

使用 Valgrind 分析 C++ 程序时,有一些问题需要留意。例如,这个程序并没有发生内存泄漏,但是从 HEAP SUMMARY 可以看到,程序分配了 303 次内存,但却只释放了 303 次内存,为什么会这样呢?
  实际上这是由于 C++ 在分配内存时,为了提高效率,使用了它自己的内存池。当程序终止时,内存池的内存才会被操作系统回收,所以 Valgrind 会将这部分内存报告为 reachable 的,需要注意,reachable 的内存不代表内存泄漏,例如,从上面的输出中可以看到,有 72704 个字节是 reachable 的,但没有报告内存泄漏。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/448065.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

十分钟入门 RocketMQ

前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家。点击跳转到教程。 本文首先引出消息中间件通常需要解决哪些问题&#xff0c;在解决这些问题当中会遇到什么困难&#xff0c;Apache RocketMQ作为阿里开源的…

使用delegate类型设计自定义事件

在C#编程中&#xff0c;除了Method和Property&#xff0c;任何Class都可以有自己的事件&#xff08;Event&#xff09;。定义和使用自定义事件的步骤如下&#xff1a; &#xff08;1&#xff09;在Class之外定义一个delegate类型&#xff0c;用于确定事件程序的接口 &#xff0…

UTC Time

整个地球分为二十四时区&#xff0c;每个时区都有自己的本地时间。在国际无线电通信场合&#xff0c;为了统一起见&#xff0c;使用一个统一的时间&#xff0c;称为通用协调时(UTC, Universal Time Coordinated)。UTC与格林尼治平均时(GMT, Greenwich Mean Time)一样&#xff0…

解决:Unknown custom element: <myData> - did you register the component correctly? For recursive compon

前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家。点击跳转到教程。 1. 引用一个组件报错&#xff1a; Unknown custom element: <myData> - did you register the component correctly?For recursi…

无处不在的container_of

无处不在的container_of linux 内核中定义了一个非常精炼的双向循环链表及它的相关操作。如下所示&#xff1a; struct list_head {struct list_head* next, * prev; };ubuntu 12.04 中这个结构定义在 /usr/src/linux-headers-3.2.0-24-generic/include/linux/types.h 中&…

程序员学习能力提升三要素

摘要&#xff1a;IT技术的发展日新月异&#xff0c;新技术层出不穷&#xff0c;具有良好的学习能力&#xff0c;能及时获取新知识、随时补充和丰富自己&#xff0c;已成为程序员职业发展的核心竞争力。本文中&#xff0c;作者结合多年的学习经验总结出了提高程序员学习能力的三…

AMD Mantle再添新作,引发下代GPU架构猜想

摘要&#xff1a;今年秋天即将发布的《希德梅尔文明&#xff1a;太空》将全面支持AMD Mantle API&#xff0c;如此强大的功能背后离不开强大的CPU、GPU支持。上周AMD爆出了下一代海盗岛R9 300系列&#xff0c;据网友猜测海盗岛家族可能用上速度更快的HBM堆栈式内存。 小伙伴们…

VUE : 双重 for 循环写法、table 解析任意 list 、万能表格组件、解析一维数组、动态生成 table 所有数据

前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家。点击跳转到教程。 1.需求&#xff1a; 我想要一个 table 组件能在实际调用时动态生成所有的 tr 、td 。 后端返回的只是一个 list &#xff0c; 前端页…

超详细 图解 : IntelliJ IDEA 逆向生成 JAVA 实体类

前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家。点击跳转到教程。 1.配置数据库,&#xff0c;这里连接的是mysql。 2.填写 连接数据库的信息&#xff0c;填写完成后可以点击Test Connection,测试一下是否…

leetcode练习——数组篇(1)(std::ios::sync_with_stdio(false);std::cin.tie(nullptr);)

题号1. 两数之和&#xff1a; 给定一个整数数组 nums 和一个目标值 target&#xff0c;请你在该数组中找出和为目标值的那 两个 整数&#xff0c;并返回他们的数组下标。 你可以假设每种输入只会对应一个答案。但是&#xff0c;你不能重复利用这个数组中同样的元素。 示例: …

intellij idea 中去除 @Autowired 注入对象带来的红色下划线报错提示

前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家。点击跳转到教程。 PS&#xff1a; 有 2 种方法&#xff0c;第 2 种方法更简单&#xff0c;在此谢谢好心友人的评论。 方法1&#xff1a; idea中通过Autow…

Coolite动态加载CheckboxGroup,无法在后台中获取

Coolite在后台动态加载CheckboxGroup&#xff0c;页面显示都正常&#xff0c;但是在后台去获取选中的checkbox时&#xff0c;使用下方法&#xff1a; ///<summary>///获取所选权限 ///</summary>///<returns></returns>privatestringGetPermiss…

图解 IDEA 中 springboot 项目 MyBatis Generator 逆向生成实体类及 mapper 配置文件

前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家。点击跳转到教程。 一、准备工作&#xff1a; 1. 新建一个 配置文件&#xff1a;generatorConfig.xml 。 <?xml version"1.0" encoding&qu…

关于IIS 7.5 限制连接数与流量限制模块

网页中的视频是用户喜闻乐见的常见形式之一&#xff0c;并在主要的站点中中以某种形式&#xff08;产品视频、教程视频、理财场景、user generated content、消费报告等&#xff09;在更广泛的应用。 其中的一个挑战是把视频加入到站点&#xff0c;虽然这并不花费很多代价。高质…

汽车标志大全 买车必知

简要介绍&#xff1a;为您提供汽车标志、世界汽车标志大全、各种汽车标志、国产汽车标志大全、汽车标志图片、汽车标志及名称、名车标志大全、世界名车排行榜、世界十大名车、世界名车图片等有关汽车标志、汽车图片、汽车名字及汽车品牌方面的知识。 欧美汽车标志图片大全_欧美…

解决: Caused by: java.lang.IllegalStateException: Cannot load driver class: com.mysql.jdbc.Driver

前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家。点击跳转到教程。 1. 报错&#xff1a; Caused by: java.lang.IllegalStateException: Cannot load driver class: com.mysql.jdbc.Driver 2.但是&…

解决:Field xxMapper in xx.service.impl.xxServiceImpl required a bean of type ‘xx.mapper.xxMapper‘

前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家。点击跳转到教程。 1. 启动 springboot 项目报错&#xff1a; Field userMapper in gentle.service.impl.UserServiceImpl required a bean of type gent…

Linux 查看 MySQL 版本的四种方法

1 在终端下执行 mysql -V 2 在help中查找 mysql --help |grep Distrib 3 在mysql 里查看 select version() 4 在mysql 里查看 status 转自&#xff1a;https://blog.csdn.net/chengyuc/article/details/77094775

html 基本布局介绍

1、div默认是纵向排列的&#xff0c;例子如下&#xff1a; <div id"wrap"><div id"div1">div1</div><div id"div2">div2</div><div id"div3">div3</div> </div> 2、如果要div横向排列…

@JsonSerialize 使用:注解方式 实现条件判断属性值、条件修改属性值

前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家。点击跳转到教程。 1. 数据库中是 1、0 记录性别。 预期效果为&#xff1a;当查到属性值为 1 时&#xff0c;就给序列化后的 json 中性别字段赋值为 “男”…