c++系列文章(8):标准库类型string、vector

string

初始化string对象的方式:

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

  当初始值只有一个时,使用直接初始化或拷贝初始化都行。如果像s4那样初始化要用到的值有多个,一般来说只能使用直接初始化。

string对象上的操作:

is >> s;   
/*
在执行读取操作时,string对象会自动忽略开头的空白(即空格符、换行符、制表符等),  
并从第一个真正的字符开始读起,直到遇见下一处空白
*/
getline(is,s); 
/*
从is中读取一行赋给s,返回is。有时希望能在读取的字符串中保留输入时的空白符,就应该使用getline来代替原来的>>运算符。
getline从给定的输入流中读取内容,直到遇到换行符(注意换行符也被读进来了),然后把所读的内容存入到那个string对象中去(注意不存换行符)
*/
s.empty(); // s为空返回true,否则返回false
s.size(); 
/*
返回s中字符的个数,对于size函数来说,其返回的是一个strin::size_type类型的值,
string类以及其他大多数标准库类型都定义了几种配套的类型,这些配套类型体现了标准库类型与机器无关的特性,
在具体使用的时候,通过作用域操作符来表明名字size_type是在类string中定义的。
string::size_type是一个人无符号类型的值而且能足够存放下任何string对象的大小。
由于size函数返回的是一个无符号整型数,因此如果在表达式中混用了带符号和无符号数将有可能产生意想不到的结果。
例如,假设n是一个具有负值的int,则表达式s.size()<n的判断结果几乎肯定是true,这是因为负值n会自动转换成一个比较大的无符号值。
因此,如果一个表达式中已经有了size()函数就不要再使用int了,这样可以避免混用int和unsigned可能带来的问题。
*/
s[n]; //返回s中第n个字符的引用,位置从0计起
s1+s2; //返回s1和s2连接后的结果,其结果是一个新的string对象
s1 + "hello"; 
/*
当把string对象和字符字面值以及字符串字面值混在一条语句中使用时,
必须确保每个加法运算符的两侧的运算对象至少有一个是string,其结果是一个新的string对象
*/
s1 = s2; //用s2的副本代替s1中原来的字符

vector

  标准库类型vector表示对象的集合,其中所有对象的类型都相同。因为vector“容纳着”其他对象,所以也被称作“容器”。C++语言既有类模板也有函数模板,其中vector是一个类模板。模板本身不是类或者函数,相反可以将模板看作为编译器生成类或函数编写的一份说明。编译器根据模板创建类或函数的过程称为实例化,当使用模板时,需要指出编译器应把类和函数实例化成何种类型。

vector<int> ivec;
vectpr<string> svec;

  在上面的例子中,编译器根据模板vector生成了两种不同的类型:vector,vector

初始化vector对象的方式:

vector<T> v1; 
/*
v1是一个空vector,它潜在的元素是T类型,执行默认初始化。看起来空vector好像没什么用,但实际上程序在运行时可以很高效地往vector对象中添加元素。事实上,最常见的方式就是先定义一个空vector,然后当运行时获取到元素的值后再逐一添加。
*/
vector<T> v2(v1); //v2中包含v1所有元素的副本
vector<T> v2 = v1; //等价于v2(v1)
vector<T> v3(n,val); //v3包含了n个重复的元素,每个元素的值都是val
vector<T> v4(n); 
/*
v4包含了n个重复地执行了值初始化的对象,如果vector对象的元素是内置类型,比如int,则元素初始值自动设为0。
如果元素是某种类类型,比如string,则元素由类默认初始化。
*/
vector<T> v5{a,b,c...}; //v5包含了初始值个数的元素,每个元素被赋予相应的初始值
vector<T> v5={a,b,c...}; //等价于v5{a,b,c...}

  在某些情况下,初始化的真实含义依赖于传递初始值时用的是圆括号()还是方括号{}。使用圆括号,提供的值是用来构造vector对象;使用花括号,是想列表初始化该vector对象。

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有两个元素,值分别是10和1

  如果初始化时使用了花括号的形式,但是提供的值又不能用来列表初始化,编译器就会尝试用这样的值来构造vector对象

vector<string> v5{"hi"}; //列表初始化:v5只有一个元素
vector<string> v6("hi"); //错误
vector<string> v7{10}; //v7有10个默认初始化的元素
vector<string> v8{10,"hi"}; //v8有10个值为“hi”的元素

