C++ primer 第3章 字符串、向量和数组

  • stringvector是两类最重要的标准库类型,string表示可变长的字符序列,vector存放某种给定类型对象的可变长序列。
  • 迭代器是string和vector的配套类型,常被用于访问string的字符或vector的元素。
  • 内置数组是更基础的类型,string和vector都是对它的抽象。
  • 内置类型是由C++语言直接定义的,体现了大多数计算机硬件本身具备的能力。标准库定义了另一组具有更高级性质的类型。

命名空间using声明

  • 域操作符:::编译器从操作符左侧名字所示的作用域中寻找右侧的名字。
  • 使用using声明就不需要每次指定命名空间就能使用一个名字,形如using namespace::name;,这样以后可直接使用name。
  • 每个using引入一个名字,因此每个名字都必须有自己的using
  • 由于头文件的代码会被拷贝到引用它的文件中,故头文件的代码不应使用using

标准库类型string

  • string是标准库类型表示可变长字符序列,定义在头文件string和命名空间std中。

定义和初始化string对象

初始化方式:

  • 直接初始化:不用等号,而用括号初始化变量。调用构造函数
  • 拷贝初始化:用等号初始化变量。调用重载的赋值运算符
  • 可以先用直接初始化构造临时量,然后拷贝初始化:string s=string(10,’c’);
string s1;          //默认初始化,为空字符串
string s2(s1);      //直接初始化,s2是s1的副本
string s2=s1;       //拷贝初始化,s2是s1的副本,等价于上一行
string s3("hiya");  //直接初始化,初始化为字面值常量
string s3="hiya";   //拷贝初始化,初始化为字面值常量,等价于上一行
string s4(10,'c');  //直接初始化,初始化为10个字符'c'
复制代码

string对象的操作

CPPprimer_ch3-tab_3_2.png

  • 从iostream中读取string:用>>读取时,string对象忽略开头的空白(包括空格、换行、制表符等),从第一个真正的字符读起,直到下一处空白为止。
  • string对象的<<>>也是返回运算符左侧的iostream对象。
  • 如要在iostream中读取时保留空白,需用getline函数,该函数从iostream中读取内容,直到遇到换行(换行也被读入),然后把读到的内容存入string对象(不存换行符)。
getline(cin, inputLine);
复制代码
  • getline也返回它的iostream,故也可作为while的条件。
  • empty函数根据string对象是否为空返回一个bool
  • size函数返回string对象的长度(字符数)
  • size函数返回的类型是string::size_type(可用decltype得到),它是无符号类型,且能放下任何string对象的大小。(attention:不能将size与int等有符号类型混合计算)
  • ==和!=验证两字符串内容是否完全相同,<、<=、>、>=比较两字符串的字典顺序(大小写敏感)
  • 用=进行string对象的拷贝和赋值
  • 用+拼接两string对象,也可拼接一个string对象和一个字符串字面值(类型转换),但不能拼接两个字符串字面值。因为string和字符串字面值是不同的类型

string对象中字符的处理

CPPprimer_ch3-tab_3_3.png

  • 使用范围for时,如果要改变序列中元素的值,必须把循环变量定义为引用类型
  • 例子:范围for中改变元素
string s("hello,world!");
for(auto &c:s)  //要改变序列中的元素,必须声明为引用类型
    c=toupper(c);
cout<<s<<endl;
复制代码
  • 访问string对象中的单个字符有两种方式:下标和迭代器
  • 下标运算符[]接受string::size_type(unsigned)类型的值,返回该位置上字符的引用(因此可修改字符)。若给索引提供signed值,会转为string::size_type表示的unsigned
  • 例子:用下标迭代
for(decltype(s.size()) index=0;             //用decltype推出string::size_type类型
    index!=s.size() && !isspace(s[index]);  //逻辑与,只有左侧为真时才检查右侧。故能保证下标合理时才访问
    ++index)
{s[index]=toupper(s[index]);}
复制代码

标准库类型vector

  • vector表示对象的集合,所有对象的类型都相同。
  • 由于vector容纳着其他对象,故称为容器
  • vector是一个类模板。C++中有类模板和函数模板。模板本身不是类或函数,可将模板看作为编译器生成类或函数的一份说明。编译器根据模板创建类或函数的过程称为实例化
  • 由模板生成类或函数时,必须指定类型
  • C++11之前,元素为vector的vector对象,模板变量里必须有空格
  • 例子:根据模板vector声明对象
vector<int> ivec;               //元素是int型对象
vector<Sales_item> Sales_vec;   //元素是Sales_item类型的对象
vector<vector<string>> file;    //元素是vector<string>类型的对象
vector<vector<string> > file;   //C++11之前的写法,元素是vector<string>类型的对象
复制代码

定义和初始化vector对象

