前段时间写了一个通过C++ array类简单了解C++容器初始化机制_Harris_ayaka的博客-CSDN博客,现在我整理一下C++ array中具体细节。
详情参考array 类(C++ 标准库) | Microsoft Docs
array类
首先是语法:
template <class Ty, std::size_t N>
class array;
接收两个模板参数,并且将数组的尺寸作为模板参数之一,因此两个不同大小但元素类型相同的的array将被视为不同的类型,例如:
array<int,5> a1;
array<int,10> a2;
将被视为不同的类型。
<array>成员
0、private成员
这是作为array容器的基础,一个是指向数组首地址的指针,一个是数组的size
template <class Type, size_t N>
class array {
private:
Type* list;
size_t size;
.....
}
1、构造函数
visual studio 2022的文档有两个构造函数:
即是正常的构造函数与复制构造函数
array();
array(const array& right);
但参考我的上一篇通过C++ array类简单了解C++容器初始化机制_Harris_ayaka的博客-CSDN博客
我还加上了一个带参的构造函数:
array(const std::initializer_list<Type> &r);
用于使用大括号初始化array对象:
array<int,n> nums{ 1,2,3,};
//用这样的形式初始化必须重载一个参数带initializer_list的函数
下面介绍三种构造函数的具体实现细节:
首先是默认构造函数,不带任何参数
template <class Type, size_t N>
array<Type, N>::array() {
size = N;
list = new Type[N];
_begin = list;
_end = list + N;
}
在这里,我有一个还未解决的问题:
众所周知,如果一个变量声明在主函数外面,那么他是一个全局变量,并且这个变量会被自动初始化,代码如下
#include<iostream>
#include<array>
using namespace std;
array<int, 10> a;
int main() {
for (int i = 0; i < 10; ++i) {
cout << a[i] << endl;
}
}
输出结果如图:
但是我在使用自定义的array类的时候,声明全局变量,并不会被初始化
#include"array.h"
#include<iostream>
using std::cout;
using std::cin;
using std::endl;
using namespace ayaka;
array<int, 10> a;
int main() {
for (int i = 0; i < 10; ++i) {
cout << a[i] << endl;
}
}
结果如下,可以看到全部都是奇怪的值。希望这个问题有大佬能够帮我解答一下: )
接下来是复制构造函数:
复制构造函数不能采用简单的把另一个变量赋值给该类,详情可以去自行了解一下深复制与浅复制机制
template <class Type, size_t N>
array<Type, N> array<Type, N>::operator=(const array<Type, N>& right) {
size = N;
list = new Type[N];
_begin = list;
_end = list + N;//迭代器要用的组件,后文会详细介绍
for (int i = 0; i < size; ++i) {
list[i] = right.list[i];
}
}
最后是能够使用初始化列表的构造函数:
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();
}
}
}
2、析构函数
众所周知,析构函数的作用是销毁对象,我们先来看看如果不写析构函数,调用默认的析构函数效果如何
#include"array.h"
#include<iostream>
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,};
array<int, 10> a(nums);
a.~array<int, n>();
for (int i = 0; i < 10; ++i) {
cout << a[i] << endl;
}
}
运行结果发现这个析构函数就是个摆设,运行结果如下:
因此有必要自己动手重写一些析构函数:
~array() {
delete[]list;
size = 0;
_begin = nullptr;
_end = nullptr;//释放数组占用的空间,并且将size归0
}
3、迭代器
C++继承了C语言的指针,在array类中,指针和迭代器可以看做是同一个东西,因此也不用去重载++,--,+,-等运算符,C指针本身就支持这些运算
但为了和保证接口相同即使用iterator来调用迭代器,因此要使用typedef,具体方法如下:
public:
typedef Type* iterator;
iterator begin();
iterator end();
private:
iterator _end;
iterator _begin;
在外部使用:
array<int, 5>::iterator it;
begin()与end()
template <class Type, size_t N>
Type* array<Type, N>::begin() {
return _begin;
}
template <class Type, size_t N>
Type* array<Type, N>::end() {
return _end;
}
//_begin是数组的首地址,_end是数组的尾地址
成员函数:
1、at()
template <class Type, size_t N>
Type& array<Type, N>::at(size_t off) {//不检查下标
return*(list + off);
}
template <class Type, size_t N>
const Type& array<Type, N>::at(size_t off) const {
return*(list + off);
}
2、assign()//C++ 11中已经过时,用fill替代,于是我就不写这个了
3、fill()
void fill(const Type& val);
template <class Type, size_t N>
void fill(const Type& val) {
for (int i = 0; i < size; ++i) {
list[i] = val;
}
}
至于其他的
back():返回最后一个元素
front():返回第一个元素
empty():返回容器是否为空等等,感觉没有什么必要写
运算符重载
最重要的是"[]"和"="两个运算符,至于其他运算符尤其是比较运算符,我个人感觉实用性不强,而且实现原理简单所以没必要写。
1、[]运算符
//定义
Type& operator[](size_t i);
const Type& operator[](size_t i)const;
//实现直接利用上文提到的at函数,实现起来比较简单,又非常好用
template <class Type, size_t N>
const Type& array<Type, N>::operator[](size_t i)const {
return at(i);
}
template <class Type, size_t N>
Type& array<Type, N>::operator[](size_t i) {
return at(i);
}
2、=运算符
template <class Type, size_t N>
array<Type, N> array<Type, N>::operator=(const array<Type, N>& right) {
for (int i = 0; i < size; ++i) {
list[i] = right.list[i];
}
return *this
}
测试一下:
#include"array.h"
#include<iostream>
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, };
array<int, 10> a;
a = nums;
for (int i = 0; i < 10; ++i) {
cout << a[i] << endl;
}
}
运行结果:
得到了正确的结果
非成员函数
1、swap()
交换两个数组的首地址,并且迭代器也需要重新赋值
template <class Type, size_t N>//声明
friend void swap(array<Type, N>& left, array<Type, N>& right);
//定义
template <class Type, size_t N>
void swap(array<Type, N>& left, array<Type, N>& right) {
Type* p = left.list;
left.list = right.list;
right.list = p;
left._begin = left.list;
right._begin = right.list;
left._end = left.list + N;
right._end = right.list + N;
}
测试一下
#include"array.h"
#include<iostream>
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, };
array<int, n> a;
a.fill(114514);
swap(a, nums);
for (int i = 0; i < n; i++) {
cout << a[i] << endl;
}
}
运行结果如下:
得到了正确的结果
2、get()
template <int i, class Type, size_t N>
Type& get(array<Type, N>& arr) {
return arr[i];
}
template <int i, class Type, size_t N>
constexpr const Type& get(const array<Type, N>& arr)noexcept {
return arr[i];
}
template <int i, class Type, size_t N>
constexpr Type&& get(const array<Type, N>& arr) noexcept {
return arr[i];
}
使用方式如下:
#include"array.h"
#include<iostream>
#include<array>
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, };
array<int, 10> a;
a = nums;
int c = get<5>(a);
cout << c << endl;
}
其中get<x>中的x必须是个常量。
总结:
这次通过手动实现array,了解了一些静态数组的机制,后面应该还会写一个在为array定义迭代器的过程中遇到的C++范围for循环的机制。辣鸡程序员,如有问题烦请各位大佬批评指正。
————————————————
版权声明:本文为CSDN博主「Harris_ayaka」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/Harris_ayaka/article/details/125101566