C++ Primer 第三章笔记

3.1 命名空间的 using 声明

​ 目前为止,我们用到的库函数基本上都属于命名空间 std,而程序也显式地将这一点标注出来。例如,std::cin 表示从标准输入中读取内容。此处的作用域操作符(::)的含义是:编译器从操作符左侧名字所示的作用域中寻找右侧那个名字。因此,std::cin 的意思就是要使用命名空间 std 的名字 cin。 ​ 较为简单且安全的方法是使用 using 声明(using declaration),它具有如下的形式:

1
using namespace::name
1
2
3
4
5
6
7
8
9
10
11
12
#include <iostream>
// using declaration; when we use the name cin, we get the one from the namespace
using std::cin;

int main()
{
int i;
cin >> i; // ok: cin is a synonym for std::cin
cout << i; // error: no using declaration; we must use the full name
std::cout << i; // ok: explicitly use cout from namepsace std
return 0;
}

每个名字都需要独立的 using 声明

​ 按照规定,每个 using 声明引入命名空间中的一个成员。

头文件不应包含 using 声明

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

3.2 标准库类型 string

​ 标准库类型 string 表示可变长的字符序列,使用 string 类型必须首先包含 string 头文件。作为标准库的一部分,string 定义在命名空间 std 中。

3.2.1 定义和初始化 string 对象

1
2
3
4
5
6
string s1;  // default initialization; s1 is the empty string
string s2(s1); // s2 is a copy of s1
string s2 = s1; // equivalent to s2(s1), s2 is a copy of s1
string s3("value"); // s3 is a copy of the string literal, not including the null
string s3 = "value" // equivalent to s3("value"), s3 is a copy of the string literal
string s4(10, 'c'); // s4 is cccccccccc

直接初始化和拷贝初始化

​ 如果使用等号(=)初始化一个变量,执行的是拷贝初始化(copy initialization)。与之相反,不使用等号,执行的是直接初始化(direct initialization)。

3.2.2 string 对象上的操作

00
00

读写 string 对象

1
2
3
4
5
6
7
8
// Note: #include and using declarations must be added to compile this code
int main()
{
string s; // empty string
cin >> s; // read a whitespace-separated string into s
cout << s << endl; // write s to the output
return 0;
}

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

读取未知数量的 string 对象

1
2
3
4
5
6
7
int main()
{
string word;
while (cin >> word) // read until end-of-file
cout << word << endl; // write each word followed by a new line
return 0;
}

使用 getline 读取一整行

​ getline 函数的参数是一个输入流和一个 string 对象,函数从给定的输入流中读入内容,内容遇到换行符为止(注意换行符也被读进来了),尔后把所读的内容存入到那个 string 对象中去(不存换行符)。getline 一遇到换行符就结束读取并返回结果。

1
2
3
4
5
6
7
8
int main()
{
string line;
// read input a line at a time until end-of-file
while (getline(cin, line))
cout << line << endl;
return 0;
}

string::size_type 类型

​ size 函数返回的是一个 string::size_type 类型的值,它是一个无符号类型的值,是一个无符号整型数。如果一条表达式中已经有了 size() 函数就不要再使用 int 了,这样可以避免混用 int 和 unsigned 可能带来的问题。例如,假设 n 是一个具有负值的 int,则表达式 s.size() < n 的判断结果几乎肯定是 true。因为负值 n 会自动转换成一个比较大的无符号值。

比较 string 对象

​ string 类定义了几种用于比较字符串的运算符。这些比较运算符逐一比较 string 对象中的字符,并且对大小写敏感。相等性运算符(== 和 !=)与关系型运算符(<, <=, >, >=)都按照(大小写敏感的)字典顺序进行比较。

字面值和 string 对象相加

​ 必须确保每个加法运算符的两侧的运算对象至少有一个是 string:

