《C++ Primer》学习笔记(三):字符串、向量和数组(转载)

专栏C++学习笔记

《C++ Primer》学习笔记/习题答案 总目录

——————————————————————————————————————————————————————

字符串、向量和数组

1、命名空间的using声明

使用 using 声明后,就无须再通过专门的前缀去获取所需的名字了。using 声明具有如下的形式:using namespace::name

举个例子:

#include <iostream>
// using 声明,当使用名字cin时,从命名空间std中获取它
using std::cin;
// 或者使用spacename,就不用单独调用具体的声明了
// using namespace std;
int main()
{
	int i;
	cin >> i; // ok:cin和std::cin含义相同
	cout << i; // error:没有对应的using声明,必须使用完整的名字
	std::cout << i; // ok:显式地从std中使用cout
	return 0 ;
}

  
  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

程序中使用的每个名字都需要用独立的 using 声明引入,或者需要引入 spacename

头文件中通常不应该包含 using 声明。这是因为头文件的内容会拷贝到所有引用它的文件巾去,如果头文件里有某个 using 声明,那么每个使用了该头文件的文件就都会有这个声明。对于某些程序来说, 由于不经意间包含了一些名字, 反而可能产生始料未及的名字冲突。

2、标准库类型string

标准库类型 string 表示可变长的字符序列,定义在头文件 string 中。

1)定义和初始化string对象

初始化 string 的方式:

在这里插入图片描述
如果 使用等号 初始化一个变量,实际上执行的是 拷贝初始化(copy initialization),编译器把等号右侧的初始值拷贝到新创建的对象中去。如果 不使用等号,则执行的是 直接初始化(direct initialization)

string s5 = "hiya";  // 拷贝初始化
string s6("hiya") ;  // 直接初始化
string s7(10, 'c');  // 直接初始化,s7的内容是cccccccccc

  
  
  • 1
  • 2
  • 3
2)string对象上的操作

string 的操作:

在这里插入图片描述
在执行读取操作时,string 对象会自动忽略开头的空白(空格符、换行符、制表符等)并从第一个真正的字符开始读取,直到遇见下一处空白为止。

#include <iostream>
#include <string>
using namespace std;
int main()
{
	string s;
	cin >> s;
	cout << s << endl;
	system("pause");
	return 0;
}

  
  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

在这里插入图片描述
注意_Hello_World_,输入的时候有三个空格(这里用三个下划线标注出来),分别在前面、中间和后面,但是输出的时候没有任何空格。

#include <iostream>
#include <string>
using namespace std;
int main()
{
	string s1, s2;
	cin >> s1 >> s2;
	cout << s1 << s2 << endl;
	system("pause");
	return 0;
}

  
  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

在这里插入图片描述
当设置多个输入或者输出时,可以连写在一起。

当希望能在输出的字符串中保留输入的空白字符时,可以使用 getline 函数,getline 函数可以读取一整行字符。该函数只要遇到换行符就结束读取并返回结果(换行符也被读进来了),然后把所读内容存入到 string 对象中(不保存换行符)。如果输入的开始就是一个换行符,则得到空 string

触发 getline 函数返回的那个换行符实际上被丢弃掉了,得到的 string 对象中并不包含该换行符。

ernpty 函数根据 string 对象是有为空返问一个对应的布尔值;size 函数返回 string 对象的长度(即 string 对象中字符的个数),返回值其实是 string::size_type 类型,这是一种无符号类型。要使用 size_type,必须先指定它是由哪种类型定义的。

如果一个表达式中已经有了 size 函数就不要再使用 int 了,这样可以避免混用 intunsigned int 可能带来的问题。

当把 string 对象和字符字面值及字符串字面值混合在一条语句中使用时,必须确保每个加法运算符两侧的运算对象中至少有一个是 string

string s4 = s1 + ", ";          // ok: 把一个string对象和一个字面值相加
string s5 = "hello" + ", ";     // error: 两个运算对象都不是string
string s6 = s1 + ", " + "world";    // ok: 每个加法运算符都有一个运算对象是string

  
  
  • 1
  • 2
  • 3

