C++primer(第五版)读书笔记&习题解答---CHAPTER 3

  • C++标准库类型包括:string,vector和迭代器,其中string是可变长的字符序列,vector存放的是某种给定类型对象的可变长序列,迭代器是string和vector的配套类型,常被用于访问string中的字符和vector中的元素。

  • 内置类型是C++直接定义的,与硬件密切相关,而标准库类型是一种高级类型,尚未实现到硬件中。如:相较于标准库类型string和vector,数组在灵活性上稍显不足。

3.1 命名空间的using声明

  • std::cin的理解:“::”表明编译器应从操作符左侧名字所示的作用域中寻找右侧那个名字,故std::cin意味使用命名空间std中的名字cin。为了在编写程序时简短输入,使用using声明,形式为:using namespace ::name;
  • 每个名字都需要独立的using声明,C++语言形式自由,既可以只有一条using声明语句,也可以多条,但要保证用到的每个名字都必须有自己的声明语句。

  • 头文件不应包含using声明,因为头文件的内容会拷贝到所有引用它的文件中,当头文件中含有using声明时,每个使用该头文件的文件都含有此条声明,对于某些程序,不经意包含一些名字,可能会发生名字冲突。

练习3.1答案
1.9 while循环实现50到100的正数相加
#include<iostream>
using std::cout
using std::endl;
int main()
{
     int sum=0,i=50;
     while(i<=100)
     {
         sum+=i;
         i++;
     }
     cout<<sum<<endl;
     system("pause");
     return 0;
}


1.10 逆序打印10到0之间的整数
#include<iostream>
using std::cout
using std::endl
int main()
{
    int i=10;
    while(i>=0){
        cout<<i<<endl;
        i--;
    }
    system("pause");
    return 0;
}
1.11 键入两整数,打印两整数间的所有整数
 #include <iostream> 
 using std::cout;
 using std::cin;
 
 void print_range(int lo, int hi)
 {
     if (lo > hi)
         print_range(hi, lo);
     for (int i = lo; i != hi; ++i)
         cout << i << " ";
 }
 
 int main()
 {
    int low = 0, high = 0;
     cout << "please input two integers:\n";
     cin >> low >> high;
     print_range(low, high);
     return 0;
 }
2.41 略