1
2
3
4
string s4 = s1 + ", ";  // ok: adding a string and a literal
string s5 = "hello" + ", "; // error: no string operand
string s6 = s1 + ", " + "world"; // ok: each + has a string operand
string s7 = "hello" + ", " + s2; // error: can't add string literals

​ 切记,字符串字面值与 string 是不同的类型。

3.2.3 处理 string 对象中的字符

​ cctype 头文件里的函数 01

处理每个字符?使用基于范围的 for 语句

for (declaration: expression)

statement

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

1
2
3
4
5
6
7
8
9
10
string s("Hello World!!!");
// punct_cnt has the same type that s.size returns; see § 2.5.3 (p. 70)
decltype(s.size()) punct_cnt = 0;
// count the number of punctuation characters in s
for (auto c : s) // for every char in s
if (ispunct(c)) // if the character is punctuation
++punct_cnt; // increment the punctuation counter
cout << punct_cnt << " punctuation characters in " << s << endl;
// The output of this program is:
// 3 punctuation characters in Hello World!!!

基于范围 for 语句改变字符串的字符

​ 如果想要改变 string 对象中字符的值,必须把循环变量定义为引用类型

1
2
3
4
5
6
7
string s("Hello World!!!");
// convert s to uppercase
for (auto &c : s) // for every char in s (note: c is a reference)
c = toupper(c); // c is a reference, so the assignment changes the char in s
cout << s << endl;
// The output of this code is:
// HELLO WORLD!!!

只处理一部分字符?

​ 一种方法是使用下标,另一种是使用迭代器。

​ 下标运算符([ ])接受的参数是 string::size_type 类型的值,这个参数表示要访问的字符的位置;返回值是该位置上字符的引用。下标从 0 开始,且必须大于等于 0 而小于 s.size()。下标的值称为 ”下标“ 或者 ”索引”。

​ 在访问指定字符前,首先检查字符是否为空,不管什么时候,对 string 对象使用下标,都要确认在那个位置上确实有值。

使用下标进行迭代

1
2
3
4
5
// process characters in s until we run out of characters or we hit a whitespace
for (decltype(s.size()) index = 0;index != s.size() && !isspace(s[index]); ++index)
s[index] = toupper(s[index]); // capitalize the current character
// This program generates:
// SOME string

3.3 标准库类型 vector

标准库类型 vector 表示对象的集合,其中所有对象的类型都相同。集合中的每个对象都有一个与之对应的索引。vector 也常被称作容器(container)。

要想使用 vector,和使用 string 一样,必须包含适当的头文件:

1
2
#inlcude <iostream>
using std::vector

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

对于类模板,我们需要提供信息指定模板实例化成什么样的类,提供方法如下:

1
2
3
vector<int> ivec;  // ivec holds objects of type int
vector<Sales_item> Sales_vec; // holds Sales_items
vector<vector<string>> file; // vector whose elements are vectors

vector 能容纳绝大类型的对象作为其元素,但是因为引用不是对象,所以不存在包含引用的 vector。除此之外,其他大多数内置类型和类类型都可以构成 vector 对象,甚至是 vector。在早期版本中,若 vector 里的元素 还是 vector,则定义方法与 C++11 有所不同,必须在外层 vector 对象的右尖括号和其它元素类型之间添加一个空格,如:

1
2
vector<vector<int> >  // old C++
vector<vector<int>> // C++11

3.3.1 定义和初始化 vector 对象

​ 和任意一种类类型一样,vector 模板控制着定义和初始化向量的方法。 02

列表初始化 vector 对象

​ 用花括号括起来得 0 个或多个初始元素值被赋给 vector 对象:

1
vector<string> articles = {"a", "an", "the"};  // the vector has three elements; the first holds the string "a", the second holds "an", and the last is "the".

