【计算机本科补全计划】C++ Primer:String Vector标准库及迭代器的使用

正文之前

今天帮学妹选了一天的电脑配件,然后从中领悟:坐看狗东黑我钱,任他涨价我不动!!最后果断的用学妹的钱冲了个Plus,然后领了一堆券,最后学妹还省了30块,另外昨晚找到了一个买酷睿i7 7700k 送主板的,又省了250,然后各种大小活动都去看了个遍,总算是学会了怎么省钱,特此还要多谢我华科电信的一位大佬--“大王”巍的指点,不然我肯定被狗东坑到怀疑人生。看配置的直接翻到正文之后看!


正文


1、 命名空间using声明

我们自己平时用的时候是:

代码语言:javascript
复制
using namespace std;

老实说我也就会这一句,所以其他的基本漠不关心,但是既然书里讲了我也就写出来了。一般的格式如下:

代码语言:javascript
复制
using std::cin;

有了上面这句,以后写cin就只是cin了。不需要std::cin了。不过也仅限于此,cout还是没法直接用,不过这样的好处就是可以单独的引用,不会造成浪费(Maybe)?

另外头文件不应该包含着using声明,因为头文件的内容会被拷贝到所有引用他的文件中取去,如果头文件内有这个using声明,那么每一个使用了该文件的文件就会有这个声明,对于某些程序来说,由于不经意间包含了一些名字,反而可能会产生始料未及的名字冲突。


2、 标准库 string的基本操作

首先,要使用string这个标准库就要声明下头文件

代码语言:javascript
复制
#include <string>
using std::string
  • string的初始化 有以下几种种方式
代码语言:javascript
复制
string s1;    //s1:empty
string s2=s1;    //s2: empty
string s3="string";    //s3:string\0
string s4(10,'c')   //s4:cccccccccc
string s5("string");    //s5:string\0

几种方式的过程和效果我都写出来了,就不多赘述了。

  • string对象的上的操作
  • 下面详细说明各个操作:
    • 读写操作,读取的时候自动忽略开头的空白,遇到第一个非空字符开始读取,之后遇到第一个空白字符就停止读取,不管后面还有没有,也不读取空白字符,输入“ hello world !!”,最后在cin>>s 中读取到的只有s=“hello\0”;
    • 读取操作可以多个对象一起进行:cin>>s1>>s2; 那么“ hello world !!”最后的结果是:s1=“hello”;s2=“world”;
    • 使用getline(cin,s)读取一整行,getline()是无返回值函数,直接对s进行操作,所以不需要额外的空间承载返回值;getline()见到"\0"就结束输入,但是得到的字符串不含有换行符,所以如果要按行输出,那么就要自己手动加上换行操作。
    • empty() ,size()操作都是很显而易见的,带有返回值的函数,而且不需要传入参数,是string自带的一个成员函数,只需要使用点操作符调用即可:s.empty() s.size() ,前者返回一个bool变量,后者直接返回一个string::typesize的无符号整数变量,它能够存放下任何string的大小,是其专属的大小变量。若要在外部定义string::typesize类型的变量,可以用auto或者是decltype得到。
