《c++ Primer中文版》第三章:字符串、 向量和数组

目录

第三章:字符串、 向量和数组

3.1命名空间using声明

3.2标准库类型string

3.2.1 定义和初始化string对象

3.2.2string对象上的操作

3.2.3处理string对象中的字符

3.3标准库类型vector

3.3.1定义和初始化vector对象​

3.3.2向vector对象中添加元素

3.3.3其他vector操作​

3.4迭代器介绍

3.4.1使用迭代器

3.4.2迭代器运算​

3.5数组

3.5.1定义和初始化内置数组

3.5.2访问数组元素

3.5.3指针和数组

3.5.4C风格字符串​​

3.5.5与旧代码的接口

3.6多维数组


第三章:字符串、 向量和数组

3.1命名空间using声明

域操作符(::)            using 声明(using declaration)       一但声明之后,就能使用名字了。

#include <iostream>

//using 声明
using std::cin;

int main()
{
   int i;
   cin >> i;
   //cout << i;//错误
   std::cout << i; //正确:显式地从std中使用const
   return 0;
}

每个名字都需要独立的using声明
头文件不应包含using声明:
位于头文件的代码一般来说不应该使用using声明。这是因为头文件的内容会拷贝到所有引用它的文件中去,如果头文件里有某个using声明,那么每个使用了该头文件的文件就都会有这个声明。对于某些程序来说,由于不经意间包含了一些名字,反而可能产生始料未及的名字冲突。

3.2标准库类型string

头文件
#include <string>
定义在名名空间 std 中
using std::string;

3.2.1 定义和初始化string对象

初始化

                       初始化string对象的方式

string s1;//默认初始化,sl是一个空串
string s2 (s1); //s2是s1的副本
string s2 = s1; //等价于s2(s1),s2是sl的副本
string s3 ("value");//s3是字面值"value"的副本,除了字面值最后的那个空字符外
string s3 = "value";//等价于s3("value"), s3是字面值"value"的副本
string s4(n, 'c');//把s4初始化为由连续n个字符c组成的串

直接初始化和拷贝初始化
等号(=)初始化一个变量,实际上执行的是拷贝初始化( copy initialization),编译器把等号右侧的初始值拷贝到新创建的对象中去。如果不使用等号,则执行的是直接初始化(direct initialization)

string s5 = "hiya" ; //拷贝初始化
string s6 ("hiya") ;//直接初始化
string s7(10, 'c') ;//直接初始化,s7的内容是CCCCCccccc

对于用多个值进行初始化的情况,非要用拷贝初始化的方式来处理也不是不可以,不过需要显式地创建一个(临时)对象用于拷贝:

string s8 = string (10, c') ;//拷贝初始化,s8的内容是cccccccccc

等价于
string temp(10, 'c') ;// temp 的内容是ccccccccee
string s8 = temp; //将temp拷贝给s8

3.2.2string对象上的操作

1.读写string对象

#include <iostream>
#include <string>
using std::string;
using std::cin;
using std::cout;
using std::endl;
int main()
{
	string s;//空字符串
	cin >> s;//将string对象读入s,遇到空白停止
	cout << s << endl;//输出s
	return 0;
}

   string s1, s2;
   cin >> s1 >> s2;//把第一个输入读到s1中,第二个输入读到s2中
   cout<<s1<<s2<<endl;//输出两个string对象

读取操作时,string 对象会自动忽略开头的空白(即空格符、换行符、制表符等)并从第一个真正的字符开始读起,直到遇见下一处空白为止。

2.读取未知数量的string对象

#include <iostream>
#include <string>
using std::string;
using std::cin;
using std::cout;
using std::endl;
int main()
{
   string word;
   while (cin >> word) // 反复读取, 直至到达文件末尾
       cout << word << endl;//逐个输出单词,每个单词后面紧跟一个换行

   return 0;
}

3.使用getline读取一整行

字符串中保留输入时的空白符,这时应该用
getline函数代替原来的>>运算符

getline 函数的参数一个输入流一个string象,函数从给定的输入流中读入内容直到遇到换行符为止(注意换行符也被读进来了),然后把所读的内容存入到那string对象中去(注意不存换行符)

getline只要一遇到换行符就结束读取操作并返回结果,哪怕输入的一开始就是换行符也是如此。如果输入真的一开始就是换行符, 那么所得的结果是个空string。

#include <iostream>
#include <string>
using std::string;
using std::cin;
using std::cout;
using std::endl;
int main()
{
   string line; //每次读入一整行,直至到达文件末尾
   while (getline(cin, line))
         cout << line << endl ;
   return 0;
}

因为line中不包含换行符,所以我们手动地加上换行操作符。使用endl结束当前行并刷新显示缓冲区。

4.string的 empty 和 size操作

empty()函数根据string对象是否为空返回一个对应的布尔值

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

   return 0;
}


size函数返回string对象的长度(即string对象中字符的个数),可以使用size函数只输出长度超过80个字符的行:
 

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

5.string::size_type 类型

size() 函数的返回值的类型 就是 size_type的 
string类及其他大多数标准库类型都定义了几种配套的类型。这些配套类型体现了标准库类型与机器无关的特性,类型size_ type 即是其中的一种。在具体使用的时候,通过作用域操作符来表明名字size_type 是在类string中定义的。
一点是肯定的:它是一个无符号类型的值而且能足够存放下任何string对象的大小。所有用于存放string类的size函数返回值的变量,都应该是string::size_type类型的。

c++11:可以通过过auto 或者 decltype 来推断。

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

6.比较string对象

   1.如果两个string对象的长度不同,而且较短string对象的每个字符都与较长string对象对应位置上的字符相同,就说较短               string对象小于较长string对象。
   2.如果两个string对象在某些对应的位置上不一致,则string对象比较的结果其实是string对象中一对相异字符比较的结果。