C++ 提供了集中不同得初始化方式,大多可以等价使用,但有例外:其一,使用拷贝初始化时(使用 = 时),只能提供一个初值;其二,如果提供的是一个类内初始值,则只能使用拷贝初始化或使用花括号初始化。第三种特殊的要求时,如果提供的时初始元素值得列表,则只能把初始值都放在花括号里进行列表初始化,而不能放在圆括号里:

1
2
vector<string> v1{"a", "an", "the"};  // list initialization
vector<string> v2("a", "an", "the"); // error

创建指定数量的元素

1
2
vector<int> ivec(10, -1);  // ten int elements, each initialized to -1
vector<string> svec(10, "hi!"); // ten strings; each element is "hi!"

值初始化

通常情况下,只提供 vector 对象容纳的元素数量而不用略去初始值。此时,库会创建一个值初始化的(value-initialized)元素初值,并把它付给容器中所有元素,这个初值由 vector 对象中的元素类型决定。

列表初始值还是元素数量?

注意花括号和圆括号:

1
2
3
4
5
6
7
8
vector<int> v1(10);  // v1 has ten elements with value 0
vector<int> v2{10}; // v2 has one element with value 10
vector<int> v3(10, 1); // v3 has ten elements with value 1
vector<int> v4{10, 1}; // v4 has two elements with values 10 and 1
vector<string> v5{"hi"}; // list initialization: v5 has one element
vector<string> v6("hi"); // error: can't construct a vector from a string literal
vector<string> v7{10}; // v7 has ten default-initialized elements
vector<string> v8{10, "hi"}; // v8 has ten elements with value "hi"

3.3.2 向 vector 对象中添加元素

​ 有时,创建一个 vector 对象时并不清楚实际所需元素个数,元素的值也经常无法确定。此时,更好的处理方法就是先创建一个空 vector,然后再运行时再利用 vector 的成员函数 push_back 向其中添加元素,例如:

1
2
3
4
5
6
7
8
9
10
11
vector<int> v2;  // empty vector
for (int i = 0; i != 100; ++i)
v2.push_back(i); // append sequential integers to v2
// at end of loop v2 has 100 elements, values 0 . . . 99

// read words from the standard input and store them as elements in a vector
string word;
vector<string> text; // empty vector
while (cin >> word) {
text.push_back(word); // append word to text
}

​ 如果循环体内部包含有向 vector 对象添加元素的语句,则不能使用范围 for 循环。范围 for 语句体内不应改变其所遍历序列的大小。

3.3.3 其他 vector 操作

03 ​ 访问 vector 对象中元素的方法也是通过元素再 vector 对象中的位置,与访问 string 对象类似:

1
2
3
4
5
6
vector<int> v{1,2,3,4,5,6,7,8,9};
for (auto &i : v) // for each element in v (note: i is a reference)
i *= i; // square the element value
for (auto i : v) // for each element in v
cout << i << " "; // print the element
cout << endl;

​ vector 对象的下标也是从 0 开始计起,vector 对象(以及 string 对象)的下标运算符可用于访问已存在的元素,而不能用于添加元素。

3.4 迭代器介绍

​ 迭代器也提供了对对象的间接访问,其对象是容器中的元素或者 string 对象中的字符。使用迭代器可以访问某个元素,迭代器也能从一个元素移动到另一个元素。迭代器有有效和无效之分,有效的迭代器或者指向某个元素,或者指向容器中尾元素的下一位置;其它所有情况都属于无效。

3.4.1 使用迭代器

​ 与指针不一样的是,获取迭代器不是使用取地址符,有迭代器的类型同时拥有返回迭代器的成员。比如,这些类型都拥有名为 begin 和 end 的成员,其中 begin 成员负责返回指向第一个元素(或第一个字符)的迭代器。如:

1
2
3
// the compiler determines the type of b and e; see § 2.5.2 (p. 68)
// b denotes the first element and e denotes one past the last element in v
auto b = v.begin(), e = v.end() ; // b and e have the same type

