天天看点

《C++primer学习笔记》——第3章 字符串、向量、数组一、using[P74]二、string[P75]二、vector[P86]四、迭代器[P95]五、数组[P101]六、多维数组[P112]

一、using[P74]

1. 概念

       使用using声明,可以使得调用函数时候无需声明命名空间(如:std::cout 可直接写cout)

2.使用方法

              using namespace::name

//;
using std::cin;
int main()
{	int i;
	cin<<i;
}
           

       若想调用一整个库则使用:

              using namespace xxx

T I P 1 \color{#FF0000}{TIP1} TIP1:头文件不应该包含using声明

头文件的内容会拷贝到所有引用它的文件中去,如果这些头文件里有某个using声明,那么每个使用了该头文件的文件就会有这个声明。

二、string[P75]

1.准备工作

需要引入string:

#include<string.h>
using std::string;
           

2.初始化

string s1;//默认初始化,s1是一个空串
string s2(s1);//s2是s1的副本
string s3("value");//注意\0结尾
string s3 = "value";//与上面等价
string s4(n,'c');//初始化为连续的n个字符c
           

3.string对象上的操作

操作汇总:

os<<s
is>>s
getline(is,s);//从is中读取字符串赋给s
s.empty();
s.size()
s[n]
s1+s2
s1=s2//赋值操作
s1==s2;//判等
s1<s2;//字典序排列
           

e x a m p l e 1 \color{#0000FF}{example1} example1:每次读入一整行,遇到空格跳过

while(getline(cin,line))
	if(!line.empty())//可以写任何逻辑,如if(line.size()>80)
		cout<<line<<endl;
           

T I P 1 \color{#FF0000}{TIP1} TIP1: 因为cout遇到空白停止读入string

string s1,s2;
cin>>s1;//输入hello word
cout<<s1<<endl;//输出hello

           

若想争取读入,可改为:

string s1,s2;
cin>>s1>>s2;
cout<<s1<<s2<<endl;//和c语言读入读出异曲同工
           

T I P 2 \color{#FF0000}{TIP2} TIP2: s.size()的返回类型

       s.size()函数返回的类型是string::size_type,并不是int或者unsigned。这体现了标准库类型与机器无关的特性。但我们可以推测他一定是一个无符号整数,所以 一 定 不 要 在 表 达 式 s . s i z e ( ) 中 混 用 整 形 变 量 \color{#FF7D00}{一定不要在表达式s.size()中混用整形变量} 一定不要在表达式s.size()中混用整形变量,如:s.size()<n。因为整数会自动转为无符号数,若不甚将n置为负数,则判断的结果几乎肯定是true。

4.比较string对象

比较string采用字典序便不必多述,要注意的是字面值和string对象相加

string s1 = "hello",s2 = "world";
string s2 = s1+s2;//正确
string s3 = "hello" + ",";//错误,不能把字面值直接相加
string s4 = "hello"+s2;//正确,一个string对象和一个字面值可以相加
string s5 = "hello"+","+s2;//错误等价于("hello"+",")+s2,同s3
string s6 = s1+","+"world";//正确(s1+",")+"world"
           

5.处理string对象中的字符

       这一节主要解决改变串中某一个字符的特性,在cctype头文件中定义了一组标准库函数:

函数名 解析
isalnum( c ) 当c是字母或数字时为真
isalpha( c ) 当c是字母时为真
iscntrl( c) 当c是控制字符时为真
isdigit( c) 当c是数字时为真
isspace( c ) 当c是空白时为真
islower( c) 当c是小写字母时为真
isuper( c) 当c是大写字母时为真
tolower( c) 若是大写字母转小写
touper( c) 若是小写字母转大写

T I P 1 \color{#FF0000}{TIP1} TIP1:使用基于范围的for语句处理每个字符

string str("hello world");
for(auto c:str)//和python的 for _ in str:一样
	cout<<c<<endl;
           

如 果 我 们 要 改 变 s t r i n g 对 象 中 的 字 符 的 值 , 必 须 把 循 环 定 义 成 引 用 类 型 \color{#FF7D00}{如果我们要改变string对象中的字符的值,必须把循环定义成引用类型} 如果我们要改变string对象中的字符的值,必须把循环定义成引用类型所谓引用只是给定对象的一个别名,所以当使用引用作为循环控制变量时,这个变量实际上被以此绑定到了序列的每一个元素上,使用这个引用我们就可以改变字符。

e x a m p l e 1 \color{#0000FF}{example1} example1:统计一个串中的标点符号字数

#include<stdio.h>
#include<string.h>
#include<cctype>
using namespace std;

string s("Hello World");
decltype(s.size()) punct_cnt = 0;
for(auto c:s)
	if(ispunct(c))
		++punct_cnt;
cout<<punct_cnt<<endl;
           

       书上的这个代码写的还是非常漂亮的,decltype的写法也非常严谨,如果是我可能直接就写int了…

e x a m p l e 2 \color{#0000FF}{example2} example2:将串转换为大写形式

string s("Hello world!");
for(auto &c:s)//注意,使用的是引用
 c = toupper(c);
 cout<<s<<endl;
           

如 果 只 想 处 理 一 部 分 字 符 , 使 用 下 表 运 算 符 ( [ ] ) \color{#FF7D00}{如果只想处理一部分字符,使用下表运算符([ ])} 如果只想处理一部分字符,使用下表运算符([])

e x a m p l e 3 \color{#0000FF}{example3} example3:第一个单词改成大写

string s("hello world");
for (decltype(s.size()) index = 0 ; index != s.size() && !isspace(s[index]);++index)
	s[index] = toupper(s[index]);
           

二、vector[P86]

1.准备工作

需要引入vector:

#include<vector>
using std::vector;
           

v e c t o r 是 一 个 类 模 板 。 模 板 本 身 不 是 类 或 函 数 , 相 反 可 以 将 模 板 看 作 为 编 译 器 生 成 类 或 函 数 编 写 的 一 份 说 明 \color{#FF7D00}{vector是一个类模板。模板本身不是类或函数,相反可以将模板看作为编译器生成类或函数编写的一份说明} vector是一个类模板。模板本身不是类或函数,相反可以将模板看作为编译器生成类或函数编写的一份说明

编 译 器 根 据 模 板 创 建 类 或 函 数 的 过 程 称 为 实 例 化 ( i n s t a n t i a t i o n ) , 使 用 模 板 时 , 需 指 出 编 译 器 应 把 类 实 例 化 成 何 种 类 型 \color{#FF7D00}{编译器根据模板创建类或函数的过程称为实例化(instantiation),使用模板时,需指出编译器应把类实例化成何种类型} 编译器根据模板创建类或函数的过程称为实例化(instantiation),使用模板时,需指出编译器应把类实例化成何种类型

对 于 类 模 板 来 说 , 我 们 需 要 通 过 一 些 额 外 信 息 制 定 是 何 种 模 板 , 也 就 是 在 模 板 后 加 一 对 尖 括 号 。 ( < > ) \color{#FF7D00}{对于类模板来说,我们需要通过一些额外信息制定是何种模板,也就是在模板后加一对尖括号。(<>)} 对于类模板来说,我们需要通过一些额外信息制定是何种模板,也就是在模板后加一对尖括号。(<>)

e x a m p l e 1 \color{#0000FF}{example1} example1:初始化

vector<int> ivec;
vector<Sales_item> Sales_vec;//保存Sales_item类型的对象
vector<vector<string>> file;//改向量的元素是vector对象
           

2.初始化

vector<T> v1;
vector<T> v2(v1);//将v2初始化为v1的副本
vector<T> v2 = v1;//等价于上条
vector<T> v3(n,val);//v3包含n个重复元素,每个元素的值都是val
vector<T> v4={a,b,c,...};
vector<T> v5{a,b,c,...};//和上面等价

vector<string> v1{"abv","asd","qwer"};
vector<string> v1{"abv"};
vector<string> v1 ("abc");//×错误,不能用字符串字面值构建vector对象
vector<int > invc(10);//10个元素,每个初始化为0
vector<int > v2{10};//1个元素,元素值是10
vector<int > invc(10,1);//10个元素,每个初始化为1
vector<int > v2{10,1};//2个元素,元素值是10和1
vector
           

如果vector对象的元素是内置类型(如int),则元素初始值自动设为0,如果元素是某种类类型,如string,则元素由类默认初始化。

3.vector对象上的操作

e x a m p l e 1 \color{#0000FF}{example1} example1:添加int元素

vector<int > v2;
for (in i =0;i!=100;++i)
	v2.	pushi_back(i);//一次把i放到v2尾端
           

e x a m p l e 2 \color{#0000FF}{example2} example2:添加string元素

string word;
vector<string > text;
while(cin>>word)
	text.push_back(word);
           
函数名 解析
v.empty() 如果v不含任何元素则返回真,否则返回假
v.size() 返回v中元素的个数
v.push_back(t) 向v的尾端添加一个值为t的元素
v[n] 返回v中第n个位置上元素的引用
v1=v2 copy
v1==v2 v1等于v2当且仅当他们的元素数量相同且对应位置元素值都相同
v1<v2 字典序比较

e x a m p l e 3 \color{#0000FF}{example3} example3:将vector中的元素依次平方

vector<int> v{1,2,3,4,5,6,7,8,9};
for (auto &i : v)
	i*=i;
for(auto i:v)
	cout<<i<<" ";
cout<<endl;
           

        第一个循环把控制变量 i 定义为引用型,这样就可以通过i给v的元素赋值,其中i的类型由auto关键字指定,第二个循环输出所有元素。

T I P 1 \color{#FF0000}{TIP1} TIP1:vector的size和empty与string是同名成员,功能完全一致。同样的size()的返回类型是size_type要包含元素类型:

vector::size_type;√

vector::size_type;×

e x a m p l e 4 \color{#0000FF}{example4} example4:将vector中的元素依次平方

vector<int> v{1,2,3,4,5,6,7,8,9};
for (auto &i : v)
	i*=i;
for(auto i:v)
	cout<<i<<" ";
cout<<endl;
           

T I P 2 \color{#FF0000}{TIP2} TIP2:正常情况下可使用**v[3]、v[0]**等下标形式访问数组,但是不能用下标形式添加元素。

如下:

e x a m p l e 5 \color{#0000FF}{example5} example5:只能对确知已存在的元素执行下标操作

vector<int > ivec;
for(decltype(ivec.size() ix=0;ix!=10;ix++)
	ivec[ix] = ix;
这是严重的错误,因为ivec初始不包含任何元素
           

正确赋值方法如下

vector<int > ivec;
for(decltype(ivec.size() ix=0;ix!=10;ix++)
	ivec.push_back(ix);
           

总 之 就 是 v e c t o r 对 象 ( 以 及 s t r i n g 对 象 ) 的 下 标 云 散 可 用 于 访 问 已 存 在 的 元 素 , 不 能 用 于 添 加 元 素 。 \color{#FF7D00}{总之就是vector对象(以及string对象)的下标云散可用于访问已存在的元素,不能用于添加元素。} 总之就是vector对象(以及string对象)的下标云散可用于访问已存在的元素,不能用于添加元素。这就是所谓的Buffer overflow(缓冲区溢出),确保下标合法的一种有效手段是尽可能使用范围for语句。

四、迭代器[P95]

      除了可以使用下标运算访问string和vector对象的元素,还有另外一种更为通用的机制也可以实现同样的目的:迭代器(iterator)。所有标准库容器都可以使用迭代器(vector是一种,后续还会介绍),但其中只有少数几种才同时支持下标运算符,严格的说string对象不属于容器类型,但是string支持很多与容器类型类似的操作。

1.使用迭代器

      类似于指针,迭代器也提供了对对象的间接访问,和指针不一样的是,获取迭代器并不是使用取地址符,有迭代器的类型同时拥有返回迭代器的成员。例如这些类型都拥有begin和end的成员。

auto b = v.begin(),e = v.end();
           

其 中 b e g i n 负 责 返 回 指 向 第 一 个 元 素 的 迭 代 器 。 \color{#FF7D00}{其中begin负责返回指向第一个元素的迭代器。} 其中begin负责返回指向第一个元素的迭代器。

其 中 e n d 负 责 返 回 容 器 尾 元 素 的 下 一 个 位 置 的 迭 代 器 。 \color{#FF7D00}{其中end负责返回容器尾元素的下一个位置的迭代器。} 其中end负责返回容器尾元素的下一个位置的迭代器。

也就是说该迭代器指示的是容器的一个本不存在的尾后元素,仅仅是个标记。en常常被称作尾后迭代器(off-the-end iterator) 或者尾迭代器。若容器为空,begin和end返回同一个迭代器。

运算符 解析
*it 返回迭代器iter所指的元素的引用
iter->mem 解引用iter并获取该元素的名为mem的成员,等价于(*iter).num
++iter 令iter指示容器的下一个元素
–iter 令iter指示容器的上一个元素
iter1==iter2 判断两个迭代器是否相等

e x a m p l e 1 \color{#0000FF}{example1} example1:将字符改写为大写形式

for(auto it = s.begin();it!=s.end()&&isspace(*it);++it)
*it = toupper(*it);
           

T I P 1 \color{#FF0000}{TIP1} TIP1:泛型编程:对于使用C或者JAVA的程序员会对for循环中使用!=而非<有些奇怪,C++程序员习惯地使用!=,原因和更愿意使用迭代器而非下标的原因一样:因为这种编程风格在标准库提供的所有容器都有效。

T I P 2 \color{#FF0000}{TIP2} TIP2:迭代器类型:就像不知道string和vector的size_type成员到底是什么样的类型一样,一般来说我们也不知道(也无需知道)迭代器的精确类型。而实际上,那些拥有迭代器的标准库类型使用iterator和const_iterator来表示迭代器的类型 其 中 c o n s t _ i t e r a t o r 为 只 读 迭 代 器 类 型 , i t e r a t o r 可 读 可 写 \color{#FF7D00}{其中const\_iterator为只读迭代器类型,iterator可读可写} 其中const_iterator为只读迭代器类型,iterator可读可写

vector<int>::iterator it;//it1能读写vector<int>的元素
string::iterator it2;//it2能读写string类型的字符
vector<int>::const_iterator it3;//it3只能读元素不能写元素
string::const_iterator it4;//it4只能读字符不能写字符
           

const_iterator有些像常量指针。如果vector对象或string对象是一个常量,只能使用const_iterator。

e x a m p l e 2 \color{#0000FF}{example2} example2:依次输出text的每一行直到遇到第一个空白行为止

for(auto it = text.cbegin();it!=text.cend()&&!it->empty();++it)
	cout<<*it<<endl;
           

其中it->mem等价于(*it).mem。

T I P 3 \color{#FF0000}{TIP3} TIP3:某些对vector对象的操作会使迭代器失效:vector虽然可以动态地增长,但是也会有一些副作用。已知的一个限制是不能在范围for循环中向vector对象添加元素,另外push_back也会使得vector对象的迭代器失效(第9章会详细阐述)所以, 但 凡 是 使 用 了 迭 代 器 的 循 环 体 , 都 不 要 向 迭 代 器 所 属 的 容 器 添 加 元 素 \color{#FF7D00}{但凡是使用了迭代器的循环体,都不要向迭代器所属的容器添加元素} 但凡是使用了迭代器的循环体,都不要向迭代器所属的容器添加元素

e x a m p l e 3 \color{#0000FF}{example3} example3:迭代器运算,二分搜索

auto beg = text.begin(),end = text.end();
auto mid = text.begin()+(end-begin)/2;
while(mid!=end&&*mid!=sought)
	if(sought<*mid)
		end = mid;
	else 
		beg = mid+1;
	mid = beg+(end-beg)/2;
           

五、数组[P101]

      数组的大小确定不变,不能随意向数组中添加元素。因为数组的大小固定,因此对某些特殊的应用来说程序的运行时性能较好,但是相应地也损失了一些灵活性。

1.定义和初始化内置数组

unsigned int cnt = 42;
string bad[cnt];//错误,cnt不是常亮表达式
constexpr unsigned sz = 42;//常量表达式
int arr[sz];//正确
string strs[get_size()];//当get_size是 constexpr时正确;否则错误
           

定义数组的时候必须指定数组的类型,不允许使用auto关键字。

数组不允许拷贝和赋值,如下操作

char a1[ ] = {'C','+','+'};
char a2[ ] =a;//错误,不允许使用一个数组初始化另外一个数组
a2 = a;//错误;不能把一个数组直接赋值给另外一个数组
           

T I P 1 \color{#FF0000}{TIP1} TIP1:一些编译器支持数组的赋值,这就是所谓的编译器拓展(compiler extension),但一般来说最好避免使用非标准特性,因为含有非标准特性的程序很可能在其他编译器上无法正常工作。

下方展示了一些复杂的数组声明:

int *ptrs[10];//ptrs是含有10个整形指针的数组
int &refs[10] = /*?*/;//错误,不存在引用的数组
int (*Parray)[10] = &arr;//Parray指向一个含有10个整数的数组
int (&arrRef)[10] = arr;//arrRef引用一个含有是个整数的数组
int *(&arry)[10] = ptrs;//arry是数组的引用,该数组含有10个指针
           

默 认 情 况 下 , 类 型 修 饰 符 从 右 向 左 依 次 绑 定 。 \color{#FF7D00}{默认情况下,类型修饰符从右向左依次绑定。} 默认情况下,类型修饰符从右向左依次绑定。

对于ptrs来说,从右向左首先我们定义的是一个大小为10的数组,他的名字的ptrs,然后知道数组中存放的是指向int型的指针。但是对于Parray来说由于存在括号,因为数组的唯独是紧跟着被声明的名字的,我们应当由内向外阅读。首先是圆括号括起来的部分,*Parray意味着Parray是个指针,接下来观察右边,可知道Parray是个指向大小为10的数组的指针,最后观察左边,知道数组中的元素是int。这样就知道*Parray是一个指针,它指向一个int数组,数组包含10个元素。同理,(&arrRef)表示arrRef是一个引用,他引用的对象是大小为10的数组,数组中的元素类型是int。

2.访问数组的元素

      在使用数组下标的时候,通常将其定义为size_t类型。size_t是一种机器相关的无符号类型,它被设计得足够大以便能够表示内存中任意对象的大小。在cstddef头文件中定义了size_t类型,这个文件是C标准库stddef.h头文件的C++语言版本。

3.指针与数组

使用数组的时候编译器一般会将它转换成指针。

string nums[] = {"one","two","three"};//数组的元素是string对象
string *p = &nums[0];//p指向nums的第一个元素
           

数组还有另一个特性:在很多用到数组名字的地方,编译器都会自动地将其替换为一个指向数组首元素的指针

这就意味着当使用数组作为一个auto变量的初始值时,推断得到的类型是指针而非数组:

int ia[]={0,1,2,3,4};
auto ia2(ia);//ia2是一个整形指针,指向ia的第一个元素
ia2 = 42;//错误:ia2是一个指针,不能用int值给指针赋值
           

必须指出的是当使用decltype关键字时,上述转换不会发生,decltype(ia)返回的类型是由一个10个整数构成的数组:

//ia3是一个含有10个整数的数组
decltype(ia) ia3={2,3,4,5,6};
ia3[4] = 1;//正确:把i的值赋值给ia3的一个元素
           

指针也是迭代器

就像使用迭代器遍历vector对象中的元素一样,使用指针也能遍历数组中的元素。当然,这样做的提前是先得获取到指向数组第一个元素的指针和指向数组尾元素的下一位置的指针。通过数组名字或数组中的首元素的地址都能得到指向首元素的指针,不过获取尾后指针就要用到数组的另外一个特殊的性质了我们可以设法获取数组尾元素之和的那个并不存在的地址:

int *end = &arr[10];//指向arr尾元素的下一个位置的指针
for(int *b=arr;b!e;++b)
	cout<<*b<<endl;//输出arr元素
           

T I P 2 \color{#FF0000}{TIP2} TIP2:尽管能计算得到尾后指针,但是这种用法极易出错。为了让指针的使用更简单、更安全。C++11新标准引入了两个名为begin和end和函数。这两个函数与容器中的两个成员功能相似,但是数组毕竟不是类类型,因此这两个函数不是成员函数。正确的使用形式是将数组作为它们的参数:

int ia[] = {0,1,2,3,4,5,6,7,8,9};
int *beg = begin(ia);//指向ia首元素的指针
int *last = end(ia);//指向arr尾元素的下一个位置的指针
           

这两个函数的定义在iterator文件中。

指针运算

和迭代器一样,两个指针相减的结果是他们之间的距离。参与运算的两个指针必须指向同一个数组的元素:

两个指针相减的结果的类型是一种名为ptrdiff_t的标准库类型,和size_t一样,ptrdiff_t也是一种定义在cstddef头文件中机器相关的类型。因为差值可能为负值,所以ptrdiff_t是一种带符号类型。

解引用和指针运算的交互

int ia[] = {0,2,4,6,8};
int last = *(ia + 4);//正确:把last初始化成8,也就是ia[4]的值
           

只要指针指向的数组中的元素,都可以执行下标运算:

int *p = &ia[2];
int j = p[1];//等价于p[3]
int k = p[-2];
           

内 置 的 下 标 运 算 符 所 用 的 索 引 值 不 是 无 符 号 类 型 , 可 以 是 负 数 , 这 一 点 与 v e c t o r 和 s t r i n g 不 同 \color{#FF7D00}{内置的下标运算符所用的索引值不是无符号类型,可以是负数,这一点与vector和string不同} 内置的下标运算符所用的索引值不是无符号类型,可以是负数,这一点与vector和string不同

4.C风格字符串

尽管C++支持C风格字符串,但是C++程序中最好还是不要想着它们。这是因为C风格字符串不仅使用起来不太方便,而且极易引发程序漏洞,是诸多安全问题的根本原因 C风格字符串不是一种类型,而是为了表达和使用字符串而形成的一种约定俗称的写法。按此习惯书写的字符串存放在字符串数组中并以空字符结束(null terminated)。以控制符结束的意思是在字符串最后一个字符后面跟着一个空字符(‘\0’)。一般利用指针来操作这些字符串。

C标准库String 函数

下表C语言标准库提供了一组函数,这些函数可用于操作C风格字符串,他们定义在cstring头文件中。

函数 解析
strlen( p ) 返回p的长度,空字符不计算在内
strcmp(p1,p2) 比较p1和p2的相等性。如果p1==p2返回0;如果p1>p2,返回一个正值;如果p1<p2返回一个负值
strcat(p1,p2) 将p2附加到p1之后,返回p1
strcpy(p1,p2) 将p2拷贝给你p1,返回p1
char ca[] = {'C','+','+'};//不以\0结束
cout<<strlen(ca)<<endl;//严重错误:ca没有以空字符结束
           

比较字符串

如果是string类可以使用 s1<s2

如果是const char ca1[ ] = “A string example”

必须使用strcmp(c1,c2)

如果是两个string对象连接我们可以使用

string largeStr = s1+" "+s2;

但如果是C语言需要使用strcat,与此同时还要保证largeStr拥有足够大的空间容纳的下字符串及末尾的空字符,虽然下面的代码很常见,但是充满了安全风险,极易引发严重错误:

strcpy(largeStr,cal);
strcat(largeStr," ");
strcat(largeStr,ca2);
           

T I P 3 \color{#FF0000}{TIP3} TIP3:对于大多数应用来说,使用标准库string要比使用C风格字符串更安全、更高效。

与旧代码的接口

由于很多C++程序在标准库出现之前就已经写成了,他们肯定没有用到string和vector类型。而且有一些C++程序实际上的与C程序或者其他语言的接口程序,当然也无法使用C++标准可。因此C++不得不语那些充满数组和C风格字符串的代码衔接,为了使这一工作简单易行,C++专门提供了一组功能。

混用string对象和C风格字符串

允许使用以空字符串结束的字符数组来初始化string对象或为string对象赋值。

在string对象的加法运算中允许以空字符结束的字符串数组作为其中一个运算对象(不能两个都是);在string对象的复合赋值运算中允许使用以空字符结束的字符数组作为右侧的运算对象。

反之则不成立,如:无法用一个string对象直接初始化指向字符的指针如:

使用数组初始化vector对象

int int_arr[]={0,1,2,3,4,5};
vector<int> ivec(begin(int_arr),end(int_arr));//拷贝全部
vector<int> subVec(int_arr+1,int_arr+4)//拷贝部分
           

T I P 4 \color{#FF0000}{TIP4} TIP4:建议使用标准库类型而非数组,应当尽量避免使用vector和迭代器,避免使用内置数组和指针;应该尽量使用string,避免使用C风格的基于数组的字符串。

六、多维数组[P112]

严格来说,C++语言中没有多维数组,通常所说的多维数组其实是数组的数组。谨记这一点,对今后的理解和使用多维数组大有益处。

int ia[3][4];//大小为3的数组,每个元素是含有4个整数的数组
int arr[10][20][30]={0};//大小为10的数组,它的每个元素都是大小为20的数组,这些数组的元素是含有30个整数的数组
           

对于二维数组,我们通常把第一个维度称作行,第二个维度称作列。

多维数组的初始化

允许使用花括号括起来的一组值初始化多维数组,如下:

int ia[3][4] = {
{0,1,2,3},
{4,5,6,7},
{8,9,10,11}
};
or
int ia[3][4]={0,1,2,3,4,5,6,7,8,9,10,11};

int ia[3][4]={{0},{4},{8}};//显式的初始化每行的首元素
           

多维数组的下标引用

ia[2][3] = arr[0][0][0];
int (&row)[4] = ia[1];//把row绑定到ia第2个4元素数组上
           

使用范围for语句处理多维数组

size_t cnt = 0;
for(auto &row : ia)
	for(auto &col :row)
	{
		col = cnt;
		cnt++;
	}
           

T I P 5 \color{#FF0000}{TIP5} TIP5:注意多维数组最好写引用,防止被auto转为指针

指针和多维数组

定义指向多维数组的指针时,千万别忘了这个多维数组实际上是数组的数组。

int ia[3][4];//大小为3的数组,每个元素是含有4个整数的数组
int (*p)[4] = ia;//p指向含有4个整数的数组,圆括号必不可少
p = &ia[2];//p指向ia的尾元素
/*****************************************/
           

随着C++11新标准的提出,通过使用auto或者decltype就能尽量避免在数组前面加上一个指针类型了

e x a m p l e 1 \color{#0000FF}{example1} example1:输出ia每个元素的值,每个内层各占一行

//p指向含有4个整数的数组
for(auto p = ia;p!=ia+3;++p){
	for(auto q = *p;q!=*p+4;++q)
		cout<<*q<<" ";
	cout<<endl;}
           

外层的for首先声明p指向ia的第一个内层数组,然后依次迭代直到ia的全部三行都处理完为止。内层的for循环负责输出内层数组所包含的值,像往常一样,数组名被自动地转换成指向数组首元素的地址。内层循环四层直到处理完为止。

也可以使用begin和end让代码更简洁:

for(auto p = begin(ia);p != end(ia); ++p)
{
for(auto q = begin(*p); q != end(*p); ++q)
	cout<< *q<<' ';
cout<<endl;
}
           

类型别名简化多维数组的指针

using int_array = int[4];//新标准下类型别名的声明
typedef int int_array[4];//等价的声明

for(int_array *p = ia;p!=ia+3; ++p)
{
	for(int *q = *p;q!= *p+4;++q)
		cout<<*q<<' ';
	cout<<endl;
}
           

这就将类型“4个整数组成的数组”命名为了int_array,用类型名int_array定义外层循环的控制变量,从而使得程序更加简洁明了。

继续阅读