#include <iostream>
#include <string>
using std::string;
using std::cin;
using std::cout;
using std::endl;
int main()
{
    string str = "Hello";
    string phrase = "Hello World";
    string slang = "Hiya";
    if(str < phrase)
        cout << " str < phrase " << endl;
    if(phrase < slang)
        cout << " phrase < slang " << endl;

}

7.为string对象赋值
在设计标准库类型时都力求在易用性上向内置类型看齐,因此大多数库类型都支持赋值操作。对于string类而言,允许把一个对象的值赋给另外一个对象:

string st1(10, 'c'), st2;// stl 的内容是ccccccccc st2是一个空字符串
st1 = st2;//赋值:用st2的副本替换st1的内容
//此时st1和st2都是空字符串

8.两个string对象相加

#include <iostream>
#include <string>
using std::string;
using std::cin;
using std::cout;
using std::endl;
int main()
{
   string s1 = "hello" , s2 = "world\n";
   string s3 = s1 + s2; // s3的内容是hello, world\n
   s1 += s2;//等价于s1=s1+s2

   cout << s1 << endl;
   cout << s3 << endl;
}

9.字面值和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 s7 = "hello" +","+ s2; //错误:不能把字面值直接相加
string s6 =(s1 + ",") + "world" ;//对的
string s7 =("hello" + ",") + s2; //错误:不能把字面值直接相加


练习3.2: 编写一段程序从标准输入中一次读入一整行,然后修改该程序使其一次读入一个词。

#include <iostream>
#include <string>
using namespace std;
int main()  // 使用getline 一次读入一整行,遇回车结束
{
    string line; //循环读取,每次读入一整行,直至文件结束或遇到异常输入
    cout << "请输入您的字符串,可以包含空格: "<< endl;
    while (getline (cin, line) )
              cout << line << endl ;
    return 0;
}
#include <iostream>
#include <string>
using namespace std;
int main()
//使用cin一次读入一个词,遇空白结束
{
    string word;
    //循环读取, 每次读入一个词, 直至文件结束或遇到异常输入
    cout << "请输入您的单词,不可以包含空格:"<< endl;
    while (cin >> word)
         cout << word << endl;
        //为了便于观察,输出每个单词后换行
    return 0;
}


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

#include <iostream>
#include <string>

using namespace std;

int main()
{   
    string word, line;
    cout << "请选择读取字符串的方式: 1表示逐词读取,2表示整行读取" << endl;
    char ch;
    cin >> ch;
    if(ch =='1')
    {
        cout << "请输入字符串:   Welcome to C++ family! "<< endl;
        cin >> word;
        cout <<"系统读取的有效字符串是:"<< endl;
        cout << word << endl;
        return  0;
    }
    //清空输入缓冲区
    cin.clear();
    cin.sync();
    if (ch == '2')
    {
        cout << "请输入字符串: Welcome to C++ family! "<< endl;
        getline(cin, line);
        cout <<" 系统读取的有效字符串是:"<< endl;
        cout << line << endl;
        return 0;
    }
     cout <<"您的输入有误!";
     return -1;

}
#include <iostream>
#include <string>

using namespace std;

int main()
{
	string word, line;
	cout << "请选择读取字符串的方式: 1表示逐词读取,2表示整行读取" << endl;
	string ch;
	getline(cin, ch);
	if (ch == "1")
	{
		cout << "请输入字符串:   Welcome to C++ family! " << endl;
		cin >> word;
		cout << "系统读取的有效字符串是:" << endl;
		cout << word << endl;
		return  0;
	}
	//清空输入缓冲区
	cin.clear();
	cin.sync();
	if (ch == "2")
	{
		cout << "请输入字符串: Welcome to C++ family! " << endl;
		getline(cin, line);
		cout << " 系统读取的有效字符串是:" << endl;
		cout << line << endl;
		return 0;
	}
	cout << "您的输入有误!";
	return -1;

}


练习3.4:编写- -段程序读入两个字符串,比较其是否相等并输出结果。如果不相等,输出较大的那个字符串。改写上述程序,比较输入的两个字符串是否等长,如果不等长,输出长度较大的那个字符串。

#include <iostream>
#include <string>
using namespace std;
int main()
{
    string s1, s2;
    cout << "请输入两个字符串:" << endl;
    cin >> s1 >> s2;
    if(s1 == s2)
    cout << "两个字符串相等" << endl;
    else if (s1 > s2)
    cout << s1 <<"大于"<< s2 <<endl;
    else
    cout << s2 <<"大于"<< s1 <<endl;
    return 0;
}
#include <iostream>
#include <string>
using namespace std;
int main()
{
    string s1, s2;
    cout << "请输入两个字符串:"<< endl;
    cin >> s1 >> s2;
    auto len1 = s1.size();
    auto len2 = s2.size();
    if(len1 == len2)
        cout<<s1<<"和"<<s2<<"的长度都是"<< len1<< endl;
    else if (len1 > len2)
        cout << s1 <<"比" << s2 <<"的长度多"<< len1 - len2 << endl;
    else
        cout << s1 <<"比" << s2  << "的长度小"<<len2-len1 << endl;

}

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

