C++小工进阶之路Ⅷ(string类的应用)

STL:对数据进行管理,对常见的数据结构的封装
线性容器:
C++98
	string:对char* 格式的字符串进行封装	
	vector:动态的顺序表		
	list:带头结点你的双向循环链表	
	deque:双端队列---了解	
	stack:栈	
	queue: 队列
	priority_queue:优先级队列--堆
C++11:
	array:静态类型的顺序表(用的不多)
	forward_list:单链表(用的不多)

C语言既然都有了对字符串进行操作的函数为什么C++还要进行封装?

在C语言中字符串就是字符数组+'\0'结尾标志,char*
为什么在C++中要重新封装char*,封装为string,因为我觉得首先是,C++是一门面向对象的语言,然后C语言里面
提供的那些操作字符串的函数都没有进行封装和C++的思想不太相符,所以才会进行封装。
其次就是C语言中如果将字符串放到那个数组里面去就会面临一个,字符串放不下的问题,然后就是使用的是char* 
来在堆上申请空间但是还是要考虑空间不足的情况,假如第一次开辟的空间不足的话,有需要用户自己手动的去申请和释放
空间,使用起来比较麻烦,但是C++中对char*进行了封装,对空间进行了管理就算空间不足也不需要用户自己手动进行扩容
还有一点就是语言的库函数,一般对函数的安全性考虑的都不是很好。
提起一个容器的接口我们经常会从六个部分去介绍这个容器
1.构造
2.容量
3.元素访问
4.修改
5.特殊操作
6.迭代器

resize()和reserve()辨析:

1.string类中的clear()清空的是有效字符,并不会清空底层的空间
2.resize()和reserve()辨析:
resize()功能:修改的是有效元素的个数,改变有效元素个数之后,假如是增大空间,然后增大后的空间用户也没有提供
填充的字符,那么就系统就会使用字符'\0'来填充。
注意:	如果使用resize()缩小有效元素的的话,他不会缩小底层的空间,它知识改变有效元素的个数。
	如果是将有效元素增多,那么可能需要扩容(扩容机制的话是每次增加16个字节)
reserve():请求容量的改变,意思也就是这个函数是用来扩容的,这里需要注意一下,假如现在有一个string类型的对
象,现在对象底层已经存在了100个有效字符,然后你要是使用reserve()来让空间缩小到10个的话,对不起,做不到。
假如一个string对象,现在的有效字符个数是5个,但是底层所占的空间是100的话,使用resever()使空间缩小到50个
的话,对不起做不到,但是让空间缩小到10个的话,那么空间就会缩小到15个,这个是为什么呢?因为resever()只有在
对象中存在的有效字符小于15个,并且用户要求缩小到 小于15(为什么是15个,因为我们给一个string对象,在被创建
却未指定大小的情况下 ,它的大小就是16个还有一个'\n',所以有效的就是15个)个空间的话,reserve()的空间缩小
才会启动,其余情况做不到,一般都是使用reserve()扩容的,或者使用reserve()预留空间,但是reserve()只是扩容
但是并不会填充任何值。
reserve()在整个操作的过程中只会改变容量的大小,而不会改变有效元素的个数

string内部的一些处理:

还有就是string在类里面,有自己维护的一个静态的数组,大小是16个字节,最后一个是'\n'也就是说有15个有效的空
间,因为假如每次是申请一个对象的话都需要去动态的申请一段空间那么就太慢了,所以在一开始的时候就会先给出16个静
态的字符串,当空间不足的时候才会去动态的申请。

正因为string内部有了上面的处理,所以在使用reserve()处理的时候才会,在有效字符个数小于15的时候才会将空间缩
小,去使用自己类里面维护的一个静态的数组。

string下标访问的区别:

string s("hello world");
cout<< s[20]<< endl; //对象中根本就没有这么多的元素所以使用下标访问运算符的话,就会触发assert()引起代码崩毁
s.at(20) //at()也是另外一种的元素访问的函数,但是和[]的区别就是假如元素访问越界,就会抛出out of range的异常
一般都是使用[]

string底层是如何实现扩容的:

在使用push_back()不断的对一个对象进行尾插的过程中,发现string底层的扩容方式是,每次大概按照1.5倍的方式
来进行扩容的。
STL有很多的版本,在vs----PJ版本底下我们发现是按照大概1.5倍的方式来进行扩容的,在Linux---SGI就是按照两倍
的大小进行扩容的

如果大概是早要给string类中存放多少元素,我们可以提前使用reserve()来预留空间,这样的话就不用不断的扩容了

关于string的字符串的反转,使用reverse(迭代器提供的指针,迭代器提供的指针)需要注意的是,这个函数不是某一个容器的内置函数,而是C++库里面给出的库函数

使用getline()从键盘接受一行单词
string s;
getline(cin,s);
字符串相加:为什么会有字符串相加,是因为现在数字特别大,大到内置内省已经没有办法来保存了,这个时候就可以使用
字符串相加的的方式来进行大数求和;
class Solution {
public:
    string addStrings(string num1, string num2) {
        int num1size = num1.size();  
        int num2size = num2.size();      
        if(num1size < num2size)  //让num1中始终保存的是两个字符串中较长的那一个
        {
            swap(num1,num2);
            swap(num1size,num2size);
        }
        string strRet(num1size+1,'0');   //然后利用较长的一个字符串给返回的 字符串确定长度
        int offset = 0;  //这个标志位使用来记录是否进位的标志
        for(int i = num1size - 1, j = num2size - 1; i >=0; --i,--j)  //注意看外部的循环是较长的字符串
        {
            char cret = num1[i] - '0';  //这里的cret是一个字符,但是用的是ASCII标志的字符,记录的却是字符代表的数字
            if(j >= 0)  //因为num2比较短,所以要进行判断,是否还能够从num2中读取到字符
            {
                cret += num2[j] - '0';  //可以读取到的话继续累加
            }
            cret += offset;  //并且还要类加上上一次的进位,第一次的话就是0,也不影响啥
            offset = 0;  //标志位累加之后
            if(cret >= 10)  
            {
                offset = 1;  //内部的主要作用就是让标志位记录下来,他大于10,然后够将10卸载下来,但是卸载下来的10用标志位保存着,
                cret -= 10;  //卸载10
            }
            strRet[i + 1] += cret;  //然后将剩余的个位数加到现在的位置
            //注意这里的+=,因为在循环之前我们申请的一个字符串中都已经被初始化成了000000000,所以现在就等于是在字符0的基础之上加数字,但是加的数字不会大于10,因为之前的10 已经卸载了
        }
        if(offset == 1)  //最后循环出来之后还需要再判断一次,判断最后一次到底有没有offset标志位
        {
            strRet[0] += 1;  //有的话就在最前面的空间加上1
        }
        else
            strRet.erase(strRet.begin());  //不然的话就把最前面的一个空间消除掉
        return strRet;
    }
};

大数相加需要考虑
999 + 1这种,最高位不需要进位的,
999 + 222 种需要进位的
循环用的是最长的字符串作为循环次数
循环出来之后还需要再加一次
是从后往前加的

上一篇:[RL 13] QMIX (ICML, 2018, Oxford)


下一篇:vector的reserve方法