向vector对象中添加元素:

  对于vector对象来说,直接初始化的方式只适用于三种情况:初始值已知且数量较少、初始值是另一个vector对象的副本、所有元素的初始值都一样。然而更常见的情况是:创建一个vector对象时并不清楚实际所需的元素个数,元素的值也经常无法确定。对于此类情况,最好的处理方式是先创建一个空vector,然后在运行时在利用vector的成员函数push_back向其中添加元素。
  C++标准要求vector应该能在运行时高效快速地添加元素。因此既然vector对象能高效地增长,那么在定义vector对象的时候设定其大小也就没有必要,事实上如果这么做性能可能更差
  虽然能够高效便捷地向vector对象中添加元素,但对编写程序的要求更高:其中一条就是必须确保所写的循环正确无误,特别是在循环有可能改变vector对象容量的时候。比如在循环体内部包含有向vector对象添加元素的语句,则不能使用范围for循环,即范围for语句体内不能改变其所遍历序列的大小。
  vector对象(以及string对象)的下标运算符可用于访问已存在的元素,但是不能用于添加元素。对于下标必须明确一点:只能对已存在的元素执行下标操作,试图用下标的形式去访问一个不存在的元素将引发错误,不过这种错误不会被编译器发现,而是在运行时产生一个不可预知的值。确保下标合法的一种有效手段就是尽可能使用范围for语句。

迭代器

  前面已经介绍了可以使用下标运算符来访问string对象的字符或vector对象的元素,还有另一种更通用的机制也可以实现同样的目的,这就是迭代器。所有标准库容器都可以使用迭代器,但是其中只有少数几种同时支持下标运算符。严格来说string不属于容器类型,但是string支持许多与容器类型类似的操作。类似于指针类型,迭代器类型也提供了对对象的间接访问,迭代器也能从一个元素移动到另一个元素。迭代器有有效和无效之分,有效的迭代器或者指向某个元素,或者指向容器中尾元素的下一个位置;其他所有情况都属于无效
  和指针不一样,获取迭代器不是使用取地址符,有迭代器的类型同时拥有返回迭代器的成员函数,其中begin成员负责返回指向第一个元素的迭代器,end成员负责返回指向容器“尾元素的下一位置”的迭代器。特殊情况下,如果容器为空,则begin和end返回的是同一个迭代器,都是尾后迭代器。

iter1 == iter2;
iter1 != iter2;

  如果两个迭代器指向的元素相同或者都是同一个容器的尾后迭代器,则它们相等;否则就说这两个迭代器不相等

*iter; //返回迭代器iter所指元素的引用
iter->mem; //解引用iter并获取该元素的名为mem的成员,等价于(*iter).mem

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

++iter;
--iter;

  因为end返回的迭代器并不实际指示某个元素,所以不能对其进行递增或解引用的操作,但可以进行递减操作。前置++和–返回的是递增或者递减之后的迭代器,后置++和–返回的是递增或者递减之前的迭代器。

for(auto it = s.begin();it != s.end();++iter)

  C++程序员在for循环中通常使用!=而不是<进行判断,其原因和他们更愿意使用迭代器而非下标的原因一样:因为这种编程风格在标准库提供的所有容器上都有效。因为标准库容器的迭代器都定义了==和!=,但是它们中大多数都没有定义<运算符。因此只要我们养成了使用迭代器和!=的习惯,就不用在意用的到底是哪种容器类型。
  就像不知道size_type成员到底是什么类型一样,我们也不知道迭代器的精确类型。实际上拥有迭代器的标准库容器使用iterator和const_iterator来表示迭代器。const_iterator和常量指针类似,能够读取但不能修改它所指的元素值。相反,iterator的对象可读可写。如果vector或者string对象是一个常量,则只能使用const_iterator;如果vector或者string对象不是常量,则既可以使用iterator也能使用const_iterator。同时begin和end返回的具体类型由对象是否是常量决定,如果对象是常量,begin和end返回const_iterator;如果对象不是常量,返回iterator。为了便于专门得到const_iterator类型的返回值,c++11新标准引入了两个新函数,分别是cbegin和cend,不论vector对象是否为常量,返回值都是const_iterator

iter1 - iter2; //两个迭代器相减的结果是它们之间的距离。

只要两个迭代器指向的是同一个容器中的元素或者尾元素的下一个位置,就能将其相减,所得的结果是两个迭代器的距离。所谓距离是指运算符右侧的迭代器向前移动多少位置就能追上左侧的迭代器,其返回类型是名为difference_type的带符号整型数

// 使用迭代器完成二分搜索
auto beg = text.begin();
auto end = text.end();
auto mid = beg + (end - beg) / 2;
while(mid != end && *mid != sought)
{
	if(sought < *mid)
		end = mid;
	else
		beg = mid + 1;
	mid = beg + (end - beg) / 2;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值