CPPprimer_ch3-tab_3_4.png

  • 圆括号是构造,花括号是列表初始化。但当花括号无法初始化时,会尝试将花括号代替为圆括号。
vector<string> v5{"hi"};    //有1个元素,是字符串"hi"
vector<string> v6("hi");    //错,不能用字符串字面值构造vector
vector<string> v7{10};      //不能列表初始化,转为构造。有10个元素,都是空字符串
vector<string> v8{10,"hi"}; //不能列表初始化,转为构造。有10个元素,都是"hi"
复制代码

vector对象操作

CPPprimer_ch3-tab_3_5.png

  • 建立空对象再向其中push_back非常高效,比创建时确定大小之后再修改的方式更快。
  • vector的size方法返回该vector的元素数量,类型是vector<type>::size_type类型。使用size_type时,需首先指定是哪种类型的size_type。
  • 对vector索引时,下标类型是相应的size_type类型
  • 不能用下标添加元素,用下标访问不存在的元素会引发错误(编译不报错),例如缓冲区溢出等
  • 确保访问元素有效的方法之一是使用范围for

迭代器

  • string对象不属于容器,但操作上和容器很接近
  • 迭代器提供了对元素对象的间接访问,类似于指针。其对象是容器中的元素,或string中的字符
  • 有效的迭代器或者指向某个元素,或者指向尾元素的下一位置,其他都是无效。

迭代器的使用

CPPprimer_ch3-tab_3_6.png

  • 有迭代器的类型都配套有返回迭代器的成员。其中begin方法返回指向首元素的迭代器,end方法返回指向尾元素的下一位置(尾后)的迭代器。空容器的begin()和end()返回同一迭代器。
  • 从函数中返回迭代器时,类型用auto,不用管迭代器的类型。
  • 可用解引用符*访问迭代器指向的元素,类似指针。试图解引用无效尾后迭代器都是未定义。
  • 对迭代器使用++可移动到下一个元素,它们是逻辑上先后的关系,空间上不一定相邻。
  • ++、--、==、!=来进行遍历操作,因为这些操作在所有容器的迭代器上都有效。而索引和<、>等操作符在大多数容器的迭代器中未定义。
  • 迭代器的类型是相应容器类型的iteratorconst_iterator,前者可读可写,后者只能读。类型书写如vector<int>::iterator。每个容器都定义了一个名为iterator的类型。
  • 如果容器内对象为常量,则begin和end返回const_iterator迭代器,否则返回iterator迭代器
  • cbegin方法和cend方法对任何容器都返回const_iterator迭代器
  • 通过迭代器调用元素对象的成员时,使用诸如(*it).function(),括号必不可少,不然it先取成员再解引用。
  • 通过迭代器调用元素对象的成员时,亦可使用简化的->操作符,它将解引用和取成员两个操作结合,it->function()等价于(*it).function()
  • 任何可能改变容器容量的操作,如push_back,都会使容器的迭代器失效。

迭代器使用示例:

vector<int> v;  //v是存放int类型变量的可变长数组,开始时没有元素
    for (int n = 0; n<5; ++n)
        v.push_back(n);  //push_back成员函数在vector容器尾部添加一个元素
    vector<int>::iterator i;  //定义正向迭代器
    for (i = v.begin(); i != v.end(); ++i) {  //用迭代器遍历容器
        cout << *i << " ";  //*i 就是迭代器i指向的元素
        *i *= 2;  //每个元素变为原来的2倍
    }
    cout << endl;
    //用反向迭代器遍历容器
    for (vector<int>::reverse_iterator j = v.rbegin(); j != v.rend(); ++j)
        cout << *j << " ";
复制代码

迭代器运算

CPPprimer_ch3-tab_3_7.png

  • stringvector是顺序存储,故它们的迭代器支持更多的操作,如迭代器运算,这些操作可使迭代器每次移动跨越多个元素,也可对迭代器比较大小。
  • 可使迭代器和整数值相加减,返回值是向前或向后移动若干位置的迭代器。
  • 可使用关系运算符>、>=、<、<=对迭代器比较大小
  • 将迭代器相减,结果是两迭代器的距离,指的是右侧迭代器向前移动多少位置能和左侧迭代器重合,距离可正可负。其类型是容器类型对应的difference_type,是signed的整型数。

数组

  • 数组也是存放类型相同的对象的容器,这些对象本身没有名字,通过在数组中的位置访问。
  • 数组大小确定不变,不能增加元素。性能比vector等容器更好。

定义和初始化数组

  • 数组是一种复合类型,声明形如int a[d];,其中a是数组名,d是数组大小。数组大小也是类型的一部分,在编译时应该已知,必须是常量表达式。
  • 默认情况下,数组元素被默认初始化
  • 定义数组时应手动写出类型,不可用auto,但可用decltype
  • 可对数组做列表初始化,此时不用手动指定大小,列表长度就是数组大小。如果手动指定大小,大小只能大于或等于初值列表大小,用初值列表初始化靠前元素,其后的默认初始化。
  • 字符数组比较特殊,可用字符串字面值初始化,且不需手动指定大小,此时会在最后加上空字符。