为了与C兼容,C++语言中的字符串字面值并不是标准库 string 的对象。 切记,字符串字面值与 string 是不同的类型。

3)处理string对象中的字符

头文件 cctype 中的字符操作函数:
在这里插入图片描述
建议使用C++版本的C标准库头文件。C语言中名称为 name.h 的头文件,在C++中则被命名为 cname

C++11提供了范围 for(range for)语句,可以遍历给定序列中的每个元素并执行某种操作。

for (declaration : expression)
    statement

  
  
  • 1
  • 2

其中,expression 部分是一个对象,用于表示一个序列。declaration 部分负责定义一个变量,该变量被用于访问序列中的基础元素。每次迭代,declaration 部分的变量都会被初始化为 expression 部分的下一个元素值。

string str("some string");
// 每行输出str中的一个字符
for (auto c : str)      // 对于str中的每个字符
    cout << c << endl;  // 输出当前字符,后面紧跟一个换行符

  
  
  • 1
  • 2
  • 3
  • 4

如果想在范围 for 语句中改变 string 对象中字符的值,必须把循环变量定义成引用类型。

#include <iostream>
#include <string>
using namespace std;
int main()
{
	string s("Hello, World!!!");
	// 转换成大写形式
	for (auto &c : s)  		// 对于s中的每个字符(注意c是引用)
		c = toupper(c);		// c是一个引用,因此赋位语句将改变s中字符的值
	cout << s << endl;
	system("pause");
	return 0;
}

  
  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

在这里插入图片描述
下标运算符接收的输入参数是 string::size_type 类型的值,表示要访问字符的位置,返回值是该位置上字符的引用。

string 对象的下标必须从0记起,范围是0至 size - 1,左闭右开。使用超出范围的下标将引发不可预知的后果。

C++标准并不要求标准库检测下标是否合法。编程时可以把下标的类型定义为相应的 size_type,这是一种无符号数,可以确保下标不会小于0,此时代码只需要保证下标小于 size 的值就可以了。另一种确保下标合法的有效手段就是使用范围 for 语句。

3、标准库类型vector

标准库类型 vector 表示对象的集合,也叫做 容器(container),定义在头文件 vector 中。vector 中所有对象的类型都相同,每个对象都有一个索引与之对应并用于访问该对象。

vector 是一个 类模板(template),模板本身不是类或函数,相反可以将模板看作为编译器生成类或函数编写的一份说
明。编译器根据模板创建类或函数的过程,称为 实例化(instantiation),当使用模板时,需要指出编译器应把类或函数实例化成何种类型。

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

  
  
  • 1
  • 2
  • 3

vector 是模板而非类型,由 vector 生成的类型必须包含 vector 中元素的类型,如 vector<int>

因为引用不是对象,所以不存在包含引用的 vector

在早期的C++标准中,如果 vector 的元素还是 vector(或者其他模板类型),定义时必须在外层 vector 对象的右尖括号和其元素类型之间添加一个空格,如 vector<vector<int> >。但是在C++11标准中,可以直接写成 vector<vector<int>>,不需要添加空格。

1)定义和初始化vector对象

初始化 vector 对象的方法:

在这里插入图片描述
初始化 vector 对象时:

  • 如果使用圆括号,可以说提供的值是用来构造(construct) vector 对象;
  • 如果使用的是花括号,则是在列表初始化(list initialize) vector 对象。
vector<int> v1(10); 	// v1有10个元素,每个的值都是0
vector<int> v2{10}; 	// v2有1个元素,该元素的值是10
vector<int> v3(10, 1); 	// v3有10个元素,每个的值都是1
vector<int> v4{10, 1}; 	// v4有2个元素,值分别是10和1

  
  
  • 1
  • 2
  • 3
  • 4

可以只提供 vector 对象容纳的元素数量而省略初始值,此时会创建一个值初始化(value-initialized)的元素初值,并把它赋给容器中的所有元素。这个初值由 vector 对象中的元素类型决定。

2)向vector对象中添加元素

push_back 函数可以把一个值添加到 vector 的尾端。

vector<int> v2;         // 空vector对象
for (int i = 0; i != 100; ++i)
    v2.push_back(i);    // 依次把整数值放到v2尾端
