本 Wiki 开启了 HTTPS。但由于同 IP 的 Blog 也开启了 HTTPS,因此本站必须要支持 SNI 的浏览器才能浏览。为了兼容一部分浏览器,本站保留了 HTTP 作为兼容。如果您的浏览器支持 SNI,请尽量通过 HTTPS 访问本站,谢谢!
这里会显示出您选择的修订版和当前版本之间的差别。
两侧同时换到之前的修订记录前一修订版后一修订版 | 前一修订版 | ||
cs:programming:cpp:cpp_primer:3_str_vec_arry [2024/01/14 13:46] – 移除 - 外部编辑 (未知日期) 127.0.0.1 | cs:programming:cpp:cpp_primer:3_str_vec_arry [2024/04/17 23:25] (当前版本) – [多维数组] codinghare | ||
---|---|---|---|
行 1: | 行 1: | ||
+ | ======字符串, | ||
+ | C++ Primer 笔记 第三章\\ | ||
+ | ---- | ||
+ | ====对命名空间使用 using 声明==== | ||
+ | 之前对命名空间的使用都是以 '' | ||
+ | <code cpp> | ||
+ | std::cin //using cin in namespace std; | ||
+ | </ | ||
+ | 当然,每次使用之前都要加上命名空间的名字确实不太方便。C++ 提供了 '' | ||
+ | <code cpp> | ||
+ | # | ||
+ | using std::cin; //using declaration for cin in namespace std | ||
+ | int i; | ||
+ | cin >> i; | ||
+ | </ | ||
+ | 通过 '' | ||
+ | <code cpp> | ||
+ | /*using declaration for 3 different names*/ | ||
+ | using std::cin; | ||
+ | using std::cout; | ||
+ | using std::endl; | ||
+ | int i; | ||
+ | cin >>i; | ||
+ | cout << i << endl; | ||
+ | </ | ||
+ | |||
+ | <WRAP center round important 100%> | ||
+ | 注意:不要把命名空间的声明放到 header 里。如果 header 里有命名空间的声明,那么所有包含了这个 header 的程序都会有同样命名空间的声明,这样很可能会导致未知的冲突。 | ||
+ | </ | ||
+ | |||
+ | ====标准库类型string==== | ||
+ | string是一个字符组成的,长度可变的序列。在C++中,使用string前需要: | ||
+ | <code cpp linnums: | ||
+ | #include < | ||
+ | using std:: | ||
+ | </ | ||
+ | 标准库里的类型大多都经过精心设计和优化,对一般的运用来说是足够了的。 | ||
+ | ===定义和初始化string=== | ||
+ | string用string关键字定义。string的初始化有好几种方法,可以大致分为**直接初始化和复制初始化**。这两者的区别主要是看在初始化的时候有没有用上 “=” 运算符。举个例子: | ||
+ | <code cpp linenums: | ||
+ | string s0; //default initialization, | ||
+ | string s1(" | ||
+ | string s2(10, ' | ||
+ | string s3 = s0; //s3 is a copy of s0; | ||
+ | string s4(s0); // | ||
+ | string s5 = " | ||
+ | </ | ||
+ | ===Operations on strings=== | ||
+ | {{ cs: | ||
+ | ==string的读 / 写== | ||
+ | string 的读入操作有两种常用的方式:''>>'' | ||
+ | ''>>'' | ||
+ | \\ \\ | ||
+ | 因为 string 是左值,因此 stream 运算符在 string 中是可以连用的: | ||
+ | <code cpp> | ||
+ | string s1, s2; | ||
+ | cin >> s1 >> s2; | ||
+ | cout << s1 << s2 << endl; | ||
+ | </ | ||
+ | ==string 的连续读写== | ||
+ | 如果需要读取不确定数量的 string,可以使用之前的 '' | ||
+ | <code cpp> | ||
+ | string word; | ||
+ | //false when hit the end of line or invalid input | ||
+ | while(cin >> word){ | ||
+ | cout << word << endl; | ||
+ | } | ||
+ | </ | ||
+ | ==getline for entire line== | ||
+ | 如果我们希望同时也读入 whitespace 字符, | ||
+ | |||
+ | '' | ||
+ | <code cpp> | ||
+ | string line; | ||
+ | while (getline(cin, | ||
+ | cout << strLine << endl; | ||
+ | } | ||
+ | </ | ||
+ | ==empty() & size()== | ||
+ | '' | ||
+ | \\ \\ | ||
+ | '' | ||
+ | <code cpp> | ||
+ | while (getline(cin, | ||
+ | if(!string.empty()) | ||
+ | cout << string << endl; | ||
+ | } | ||
+ | </ | ||
+ | |||
+ | |||
+ | |||
+ | 同样的,// | ||
+ | 不过需要注意的是,// | ||
+ | |||
+ | // | ||
+ | * // | ||
+ | * 不能把 // | ||
+ | ==Comparing strings== | ||
+ | String 之间的比较分为比较**是否相等**和比较**大小**两大类。比较是**区分大小写**的。 | ||
+ | \\ \\ | ||
+ | 当比较相等的时候,使用 equality operator ('' | ||
+ | \\ \\ | ||
+ | 当比较大小的时候,使用 relational operator (''<'' | ||
+ | * 如果两个 string 长度不相同,且长度较短的 string 内所有的字符与较长 string **对应位置上的字符都相同**,那么较长的 string 比较大。 | ||
+ | * 如果两个 string 对应位置上的字符不完全相同,那么该比较将会转化为对两个 string 上**第一位不相同的字符**的比较(ASCII 值)。 | ||
+ | < | ||
+ | " | ||
+ | " | ||
+ | </ | ||
+ | |||
+ | ==string 的其他操作== | ||
+ | * string 的赋值:将一个 string 拷贝给另外一个 string | ||
+ | * string 的相加:将两个 string 连接起来,将加号右边的 string 加到左边 string 的末尾 | ||
+ | * string 与 literal 的混合相加:将 string 与 literal 连起来 | ||
+ | <code cpp> | ||
+ | string s1 = " | ||
+ | s2 = s1; // | ||
+ | s3 = s1 + s2; //adding two strings | ||
+ | s1 += s2; //adding s2 to the end of s1 | ||
+ | s4 = s1 + ", " + s2; //adding literals and strings | ||
+ | </ | ||
+ | 对于 string 和 literal 的直接相加,我们要注意的是 **+ 号两边的 operand 必须至少有一个是 string 的对象**。注意在有多个 operand 的时候,在遵循当前运算优先级的情况下,如果 + 号两边没有 string 对象,也是非法的,比如: | ||
+ | <code cpp> | ||
+ | string s2; | ||
+ | string s = " | ||
+ | </ | ||
+ | ===string中的字符操作=== | ||
+ | C++ 通过头文件 '' | ||
+ | {{ cs: | ||
+ | <WRAP center round tip 100%> | ||
+ | 在对 string 中的字符操作过程中,C++ 继承了 C 的字符操作库 ctype.h。不过在 C++ 中,有属于 C++ 自己的版本来表达这个库,那就是去掉扩展名(.h suffix), 然后在原有的库名前加一个 c (c 开头表示这个 header 来源于 c 标准库的一部分),这样 '' | ||
+ | </ | ||
+ | |||
+ | ==string的字符遍历== | ||
+ | C++11中提出了一种新的方法遍历string: | ||
+ | <code cpp> | ||
+ | for (declaration : expression) | ||
+ | statement | ||
+ | </ | ||
+ | expression 表示某种可以代表【一系列元素组成的序列】的**对象**,declaration 定义了用于访问字符串中元素的变量。以字符串来为例来说: | ||
+ | <code cpp> | ||
+ | string str (" | ||
+ | for (auto c : str) | ||
+ | cout << | ||
+ | </ | ||
+ | 这里的 '' | ||
+ | <code cpp> | ||
+ | string s(" | ||
+ | decltpye(s.size()) punct_cnt = 0; | ||
+ | for (auto c : s) { | ||
+ | if (ispunct(c)) { | ||
+ | ++punct_cnt; | ||
+ | } | ||
+ | cout << punct_cnt << endl; | ||
+ | } | ||
+ | </ | ||
+ | 这个例子中,因为 '' | ||
+ | 当然操作必不可少的要牵涉到修改原string的内容。需要注意的是:C++ range for 使用< | ||
+ | <code cpp> | ||
+ | string str (" | ||
+ | for (auto &c : str)// c is reference here | ||
+ | c = toupper(c); | ||
+ | </ | ||
+ | ==Processing Only Some Characters== | ||
+ | 如果只想处理字符串中的某些字符,有两种方法: | ||
+ | \\ \\ | ||
+ | 第一种方法是使用下标(// | ||
+ | <WRAP center round alert 100%> | ||
+ | 通过下标访问某个元素前,必须保证下标所在位置在字符串的范围内,否则会导致 undefined. 也就是说,通过下标访问 empty string 的行为是 undefined 的行为! | ||
+ | </ | ||
+ | 因此,使用下标访问字符串之前,最好检查字符串是否是空字符串: | ||
+ | <code cpp> | ||
+ | string s(" | ||
+ | if (!s.empty()) { | ||
+ | s[0] = toupper(s[0]); | ||
+ | } | ||
+ | </ | ||
+ | 如果需要按 word 为单位来进行修改操作,可以通过判断**当前的字符是不是空格**来得知之前的内容是否是为一个完整的 word。这种情况下使用标准的 for 循环遍历即可: | ||
+ | <code cpp> | ||
+ | string str (" | ||
+ | for(decltype(s.size()) index = 0; | ||
+ | index != s.size() && !isspace(s[index]; | ||
+ | s[index] = toupper(s[index]); | ||
+ | </ | ||
+ | |||
+ | ====标准库类型Vector==== | ||
+ | //Vector// 是 C++ 标准库中提供的一种容器(// | ||
+ | <code cpp> | ||
+ | #Include < | ||
+ | using std:: | ||
+ | </ | ||
+ | Vector 本身是类模板,而类模板本身是一系列指令,用于指导编译器生成类和方法。我们把这个生成的过程称为**实例化( // | ||
+ | <code cpp> | ||
+ | vector< | ||
+ | vector< | ||
+ | vector< | ||
+ | </ | ||
+ | 由于 vector 是模板,不是类型。所以**vector进行实例化必须指定对象的类型**,所以才有了vector< | ||
+ | \\ \\ | ||
+ | 需要注意的是,早期的 C++,定义存放vector的vector跟C++11有一点不同: | ||
+ | <code cpp > | ||
+ | vector< | ||
+ | vector< | ||
+ | </ | ||
+ | ===Vector的定义和初始化=== | ||
+ | * 由于我们能够很有效的在 runtime 期间对 vector 进行操作,所以最普遍的 vector 初始化方式就是定义一个**空**的 vector。 | ||
+ | * 如果希望使用其他 vector 进行初始化,那么 initializer vector 必须与目标 vector 的**类型相同**。 | ||
+ | <code cpp> | ||
+ | vector< | ||
+ | vector< | ||
+ | vector< | ||
+ | vector< | ||
+ | vector< | ||
+ | vector< | ||
+ | vector< | ||
+ | </ | ||
+ | ==List Initializing a vector== | ||
+ | vector listing initialization 是 C++ 11的新特性,允许我们用 list (大括号) 的形式对 vector 进行初始化: | ||
+ | <code cpp> | ||
+ | vector< | ||
+ | </ | ||
+ | 需要注意的是,initializer 的数量是受初始化的形式限制的。如果初始化使用的 copy 的形式,比如(赋值 '' | ||
+ | <code cpp> | ||
+ | vector< | ||
+ | </ | ||
+ | ==创建指定数量的元素== | ||
+ | 如果需要创建指定元素个数的 vector,我们使用括号进行初始化: | ||
+ | <code cpp> | ||
+ | vector< | ||
+ | </ | ||
+ | ==Value Initialization== | ||
+ | 使用值对 vector 初始化的时候,可以只指定 vector 的大小。vector 会自动创建 value-initialized 元素来进行初始化。通常情况下,如果 vector 元素的类型是 Build-in type,那么初始值为 '' | ||
+ | \\ \\ | ||
+ | 值初始化有两种形式上的限制: | ||
+ | * 某些类在初始化的时候要求显式的给与 initializer;对于这种不能默认初始化的类,我们必须同时给与大小与初始值。 | ||
+ | * 如果使用单个元素的值初始化,那么必须使用直接初始化形式(copy 形式的初始化必须提供元素个数) | ||
+ | ==List Initializer or Element Count?== | ||
+ | 需要注意的是,使用大括号的初始化意味着完全不同的意义: | ||
+ | <code cpp> | ||
+ | vector< | ||
+ | vector< | ||
+ | </ | ||
+ | 在使用了 list initialization 后,编译器会尽可能将 curly brace 内的内容视作 list initialization 的初始值;只有在无法使用这些内容进行 list initialization 的时候,编译器才会考虑其他的办法来对 vector 进行初始化。比如如下的例子,当 vector 的类型与初始化值不匹配的时候: | ||
+ | <code cpp> | ||
+ | vector< | ||
+ | vector< | ||
+ | </ | ||
+ | 这种行为被称为 list initialization 的 construct 功能。需要注意的是,**普通的括号初始化则没有这种construct功能**。 | ||
+ | |||
+ | ===Vector的操作=== | ||
+ | Vector 由于其可以在 runtime 下修改的特性,往往用在比较 flexble 的场景中。C++ 中提供了非常多的函数用于对 vector 的数据进行操作。 | ||
+ | ==为 vector 添加元素== | ||
+ | C++ 使用 '' | ||
+ | <code cpp> | ||
+ | vector< | ||
+ | for (int i = 0; i != 100; ++i) | ||
+ | v2.push_back(i); | ||
+ | // at end of loop v2 has 100 elements, values 0 . . . 99 | ||
+ | </ | ||
+ | 可以看到数字是一个一个被添加到 vector 的末尾的。另外一个应用则是将每次输入的 word 添加到 vector 中: | ||
+ | <code cpp> | ||
+ | string word; | ||
+ | vector< | ||
+ | while(cin >> word) { | ||
+ | v.push_back(word); | ||
+ | } | ||
+ | </ | ||
+ | <WRAP center round tip 100%> | ||
+ | 由于 STL 对 vector 添加元素有性能上的要求,因此在定义的时候指定 vector 的大小往往会导致更差的性能表现(除非是所有的 vector 元素都一样)。这点与 array 的用法是完全不同的。 | ||
+ | </ | ||
+ | <WRAP center round important 100%> | ||
+ | Vector 是大小可变的。如果在循环体内包含了 vector,那么循环本身有可能会改变 vector 的大小 - 这点必须特别注意,尤其是在使用 for range 迭代的时候,for range 的范围大小是**不应该被改变的**。 | ||
+ | </ | ||
+ | |||
+ | |||
+ | ==其他操作== | ||
+ | 除开 '' | ||
+ | \\ \\ | ||
+ | {{ cs: | ||
+ | \\ \\ | ||
+ | 值得提醒的是,'' | ||
+ | <code cpp> | ||
+ | vector< | ||
+ | </ | ||
+ | 除此之外,vector 也可以使用 equality op 与 relational op,策略与 string 是类似的: | ||
+ | * vector 在长度以及每个对应元素都相等的情况下相等 | ||
+ | * 如果 vector 一场一短,短 vector 中元素与长 vector 中元素一一对应并相等,那么 长的 vector 比较大 | ||
+ | * 否则比较第一个不同的元素 | ||
+ | * 比较元素的前提是元素之间存在着大小关系。 vector 无法比较不存在 equality 和 relational 关系的元素。 | ||
+ | 当然,像 string 一样,也可以使用 '' | ||
+ | <code cpp> | ||
+ | vector< | ||
+ | for (auto &i : v) | ||
+ | i*=i;// | ||
+ | for (auto i : v) | ||
+ | cout << i << endl; | ||
+ | </ | ||
+ | ==Computing a vector Index== | ||
+ | 与 string 类似,vector 也可以通过类型为 '' | ||
+ | <code cpp> | ||
+ | /* | ||
+ | *给定一串 100 以内的数,将 100 分成 10 个相等区间,统计每个数出现在自己区域的出现次数。 | ||
+ | */ | ||
+ | vector< | ||
+ | unsigned grade; | ||
+ | while (cin >> grade) { | ||
+ | if (grade <= 100) { | ||
+ | ++scores[grade / 10]; | ||
+ | } | ||
+ | }</ | ||
+ | 这里通过 grade 除以 10 得到该数所处的区间编号(使用下标访问),再对对应位置的 counter 做自加处理即可。\\ \\ | ||
+ | 需要注意的是,在用下标访问 vector 的时候,有时候我们会想当然的使用下标给 vector 的添加元素: | ||
+ | <code cpp> | ||
+ | vector< | ||
+ | for(decltype(v.size() ix = 0; ix != 10; ++ix) | ||
+ | v[ix] = ix; //error!! | ||
+ | </ | ||
+ | 这样写是< | ||
+ | <WRAP center round alert 100%> | ||
+ | vector 的元素**不能通过下标来进行添加**!如果 vector 本身为空,那么使用下标对其访问是非法的。如果想添加元素,正确的方法是使用 push_back()。 | ||
+ | </ | ||
+ | |||
+ | |||
+ | ====迭代器==== | ||
+ | 迭代器(// | ||
+ | \\ \\ 迭代器类似于指针,代表了当前元素的位置与内容;同时迭代器通过自身的运算可以改变指向的元素,方便遍历。 | ||
+ | 与指针不同的是,迭代器并不直接使用地址来指向元素,而是使用成员函数 '' | ||
+ | \\ | ||
+ | < | ||
+ | <img src="/ | ||
+ | </ | ||
+ | </ | ||
+ | ===迭代器的操作=== | ||
+ | 迭代器支持一系列的操作:\\ \\ | ||
+ | {{cs: | ||
+ | \\ \\ | ||
+ | 利用迭代器性质判断容器是否为空: | ||
+ | <code cpp> | ||
+ | string s("a string" | ||
+ | if(s.begin() != s.end()) {//if s is not empty | ||
+ | auto it = s.begin(); // we don't care about what kind of type that iterator has. | ||
+ | *it = toupper(*it);// | ||
+ | } | ||
+ | </ | ||
+ | 对于 iterator 的类型,我们一般用 auto 去推断。iterator 对元素的访问也跟 pointer 类似,都是用 '' | ||
+ | ==Moving Iterators== | ||
+ | 迭代器通过 '' | ||
+ | <code cpp> | ||
+ | // | ||
+ | string s = "Hello World"; | ||
+ | for (auto it = s.begin(); it != s.end() && isspace(*it); | ||
+ | *it = toupper(*it); | ||
+ | } | ||
+ | </ | ||
+ | 需要注意的是,这里使用了 equality op 而不是用 relational op 做条件运算。这样使用是因为基本上所有的容器都定义了 '' | ||
+ | ==Iterator Types== | ||
+ | 迭代器的类型与之前提到过的 '' | ||
+ | <code cpp> | ||
+ | vector< | ||
+ | vector< | ||
+ | string:: | ||
+ | string:: | ||
+ | </ | ||
+ | 与指针的类似,迭代器也可以通过指定 '' | ||
+ | <WRAP center round info 100%> | ||
+ | 迭代器类型有好几种不同的意义: | ||
+ | * 迭代器本身的概念 | ||
+ | * 容器定义的迭代器类型 | ||
+ | * 迭代器对象时的类型 | ||
+ | 但无论意义若何,这一套解释都是为了说明迭代器支持一系列的,可以让我们访问或者移动容器中的元素的操作。换句话说,如果某个类型支持迭代器概念定义的操作,那这个类型就是迭代器类型。 | ||
+ | </ | ||
+ | ==begin / end 与 const== | ||
+ | '' | ||
+ | <code cpp> | ||
+ | vector< | ||
+ | const vector< | ||
+ | auto it1 = v.begin(); //iterator | ||
+ | auto it2 = cv.begin(); // | ||
+ | </ | ||
+ | C++11 提供了 '' | ||
+ | <code cpp> | ||
+ | auto it3 = v.cbegin(); // | ||
+ | </ | ||
+ | <WRAP center round tip 100%> | ||
+ | Good practice: 不修改容器元素的情况下使用常量迭代器。 | ||
+ | </ | ||
+ | |||
+ | ==解应用与成员函数的连用== | ||
+ | 有时候我们会使用装载类的容器。这种情况下一个常见的操作是首先是先访问类(解应用 '' | ||
+ | <code cpp> | ||
+ | (*it).empty(); | ||
+ | *it.empty(); | ||
+ | </ | ||
+ | 为了避免上述的错误,C++ 提供了另外一种写法: | ||
+ | <code cpp> | ||
+ | it-> | ||
+ | </ | ||
+ | 这种写法等同于解应用再调用成员函数的写法。下面是使用该写法做判定容器中某个元素是否为空的一个例子: | ||
+ | <code cpp> | ||
+ | for (auto it = text.cbegin(); | ||
+ | </ | ||
+ | |||
+ | |||
+ | ==Vector 导致迭代器无效的情况== | ||
+ | 需要注意的是 vector 有导致迭代器无效的潜在危险。比如像已经确定范围的 vector 遍历过程中再对该 vector 添加新的元素,那么迭代器会有很大的概率会失效。因此,需要确保在使用迭代器循环的时候不能往其操作的容器中添加新的元素。 | ||
+ | ===迭代器的运算=== | ||
+ | 迭代器通常支持位移('' | ||
+ | 当迭代器进行算术运算的时候,参与运算的迭代器必须指向同一个容器内的元素,或者是指向容器最后一个元素的下一个位置(off-the-end-iterator)。下面一个例子使用了头部迭代器和 size 信息计算了容器中部的迭代器: | ||
+ | <code cpp> | ||
+ | auto mid = vi.begin() + vi.size() / 2; | ||
+ | </ | ||
+ | 如果容器的类型是 String 和 vector,那么这两者的迭代器还可以进行**关系运算**。进行关系运算的迭代器还需要满足: | ||
+ | * 参与运算的迭代器有效 | ||
+ | * 参与运算的迭代器处于同个容器的有效范围内 | ||
+ | 除此之外,两个满足上述要求的迭代器还能进行**相减**的操作。得到的结果是两个迭代器之间的**距离**;也就是一个迭代器要到达另外一个迭代器所在位置需要移动的单位数。相减得到的结果是一个**有符号**的、类型名为 '' | ||
+ | ==使用迭代器运算== | ||
+ | 一个比较经典的,使用迭代器的算法是二分查找法(Binary search)。 | ||
+ | <code cpp> | ||
+ | auto beg = text.begin(), | ||
+ | auto mid = text.begin() + (end - beg) / 2; | ||
+ | |||
+ | while (mid != end && *mid != sought) { | ||
+ | if (sought < *mid){ | ||
+ | end = mid; | ||
+ | } | ||
+ | else { | ||
+ | beg = mid + 1; | ||
+ | } | ||
+ | mid = beg + (end - beg) / 2; | ||
+ | } | ||
+ | </ | ||
+ | 通过反复比较目标与 Mid 的值,来改变 mid 的位置,从而实现求解的目的。 | ||
+ | ====数组==== | ||
+ | 数组(// | ||
+ | |||
+ | ===定义和初始化数组=== | ||
+ | 定义数组的形式是 '' | ||
+ | <code cpp linenum: | ||
+ | int i = 10; | ||
+ | constexpr ci = 10; | ||
+ | int arr[10]; // ok | ||
+ | int *parr[ci]; //ok, 42 pointers | ||
+ | int bad[i]; // error, dimension of array must be a constant expression | ||
+ | string strs[get_size()];// | ||
+ | </ | ||
+ | 几个需要注意的事情: | ||
+ | - Build-in type 类型的数组在函数内部的**默认初始化**结果与 build-in type 一致,都是 undefined. | ||
+ | - <wrap em> | ||
+ | - 数组装载的元素均为对象,因此不存在元素为引用的数组。 | ||
+ | ==显式初始化数组== | ||
+ | 当采用 List 的方式初始化数组时,如果不设置 dimension, 编译器会根据**初始化元素的个数**推断dimension;如果初始化元素个数小于 dimension, | ||
+ | <code cpp> | ||
+ | const unsigned sz = 3; | ||
+ | int a1[sz] = {0,1,3}; //dimension = 3 | ||
+ | int a2[] = {0,1,2}; // | ||
+ | int a3[5] = {0,1,2}; // two elements are value initialized, | ||
+ | </ | ||
+ | ==字符串数组的初始化== | ||
+ | 字符串是比较特别的一种数组。当使用字符串 literal 对字符串数组进行初始化的时候,字符串需要额外的一位空间来装载字符串的终止符 '' | ||
+ | <code cpp> | ||
+ | char a1[] ={' | ||
+ | char a2[] ={' | ||
+ | char a3[] = " | ||
+ | const char a4[3] = " | ||
+ | </ | ||
+ | ==无法直接拷贝 / 赋值== | ||
+ | 数组是无法通过另外一个数组来初始化的,也不能直接将一个数组赋值给另外一个数组: | ||
+ | <code cpp> | ||
+ | int a[] = {1,2}; | ||
+ | int b[] = a; //error | ||
+ | b = a; //error | ||
+ | </ | ||
+ | <WRAP center round tip 100%> | ||
+ | 某些编译器允许数组的赋值,但并不是标准,应该尽量避免。 | ||
+ | </ | ||
+ | |||
+ | ==复杂的数组声明== | ||
+ | 数组也可以与复合类型一起使用,组成更复杂的类型。除了引用以外,数组可以与绝大部分类型一起使用,但是也更复杂。相较于之前从右往左看的识别方法,识别数组声明的类型是**从内往外看**的,比如下面例子的第三个定义: | ||
+ | <code cpp> | ||
+ | int *ptrs[10]; //array of pointers to int | ||
+ | int & | ||
+ | int (*Parray)[10] = &arr; //points to an array of ten ints | ||
+ | int (& | ||
+ | </ | ||
+ | - 首先找到 变量名 Parray,发现 Parray 是一个指针类型的变量 '' | ||
+ | - 然后跳出括号看,左边是这个指针指向数组的大小,右边是这个数组的类型。 | ||
+ | - 因此 '' | ||
+ | |||
+ | 再来看一个更复杂的例子 '' | ||
+ | - 首先看到了括号,那么对括号里的内容进行分析,括号里从右往左看发现 arry 是一个引用 | ||
+ | - 接着往外看,'' | ||
+ | - 再往外看,左边是 '' | ||
+ | - 那么这个例子定义的是一个引用,这个引用 refer to 一个数组,数组里装载了 10个指向 int 类型数据的指针。 | ||
+ | ===访问数组=== | ||
+ | 我们可以用下标或者 Range for 来访问数组;**下标的范围是[0, | ||
+ | 使用下标访问数组与 vector 基本上类似,唯一的区别在于数组与 vector 声明的方式不一样。除此之外,'' | ||
+ | \\ \\ | ||
+ | 除此之外,数组还能用 range for 遍历: | ||
+ | <code cpp> | ||
+ | for (auto i : arry) | ||
+ | cout << i << | ||
+ | </ | ||
+ | ==下标的检查== | ||
+ | 不管是 string、vector 还是数组,保证下标的值在 [0 , size() -1] 的范围内都是极其重要的一件事。这种因为下标越界而带来的安全问题称为 //Buff Overflow Bug//。 | ||
+ | |||
+ | ===数组和指针=== | ||
+ | 在 C++ 中,指针与数组的联系非常紧密。总的来说,指针通过 // | ||
+ | <code cpp> | ||
+ | string num[] ={" | ||
+ | string *p = & | ||
+ | </ | ||
+ | 默认情况下,如果不指定访问的数组元素位置,那么编译器会将指针指向数组的第一个元素: | ||
+ | <code cpp> | ||
+ | string *p2 = num; //equal string *p2 = & | ||
+ | </ | ||
+ | 实际上,C++ 中很多地方都在暗示对数组的操作实际上就是对指针的操作。比如我们使用 '' | ||
+ | <code cpp> | ||
+ | int arry[] = {0,1,2}; | ||
+ | auto t(arry); // equivalent to auto t = arry; auto deduces that t is a int* type | ||
+ | t = 42; //error, can't assign a int to a pointer | ||
+ | </ | ||
+ | 在这里,编译器实际上是这么进行初始化类型判定的: | ||
+ | <code cpp> | ||
+ | auto t(& | ||
+ | </ | ||
+ | 需要注意的是,'' | ||
+ | <code cpp> | ||
+ | int ia[] ={1,1,1,1} | ||
+ | decltype(ia) ia2 = {1,2,3,4}; | ||
+ | ia2[2] = 4; // ok, assgin int to an element in ia2 | ||
+ | </ | ||
+ | ==指针是迭代器== | ||
+ | 在数组中,可以将指针像在 vector 中使用迭代器一样使用: | ||
+ | <code cpp> | ||
+ | int arr [] = {1, | ||
+ | int *p = arr; | ||
+ | ++p; // p will points from arr[0] to arr[1] | ||
+ | </ | ||
+ | 同样,指针也可以像迭代器一样遍历数组。需要的信息也相同,只需要找到数组的第一个元素的位置和 off-the-end 位置就可以。我们可以通过定义指向数组最后一位的下一位的指针来获取该数组的 off-the-end pointer,之后就可以使用迭代器的方式对数组进行遍历: | ||
+ | <code cpp> | ||
+ | int arry[] = {0,1,2,3,4} | ||
+ | int *e = & arry[5]; | ||
+ | for (int *b = arry; b != e; ++b) | ||
+ | cout << *b << endl; | ||
+ | </ | ||
+ | |||
+ | ==标准库 begin() / end()== | ||
+ | 尽管能通过下标得到 off-the-end pointer, 但这样做真的很容易犯错。幸好 C++ 11 为指针也提供了类似迭代器的 '' | ||
+ | <code cpp> | ||
+ | int arry[] = {0,1,2,3,4} | ||
+ | int *pBeg = begin(arry); | ||
+ | int *pEnd = end(arry); | ||
+ | for (p_beg; pBeg != pEnd; ++pBeg) | ||
+ | statements; | ||
+ | </ | ||
+ | 使用的时候需要申明其所在的 scope,包括定义这俩函数的头文件 '' | ||
+ | <code cpp> | ||
+ | #include < | ||
+ | using std::begin; | ||
+ | using std::end; | ||
+ | </ | ||
+ | <WRAP center round important 100%> | ||
+ | //begin() / end()// | ||
+ | </ | ||
+ | |||
+ | ==指针的算术操作== | ||
+ | 在数组中,指针可以进行一切迭代器可以进行的运算,意义也是一样的;使用迭代器的限制也同样应用于指针: | ||
+ | <code cpp> | ||
+ | constexpr size_t sz = 5; | ||
+ | int arr[sz] = {1, | ||
+ | int *ip = arr; | ||
+ | int *ip2 = ip + 5; // a new pointer points the 5th element behind the first element | ||
+ | int *ip3 = ip + 6; //error, arr has 5 elements, ip3 has an undefined value | ||
+ | int *ip4 = arr + sz; //ok but do not dereference | ||
+ | </ | ||
+ | 同样,两个指针也可以进行减法。得到的值类型为 '' | ||
+ | \\ \\ | ||
+ | 关系运算也可以用于指针中,前提是指针指向的位置必须是数组元素的位置,或者是 off-the-end pointer 的位置。利用该运算可以写出如下的遍历写法: | ||
+ | <code cpp> | ||
+ | int *b = arr; | ||
+ | int *e = arr + sz; | ||
+ | while (b < e) { | ||
+ | do sth.... | ||
+ | ++b; | ||
+ | } | ||
+ | </ | ||
+ | 除此之外,指针的运算也适用于空指针和指向不是数组中对象的指针。 | ||
+ | ==解应用与指针运算== | ||
+ | 指针与整型的加减会返回一个指针。如果想获取该指针对应的元素内容,需要使用括号确定指针的加减运算: | ||
+ | <code cpp> | ||
+ | int a[] = {0, | ||
+ | int last = *(a + 4); // last = a[4]; | ||
+ | last= *a + 4 // last = a[0] + 4; | ||
+ | </ | ||
+ | |||
+ | ==数组下标与指针== | ||
+ | 使用下标访问数组实际上就是在对当前的指针进行计算,比如: | ||
+ | <code cpp> | ||
+ | int ia = {0, | ||
+ | int i = ia[2]; | ||
+ | </ | ||
+ | 实际上是做了如下的操作: | ||
+ | <code cpp> | ||
+ | int *p = ia; // p points to the first element of ia | ||
+ | ia[2] = *(p + 2); | ||
+ | </ | ||
+ | 可以看出来下标运算符实际上等同于指针的算术运算;因此指针的加减法可以用下标替代: | ||
+ | <code cpp> | ||
+ | int *p = &ia[2]; // p points to 3rd element of ia | ||
+ | int j = p[1]; // p[1] equal *(p + 1), a[3] | ||
+ | int k =p[-2] // p[-2] equal *(p - 2), a[0] | ||
+ | </ | ||
+ | 注意这里的下标可以是**负数**,这是**数组下标**(原生下标)**独有**的写法。相比之下,Vector / spring 的下标都是 '' | ||
+ | ===C style string=== | ||
+ | <WRAP center round important 100%> | ||
+ | 本章作者介绍 c style string 的意图应该是想介绍一些兼容老代码的背景知识。C++ 程序中应该避免使用 c style string。 | ||
+ | </ | ||
+ | 字符串 literal 并不是类型,而是一种继承自 c style string 的实例。这种结构实际上是一个以 ''/ | ||
+ | ==C Library string functions== | ||
+ | C 中有操作该类字符串的函数,定义于 '' | ||
+ | {{ cs: | ||
+ | 以上的函数均不会校验其收到的参数,因此需要特别注意参数的合法性。一个比较常见的错误就是将没有 //null terminated// | ||
+ | <code cpp> | ||
+ | char ca[] = {' | ||
+ | cout << strlen(ca) << endl; // disaster,undefined | ||
+ | </ | ||
+ | ==Comparing Strings== | ||
+ | 不像 C++ string, C style string 并不能直接使用关系运算符比较大小: | ||
+ | <code cpp> | ||
+ | char ca[] = " | ||
+ | char cb[] = " | ||
+ | if (ca > cb) // undefined | ||
+ | </ | ||
+ | 这是因为 C style string 的本质是一个字符数组,因此上述的比较操作会被转化成两个指向数组头元素指针的大小比较。因为这两个指针分别指向了不同对象(数组),因此他们之间的比较是**未定义**的。正确的方法是使用函数 '' | ||
+ | <code cpp> | ||
+ | if (strcmp(ca, cb) < 0) | ||
+ | </ | ||
+ | ==调用者决定结果的大小== | ||
+ | 如果想将两个字符串连接起来,C++ 中使用 '' | ||
+ | <WRAP center round info 100%> | ||
+ | 这是另外一个不使用 c style string 的重要原因:每次字符串的连接都要求用户确保目标字符串的大小。 | ||
+ | </ | ||
+ | ===Interfacing Old code=== | ||
+ | 很多在 STL 出来之前的 C++ 程序,或是为 C 程序做的接口,都没有用上 vector 和 string。C++ 提供了一些功能用于衔接这些老程序和现代 C++。 | ||
+ | ==mixing string & c-string== | ||
+ | 首先,C style string,也就是 null-terminated char array,可以用于任何 string literal 适用的场景,比如: | ||
+ | * 使用 c-string 初始化 & 赋值 string | ||
+ | * 与 string 混用时,c-string 可以充当算子:比如 string 与 c-string 的相加。 | ||
+ | 需要注意的是,上述规则反过来用是不行的。如果在某些特定的情况下需要使用 string 作为 c-string 的初始值,那么可以用到 string 的成员函数 '' | ||
+ | <code cpp> | ||
+ | const char *str = s.c_str(); | ||
+ | </ | ||
+ | 该函数会返回一个 c-string,也就是一个 '' | ||
+ | <WRAP center round important 100%> | ||
+ | c_str() 并不能保证返回值的有效性。改变 string 很可能导致 c_str() 之前返回的数组无效。使用的时候最好将得到的数组复制一份使用。 | ||
+ | </ | ||
+ | | ||
+ | C++ 中,vector 不能初始化数组,但反过来可以。vector 的初始化可以接收两个数组指针作为参数,来确定初始值: | ||
+ | <code cpp> | ||
+ | int int_arr[] {0, | ||
+ | vector< | ||
+ | </ | ||
+ | 还可以使用指针的算数运算来指定数组的子集作为初始值: | ||
+ | <code cpp> | ||
+ | vector< | ||
+ | </ | ||
+ | ====多维数组==== | ||
+ | 在多维数组的定义中,定义的实际是包含数组的数组。我们可以看到,多维数组的定义实际上是:\\ | ||
+ | <code cpp> | ||
+ | type array[sub_array1][sub_array2]...[elements]; | ||
+ | </ | ||
+ | \\ \\ | ||
+ | {{ : | ||
+ | \\ \\ | ||
+ | 二维数组的定义中,一般定义第一行为数组的个数(行),第二行为数组包含元素的个数(列),层级从右到左依次往上。 | ||
+ | ===多维数组的初始化=== | ||
+ | 我们再用list初始化的方法来看一看二维数组: | ||
+ | <code cpp linenums: | ||
+ | int ia[3][4] = { | ||
+ | | ||
+ | | ||
+ | | ||
+ | }; | ||
+ | </ | ||
+ | 从这里可以发现,这根先前我们总结的定义是符合的,3是指的行数(数组个数),4指的是列数(元素个数)。以上的定义中,定义中的每一行的大括号,都代表这些大括号里的元素属于一个数组。因此,我们可以用大括号来控制每个数组的初始化: | ||
+ | <code cpp linenums: | ||
+ | int ia[3][4] = { {0}, {4}, {8} }; | ||
+ | // | ||
+ | </ | ||
+ | 如果不加大括号,初始化就是按 List 中的元素顺序进行初始化: | ||
+ | <code cpp> | ||
+ | int ix [3][4] = {1, | ||
+ | // | ||
+ | </ | ||
+ | ===访问多维数组=== | ||
+ | |||
+ | ==使用下标访问== | ||
+ | 对于多维数组的遍历,外层循坏对应数组左边的 subscript, 内层循环对应数组右边的 subscript。所以对于二维数组的遍历,我们是在外层循环遍历行(以数组为单位的元素),内层循环则遍历该数组的内部元素。比如: | ||
+ | <code cpp> | ||
+ | constexpr size_t rowCnt = 3, colCnt = 4; | ||
+ | int ia[rowCont][colCnt]; | ||
+ | //for each row | ||
+ | for (size_t i = 0; i < rowCnt; ++i) { | ||
+ | //for each elements (column) | ||
+ | for (size_t j = 0; j < colCnt; ++j) { | ||
+ | ia[i][j] = i * colCnt + j; | ||
+ | } | ||
+ | } | ||
+ | </ | ||
+ | ==使用 range for 访问== | ||
+ | C++ 11 允许使用 range for 访问多维数组: | ||
+ | <code cpp> | ||
+ | size_t cnt = 0; | ||
+ | for (auto &row : ia) { | ||
+ | for (auto &col : row) { | ||
+ | col = cnt; | ||
+ | ++cnt; | ||
+ | } | ||
+ | } | ||
+ | </ | ||
+ | 该循环里使用了引用,因为需要修改元素的值。然而使用引用还有另外一层原因。下面的例子: | ||
+ | <code cpp> | ||
+ | for (const auto &row : ia) | ||
+ | for (auto col : row) | ||
+ | cout << col << endl; | ||
+ | </ | ||
+ | 这个循环只有读操作,可是外层的循环变量还是加上了引用。之所以这么做是为了**防止数组到指针的转化**。row 是外层的数组,如果不使用引用定义的话,编译器会自动将 row 的类型从数组转化为指向 row 第一个元素的指针。很显然,内层循环无法去遍历一个指针。\\ \\ | ||
+ | 因此可以得出结论:**使用 range for 访问多维数组,除开最内层的循环,所有外层循环的控制变量都必须是引用。** | ||
+ | ==指针与多维数组== | ||
+ | 首先需要再次明确的是,当使用**数组名**的时候,数组会自动的转换成指向第一个元素的指针。因为多维数组是真正的包含数组的数组,因此该指针指向的对象实际上是**第一个内层数组**: | ||
+ | <code cpp> | ||
+ | int ia[3][4]; | ||
+ | int (*p)[4] = ia;// p is pointer points to an array with 4 ints | ||
+ | p = &ia[2]; //p points to the 3rd sub array in ia | ||
+ | </ | ||
+ | 需要注意的是,上面代码中定义指针的括号是不能省的: | ||
+ | <code cpp> | ||
+ | int *p[4]; // array with 4 pointers that point to int | ||
+ | int (*p)[4]; // a pointer points to an array with 4 ints | ||
+ | </ | ||
+ | C++11 中可以通过使用 '' | ||
+ | <code cpp> | ||
+ | for (auto p = ia; p != i * a + 3; ++p) { | ||
+ | for (auto q = *p; q != *p + 4; ++q) | ||
+ | cout << *q << " "; | ||
+ | cout << endl; | ||
+ | } | ||
+ | </ | ||
+ | 这个循环里使用了 '' | ||
+ | - 首先需要获取内层数组。'' | ||
+ | - 再次进行数组名到指针的转换。此处定义了 '' | ||
+ | - 再通过位移 '' | ||
+ | 当然,使用标准库函数 '' | ||
+ | <code cpp> | ||
+ | for (auto p = begin(ia); p != end(ia); ++p) { | ||
+ | for (auto q = begin(*p); q != end(*p); q++){ | ||
+ | cout << *q << " "; | ||
+ | } | ||
+ | cout << endl; | ||
+ | } | ||
+ | </ | ||
+ | ==使用 type alias 简化上述过程== | ||
+ | 另一种方式是使用 type alias 简化数组的类型: | ||
+ | <code cpp> | ||
+ | typedef int(*p)[4] int_arr; | ||
+ | using int_arr = int[4]; | ||
+ | for (int_arr p = ia; p != ia +3; ++p) { | ||
+ | for (int_arr q = *p; q != *p + 4; ++q) { | ||
+ | cout << *q << endl; | ||
+ | } | ||
+ | } | ||
+ | </ | ||