【C++笔记】3. 字符串、向量和数组

3. 字符串、向量和数组

3.1 命名空间的using声明

  1. using std::cin;,之后便可直接使用cin
    每个名字都需要独立的using声明。
    头文件中不应包含using声明

3.2 标准库类型string

  1. string表示可变长的字符序列,vector存放的是某种给定类型对象的可变长序列。

  2. 标准库类型string,需包含string头文件。

  3. 初始化string对象的方式:

    初始化方法说明
    string s1;s1是个空字符串
    string s2(s1);s2是s1的副本
    string s2 = s1;同上
    string s3("value");s3是"value"的副本
    string s3 = "value";同上
    string s4(n, 'c');n个字符c组成的字符串
  4. 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个字符的引用,从0开始
    s1+s2返回s1和s2连接后的结果
    s1=s2用s2的副本代替s1中原来的字符
    s1==s2s1!=s2如果s1和s2中所含的字符完全一样,则相等。string对象的相等性判断对字母的大小写敏感
    <, <=, >, >=利用字符在字典中的顺序进行比较,且对大小写敏感。
  5. cin读入字符串时,string对象会自动忽略开头的空白,直到遇到下一处空白开始。

  6. getline()读取一整行:替代>>运算符,可以保留空白符,遇到换行符结束。
    实际上,换行符会被读入内容,但不会被存入对象。
    最后换行符会被丢弃,同时在结尾加上\0

  7. string的empty和size操作。
    empty返回一个布尔值,表示是否为空。
    size返回一个string::type类型的值,表示sting对象的长度。
    在C++标准中,可以使用auto和decltype来推断变量的类型。
    这里知道,size返回值是一个无符号整型数。
    因此在做条件判断时,n若小于0,s.size()<n必成立。
    显然,这是错误的。

  8. 比较string对象:先比长度,再从前向后比字符。

  9. 为string对象赋值:可以直接用赋值符号=

  10. 两个string对象相加:字符串相接。

  11. 字面值和string相加:自左向右结合,要求+两侧至少一个string对象。
    string s6 = s1 + ", " + "World.";正确。
    string s7 = "Hello" + "," + s2; 错误。

  12. 建议C++程序使用cname的头文件而非name.h。
    标准库中的名字总能在命名空间std中找到。

  13. 针对单个字符,cctype头文件中定义了一组标准库函数来处理这部分工作。

    函数说明
    isalnum©当c是字母或数字时为真
    isalpha©当c是字母时为真
    islower©当c是小写字母时为真
    isupper©当c是大写字母时为真
    isdigit©当c是数字时为真
    isxdigit©当c是十六进制数字时为真
    iscntrl©当c是控制字符时为真
    isgraph©当c不是空格但可以打印时为真
    isprint©当c是可打印字符时为真(即c是空格或具有可视形式)
    ispunct©当c是标点符号时为真(即c不是控制字符、数字、字母、可打印空白)
    isspace©当c是空白时为真(即c是空格、横向制表符、纵向制表符、回车符、换行符、进纸符中的一种
    tolower©如果c是大写字母,输出对应的小写字母;否则原样输出c
    toepper©如果c是小写字母,输出对应的大写字母;否则原样输出c
  14. 范围for语句for(declaration:expression){statement};
    expression是一个对象,用于表示一个序列。
    declaration负责定义一个变量,用于访问序列中的基础元素。
    每次迭代,declaration部分的变量会被初始值化为expression部分的下一个元素值。
    循环读作:对于字符串str中的每个字符c。

    string str("Hello World!!!");
    for (auto c : str)
        cout << c << endl;
    
  15. 范围for语句还可以用来改变字符串中的字符。
    想要改变str对象中字符的值,必须把循环变量定义成引用
    使用引用作为循环控制变量,使变量依次绑定到序列的每个元素上。
    使用这个引用,就能改变它绑定的字符。
    如果直接使用c,则只会改变c的值(类比于指针)。

    string str("Hello World!!!");
    for (auto &c : str)
        c = toupper(c);   // c必须是一个引用。
    cout << str << endl;
    
  16. 只处理一部分字符,可以使用下标,或迭代器。

  17. C++中,string可以使用下标访问string中字符。使用下标时须注意下标范围。

  18. 逻辑运算符&&,运算时,会屏蔽掉0后面的值。

  19. 有一个好习惯是,for循环中的i类型最好设置为decltype(str.size())

3.3 标准库类型vector

  1. 因为vector“容纳着”其他对象,所以也常被称作容器(container)。

  2. 标准库类型vector表示对象的集合,其中所有对象的类型都相同。
    集合中的每个对象都有一个与之对应的索引,索引用于访问对象。
    C++既有类模板,也有函数模板。vector是一个类模板。
    使用vector,须包含vector头文件:
    include <vector>
    using std::vector

  3. vector是一种类模板。编译器通过模板创建类或函数的过程称为实例化。
    当使用模板时,应指出编译器应把类或函数实例化成何种类型:

    vector<int> ivec;                // ivec保存int对象
    vector<Sales_item> Sales_vec;    // 保存Sales_item对象
    vector<vector<string> > file;    // 该向量的元素是vector对象
    // 注意:这里最好使用老式语句来声明,即中间加一个空格
    
  4. vector是模板而非类型。

  5. vector能容纳大多数类型的对象作为其元素(但不能是引用)

  6. 初始化vector的方法:

    初始化方法说明
    vector<T> v1v1是个空字符串,默认初始化
    vector<T> v2(v1)v2是v1的副本
    vector<T> v2 = v1同上
    vector<T> v3{a,b,c...}v3是包含了初始值个数的元素,每个元素被赋予相应的初始值
    vector<T> v3={a,b,c...}同上
    vector<T> v4(n,val)v4包含了n个值为val的元素
    vector<T> v5(n)v5包含了n个元素,默认初始化
  7. 初始化vector时,(){}是不一样的。
    直接初始化、拷贝初始化、列表初始化、值初始化

    vector<int> v0 = 10;              // 错误:不能使用int值构建vector对象
    vector<int> v1(10);               // v1有10个元素,每个值都是 0
    vector<int> v2{10};               // v2有 1个元素,元素的值是10
    vector<int> v3(10,1);             // v3有10个元素,每个值都是 1
    vector<int> v4{10,1};             // v4有 2个元素,分别是 1和10
    vector<string> v5("hi");          // 错误:不能使用字符串字面值构建vector对象。
    vector<string> v6{"hi"};          // v6有 1个元素,每个string都是hi
    vector<string> v7(10);            // v7有10个元素,每个string都是空字符串
    vector<string> v8{10};            // 同上
    vector<string> v9(10, "hi");      // v9有10个元素,每个string都是hi
    vector<string> vX{10, "hi"};      // 同上
    
  8. 向vector中添加元素,须使用成员函数push_back()
    通过for循环,依次把整数值放到vector对象的尾端。
    如需向已有内容的vector对象添加元素,不建议使用for循环。
    基于这种特性,推荐C++在使用vector时不指定长度。

  9. 其他vector操作:

    操作说明
    v.empty()如果v不含有任何元素,返回真
    v.size返回v中元素个数
    v.push_back(t)向v的尾端添加一个值为t的元素
    v[n]返回v中第n个位置上的元素
    v1 = v2用v2中元素的拷贝替换v1中的元素
    v1 = {a,b,c...}用列表中元素的拷贝替换v1中的元素
    v1 == v2v1 != v2v1和v2相等,当且仅当它们的元素数量相同且对应位置的元素值都相同
    <, <=, >, >=按字典顺序进行比较
  10. 需要注意的是:
    (1)size使用时应指明类型:
    vector<int>::size_type;正确。
    vector::size_type错误。
    (2)vector对象可以使用范围for循环。
    (3)和string一样,不能通过下标形式添加元素。

3.4 迭代器

  1. 使用下标可以访问string中的字符、vector中的元素。还可以通过迭代器实现。
    严格来说,string不属于容器,但string确实支持很多与容器类似的操作。
    如:vector和string都支持下标,都支持迭代器。
    类似于指针类型,迭代器也提供了对对象的间接访问。
    有效的迭代器或者指向某个元素,或者指向容器中尾元素的下一个位置;其他情况都属于无效。

  2. 拥有迭代器的类型,都拥有begin和end成员。
    begin负责返回指向第一个元素的迭代器,end负责返回指向容器不存在的“尾后”元素。
    若容器为空,则有begin==end

  3. 迭代器的运算:

    运算说明
    *iter返回迭代器iter所指的元素的引用(下标返回的才是本身)
    iter->mem解引用iter并获取该元素的名为mem的成员,等价于(*iter).mem
    ++iter令iter指示容器中的下一个元素
    --iter令iter指示容器中的上一个元素
    iter1==iter2iter1!=iter2判断两个迭代器是否相等;指向同一个元素或“尾后”元素
  4. 好的编程习惯:使用迭代器写for循环。这种风格在标准库提供的所有容器上都有效。
    养成使用迭代器的习惯,就不用太在意到底是哪种容器类型。即泛型编程。

    for(auto it = s.begin(); it != s.end() && !isspace(*it); ++it)
        *it = toupper(*it);
    
  5. 迭代器类型为iteratorconst_iterator
    前者可读可写,故可进行加、减、相减运算;后者只能读元素。
    begin()end()返回的类型为iterator,除非原容器是const对象。
    保险起见,须返回const_iterator的场合使用cbegin()cend()

  6. 解引用迭代器后访问成员,(*iter).mem等价于iter->mem

  7. 任何一种可能改变vector对象容量的操作,都会使原vector对象的迭代器会失效

  8. 迭代器的算术运算:
    迭代器运算的返回类型为difference_type的带符号整型数,距离可正可负。

    运算说明
    iter + n迭代器指示的新位置前进若干元素(或“尾后”元素)
    iter - n…后退…
    iter += niter + n 后的结果赋给iter
    iter -= niter - n 后的结果赋给iter
    iter1 - iter2计算两元素之间的距离
    > >= < <=判断所指位置谁更靠前或靠后
  9. 使用迭代器的经典场景是二分搜索。

3.5 数组

  1. 数组与vector相似,也是一种存放类型相同的对象的容器。
    这些对象本身没有名字,需要通过其所在位置访问。
    与vector不同的是,数组大小不变,不能随意向数组中增加元素。
    因为数组的大小固定,因此对某些特殊程序运行较好,但相应的损失了一些灵活性。

  2. 定义和初始化数组时,a[d]中的d必须是常量表达式。

  3. 显式初始化数组,必须使用花括号。

  4. 字符数组可以按照C风格字符串来进行初始化。

  5. 不存在引用的数组,但可以引用数组。

  6. 数组不允许直接拷贝和赋值。
    原因:数组名为指针(此处和C语言保持一致)

  7. 复杂的数组声明:

    int array1[10];                // 警告:元素的值未初始化
    int array2[10] = {};           // array2有10个元素,且元素都为0
    int arr[5] = {0, 1, 2, 3, 4};  // 初始化数组只能使用花括号
    int *p[5];                     // p是1个数组,存放了5个指针,指针指向的都是int
    int &r[5];                     // 错误:不存在存放引用的数组
    int (*parr)[5] = &arr;         // parr是1个指针,指向了1个有5个int的数组(arr)
    int (&rarr)[5] = arr;          // rarr是1个引用,引用了1个有5个int的数组(arr)
    int *(&rparr)[5] = p;          // rparr是1个引用,引用了1个数组(p)
    
  8. 访问数组元素可以使用范围for语句,也可以使用下标运算符。

  9. 数组下标数据类型为size_t,它是一种无符号类型。

  10. 数组在使用下标时,和vector一样,要注意避免下标越界。

  11. 使用数组时,编译器会把它转化为指针。实际上,数组名就是指针。

  12. 因此,当使用数组作为一个auto变量的初始值时,推断的类型是指针!
    decltype(array)时,返回的类型是数组。

    int ia[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};          // ia是一个含有10个整数的数组
    auto ia1 (ia);                                      // ia2是一个整型指针,指向ia的第一个元素
    ia1 = 42;                                           // 错误:ia2是一个指针,不能用int值给指针赋值
    auto ia2(&ia[0]);                                   // 显然,ia2的类型是int*
    decltype(ia) ia3 = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};  // ia3是一个含有10个整数的数组
    ia3 = p;                                            // 错误:不能用整型指针给数组赋值
    ia3[4] = i;                                         // 正确:把i的值赋给ia3的一个元素
    
  13. 数组当作指针使用时,与迭代器有以下异同:
    (1)首元素地址:数组名,即迭代器的begin()。
    (2)尾后元素地址:int *e = &arr[10];,其中arr[10]是不存在的元素。

  14. 标准库函数begin和end(iterator头文件中)
    与迭代器不同的是,这两个函数仅仅是普通函数,并非成员函数。

  15. 指向数组元素的指针可以进行解引用、递增、递减、比较、与整数相加(减)的运算。

  16. 两指针相减将得到ptrdiff_t类型的值,是带符号类型。
    迭代器运算的返回类型为difference_type的带符号整型数。
    数组下标是size_t类型,是无符号类型。

  17. 指针和数组下标可以混合使用。
    数组下标不能为负数,但指针下标可以。

    int *p = &a[4];      // p指向a[2]
    int j  =  p[1];      // a[3]的值赋给j
    int k  =  p[-2];     // a[0]的值赋给k
    
  18. C++支持C风格字符串,但不建议使用。
    cstring头文件就是string.h的C++版本。
    C风格字符串不是一种独立的类型。
    而是一种以\0为结尾的char型数组

  19. string标准库函数:
    strlen(s):返回长度
    strcmp(s1, s2):判断是否相等
    strcat(s1, s2):s2接在s1后面
    strcpy(s1, s2):s2拷贝给s1

  20. 两个string对象可以直接比较字符串内容。
    两个C风格字符串直接比较两个地址,是无意义的。
    若想比较C风格字符串的内容,须使用strcmp()函数。

  21. string对象可以用以\0为结尾的字符数组来初始化。
    string对象加法运算,须保证\0不会“夹在”字面值的内部。
    (也就是只允许最右边的string对象有\0

  22. string对象提供了c_str()成员函数,可以用来初始化C风格字符串。

  23. 数组可以初始化vector对象:
    int arr[] = {0, 1, 2, 3, 4, 5};
    vector<int> myVec( begin(arr), end(arr) );

3.6 多维数组

  1. 多维数组就是数组的数组。如int a[3][4];
    a是数组,有3行元素,每个元素是个(列)数组。
    每一个(列)数组都有4个元素,每个元素是int。

  2. 多维数组的初始化:

    // 这两种方式是等价的
    int a[2][3] = { 1, 2, 3, 4, 5, 6 };
    int a[2][3] = {
        {1, 2, 3},
        {4, 5, 6}
    };
    // 初始化第一行,其余默认
    int a[2][3] = { 1, 2, 3, 4 };
    // 初始化第一列,其余默认
    int a[2][3] = {{1},{4}};
    
  3. 多维数组下标引用:int (&row)[3] = a[1];
    表示把row[3]绑定到a[1](三元素的数组)上。

  4. 多维数组的遍历:

    size_t cnt = 0;
    for (auto &row :ia){          // 对于外层数组的每一个元素
        for (auto &col : row){    // 对于内层数组的每一个元素
            col = cnt;            // 将下一个值赋给该元素
            ++cnt;                // 将cnt加1
        }
    }
    // 由上一节知道,auto初始化一个数组会得到指针
    // 因此此处的外层循环row只能是引用。
    // 要使用范围for语句处理多维数组,除了最内层循环外,
    // 所有外层循环的控制变量都应该是引用类型
    
  5. 地址和多维数组
    因为多维数组是数组的数组,所以由多维数组名转换来的指针,
    实际上是指向第一个内层数组的指针。

    int ia[3][4];       // 大小为3的数组,每个元素是含有4个整数的数组
    int (*p)[4] = ia;   // p指向含有4个整数的数组
    p = &ia[2];         // p指向ia的尾元素
    // 上述声明中,圆括号必不可少
    int  *p [4];        // 整型指针的数组
    int (*p)[4];        // 指向含有4个整数的数组
    
  6. C++11新标准中,使用auto或decltype就能使上述代码得到简化:

    for (auto p = ia;  p != ia + 3;  ++p){
        for (auto q = *p;  q != *p + 4; ++q){
            cout << *q << ' ';
        }
        cout << endl;
    }
    
  7. 使用标准库函数begin和end也能实现同样功能。

    for (auto p = begin(ia); p != end(ia); ++p){
        for (auto q = begin(*p); q != end(ia); ++q){
            cout << *q << ' ';
        }
        cout << endl;
    }
    
  8. 使用类型别名简化多维数组的指针:

    using int_array = int[4];    // 声明
    typedef int int_array[4];    // 等价的typedef声明
    for (int_array *p = ia; p != ia + 3; ++p){
        for (int *q = *p; q != *p +4; ++q){
            cout << *q << ' ';
        }
        cout << endl;
    }
    

3.7 补充:size_t和size_type

  1. 为了使自己的程序有很好的移植性,c++程序员应该尽量使用size_tsize_type而不是int, unsigned
  2. size_t全局定义的类型;
    size_type是STL类中定义的类型属性,用以保存任意stringvector类对象的长度。
  3. size_t使用的时候头文件需要 <cstddef>
    size_type使用的时候需要<string>或者<vector>
  4. 二者联系:在用下标访问元素时,vector使用vector::size_type作为下标类型。
    而数组下标的正确类型则是size_t
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值