// 循环结束后v2有100个元素,值从0到99

  
  
  • 1
  • 2
  • 3
  • 4

范围 for 语句体内不应该改变其所遍历序列的大小。

3)其他vector操作

vector 支持的操作:

在这里插入图片描述
vectorstring 对象的下标运算符只能用来访问已经存在的元素,而不能用来添加元素。

vector<int> ivec; 		// 空vector对象
cout << ivec[O]; 		// 错误:ivec不包含任何元素
vector<int> ivec2(10); 	// 含有10个元素的vector对象
cout << ivec2[10]; 		// 错误: ivec2元素的合法索引是从0到9

  
  
  • 1
  • 2
  • 3
  • 4

正确的添加方法是使用 push_back

vector<int> ivec;   	// 空vector对象
for (decltype(ivec.size()) ix = 0; ix != 10; ++ix)
{
    ivec[ix] = ix;  	// 严重错误:ivec包含任何元素
    ivec.push_back(ix); // 正确:添加一个新元素,该元素的值是ix
}

  
  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

4、迭代器介绍

迭代器的作用和下标运算类似,但是更加通用。所有标准库容器都可以使用迭代器,但是其中只有少数几种同时支持下标运算符。迭代器的作用也和指针类型类似,也提供了对对象的间接访问。

1)使用迭代器

定义了迭代器的类型都拥有 beginend 两个成员函数。begin 函数返回指向第一个元素的迭代器,end 函数返回指向容器 尾元素的下一位置(one past the end) 的迭代器。

// 由编译器决定b和e的类型;
// b表示v的第一个元素, e表示v尾元素的下一位置
auto b = ivec.begin(), e = ivec.end();    // b和e的类型相同

  
  
  • 1
  • 2
  • 3

end 函数通常被称作 尾后迭代器(off-the-end iterator) 或者简称为 尾迭代器(end iterator)

如果容器为空,则 beginend 返回的是同一个迭代器,都是尾后迭代器。

标准容器迭代器的运算符:
在这里插入图片描述
因为 end 返回的迭代器并不实际指向某个元素,所以不能对其进行递增或者解引用的操作。

for 或者其他循环语句的判断条件中,最好使用 != 而不是 <。所有标准库容器的迭代器都定义了 ==!=,但是只有其中少数同时定义了 < 运算符。

如果 vectorstring 对象是常量,则只能使用 const_iterator 迭代器,该迭代器只能读元素,不能修改元素。

vector<int>::iterator it;		   // it能读写vector<int>的元素
string::iterator it2;			   // it2能读写string对象中的字符

vector<int>::const iterator it3; // it3只能读元素,不能写元素
string::const_iterator it4; // it4只能读字符,不能写字符

  • 1
  • 2
  • 3
  • 4
  • 5

beginend 返回的迭代器具体类型由对象是否是常量决定,如果对象是常量,则返回 const_iterator;如果对象不是常量,则返回 iterator

vector<int> v;
const vector<int> cv;
auto it1 = v.begin();   // it1的类型是vector<int>::iterator
auto it2 = cv.begin();  // it2的类型是vector<int>::const_iterator

 
 
  • 1
  • 2
  • 3
  • 4

C++11新增了 cbegincend 函数,不论 vectorstring 对象是否为常量,都返回 const_iterator 迭代器。

auto it3 = v.cbegin(); 	// it3的类型是vector<int>::const_iterator

 
 
  • 1
  • 不能在范围for循环中向vector对象添加元素。
  • 任何可能改变容器对象容量的操作,都会使该对象的迭代器失效。
2)迭代器运算

vectorstring 迭代器支持的操作:
在这里插入图片描述

if ( it < mid)
// 处理Vl前半部分的元素

 
 
  • 1
  • 2

只要两个迭代器指向的是同一个容器中的元素或者尾元素的下一位置,就能将其相减,所得结果是两个送代器的距离。所谓距离,是右侧的迭代器向前移动多少位置就能追上左侧的迭代器,difference_type 类型用来表示两个迭代器间的距离,这是一种带符号整数类型。

使用迭代器完成了二分搜索:

// text必须是有序的
// beg和end表示我们搜索的范围
// beg指向搜索范围内的第一个元素、end指向居元素的下一位置、mid指向中间的那个元素
auto beg = text.begin(), end = text.end();
auto mid = text.begin() + (end - beg)/2; 	// 初始状态下的中间点
// 当还有元素尚未检查并且还没有找到sought时执行循环
whi1e (mid != end && *mid != sought)
{
	if (sought < *mid) 			// 我们要找的元素在前半部分吗?
		end = mid; 				// 如果是,调整搜索范围使得忽略掉后半部分
	e1se 						// 我们要找的元素在后半部分
		beg = mid + 1; 			// 在mid之后寻找
	mid = beg + (end - beg)/2; 	// 新的中间点
}

 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

5、数组

数组类似 vector,也是存放类型相同的对象的容器,但数组的大小确定不变,不能随意向数组中添加元素。

如果不清楚元素的确切个数,应该使用 vector

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

数组是一种复合类型,声明形式为 a[d],其中 a 是数组名称,d 是数组维度。维度说明了数组中元素的个数,因此必须大于0 。数组中元素的个数也属于数组类型的一部分, 编译的时候维度应该是己知的,即维度必须是一个常量表达式。

unsigned cnt = 42; 				// 不是常量表达式
constexpr unsigned sz = 42; 	// 常量表达式
int arr[10]; 					// 含有10个整数的数组
int *parr[sz]; 					// 含有42个整型指针的数组
string bad[cnt]; 				// error:cnt不是常量表达式
string strs[get_size()]; 		// 当get_size是constexpr时正确,否则错误

 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

默认情况下,数组的元素被默认初始化。

定义数组的时候必须指定数组的类型,不允许用 auto 关键字由初始值列表推断类型。

如果定义数组时提供了元素的初始化列表,则允许省略数组维度,编译器会根据初始值的数量计算维度。但如果显式指明了维度,那么初始值的数量不能超过指定的大小。如果维度比初始值的数量大,则用提供的值初始化数组中靠前的元素,剩下的元素被默认初始化。

const unsigned sz = 3;
int ia1[sz] = {0,1,2};  		// 含有3个元素的数组,元素值分别是0,1,2
int a2[] = {0, 1, 2};   		// 维度是1的数组
int a3[5] = {0, 1, 2};  		// 等价于a3[] = {0, 1, 2, 0, 0}
string a4[3] = {"hi", "bye"};   // 等价于a4[] = {"hi", "bye", ""}
int a5[2] = {0,1,2};    		// error:初始值过多

 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

可以用字符串字面值初始化字符数组,但字符串字面值结尾处的空字符也会一起被拷贝到字符数组中。

char a1[] = {'C', '+', '+'};        // 列表初始化,没有空字符
char a2[] = {'C', '+', '+', '\0'};  // 列表初始化,含有显式的空字符
char a3[] = "C++";      			// 自动添加表示字符串结束的空字符
const char a4[6] = "Daniel";    	// error:没有空间可存放空字符!

 
 
  • 1
  • 2
  • 3
  • 4

不能将数组的内容拷贝给其他数组作为其初始值,也不能用数组为其他数组赋值:

int a[] = {O , 1 , 2};	// 含有3个整数的数组
int a2[] = a;			// error:不允许使用一个数组初始化另一个数组
a2 = a;					// error:不能把一个数组直接赋值给另一个数组

 
 
  • 1
  • 2
  • 3

从数组的名字开始由内向外阅读有助于理解复杂数组声明的含义。

int *ptrs[10];              // ptrs是含有10个整型指针的数组
int &refs[10] = /* ? */;    // error:不存在引用的数组
int (*Parray)[10] = &arr;   // Parray指向一个含有10个整数的数组
int (&arrRef)[10] = arr;    // arrRef引用一个含有10个整数的数组

 
 
  • 1
  • 2
  • 3
  • 4
2)访问数组元素

数组下标通常被定义成 size_t 类型,这是一种机器相关的无符号类型,可以表示内存中任意对象的大小。size_t 定义在头文件 cstddef 中。

数组除了大小固定这一特点外,其他用法与 vector 基本类似。