​ end 成员则负责返回指向容器(或 string 对象)“尾元素的下一位置(one past the end)” 的迭代器,即一个本不存在的 “尾后” 元素。这样的迭代器并没有什么实际含义,仅是个标记,表示问哦们已经处理完了容器中的所有元素。end 成员返回的迭代器被称作尾后迭代器(off-the-end iterator)或者称为尾迭代器(end iterator)。若容器为空,begin 和 end 返回的是同一个迭代器,都是尾后迭代器。

迭代器运算符

04
04

​ 与指针类似,可以解引用迭代器来获取它所指示的元素,执行解引用的迭代器必须合法并确实指示某个元素。

1
2
3
4
5
string s("some string");
if (s.begin() != s.end()) { // make sure s is not empty
auto it = s.begin(); // it denotes the first character in s
*it = toupper(*it); // make that character uppercase
}

将迭代器从一个元素移动到另外一个元素

​ 迭代器使用 ++ 运算符将一个元素移动到下一个元素,迭代器的递增是将迭代器 “向前移动一个位置”。end 返回的迭代器不实际指示某个元素,所以不能对其进行递增或解引用的操作。

1
2
3
// process characters in s until we run out of characters or we hit a whitespace
for (auto it = s.begin(); it != s.end() && !isspace(*it); ++it)
*it = toupper(*it); // capitalize the current character

迭代器类型

​ 实际上,拥有迭代器的标准类型使用 iterator 和 const_iterator 来表示迭代器的类型:

1
2
3
4
vector<int>::iterator it;  // it can read and write vector<int> elements
string::iterator it2; // it2 can read and write characters in a string
vector<int>::const_iterator it3; // it3 can read but not write elements
string::const_iterator it4; // it4 can read but not write characters

​ 我们认定某个类型是迭代器当且仅当它支持一套操作,这套操作使得我们能访问元素或者从某个元素移动到另外一个元素。

begin 和 end 运算符

​ begin 和 end 返回的具体类型由对象是否是常量决定,如果是常量,begin 和 end 返回 const_iterator;如果对象不是常量,返回 iterator:

1
2
3
4
vector<int> v;
const vector<int> cv;
auto it1 = v.begin(); // it1 has type vector<int>::iterator
auto it2 = cv.begin(); // it2 has type vector<int>::const_iterator

​ C++11 引入两个新的函数,cbegin 和 cend 专门得到 const_iterator 类型的返回值:

1
auto it3 = v.cbegin();  // it3 has type vector<int>::const_iterator

结合解引用和成员访问操作

1
2
3
(*it).empty()  // dereferences it and calls the member empty on the resulting object
*it.empty() // error: attempts to fetch the member named empty from it
// but it is an iterator and has no member named empty

​ C++ 定义了箭头运算符(- >)将解引用和成员访问两个操作结合在一起。

1
2
3
// print each line in text up to the first blank line
for (auto it = text.cbegin(); it != text.cend() && !it->empty(); ++it)
cout << *it << endl;

3.4.2 迭代器的算术运算

05
05
1
2
3
4
5
// compute an iterator to the element closest to the midpoint of vi
auto mid = vi.begin() + vi.size() / 2;

if (it < mid)
// process elements in the first half of vi

使用迭代器运算

1
2
3
4
5
6
7
8
9
10
11
12
// text must be sorted
// beg and end will denote the range we're searching
auto beg = text.begin(), end = text.end();
auto mid = text.begin() + (end - beg)/2; // original midpoint
// while there are still elements to look at and we haven't yet found sought
while (mid != end && *mid != sought) {
if (sought < *mid) // is the element we want in the first half?
end = mid; // if so, adjust the range to ignore the second half
else // the element we want is in the second half
beg = mid + 1; // start looking with the element just after mid
mid = beg + (end - beg)/2; // new midpoint
}

3.5 数组

​ 不清楚元素的确切个数,请使用 vector。