char a1[]={'C','+','+'};        //列表初始化,无空字符
char a1[]={'C','+','+','\0'};   //列表初始化,有空字符
char a3[]="C++";                //字符串字面值初始化,有空字符
const char a4[6]="Daniel";      //错,没有空间放空字符了
复制代码
  • 不能将数组拷贝给其他数组作为初值,也不能用数组为数组赋值。因为数组名是首元素地址,不能代表整个数组。

数组相关的复合定义:

int arr[10];
int *ptrs[10];          //指针的数组。是长度为10的数组,元素是指针,指向int型
int &refs[10]=/*?*/     //错,引用不是对象,不存在引用的数组
int (*Parray)[10]=&arr; //数组的指针。是指针,指向长度为10的数组,元素是int型
int (&arrRef)[10]=arr;  //数组的引用。是引用,引用长度为10的数组,元素是int型
int *(&arry)[10]=ptrs;  //指针数组的引用。是引用,引用长度为10的数组,元素是指针,指向int型
复制代码

访问数组元素

  • 数组可用范围for或者下标访问
  • 使用数组下标时,将其定义为size_t类型,这是机器相关的unsigned类型,足够表示内存中任意对象的大小,在cstddef头文件中定义。
  • 数组的下标类型由C++语言定义,vector等容器的下标类型由标准库定义。

指针和数组的联系

  • 使用数组名的时候,编译器一般会将其转换为指向首元素的指针
  • 对数组元素用取地址符&能手动得到其指针
  • 当数组名作为auto变量初值时,推出的类型是指针而非数组,效果相当于对首元素取地址再给初值。但用decltype时,得到的是数组类型
  • 指针也是迭代器,string和vector的迭代器支持的运算都可被数组指针支持。
  • beginend得到数组的首元素指针和尾后指针,这两个函数定义于iterator头文件中。由于数组不是类,故它们也不是成员方法
  • 两指针相减的结果类型是ptrdiff_t,定义于cstddef头文件,是一种signed类型。
  • 指针运算与下标:表达式*(ia+4)计算指针ia前进4个元素后的新地址并解引用,等价于ia[4]
  • 指针可使用下标。实际上,对数组使用下标,其实是对首元素指针使用下标。此时编译器将数组名转为首元素指针,再对指针使用下标。
  • 标准库内的迭代器下标必须为unsigned,但指针的下标是C++内置,可以处理负值,即指针可接受signed下标

多维数组

  • C++语言并无多维数组,多维数组是数组的数组
  • 对于二维数组而言,第一个维度为行,第二个维度为列
  • 可对数组用列表初始化,花括号可嵌套也可不嵌套。因为数组连续存储(自动reshape为定义的维度尺寸)
  • 例子:多维数组的定义
int ia[3][4]={0,1,2,3,4,5,6,7}; //ia是3元素数组,每个元素是4元素数组,ia三行四列。将前两行初始化,第三行默认初始化
int ib[3][4]={{0},{4},{8}};     //ib是3元素数组,每个元素是4元素数组,ia三行四列。将第一列初始化,其余元素默认初始化
int (&row)[4]=ia[1]             //row是引用,引用一个4元素数组,其元素类型为int,被初始化为ia的第二行
复制代码
  • 如果用范围for循环搭配auto来遍历、修改多维数组,除了最内层循环,其他的循环的auto元素都应该为引用类型。
int a[3][4] {1,3,5,6,6,3,5,6,8,9,4,6};
for(auto &row : a){             //这里需要声明为引用类型,否则下一层循环无法对指针进行遍历
    cout << row << endl;        //打印行的头指针,指向每一行的第一个元素
    for(auto &num : row){       //这是最后一层循环,auto推出的类型为存放的数据类型,可以不加引用声明
        cout << "num : " << num << endl;
        // 这里可以对数组元素进行修改(引用类型)
    }
}

// 修改元素
size_t cnt=0;
for(auto &row:ia){      //遍历行,外层循环声明为引用的原因是避免数组被auto为指针
    for(auto &col:row){ //遍历列,内层循环声明为引用的原因是要修改元素
        col=cnt;        //修改元素
        ++cnt;
    }
}
// 打印元素
for(const auto &row:ia) //避免数组被auto为指针
    for(auto col:row)   //最内层循环是元素不是数组,不存在被auto为指针的问题
        cout<<col<<endl;
// 错误例子
for(auto row:ia)        //ia被auto为指针类型
    for(auto col:row)   //错,指针不可遍历
复制代码
© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享