001-C++字符串、向量

标准库类型:string和vector

string表示可变长的字符序列。

vector存放某种给定类型对象的可变长序列。

String

作为标准库的一部分,string定义在命名空间std中。

初始化

C++语言有几种不同的初始化方式,如果使用等号(=)初始化一个变量,实际上执行的是拷贝初始化,编译器把等号右侧的初始值拷贝到新创建的对象中去。不使用等号,则执行的是直接初始化。

当初始值只有一个时,使用上述两种方式都行。如果初始值有多个,一般使用直接初始化方式。

String对象上的操作

getline读取一整行

getline函数的参数是一个输入流和一个string对象,函数从给定的输入流中读入内容,直到遇到换行符为止(注意换行符也被读进来了),然后把所读的内容存入到那个string对象中去(注意该对象中不存在换行符,换行符实际上被丢弃掉了)。一开始输入换行符,结果是个空string。

string::size_type类型

string类及其他大多数标准库类型都定义了几种配套的类型。这些配套类型体现了标准库类型与机器无关的特性。类型size_type就是其中的一种。

size_type是一个无符号类型的值,且能够存放下任何string对象的大小。

对象比较

比较运算符逐一比较string对象中的字符,并且对大小写敏感。

比较运算符都依照(大小写敏感的)字典顺序:

  1. 如果两个对象的长度不同,且较短对象的每个字符都与较长string对象对应位置上的字符相同,就说较短对象小于较长对象。

    string str1 = "Hello";
    string str2 = "Hello World";
    

    上述两个对象,根据规则1判断,对象str1小于str2。

  2. 如果两个对象在某些对应位置上不一致,则string对象比较的结果就是对象中第一对相异字符比较的结果。

    string str3 = "Hiya";
    

    根据规则2判断,对象str3大于对象str1也大于str2。

处理string对象中的字符

在cctype头文件中定义了一组标准库函数处理这部分工作。

对string对象中的每个字符做操作,目前最好的办法是使用C++11新标准提供的一种语句:范围for语句。即遍历给定序列中的每个元素并对序列中的每个值执行某种操作,语法格式如下:

for (declaration : expression)
	statement
  • expression部分是一个对象,用于表示一个序列。
  • declaration部分负责定义一个变量,该变量将被用于访问序列中的基础元素。每次迭代,declaration部分的变量会被初始化为expression部分的下一个元素值。
/* 把string对象中的字符每行一个输出出来 */
string str("some string");
for (auto c : str)
	cout << c <<endl;

上述例子中,使用auto关键字让编译器来决定变量c的类型,这里c的类型是char

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

举例:把字符串改写成大写字母的形式,可以使用标准库函数toupper,该函数接收一个字符,然后输出其对象大写形式。

string str("some, string!!");

for (auto &c : str) //注意,此处c是一个引用
{
    c = toupper(c);
}
cout << str << endl;

每次迭代时,变量c引用string对象str的下一个字符,幅值给c也就是在改变str中对应字符的值。

只处理一部分字符

想要访问string对象中的单个字符有两种方式:

  1. 使用下标
  2. 使用迭代器

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

使用下标随机访问

例:编写一个程序,把0到15之间的十进制数转换为对应的十六进制形式:

const string hex_digits = "0123456789ABCDEF";
if (n < hex_digits.size())
    result += hex_digits[n];

vector

标准库类型vector表示类型相同的对象的集合,集合中的每个对象都有与之对应的索引,索引用于访问对象。

vector是模板而非类型,由vector生成的类型必须包含vector中元素的类型,例如vector<int>。

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

vector的初始化

vector<int> v1(10);    //v1有10个元素,每个的值都是0
vector<int> v2{10};    //v2有1个元素,该元素的值为10
vector<int> v1(10, 1); //v3有10个元素,每个的值都是1
vector<int> v1{10, 1}; //v4有2个元素,值分别是10和1

上述初始化方式,如果使用圆括号,可以说提供的值是用来构造vector对象的。如果使用花括号,可以表述成用列表初始化该vector对象。

如果初始化时使用了花括号的形式,但是提供的值又不能用来列表初始化,就要考虑用这样的值来构造vector对象了,例如:

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

要想列表初始化vector对象,花括号里的值必须与元素类型相同。

vector对象添加元素

直接初始化适合初始值已知且数量较少的情况下,对于元素未知或者元素数量较大的情况,可以先创建一个空vector,再在运行时利用vector的成员函数push_back(负责把一个值压入vector尾部)向其中添加元素。

访问vector对象中元素的方法:

vector<int> v{1, 2, 3, 4, 5};
for (auto &i : v)
	i *= i;
for (auto i : v)
	cout << i << " ";
cout << endl;

vector中size返回对象中元素的个数,返回值的类型是由vector定义的size_type类型。要使用size_type,需要先致电给vector是由哪种类型定义的,即vector<int>::size_type。

两个vector对象相等,当且仅当它们所含的元素个数相同,且对应位置的元素值也相同。

不能用下标形式添加元素,因为vector对象(以及string对象)的下标运算符可用于访问已存在的元素,而不能用于添加元素!

迭代器

类似于指针类型,迭代器也提供了对对象的间接访问。

就迭代器而言,其对象是容器中的元素或者string对象中的字符。使用迭代器可以访问某个元素,迭代器也能从一个元素移动到另一个元素。

迭代器有有效和无效之分,和指针类似。有效迭代器或者指向某个元素,或者指向容器中尾元素的下一位置,其他情况都属于无效。

使用迭代器

和指针不同的是,获取迭代器不是使用取地址符,有迭代器的类型同时拥有返回迭代器的成员。比如,这些类型拥有名为begin和end的成员。begin成员负责返回指向第一个元素的迭代器,end成员则是返回指向容器尾元素的下一个位置的迭代器。

如果容器为空,则begin和end返回的是同一个迭代器,都是尾迭代器。

使用迭代器将字符串第一个字母改为大写:

string s("some string!");
if (s.begin() != s.end()) //确保s非空
{
	auto it = s.begin();  //声明一个迭代器变量it,把begin返回的结果赋给它
	*it = toupper(*it);
}

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

迭代器使用递增(++)运算符来从一个元素移动到下一个元素。因为end返回的迭代器并不实际指示某个元素,所以不能对其进行递增或解引用的操作。

使用迭代器及递增将字符串改成大写格式:

string s("some string!");
/* 依次处理s的字符直至处理完全部字符或遇到空白,isspace()函数判断是否遇到空白 */
for (auto it = s.begin(); it != s.end() && !isspace(*it); ++it)
	*it = toupper(*it);

迭代器类型

一般我们并不知道迭代器的精确类型(其实也无需知道)。实际上,迭代器的标准库类型使用iterator和const_iterator来表示迭代器的类型。

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

begin和end返回的具体类型由对象是否是常量决定。有时,这种默认行为并非所需,如果对象只需读操作而无需写操作,最好使用常量类型(比如const_iterator)。

为了便于专门得到const_iterator类型的返回值,C++11新标准引入了两个新函数分别是cbegin和cend。

迭代器失效

虽然vector对象可以动态地增长,但是也会产生副作用。下列情况下,迭代器会失效:

  • 不能再范围for循环中向vector对象添加元素。
  • 任何一种可能改变vector对象容量的操作,比如push_back。

迭代器的应用

使用迭代器运算的一个经典算法是二分搜索,即从有序序列中寻找某个给定的值。

posted @ 2022-04-11 16:41  tiandatian  阅读(30)  评论(0编辑  收藏  举报