#include <iostream>
#include <string>
using namespace std;
int main()
{
    char cont = 'y' ;
    string s, result;
    cout <<"请输入第一个字符串: "<< endl;
    while(cin >> s)
    {
        result += s;
        cout << "是否继续(y or n)?" << endl;
        cin >> cont;
        if (cont == 'y' || cont == 'Y')
            cout <<"请输入下一个字符串:"<< endl;
        else
            break;
     }
    cout <<"拼接后的字符串是: "<< result <<endl;

}
#include <iostream>
#include <string>
using namespace std;
int main(){
    char cont = 'y';
    string s, result;
    cout << "请输入第一个字符串:"<< endl;
    while(cin >> s)
    {
        if(!result.size())
           //第一个拼接的字符串之前不加空格
           result += s;
        else
            //之后拼接的每个字符串之前加一个空格
            result = result +" "+ s;
        cout << "是否继续(y or n)?"<< endl;
        cin >> cont;
        if(cont == 'y' || cont == 'Y')
            cout <<"请输入下一个字符串:"<< endl;
        else
            break;
    }

    cout << "拼接后的字符串是:"<< result <<endl;
    return 0;
}


3.2.3处理string对象中的字符

1.cctype头文件

2.处理每个字符?使用基于范围的for语句  c++11   范围for (range for)

string str ("some string") ;
//每行输出str中的一个字符。
for(auto c : str)//对于str中的每个字符
   cout<< c <<endl;//输出当前字符,后面紧跟一个换行符
#include <iostream>
#include <string>
#include <typeinfo>

using namespace std;

int main()
{
    string s("Hello World!!!"); // punct_ cnt的类型和s.size的返回类型一样;参见2.5.3节(第62页)
    decltype(s.size()) punct_cnt = 0;
             //统计s中标点符号的数量
    for (auto c : s) //对于s中的每个字符
        if (ispunct(c)) //如果该字符是标点符号
            ++punct_cnt; //将标点符号的计数值加1
    cout << punct_cnt <<"punctuation characters in " << s << endl;
    return 0;
}

3.使用范围for语句改变字符串中的字符

#include <iostream>
#include <string>
#include <cctype>

using namespace std;

int main()
{
    string s("Hello World!!!");//转换成大写形式。
    for (auto &c : s)//对于s中的每个字符(注意: C是引用)
            c = toupper (c) ;//c是一个引用,因此赋值语句将改变s中字符的
    cout << s << endl;

    return 0;

}

4.只处理一部分字符?
下标运算符([ ])接收的输入参数是string::size_type类型的值,这个参数表示要访问的字符的位置:返回值是该位置上字符的引用。

string对象的下标从0计起。如果string对象s至少包含两个字符,则s[0]是第1个字符、s[1]是第2个字符、s[s.size()-1] 是最后一个字符。
5.使用下标执行迭代

#include <iostream>
#include <string>
#include <typeinfo>
#include <cctype>

using namespace std;

int main()
{
    string s("some string!");
    //依次处理s中的字符直至我们处理完全部字符或者遇到一个空白
    for ( decltype(s.size()) index = 0;
        index != s.size() && !isspace (s[index]); ++index)
            s[index] = toupper (s[index]); //将当前字符改成大写形式

    cout << s << endl;
    return 0;
}

6.使用下标执行随机访问

#include <iostream>
#include <string>
#include <typeinfo>
#include <cctype>

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;//用于保存从输入流读取的数
    while (cin >> n){
        if (n < hexdigits.size())// 忽略无效输入
            result += hexdigits[n]; // 得到对应的十六进制数字
    }

    cout << "Your hex number is : "<< result << endl;

    return 0;
}

练习3.6:编写一段程序,使用范围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;
    return 0;
}
#include <iostream>
#include <string>
using namespace std;
int main(){
    string s;
    cout << "请输入一个字符串,可以包含空格:"<< endl;
    getline(cin, s); //读取整行,遇回车符结束
    for (auto &c : s)//依次处理字符串中的每一个字符
    {
        if(isalpha(c))
           c ='X';
    }
    cout << s <<endl;
    return 0;
}

 

3.3标准库类型vector

标准库类型vector表示对象的集合,其中所有对象的类型都相同。集合中的每个对象都有一个与之对应的索引,索引用于访问对象。因为vector“容纳着”其他对象,所以它也常被称作容器(container)

是个类模板( class template )编译器根据模板创建类或函数的过程称为实例化(instantiation)

#include <vector>
using std::vector;

vector能容纳绝大多数类型的对象作为其元素,但是因为引用不是对象,所以不存在包含引用的vector。除此之外,其他大多数(非引用)内置类型和类类型都可以构成vector对象

c++11

3.3.1定义和初始化vector对象

vector<string> svec; //默认初始化,svec 不含任何元素
vector<int> ivec ;//初始状态为空
//在此处给ivec添加一些值
vector<int> ivec2 (ivec) ;//把ivec的元素拷贝给ivec2
vector<int> ivec3 = ivec; //把ivec的元素拷贝给ivec3
vector<string> svec(ivec2); // 错误: svec 的元素是string对象,不是int

列表初始化vector对象   c++11

vector<string> articles = {"a","an","the"} ;
vector<string> v1{"a","an", "the"}; //列表初始化
vector<string> v2 ("a", "an", "the"); //错误

创建指定数量的元素

vector<int> ivec(10, -1) ;// 10个int类型的元素,每个都被初始化为一1
vector<string> svec(10, "hi !"); // 10个string类型的元素;//每个都被初始化为"hi !”

值初始化
值初始化的( value- initialized )

vector<int> ivec(10) ;// 10个元素,每个都初始化为0
vector<string> svec(10) ;//10个元素,每个都是空string对象
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

圆括号,可以说提供的值是用来构造( construct) vector对象的。花括号,可以表述成我们想列表初始化( list initialize)

vector<string> v5{"hi"}; //列表初始化: v5有一个元素
vector<string> v6("hi"); //错误:不能使用字符串字面值构建vector对象
vector<string> v7(10};// v7有10个默认初始化的元素
vector<string> v8{10,"hi"};//v8有10个值为"hi"的元素

3.3.2向vector对象中添加元素

#include <iostream>
#include <vector>
using namespace std;
int main(){
    vector<int> v2;//空vector对象
    for(int i=0;i!=100;++i)
      v2.push_back(i); //依次把整数值放到v2尾端
    // 循环结束后v2 有100个元素,值从0到99

     //打印
     for(auto i: v2)
         cout << i << " ";
    return 0;
}
#include <iostream>
#include <vector>
#include <string>
using namespace std;
int main(){
    //从标准输入中读取单词,将其作为vector对象的元素存储
    string word;
    vector<string> text; //空vector对象
    while (cin >> word) {
        text.push_back(word);//把word添加到text后面
        if(word == "end")
            break;
    }
    //打印
    for(auto i: text)
        cout << i << " ";
    return 0;
}

向vector对象添加元素蕴含的编程假定
随着对vector的更多使用,其中一条是现在就要指出的:如果循环体内部包含有向vector对象添加元素的语句,则不能使用范
围for循环。

3.3.3其他vector操作

#include <iostream>
#include <vector>
#include <string>
using namespace std;
int main(){
    vector<int> v{1,2,3,4,5,6, 7,8,9};
    for (auto &i :v)//对于v中的每个元素(注意: i是一个引用)
        i*=i;//求元素值的平方
    for (auto i : v)//对于v中的每个元素
      cout << i<<" ";//输出该元素
    cout << endl;

    return 0;
}


计算vector内对象的索引

看下面的代码:

不能用下标形式添加元素

严重错误

#include <iostream>
#include <vector>
#include <string>
using namespace std;
int main(){
   
   vector<int> iver ;//空vector对象
   for(decltype(ivec.size()) ix = 0;
          ix != 10; ++ix )
          ivex[ix] = ix; //严重错误:ivex不包含任何元素

    return 0;
}


 


练习3.17:从cin读入一组词并把它们存入一个vector对象,然后设法把所有词都改写为大写形式。输出改变后的结果,每个词占一行。

#include <iostream>
#include <vector>
#include <string>

using namespace std;

int main(){

   //从cin读入一组词并把它们存入一个vector对象,
   //然后设法把所有词都改写为大写形式。输出改变后的结果,每个词占一行。
   vector<string> vString; //元素类型为string的vector对象
   string s;               //记录用户的输入值
   char cont = 'y';        //与用户交互,决定是否继续输入
   cout << "请输入第一个词:"<< endl;
   while(cin >> s)
   {
       vString.push_back(s) ;   //向vector对象中添加元素
       cout <<"请问是否继续操作,是y 否 n?" <<endl;
       cin >> cont;
       if(cont != 'y' && cont != 'Y')
          break;
       cout << "请输入下一个词 " << endl;
   }
   cout << "转换后的结果是:" << endl;
   for(auto &mem : vString)
   {
       for(auto &C:mem)
       {
           C = toupper(C);

       }
       cout << mem << endl;
   }

    return 0;
}

练习3.20:读入一组整数并把它们存入一个,vector对象,将每对相邻整数的和输出出来。改写你的程序,这次要求先输出第1个和最后1个元素的和,接着输出第2个和倒数第2个元素的和,以此类推。

#include <iostream>
#include <vector>
#include <string>

using namespace std;

int main(){

   //求相邻元素的程序
   vector <int> vInt;
   int iVal = 0;
   cout << "请输入一串数字:" << endl;
   while(cin >> iVal)
   {
       vInt.push_back(iVal);
   }

   if(vInt.size() == 0)
   {
       cout << "没有任何的元素" << endl;
       return -1;
   }

  cout << "求首尾元素的和依次:"<< endl;
  //利用decltype推断i的类型
  for(decltype(vInt.size()) i = 0 ; i < vInt.size()/2 ; i++)
  {
      //求首尾两项的和
      cout << vInt[i] + vInt[vInt.size() - 1 -i] << " ";
      //每行输出5个元素
      if((i + 1) % 5 == 0)
         cout << endl;
  }
  //如果元素是奇数 ,单独处理最后一个元素
    if(vInt.size()%2 != 0 )
        cout <<vInt[vInt.sze() / 2];
    return 0;
}
#include <iostream>
#include <vector>
#include <string>

using namespace std;

int main(){

   //求相邻元素的程序
   vector <int> vInt;
   int iVal = 0;
   cout << "请输入一串数字:" << endl;
   while(cin >> iVal)
   {
       vInt.push_back(iVal);
   }

   if(vInt.size() == 0)
   {
       cout << "没有任何的元素" << endl;
       return -1;
   }

   cout << "相邻两项的和依次是:" <<endl;
   //利用decltype推断i的类型
   for(decltype (vInt.size()) i = 0; i <vInt.size() - 1 ;i += 2 )
   {
       //求相邻两项的和
       cout <<vInt[i] + vInt[i+1] << " ";
       //每行输出5个数字
       if((i+2)%10 == 0)
           cout << endl;
   }
   //如果元素数是奇数,单独处理最后一个元素
   if(vInt.size() % 2 != 0)
      cout << vInt[vInt.size() -1];

    return 0;
}

3.4迭代器介绍

迭代器( iterator)

3.4.1使用迭代器

//由编译器决定b和e的类型
// b表示v的第一个元素,e表示v尾元素的下一位置
auto b = v.begin(), e = v.end(); //b 和e的类型相同

end成员则负责返回指向容器(或string对象)“尾元素的下一位置(one past the end)”的迭代器,该迭代器指示的是容器的一个本不存在的“尾后(offthe end)"元素。

这样的迭代器没什么实际含义,仅是个标记而已,表示我们已经处理完了容器中的所有元素。end成员返回的迭代器常被称作尾后迭代器(off-the -end iterator)或者简称为尾迭代器(end iterator)。