3.2 标准库类型string

  • string是可变长的字符序列,使用string类型必须首先包含string头文件,string定义在命名空间std中。
  • string的初始化

    string s1;               // 默认初始化,s1是一个空字符串
    string s2 = s1;          // s2是s1的副本
    string s3 = "hiya";  // s3是该字符串字面值的副本
    string s4(10, ' c ');   // s4的类容是cccccccccc
  • string s1     // 默认初始化,s1是一个空串
  • string s2(s1)      // s2是s1的副本
  • string s2 = s1      // 等价于s2(s1),s2是s1的副本
  • string s3("value")     // s3是字面值“value"的副本,除了字面值最后的那个空字符外
  • string s3 = "value"     // 等价于s3("value"),s3是字面值”value"的副本
  • string s4(n, ' c')    // 把s4初始化为由连续n个字符c组成的串
  • 使用等号初始化实际上是拷贝初始化,编译器直接把等号右侧的初始值拷贝到新的对象上,想反,不含等号的初始化是直接初始化化
    string s5 = "hiya";                 //拷贝初始化
    string s6("hiya");                  //直接初始化
    string s7(10, ' c');                 //直接初始化
  • 用拷贝初始化对多个值进行初始化时,须创建一个临时对象用于拷贝
    string s8 = string(10, 'c');          //可读性差
    等价于如下:
    string temp(10, 'c');
    string s8 = temp;
  • 一个类除规定初始化其对象方式外,还需定义在对象上所能执行的操作
  • 有关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中所含的字符完全一样,则它们相等,返回true
    s1!=s2 如果s1和s2中所含的字符不一样,返回true
    <, <=, >, >= 利用字符在字典中的顺序进行比较,且对字母的大小写敏感
  • 读写string,输入cin >> s;执行读取操作时,string对象会自动忽略开头的空白(空格符、换行符、制表符等)并从第一个真正的字符开始读起,直到下一处空白为止。
    输入 cin >> s1 >> s2 >>……;执行读取多个输入
  • 读取数量未知数量的string对象
    #include <iostream>
    #include <string>
    using namespace std;
    int main()
    {
             string word;
             while (cin >> word)         // 反复读取,直到文件末尾(Windows以Crtl+Z表示结束符)
                       cout << word << endl;
             return 0;
    }
  • 使用getline读取一整行,getlin函数的参数是一个输入流和一个string对象,函数从给定的输入流读入内容,直到换行符(包括换行符)为止,然后将所读内容拷贝到string的对象中去(此时不包含换行符), getline只要一遇到换行符就结束读取操作并返回结果,当开始输入的就是换行符,那么换回的结果就是个空的string。
    #include <iostream>
    #include <string>
    using namespace std;
    int main()
    {
             string line;
             while (getline(cin, line))         // 每次读如一整行,直到文件末尾(Windows以Crtl+Z表示结束符)
                       cout << line << endl;
             return 0;
    }
  • empty是string的一个成员函数,可以使用点操作符调用empty函数

    // 每次读入一整行,遇到空行直接跳过 #include <iostream>
    #include <string>
    using namespace std;
    int main()
    {
    string line;
    while (getline(cin, line)
              if (!line.empty())
              cout << line << endl;
    return 0;

     
  • 同样,size也是string的一个成员函数

    //每次读入一整行,输出其中超过80个字符的行
    #include <iostream>
    #include <string>
    using namespace std;
    int main()
    {
    string line;
    while (getline(cin, line)
         if (line.size() > 80)
         cout << line << endl;
    return 0;
    }

  • 在调用size成员函数时,返回的是一个string::size_type类型的值,是一个无符号类型的值且能够存放任何string对象的大小,所有用于存放string类的size函数返回值的变量,都应是string::size_type类型,在C++11中,通过auto或decltype推断变量的类型:

    auto len = line.size();    //  len的类型是string::size_type

    注意在表达式中不能混用带符号数与无符号数(将会产生意想不到的结果),例如n是一个具有负值的int,则表达式s.size()<n的判断结果几乎肯定是true(因为负值n会自动转换成一个比较大的无符号数)
  • 如果在一条表达式中已经有了size()函数就不要在使用int了,可以避免因混用int与unsigned可能带来的问题
  • string对象相等意味它们的长度相同且所含字符也全都相同,关系运算符<、<=、>、>=分别检验一个string对象是否小于、小于等于、大于、大于等于另外一个string对象。检验顺序按照字典顺序:
            (1)若两个string对象长度不同,且较短string对象的每个字符与较长string对象对应位置上的字符相同,称较短string对象小于较长string对象。
            (2)如果两个string对象在某些对应位置上的不一致,则string对象比较的结果为string对象中第一对相异字符比较的结果
    示例:
    string str = "Hello";
    string phrase = "Hello World";
    string slang = "Hiya";
    可知:str < phrase"; slang > phrase > str.
  • string对象的赋值,string类允许把一个对象的值赋给另外一个对象:
    string st1(10, 'c'),st2;         //st1内容是cccccccccc; st2是一个空串
    st1 = st2;                              //st1与st2都是空串
  • 两个string对象得到新的string对象,该对象内容是把左侧运算对象与右侧运算对象串拼接而成。
    string s1 = "hello, ", s2 = "world\n";
    string s3=s1 + s2;                 // s3的类容是hello, world\n
    s1 += s2;                                 //等价于s1=s1+s2
  • 即使一种类型并非所需,我们也可以使用它,前提是该种类型可以自动转换成所需类型,标准库允许把字符字面值和字符串字面值转换为string对象,在需要string对象的地方可以使用以上两种字面值代替
    string s1 = "hello", s2 = "world";   // 在s1和s2中都没有标点符号
    string s3 = s1 + ", " + s2 + '\n';
    当把string对象与字面值及字符串字面值混合使用时,须确保每个加法运算符的两侧的运算对象至少有一个string
    string s4 = s1 + ", ";     // 正确:string对象与字面值相加
    string s5 = "hello" + ", ";     // 错误:两个运算对象都不是string
    string s6 = s1 + ", " + "world";     // 正确:每个加号都有一个对象是string,
                 等价于string s6 = (s1 + ", ") + "world";
                 等价于:string tmp = s1 + ", "; s6 = tmp + "world";
    string s7 = "hello" + ", " + s2;     // 错误:不能把字面值直接相加
                 等价于string s7 = ("hello" + ", ") + s2;    //错误:不能把字面值直接相加
  • 因为历史原因,C++中的字符串字面值不是标准库类型string的对象,切记,字符串字面值与string是不同的类型

  • 练习3.2:
    编程以实现从标准输入中一次读入一整行;然后修改程序使其一次读入一个词。

    //从标准输入中一次读入一整行

    int main()

    {

        string line;

        cout << "请输入一行文字,包括空格:" << endl;

        while(getline(cin,line))

            cout << line << endl;

        system("pause");

        return 0;

    }

    //一次读入一个词
    int main()
    {
        string word;
        cout << "请输入文字,不含空格:" << endl;
        while(cin >> word)
            cout << word << endl;
        system("pause");
        return 0;
    }


    练习3.3:说明string类的输入运算符和getline函数分别是如何处理空白字符的

    答:string类输入运算符自动忽略空白字符(空格符、换行符、制表符等)并从第一个真正的字符开始读起,直到下一处空白为止,而getline函数从给定的输入流读入内容,直到换行符(包括换行符)为止,但换行符最后并不存储在最后的字符串中。

    联系3.4:编程读入两个字符串并比较器是否相等并输出结果,若不等,输出较大的字符串,编程改写以上程序,比较输入的两个字符串是否等长,若不等长,输出长度交大的字符串。

    // 读入两个字符串并比较器是否相等并输出结果,若不等,输出较大的字符串
    #include <iostream>

    #include <string>

    using namespace std;

    int main()

    {

        string st1,st2;

        cout << "请输入两个字符串" << endl;

        cin >> st1 >> st2;

        if(st1 == st2)

            cout << "两个字符串相等" << endl;

        if(st1 != st2)

        {

            if(st1 < st2)

                cout << "较大的字符串为:" << st2 <<endl;

            else

                cout << "较大的字符串为:" << st1 <<endl;

        }

        system("pause");

        return 0;

    }

    // 编程改写以上程序,比较输入的两个字符串是否等长,若不等长,输出长度交大的字符串

    #include <iostream>
    #include <string>
    using namespace std;
    int main()
    {
        string st1,st2;
        cout << "请输入两个字符串" << endl;
        cin >> st1 >> st2;
        if(st1.size() == st2.size())
            cout << "两个字符串长度相等" << endl;
        if(st1.size() != st2.size())
        {
            if(st1.size() < st2.size())
                cout << "较长的字符串为:" << st2 <<endl;
            else
                cout << "较长的字符串为:" << st1 <<endl;
        }
        system("pause");
        return 0;
    }


    练习3.5:编程实现从标准输入中读入多个字符串并将它们连接在一起,输出连接成的大字符串,然后修改上述程序,用空格把输入的多个字符串分隔开来。

    // 从标准输入中读入多个字符串并将它们连接在一起,输出连接成的大字符串
    #include <iostream>
    #include <string>
    using namespace std;
    int main()
    {
        string st1,st2;
        cout << "请输入多个字符串" << endl;
        while(cin >> st1)
        {
            st2 += st1;
        }
        cout << "拼接的字符串为:" << st2 <<endl;
        system("pause");
        return 0;
    }
    在以上程序中,结束输入时必须在键盘上输入文件结束符(windows为CRTL+Z,UNIX与MAC OS X为CRTL+D),否则程序进入了死循环,这样编写不利于人性化(总有人不知道结束符),推荐如下:
    #include <iostream>
    #include <string>
    using namespace std;
    int main()
    {
        string st1,st2;
        char cnt = 'y';
        cout << "请输入一个字符串" << endl;
        while(cin >> st1)
        {
            st2 += st1;
            cout << "是否继续(y or n)" << endl;
            cin >> cnt;
            if(cnt == 'y'|| cnt == 'Y')
                cout << "请输入下一个字符串" << endl;
            else
                break;
        }
        cout << "拼接的字符串为:" << st2 <<endl;
        system("pause");
        return 0;
    }

    // 修改上述程序,用空格把输入的多个字符串分隔开来
    #include <iostream>
    #include <string>
    using namespace std;
    int main()
    {
        string st1,st2;
        char cnt = 'y';
        cout << "请输入一个字符串" << endl;
        while(cin >> st1)
        {
            st2 += st1;
            st2 += " ";
            cout << "是否继续(y or n)" << endl;
            cin >> cnt;
            if(cnt == 'y'|| cnt == 'Y')
                cout << "请输入下一个字符串" << endl;
            else
                break;
        }
        cout << "拼接的字符串为:" << st2 <<endl;
        system("pause");
        return 0;
    }




    3.2.3 处理string对象中的字符

    • cctype头文件中的函数
      isalnmu(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是空格、横向制表符、纵向制表符、回车符、换行符、进制符中一种)
      issupper(c) 当c是大写字母为真
      isxdigit(c) 当c是十六进制数字为真
      tolower(c) 若c是大写字母,输出对应小写字母;否则原样输出c
      toupper(c) 若c是小写字母,输出对应大写字母;否则原样输出c
    • 在C++中,应该使用名为cname的头文件而不使用name.h的形式
    • C++11范围for语句:遍历给定序列中的每个元素并对序列中的每个值执行某种操作,语法形式为 for(declaration : expression); 其中,expression部分是一个对象,表示一个序列,declaration部分定义一个变量,该变量用于访问序列中的基础元素,每次迭代时,declaration部分的变量会被初始化为expression部分的下一个元素值。

      // 用范围for语句在每行输出string对象中的字一个符(使用范围for语句遍历给定序列的每个元素)
      #include <iostream>
      #include <string>
      using namespace std;
      int main()
      {
      string str("some string");
      for (auto c : str)            // 对于str中的每个字符  c的类型是char
            cout << c <<endl;
      return 0;
      }
      该循环还可以读作对于字符串str中的每个字符c,

      // 用范围for语句和ispunct函数统计string对象中标点符号的个数(使用范围for语句遍历给定序列的每个元素)
      #include <iostream>
      #include <string>
      using namespace std;
      int main()
      {
      string s("Hello World!!!");
      decltype(s.size()) punct_cnt = 0;            //punct_cnt的类型同s.size(),即为string :: size_type
      for (auto c : s)            // 对于s中的每个字符
            if (ispunct(c))       // 如果该字符是标点符号
                 ++ispunct_cnt;  // 计数
      cout << punct_cnt << " punctuation characters in " << s << endl;
      return 0;
      }

      // 用范围for语句将字符串改写为大写字母的形式(使用范围for语句改变字符串中的字符)
      #include <iostream>
      #include <string>
      using namespace std;
      int main()
      {
      string s ("Hello World!!!")
      // 转换成大写形式
      for (auto &c : s)          // 对于s中的每个字符(c是引用)
            c = toupper(c);     // c是一个引用,赋值语句改变了c绑定的字符的值,标准库函数toupper将小写的参数c改为大写
      cout << s << endl;
      return 0;
      }
    • 访问string对象中的单个字符由两种方式:一是使用下标,二是使用迭代器
    • 下标运算符([  ])接收的输入参数是string :: size_type类型的值,参数是要访问的字符的位置,返回值是该位置上字符的引用
    • string对象的下标必须大于等于0二小于s.size(),使用超出范围的下标将引发不可预知的后果,所以,使用下标访问空string也会引发不可预知的后果
    • 下标又称索引,任何表达式只要它的值是一个整型值就能作为索引,如果某个索引是带符号类型的值会自动转换成由string :: size_type表达的无符号类型
    • 不管什么时候只要对string对象使用了下标,必须确保那个位置上确实有值
    • 只要字符串不是常量,就能为下标运算符返回的字符赋新值
      // 将字符串首字符改写为大写形式
      #include <iostream>
      #include <string>
      using namespace std;
      int main()
      {
      string s("some string");
      if (!s.empty())       // 确保s[0]的位置确实有字符
             s[0] = toupper(s[0]);    // 为s的第一个字符赋值
      cout << s << endl;
      return 0;
      }
      程序输出结果为:Some string

      // 将字符串中的第一个词改为大写形式 (使用下标执行迭代)
      #include <iostream> 
      #include <string>
      using namespace std;
      int main()
      {
      string s("some string")
      for(decltype(s.size()) index = 0; index != s.size() && !isspace(s[index]); ++index)       // 只有在index达到s.size()之前才会执行s[index],随着index的增加,它永远也会超过s.size(),确保了index比s.size()小
      s[index] = toupper(s[index]);
      程序输出结果为:SOME string
    • C++规定只有当左侧运算对象为真时才会检查右侧运算对象
    • 一种确保下标必须大于0而小于字符串的s.size()值的简便方法是:总是设下标的类型为string :: size_type,因为此类型是无符号数,以确保下标不会小于0,此时,代码只需保证下标小于size()的值就可以了
    • C++标准不要求标准库检查下标的合法性,一旦下标超出范围,就会产生不可预知的结果
    • 使用下标执行随机访问:通过计算得到某个下标值,人那就直接获取对应位置的字符,并不是每次都得从前往后遍历
      // 把0到15之间的十进制数转换为对应的十六进制形式
      #include <iostream> 
      #include <string>
      using namespace std;
      int main()
      {
      const string hexdigits = "0123456789ABCDEF";         // 可能的十六进制数字
      cout << "Enter a series of numbers between 0 and 15 separated by spaces. Hit ENTER when finished: " << endl;
      string result;             // 保存十六进制的字符串
      string :: size_type n;          // 用于保存从输入流读取的数(因为string下标为string :: size_type类型的值)
      while (cin >> n)
                if (n < hexdigits.size())          // 忽略无效输入
                       result += hexdigits[n];       // 得到对应的十六进制数字
      cout << "Your hex number is: " << result <<endl;
      return 0;
      }
      在上述程序中,把hexdigits声明为常量是因为在后面的程序不打算在改变它的值;除应检查字符串下标的合法性外,在实际使用时,还需要检查n是否小于hexdigits的长度。

                    

    练习3.6:编写程序,使用范围for语句将字符串内的所有字符用X代替

    // 使用范围for语句将字符串内的所有字符用X代替
    #include <iostream> 
    #include <string>
    using namespace std;
    int main()
    {
    string s;
    cout << "请输入一个字符串,可以含空格" << endl;
    getline(cin,s);
    for(auto &c : s)
        c='X';
    cout << s <<endl;
    system("pause");
    return 0;
    }



    练习3.7:就3.6而言,若将循环控制变量的类型设为char将发生什么?先估计,在编程进行验证

    答: 就3.6而言,不会产生任何影响,因为auto推断符同样是char型。

    练习3.8:分别用while循环和传统的for循环重写第一题的程序,判断哪种形式更好,说明原因。

    // 用while循环重写练习3.6
    #include <iostream> 
    #include <string>
    using namespace std;
    int main()
    {
    string s;
    int i = 0;
    cout << "请输入一个字符串,可以含空格" << endl;
    getline(cin,s);
    while(s[i] != '\0')     //'\0'为字符串结束符
    {
        s[i] = 'X';
        ++i;
    }
    cout << s <<endl;
    system("pause");
    return 0;
    }

    // 用for循环重写练习3.6
    #include <iostream> 
    #include <string>
    using namespace std;
    int main()
    {
    string s;
    cout << "请输入一个字符串,可以含空格" << endl;
    getline(cin,s);
    for(string::size_type i = 0;i < s.size(); ++i)
        s[i] = 'X';   
    cout << s <<endl;
    system("pause");
    return 0;
    }

    通过对比以上两个程序,我们发现区别是:while循环通过循环控制变量从前到后检查每个字符是否为字符串结束符,然后在替换字符串中的字符,而for循环通过索引i直接修改字符串中的每个,我们希望的是处理字符串中的全部字符,不用关心处理顺序,所以相比while循环,for循环更加简洁明了。
    练习3.9:判断程序是否合法,有何作用,说明原因

    答:非法,使用下标访问空string会引发不可预知的后果,该程序的作用是通过下标输出string对象s中的第一个字符。

    练习3.10:编程实现读入一个包含标点符号的字符串,将标点符号去除后输出字符串的剩余部分

    // 读入一个包含标点符号的字符串,将标点符号去除后输出字符串的剩余部分
    // 使用范围for
    #include <iostream> 
    #include <string>
    using namespace std;
    int main()
    {
    string s,result;
    cout << "请输入一个字符串,包含标点符号" << endl;
    getline(cin,s);
    for(auto c : s)
        if(!ispunct(c))
            result += c;
    cout << result <<endl;
    system("pause");
    return 0;
    }
    //使用普通for
    #include <iostream> 
    #include <string>
    using namespace std;
    int main()
    {
    string s,result;
    cout << "请输入一个字符串,包含标点符号" << endl;
    getline(cin,s);
    for(decltype(s.size()) i = 0; i < s.size(); ++i)
        if(!ispunct(s[i]))
            result += s[i];
    cout << result <<endl;
    system("pause");
    return 0;
    }


    练习3.11:判断语句是否合法,若合法,说明c的类型

    答:合法,c的类型是常量引用,不能通过c修改其绑定的对象。



    3.3 标准库类型vector

    • 标准库类型vector表示对象的集合,所有对象类型相同。vector又称为容器,在使用vector时,必须包含适当的头文件"#include <vector>" , vector是一个类模板
    • 模板不是类或函数,可以将模板看作为编译器生成类或函数编写的一份说明,编译器根据模板创建类或函数的过程称为实例化,使用模板时,需要指出编译器应把类或函数实例化成何种类型
    • 对类模板来说,通过提供一些额外信息指定模板到底实例化成什么样的类,需要提供哪些信息由模板决定,这些信息用一对尖括号包围跟在模板名字后
      以vector为例,提供的额外信息是vector内所存对象的类型
      vector<int> ivec;     // ivec是保存int类型的对象
      vector<Sales_item> Sales_vec;     // 保存Sales_item类型的对象
      vector<vector<string>> file;     // 该向量的元素是vector对象
    • vector是模板而非类型,由vector生成的类型必须包含vector中元素的类型,如vector<int>
    • 不存在包含引用的vector,因为引用就不是对象!
    • 早期版本与C++11中当vector的元素还是vector(或者其他模板类型)时,早期版本须在外层vector对象的右尖括号和其他元素类型之间添加一个空格,如vector<vector<int> >,而非vector<vector<int>>
    • 某些编译器可能仍需以老式的声明语句来处理元素为vector的vector对象,如vector<vector<int> >

    3.3.1定义和初始化vector对象

    • 初始化vector对象的常用方法

      vector<T> v1 v1是一个空vector,它潜在的元素是T型的,执行默认初始化
      vector<T> v2(v1) v2中包含v1所有元素的副本
      vector<T> v2 = v1 等价于v2(v1),v2中包含有v1所有元素的副本
      vector<T> v3(n,val) v3包含了n个重复的元素,每个元素的值都是val
      vector<T> v4(n) v4包含了n个重复地执行了值初始化的对象
      vector<T> v5{a,b,c…} v5包含了初始值个数的元素,每个元素被赋予相应的初始值
      vector<T> v5 = {a,b,c…} 等价于v5{a,b,c…}

      vector<string> svec;   //默认初始化,svec是一个不含任何元素的vector对象,这是最常见的定义方式,因为空vector在程序运行时可以很高效地往vector对象中添加元素,
      vector<int> ivec;   // 初始状态为空
      vector<int> ivec2(ivec);     //把ivec的元素拷贝给ivec2
      vector<int> ivec3 = ivec;     // 把ivec的元素拷贝给ivec3
      vector<sting> svec(ivec2);    // 错误:svec的元素是string对象,不是int

    • 列表初始化vector对象的元素:用花括号括起来的0个或多个初始元素值被赋给vector对象——>vectro<string> articles = {"a", "an", "the"}

    • 列表初始化与默认初始化在大多数情况下等价使用,但又如下例外情况:
      (1) 使用拷贝初始化(使用=)时,只能提供一个初始值;
      (2) 如果提供的是一个类内初始值,只能使用拷贝初始化或使用花括号的形式初始化;
      (3) 如果提供的是初始元素值的列表,则只能把初始值都放在花括号里进行列表初始化,而不能放在圆括号里
            vector<string> v1{"a", "an", "the"};      // 列表初始化
            vector<string> v2("a", "an", "the");      // 错误

    • 创建指定数量的元素:用vector对象容纳的元素数量和所有元素的统一初始值来初始化vector对象:
      vector<int> ivec(10,-1);     //10个int类型的元素,每个都被初始化为-1
      vector<string> svec(10, "hi!");     // 10个string类型的元素,每个都被初始化为"hi!"
      vector<int> ivec(10);          //10个元素,每个都初始化为0
      vector<string> svec(10);    //10个元素,每个都是空string对象
      在后两种初始化方式当中使用了值初始化(只提供vector对象容纳元素的数量而省略初始值,此时库会创建一个值初始化的元素初值并把它赋给容器中的所有元素)的方式,但这种方式受到两个限制:
      (1)一些类要求必须明确地提供初始值,若vector对象中的元素类型不支持默认初始化,就必须提供元素的初始值
      (2)只提供元素的数量而无初始值,只能使用直接初始化:
      vector<int> vi = 10;     //错误:必须使用直接初始化的形式指定向量的大小

    • 使用花括号与圆括号区分列表初始值与元素数量:
      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个元素,值分别是10和1
      通过以上的初始化我们可以看出,使用圆括号所提供的值用来构造vector对象,使用花括号为列表初始化vector对象,但是当使用花括号的形式但是提供的值又不能用来列表初始化,需考虑这样的值来构造vector对象
      vector<string> v5{"hi"};    //列表初始化:v5有一个元素
      vector<string> v6("hi");    //错误:不能使用字符串字面值构建vector对象
      vector<string> v7{10};       //v7有10个默认初始化的元素
      vector<string> v8{10, "hi"};     //v8有10个值为"hi"的元素
      在以上4个例子中,只有v5是列表初始化,也就是说进行列表初始化vector对象时,花括号里的值必须与元素类型相同,显然不能用int初始化string对象,所以v7,v8提供的值不能作为元素的初始值,确认无法列表初始化后,编译器会尝试用默认值初始化vector对象

    练习3.12:下列vector对象的定义不正确的地方吗?若有,请指出来,对于正取的,描述其执行结果;对于不正确的,说明其错误原因。
    (a) vector<vector<int>> ivec;
    (b) vector<string> svec = ivec;
    (c) vector<string> svec(10, "null");

    答:(a)正确,定义了名为ivec的vector对象,其中每个元素都是vector<int>对象;(b)不正确,定义并初始化名为svec的vector对象,但是ivec的元素是int类型,svec的元素是string类型,所以不能进行赋值;(c)正确,定义并初始化了名为svec的vector对象,该svec的元素个数为10,且每个元素的初始值为空串

    练习3.13:下列的vector对象个包含多少个元素?这些元素的值分别是多少?
    (a) vector<int> v1;     (b) vector<int> v2(10);
    (c) vector<int> v3(10, 42);      (d) vector<int> v4{10};
    (e) vector<int> v5{10, 42};       (f) vector<string> v6{10};
    (g) vector<string> v7{10, "hi"};   

    答:(a) v1包含1个元素,初值为0;(b) v2包含10个元素,每个元素的初值为0;(c) v3包含10个元素,每个元素的值为42;(d) v4包含1个元素,初值为10;(e) v5包含2个元素,元素初值分别是10和42;(f) v6包含10个元素,每个元素默认出事化为空串,(g) v7包含10个元素,每个元素的值都为"hi";

    3.3.2 向vector对象添加元素

    • 对于vector对象,直接初始化适用于:初始值已知且数量较少;初始值是另一个vector对象的副本;所有元素的初始值都一样
    • vector的成员函数push_back可以向vector中添加元素,push_back负责把一个值当初vector对象的尾元素"压到(push)"vector对象的"尾端(back)"。如:
      //向vector中添加0到99的元素
      vector<int> v2;    //空vector对象
      for (int i = 0; i != 100; ++i)
            v2.push_back(i);   //依次把整数值放到v2尾端
      //循环结束后v2有100个元素,值从0到99
      在上例中,一开始还是把vector声明为空vector,在每次迭代时才顺序地把下一个整数作为v2的新元素添加给vector
      //从表中输入中读取单词,将其作为vector对象的元素存储
      string word;
      vector<string> text;   //空vector对象
      while (cin >> word) {
             text.push_back(word);
      }
      在上例中,先创建一个空vector,之后依次读入未知数量的值并保存到text中。
    • 由于能够高效地向vector对象添加元素,必须确保缩写的循环正确无误,特别是在循环有可能改变vector对象容量的时候
    • 使用vector的隐含要求:如果循环体内部包含有想vector对象添加元素的语句,则不能使用范围for循环
    • 范围for语句体内不应改变其所遍历序列的大小
    练习3.14:编写程序,用cin读入一组整数并把它们存入一个vector对象。

    #include <iostream> 
    #include <vector>
    using namespace std;
    int main()
    {
    int num;
    char cont = 'y';
    vector<int> ivec;
    while(cin >> num)
    {
        ivec.push_back(num);
        cout << "还要继续输入吗?(y或者n)"<<endl;
        cin >> cont;
        if(cont != 'Y'&&cont != 'y')
            break;
    }
    for(auto mem : ivec)     //使用范围for循环遍历ivec中的每个元素
        cout << mem << " ";
    cout << endl;
    system("pause");
    return 0;
    }

    练习3.15:改写上述程序,不过这次读入的是字符串

    #include <iostream> 
    #include <vector>
    #include <string>
    using namespace std;
    int main()
    {
    string str;
    char cont = 'y';
    vector<string> svec;
    while(cin >> str)
    {
        svec.push_back(str);
        cout << "还要继续输入吗?(y或者n)"<<endl;
        cin >> cont;
        if(cont != 'Y'&&cont != 'y')
            break;
    }
    for(auto mem : svec)
        cout << mem << " ";
    cout << endl;
    system("pause");
    return 0;
    }

    3.3.3 其他vector操作

    posted on 2015-12-05 18:55  再不努力就死翘翘了  阅读(1135)  评论(0编辑  收藏  举报

    导航