标准库中的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('.');
//返回的还是string
string 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位置向前找所给字符串里的任意字符后 并返回对应下标