代码语言:javascript
复制
auto len=s.size();  decltype(s.size()) len;
  • 比较运算符其实也没啥差别,但是比较大小的话,string中有一套规则(但是我自己总结下来:只要从头开始看起,第一个不相同的对应位置的字符比较大小就全权代表了这个字符串的大小,另外结束符号小于一切的字符!通用,不信你看):
    • 如果两个string长度不同,较短的string对象每个字符都与较长的对象对应部分相同,那么较短的就短于长的;(like:abc < abcd )
    • 如果在相应的位置不一样,那么就比较第一个相异的字符;(like: abd > abcd )
  • 相加相减操作:就直接加上咯,相当于拼接,反正string是动态增长长度的,所以随便你加多少。还有一种骚操作,那就是直接用原本的字符串变量直接加上一个字符串:但是记住,加号两边一定要有一个是string变量,两个字符串字面值直接加起来是违法操作!``` string s2=s1+"zhang"+"z"+"b" //从左到右相加,所以左边一直都是string变量!!

3、 处理string对象中的字符

采用基于范围的for循环实现:

代码语言:javascript
复制
for ( auto c:str)  //此句的意思是:对于str中的每个字符进行拷贝
  cout<<c<<endl;

上述只能实现读而不能实现存,因为是直接拷贝,所以无法对string的单个字符进行操作,但是如果是引用,就可以实现了。

代码语言:javascript
复制
for ( auto &c:str)  //此句的意思是:对于str中的每个字符进行引用
{
  cout<<c<<endl;
  c=toupper(c);
}

利用这些特性,结合一个大家常见的下标运算符(这个点就不讲了,跟数组一样,烂大街了),我自己实现了一个把所有的英文单词的首字母改成大写的程序:

代码语言:javascript
复制
string w="hello boy I don't want to hurt you? baby~~~";
w[0]=toupper(w[0]);
for (decltype(w.size()) index=0; index < w.size(); ++index)
{
    if (isspace(w[index]))
    {
        w[index+1]=toupper(w[index+1]);
    }
}
cout<<"================================================\n"<<w<<"\n================================================\n"<<endl;

4、 标准库类型Vector定义与初始化

  • vector是一种对象的集合,可以看作是一种容易,好比是房子,前面的string可以看做是教室排座位,每个座位上按序号只能坐入字符,不能是别的类型,并且多个字符可以组成一个小组,string对象就是一个小组;而vector就好比是大楼,可以安置的类型更加宽广。可以放入int,string,char等一些类型。因为在具体定义一个vector对象前不知道类型,所以我们称之为类模板,对其实施创建的时候称此过程为实例化,定义如下:
代码语言:javascript
复制
vector<int> ivec;
vector<string> strvec;
vector<vector<int> > vecivec;

因为vector是容纳对象的,所以不存在包含引用的vector;初始化如下:

代码语言:javascript
复制
vector<T> tvec;   // Empty vector
vector<T> tvec(v1);  //  copy v1 to tvec
vector<T> tvec = v1 ; // 同上
vector<T> tvec(n,val); //tvec包含了n个相同T类型的val元素;
vector<T> tvec(n); //执行n次空的初始化;
vector<T> tvec{a,b,c,d  ···};  //具体的初始化
vector<T> tvec={a,b,c,d ···};  //同上

注意如果是拷贝初始化的话,不同类型的vector是不能相互拷贝的!


5、 向vector中添加元素

对于vector这个容器,只能用专用的内置函数来对其增加元素,push_back具体用法如下:

代码语言:javascript
复制
vector<int> ivec;
for(int i=0;i!=100;++i)
{
  ivec.push_back(i);
}

从上面我们可以知道,vector具有良好的动态增长的性能,所以一开始如果就限定其大小的话,是一种对特性的浪费,一开始初始化的时候就限定容量是不是一件明智的事情!(注意,其实上面的操作不是很符合规定,因为隐性规定:在循环体中改变了遍历序列的长度的操作不用for 循环,可能造成缓冲区溢出)


6、 其他vector操作

我们可以看到,对于vector只有一种添加元素的办法,但是对于元素的读写,可以直接用下标表示法,这个数组,string是完全共同的, 但是请注意,千万不要给定一个不存在的下标,如果超出了vector变量的长度,那么毫无疑问你的,会产生严重错误!甚至可能导致 缓存区溢出!!


7、 迭代器

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

    指针是c语言中就有的东西,迭代器是c++中才有的,指针用起来灵活高效,迭代器功能更丰富些。

    迭代器提供一个对容器对象或者string对象的访问的方法,并且定义了容器范围。

  • 对于上面介绍的几种标准库类型,都有内置的迭代器操作,所谓迭代器,就是两个地址。比如说下面的例子:
代码语言:javascript
复制
vector<int > v;
auto b=v.begin();

此时如果可以查看b的类型,你会发现其实就是个指针对象。只是其类型由编译器给定。我们只管auto 或者 decltype就好了!,另外还有一个end()函数返回尾后迭代器,没有什么实际意义,正如名字,是在最后一个元素的下一个位置,用于判断是否为空的容器(begin end指向一个位置的时候)

  • 下面是一些关于迭代器的操作,其中iter就是迭代器,跟指针其实没啥区别


  • 每种容器类型都定义了自己的迭代器类型,如vector

代码语言:javascript
复制
vector<int>::iterator iter;

语句定义了一个名为 iter 的变量,它的数据类型是 vector定义的 iterator 类型。每个标准库容器类型都定义了一个名为 iterator 的成员,这里的 iterator 与迭代器实际类型的含义相同。

  • 前面的程序用vector::iterator 改变 vector 中的元素值。每种容器类型还定义了一种名为 constiterator 的类型,该类型只能用于读取容器内元素,但不能改变其值。 当我们对普通 iterator 类型解引用时,得到对某个元素的非 const。而如果我们对constiterator 类型解引用时,则可以得到一个指向 const 对象的引用,如同任何常量一样,该对象不能进行重写。
代码语言:javascript
复制
for (vector<string>::const_iteratoriter = text.begin();iter != text.end(); ++iter)
cout &lt;&lt; *iter &lt;&lt; endl; // printeach element in text</code></pre></div></div><p>使用 const_iterator 类型时,我们可以得到一个迭代器,它自身的值可以改变,但不能用来改变其所指向的元素的值。可以对迭代器进行自增以及使用解引用操作符来读取值,但不能对该元素赋值。</p><ul class="ul-level-0"><li>使vector对象的迭代器失效的操作</li><li>for中添加元素</li><li>push_back或者改变容量的操作</li></ul><p><strong>记住一点:但凡是使用了迭代器的循环体,此时就不要像迭代器所属的容器进行添加元素的操作了!!!千万不要!!</strong></p><ul class="ul-level-0"><li>迭代器的算术操作(跟指针没差别,只是是标准库自带的类型)</li></ul><div class="rno-markdown-code"><div class="rno-markdown-code-toolbar"><div class="rno-markdown-code-toolbar-info"><div class="rno-markdown-code-toolbar-item is-type"><span class="is-m-hidden">代码语言:</span>javascript</div></div><div class="rno-markdown-code-toolbar-opt"><div class="rno-markdown-code-toolbar-copy"><i class="icon-copy"></i><span class="is-m-hidden">复制</span></div></div></div><div class="developer-code-block"><pre class="prism-token token line-numbers language-javascript"><code class="language-javascript" style="margin-left:0">iter + n

iter - n
iter1 - iter2
vector<int>::iterator mid = vi.begin() +vi.size() / 2;

任何改变 vector 长度的操作都会使已存在的迭代器失效。例如,在调用 push_back 之后,就不能再信赖指向 vector 的迭代器的值了!!!!!!!~


正文之后

我只能看着这个配置流口水啊!!具体的购买详细和指导请看我另一篇文:万元台式机组装养成记

后来又加了三件配个套: