STL六大组件
仿函数、算法(查找、归并、分类)、迭代器(iterator)、空间配置器(allocator和内存池先(只申请空间但是并没有初始化,避免频繁从堆中申请内存影响效率))、容器(string、vector)、配接器
string
c语言的字符串不能很好的按需进行修改,比如因为字符串一般存储在字符数组当中,而字符数组的大小是定死的,如果要对字符数组里面的内容进行调整可能会受限,所以有了string这个类型,但是其底层管理的依旧还是字符数组,支持增删查改。
stirng也是类模板typedef过来的:typedef basic_string<char> string
basic_string<T>:这个类模板发展了许多的类,来应对不同的需要。如下:typedef basic_string<wchar_t> wstring
typedef basic_string<char32_t> u32string
typedef basic_string<char16_t> u16string
一个汉字可以看成ASCII码表上的一个字符,但只用一个字节表示汉字,由于状态只有256种太少不适用->unicode(万国):UTF-8(一个字节为单位,可以用1个字节或者2个、3个、4个字节来编码,对不同范围的字符是同不同长度的编码。比如常见的汉字采用2个字节,生僻的汉字采用4个字节)、UTF-16、UTF-32(更加的统一但是也更加的浪费)。
int main() { char str1[] = "apple"; char str2[] = "高手"; cout << sizeof(str1) << endl;//6 cout << sizeof(str2) << endl;//5,看下面的监视我么们可以发现每一个汉字需要两个字符表示,而每个字符都是负数,这是因为ASCII码用了正数的部分,导致如果想要兼容ASCII的话那么汉字编码的部分只能使用负数部分,如果和ascii码混用的话,会有问题,我们拿到两个字符,到底是对应着一个汉字,还是两个ASCII符号。现在我们可以拿到一个字符,看第一个bit位的数值,如果是0对应着ASCII码,如果是1就和下面一个字符联合对应一个汉字。 return 0; }
int main() { char str2[] = "高手"; str2[3]--; cout << str2 << endl; str2[3]--; cout << str2 << endl; str2[3]--; cout << str2 << endl; str2[3]--; cout << str2 << endl; str2[3]--; cout << str2 << endl; str2[3]--; cout << str2 << endl; return 0; }
由上可知,gbk编码是将同音字编码到一块的。
string使用
int main() { string s1;//无参构造string() string s2("hello world");//string (const string& str) string s3 = "hello world";//单参数构造函数可以直接赋值,因为支持隐式类型转换:构造+拷贝=>构造 string s4(s3, 6, 3);//string (const string& str, size_t pos, size_t len = npos)从下标pos位置开始拷贝npos长度 cout << s4 << endl; string s5(s3, 6, 13);//npos超过s5的末尾时,停止拷贝 cout << s5 << endl; string s6(s3, 6);//static const size_t npos = -1;相当于将32bit的1赋值给一个无符号整形,解读成42亿多 cout << s6 << endl; string s7("hello world", 5); //string(const char* s, size_t n)拷贝字符串s的前n个位置的值 cout << s7 << endl; string s8(10, '*');//string (size_t n, char c)字符串填充n个字符c cout << s8 << endl; for (size_t i = 0; i < s2.size(); ++i) { s2[i]++;//[]运算符重载 } cout << s2 << endl; for (size_t i = 0; i < s2.size(); ++i) { cout << s2[i] << " "; } cout << endl; return 0; }
int main()
{
string s1("hello world");
cout << s1.size() << endl;
cout << s1.length() << endl;//length和size的结果是一样的,功能一样,size()之所以出现是为了和stl库的其它结构保持一致
cout << s1.max_size() << endl;//意义不大
cout << s1.capacity() << endl;//capacity是不包含'/0'的,但实际上是开了'\0'的空间。也就是如果结果是15,实际上开了16个字节的空间
return 0;
}
int main()
{
string s1("hello");
s1.push_back(' ');
s1.push_back('!');//插入一个字符
cout << s1 << endl;
s1.append("world");//插入一个字符串
cout << s1 << endl;
s1 += ' ';
s1 += '!';
s1 += "world";//其实+=的底层实现还是push_back和append
cout << s1 << endl;
}
class string
{
private:
char* _ptr;
char _buf[16];
size_t _size;
size_t _capacity;
};//其实string结构和这个比较类似,当需要存储的字符串空间比较小的时候直接存在_buf数组当中,相当于浪费了一个ptr指针,但是ptr指针指向的堆区空间此时是不需要开辟的
//当需要存储的字符串的大小大于16的时候就不存储在_buf当中了,直接存储在ptr所指向的堆区空间上了,这个时候浪费了一个16个字节大小的字符数组,这也是x86环境下sizeof(string)的大小是28=12+char _buf[16]的原因
int main() { // 观察扩容情况 -- 1.5倍扩容 string s; cout << sizeof(s) << endl;//28 cout << "making s grow:\n"; size_t sz = s.capacity(); cout << "capacity changed: " << sz << '\n'; for (int i = 0; i < 100; ++i) { s.push_back('c'); if (sz != s.capacity()) { sz = s.capacity(); cout << "capacity changed: " << sz << '\n'; } } return 0;
由下图可知第一次的capacity是15,但是根据string的类结构我们可以知道这并不是额外开辟的,而是本身结构自带的,而且也应证了capacity是不计算'\0'的。所以第一次正经在堆区开辟的空间的大小其实是32。但是不同的编译器扩容的原理是不一样的 。
reserve(100)只会改变capacity但是不会改变size,也就是将capacity扩容到100。
resize(100)是size和capacity都会编程100,空间填入缺省值'\0'。也可以s1.resize(100,'x'),指定用字符'x'来填充capacity。可以理解为扩容+初始化。也可以用来缩小size,s1.resize(5)
就是只保留5个,而capacity是不会动的。这是因为缩容是不支持原地缩容,如果想要原地缩容就必须支持释放部分空间,但是这在底层是不支持的,会增加内存管理的难度,缩容是开辟一块新的空间再按需拷贝部分数据,这样的实现代价是非常大的。
迭代器:
迭代器对于string这个容器而言和[]下标或者范围for比较,用起来并不是那么的方便。但是string必须要有迭代器,目的是为了和其他的容器在使用上保持异种一致。就好像string有了length()函数之后为什么还要有capacity()函数。[]下标在string容器比较好用,但是如果更换了容器就不一定了。
int main() { string s1("hello world"); string::iterator it = s1.begin(); while (it != s1.end())//采用迭代器进行遍历。end是最后字符串最后一个有效字符的下一个位置,类似于c语言字符串的'\0' { cout << *it << " "; ++it; } cout << endl; for (auto ch : s1)//采用范围for来进行遍历,但是auto的底层实现和迭代器是一样的 { cout << ch << " "; } cout << endl; return 0; } /// int main() { string s1("hello world"); string::reverse_iterator rit = s1.rbegin();//反向迭代器,从后向前,而且反向迭代器的类型名称又发生了变化 while (rit != s1.rend())//rbegin是最后一个有效字符的位置,也就是d的位置。而rend的位置是第一个字符的前一个位置 { cout << *rit << " "; ++rit;//注意是++ } } /// void Func(const string& s) { // 遍历和读容器的数据,不能写,只能读取 string::const_iterator it = s.begin();//此时迭代器的类型变成了const类型的的string的迭代器:const_iterator begin() const; while (it != s.end()) { cout << *it << " "; ++it; } cout << endl; auto rit = s.rbegin(); while (rit != s.rend())//时迭代器的类型变成了const类型的的string的反向迭代器:string::const_reverse_iterator rit = s.rbegin(); { cout << *rit << " "; ++rit; } cout << endl; } //迭代器的类型在这里共有4种。const做为关键字修饰定义出来的对象和变量是不可修改的
at()和[]的区别
int main() { string s1("hello world"); try { s1.at(100);//越界了是抛异常,处理方式相对比较合理。 si[100];//越界之后是采用的assert断言,所以程序会直接中断,比较激进 } catch (const exception& e) { cout << e.what() << endl; } return 0; }
int main() { //insert/erase不推荐经常使用,能少用就少用。因为他们可能都存在要挪动数据,效率低下。删除的话如果全部删除还好说,但是如果只是删除中间的部分的话代价就会非常的大 string s1("world"); s1.insert(0, "hello"); cout << s1 << endl; s1.insert(5, 1, ' ');//string& insert (size_t pos, size_t n, char c)从下标pos开始插入n个字符c cout << s1 << endl; s1.insert(5, " ");//string& insert(size_t pos, const char* s)从下标pos开始插入字符串s cout << s1 << endl; s1.insert(s1.begin()+5, ' ');//从begin()+5的位置开始插入 cout << s1 << endl; string s2("hello world"); s2.erase(5, 1);//string& erase (size_t pos = 0, size_t len = npos); cout << s2 << endl; s2.erase(s2.begin() + 5);//iterator erase (iterator p) cout << s2 << endl; string s3("hello world"); s3.erase(5, 30); cout << s3 << endl; string s4("hello world"); s4.erase(5);//string& erase (size_t pos = 0, size_t len = npos)这里采用缺省值直接全删除了 cout << s4 << endl; return 0; }
replace函数并不建议,因为会涉及到空间不够扩容和挪动数据,效率不够
int main() { string s1("hello world"); s1.replace(5, 1, "%%d"); cout << s1 << endl; string s2("hello world i love you"); size_t num = 0; for (auto ch : s2)//提前统计空格的数量 { if (ch == ' ') ++num; } s2.reserve(s2.size() + 2 * num);//提前开空间,避免repalce时扩容 size_t pos = s2.find(' '); while (pos != string::npos)//string::npos是静态局部常量static const size_t, { s2.replace(pos, 1, "%20"); pos = s2.find(' ', pos + 3);//减少重复搜索,直接从上次搜索的地方开始 } cout << s2 << endl; // 下面是采用空间换时间的方法 string s3("hello world i love you"); string newStr; num = 0; for (auto ch : s3) { if (ch == ' ') ++num; } newStr.reserve(s3.size() + 2 * num); for (auto ch : s3) { if (ch != ' ') newStr += ch; else newStr += "%20"; } cout << newStr << endl; return 0; }
String::swap(void swap (string& str))和swap的区别?
string::swap是根据string类定义的成员函数,所以更有针对性,可以采用更高效的方法进行交换,直接交换两个string对象中_ptr的指向就可以了。但是标准库里面的swap泛函数效率就比较低了,是 通过新建一个临时变量进行swap的。
int main() { string s1("hello world"); cout << s1 << endl;//const char* c_str() const:调用的是string的<<运算符重载函数 cout << s1.c_str() << endl;//调用的是iostream库里面的<<运算符重载 cout << (void*)s1.c_str() << endl;//这里打印的是地址 cout << s1 << endl; cout << s1.c_str() << endl; s1 += '\0'; s1 += '\0'; s1 += "xxxxx"; cout << s1 << endl;//是根据s1的size来进行打印的 cout << s1.c_str() << endl;//打印字符串,遇到'\0'就截止了 //c_str()函数的作用:为c语言提供一个函数接口,更好的兼容。虽然在有些时候功能有些重合 string filename("test.cpp");//此时fopen是没有办法打开string类的 FILE* fout = fopen(filename.c_str(), "r"); if (fout == nullptr) perror("fopen fail"); char ch = fgetc(fout); while (ch != EOF) { cout << ch; ch = fgetc(fout);//不写的话会造成死循环,也就是一直读取同一个字符 } fclose(fout); return 0; }
int main() { string file("string.cpp.tar.zip"); size_t pos = file.rfind('.');//从后向前找 if (pos != string::npos) { string suffix = file.substr(pos);//string substr (size_t pos = 0, size_t len = npos) const:从pos位置开始读取len长度的字符 cout << suffix << endl; } string url("http://www.cplusplus.com/reference/string/string/find/"); cout << url << endl; size_t start = url.find("://");//find找字符串的位置 if (start == string::npos) { cout << "invalid url" << endl; } start += 3; size_t finish = url.find('/', start); string address = url.substr(start, finish - start); cout << address << endl; return 0; }
int main() { std::string str("Please, replace the vowels in this sentence by asterisks."); std::size_t found = str.find_first_of("abcdv");//从头找str中"abcdv"中的任意一个字符就返回下标。find_last_not_of就是从str的最末尾开始向前找 while (found != std::string::npos) { str[found] = '*'; found = str.find_first_of("abcdv", found + 1);//优化防止从头开始找 } std::cout << str << '\n'; string s1("hello world"); string s2("hello world"); s1 == s2; s1 == "hello world"; "hello world" == s1; return 0; }