3.5.1 定义和初始化内置数组

1
2
3
4
5
6
7
unsigned cnt = 42;  // not a constant expression
constexpr unsigned sz = 42; // constant expression
// constexpr see § 2.4.4 (p. 66)
int arr[10]; // array of ten ints
int *parr[sz]; // array of 42 pointers to int
string bad[cnt]; // error: cnt is not a constant expression
string strs[get_size()]; // ok if get_size is constexpr, error otherwise

定义数组必须指定数组的类型,不允许用 auto 关键字,且不存在引用的数组。

显式初始化数组元素

​ 对数组的元素进行列表初始化,此时允许忽略数组的维度。

1
2
3
4
5
6
const unsigned sz = 3;
int ia1[sz] = {0,1,2}; // array of three ints with values 0, 1, 2
int a2[] = {0, 1, 2}; // an array of dimension 3
int a3[5] = {0, 1, 2}; // equivalent to a3[] = {0, 1, 2, 0, 0}
string a4[3] = {"hi", "bye"}; // same as a4[] = {"hi", "bye", ""}
int a5[2] = {0,1,2}; // error: too many initializers

字符数组的特殊性

​ 字符数组可以用字符串字面值进行初始化,注意字符串字面值结尾还有一个空字符:

1
2
3
4
5
char a1[] = {'C', '+', '+'};  // list initialization, no null
char a2[] = {'C', '+', '+', '\0'}; // list initialization, explicit null
char a3[] = "C++"; // null terminator added
automatically
const char a4[6] = "Daniel"; // error: no space for the null!

不允许拷贝和赋值

​ 不能将数组的内容拷贝给其他数组作为其初始值,也不能用数组为其他数组复制。

理解复杂的数组声明

1
2
3
4
int *ptrs[10];  // ptrs is an array of ten pointers to int
int &refs[10] = /* ? */; // error: no arrays of references
int (*Parray)[10] = &arr; // Parray points to an array of ten ints
int (&arrRef)[10] = arr; // arrRef refers to an array of ten ints

​ 按照由内向外的顺序阅读。

3.5.2 访问数组元素

​ 使用数组下标的时候,通常将其定义为 size_t 类型。size_t 是一种机器相关的无符号类型,它被设计得足够大以便能表示内存中任意对象的大小。

​ 与 vector 和 string 一样,数组的下标是否在合理范围之内由程序员负责检查。大多数常见的安全问题都源于缓冲区溢出错误。当数组或其他类似数据结构的下标越界并试图访问非法内存区域时,就会产生此类错误。

3.5.3 指针和数组

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
string nums[] = {"one", "two", "three"};  // array of strings
string *p = &nums[0]; // p points to the first element in nums
string *p2 = nums; // equivalent to p2 = &nums[0]

int ia[] = {0,1,2,3,4,5,6,7,8,9}; // ia is an array of ten ints
auto ia2(ia); // ia2 is an int* that points to the first element in ia
ia2 = 42; // error: ia2 is a pointer, and we can't assign an int to a pointer

auto ia2(&ia[0]); // now it's clear that ia2 has type int*

// ia3 is an array of ten ints
decltype(ia) ia3 = {0,1,2,3,4,5,6,7,8,9};
ia3 = p; // error: can't assign an int* to an array
ia3[4] = i; // ok: assigns the value of i to an element in ia3

​ 使用 decltype 关键字时上述转换不会发生。

指针也是迭代器

​ vector 和 string 的迭代器支持的运算,数组的指针全部支持。例如,允许使用递增运算符将指向数组元素的指针向前移动到下一个位置上:

1
2
3
int arr[] = {0,1,2,3,4,5,6,7,8,9};
int *p = arr; // p points to the first element in arr
++p; // p points to arr[1]

​ 获取尾元素之后的那个并不存在的元素的地址:

1
int *e = &arr[10];  // pointer just past the last element in arr

