美文网首页
第 3 章:字符串、向量和数组

第 3 章:字符串、向量和数组

作者: 修司敦 | 来源:发表于2018-10-31 17:42 被阅读0次
  1. 头文件中不应该包含 using 声明,不然每一个包含了改头文件的文件都会有这个声明,导致不可预料的命名冲突。

  2. 每个 string 对象的末尾都有一个 '\0',名为“空字符”,用于指示字符串的结束。如果在程序中人为的修改了这个空字符,那么会发生不可预料的结果。

  3. string 类型的初始化中,如果用到了赋值运算符 =,那么就是拷贝初始化,右值会先被拷贝一份,然后把拷贝后的内存交给新创建的对象。其余的都是直接初始化,分配一块内存然后直接填值。

  4. 在从输入流读取 string 的时候,cin1.1. 会忽略掉开头的所有空白 (包括空格、'\n''\t' 等),并从第一个合法的字符读起,直到遇到了下一处空白为止 (这处空白不会存在 string 中)。例如,如果输入的是 " houston ",那么这个 string 最终的内容是 houston。如果输入的是 " h o u s t o n ",那么这个 string 只能拿到 "h"

  5. 如果不想上面那样子的事情发生,可以使用 getline(in, str) 函数解决。该函数可以读入一整行,其中的两个参数 in 代表一个输入流,一般用 cinstr 代表要读入的字符串的标识符。所以要在调用 getline(in, str) 函数之前新建一个空字符串。注意,当你在一开始就按下了回车键,那么 getline 拿到的就是一个空串。正常输入的话,空白都会被保留,直到你按下换行键。换行键会被流读取到,但是不会存到 str 中。所以如果输入的是 " h o u s t o n ",然后按下换行符,那么这个 string 拿到的就是 " h o u s t o n "

  6. string::size() 返回的是 string::size_type 类型的数字,这是一个无符号类型 unsigned long 的数字,所以注意:当你要用到某个 stringsize() 函数的时候,那么其他介入的数字最好也都是无符号类型的,这样不会发生意想不到的错误。否则,如果你拿 str.size()-1 相比,那么既有可能返回的是 -1 更大,因为 -1 会先转成一个无符号数字,结果可是 2^64-1 啊!

  7. 当使用字符串的加法时,要确保每个加号两边都至少有一个对象是 string 类型。两个字符串字面值由于是 const char [] 类型,所以它俩是不能直接相加的,例如 "hello" + ", world" 是不合法的。

  8. range for 语句:

    for (auto declaration : expression)
    { statement }
    

    其中的 auto 可以自动探测变量类型,expression 部分是要被遍历的序列,declaration 是一个变量,它代表序列中每次被遍历到的单个元素。举个例子:

    string str = "hungry!"
    for (auto c : str)
    { cout << c << '-'; }
    // "h-u-n-g-r-y-!-"
    
  9. 字符串的下标 (索引) 会自动转为 string::size_type 这个无符号的 unsigned long。所以,如果你访问 str[-1],其实你在 x64 的机器上得到的是 str[18446744073709551615]。在访问字符串的某一位的时候,一定要确保该位确实有值。如果这个字符串本身为空,那么你的访问得到的结果是未定义的。所以在访问数组下标的时候,推荐的做法是:总是使用 decltype(str.size())这个类型来声明所有下标的类型。

  10. vector 是模版而非类型。给 vector 提供了具体的元素类型之后,他才是一个类型,例如 vector<string>

  11. vector 能容纳绝大多数类型的对象,包括内置类型、用户自定义类型和其他容器。但是不能够容纳引用,因为引用只是一个别名而不是一个对象。当使用一些老旧的编译器的时候,声明一个 vectorvector 元素的时候,要在第一个右尖括号和第二个右尖括号之间插入一个空格,例如 vector<vector<string> >,这一点要稍微注意一下。不过现在使用的编译器版本都是不需要加这个空格的啦!

  12. range for 语句内不能改变其所遍历序列的大小。但凡是使用了迭代器的循环器,都不要想迭代器所属的容器添加元素。

  13. 只能对确知已存在的元素执行下标操作。否则会导致缓冲区溢出。

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

  15. 执行解引用的迭代器必须合法并确实指示着某个元素。试图解引用一个非法迭代器或者尾后迭代器都是未被定义的行为。

  16. 两个迭代器相减得到的是一个 difference_type 的带符号类型。

  17. 在声明数组的时候,要么使用常量表达式说明数组的长度,要么使用字面值。不能使用变量来声明数组的大小。定义数组的时候必须指定数组类型,不允许使用 auto,哪怕你提供了初始值列表也不行。和 vector 一样,不存在引用的数组,但是有指针的数组。可以定义数组的指针和数组的引用:

     constexpr int sz = 10;    // 常量表达式
     int arr[sz];              // 常量表达式初始化数组
     int *ptrs[sz];            // 指针的数组
     int (*Parr)[sz] = &arr;   // Parr 指向 arr
     int (&Rarr)[sz] = arr;    // Rarr 引用 arr
     int *(&Rptrs)[sz] = ptrs; // Rptrs 引用 ptrs
    

    理解上述代码可能有些困难,有一个简单的方法就是看有没有括号。括号的作用是强行绑定几个元素,所以当 * 或者 & 出现在括号里头的时候,它们归属于括号里它们右边的标识符;当 *& 没有被括号限制的时候,它们归属于左边的类型。例如 ptrs,它左边的 * 没有被括号所限制,那么 * 和左边的 int 在一起,指示的是数组的元素为指针类型。再看 Parr* 被括号强行限制住了,那么 * 归属于 Parr,代表 Parr 自己是个指针。同样的,Rarr 左边的 &Rarr 绑定,那么代表 Rarr 本身是一个引用。最后一个 Rptrs 也就好理解了,& 说明 Rptrs 是个引用,* 说明数组的元素都为指针类型。

  18. 前一章讲到过 autodecltype 的区别:auto 不能保留引用类型和顶层常量属性,但是 decltype 可以。现在指出新的一点:由于单独操作数组名字时其实操作的是一个指针,所以当把数组名赋值给另一个变量的时候,这个变量用 auto 检测到的类型是指针而不是数组:

     int a1[10] = {0,1,2,3,4,5,6,7,8,9};
     auto a2 = a1;  // a2 是一个 int * 指针,指向 a1 的第一个元素
     for (auto i : a2) {}; // 编译错误, a2 不是一个数组,所以不能用 auto
     decltype(a1) a3 = {1,2,3,4,5,6,7,8,9,10}; // a3 是一个 int[10] 数组
     a3 = &(a1[2]); // 编译错误,a3 是一个数组,不能给它赋地址
    

    但是 decltype 却不是这样,检测到的类型并不是指针而同样是一个数组。由此可见,decltype 非常的底层,它能够突破引用和数组的表象深挖到对象的本质,即引用类型和数组类型;而 auto 会把引用当成一个别名,会把数组当成一个指针。

  19. <iterator> 头文件中有两个函数 begin()end(),能够接受数组作为参数,返回数组的头指针和尾后指针,就像迭代器那样。

  20. 两个数组指针相减得到的是一个 ptrdiff_t 的带符号类型。

  21. 标准库类型 stringvector 可以执行下标运算,但是限定下标是无符号类型。然而数组的下标运算符 [] 可以接受有符号的下标,例如 p[-2] 代表向前移动 p 两个单位。下标运算符的本质是对指针进行位移。

  22. <string> 是标准库头文件,<string.h> 是 C 风格字符串的头文件,<cstring><string.h> 的 C++ 风格。写 C++ 程序时,应尽量使用标准库 <string>

  23. <cstring> 提供一些 C 风格字符串的函数,例如 strlen(p)strcmp(p1, p2)strcat(p1, p2)strcpy(p1, p2)等。传入此类函数的指针必须指向一个明确以空字符作为结束的 char 数组,否则会发生严重错误。

  24. 为了方便老旧代码和新标准代码的混用,C++ 允许使用字符串字面值来初始化 string 对象,允许使用以空字符结束的字符数组 (又称 C 风格字符串) 来初始化 string 对象或赋值,允许将 string 对象与 C 风格字符串相加。这是新特性向下兼容旧特性。所以反过来是不成立的,不能用 string 对象来直接初始化一个指向字符的指针。为了完成这一功能,string 专门提供了一个函数 c_str(),能够接受一个 string 对象,传出一个指针,指向内容一模一样的只读的 C 风格字符串:

    string s("C++ is hard");
    char *cs = s; // 错误!不能用 string 对象初始化 char *
    const char *str = s.c_str(); // OK
    
  25. 可以使用 begin()end() 来用数组初始化一个 vector:

    int ar[] = {2,3,5,7,11,13,17};
    vector<int> v(begin(ar), end(ar));
    

    在上述代码中,两个指针指明了用来初始化的值的区间,所以你可以任意指定合法的区间来初始化。注意,第二个参数应是带拷贝区域的尾后指针,也就是说如果你要初始化数组的第 0 位到第 4 位,你需要传入的是

    vector<int> vv(begin(ar), begin(ar)+5);
    
  26. 在使用范围 for 语句处理多维数组的时候,要把索引写成引用,这样子可以避免索引被转为指针:

    int arr[2][3] = { {1,3,5}, {2,4,6} };
    for (auto &r : arr) {
        for (auto &c : r) {
           c *= 3;
        }
    }
    

    如果不把 r 声明称引用,那么 r 会被处理成指针,内层的 for 语句就不能正确编译了。

相关文章

网友评论

      本文标题:第 3 章:字符串、向量和数组

      本文链接:https://www.haomeiwen.com/subject/mvvrtqtx.html