【C++Primer笔记】第三章 字符串、向量和数组

应该优先选用标准库提供的类型,之后再考虑内置的低层的替代品数组或指针。

  1. 命名空间的using声明不应该出现在头文件里。

    using std::cin;
    using namespace std; //懒猪写法
    

string

  1. 初始化:

    #include <string>
    using std::string;
    
    string s1; //s1是空字符串
    string s2 = s1;
    string s3 = "Hiya"; //s2和s3是拷贝初始化
    string s4(10, 'a'); //s2是"aaaaaaaaaa"
    
  2. is>>s从is中读取s的值,以空白格为分隔符。

    getline获取一整行,直到遇到\n(换行符不被读进去)。

    while (cin >> s)
    {...} //直到遇到文件结束符
    while (getline(cin, ss))
    {...} //每次读入一整行,直到到达文件末尾
    
  3. empty()函数返回一个布尔值,size()函数返回一个string::size_type的值(无符号类型)。size_type型和int型不要混用。凡是遇到取字符串中某下标的值,下标要设置为size_type型,且必须检查其小于string.size()(防止数组越界)。

  4. 标准库允许把字符字面值和字符串字面值转换为string对象:

    string s1 = "hello";
    string s2 = s1 + "worl" +'d';
    //注意!!!+的左右两侧至少要有一个是string
    
  5. cctype头文件用于判断字符特性:

    • isalnum© (字母或数字)
    • isalpha© (字母)
    • isdigit© (数字)
    • issapce© (空白)
    • islower©/isupper© (小/大写字母)
    • ispunct© (标点)
    • tolower©/toupper© (不是字母则原样输出)
  6. 范围for语句

    string s("Hello World!!!");
    decltype(s.size()) punctCnt = 0; //为size_type型
    for (auto c : s) //范围for语句
        if (ispunct(c))
            punctCnt ++;
    cout << punctCnt << "punctuation characters in" << s << endl;
    

    如果想要改变string对象中字符的值,必须把循环变量定义为引用类型:auto &c : s

  7. (参见第三点)下标运算符[]返回字符的引用。在访问指定字符前,必须检查s是否为空。整形的下标也会自动转换为size_type型。以下是一个十进制数转十六进制数的神奇方法:

    const string hexdigits = "0123456789ABCDEF";
    string result;
    string::size_type n; //保证大于0
    while (cin >> n)
        if (n < hexdigits.size()) //保证小于size
            result += hexdigits[n]; //这样就不用写switch语句
    

vector

  1. vector是一个类模板,被称为向量或容器。

    vectro<int> i1(10); //十个元素,每个都是0
    vector<string> s1(10, "hi"); //十个元素,每个都是hi
    vector<string> s2{10, "hi"}; //同上
    vector<string> s3{"hi", "hi"}; //两个hi元素
    
  2. vector对象中添加数据用push_back()函数(注:如果循环体内包含向vector对象添加元素的语句,则不能用范围for语句)。vector的操作函数和string差不多(没有+=)。

  3. vector对象的类型总是包括着元素的类型:

    vector::size_type //错误!
    vector<int>::size_type //正确!
    
  4. vector对象和string对象的下标运算符可用于访问已存在的元素,但不能用于添加元素(确保下标合法的一种有效手段:尽可能使用范围for语句)。

迭代器

  1. 除了vector容器之外,标准库还定义了许多容器,它们都能使用迭代器(string也能使用),但只有其中很少几种支持下标运算符。

  2. 迭代器的使用:

    //由编译器决定b和e的类型
    //end成员返回指向容器“尾元素下一位置”的迭代器(不能解引用)
    auto b = v.begin(), e = v.end();
    
  3. 标准容器迭代器的运算符:*iter、->iter、++iter、–iter、iter1==iter2、iter1!=iter2。运算方式和指针类似

    string s("some string");
    if (s.begin() != s.end()) { //判断非空
        auto it = s.begin();
        *it = toupper(*it);
    }
    
  4. 由于标准库的很多容器的迭代器都没定义<运算符,所以我们只要养成使用迭代器和!=的习惯,而不用太在意是哪种容器。如:

    for (auto it=s.begin(); it!=s.end() && 			  !isspace(*it); ++it)
        *it = toupper(*it);
    
  5. 拥有迭代器的标准库类型使用iteratorconst_iterator(能读取但不能修改它所指的元素值)来表示迭代器的类型。如果string对象或vector对象是常量,只能使用后者;反之两者都可以。

    //如果只需要读操作而不需要写操作,最好使用常量类型
    //it的类型是vector<int>::const_iterator
    //不论vector对象是否为常量,返回值都是const_iterator
    auto it1 = v.cbegin(); 
    auto it2 = v.end();
    
  6. 和范围for一样,但凡使用了迭代器的循环体,都不要向迭代器所属的容器添加元素

  7. vectorstring还支持的运算有:iter+n、iter-n、iter1+=n、iter1-=n、iter1-iter2(返回带符号的difference_type型) 、>、<、>=、<=。

  8. 使用迭代器完成二分搜索:

    //text为有序序列
    auto beg = text.begin(), end = text.end();
    auto mid = beg + (end - beg)/2;
    while (mid!=end && *mid!=sought) {
        if (sought < *mid)
            end = mid;
        else
            beg = mid + 1;
        mid = beg + (end - beg)/2;
    }
    