​ 这里显然索引了一个不存在的元素,唯一的用处是提供地址初始化 e,不能对尾后指针执行解引用或递增的操作。

标准库函数 begin 和 end

1
2
3
int ia[] = {0,1,2,3,4,5,6,7,8,9};  // ia is an array of ten ints
int *beg = begin(ia); // pointer to the first element in ia
int *last = end(ia); // pointer one past the last element in ia

指针运算

1
2
3
4
5
6
7
8
9
10
constexpr size_t sz = 5;
int arr[sz] = {1,2,3,4,5};
int *ip = arr; // equivalent to int *ip = &arr[0]
int *ip2 = ip + 4; // ip2 points to arr[4], the last element in arr

// ok: arr is converted to a pointer to its first element; p points one past the end of arr
int *p = arr + sz; // use caution -- do not dereference!
int *p2 = arr + 10; // error: arr has only 5 elements; p2 has undefined value

auto n = end(arr) - begin(arr); // n is 5, the number of elements in arr

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

​ 如果两个指针分别指向不相关的对象,则不能比较它们。两个空指针允许彼此相减,结果为 0。

下标和指针

​ 对数组执行下标运算其实是对指向数组元素的指针执行下标运算:

1
2
3
4
5
6
7
8
9
int i = ia[2];  // ia is converted to a pointer to the first element in ia
// ia[2] fetches the element to which (ia + 2) points
int *p = ia; // p points to the first element in ia
i = *(p + 2); // equivalent to i = ia[2]

int *p = &ia[2]; // p points to the element indexed by 2
int j = p[1]; // p[1] is equivalent to *(p + 1),
// p[1] is the same element as ia[3]
int k = p[-2]; // p[-2] is the same element as ia[0]

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

3.5.4 C 风格字符串

​ 尽量不要使用。

字符串字面值是一种通用结构的实例,这种结构即是 C++ 由 C 继承而来的 C 风格字符串。C 风格字符串不是一种类型,而是为了表达和使用字符串而形成的一种约定俗成的写法。按此风格书写的字符串存放在字符数组中并以空字符结束。

C 标准库 String 函数

06
06

​ 传入此类函数的指针必须指向以空字符作为结束的数组:

1
2
char ca[] = {'C', '+', '+'};  // not null terminated
cout << strlen(ca) << endl; // disaster: ca isn't null terminated

比较字符串

​ 比较两个 C 风格字符串的方法和之前学习过的比较标准库 string 对象的方法大相径庭。比较标准库 string 对象的时候,用的是普通的关系运算符和相等性运算符:

1
2
3
string s1 = "A string example";
string s2 = "A different string";
if (s1 < s2) // false: s2 is less than s1

​ 若把这些运算符用在两个 C 风格字符串上,实际比较的是指针而非字符串本身:

1
2
3
const char ca1[] = "A string example";
const char ca2[] = "A different string";
if (ca1 < ca2) // undefined: compares two unrelated addresses

​ 要想比较两个 C 风格字符串需要调用 strcmp 函数,如果两个字符串相等,返回 0;如果前面的字符串比较大,返回正值;反之,返回负值:

1
if (strcmp(ca1, ca2) < 0)  // same effect as string comparison s1 < s2

目标字符串的大小由调用者指定

​ 连接或拷贝 C 风格字符串需要使用 strcat 函数和 strcpy 函数。使用这两个函数,还必须提供一个用于存放结果字符串的数组,该数组必须足够大以便容纳下结果字符串及末尾的空字符:

1
2
3
4
// disastrous if we miscalculated the size of largeStr
strcpy(largeStr, ca1); // copies ca1 into largeStr
strcat(largeStr, " "); // adds a space at the end of largeStr
strcat(largeStr, ca2); // concatenates ca2 onto largeStr

3.5.5 与旧代码的接口

