C++多维数组

0

本文涉及:

  • 多维数组
  • constexpr关键字
  • size_t关键字
  • 基于范围的for循环
  • 标准库begin()、end()函数

C++语言并没有多维数组,而我们常说的多维数组,准确的说是数组的数组。

1
2
int ia[3][4];
int arr[3][4][5] = {0}; //将所有的元素初始化为0

对于复杂的表达式,更好的理解方式是从里向外的理解,int ia[3][4]声明了一个名为ia的有3个元素的数组,元素是含有4个int型元素的数组。有点绕,分解一下理解,ia是一个数组,里面的元素是整形数组。arr是有3个元素的数组,这3个元素是有4个元素的数组,这个数组的元素是有5个元素的整形数组,即说明arr是60个int型对象的集合。

基于下标访问多维数组

可以使用下标运算符访问多维数组的元素,不同维度的下标,代表着不同数组。

当下标的维度等于数组的维度,访问的对象就是给定类型的对象;下表维度低于数组维度时,访问对象就是对应维度索引指向的内层数组。

1
2
ia[2][3] = arr[0][0][0];
int (&ra)[5] = arr[2][3];

在第一个例子中,对于用到的两个数组来说,表达式提供的下标运算符数量都和它们各自的维度相同。在等号左侧,ia[2]得到数组ia的最后一行,此时返回的是表示ia最后一行的那个一维数组而非任何实际元素;对这个一维数组再取下标,得到编号为[3]的元素,也就是这一 行的最后一个元素。类似的,等号右侧的运算对象包含3个维度。首先通过索引0得到最外层的数组,它是一个大小为3的(多维)数组;接着获取这4个元素数组的第一个元素,得到一个大小为4的一维数组;最后再取出其中的第一个元素。

在第二个例子中,把row定义成一个含有5个整数的数组的引用,然后将其绑定到arr的最外层数组第3行元素的第4个对象上。

对于数组的遍历,更多的用到循环语句,多维数组的遍历通常使用for循环嵌套访问。

1
2
3
4
5
6
7
8
9
constexpr size_t rCnt = 3,cCnt = 4;	//使用了constexpr和size_t关键字

//test_arrary
int a[rCnt][cCnt] = {0};

//test01
for(size_t i = 0;i != rCnt;i++)
for(size_t j = 0;j != cCnt;j++)
a[i][j] = i * cCnt + j;

size_t是与机器相关的无符号整数,设计的足够大,用来表示数组的下标。

constexpr关键字表明,声明的对象是一个常量,在编译阶段就能得到值。

使用基于范围的for循环

C++11新标准引入了基于范围的for循环,配合auto关键字,可以不必在意多维数组的类型,通过对象引用遍历元素。

1
2
3
4
5
6
7
//test02
//cout << "test02" << endl;
for(const auto &row : a){
for(const auto &col : row)
cout << col <<' ';
cout << endl;
}

使用基于范围的for循环,使用对象的引用,是有着多重考虑的。如果这样定义for(auto row : a)循环体,这是row会被编译器推断为指针类型int *,此时访问的是大小为4的数组。因为row不是引用类型,编译器无法在int *内部遍历,auto col : row也就不合法了。

值得注意的是,使用基于范围的for循环处理多维数组是,除了最内层的循环,其他循环的控制变量应该均为引用类型。

1
2
3
4
5
for(auto &row : a){
for(auto col : row)
cout << col <<' ';
cout << endl;
}

上述表达也是同样的效果,对于不改变对象状态的参数传递或者控制变量,使用const关键字限制,是一个好习惯,即const auto &row : a

指针和多维数组

使用数组名时,会自动的转换为一个指向数组首元素的指针,多维数组也是这样。理解多维数组时,要更注意一点,多维数组是数组的数组。

1
2
3
int ia[3][4];
int (*p)[4] = ia;
p = &ia[2];

C++11新标准的引入auto关键字,编译器可以根据表达式推断出返回的类型。

1
2
3
4
5
6
7
8
//test03
//cout << "test03" << endl;
for(int (*p)[cCnt] = a;p != a+rCnt;p++){
for(int *q = *p;q != *p+cCnt;q++){
cout << *q << " ";
}
cout << endl;
}

使用了auto关键字,可以不写明指针类型,编译器根据数组名推断出类型。

1
2
3
4
5
6
7
8
//test04
//cout << "test04" << endl;
for(auto p = a;p != a+rCnt;p++){
for(auto q = *p;q != *p+cCnt;q++){
cout << *q << " ";
}
cout << endl;
}

当然,使用标准库提供的begin()、end()函数的话,上述代码可以更简洁。

1
2
3
4
5
6
7
8
//test05
//cout << "test05" << endl;
for(auto i = begin(a);i != end(a);i++){
for(auto j = begin(*i); j != end(*i);j++){
cout << *j << ' ';
}
cout << endl;
}

begin()函数返回指向数组名首元素的指针;end()函数返回指向尾元素下一位置的指针,这个位置可以作为哨兵节点,通过判断当前位置和哨兵节点位置,来控制循环结束条件。这两个函数定义在iterator头文件。

还要说明的是,上说例子中,循环结束的条件都是与哨兵节点比较(准确说是比对或判等,而比较倾向于大小比较),而不是使用<=。这是因为,STL中的容器实现时,采用的是泛型编程的思想,构建都是类模板,实例化之前,并不能确定所有对象类型都支持大小比较。这里与标准库实现统一,都是使用比对!=的方式,控制循环。

-------------本文结束感谢您的阅读-------------