zoukankan      html  css  js  c++  java
  • C++ Primer 第三章 字符串,向量和数组

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

    3.1 using声明

    • 使用using namespace::name;声明就可以无需专门的前缀也能使用需要的名字了,例如using std::cin
    • 每个using声明引入命名空间中的一个成员,每个名字都需要独立的using声明;
    • 头文件不应包含using声明,因为头文件的内容会拷贝到所有引用他的文件中,如果头文件有using声明,使用了头文件的文件也会有这个声明,对于某些情况,可能会发生始料未及的冲突.

    3.2 string

    3.2.1 定义和初始化

    • 直接初始化 string s1("value");
    • 拷贝初始化 string s1 = "value";
    string s = string(10,'c');
    //等价于
    string tmp(10,'c');
    string s = tmp;
    
    • 上面表示的方法,相比于直接初始化而言,没有什么优势,还会导致可读性较差

    3.2.2 对象上的操作

    • 使用getline读取一整行,可以读到空格和空行
      • getline的输入参数是一个输入流和一个string对象,函数会从给定的输入流中读入内容,直到遇到换行符为止(换行符也会被读进来),然后把所有内容存到string对象,getiline一遇到换仿佛就会结束读取操作并返回结果,如果一开始就是换行符,就会得到一个空的string
      • getline返回的换行符被丢弃了,得到的string实际上不包括换行符
    • string::size_type
      • string.size()不论是返回一个int或者unsigned都是合理的,但实际上它返回的是string::size_type类型的值.
      • 他是一个无符号类型的值,足够存放任何string对象的大小,C++11中允许通过auto或者decltype推断string.size()的类型auto len = line.size();
      • 因为size()是一个无符号数,所以他和有符号数混用就会产生意想不到的结果,例如n是一个负值,s.size() < n的判断结果几乎肯定是True,因为n会自动转化为一个无符号的比较大的值
    • 字面值和对象相加
      • string s3 = s1 + ", " + s2 + ' ';
      • 当string对象和字符字面值以及字符串混在一起使用时,确保每个 + 的两侧运算对象至少有一个是string
    string s6 = s1 + ", " + "world";
    //等价于
    string s6 = (s1 + ", ") + "world"; //正确
    
    string s7 = "hello" + ", " + s2;
    //等价于
    string s8 = ("hello" + ", ") + s2; //错误,两个字符串字面值不能加在一起
    

    3.2.3 处理string对象中的字符

    • C++11中提供的范围for语句,其语法形式是for (declaration: expression) statement;,其中declaration部分负责定义一个变量,expression部分是一个对象,每次迭代,declaration部分的变量会被初始化为expression部分的下一个元素值
    //例如把string中的每个字符每行一个输出
    string str("some string");
    for (auto c : str) {
        cout << c << endl; 
    }
    
    • 使用范围for语句改变字符串中的字符,因为declaration每次是被初始化为下一个元素值,所以可以采用引用的方式,引用不可修改绑定,但是初始化时可以绑定
    string str("some string");
    for(char & c : str) {
        c = toupper(c);
    }
    

    3.3 标准库类型vector

    • vector是模板而非类型,由vector生成的类型必须包含vector中元素的类型,例如vector
    • vector能包含绝大多数的对象作为元素,但是因为引用不是对象,所以引用不能被包含

    3.3.1 定义和初始化

    //默认初始化
    vector<string> a;
    //列表初始化
    vector<string> v1 = {"a","an","the"};
    //创建指定数量的初始化
    vector<string> ivec(10,"hi"); //创建包含10个hi的vector
    vector<int> ivec(10); //创建10个元素,每个都是0
    
    • 对于语句vector<int> svec(10),可以被理解为创建10个元素,也可以被理解为创建一个元素值为10的对象。我们通过圆括号和花括号区分这两个情况
      • vector<int> svec{10}表示用列表初始化的方式,给vector初始化一个值为10的对象
      • vector<int> svec(10)表示10是用来构造vector的,表明的是初始化的容量是10

    3.3.2 向vector添加元素

    • vector通过push_back向末尾插入元素
    • 对于C和Java来说,能预计在创建vector对象时顺便指定容量是最好的,但是事实上恰恰想反,由于vector对象能高效的增长,动态的不断添加元素大多数时候性能会优于直接设定大小,只有在他所有的元素都一样,动态不断添加元素会更好
    • tips:如果循环体内有向vector添加元素的语句,就不能使用范围for循环,范围for循环语句体内不应改变其遍历序列的大小
    • vector的[]返回的是对应位置上的一个引用,如果访问了不存在的元素,就会产生缓冲区溢出的错误。

    3.4 迭代器

    3.4.1 使用迭代器

    • 尾后迭代器
      • end()标识的是一个本不存在的尾后迭代器,这样的迭代器没什么实际含义,只是个标识,表示尾元素的下一位置,如果容易为空,bigin()和end()返回的是同一个迭代器
      • end()返回的迭代器并不实际指示某个元素,所以不能对其进行递增或者解引用的操作
    • 迭代器类型
      • 拥有迭代器的标准库类型使用iterator和const_iterator来表示迭代器的类型
      • const_iterator和常量指针差不多,能读取但是不能修改他所指的元素值,iterator的对象可读可写
      • 如果vector对象或者string对象是一个常量,则只能使用const_iterator,如果不是,则两者都能使用
    • begin()和end()
      • begin()和end()的默认返回指针是iterator,如果我们只需要读数据,就希望可以只返回一个const_iterator
      • 这种情况下,可以考虑采用cbegin()和cend()
    • 成员访问
      • 对于vector<string>::iterator it;而言,可以通过(*it).size()确定迭代器表示字符串的长度,圆括号必不可少,因为.的优先级更高
      • 为了简化这种操作,C++定义了->(箭头运算符),it->size()表达的意思和(*it).size()相同
    • 迭代器的安全问题
      • 但凡使用了迭代器的循环体,不要向迭代器的容器中添加元素
      • 例如往vector中进行可能改变容量的操作,例如push_back,都可能会使得迭代器失效

    3.4.2 迭代器运算

    • vector和string迭代器支持的运算
    • iter + n 将迭代器向前移动n个位置,最终指向容器内的一个元素或者尾元素的下一位置
    • iter - n 将迭代器向后移动n个位置,最终指向容器内的一个元素或者尾元素的下一位置
    • iter1 - iter2 返回它们之间的距离,将右侧迭代器向前移动个差值元素得到左侧的迭代器,参与运算的两个迭代器必须指向同一个容器中的元素或者尾元素的下一个位置,所得结果是名为difference_type的带符号整型数

    3.5数组

    • 现代的C++程序应该尽量使用vector和迭代器,避免使用内置数组的指针;应该尽量使用string而不是C语言基于数组的字符串
    • 因为指针常常会引发一些错误,特别是声明指针时候的语法错误

    3.5.1 定义和初始化

    • 数组不允许拷贝和赋值,有一些编译器支持,这属于编译器扩展,一般来说不建议用
    • 理解复杂数组的声明
    int *ptrs[10]; //指向10个int指针的数组
    int &refs[10] = ...;  //不存在引用的数组
    int (*Parray)[10] = &arr; //指向大小为10的int数组
    int (&arrRef)[10] = &arr; //表示一个大小为10的int数组的引用
    
    • 类型修饰符由右往左绑定,数组由内向外绑定
      • Parray首先是一个指针(*Parray),然后是一个指向大小为10的数组的指针,然后是这个数组是一个int
      • arrRef首先是一个引用,引用的是一个大小为10的数组,之后是数组的类型为int
        int *(&array)[10] = ptrs; array是一个引用了大小为10的int*数组

    3.5.2 访问数组元素

    • 使用数组下标的时候,通常将其定义为size_t类型,size_t是一种机器相关的无符号类型,它被设计的足够大以便能表示内存中任意对象的大小,定义在cstddef中

    3.5.3 指针和数组

    • 使用数组的时候编译器一般会把它转换成指针
    • 数组的指针拥有string和vector迭代器拥有的全部作用,两个指针相减的结果是名为ptrdiff_t的标准库类型,和size_t一样被定义在cstddef中,因为差值可以是负的,所以它是一种带符号类型

    3.5.4 C风格字符串

    • 为了兼容C对字符串的接口,string可以通过.c_str()函数返回一个C风格的字符串,指针类型是const char*,以确保我们不会改变字符数组的内容
    • 但是无法保证c_str返回的数组一直有效,如果后续的操作改变了s的值就可能让之前返回的数组失效,如果执行完c_str函数后程序想一直都能使用其返回的数组,最好将他重新拷贝一份
    • 使用数组初始化vector对象vector<int>ivec(begin(int_arr),end(int_arr))

    3.6 多维数组

    • 严格来说,C++没有多维数组,通常所说的数组其实是数组的数组
    • 当一个数组的元素依然是数组的时候,它通常使用两个维度来定义他
      • 一个维度表示数组本身的大小
      • 另一个维度表示其元素(也是数组)的大小
    int ia[3][4]; //大小为3的数组,每个元素是含有4个int的数组
    int arr[10][20][30]; //大小为10的数组,每个元素是大小为20的数组,这些数组的元素含是有30个int的数组
    
    • 使用范围for语句处理多维数组
    size_t cnt = 0;
    for (auto &row : ia) { 
      for (auto &col : row) {
        col = cnt;
        cnt++;
      }
    }
    //因为我们需要修改数组的元素,所以我们很容易想到去使用引用
    for (const auto &row : ia) { //auto->int[4]
      for (auto col : row) {   //auto->int
        cout << col << endl;
      }
    }
    //但是这个程序,我们依然使用了引用,原因是auto如果不引用,会把col当成是一个普通的指针而非数组
    
    • 以上,使用范围for处理多维数组的时候,除了最内层的循环之外,其他的所有循环的控制变量都应该是引用类型
    • 使用类型别名简化多维数组的指针
    using int_array = int[4];
    typedef int int_array[4];
    
  • 相关阅读:
    常用的输出方法
    使用Action()和ActionLink()生成链接
    "??"运算符
    使用路由数据
    路由匹配总结
    routes.MapRoute()定义路由的要求
    控制器和视图数据传递小结
    跨请求数据传递TempData
    Redis安装创建
    JAVA获取当前时间加一天
  • 原文地址:https://www.cnblogs.com/Hugh-Locke/p/13040330.html
Copyright © 2011-2022 走看看