C++Primer笔记(第三章)

关键词 : string, vector, 迭代器, 数组

第三章

关于string类型 :

  • 直接初始化和拷贝初始化 :

    1
    2
    3
    4
    /* 我理解使用的场景就是初始化需要重复的时候用直接初始化 */
    string s5 = "hiya";
    string s6("hiya"); // 直接初始化
    string s7(10, 'c'); // 直接初始化,s7 = "cccccccccc"
  • 读取操作时, string对象会自动忽略掉开头的空白(空格符, 换行符, 制表符等) , 并从第一个真正的字符开始读起 , 直到遇见下一处空白为止.

  • string类型内置的size函数返回string对象中字符的个数 , 但是其类型并不是int或者unsigned, 而是string::size_type , 作为标准库类型与机器无关 , 但可以确定的是size函数返回的是一个无符号整数 , 所以尽量避免在size()的表达式中调用int类型.

    1
    if(s.size() < -1)		// 通常为true, -1会被自动转换成一个较大的无符号值
  • 标准库允许把字符字面值和字符串字面值转换为string对象, 但每个加法运算符的两侧必须确保至少有一个string对象.

  • “ 因为某些历史原因 , 也为了与C兼容 “, C++中的字符串字面值并不是标准库类型string的对象, 即字符串字面值和string是不同的类型.

  • cctype头文件中定义了一组标准库函数改变某个字符的特性.

  • 使用范围for语句改变字符串中的字符 , 必须把循环变量定义成引用类型 :

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    string str1 = "moss";
    for(auto c: str1)
    {
    c = toupper(c);
    }

    cout << "str1 " + str1 << endl; // 输出str1 moss

    for(auto &c: str1)
    {
    c = toupper(c);
    }
    cout << "str2 " + str1 << endl; // 输出str2 MOSS
  • 下标运算符接受的输入参数是string::size_type类型的值 , 返回值是该位置上字符的引用.

关于vector :

模板本身不是类或函数 , 相反可以将模板看做为编译器生成类或函数编写的一份说明 . 编译器根据模板创建类或函数的过程称为实例化.

  • vector能容纳绝大多数类型的对象作为其元素 , 但是因为引用不是对象 , 所以不存在包含引用的vector.

  • 早期对于嵌套的vector需要在右尖括号和元素类型间加个空格 :

    1
    2
    vector<vector<int> >		// old
    vector<vector<int>> // now
  • 初始化的三个要点:

    • 使用拷贝初始化时 , 只能提供一个初始值 .
    • 如果提供的是一个类内初始值 , 则只能使用拷贝初始化或者使用花括号的形式初始化 (还有什么方式? 直接初始化? ).
    • 如果提供的是初始元素值的列表 , 则只能把初始值都放在花括号里进行列表初始化 , 而不是圆括号里.
1
2
3
4
5
6
vector<string> articles = {"a", "an", "the"};		// 列表初始化
vector<int> ivec(10); // 值初始化,长度为10,每个都是0
vector<string> svec(10); // 值初始化,长度为10,每个都是空字符串
/* 如果vector对象中元素的类型不支持默认初始化, 则必须提供初始的元素值 */
/* 如果只提供了元素的数量而没有设定初始值, 则只能使用直接初始化 */
/* 总结: 圆括号()用来构造vector对象, 花括号{}用于列表初始化vector对象*/
  • 范围for语句内不应改变其所遍历序列的大小( 这点lua与其相同 ).
  • vector对象(以及string对象)的下标运算符可用于访问已存在的元素 , 而不能用于添加元素 .(仅仅是添加吗 ? 删除可不可以?)

