《C++ Primer》笔记 第3章 字符串、向量和数组
阅读原文时间:2023年07月09日阅读:1
  1. 位于头文件的代码一般来说不应该使用using声明。

  2. 如果使用等号(=)初始化一个变量,实际上执行的是拷贝初始化,编译器把等号右侧的初始值拷贝到新创建的对象中去。与之相反,如果不使用等号,则执行的是直接初始化。

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

  4. string类的size函数返回的是一个string::size_type类型的值,它是一个无符号类型的值而且能足够存放下任何string对象的大小。所有用于存放string类的size函数返回值的变量,都应该是string::size_type类型的。由于size函数返回的是一个无符号整型数,因此切记,如果在表达式中混用了带符号数和无符号数将可能产生意想不到的结果。

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

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

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

  8. cctype头文件中的函数

    函数

    解释

    isainum(c)

    当c是字母或数字时为真

    isalpha(c)

    当c是字母时为真

    iscntrl(c)

    当c是控制字符时为真

    isdigit(c)

    当c是数字时为真

    isgraph(c)

    当c不是空格但可打印时为真

    islower(c)

    当c是小写字母时为真

    isprint(c)

    当c是可打印字符时为真(即c是空格或c具有可视形式)

    ispunct(c)

    当c是标点符号时为真(即c不是控制字符、数字、字母、可打印空白中的一种)

    isspace(c)

    当c是空白时为真(即c是空格、横向制表符、纵向制表符、回车符、换行符、进纸符中的一种)

    isupper(c)

    当c是大写字母时为真

    isxdigit(c)

    当c是十六进制数字时为真

    tolower(c)

    如果c是大写字母,输出对应的小写字母;否则原样输出c

    toupper(c)

    如果c是小写字母,输出对应的大写字母;否则原样输出c

  9. 范围for语句遍历给定序列中的每个元素并对序列中的每个值执行某种操作,其语法形式是:

      for (declaration : expression)
            statement
  10. 访问string对象的下标运算符([])接受的输入参数是string::size_type类型的值,这个参数表示要访问的字符的位置;返回值是该位置上字符的引用。

  11. 初始化vector对象的方法

    方法

    解释

    vector v1

    v1是一个空vctor,它潜在的元素是T类型的,执行默认初始化

    vector v2(v1)

    v2中包含有v1所有元素的副本

    vector v2 = v1

    等价于v2(v1),v2中包含有v1所有元素的副本

    vector v3(n, val)

    v3包含了n个重复的元素,每个元素的值都是val

    vector v4(n)

    v4包含了n个重复地执行了值初始化的对象

    vector v5{a,b,c…}

    v5包含了初始值个数的元素,每个元素被赋予相应的初始值

    vector v5={a,b,c…}

    等价于v5{a,b,c…}

  12. 初始化过程会尽可能地把花括号内的值当成是元素初始值的列表来处理,只有在无法执行列表初始化时才会考虑其他初始化方式。(例:vector<string> v1{10,"hi"};v1有10个值为"hi"的元素)确认无法执行列表初始化后,编译器会尝试用默认值初始化vector对象。

  13. vector<string> v2("hi");错误:不能使用字符串字面值构建vector对象。因为()调用构造函数。

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

  15. v1 = v2用v2中元素的拷贝替换v1中的元素,v1 = {a, b, c...}用列表中元素的拷贝替换v1中的元素

  16. 要使用size_type,需首先指定它是由哪种类型定义的。vector对象的类型总是包含着元素的类型:vector<int>::size_type正确,vector::size_type错误。

  17. 只要vector对象不是一个常量,就能向下标运算符返回的元素赋值。

  18. begin成员负责返回指向第一个元素的迭代器。end成员则负责返回指向容器“尾元素的下一位置”的迭代器。end成员返回的迭代器常被称作尾后迭代器或者简称为为迭代器。如果容器为空,则begin和end返回的是同一个迭代器,都是尾后迭代器。

  19. 迭代器使用递增(++)运算符来从一个元素移动到下一个元素。因为end返回的迭代器并不实际指示某个元素,所以不能对其进行递增或解引用的操作。

  20. 使用iterator和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只能读字符,不能写字符
  21. 我们认定某个类型是迭代器当且仅当它支持一套操作,这套操作使得我们能访问容器的元素或者从某个元素移动到另外一个元素。

  22. 如果对象是常量,begin和end返回const_iterator;如果对象不是常量,返回iterator。为了便于专门得到const_iterator类型的返回值,C++11引入了两个新函数,分别是cbegin和cend,不论vector对象(或string对象)本身是否是常量,返回值都是const_iterator。

  23. 解引用迭代器可获得迭代器所指的对象,如果该对象的类型恰好是类,就有可能希望进一步访问它的成员。

  24. 不能在范围for循环中向vector对象添加元素。任何一种可能改变vector对象容量的操作,比如push_back,都会使该vector对象的迭代器失效。谨记,但凡是使用了迭代器的循环体,都不要向迭代器所属的容器添加元素。

  25. 数组的声明形如a[d],其中a是数组的名字,d是数组的维度,维度必须大于0且必须是一个常量表达式。

  26. 和内置类型的变量一样,如果在函数内部定义了某种内置类型的数组,那么默认初始化会令数组含有未定义的值。

  27. 定义数组的时候必须指定数组的类型,不允许使用auto关键字由初始值的列表推断类型。另外和vector一样,数组的元素应为对象,因此不存在引用的数组。

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

  29. 理解复杂的数组声明:

      int *ptrs[10]; // ptrs是含有10个整形指针的数组
      int &refs[10] = /* ? */; // 错误:不存在引用的数组
      int (*Parray)[10] = &arr; // Parray指向一个含有10个整数的数组
      int (&arrRef)[10] = arr; // arrRef引用一个含有10个整数的数组
  30. 在使用数组下标的时候,通常将其定义为size_t类型,size_t是一种机器相关的无符号类型,它被设计得足够大以便能表示内存中任意对象的大小。在cstddef头文件中定义了size_t类型。

  31. 在大多数表达式中,使用数组类型的对象其实是使用一个指向该数组首元素的指针。在一些情况下数组的操作实际上是指针的操作,当使用数组作为一个auto变量的初始值时,推断得到的类型是指针而非数组,如下例。而decltype(ia)返回的类型是由10个整数构成的数组。

      int ia[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}; // ia是一个含有10个整数的数组
      auto ia2(ia); // ia2是一个整型指针,指向ia的第一个元素
      ia2 = 42; // 错误:ia2是一个指针,不能用int值给指针赋值
  32. 指针也是迭代器。使用int *e = &arr[10];指向arr尾元素的下一位置的指针。也可以使用begin函数返回指向ia数组首元素的指针,end函数返回指向ia数组尾元素下一位置的指针。这两个函数定义在iterator头文件中。

      int ia[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}; // ia是一个含有10个整数的数组
      int *beg = begin(ia); // 指向ia首元素的指针
      int *last = end(ia); // 指向arr尾元素的下一位置的指针
  33. 两个指针相减的结果的类型是一种名为ptrdiff_t的标准库类型。和size_t一样,ptrdiff_t也是一种定义在cstddef头文件中的机器相关的类型。因为差值可能为负值,所以ptrdiff_t是一种带符号类型。

  34. 标准库类型(比如vector和string)限定使用的下标必须是无符号类型,而内置的下标运算无此要求。内置的下标运算符所用的索引值不是无符号类型,这一点与vector和string不一样。(数组的下标运算实质上是指针加减操作,arr[1]等价于*(arr+1)

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

    • 允许使用以空字符结束的字符数组来初始化string对象或为string对象赋值。
    • 在string对象的加法运算中允许使用以空字符结束的字符数组作为其中一个运算对象(不能两个运算对象都是);在string对象的复合赋值运算中允许使用以空字符结束的字符数组作为右侧的运算对象。
  36. string对象的成员函数c_str()的返回值是一个C风格的字符串。也就是说,函数的返回结果是一个指针,该指针指向一个以空字符结束的字符数组。结果指针类型是const char*确保我们不会改变字符数组的内容。如果后续的操作改变了string对象的值就可能让之前返回的数组失去效用。如果执行完c_str()函数后程序想一直都能使用其返回的数组,最好将该数组重新拷贝一份。

  37. 虽然不允许使用一个数组为另一个内置类型的数组赋初值,也不允许使用vector对象初始化数组,但是允许使用数组来初始化vector对象。要实现这一目的,只需指明要拷贝区域的首元素地址和尾后地址就可以了。例:vector<int> ivec(begin(int_arr), end(int_arr));

  38. 现代的C++程序应当尽量使用vector和迭代器,避免使用内置数组和指针;应该尽量使用string,避免使用C风格的基于数组的字符串。

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

      int ia[3][4];
      size_t cnt = 0;
      for (auto &row : ia) // 对于外层数组的每一个元素
            for (auto &col : row) // 对于内层数组的每一个元素
            {
                  col = cnt; // 将下一个值赋给该元素
                  ++cnt; // 将cnt加1
            }
    
      for (const auto &row : ia) // 对于外层数组的每一个元素
            for (auto col : row) // 对于内层数组的每一个元素
                  cout << col << endl;
  40. 要使用范围for语句处理多维数组,除了最内层的循环外,其他所有循环的控制变量都应该是引用类型。以下形式将不能通过编译:

      for (auto row : ia) // int* const row
            for (auto col : row) // 试图在一个int*内遍历
  41. 定义指向多维数组的指针时,千万别忘了这个多维数组实际上是数组的数组。

  42. 使用标准库函数begin和end简化多维数组的遍历:

      for (auto p = begin(ia); p != end(ia); ++p)
      {
            for (auto q = begin(*p); q != end(*p); ++q)
                  cout << *q << ' ';
            cout << endl;
      }
  43. 拷贝初始化:使用赋值号(=)的初始化形式。新创建的对象是初始值的一个副本。

  44. difference_type:由string和vector定义的一种带符号整数类型,表示两个迭代器之间的距离。

  45. 直接初始化:不使用赋值号(=)的初始化形式。

  46. getline:在string头文件中定义的一个函数,以一个istream对象和一个string为输入参数。该函数首先读取输入流的内容直到遇到换行符停止,然后将读入的数据存入string对象,最后返回istream对象。其中换行符读入但是不保留。

      #include <iostream>
      #include <string> // Header file needed to use string objects
      using namespace std;
      int main()
      {
          string name;
          cout << "Please enter your name: ";
          getline(cin, name);
          cout << "Hello, " << name << endl;
      return 0;
    }