Skip to content

Latest commit

 

History

History
198 lines (173 loc) · 13.4 KB

第三章 字符串、向量和数组.md

File metadata and controls

198 lines (173 loc) · 13.4 KB

第三章 字符串、向量和数组

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

    操作 说明
    os<<s 将s写到输出流os当中,返回os
    is>>s 从is中读取字符串赋给s,字符串以空白分隔,返回is
    getline(is, s) 从is中读取一行赋给s,返回is
    s.empty() s为空返回true,否则返回false
    s.size() 返回s中字符的个数
    s[n] 返回s中第n个字符的引用,位置n从0计起
    s1+s2 返回s1和s2连接后的结果
    s1=s2 用s2的副本代替s1中原来的字符
    s1==s2 如果s1和s2中所含的字符完全一样,则它们相等;string对象的相等性判断对字母的大小写敏感
    s1!=s2 如果s1和s2中所含的字符不完全一样,则它们不相等
    <,<=,>,>= 利用字符在字典中的顺序进行比较,且对字母的大小写敏感
  • 触发getline函数返回的那个换行符实际上被丢弃了。

  • 所有用于存放string类的size函数返回值的变量,都应该是string::size_type类型的。

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

    string s2 = "world";
    string s7 = "hello" + "" + s2;    //错误:不能把字面值直接相加
  • 处理string对象中的字符:

    cctype头文件中的函数 说明
    isalnum(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
  • 处理每个字符

    for(declaration:expression)
        statement 
    

    中的declaration是副本,要更改其内容需要如下形式:

    for(type &var:expression)
        statement
    
  • 如果某个索引是带符号类型的值将自动转换成由string::size_type表达的无符号类型。

  • vector能容纳绝大多数类型的对象作为其元素,但是因为引用不是对象,所以不存在包含引用的vector。除此之外,其它大多数(非引用)内置类型和类类型都可以构成vector对象,甚至组成vector的元素也可以是vector。

  • 三种情况下C++的几种不同初始化方式不通用:

    • 使用拷贝初始化时(即使用=时),只能提供一个初始值。
    • 如果提供的是一个类内初始值,则只能使用拷贝初始化或使用花括号的形式初始化。
    • 如果提供的是初始值列表,则只能把初始值都放在花括号里进行列表初始化,而不能放在圆括号里:
    vector<string> v1{"a", "an", "the"};    //列表初始化
    vector<string> v2("a", "an", "the");    //错误
  • 不能使用字符串字面值构建vector对象(字符串字面值不是string对象):

    vector<string> v6("hi");    //错误
  • 要使用size_type,需要首先指定它是由哪种类型定义。vector对象的类型总是包含着元素的类型:

    vector<int>::size_type    //正确
    vector::size_type         //错误
  • 只有当元素的值可比较,vector对象才能被比较。

  • 迭代器的距离指的是右侧的迭代器向前移动多少位置就能追上左侧的迭代器,其类型是名为 difference_type 的带符号整型数。string和vector都定义了difference_type,因为这个距离可正可负,所以difference_type是带符号类型的。

  • 因为end返回的迭代器 并不实际 指示某个元素,所以不能对其进行递增或解引用的操作。

  • 只有string和vector等一些标准库类型有下标运算符,而 并非全都 如此。与之类似,所有标准库容器的迭代器都定义了==和!=,但是它们中的大多数都没有定义<运算符。因此,只要我们养成使用迭代器和!=的习惯,就不用太在意用的到底是哪种容器类型。

  • 迭代器这个名词有三种不同的含义:可能是迭代器概念本身,也可能是指容器定义的迭代器类型,还可能是指某个迭代器对象。

  • 我们认定某个类型是迭代器当且仅当它支持一套操作,这套操作使得我们能访问容器的元素或者从某个元素移动到另外一个元素。每个容器类定义了一个名为iterator的类型,该类型支持迭代器概念所规定的一套操作。

  • 但凡是使用了迭代器的循环体,都不要向迭代器所属的容器添加元素。(原因见9.3.6节,P315)

  • 当使用字符串字面值对字符数组初始化时,一定要注意字符串字面值的结尾处还有一个空字符,这个空字符也会像字符串的其他字符一样被拷贝到字符数组中去:

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

    int a[] = {0, 1, 2};    //含有3个整数的数组
    int a2[] = a;           //错误:不允许使用一个数组初始化另一个数组
    a2 = a;                 //错误:不能把一个数组直接赋值给另一个数组
    • 一些编译器支持数组的赋值,这就是所谓的编译器扩展。但一般来说,最好避免使用非标准特性,因为含有非标准特性的程序很可能在其他编译器上无法正常工作。
    • 但允许如下初始化:
    int ia[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};    //ia是一个含有10个整数的数组
    auto ia2(ia);                                 //ia2是一个整型指针,指向ia的第一个元素
      但当使用decltype关键字时,上述转换不会发生,decltype(ia)返回的类型是由10个整数构成的数组:
    
    decltype(ia) ia3 = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
    ia3 = p;        //错误:不能用整型指针给数组赋值
    ia3[4] = i      //正确:把i的值赋给ia3的一个元素
  • 理解复杂的数组声明, 要想理解数组声明的含义,最好的办法是从数组的名字开始按照由内向外的顺序阅读

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

  • 初始化数组方法之一:

    unsigned scores[11] = {};    //全部初始化为0
  • C++11引入了begin和end函数,与容器中同名函数作用相似,但可用于数组:

    int ia[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};    //ia是一个含有10个整数的数组
    int *beg = begin(ia);                         //指向ia首元素
    int *last = end(ia);                          //指向ia尾元素的下一位置的指针
  • 一个指针如果指向了某种内置类型数组的尾元素的“下一位置”,则其具备与vector的end函数返回的与迭代类似的功能。特别要注意,尾后指针不能执行解引用和递增操作

  • 和迭代器一样,两个指针相减的结果是它们之间的距离。参与运算的两个指针必须指向同一个数组当中的元素:

    auto n = end(arr) - begin(arr);    //n的值是5,也就是arr中元素的数量
    • 两个指针相减的结果的类型是一种名为 ptrdiff_t 的标准库类型,和size_t一样,ptrdiff_t也是一种定义在cstddef头文件中的机器相关的类型。因为差值可能为负值,所以ptrdiff_t是一种带符号类型。
  • 只要两个指针指向同一个数组的元素,或者指向该数组的尾元素的下一位置,就能利用关系运算符对其进行比较。例如,可以按照如下的方式遍历数组中的元素:

    int *b = arr, *e = arr + sz;
    while(b < e)
    {
        //使用*b
        ++b;
    }

    如果两个指针分别指向不相关的对象,则不能比较它们:

    int i = 0, sz = 42;
    int *p = &i, *e = &sz;
    //未定义的:p和e无关,因此比较毫无意义!
    while(p < e)

    上述指针运算同样适用于空指针和所指对象并非数组的指针。在后一种情况下,两个指针必须指向同一个对象或该对象的下一位置。如果p是空指针,允许给p加上或减去一个值为0的整型常量表达式

  • 虽然标准库类型string和vector也能执行下标运算,但是数组与它们相比还是有所不同。标准库类型限定使用的下村必须是无符号类型,而内置的下标运算无此要求。

  • 不允许使用一个数组为另一个内置类型的数组赋初值,也不允许使用vector对象初始化数组。 相反的,允许使用数组来初始化vector对象。要实现这一目的,只需指明要拷贝区域的首元素地址和尾后地址就可以了:

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

    int ia[3][4];                //大小为3的数组,每个元素是含有4个整数的数组
    //大小为10的数组,它的每个元素都是大小为20的数组,
    //这些数组的元素是含有30个整数的数组
    int arr[10][20][30] = {0};   //将所有元素初始化为0
  • 多维数组可以如下初始化

    //没有标识每行的花括号,与之前的初始化语句是等价的
    int ia[3][4] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11};

    如果仅仅想初始化每一行的第一个元素,通过如下的语句即可:

    //显式地初始化每行的首元素
    int ia[3][4] = {{ 0 }, { 4 }, { 8 }};
  • 多维数组的下标引用

    //用arr的首元素为ia最后一行的最后一个元素赋值
    ia[2][3] = arr[0][0][0];
    int (&row)[4] = ia[1];    //把row绑定到ia的第二个4元素数组上