特殊情况下如果容器为空,则begin和end返回的是同一个迭代器。

1.迭代器运算

和指针类似,也能通过解引用迭代器来获取它所指示的元素,执行解引用的迭代器必须合法并确实指示着某个元素。试图解引用一个非法迭代器或者尾后迭代器都是未被定义的行为。

#include <iostream>
#include <vector>
#include <string>

using namespace std;

int main(){

    string s("some string");
    if (s.begin() != s.end()) { // 确保s非空
        auto it = s.begin();// it表示s的第一个字符
        *it = toupper(*it);//将当前字符改成大写形式
    }
    cout << s << endl;

    return 0;
}

2.将迭代器从一个元素移动到另外一个元素

递增(++)“迭代器的递增则是将迭代器“向前移动一个位置”。

#include <iostream>
#include <vector>
#include <string>

using namespace std;

int main(){
    string s("hello world!");

    //依次处理s的字符直至我们处理完全部字符或者遇到空白
    for (auto it = s.begin(); it != s.end() && !isspace(*it); ++it)
      *it = toupper(*it); //将当前字符改成大写形式
    cout << s << endl;

    return 0;
}

3.迭代器类型

vector<int>::iterator it;// it能读写vector<int>的元素
string::iterator it2;// it2能读写string对象中的字符
vector<int>::const_iterator it3; // it3 只能读元素,不能写元素
string::const_iterator it4;// it4只能读字符,不能写字符

const_ iterator 和常量指针差不多,能读取但不能修改它所指的元素值。iterator的对象可读可写。
如果vector对象或string对象是一个常量,只能使用const_ iterator; 
如果vector对象或string对象不是常量,那么既能使用iterator也能使用const_iterator。

4.begin和 end运算符

vector<int> v;
const vector<int> CV;
auto it1 = v.begin();// itl的类型是vector<int>::iterator
auto it2 = cv.begin();// it2的类型是vector<int>::const_iterator

c++11

不论vector对象(或string对象)本身是否是常量,返回值都是const_ iterator.

auto it3= v.cbegin() ; // it3的类型是vector<int>::const_iterator

5.结合解引用和成员访问操作

(*it).empty() // 解引用it,然后调用结果对象的empty成员
*it.empty()//错误:试图访问it的名为empty的成员,但it是个迭代器,
            //没有empty成员

箭头运算符(->)

//依次输出text的每一行直至遇到第一个空白行为止
for (auto it = text.cbegin() ;
it != text.cend() && !it->empty(); ++it)
cout << *it << endl;


6.某些对vector对象的操作会使迭代器失效
已知的一个限制是不能在范围for循环中向vector 对象添加元素。

另外一个限制是任何一种可能改变vector对象容量的操作,比如push_ back,都会使该vector对象的迭代器失效。

练习3.22:修改之前那个输出text第一段的程序,首先把text的第一段全都改成大写形式,然后再输出它。

#include <iostream>
#include <vector>
#include <string>
using namespace std;
int main()
{
    vector<string> text;
    string s;  //利用getline读取一句话,直接回车产生一个空串,表示段落结束
    while (getline(cin, s))
    {
        if(s.size() == 0)
            break;
        text.push_back(s); //逐个添加到text中
    }

    cout << text.size() << endl;
    //利用迭代器遍历全部字符串,遇空串停止循环
    for (auto it = text.begin(); it != text.end() && !it -> empty(); it++)
    {
        //利用迭代器遍历当前字符串!
        for (auto it2 = it -> begin(); it2 != it -> end() ; it2++)
            *it2 = toupper(*it2); // 利用toupper改写成大写形式
        cout << *it << endl;//输出当前字符
    }
    return 0;
}

练习3.23:编写一段程序,创建一个含有10个整数的vector对象,然后使用迭代器将所有元素的值都变成原来的两倍。输出vector对象的内容,检验程序是否正确。

#include <iostream>
#include <vector>
#include <ctime>
#include <cstdlib>

using namespace std;
int main()
{
    vector<int> vInt;
    srand ( (unsigned)time(NULL) ) ;//生成随机数种子
    for(int i = 0; i < 10; i++)
    //循环10次
    {
        //每次循环生成一个1000以内的随机数并添加到vInt中
        vInt.push_back( rand()%1000 ) ;
    }
        cout << "随机生成的10个数字是:"<< endl;
        //利用常量迭代器读取原始数据
        for (auto it = vInt.cbegin(); it !=vInt.cend() ; it++)
        {
            cout << *it <<" "; //输出当前数字
        }
        cout<<endl;
        cout << "翻倍后的10个数字是:"<< endl;
        //利用非常量迭代器修改vInt内容并输出
        for (auto it = vInt.begin() ; it != vInt.end(); it++)
        {
            *it *=2;
            cout<<*it<<" "; //输出当前数字
        }

        cout<<endl;
        return 0;

}

3.4.2迭代器运算

迭代器的算术运算

//计算得到最接近vi中间元素的一个迭代器
auto mid = vi.begin() + vi.size() / 2;

if(it <mid) //处理vi前半部分的元素

只要两个迭代器指向的是同一个容器中的元素或者尾元素的下一位置,就能将其相减,所得结果是两个迭代器的距离。

所谓距离指的是右侧的迭代器向前移动多少位置就能追上左侧的迭代器,其类型是名为difference_ type 的带符号整型数。

string 和vector都定义了difference type,因为这个距离可正可负,所以difference_ type 是带符号类型的。

使用迭代器运算 

