string
和vector
是两类最重要的标准库类型,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对象的操作
从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对象中字符的处理
- 使用范围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对象
- 圆括号是
构造
,花括号是列表初始化
。但当花括号无法初始化时,会尝试将花括号代替为圆括号。
vector<string> v5{"hi"}; //有1个元素,是字符串"hi"
vector<string> v6("hi"); //错,不能用字符串字面值构造vector
vector<string> v7{10}; //不能列表初始化,转为构造。有10个元素,都是空字符串
vector<string> v8{10,"hi"}; //不能列表初始化,转为构造。有10个元素,都是"hi"
复制代码
vector对象操作
- 建立空对象再向其中push_back非常高效,比创建时确定大小之后再修改的方式更快。
- vector的size方法返回该vector的元素数量,类型是
vector<type>::size_type
类型。使用size_type时,需首先指定是哪种类型的size_type。 - 对vector索引时,下标类型是相应的
size_type类型
- 不能用下标添加元素,用下标访问不存在的元素会引发错误(
编译不报错
),例如缓冲区溢出等 - 确保访问元素有效的方法之一是使用范围
for
迭代器
- string对象不属于容器,但操作上和容器很接近
- 迭代器提供了对元素对象的
间接访问
,类似于指针。其对象是容器中的元素
,或string中的字符
- 有效的迭代器或者指向
某个元素
,或者指向尾元素的下一位置
,其他都是无效。
迭代器的使用
- 有迭代器的类型都配套有返回迭代器的成员。其中
begin
方法返回指向首元素的迭代器,end方法返回指向尾元素的下一位置
(尾后)的迭代器。空容器的begin()和end()
返回同一迭代器。 - 从函数中返回迭代器时,类型用
auto
,不用管迭代器的类型。 - 可用解引用符
*
访问迭代器指向的元素,类似指针。试图解引用无效
或尾后
迭代器都是未定义。 - 对迭代器使用
++
可移动到下一个元素,它们是逻辑上先后的关系,空间上不一定相邻。 - 用
++、--、==、!=
来进行遍历操作,因为这些操作在所有容器的迭代器上都有效。而索引和<、>
等操作符在大多数容器的迭代器中未定义。 - 迭代器的类型是相应容器类型的
iterator
和const_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 << " ";
复制代码
迭代器运算
string
和vector
是顺序存储,故它们的迭代器支持更多的操作,如迭代器运算,这些操作可使迭代器每次移动跨越多个元素,也可对迭代器比较大小。- 可使迭代器和整数值相加减,返回值是向前或向后移动若干位置的迭代器。
- 可使用关系运算符
>、>=、<、<=
对迭代器比较大小 - 将迭代器相减,结果是两迭代器的
距离
,指的是右侧迭代器向前移动多少位置能和左侧迭代器重合,距离可正可负。其类型是容器类型对应的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的迭代器支持的运算都可被数组指针支持。
- 用
begin
和end
得到数组的首元素指针和尾后指针,这两个函数定义于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