数组

  1. 如果不清楚元素的确切个数(维度),请使用vector

  2. 初始化数组时,维度必须是一个常量表达式

    unsigned cnt = 10; //cnt不是常量表达式
    const unsigned num = 42;
    int a[10] = {}; //初始化10个元素为0
    int *parr[num]; //含有42个整形指针的数组(如果定义在函数内部,默认初始化会令数组含有未定义的值)
    int (*par)[num]; //par指向一个含有42个整数的数组
    int *(&array)[num]; //array是数组的引用,该数组包含42个指针
    
  3. vector一样,数组的元素应为对象,因此不存在引用的数组。

  4. 如果用字符串字面值初始化字符数组,则最后一个元素为’\0’。注意char a[6] = "Daniel";的语句是错误的(数组大小至少是7)。

  5. 数组间不允许拷贝和赋值!

  6. 使用数组下标时,通常将其定义为size_t型(带符号),该类型定义在头文件cstddef中。两数组元素指针相减(这两个指针须指向同一数组内的元素)的结果为ptrdiff_t类型(带符号),同样定义在cstddef头文件中。

  7. 数组名很多时候会被自动转换为指向首元素地址的指针。

    int ia[] = {0,1,2,3,4,5,6};
    auto ia2 = ia; //ia2是一个整形指针
    //上一句等价于 auto ia2(&ia[0]);
    decltype(ia) ia3 = {0,1,2}; //例外!!!
    ia3[2] = 0; //decltype返回数组类型
    
  8. beginend函数被定义在iterator头文件中:

    int ia[] = {0,1,2};
    int *pbeg = begin(ia); //指向第一个元素
    int *pend = end(ia); //指向尾元素的下一位置(尾后指针不能解引用或递增)
    
  9. 只要指针指向的是数组中的元素(或尾元素的下一位置),都可以执行下标运算:

    int a[] = {0,1,2,3,4};
    int p = *(a+3); //p为3
    int *i = &a[2]; //i指向2
    int j = i[1] //i[1]等价于*(i+1),即3
    int k = i[-1] //i[-1]等价于*(i-1),即1
    

C风格字符串(不推荐使用)

  1. 定义在头文件ccstring中的函数包括:strlen()strcmpstrcatstrcpy。传入此类函数的指针必须指向以空字符作为结束的数组

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

    • 允许使用以空字符结束的字符数组初始化string对象或为string对象赋值。
    • string对象的加法运算中,允许使用以空字符结束的字符数组作为其中一个运算对象。

    上述性质反过来不成立。

    string s("Hello world!");
    const char *p = s.c_str();
    
  3. 类似地,允许使用数组初始化vector对象,但不允许用vector对象初始化数组。

    int intArr[] = {0,1,2,3};
    vector<int> ivec(begin(intArr), end(intArr));
    

多维数组

  1. 通常所说的多维数组其实是数组的数组。

    int ia[3][4] = {
        {0,1,2,3},
        {4,5,6,7},
        {8,9,10,11}
    };
    int ib[3][4] = {0,1,2,3,4,5,6,7,8,9,10,11}; //同上
    int ic[3][4] = {{0},{4},{8}}; //未列出的元素默认初始化
    int id[3][4] = {0,3,6,9}; //初始化第一行的4个元素
    
  2. 若使用范围for处理多维数组,除了最内层的循环外,其他所有循环的控制变量都必须是引用类型(避免数组被自动转换为指针)。

    for (const auto &row : ia)
    	for (auto col : row)
    		cout << col << endl;
    
  3. 由多维数组名转换得来的指针实际上是指向第一个内层数组的指针:

    int ia[3][4];
    int (*p)[4] = ia; //p指向ia首元素(一个有4个元素的数组)
    p = &ia[2]; //p指向ia尾元素(同样为一个数组)
    
  4. 使用autodecltype、标准库函数beginend,看起来更简洁:

    for (auto p=begin(ia); p!=end(ia); ++p) {
        for (auto q=begin(*p); q!=end(*p); ++q)
            cout << q << endl;
    } //注意解引用!(p是指向数组的指针)
    
  5. 类型别名化简化多维数组的指针:

    using array = int[4];
    //typedef int array[4];
    for (array *p=ia; p!=ia+3; ++p)
        for (int *q=*p; q!=*p+4; ++q)
            cout << *q << endl;
    
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值