大多数常见的安全问题都源于缓冲区溢出错误。当数组或其他类似数据结构的下标越界并试图访问非法内存区域时,就会产生此类错误。

3)指针和数组

在大多数表达式中,使用数组类型的对象其实是在使用一个指向该数组首元素的指针。

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

 
 
  • 1
  • 2
  • 3

一维数组寻址公式:
在这里插入图片描述
当使用数组作为一个 auto 变量的初始值时,推断得到的类型是指针而非数组。但 decltype 关键字不会发生这种转换,直接返回数组类型。

int ia[] = {0,1,2,3,4,5,6,7,8,9};   // ia是一个含有10个整数的数纽
auto ia2(ia);   					// ia2是一个整型指针,指向ia的第一个元素
ia2 = 42;       					// error:ia2是一个指针,不能用int值给指针赋值
auto ia2(&ia[0]);   				// 显然ia2的类型是int*
// ia3是一个含有10个整数的数组
decltype(ia) ia3 = {0,1,2,3,4,5,6,7,8,9};
ia3 = p;    						// error:不能用整型指针给数组赋值
ia3[4] = i;     					// ok:把i的值赋给ia3的一个元素

 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

C++11在头文件 iterator 中定义了两个名为 beginend 的函数,功能与容器中的两个同名成员函数类似,参数是一个数组。

特别注意,后指针不能执行解引用和递增操作。

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

 
 
  • 1
  • 2
  • 3

给(从) 一个指针加上(减去)某整数值,结果仍是指针。两个指针相减的结果类型是 ptrdiff_t,这是一种定义在头文件 cstddef 中的带符号类型。因为差值可能为负值, 所以 ptrdiff_t 是一种带符号类型。

constexpr size_t sz = 5;
int arr[sz] = {1, 2, 3, 4, 5};
int *ip = arr; 		// 等价于int*ip = &arr[O]
int *p2 = ip + 4; 	// ip2指向arr的尾元素arr[4]
auto n = end(arr) - begin(arr); // n的值是5 ,也就是arr中元素的数量

 
 
  • 1
  • 2
  • 3
  • 4
  • 5

表达式 * (ia+4) 计算 ia 前进4个元素后的新地址,解引用该结果指针的效果等价于表达式 ia[4]

int ia[] = {0, 2, 4, 6, 8}; // 含有5个整数的数组
int last = *(ia + 4); 		// ok:把last初始化成8,也就是ia[4]的值

 
 
  • 1
  • 2

标准库类型限定使用的下标必须是无符号类型,而内置的下标运算无此要求。

4)C风格字符串

尽管C++支持C风格字符串,但在C++程序中最好还是不妥使用它们。这是因为C风格字符串不仅使用起来不太方便,而且极易引发程序漏洞, 是诸多安全问题的根本原因。

字符串字面值是一种通用结构的实例,这种结构即是C++由C继承而来的 C风格字符串(C-style character string)。C风格字符串是将字符串存放在字符数组中,并以 空字符结束(null terminated)。这不是一种类型,而是一种为了表达和使用字符串而形成的约定俗成的书写方法。以空字符结束的意思是在字符串最后一个字符后面跟着一个空字符 \0
一般利用指针来操作这些字符串。

C风格字符串的函数:

在这里插入图片描述
C风格字符串函数不负责验证其参数的正确性,传入此类函数的指针必须指向以 空字符 作为结尾的数组。

对大多数程序来说,使用标准库string要比使用C风格字符串更加安全和高效。

5)与旧代码的接口

任何出现字符串字面值的地方都可以用以空字符结束的字符数组来代替:

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

不能用 string 对象直接初始化指向字符的指针。为了实现该功能,string 提供了一个名为 c_str 的成员函数,返回 const char* 类型的指针,指向一个以空字符结束的字符数组,数组的数据和 string 对象一样。

string s("Hello World");    // s的内容是Hello World
char *str = s;  			// error: 不能用string对象初始化char*
const char *str = s.c_str();// ok

 
 
  • 1
  • 2
  • 3

针对 string 对象的后续操作有可能会让 c_str 函数之前返回的数组失去作用,如果程序想一直都能使用其返回的数组,最好将该数组重新拷贝一份。