二分搜索

    //text 必须是有序的
    // beg 和 end  表示我们搜索的范围
    auto beg = text.begin() , end = text.end();
    auto mid = text.begin() + (end - beg) / 2 ;//初始状态下的中间点
    // 当还有元素尚未检查并且我们还没有找到sought时执行
    
    while(mid != end && *mid != sought){
        if(sought < *mid)  //目标在前半部分
            end = mid;     //如果是就忽略掉后半部分
        else 
            beg = mid + 1; //在mid之后寻找
        mid = beg + (end - beg) /2;  //新的中间点
    }


练习3.24:

#include <iostream>
#include <vector>
#include <ctime>
#include <cstdlib>

using namespace std;
int main()
{
//求相邻元素的程序
   vector <int> vInt;
   int iVal = 0;
   cout << "请输入一串数字:" << endl;
   while(cin >> iVal)
   {
       vInt.push_back(iVal);
   }
   if (vInt. cbegin() == vInt. cend())
   {
       cout << "没有任何的元素" << endl;
       return -1;
   }

   cout << "相邻两项的和依次是:" <<endl;
   //利用auto推断it的类型
  for(auto it = vInt.cbegin(); it != vInt.cend() - 1; it++)
  {
   //求相邻两项的和
    cout << (*it + *(++it) )<< " ";
    // 每行输出 5个数字
    if ((it - vInt.cbegin() +1) %10  == 0)
        cout << endl ;
    }
    // 如果元素个数是奇数, 单独处理最后一个元素
    if (vInt.size()%2 != 0)
        cout <<*(vInt.cend() -1);

   return 0;
}
#include <iostream>
#include <vector>
#include <ctime>
#include <cstdlib>

using namespace std;
int main()
{
//求相邻元素的程序
   vector <int> vInt;
   int iVal = 0;
   cout << "请输入一串数字:" << endl;
   while(cin >> iVal)
   {
       vInt.push_back(iVal);
   }
   if (vInt. cbegin() == vInt. cend())
   {
       cout << "没有任何的元素" << endl;
       return -1;
   }

    cout << "首尾两项的和依次是:" << endl;
    auto beg = vInt.begin() ;
    auto end = vInt.end() ;
    //利用auto推断it的类型
    for (auto it = beg; it!=beg+(end-beg)/2;it++)
    {
    //求首尾两项的和
    cout<<(*it+*(beg+(end-it)-1))<< " ";
    //每行输出5个数字
    if((it- beg+1)%5==0)
    cout << endl ;
   }
   //如果元素个数是奇数,单独处理最后一个元素
    if (vInt.size()%2!= 0)
        cout <<*(beg +(end - beg)/2);

   return 0;
}


练习3.26:在二分搜索程序中,为什么用的是mid= beg + (end 一beg) / 2,而非mid = (beg + end) /2;?

C++并没有定义两个迭代器的加法运算,实际上直接把两个迭代器加起来是没有意义的。
与之相反,C++定义了迭代器的减法运算,两个迭代器相减的结果是它们之间的距离,也就是说,将运算符右侧的迭代器向前移动多少个元素后可以得到左侧的迭代器,参与运算的两个迭代器必须指向同一个容器中的元素或尾后元素。
另外,C++还定义了迭代器与整数的加减法运算,用以控制迭代器在容器中左右移动。
在本题中,因为迭代器的加法不存在,所以mid= (beg + end) /2;不合法。mid = beg+ (end - beg) / 2;的含义是,先计算end-beg的值得到容器中的元素个数,然后控制迭代器从开始处向右移动二分之一容器的长度,从而定位到容器正中间的元素。
 

3.5数组

3.5.1定义和初始化内置数组

unsigned cnt = 42; //不是常量表达式
constexpr unsigned sz = 42; //常量表达式
int arr[10] ;//含有10个整数的数组
int *parr[sz];//含有42个整型指针的数组
string bad[cnt];//错误: cnt不是常量表达式
string strs[get_size()]; //当get_ size 是constexpr时正确;否则错误


显式初始化数组元素

const unsigned sz = 3;
int ial[sz] = {0, 1, 2};//含有3个元素的数组,元素值分别是0, 1, 2
int a2[] = {0,1, 2};//维度是3的数组
int a3[5] = {0, 1, 2};//等价于a3[] = {0, 1, 2, 0, 0}
string a4[3] = {"hi", "bye"};//等价于a4[] = {"hi", "bye", ”"}
int a5[2] = {0,1,2};//错误:初始值过多

字符数组的特殊性

char al[] = {'C', '+', '+'};//列表初始化,没有空字符
char a2[] = {'C', '+', '+', '\O'};//列表初始化,含有显式的空字符
char a3[] = "C++"; //自动添加表示字符串结束的空字符
const char a4[6] = "Daniel";//错误:没有空间可存放空字符!

不允许拷贝和赋值

int a[] = {0,1,2};//含有3个整数的数组
int a2[] = a;//错误:不允许使用一个数组初始化另一个数组
a2= a;//错误:不能把一个数组直接赋值给另一个数组

理解复杂的数组声明

int *ptrs[10];//ptrs是含有10个整型指针的数组
int &refs[10] = /* ?*/;//错误:不存在引用的数组
int (*Parray) [10] = &arr;// Parray 指向一个含有10个整数的数组
int (&arrRef) [10] = arr;// arrRef 引用一个含有10个整数的数组
int *(&arry) [10] = ptrs;// arry是数组的引用,该数组含有10个指针

练习3.29:相比于vector来说,数组有哪些缺点,请列举些
数组与vector的相似之处是都能存放类型相同的对象,且这些对象本身没有名字,需要通过其所在位置访问。
数组与vector的最大不同是,数组的大小固定不变,不能随意向数组中增加额外的元素,虽然在某些情境下运行时性能较好,但是与vector相比损失了灵活性。