关于迭代器 :

  • 所有标准库容器都可以使用迭代器 , 但是其中只有少数几种才同时支持下标运算符 .

  • end成员返回的迭代器称为尾后迭代器 , 指向容器尾元素的下一位置 , 当容器为空时 , begin和end指向的是同一个迭代器 .

  • 迭代器类型 :

    拥有迭代器类型的标准库类型使用iteratorconst_iterator来表示迭代器的类型 . 顾名思义, const_iterator只能读它所指元素值. 当容器中对象是一个常量 , 则只能使用const_iterator , 同样返回值也依据这个规则 , 可以使用cbegin()cend()来强制获得const_iterator.

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

    解引用迭代器可获得迭代器所指的对象, 对于类对象可以进一步访问它的成员 :

    1
    2
    3
    4
    vector<string> vecs;						// 定义vecs
    vector<string>::iterator it = vecs.begin(); // 定义vecs的迭代器
    (*it).empty(); // 解引用it, 并调用对象的empty成员, 必须加圆括号(应该是运算符优先级问题)
    it->empty(); // 与上文等效

    箭头运算符把解引用和成员访问两个操作结合到了一起.

  • 已知的一个限制是不能在范围for循环中向vector对象添加元素 , 另外一个限制是任何一种可能改变vector对象容量的操作 , 比如push_back, 都会使该vector对象的迭代器失效(待测试删除)

关于数组 :

  • 数组长度固定 , 在编译的时候维度应该是已知的 , 所以定义的时候维度应是一个常量表达式.

  • 字符串字面值末尾的空字符也会被拷贝到字符数组中.

  • 不能将数组的内容拷贝给其他数组作为初始值, 也不能用数组给其他数组赋值. (某些编译器可能支持但最好不要依赖)

  • 复杂的数组声明 :

    1
    2
    3
    4
    5
    6
    /* 要想理解数组声明的含义, 最好的办法是从数组的名字开始按照由内向外的顺序阅读 */
    int *ptrs[10]; // 含有10个整型指针的数组
    int &refs[10] = ; // 不存在引用的数组
    int (*Parray)[10] = &arr; // 指针, 指向一个长度为10的整型数组
    int (&arrRef)[10] = arr; // 引用, 绑定于一个长度为10的整型数组
    int *(&arry)[10] = ptrs; // 引用, 绑定于一个含有10个指针的数组
  • 在大多数表达式中 , 使用数组类型的对象其实是使用一个指向该数组首元素的指针.

    1
    2
    3
    4
    int ia = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
    auto ia2(ia); // ia2是一个整型指针, 指向ia的第一个元素
    /* decltype关键字不会发生从数组对象到指针的转换 */
    decltype(ia) ia3 = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}; // ia3是一个长度为10的整形数组
  • 标准库函数begin和end

    由于数组不是类 , 所以没有对应的成员函数 , 不过可以作为参数传入begin和end中返回对应位置的指针 .

  • 两个指针相减的结果类型为ptrdiff_t, 是一种带符号类型 . 参与运算的两个指针必须是指向同一个数组中的元素.

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

    1
    2
    int *p = &ia[2];
    int k = p[-2]; // p[-2] = ia[0]
  • 多维数组 : 数组的数组

    要使用范围for语句处理多维数组 , 除了最内层的循环外 , 其他所有循环的控制边浪都应该是引用类型. (否则控制变量将转换为指针, 遍历指针将非法)

    1
    2
    for(auto row : ia)
    for(auto col: row) // 将无法通过编译

string对象与C风格字符串

  • 允许使用以空字符结束的字符数组来初始化string对象或为string对象赋值

  • 以空字符结束的字符数组可以参与string对象的加法运算 ; 在string对象的复合赋值运算中允许使用以空字符结束的字符数组作为右侧的运算对象

  • 规则反过来则不成立

    1
    2
    3
    char *str = s;		// 错误, 不能用string对象初始化char*
    const char *str = s.c_str(); // 正确
    /* 无法保证c_str返回的数组一直有效, 所以如果想一直使用最好copy一份自己维护 */
  • 可以使用数组初始化vector对象:

    1
    vector<int> ivec(begin(ia), end(ia));	// 同理, 可以计算指针使用部分ia内容初始化ivec