最近为了让自己深入了解C++,于是开始动手实现C++的一些简单的常用组件,在基本完成了最简单的链表、栈、队列等数据结构之后,我又挑了一个最简单的,开始实现C++的静态数组array类
参考文档:<array> 函数 | Microsoft Docs
array
C++的array是一个定长静态数组,声明如下。接收两个模板参数:元素类型,一个是数组长度。把数组长度也作为模板参数之一,数组长度不能修改,如果要改变数组长度,那还是使用vector或者deque吧
template <class Ty, std::size_t N>
class array;
定长数组类似于C风格的数组,支持随机访问,插入删除时间复杂度O(n),随机访问时间复杂度O(1)。整体难说实现过程没有什么难点,我在这个过程中遇到的唯一遇到的问题,就是使用初始化列表初始化一个array
首先看一下array的构造函数:这是上面提到的visual studio官方文档给的构造函数,一个是默认的构造函数,一个是复制构造函数。于是我就照着写了
array();
array(const array& right);
我在继续阅读的时候,发现了如下初始化形式:通过一个初始化列表,来初始化array,如果初始化列表中的元素个数小于array的size,那么array中剩下的元素会被赋默认初始值。
array<int, 4> ai = { 1, 2, 3 };
简单提一下什么是默认初始值:一般在变量声明为全局变量的时候,都会被赋与默认初始值,比如在main函数外面声明了一个int x; x会被自动初始化为0,关于初始化机制,简单来说就是如果是数值类型,默认初始值为0,如果是字符串类型,初始值就是个空串,如果是指针应该就是一个nullptr等等之类的
初始化列表initializer_list
但是我在实现了文档中提到的两种构造函数形式之后,并不能使用上述代码那样的初始化列表
const size_t n = 10;
array<int,n> nums{ 1,2,3,};
//有报错:无法从“initializer list”转换为“ayaka::array<int,10>”
在查阅资料之后,才知道要写一个参数是initializer_list的构造函数
于是简要了解一下initializer_list,
#include <initializer_list>
template <class Type>
class initializer_list{
...
}
只接收一个模板参数,需要包含头文件<initializer_list>,同时不支持随机访问,支持使用迭代器顺序访问。因此可以先不管原理,直接把它当成一个顺序容器,用迭代器访问即可
初始化列表赋值实现
根据上述思路,写出了array的构造函数如下
array(const std::initializer_list<Type> &r);//声明
template <class Type, size_t N>
array<Type, N>::array(const std::initializer_list<Type>& r) {
size = N;
list = new Type[N];
auto it = r.begin();
for (size_t i = 0; i < r.size(); ++i) {
list[i] = *it++;
}
}
这样就使用了初始化列表给array赋与初始值,接下来测试一下效果
#include<iostream>
#include"array.h"
using std::cout;
using std::cin;
using std::endl;
using namespace ayaka;
int main() {
const size_t n = 10;
array<int,n> nums{ 1,2,3,};
for (int i = 0; i < n;++i){
cout << nums[i] << endl;
}
}
很显然,只有前三个被初始化了,后面全是奇怪的值。改用std库的array看看
#include<iostream>
#include<array>
using std::cout;
using std::cin;
using std::endl;
int main() {
const size_t n = 10;
std::array<int,n> nums{ 1,2,3,};
for (int i = 0; i < n;++i){
cout << nums[i] << endl;
}
}
然而标准库的array其余的值得到了正确的默认初始化的值。这里我又遇到了一个问题,当初始化列表的size小于array.size的时候,如何给剩下的值进行默认初始化
查阅资料之后回想起来:C++可以通过Type()来调用某个类型的对象的初始值,例如我们想获取int的初始值,可以这样写:
int x = int()
看上去类似于调用类的默认的构造函数
因此要解决上述问题就很简单了:稍微改造一下上述构造函数,剩余的值就调用Type()即可
template <class Type, size_t N>
array<Type, N>::array(const std::initializer_list<Type>& r) {
size = N;
list = new Type[N];
_begin = list;
_end = list + N;
auto it = r.begin();
for (size_t i = 0; i < r.size(); ++i) {
list[i] = *it++;
}
if (N > r.size()) {
for (size_t i = r.size(); i < N; i++) {
list[i] = Type();
}
}
}
总结
使用列表初始化是C++11的一个新特性,对于C++标准库中能够使用大括号赋初值的容器,基本上都是利用initializer_list实现的。在这次实现array的过程中,让我简单的理解了这一个特性背后的原理。比起分享某一个知识点,这篇文章的重点还是在于记录我个人学习中遇到的障碍,虽然写出来可能用不了多久,但解决一些bug有时候会花费大量的时间。作为一个菜鸡,写的文章水平也相当有限,因此如果有不对的地方,我斗胆请各位大佬对菜鸡新人写的文章的错误进行批评指正。还有其他不足的地方还请多多包涵