学习目标:
字符串、vector和数组
学习内容:
一、命名空间的using声明
-
形式:using std::name 编译器会从左侧的命名空间中去寻找右侧那个名字
#include <iostream> using std::cin; using std::cout; using std::endl; int main(){ int x; cin >> x; cout << x << endl; return 0; }
-
但是头文件不宜有using,因为头文件可能会被拷贝到其他文件中,那么每个拷贝这个头文件的文件都会包含这个声明。
-
更常用的方式:using namespace name;
#include <iostream> using namespace std; int main(){ int x; cin >> x; cout << x << endl; return 0; }
二、标准库类型String
string类型表示可变长的字符串序列。string类型使用都要引用头文件:
#include <string>
1. string初始化
-
初始化方式
i. string s1; 默认初始化,s1是空串 直接初始化
ii. string s2(s1); 直接初始化 <> string s2 = s1; 拷贝初始化
iii. string s3(“hello”) 直接初始化 <> string s3 = “hello”; 拷贝初始化
iv. string s4(n, ‘h’); 把字符’h’复制n份 直接初始化 string s4(n, ‘h’)
<=>string s5 = string(n, ‘h’)<=> string temp = string(n, ‘h’);string s6 = temp; -
用等号=初始化:拷贝初始化;不用等号,直接初始化;
2. string对象操作
2.1 读写对象操作
#include <iostream>
#include <string>
using namespace std;
int main(){
string s;
cin >> s;
cout << s << endl;
return 0;
}
-
执行读取操作时,会自动忽略开头的空白(空格、换行符、制表符等),即输入" sda “,输出还是"sda”;
-
输入输出返回的是左侧的运算对象,所以可以连续输入输出;
#include <iostream> #include <string> using namespace std; int main(){ string s1, s2; cin >> s1 >> s2; cout << s1 << " " << s2 << endl; return 0; }
-
读取未知数量的string对象: while(cin >> s) 以非空格符开始,空格符结束
#include <iostream> #include <string> using namespace std; int main(){ string s; // 判断输入流是否有效,如果有效执行while内语句 // 如果输入流无效,输入结束符,结束while循环 // 以非空格开始 空格符结束 如输入' abc def' // 实际输出两个值'abc'和'def' while (cin >> s){ cout << s << endl; } return 0; }
2.2 使用getline读取一整行getline(cin,s):以换行符结束,会读取这一行所有空格符(空格符也被当中正常的字符)。所以就算输入是空行,也同样会正常读取,输出空行。
#include <iostream>
#include <string>
using namespace std;
int main(){
string line;
// 以换行符结束 输入' abc sds '
// 输出15 和 ' abc sds '
while(getline(cin, line)){
cout << line.length() << line << endl;
}
return 0;
}
2.3 判断string对象是否为空:s.empty(),空返回True,否则返回True
#include <iostream>
#include <string>
using namespace std;
int main(){
string line;
while(getline(cin, line)){
if(!line.empty()){
cout << line << endl;
}
}
return 0;
}
2.4 返回string对象长度,即有多少个字符:s.size()、s.length(); 注意size和length返回的都是string::size_type类型。所以一般不用一个int型的变量存放size函数返回值。
#include <iostream>
#include <string>
using namespace std;
int main(){
string s = " hello world ";
cout << s.size() << endl; // 13
cout << s.length() << endl; // 13
int len = s.size(); // 最好不要这样写
auto len = s.size(); // 利用c++11新特性auto这样写比较好
}
2.5 比较string对象
-
==或者 != :两个对象,只有等长,对所有字符完全一样(包括大小写),==才成立;
-
<、>、<=、>=:按照字符顺序从第一个字符开始比较(大小写敏感);
#include <iostream> #include <string> using namespace std; int main(){ // s3 > s2 > s1 string s1 = "hello"; string s2 = "hello world"; string s3 = "hiya"; }
2.6 string对象赋值:和普通变量一样
#include <iostream>
#include <string>
using namespace std;
int main(){
string s1 = "hello", s2; // 初始化
s2 = s1; // 对象赋值
}
2.7 两个string对象相加,相当于将两个string对象串联起来,生成一个新的string对象。
#include <iostream>
#include <string>
using namespace std;
int main(){
string s1 = "hello", s2 = " world", s3;
s3 = s1 + s2;
s1 += s2;
cout << s1 << endl; // s1 = 'hello world'
cout << s3 << endl; // s3 = 'hello world'
}
2.8 string对象和字面值常量相加
-
string字面值常量和string是不同的类型,但是标准库可以将string字面值常量转换为string对象,所以可以将string对象和string字面值常量相加;
-
遵循原则:+两边至少要有一个string对象
#include <iostream> #include <string> using namespace std; int main(){ string s1 = "hello", s2 = " world"; string s3 = s1 + s2; string s4 = s1 + ", "; string s5 = "hello" + "world"; // 错误 两个字面值常量不能直接相加 // 正确 和连续输入输出一样 s1 + ", "返回的是string对象 // 等价于string temp = s1 + ", "; string s6 = temp + "world" string s6 = s1 + ", " + "world"; string s7 = "hello, " + "world" + s2; // 错误 两个字面值常量不能直接相加 }
3. 处理string对象中的字符
-
为了方面我们访问string对象中的字符,cctype头文件中定义了一组标准库函数来处理,而cctype头文件中定义的名字从属于命名空间std中,所以只要引用std命名空间,即可直接访问下面这一组库函数。
-
访问string对象中的字符:范围for语句 形式:for(declaration : expression) statement; 其中expression表示对象,declaration定义一个变量,这个变量是expression对象(序列)中的基本元素.每次for迭代expression对象都会被初始化为expression中的下一个元素。而statement可以定义一些对基本元素的操作。
#include <iostream> #include <string> using namespace std; int main(){ string str = "hello world"; for(auto s : str){ cout << s << endl; } }
-
统计string对象中的字符:不改变string对象 例子:统计string对象中的小写字母个数
#include <iostream> #include <string> using namespace std; int main(){ string str = "Hello World"; // string::size_type类型 decltype(str.size()) count = 0; for(auto s : str){ if(islower(s)) ++count; } cout << count << endl; // 8 }
-
改变string对象中的字符:在declaration中引用string对象中的每个元素 例子:把string对象中每个小写字母该成大写字母
#include <iostream> #include <string> using namespace std; int main(){ string str = "Hello World"; for(auto &s : str){ // 这里要把输出的大写形式重新赋值给s // s其实就是str中元素的别名,所以对别名操作,也是对str中元素进行操作 s = toupper(s); } cout << str << endl;
-
使用下标访问string对象中的元素 下标[],输入一个索引(string::size_type类型),指示元素位置,返回该位置上字符的引用。 索引最好是使用string::size_type类型,范围在0~size-1之间,但是如果是int带符号类型,系统也会自动转换为string::size_type无符号类型;
i. 只使用下标:访问string对象中的某个元素 例子:把string对象中第一个元素变为大写#include <iostream> #include <string> using namespace std; int main(){ string str = "hello world"; if(!str.empty()){ str[0] = toupper(str[0]); // 下标返回的是引用,所以可以直接重新赋值 } cout << str << endl; // Hello world }
ii. 使用下标+for迭代:访问string对象中的部分元素 例子:把string对象中的第一个单词该成大写形式
#include <iostream> #include <string> using namespace std; int main(){ string str = "hello world"; for(decltype(str.size()) index = 0; index < str.size() && !isspace(str[index]); ++index) str[index] = toupper(str[index]); cout << str << endl; // HELLO world }
三、标准库类型vector容器
vector表示对象的集合,而且所有对象的类型都必须一样。 vector是一个类模板,模板本身不是类或函数。 可以将模板看作编译器生成类或函数的一份说明,编译器根据模板创建类或函数的过程可以称为实例化。 vector可以使用大多数类型(甚至可以是vector类型的对象)的对象作为其元素,但是引用不行,引用不是对象。 vector的使用是这样的:
#include <iostream>
#include <string>
#include <vector> // 引入vector头文件
using namespace std;
int main(){
// 必须指明vector到底要实例化一个什么类
// 这里指明实例化一个vector 这个vector存放的所有对象必须是int类型
vector<int> ivec = {1, 2, 3};
}
1. vector初始化
-
常见的vector初始化方法
i. 默认初始化: vector<类型> v1;
ii. 直接初始化:vector<类型> v2(v1);
iii. 拷贝初始化: vector<类型> v3 = v1;
iv. 列表初始化: vector<类型> v4 = {a,b,c};
v. 指定初始化个数和值: vector<类型> v5(n, val); 包含n个元素,每个元素都是val;
vi. 值初始化: vector<类型> v6(n); 包含n个元素 每个元素都是默认初始化;#include <iostream> #include <string> #include <vector> using namespace std; int main(){ vector<int> v1; // 默认初始化 {} 生成一个空vector对象 0个元素 vector<int> v2(v1); // 直接初始化 {} vector<int> v3 = v1; // 拷贝初始化 {} vector<int> v4 = {1, 2, 3}; // 列表初始化 {1,2,3} vector<int> v5(4, 1); // 指定初始化个数和值 {1,1,1,1} vector<int> v6(5); // 值初始化 {0,0,0,0,0} }
-
注意这几种初始化方式在大多数情况可以相互等价,但是在以下3种情况下不行:
i. 使用拷贝初始化时,只能提供一个初始值;
ii. 如果是类内初始化,只能使用拷贝初始化或者列表初始化{};
iii. 如果要初始化一个列表,只能用花括号,不能用圆括号; -
如何确定()和{}的含义,到底执行列表初始化还是构造初始化
i. 如果是使用(),肯定是构造初始化,即通过()内的参数,来构造vector对象;如果()不能执行构造初始化,会直接报错,因为它不可以执行列表初始化;vector<int> v1(10); // 构造初始化 10个元素 每个元素都是默认初始化0 vector<int> v2(10, 1); // 构造初始化 10个元素 每个元素都是1 vector<int> v3("hello"); // 报错 无法执行构造初始化,不能执行列表初始化 报错
ii. 如果是使用{},第一下肯定看它能否执行列表初始化,如果不可以,它可以执行构造初始化;
vector<int> v1{10}; // 列表初始化 一个元素 10 vector<int> v2{10, 2}; // 列表初始化 两个元素 10 2 vector<string> v3{"hello"}; // 列表初始化 一个元素 "hello" vector<string> v4{10}; // 无法执行列表初始化 -> 构造初始化 10个元素 都是"" vector<string> v5{10, "hello"}; // 无法执行列表初始化 -> 构造初始化 10个元素 都是"hello"
2. 对vector对象中加入元素:push_back
使用vector的成员函数push_vector,可以让vector高效动态增长(添加元素) 例子:
#include <iostream>
#include <vector>
using namespace std;
int main(){
vector<int> v1;
for(int i = 0; i < 100; i++){
v1.push_back(i);
}
return 0;
}
3. 其他vector操作
-
v.empty():如果v不含任何元素,返回True,否则返回False;
-
v.size(): 返回容器中元素的个数, v.size()返回的是vector<类型>::size_type类型;
-
v.push_back(t): 在容器末尾添加一个元素t;
-
同样可以用范围for语句访问容器中的所有元素和修改(&)容器中的所有元素;
#include <iostream> #include <vector> using namespace std; int main(){ vector<int> v = {1, 2, 3, 4}; for(auto i : v){ // 访问 cout << i << endl; } for(auto &j : v){ // 修改 j *= j; } // v = {1, 4, 9, 16} return 0; }
-
v[i]:访问或修改容器v中idx=i的元素,返回的是这个元素的引用;
i. 下标访问容器中元素时,下标必须合法,即处于0->size-1之间,超出这个范围会报错;
ii. 所以为了保证下标合法,应该尽量用for语句,for(int i = 0; i < v.size(); i++),肯定不会出错;
iii. 下标是无法添加元素的,只能访问元素,添加只能用push_back函数; -
比较:==、!=,只有当两个容器中原始个数相同且每个元素值也完全相同时,等式成立,返回True,否则返回False;
-
比较:<、>、<=、>=,将每个元素从左到右按字典顺序进行比较;
-
赋值:v1 = v2; v1 = {a,b,c,d},将v2或者{a,b,c,d}拷贝替换v1中的元素;
四、迭代器
1. 使用迭代器
-
string对象不属于容器,但是它们有很多相似的操作
i. 都支持下标访问;
ii. 都支持迭代器访问; -
遍历string和vector的方式
i. for(auto temp : v)访问
ii. 下标访问:for(int i = 0; i < v.size; ++i)
iii. 迭代器访问:for(auto it = v.begin(); it != v.end(); ++it) -
如何使用迭代器遍历一个vector v.begin()函数返回第一个元素的迭代器;v.end()函数返回尾(后)迭代器。 当且仅当vector为空时,begin和end返回的都是尾迭代器。所以通常用v.begin() == v.end()来判断vector是否为空。
#include <iostream> #include <vector> using namespace std; int main(){ vector<int> v = {1, 2, 3, 4}; for(auto it = v.begin(); it != v.end(); ++it){ *it = 100; // *it返回的是当前迭代器所指的元素的引用,所以可以直接修改当前元素值 cout << *it << endl; //返回当前迭代器所指的元素的引用 } return 0; }
-
迭代器常见的运算符
i. 星号it:返回迭代器it所指对象的引用 和指针类似,迭代器也可以使用解引用符号*返回当前迭代器所指示对象的引用,所以可以直接用( *it)修改当前元素的内容; 执行解引用的迭代器必须合法,即通常要先判断vector是否为空,即当前迭代器是否执行某个具体的元素;
ii. it->item:解引用it,并且返回这个元素名为item的成员,等价于(*it).item,因为解引用返回的是迭代器所指的对象引用,如果这个对象有成员,那么就可以直接访问到;// 假如it是一个vector<string>中的对象 (*it).empty(); // 解引用后返回迭代器所指的对象引用,再用点运算符执行string的内置函数.empty()判断这个对象是否为空 it->empty(); // 两句话等价 (*it).empty() <==> it->empty() *it.empty(); // 错 这样会执行it.empty() 只要*先解引用才能返回迭代器所指的对象引用
iii. ++it:返回下一个迭代器
iv. --it: 返回上一个迭代器
v. it1 == it2:两个迭代器指向同一个元素返回True,否则返回False
vi. it1 != it2:两个迭代器指向不同元素返回Ture,否则返回False -
练习1:用迭代器把string对象中第一个字符改为大写
#include <iostream> #include <vector> #include <string> using namespace std; int main(){ string s = "hello world"; if(s.begin() != s.end()){ auto it = s.begin(); *it = toupper(*it); // "Hello world" } return 0; }
-
练习2:用迭代器把string对象中第一个单词字符全部改为大写
#include <iostream> #include <vector> #include <string> using namespace std; int main(){ string s = "hello world"; for(auto it = s.begin(); it != s.end() && !isspace(*it); ++it){ *it = toupper(*it); // "HELLO world" } return 0; }
注意:这里for循环里面为什么要使用!=,而不用<
为了泛型编程,因为标准库中所有的容器都定义了==和!=,但是大多数没定义< -
迭代器类型:iteration 和 const_iteration
i. iteration:可读可写,如果vector或者string对象不是一个常量
ii. const_iteration:只读,如果vector或者string对象是一个常量
iii. 返回的begin和end迭代器类型由vector或者string对象类型决定vector<int> v; const vector<int> cv; auto it1 = v.begin(); // vector<int>::iteration类型 auto it2 = cv.end(); // vector<int>::const_iteration类型
iv. c++11引入两个新函数:cbegin和cend,无论vector或者string是否是常量,都会返回const_iteration
vector<int> v; const vector<int> v; auto it1 = v.cbegin(); // vector<int>::const_iteration类型 auto it2 = v.cend(); // vector<int>::const_iteration类型
2. 迭代器运算
-
两个迭代器运算的前提条件:两个迭代器必须处于同一个vector/string中
-
迭代器和一个整数相加/相减:返回的依然是一个迭代器,新迭代器相比原迭代器向右/向左移动了若干个位置。注意不能超过vector或者string对象的左右范围,如果超过了程序会出问题。
-
两个迭代器没有相加操作;
i. iter + n
ii. iter - n
iii. iter += n 迭代器自身向前移动n个位置
iv. iter -= n 迭代器自身向后移动n个位置vector<string> vec = {"he", "ll", "o", "wo", "rl", "d"}; auto it1 = vec.cbegin(); // he auto it2 = it1 + 2; // o auto it3 = it2 - 1; // ll auto it4 = vec.cbegin() + vec.size() / 2; // wo 找到vector的中间迭代器 auto it5 = it1 - 2; // 超出vector右边界 程序出问题 auto it6 = it1 + 6; // 超出vector左边界 程序出问题
-
判断两个迭代器的位置关系 >、>=、<、<=、== 以 (it1>it2) 为例,如果it1在it2位置右边,返回True=1,否则返回False=0,特殊的当it1和it2是同一个迭代器,it1==it2
vector<string> vec = {"he", "ll", "o", "wo", "rl", "d"}; auto it1 = vec.cbegin(); // he auto it2 = it1 + 2; // o cout << (it2 > it1) << endl; // 1
访问vector前一半迭代器:
vector<string> vec = {"he", "ll", "o", "wo", "rl", "d"}; auto it = vec.cbegin(); auto mid = it + vec.size() / 2; while(it < mid){ // 访问前一半迭代器 he ll o cout << *it << endl; ++it; }
-
判断两个迭代器之前相差几个迭代器:it1-it2,返回类型difference_type,是一个带符号的整数。当it1在it2右边,返回值大于0,当it2在it1左边,返回值小于0。
vector<string> vec = {"he", "ll", "o", "wo", "rl", "d"}; auto it = vec.cbegin(); auto mid = it + vec.size() / 2; cout << mid - it << endl; // 3 cout << it - mid << endl; // -3
五、 数组
数组也是存放相同对象一个容器。 与vector不同的是:数组的大小确定不变,不能任意向数组中添加元素。
1. 定义和初始化数组
-
形式:类型名 数组名[数组维度]
-
类型名必须明确,不能用auto,也不允许是引用类型(引用不是对象)
auto arr3[10]; // 必须明确数组类型 int &arr2[10]; // 错 引用不是对象
-
默认初始化 数组维度必须明确,必须是常量表达式
unsigned a = 42; // 非常量表达式 const unsigned b = 42; // 常量表达式 int arr[10]; // 定义一个含有10个整数的数组 默认初始化全为0 string sarr1[a]; // 错 不是常量表达式b string sarr[b]; // 定义一个含有b个string的数组 默认初始化全为"" int *parr[3]; // 含义3个整型指针的数组(数组中每个元素都是整型指针) int c[4*7-14]; // 合法
-
列表初始化
i. 允许忽略维度,如果维度没有明确,编译器会根据列表初始值的个数推测出来;
ii. 如果明确了维度,列表初始值的个数不能超过维度,超过会报错;如果维度比列表初始值个数更多,剩下的部分执行默认初始值int a[] = {1, 2, 3}; // 3个对象 {1,2,3} int b[5] = {1, 2, 3}; // 5个对象 {1,2,3,0,0} char e[] = {'a', 'b', 'c'}; // 3个对象 {'a', 'b', 'c'} string c[4] = {"hello", "world"}; // 4个对象 {"hello", "world", "", ""} int d[2] = {1, 2, 3}; // 报错
-
字符串字面值初始化:字符数组独有的初始化方式,字符串字面值尾部还有一个空字符,所以以字符串字面值对字符数组初始化时,后面还有一个空字符;
char f[] = "hello"; // 6个对象 {'h','e','l','l','o',''} char g[5] = "world"; // 报错 没有空间放空字符
-
不允许将数组的内容拷贝给其他数组作为初始值,也不能用数组为其他数组赋值;
int a[3] = {1, 2, 3}; int b[3]; int c[3] = a; // 错 不能拷贝初始化 b = a; // 错 不能赋值
-
数组、指针和引用(从数组名字开始从内向外阅读)
i. int *p[10]; 定义了一个含义10个对象的数组,每个对象都是一个int型的指针;
ii. int &p[10] = …; 非法 因为数组存储是对象,引用不是对象;
iii. int (*p)[10] = &arr; 定义了一个指针,这个指针指向一个int数组,这个数组包含10个元素;
iv. int (&p)[10] = arr; 定义了一个引用,这个引用指向一个int数组,这个数组包含10个元素;
2. 遍历数组:访问和修改数组元素
-
范围for
int a[3] = {1, 2, 3}; for(auto temp : a){ // 遍历 cout << temp << endl;// 访问数组 } for(auto &temp : a){ temp = 4; // 修改数组 }
-
下标:下标访问要注意下标溢出问题
int a[3] = {1, 2, 3}; for(int i = 0; i < sizeof(a) / sizeof(a[0]); ++i){ // 遍历 a[i] = 4; // 修改数组元素 cout << a[i] << endl; // 访问数组 }
-
使用指向数组元素的指针,可以做到类似迭代器的功能
string str[] = {"hello", "world", "hk"}; string *e = &str[3]; for(string *s = str; s != e; ++s){ cout << *s << endl; }
但是这种做法很不安全,所以通常用标准库函数begin和end模拟迭代器进行遍历
string str[] = {"hello", "world", "hk"}; string *b = begin(str); // 指向str的首元素 string *e = end(str); // 指向str的尾元素的下一个元素 while(b != e){ cout << *b << endl; ++b; }
3. 指针和数组
-
在c++中,指针和数组有着非常紧密的关系,使用数组的时候,编译器一般会把数组转换为指针。
-
对数组执行下标运算可以得到数组中指定位置的对象,再对这个对象使用取地址符就可以得到指向该元素的指针。
string str[] = {"hello", "world", "hk"}; string *p1 = &str[1]; // string类型的指针p1,指向数组中第1个对象string "world"
-
数组还有一个特殊的性质:使用数组的对象时,通常都是指向该数组的首元素的指针。
string str[] = {"hello", "world", "hk"}; string *p2 = str; // string类型的指针p2,指向数组的首元素str[0] auto p3 = str; // p3是string类型的指针 cout << *p3 << endl; // "hello"
-
指向数组元素的指针也可以当做迭代器使用,对数组进行遍历;
-
指向数组元素的指针拥有和迭代器一样的相关运算:解引用、递增、递减、与整数相加、两个指针相减等;
int a[] = {0, 2, 4, 6, 8}; int *p1 = &a[0]; int *p2 = &a[2]; cout << *p1 << endl; // 解引用 cout << *(++p1) << endl; // 自增 相当于指针向后移动一位 再*解引用 cout << (p1 < p2) << endl; // 比较 < > <= >= == cout << *(p1 + 1) << endl; // 与整数相加 相当于指针向后移动1位 再*解引用 cout << (*p1) + 1 << endl; // 注意两者区别 此时是先解引用 再把解引用的结果加1 cout << p1 - p2 << endl; // 两个指针相减 必须是同一个数组的指针 两个指针相差多少个元素
-
下标和指针
i. a[2]等价于*(p+2);
ii. 只要指针指向的是数组中的元素,那么就可以对这个指针执行下标运算int a[] = {0, 2, 4, 6, 8}; int *p1 = &a[3]; // p1执行idx=3的元素6 int p2 = p1[1]; // p1[1]等价于*(p+1)=8 int p3 = p1[-2]; // p1[-2]等价于*(p-2)=2
-
可以发现,string和vector的下标只能是无符号类型,但是数组的下标可以是负数;
六、多维数组
-
多维数组定义 int a[3][4]; 表示定义了一个3行4列的多维数组
-
多维数组初始化
i. 大括号嵌套形式 + 一个大括号形式int a[3][4] = {{0,1,2,3}, {4,5,6,7},{8,9,10,11}}; int b[3][4] = {0,1,2,3,4,5,6,7,8,9,10,11}; // a == b
ii. 和一维数组一样,没有初始化的元素会执行默认初始化
int a[3][4] = {0}; // {{0,0,0,0}, {0,0,0,0},{0,0,0,0}} int b[3][4] = {{0,1,2}, {4,5,6},{8,9,10}}; // {{0,1,2,0}, {4,5,6,0},{8,9,10,0}} int c[3][4] = {0,1,2,3,4,5,6,7,8}; // {{0,1,2,3}, {4,5,6,7},{8,0,0,0}} int d[3][4] = {{1}, {2}, {3}}; // {{1,0,0,0},{2,0,0,0},{3,0,0,0}} int e[3][4] = {1,2,3}; // {{1,2,3,0},{0,0,0,0},{0,0,0,0}}
-
多维数组的遍历/修改
下标方式
i. 当表达式下标运算符数量和多维数组的维度一样多时,该表达式返回的是多维数组对应位置的元素;但是当表达式中的运算符个数少于多维数组的维度,那么表达式返回的对应索引位置处的内层数组;int a[2][3]; a[0] // 表示第一行 内置4个int型元素 a[0][0] // 表示第一行第一列这个元素
ii. 遍历和修改两层数组
constexpr size_t row = 3, col = 4; int a[row][col]; // {{0,1,2,3},{4,5,6,7},{8,9,10,11}} for(size_t i = 0; i != row; ++i){ for(size_t j = 0; j != col; ++j){ a[i][j] = i * col + j; // 修改数组 cout << a[i][j] << endl; // 遍历数组 } }
范围for语句方式
i. 遍历两层数组 使用范围for语句遍历多维数组时,除了最内层的循环以外,其他所有层循环都必须使用引用形式。 主要是为了避免数组被自动转换成指针。因为如果第一层实际上每一个row都是一个大小为4的数组,而如果不用&的话,编译器会自动将这个数组转换为指向这个数组内首元素的指针,那么得到的row就是一个int*类型了,导致最终第二层循环异常。int a[3][4] = {0}; int cnt = 0; for(auto &row : a){ for(auto col : row){ cout << col << endl; } }
ii. 修改两层数组 因为要改变数组元素,所以多维数组中所有层都必须采样引用的形式&遍历行和列。
int a[3][4]; // {{0,1,2,3},{4,5,6,7},{8,9,10,11}} int cnt = 0; for(auto &row : a){ for(auto &col : row){ col = cnt; ++cnt; } }
-
指针和多维数组
i. 当程序使用多维数组的名字时,会自动将其转换为指向第一个内层数组的指针。int a[3][4] = {1,2,3,4,5,6,7,8,9,10,11,12}; // int *p = a; // 报错 使用多维数组时 会自动将其转换为指向第一个内层数组的指针 即int *[4]类型 int (*p)[4] = a; // 对 p指针指向第一行 *p=[1,2,3,4] p = &a[2]; // a[2] = (a+2) p指针指向第三行 *p = [9,10,11,12]
注意上面 int *p[4]代表一个整型指针的数组,而int ( *p)[4]表示指向含义4个整数的数组。
ii. 使用指针也可以进行遍历int a[3][4] = {1,2,3,4,5,6,7,8,9,10,11,12}; // 指针p指向a的第一行 ++p将指针指向第二行 直到4行遍历完为止 // p是一个指针 指向一个int[4]的数组 // q是一个指针 指向一个int整数 for(auto p = a; p != a + 3; ++p){ for(auto q = *p; q != *p + 4; ++ q){ cout << *q << endl; } }
iii. 标准库函数begin()和end()遍历多维数组更安全
int a[3][4] = {1,2,3,4,5,6,7,8,9,10,11,12}; for(auto p = begin(a); p != end(a); ++p){ for(auto q = begin(*p); q != end(*p); ++p){ cout << *q << endl; } }
iv. 但是有时候为了让代码看出去可读性更强,会使用类型别名来声明
using int_array = int[4]; // 类型别名 直接说清楚p是一个指针 指向一个int[4]数组 代码看上去更清晰 for(int_array *p = begin(a); p != end(a); ++p){ for(int *q = begin(*p); q != end(*p); ++q){ cout << *q << endl; } }