可以使用数组来初始化 vector 对象,但是需要指明要拷贝区域的首元素地址和尾后地址。

int int_arr[] = {0, 1, 2, 3, 4, 5};
// ivec有6个元素,分别是int_arr中对应元素的副本
vector<int> ivec(begin(int_arr), end(int_arr));

 
 
  • 1
  • 2
  • 3

在新版本的C++程序中应该尽量使用 vectorstring 和迭代器,避免使用内置数组、C风格字符串和指针。

6、多维数组

C++中的多维数组其实就是数组的数组。当一个数组的元素仍然是数组时,通常需要用两个维度定义它:一个维度表示数组本身的大小,另一个维度表示其元素(也是数组)的大小。通常把二维数组的第一个维度称作行,第二个维度称作列。

多维数组初始化的几种方式:

int ia[3][4] = 
{   // 三个元素,每个元素都是大小为3的数组
    {0, 1, 2, 3},   // 第1行的初始值
    {4, 5, 6, 7},   // 第2行的初始值
    {8, 9, 10, 11}  // 第3行的初始值
};
// 没有标识每行的花括号,与之前的初始化语句是等价的
int ib[3][4] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11};
// 显式地初始化每行的首元素
int ic[3][4] = {{ 0 }, { 4 }, { 8 }};
// 显式地初始化第1行,其他元素执行值初始化
int id[3][4] = {0, 3, 6, 9};

 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

可以使用下标访问多维数组的元素,数组的每个维度对应一个下标运算符。

  • 如果表达式中下标运算符的数量和数组维度一样多,则表达式的结果是给定类型的元素。
  • 如果下标运算符数量比数组维度小,则表达式的结果是给定索引处的一个内层数组。
// 用arr的首元素为ia最后一行的最后一个元素赋值
ia[2][3] = arr[0][0][0];
int (&row)[4] = ia[1];  // 把row绑定到ia的第二个4元素数组上

 
 
  • 1
  • 2
  • 3

多维数组寻址公式:
在这里插入图片描述
使用范围 for 语句处理多维数组时,为了避免数组被自动转换成指针,语句中的外层循环控制变量必须声明成引用类型。

for (const auto &row : ia)  // 对于外层数组的每一个元素
    for (auto col : row)    // 对于内层数组的每一个元素
        cout << col << endl;

 
 
  • 1
  • 2
  • 3

如果 row 不是引用类型,编译器初始化 row 时会自动将数组形式的元素转换成指向该数组内首元素的指针,这样得到的 row 就是 int* 类型,而之后的内层循环则试图在一个 int* 内遍历,程序将无法通过编译。

for (auto row : ia)
    for (auto col : row)

 
 
  • 1
  • 2

使用范围 for 语句处理多维数组时,除了最内层的循环,其他所有外层循环的控制变量都应该定义成引用类型。

因为多维数组实际上是数组的数组,所以由多维数组名称转换得到的指针指向第一个内层数组。

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

 
 
  • 1
  • 2
  • 3

声明指向数组类型的指针时,必须带有圆括号。

int *ip[4];     // 整型指针的数组
int (*ip)[4];   // 指向含有4个整数的数组

 
 
  • 1
  • 2

C++11新标准,使用 autodecltype 能省略复杂的指针定义。

// 输出ia中每个元素的值,每个内层数组各占一行
// p指向含有4个整数的数组
for (auto p = ia; p != ia + 3; ++p) 
{
    // q指向4个整数数组的首元素,也就是说,q指向一个整数
    for (auto q = *p; q != *p + 4; ++q)
        cout << *q << ' ';
    cout << endl;
}

 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

使用标准库函数 beginend 也能实现同样的功能,而且看起来更简洁一些。

// p指向ia的第一个数组
for (auto p = begin(ia); p != end(ia); ++p) 
{
	// q指向内层数组的首元素
	for (auto q = begin(*p); q != end(*p); ++q)
		cout << *q << ' '; 	// 输出q所指的整数值
cout << endl;
}

 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

参考文章

  • 《C++ Primer》
                                </div>
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值