​ 很多 C++ 程序在标准库之前就已经写成了,它们肯定没用到 string 和 vector 类型。因此,为了与充满了数组和 C 风格字符串的代码衔接,C++ 专门提供了一些功能。

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

​ 用 string 对象直接初始化指向字符的指针,调用 c_str 函数:

1
2
char *str = s;  // error: can't initialize a char* from a string
const char *str = s.c_str(); // ok

使用数组初始化 vector 对象

1
2
3
int int_arr[] = {0, 1, 2, 3, 4, 5};
// ivec has six elements; each is a copy of the corresponding element in int_arr
vector<int> ivec(begin(int_arr), end(int_arr));

3.6 多维数组

​ 多维数组其实就是数组的数组。

1
2
3
int ia[3][4];  // array of size 3; each element is an array of ints of size 4
// array of size 10; each element is a 20-element array whose elements are arrays of 30 ints
int arr[10][20][30] = {0}; // initialize all elements to 0

多维数组的初始化

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
int ia[3][4] = {  // three elements; each element is an array of size 4
{0, 1, 2, 3}, // initializers for the row indexed by 0
{4, 5, 6, 7}, // initializers for the row indexed by 1
{8, 9, 10, 11} // initializers for the row indexed by 2
};

// equivalent initialization without the optional nested braces for each row
int ia[3][4] = {0,1,2,3,4,5,6,7,8,9,10,11};

// explicitly initialize only element 0 in each row
int ia[3][4] = {{ 0 }, { 4 }, { 8 }};

// explicitly initialize row 0; the remaining elements are value initialized
int ix[3][4] = {0, 3, 6, 9};

多维数组下标的引用

​ 数组的每个维度对应一个下标运算符。

1
2
3
4
5
6
7
8
9
10
constexpr size_t rowCnt = 3, colCnt = 4;
int ia[rowCnt][colCnt]; // 12 uninitialized elements
// for each row
for (size_t i = 0; i != rowCnt; ++i) {
// for each column within the row
for (size_t j = 0; j != colCnt; ++j) {
// assign the element's positional index as its value
ia[i][j] = i * colCnt + j;
}
}

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

1
2
3
4
5
6
size_t cnt = 0;
for (auto &row : ia) // for every element in the outer array
for (auto &col : row) { // for every element in the inner array
col = cnt; // give this element the next value
++cnt; // increment cnt
}

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

指针和多维数组

​ 当使用多维数组的名字时,也会自动转成指向数组首元素的指针。因为多维数组实际上时=是数组的数组,所以由多维数组名转换得来的指针实际上是指向第一个内层数组的指针:

1
2
3
int ia[3][4];  // array of size 3; each element is an array of ints of size 4
int (*p)[4] = ia; // p points to an array of four ints
p = &ia[2]; // p now points to the last element in ia

​ 通过使用 auto 或者 decltype 就能尽可能地避免在数组前面上加一个指针类型了:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// print the value of each element in ia, with each inner array on its own line
// p points to an array of four ints
for (auto p = ia; p != ia + 3; ++p) {
// q points to the first element of an array of four ints; that is, q points to an int
for (auto q = *p; q != *p + 4; ++q)
cout << *q << ' ';
cout << endl;
}

// p points to the first array in ia
for (auto p = begin(ia); p != end(ia); ++p) {
// q points to the first element in an inner array
for (auto q = begin(*p); q != end(*p); ++q)
cout << *q << ' '; // prints the int value to which q points
cout << endl;
}

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

1
2
3
4
5
6
7
8
using int_array = int[4];  // new style type alias declaration; see § 2.5.1 (p.68)
typedef int int_array[4]; // equivalent typedef declaration; § 2.5.1 (p.67)
// print the value of each element in ia, with each inner array on its own line
for (int_array *p = ia; p != ia + 3; ++p) {
for (int *q = *p; q != *p + 4; ++q)
cout << *q << ' ';
cout << endl;
}
# C++

Comments

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×