具体来说,数组的维度在定义时已经确定,如果我们想更改数组的长度,只能创建一个更大的新数组,然后把原数组的所有元素复制到新数组中去。我们也无法像vector那样使用size函数直接获取数组的维度。如果是字符数组,可以调用strlen函数得到字符串的长度;如果是其他数组,只能使用sizeof (array) /sizeof (array[0])的方式计算数组的维度
 

3.5.2访问数组元素

在使用数组下标的时候,通常将其定义为size_t类型cstddef头文件

//以10分为一个分数段统计成绩的数量: 0~9, 10~ 19.... 90~ 99, 100
unsigned scores[11] = {}; // 11个分数段,全部初始化为0
unsigned grade; 
while (cin >> grade) {
    if (grade <= 100)
         ++scores [grade/10]; // 将当前分数段的计数值加1
}

检查下标的值

3.5.3指针和数组

string nums[] = {"one", "two", "three"}; //数组的元素是string对象
string *p = &nums[0] ;// p指向nums的第一个元素

string *p2 = nums ; //等价于p2 =  &nums [0]

int ia[] = {0,1,2,3,4,5,6, 7,8,9}; // ia是一个含有10个整数的数组
auto ia2 (ia) ;// ia2是一个整型指针,指向ia的第一个元素
ia2 = 42;//错误: ia2是一个指针,不能用int值给指针赋值
auto ia2(&ia[0]) ; //显然ia2的类型是int*

decltype (ia)返回的类型是由10个整数构成的数组:

decltype(ia) ia3 = {0,1,2,3,4,5,6,7,8,9};
ia3 = p; //错误:不能用整型指针给数组赋值
ia3[4] = i; //正确:把i的值赋给ia3的一个元素

指针也是迭代器

int arr[] = {0,1,2,3,4,5,6, 7,8,9};
int *p = arr;// p指向arr的第一个元素
++p;// p指向arr[1]

标准库函数begin和end  c++11
//不是成员函数

int ia[] = {0,1,2,3,4,5,6, 7,8,9}; // ia是一个含有10个整数的数组
int *beg = begin(ia) ;//指向ia首元素的指针
int *last = end(ia) ;//指向arr尾元素的下一位置的指针
// pbeg指向arr的首元素,pend指向arr尾元素的下一位置
int *pbeg = begin(arr), *pend = end(arr) ;
while (pbeg != pend && *pbeg >= 0) //寻找第一个负值元素,如果已经检查完全部元素则结束循环
         ++pbeg;

指针运算

constexpr size_t sz = 5;
int arr[sz] = {1,2,3,4,5};
int *ip = arr;//等价于int *ip = &arr[0]
int *ip2=ip+4;// ip2指向arr的尾元素arr[4]

//正确: arr转换成指向它首元素的指针; p指向arr尾元素的下一位置
int*p=arr+sz;//使用警告:不要解引用!
int *p2 = arr + 10;//错误:arr只有5个元素,p2的值未定义
auto n = end(arr)一begin(arr); // n的值是5,也就是arr中元素的数量

两个指针相减的结果的类型是一种名为 ptrdiff_t的标准库类型,和size_t一样,

ptrdiff_t也是一种定义在cstddef头文件中的机器相关的类型。因为差值可能为负值,

所以ptrdiff_t是一种带符号类型。
int *b = arr, *e = arr + sz;
while(b < e){
  //使用*b 
  ++b;
}

解引用和指针运算的交互

int ia[] = {0,2,4,6,8}; // 含有5个整数的数组
int last = *(ia+4);//正确:把last初始化成8,也就是ia[4]的值

last = *ia + 4; //正确:等价于ia[0] +4

下标和指针

int i =ia[2];// ia转换成指向数组首元素的指针
            // ia[2]得到(ia + 2)所指的元素
int *p = ia;// p指向ia的首元素
i = *(p+2); //等价于i= ia[2]

int *p = &ia[2] ; // p指向索引为2的元素
int j = p[1]; // p[1]等价于*(p + 1),就是ia[3]表示的那个元素
int k = p[-2]; // p[-2]是ia[0]表示的那个元素

练习3.36: 编写一段程序,比较两个数组是否相等。再写一段程序,比较两个vector对象是否相等。
 

3.5.4C风格字符串


char ca[] = {'C', '+', '+'}; // 不以空字符结束
cout << strlen(ca) << endl; //严重错误: ca没有以空字符结束

比较字符串
 

区别:
string s1 = "A string example";
string s2 ="A different string";
if(s1<s2)//false:s2小于s1


const char cal[] = "A string example";
const char ca2[] = "A different string";
if(cal<ca2)//未定义的:试图比较两个无关地址
if(strcmp (cal, ca2) < 0); //和两个string对象的比较s1 s2效果一样

目标字符串的大小由调用者指定

//将largeStr初始化成s1、一个空格和s2的连接
string largeStr = s1 + " " + s2;

如果我们计算错了largeStr的大小将引发严重错误
strcpy (largeStr, cal) ; //把cal拷贝给largeStr
strcat (largeStr," ") ; //在largeStr的末尾加上一个空格
strcat (largeStr, ca2) ; //把ca2连接到largeStr后面

一个潜在的问题是,我们在估算largeStr所需的空间时不容易估准,
而且largeStr所存的内容一旦改变, 就必须重新检查其空间是否足够。
不幸的是,这样的代码到处都是,程序员根本没法照顾周全。这类代码充满了风险而且经常导致严重的安全泄漏。

练习3.39:编写段程序, 比较两个string对象。再编写段程序, 比较两个C风格字符串的内容。

