一.汉字的编码
我们知道计算机存储英文字母,标点,数字用的是ascall码,128种用一个字节表示绰绰有余。而汉字远远不止128种,因此汉字需要两个字节表示。
1.gbk编码中汉字占两个字节。
2.utf-8中,一个汉字占三个字节。
> UTF规定:如果一个符号只占一个字节,那么这个8位字节的第一位就为0。如果为两个字节,那么规定第一个字节的前两位都为1,然后第一个字节的第三位为0,第二个字节的前两位为10,然后如果是三个字节的话,那么第一个字节的前三位为111,第四位为0,剩余的两个字节的前两位都为10。按照这样的算法去思考一个中文字符的UTF-8是怎么表示的:一个中文字符需要两个字节来表示,两个字节一共是16位,那么UTF-8下,两个字节是不够的,因为两个字节下,第一个字节已经占据了三位:110,然后剩余的一个字节占据了两位:10,现在就只剩下8位,与Unicode下的两个字节,16位去表示任意一个字符是相悖的,也就是Unicode下的16位减去UTF-8下的8位=8位,刚好差了一个字节的空间,所以就使用三个字节去表示非ANSI字符:三个字节下,一共是24位,第一个字节头四位是:1110,后两个字节的前两位都是:10,那么24位-8位=16位,刚好两个字节去表示Unicode下的任意一个非ANSI字符。
二.string类
参考string官方文档
1.扩容
这里resize和reserve都是在string对象后开指定大小的可用空间,而不是把它的容量设置为指定大小。
resize改变了size大小,而reserve不改变.
resize空间小于size大小时会删除元素,reserve空间小于size时不会改变容量也不删除元素。
2.小试牛刀
2.1把字符串转化为整数
迭代器实现
#include <cctype>
class Solution {public:int StrToInt(string str) {int ans = 0;int i=0;for (string::reverse_iterator rit = str.rbegin(); rit != str.rend(); ++rit) {if (isdigit(*rit)) {ans=ans+((*rit)-48)*pow(10,i++);continue;}if (*rit == '+')break;else if (*rit == '-')ans *= -1;elsereturn 0;}return ans;}
};
2.2.Leetcode415.字符串相加
给定两个字符串形式的非负整数 num1 和num2 ,计算它们的和并同样以字符串形式返回。
你不能使用任何內建的用于处理大整数的库(比如 BigInteger), 也不能直接将输入的字符串转换为整数形式。
示例 1:
输入:num1 = “11”, num2 = “123”
输出:“134”
class Solution {
public:string addStrings(string num1, string num2) {int end1=num1.size()-1,end2=num2.size()-1;int carry=0;string ans;//提前扩一波容ans.reserve(num1.size()>num2.size()?num1.size()+1:num2.size()+1);while(end1>=0||end2>=0){int val1=end1>=0?num1[end1]-'0':0;int val2=end2>=0?num2[end2]-'0':0;int ret=val1+val2+carry;carry=ret/10;ans+=ret%10+'0';--end1;--end2;}if(carry)ans+='1';reverse(ans.begin(),ans.end());return ans;}
};
这里我们用数学中用竖式计算加法的方法,用carry表示进位,从后往前一步一步加到ans中,同时注意细节处理,最后反转一下ans得到答案。
3.find+replace(用xx替换xx)
这里我们用空格替换“/”,如果用多个字符替换呢?那么上述方法就显得很低效,因为不仅开空间还要往后挪数据。解决方法:以空间换时间,重新定义一个空字符,遍历两遍老字符。第一遍开空间,第二遍往空字符加数据。如下图
4.string类的swap和算法的swap
我们可以看到string的swap只需要改变指针指向就可以了,而std库里的需要创建一个临时对象还要拷贝赋值完成交换。
5.c_str()
将string转化为c语言能够识别的const char*,如下打开文件并读取
三.string类的写时拷贝(copy-on-write)
1.看下面这段代码
#include<stdio.h>
#include<string>using namespace std;
int main()
{string s1("hello,world");string s2(s1);printf("%x\n",s1.c_str());printf("%x\n",s2.c_str());s1[2]='m';printf("%x\n",s1.c_str());printf("%x\n",s2.c_str());s2[2]='m';printf("%x\n",s1.c_str());printf("%x\n",s2.c_str());return 0;
}
下面代码在g++下运行结果
我们发现前面不修改s1和s2时两者地址是一样的,而修改s1中的字符时,操作系统此时才会给s1开辟一块独立的新空间把数据拷贝过去并修改。这就是所谓的写时拷贝技术。
2.简要理解如何实现?
每当我们为string分配内存时,我们总是要多分配一个空间用来存放这个引用计数的值,只要发生拷贝构造可是赋值时,这个内存的值就会加一。而在内容修改时,string类为查看这个引用计数是否为0,如果不为零,表示有人在共享这块内存,那么自己需要先做一份拷贝,然后把引用计数减去一,再把数据拷贝过来。
四.string类的模拟实现
class string {public:typedef char* iterator;iterator begin(){return _str;}iterator end(){return _str+_size;}string(const char* str = "") :_size(strlen(str)){_capacity = _size;_str = new char[_capacity + 1+1];strcpy(_str, str);}/*string(const string& s):_size(s._size), _capacity(s._capacity){_str = new char[_capacity + 1];strcpy(_str, s._str);}*///现代写法void swap(string& tmp){::swap(_str, tmp._str);::swap(_size, tmp._size);::swap(_capacity, tmp._capacity);}string(const string& s):_str(nullptr),_size(0),_capacity(0){string tmp(s._str);swap(tmp);_str[_capacity + 1]++;}~string(){delete[]_str;_str = nullptr;_size = _capacity = 0;}//实现赋值操作string& operator=(const string& s){if (this != &s){/*delete[] _str;_str = new char[strlen(s._str) + 1];strcpy(_str, s._str);*/char* tmp = new char[strlen(s._str) + 1];strcpy(tmp, s._str);delete[] _str;_str = tmp;_size = s._size;_capacity = s._capacity;}return *this;}const char* c_str()const{return _str;}size_t size()const{return _size;}size_t capacity()const{return _capacity;}char& operator[](size_t pos){return _str[pos];}const char& operator[](size_t pos)const{assert(pos < _size);return _str[pos];}void reserve(size_t n){if (n > _capacity){char* tmp = new char[n + 1];strcpy(tmp, _str);delete[] _str;_str = tmp;_capacity = n;}}void push_back(char ch){if (_size == _capacity){reserve(_capacity == 0 ? 0 : _capacity * 2);}_str[_size] = ch;++_size;_str[_size] = '\0';}void append(const char* str){size_t len = strlen(str);if (_size + len > _capacity){reserve(_size + len);}strcpy(_str + _size, str);_size += len;}string& operator+=(char ch){push_back(ch);return *this;}string& operator+=(const char* str){append(str);return *this;}string& insert(size_t pos, char ch){assert(pos <= _size);if (_size == _capacity){reserve(_capacity == 0 ? 4 : _capacity * 2);}/*size_t end = _size;while (end >= pos){_str[end + 1] = _str[end];--end;}*/size_t end = _size + 1;while (end > pos){_str[end] = _str[end - 1];--end;}_str[pos] = ch;++_size;return*this;}string& insert(size_t pos, const char* str){assert(pos <= _size);size_t len = strlen(str);if (_size + len > _capacity){reserve(_size + len);}size_t end = _size + len;while (end >= pos + len){_str[end] = _str[end + len];--end;}strncpy(_str + pos, str, len);_size += len;}void erase(size_t pos, size_t len = npos){assert(pos < _size);if (len == npos || pos + len >= _size){_str[pos] = '\0';_size = pos;}else{strcpy(_str + pos, _str + pos + len);}}size_t find(char ch,size_t pos = 0)const{assert(pos < _size);for (size_t i = pos; i < _size; i++){if (ch == _str[i]){return i;}}return npos;}size_t find(const char* sub, size_t pos = 0)const{assert(sub);assert(pos < _size);const char* ptr = strstr(_str + pos, sub);if (ptr == NULL){return npos;}else{return ptr - _str;}}string substr(size_t pos, size_t len = npos)const{assert(pos < _size);size_t reallen = len;if (len == npos || pos + len > _size){reallen = _size - pos;}string sub;for (size_t i = 0; i < reallen; i++){sub += _str[pos + i];}return sub;}private:char* _str;size_t _size;size_t _capacity;public:static size_t npos;};size_t string::npos = -1;istream& operator>>(istream& in, string& s){char ch;ch = in.get();const size_t N = 32;char buff[N];size_t i = 0;while (ch != ' ' && ch != '\n'){buff[i++] = ch;if (i=N-1){buff[i] = '\0';s += buff;i = 0;}ch = in.get();}buff[i] = '\0';s += buff;}