标准库中的string类
string类(了解)
string类的文档介绍
注意:在使用string类时,必须包含#include头文件以及using namespace std;
auto和范围for
在了解string的用法前在学习一个知识;
auto关键字
- auto是作为一个新的类型指示符来指示编译器,auto声明的变量必须由编译器在编译时期推导而得。
- 用auto声明指针类型时,用auto和auto*没有任何区别,但用auto声明引用类型时则必须加&
- auto不能作为函数的参数,可以做返回值,但是建议谨慎使用(尽量不要使用,只要套娃,就很难使用。)
- auto不能直’接用来声明数组
- 在同一行声明多个变量时,这些变量必须是相同的类型,否则编译器将会报错,因为编译器实际只对第一个类型进行推导,然后用推导出来的类型定义其他变量。
相关解释与代码
代码内容:
void Test3()
{int a = 10;auto b = a;auto c = 'a';auto d = func1();// 编译报错:rror C3531: “e”: 类型包含“auto”的符号必须具有初始值设定项// 这点与引用 十分相似//auto e;cout << typeid(b).name() << endl;cout << typeid(c).name() << endl;cout << typeid(d).name() << endl;int x = 10;auto y = &x;auto* z = &x;auto& m = x;cout << typeid(x).name() << endl;cout << typeid(y).name() << endl;cout << typeid(z).name() << endl;auto aa = 1, bb = 2;// 编译报错:error C3538: 在声明符列表中,“auto”必须始终推导为同一类型//auto cc = 3, dd = 4.0;// 编译报错:error C3318: “auto []”: 数组不能具有其中包含“auto”的元素类型//auto array[] = { 4, 5, 6 };}
分析解释
“auto”的符号必须具有初始值设定项
因为,auto本来就是 编译器通过对相关变量的推导,才能得到类型。没有初始化,怎么推导;
为什么不能做参数 但能做返回值
这是就祖师爷设计的问题,一些遐思。而且做返回值若遇到那种 函数套函数的,在用变量接收时就难以辨认,这个变量到底是什么类型
auto text1()
{double a = 2.0;return a;
}auto text2()
{return text1();
}auto text3()
{return text2() + 3;
}
范围for
- 对于一个有范围的集合而言,由程序员来说明循环的范围是多余的,有时候还会容易犯错误。因此C++11中引入了基于范围的for循环。for循环后的括号由冒号“ :”分为两部分:第一部分是范围内用于迭代的变量,第二部分则表示被迭代的范围,自动迭代,自动取数据,自动判断结束。
- 范围for可以作用到数组和容器对象上进行遍历
- 范围for的底层很简单,容器遍历实际就是替换为迭代器,这个从汇编层也可以看到。
底层
相关解释与代码
代码内容:
void Test9()
{string s2("hello world");string::iterator it = s2.begin();while (it != s2.end()){*it += 2;cout << *it << " ";++it;}cout << endl;//for (auto e : s2)//{// e -= 2;// cout << e << " ";//}//cout << endl;for (auto& e : s2){e -= 2;cout << e << " ";}cout << endl;
}
分析解释
Auto 与模板相似 不一样地方也有;(相同的地方,都是由编译器推导)
范围for的注意点;
Auto name : 返回的是值 相当于浅拷贝
& 则是返回引用 就能改变
一般来说 只对于非常大的对象 用引用才能增大效率
小的 影响十分小
string类的常用接口说明
这里只简绍几种常见的,具体的可以去string类的文档自行查找哦。
string类对象的常见构造
点击观看全部内容
第一次接触看这里库里的内容,第一步就是猜测,猜这个函数应该怎么应用;然后看具体是什么在敲代码验证;
相关解释与代码
代码内容:
void Test4()
{string s0("abcdefg");//default (1)string();string s1;//from c - string(4)string(const char* s);string s4("aaaaaaaaa");//substring (3) string(const string & str, size_t pos, size_t len = npos);string s3(s0, 0, 1);//copy (2) string(const string & str);string s2(s0);//from sequence(5) string(const char* s, size_t n);string s5("aaaa", 2);//fill(6) string(size_t n, char c);string s6('a', 6);//range(7) template <class InputIterator>// string(InputIterator first, InputIterator last);string s7(++s0.begin(), s0.end()--);
}
分析解释
可以在main() 函数里打印,验证猜想
s7是迭代器的用法,如果这里看不懂可以看完下面的” string类对象的访问及遍历操作(iterator)“ 再来看,会有更加深刻的理解哦;
operator会在后面的深浅拷贝里仔细分析哦;
string类对象的容量操作(capacity)
相关解释与代码
代码内容:
void Test5()
{string s1("abcdefg");string s2(s1);cout << s1.size() << endl;cout << s1.length() << endl;cout << s1.capacity() << endl << endl;cout << s1.empty() << endl;s1.clear();cout << s1.empty() << endl;cout << s1.size() << endl;cout << s1.capacity() << endl << endl;string s3;//reserve 的好处 提前开好空间 在插入时不用在扩容 提升了效率s2.reserve(sizeof(s2));//operator = 重载 后面深浅拷贝会说s3 = s2;s2.resize(20, 'v');cout << s2 << endl;cout << s2.size() << endl;cout << s2.capacity() << endl;std::string str(100, 'x');std::cout << "1. capacity of str: " << str.capacity() << '\n';str.resize(10);std::cout << "2. capacity of str: " << str.capacity() << '\n';str.shrink_to_fit();std::cout << "3. capacity of str: " << str.capacity() << '\n';
}
分析解释
打印结果
注意点1:
size 和length 的用法是一摸一样的;但为什么名字不一样呢?
因为c++ 的历史太过久远,原本是用length的 但是后来为了与其他容器 保证一至;所以都用了size; 这一点就体现的面对程序的封装;
注意点2:
为啥capacity 不一样呢?从库里的解释(如下图)可知,capacity 的话题等于 或 大于 字符长度的;这完全取决于编译器,c++没有严格的要求;
这里就可以看看VS2019中 string 是怎么开辟空间的
可以看出来,就第一次改变 是大概两倍,后面都接近 1.5倍。(不精确的原因主要是容器内容 不算字符串最后的’/0‘ ,但会为它开辟空间;
还有就是
VS2019 中string 的实现 由两个组成,_Buf _Ptr,
原理大概就是:
先在栈上开辟 buf 大小为 16 字节;若大小超过,就删除移到 堆上建立ptr(双倍的buf),然后后面再扩容就是 1.5倍
注意点3:
reverse
代码及其打印
string类对象的访问及遍历操作(iterator)
先说,迭代器是类似与指针的一个东西,虽然有的容器迭代器底层就是指针实现的,但是并不是全部。
相关解释与代码
代码内容:
void Test8()
{//string s1("hello worldxxxxxxxxxxxxx");//auto it = s1.begin();//cout << typeid(s1).name() << endl;string s2("hello world");string::iterator it = s2.begin();while (it != s2.end()){*it += 2;cout << *it << " ";++it;}cout << endl;string::reverse_iterator rit = s2.rbegin();while (rit != s2.rend()){cout << *rit << " ";++rit;}cout << endl;const string s3("hello world");//同时体现了 auto的方便性;//string::const_iterator cit = s3.begin();auto cit = s3.begin();while (cit != s3.end()){//*cit += 2;cout << *cit << " ";++cit;}cout << endl;//string::const_reverse_iterator rcit = s3.rbegin();auto rcit = s3.rbegin();while (rcit != s3.rend()){// *rcit += 2;cout << *rcit << " ";++rcit;}cout << endl;
}
分析解释
总结
迭代器有四种: 普通迭代器 const迭代器
普通反向迭代器 const 反向迭代器
string类对象的读取
string 的底层地址是连续的 因此能源【】来读;像list就不行
一个可读可写的接口
一个 只读接口 看看权限大小用合适的 string 有很多类型都是这样
相关解释与代码
代码内容:
void Test11()
{string s1("hello world");s1.back() = '!';cout << s1 << endl;//s1.front() = 'aaaa';//front 的返回类型是 char 这种写法虽然能过 但只取第一个 最好不写这种s1.front() = 'a';cout << s1 << endl;for (unsigned i = 0; i < s1.size(); ++i){std::cout << s1.at(i);}cout << endl;for (unsigned i = 0; i < s1.size(); ++i){std::cout << s1[i];}cout << endl;
}
分析解释
三种遍历方式
- 下标+[] 2. 迭代器 3. 范围for (虽然at能遍历,但是不常用且和【】差不多,这里就不列举了)
string类对象的修改操作
这里要提醒的点就是,这里的参数类型很多,要注意不能弄错了;
相关解释与代码
代码内容:
string类非成员函数
相关代码和参考
代码内容:
分析解释
这里主要提一下 getline,其他的基本在实现其他的时候也用到过,都知道怎么使用;
getline 就是 改变 插入 流出的方式;
举一个列子 编译器默认 cin 时,输入‘ ’ (空格)是要记录到下一个数据上的;但如果用getline重定义cin,就可以自己定输入那个符号时,接下来的会插入下一个数据中
string类和对象的操作函数
这些函数各有各的优缺点,虽然string的操作接口很多,但也不是都有用,很恶心
标红的是自我感觉比较常用的,其他也了解了解更好;
参考代码及打印
void SplitFilename(const string& s1)
{cout << "split" << s1 << endl;size_t found = s1.find_last_of("\\/");cout << "path:" << s1.substr(0, found) << endl;cout << "name:" << s1.substr(found + 1) << endl;
}void Test15()
{string s2("asdfg");cout << s2.data() << endl;string s("test.cpp.zip.zzp");size_t pos = s.find('.');//返回的还是stringstring suffix = s.substr(pos);cout << suffix << endl;pos = s.rfind('.');suffix = s.substr(pos);cout << suffix << endl;std::string str("Please, replace the vowels in this sentence by asterisks.");std::cout << str << '\n';std::size_t found = str.find_first_of("abc");while (found != std::string::npos){str[found] = '*';found = str.find_first_of("abc", found + 1);}std::cout << str << '\n' << '\n';std::string st1("Please, replace the vowels in this sentence by asterisks.");std::size_t found1 = st1.find_first_not_of("abc");while (found1 != std::string::npos){st1[found1] = '*';found1 = st1.find_first_not_of("abc", found1 + 1);}std::cout << st1 << '\n';//在流输入过程中,单独的\ 会被编译器当作操作符,若要使用需要 在\前加 \ ;std::string ss1("/usr/bin/man");std::string ss2("D:\\qq\\QQMusic\\AssGenerator");//如此应用,可以很容易找到所在文件架//应用的一种 文件名 与路径分离SplitFilename(ss1);SplitFilename(ss2);
}
参考打印结果
总结
这里解释一下这里的find
- find: 从pos位置向后找到第一个字符 并返回对应下标
- rfind: 从pos位置向后找到最后一个字符 并返回对应下标
- find_frist_of:从pos位置向后找所给字符串里的任意字符后 并返回对应下标
- find_last_of:从pos位置向前找所给字符串里的任意字符后 并返回对应下标
- find_frist_not_of:从pos位置向后找所给字符串里的任意没有的字符后 并返回对应下标
- find_last_not_of :从pos位置向前找所给字符串里的任意字符后 并返回对应下标
这个是any of 是任意一个 字符(“abcd”)找到中任意一个字符 就返回Find(“a”) 只有一个