#include <iostream>
#include <string>
using namespace std;
int main()
{
	string str1, str2;
	cout << "请输入两个字符串:"<< endl;
		cin >> str1 >> str2;
	if (str1 > str2)
		cout << "第一个字符串大于第二个字符串" << endl;
	else if (str1 < str2)
		cout << "第一个字符串小于第二个字符串" << endl;
	else
		cout << "两个字符串相等" << endl;
	return 0;

}
#include <iostream>
#include <vector>
#include <ctime>
#include <cstdlib>
#include <cstring>

using namespace std;
int main()
{
    char str1[80], str2[80] ;
    cout << "请输入两个字符串:"<< endl;
    cin >> str1 >> str2;
    // 利用cstring 头文件中定义的strcmp函数比较大小
    auto result = strcmp (str1, str2) ;
    switch (result)
    {
        case 1 :
            cout << "第一个字符串大于第二个字符串" << endl ;
            break;
        case -1 :
            cout << "第一个字符串小于第二个字符串"<< endl;
            break;
        case 0:
            cout << "两个字符串相等" <<endl;
            break;
        default :
            cout << "未定义的结果" << endl;
            break;
    }


   return 0;
}


练习3.40:编写段程序, 定义两个字符数组并用字符串字面值初始化它们;接着再定义一个字符数组存放前两个数组连接后的结果。使用strcpy和strcat把前两个数组的内容拷贝到第三个数组中。

#include <iostream>
#include <cstring>
#include <string.h>
using namespace std;
int main()
{
	char str1[] = "Welcome to ";
	char str2[] = "C++ family!";
	//利用strlen函数计算两个字符串的长度,并求得结果字符串的长度
	char result[ strlen(str1) + strlen(str2) - 1];
	strcpy(result, str1); // 把第一个字符串拷贝到结果字符串中
	strcat(result, str2); // 把第二个字符串拼接到结果字符串中
	cout << "第一个字符串是:"<< str1 << endl;
		cout << "第二个字符串是:"<< str2 << endl;
		cout << "拼接后的字符串是:" << result << endl;
	return 0;
}

3.5.5与旧代码的接口

混用string对象和C风格字符串

string s("Hello World") ; // s的内容是Hello World

上述性质反过来就不成立了:如果程序的某处需要一个C风格字符串,无法直接用string对象来代替它

char *str = s; //错误:不能用string对象初始化char*
const char*str = s.c_str();//正确

我们无法保证c_str函数返回的数组一直有效,事实上,如果后续的操作改变了s的值就可能让之前返回的数组失去效用。

使用数组初始化vector对象

int int_arr[] = {0, 1,2, 3,4, 5};
// ivec有6个元素,分别是int_arr中对应元素的副本
vector<int> ivec (begin(int_arr), end(int_arr)) ;
//拷贝三个元素: int_arr[1]、 int_arr[2]、int_arr[3]
vector<int> subVec(int_arr + 1, int_arr + 4) ;

3.6多维数组

int ia[3][4]; //大小为3的数组,每个元素是含有4个整数的数组
//大小为10的数组,它的每个元素都是大小为20的数组,
//这些数组的元素是含有30个整数的数组
int arr[10][20][30] = {0}; //将所有元素初始化为0

多维数组的初始化

int ia[3][4] ={ //三个元素,每个元素都是大小为4的数组
{0, 1, 2, 3},//第1行的初始值
{4, 5,6,7},//第2行的初始值
{8,9,10,11}//第3行的初始值
};


//没有标识每行的花括号,与之前的初始化语句是等价的
int ia[3][4] = {0,1,2,3,4,5, 6, 7,8,9,10,11} ;


//显式地初始化第1行,其他元素执行值初始化
int ix[3] [4]{0, 3, 6, 9} ;

多维数组的下标引用

//用arr的首元素为ia最后一行的最后一个元素赋值
ia[2][3] = arr[0][0][0] ;
int (&row)[4] = ia[1]; //把row绑定到ia的第二个4元素数组上
constexpr size_t rowCnt = 3,colCnt = 4;
int ia[rowCnt] [colCnt]; // 12个未初始化的元素
//对于每一行
for (size_t i=0;i != rowCnt; ++i) {
  //对于行内的每一列
  for (size_t j = 0;j != colCnt; ++j) {
      //将元素的位置索引作为它的值
     ia[i][j] =i*colCnt + j;
    }
}

使用范围for语句处理多维数组  c++11

size_t cnt=0;
for (auto &row : ia)//对于外层数组的每一个元素
    for (auto &col : row) {//对于内层数组的每一个元素
        col = cnt;//将下一个值赋给该元素
         ++cnt;//将cnt加1
     }


for(const auto &row : ia) // 对于外层数组的每一个元素
    for (auto col : row)//对于内层数组的每一个元素
       cout << col << endl ;

指针和多维数组

int ia[3][4];//大小为3的数组,每个元素是含有4个整数的数组
int (*p) [4] = ia;// p指向含有4个整数的数组
p = &ia[2];// p指向 ia的尾元素

//输出ia中每个元素的值,每个内层数组各占一行
//P指向含有4个整数的数组
for (auto p = ia;p != ia+3;++p) {//q指向4个整数数组的首元素,也就是说,q指向一个整数
  for (auto q= *P;q!=*p+4; ++q)
     cout << *q << ' ';
  cout << endl;
}
// p指向ia的第一个数组
for (auto P = begin(ia); P != end(ia); ++p) { // q指向内层数组的首元素
for(auto q = begin(*p); q != end(*p); ++q)
  cout<<*q<< ' ' ;//输出q所指的整数值
cout << endl;
}

类型别名简化多维数组的指针

using int array = int[4]; 
typedef int int_array[4]; 
// 输出ia中每个元素的值, 每个内层数组各占一行
for (int_array *p = ia; p != ia+3; ++p) {
    for (int *q= *p;q != *p + 4; ++q)
     cout << *q <<' ';
cout << endl;
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值