构造操作
接口较多只需记住几个常用的:
(constructor)函数名称 | 功能说明 |
---|---|
string() | 构造空的string类对象,即空字符串 |
string(const char* s) | 用C-string来构造string类对象 |
string(size_t n,char c) | string类对象中包含n个字符c |
string(const string& s) | 拷贝构造函数 |
string s1;//空对象,默认全是\0
string s2("hello world"); 用C格式字符串构造string类对象s2
string s3(5, 'x');//用n个C格式字符构造string类对象
string s4(s3);//拷贝构造
容量操作
函数名称 | 功能说明 |
---|---|
size(重点) | 返回字符串有效字符长度 |
length | 返回字符串有效字符长度 |
capacity(重点) | 返回空间总大小 |
empty | 检测字符串释放为空串,是返回true,否则返回alse |
clear | 清空有效字符串 |
reserve(重点) | 为字符串预留空间 |
resize(重点) | 将有效字符的个数改成n个,多出的空间用字符c填充 |
//测试代码
void test_string()
{
string s1("hello world");
cout << "有效长度为:" << s1.size() << endl;
cout << "空间总大小为:" << s1.capacity() << endl;
cout << "s1是否为空:" << s1.empty() << endl;
cout << s1 << endl << endl;;
s1.clear();
cout << "有效长度为:" << s1.size() << endl;
cout << "空间总大小为:" << s1.capacity() << endl;
cout << "s1是否为空:" << s1.empty() << endl<<endl;
s1.reserve(500);
cout << "预留的空间总大小为:" << s1.capacity() << endl<<endl;
s1.resize(5, 'x');
cout << "有效长度为:" << s1.size() << endl;
cout << "空间总大小为:" << s1.capacity() << endl;
cout << "s1是否为空:" << s1.empty() << endl;
cout << s1 << endl;
}
运行结果为:
注意:
- size()与length()方法底层实现原理完全相同,引入size()的原因是为了与其他容器的接口保持一致,一般情况下基本都是用size()。
- clear()只是将string中有效字符清空,不改变底层空间大小。
- resize(size_t n) 与 resize(size_t n, char c)都是将字符串中有效字符个数改变到n个,不同的是当字符个数增多时:resize(n)用0来填充多出的元素空间,resize(size_t n, char c)用字符c来填充多出的元素空间。注意:resize在改变元素个数时,如果是将元素个数增多,可能会改变底层容量的大小,如果是将元素个数减少,底层空间总大小不变。
- reserve(size_t res_arg=0):为string预留空间,不改变有效元素个数,当reserve的参数小于string的底层空间总大小时,reserver不会改变容量大小。
访问及遍历操作
函数名称 | 功能说明 |
---|---|
operator[](重点) | 返回pos位置的字符,const string类对象调用 |
begin+end | begin获取第一个字符的迭代器 + end获取最后一个字符下一个位置的迭代器,即’‘\0’’ |
rbegin+rend | rbegin获取最后一个字符的反向迭代器 + rend获取第一个字符的前一个位置的反向迭代器 |
范围for | C++11支持更简洁的范围for的新遍历方式 |
//验证重载[]
void test_string()
{
string s1("hello world");
cout <<s1 << endl;
//遍历s1,并讲s1的所有字符加1:
for (int i = 0;i < s1.size();i++)
{
s1[i]++;//通过[]修改字符
}
cout << s1 << endl;
}
//验证正向迭代器(begin和end函数)
void test_string()
{
string s1("hello world");
string::iterator it = s1.begin();
cout << s1 << endl;
while (it != s1.end())
{
(*it)++;
cout << *it << " ";
it++;
}
}
//验证反向迭代器(rbegin和rend函数)
void test_string()
{
string s1("hello world");
cout << s1 << endl;
string::reverse_iterator rit = s1.rbegin();
while (rit != s1.rend())
{
cout << *rit;
rit++;
}
}
//验证范围for————自动迭代,自动判断结束(底层实际为迭代器)
void test_string()
{
string s1("hello world");
cout << s1 << endl;
//依次取s1中每个字符,赋值给ch
for (auto& ch : s1)//auto自动判断类型
{
ch++;
cout << ch << " ";
}
}
注意:若是const迭代器则不能修改字符串的字符!
修改操作
函数名称 | 功能说明 |
---|---|
push_back | 在字符串后尾插字符c |
append | 在字符串后追加一个字符串 |
operator+=(重点) | 在字符串后追加字符串str |
c_str | 返回c格式的字符串 |
find+npos | 从字符串pos位置开始往后找字符c,返回该字符在字符串中的位置 |
rfind | 从字符串pos位置开始往前找字符c,返回该字符在字符串中的位置 |
substr | 在str中从pos位置开始,截取n个字符,然后将其返回 |
insert | 在pos位置插入字符或字符串 |
erase | 删除pos位置上的字符 |
其中append、operator+=、find、rfind有多个重载接口,只需要学习常用的:
//验证push_back、insert、erase、append
//push_back接口:
void push_back (char c);
//append接口:
string& append (const char* s);
string& append (const string& str);
//insert接口:
string& insert (size_t pos, const string& str);
string& insert (size_t pos, const string& str, size_t subpos, size_t sublen);
string& insert (size_t pos, const char* s);
string& insert (size_t pos, const char* s, size_t n);
string& insert (size_t pos, size_t n, char c);
//erase接口:
string& erase (size_t pos = 0, size_t len = npos);
void test_string7()
{
string s1("hello");
cout << s1 << endl;
s1.push_back('-');
s1.append("world");
s1.push_back('-');
string s2("I am a student");
//将s2追加到s1后
s1.append(s2);
cout << s1 << endl;
s1.push_back('-');
s1.insert(s1.size(), "i am 19");
cout << s1 << endl;
s1.erase(0,1);
cout << s1 << endl;
}
//验证operator+=:
//operator接口:
string& operator+= (const string& str);
string& operator+= (const char* s);
string& operator+= (char c);
void test_string()
{
string s1("hello");
cout << s1 << endl;
string s2(" world");
//string& operator+= (const string& str);
s1 += s2;
//string& operator+= (char c);
s1 += '-';
s1 += '-';
//string& operator+= (const char* s);
s1 += "i am a student";
cout << s1 << endl;
}
-
在string尾部追加字符时,s.push_back© / s.append(1, c) / s += 'c’三种的实现方式差不多,一般情况下string类的+=操作用的比较多,+=操作不仅可以连接单个字符,还可以连接字符串。
-
对string操作时,如果能够大概预估到放多少字符,可以先通过reserve把空间预留好。
//验证c_str
//c_str接口:
const char* c_str() const;
void test_string()
{
//简单验证:
string s1("hello");
cout << s1 << endl;
cout << s1.c_str() << endl;
s1 += '\0';
s1 += " world";
//string对象以size为准,c语言字符串以\0为准
cout << s1 << endl;
cout << s1.c_str() << endl;
//实践验证
string filename("test.cpp");
cout << filename << endl;
//返回c语言风格字符串
cout << filename.c_str() << endl;
FILE* fout = fopen(filename.c_str(), "r");
//如果不以c语言字符串做参数则会报错,错误示范:
//FILE* fout = fopen(filename, "r");
assert(fout);
char ch = fgetc(fout);
while (ch!=EOF)
{
cout << ch;
ch = fgetc(fout);
}
}
//验证find和substr,以分割URL(协议+域名+路径)为例
//find的四大接口:
size_t find (const string& str, size_t pos = 0) const;
size_t find (const char* s, size_t pos = 0) const;
size_t find (const char* s, size_t pos, size_t n) const;
size_t find (char c, size_t pos = 0) const;
//substr接口:
string substr (size_t pos = 0, size_t len = npos) const;
void DealUrl(const string& url)
{
size_t pos1 = url.find("://");
if (pos1 == string::npos)
{
cout << "非法url" << endl;
return;
}
//从0位置开始截取pos1个字符
string protocol = url.substr(0, pos1);
cout << protocol << endl;
size_t pos2 = url.find('/',pos1+3);
if (pos2 == string::npos)
{
cout << "非法url" << endl;
return;
}
string domain = url.substr(pos1 + 3, pos2 - pos1 - 3);
cout << domain << endl;
string uri = url.substr(pos2+1);
cout << uri << endl << endl;
}
void test_string()
{
string url1("https://cplusplus.com/reference/string/string/find/");
string url2("https://blog.csdn.net/weixin_73178229?type=lately");
DealUrl(url1);
DealUrl(url2);
}
//验证rfind,以取一个文件的后缀名为例:
//rfind接口:
size_t rfind (const string& str, size_t pos = npos) const;
size_t rfind (const char* s, size_t pos = npos) const;
size_t rfind (const char* s, size_t pos, size_t n) const;
size_t rfind (char c, size_t pos = npos) const;
void test_string() {
string filename("test.cpp.tar.zip");
size_t pos = filename.rfind('.');//从后往前找
if (pos == string::npos)
{
cout << "failed document" << endl;
return;
}
string stuff = filename.substr(pos, filename.size() - pos);
cout << stuff << endl;
}
注意:不是所有的接口都要记,只需会使用
非成员函数
函数名称 | 功能说明 |
---|---|
operator+ | 尽量少用,因为传值返回,导致深拷贝效率低 |
operator>> | 输入运算符重载 |
operator<< | 输出运算符重载 |
getline | 获取一行字符串 |
relational operators | 大小比较 |
void test_string()
{
string s1("hello");
string s2(" world");
string s3 = s1 + s2;
cout << s3 << endl;
cin >> s3;
cout << s3 << endl;
cout << (s1 == s2) << endl;
cout << (s1 > s2) << endl;
}
原因:重载>>使用时,读取到空格或回车便停止读取,所以我们不能使用>>将含有空格的字符串读入到string对象中,需要用到getline函数:
//验证getline
void test_string()
{
string s1("hello");
string s2(" world");
string s3 = s1 + s2;
cout << s3 << endl;
//cin >> s3;
getline(cin,s3);
cout << s3 << endl;
cout << (s1 == s2) << endl;
cout << (s1 > s2) << endl;
}
vs和Linux中g++的string结构的说明
vs下的string结构
注意:下述结构是在32位平台下进行验证,32位平台下指针占4个字节
string总共占28个字节,内部结构稍微复杂一点,先是有一个联合体,联合体用来定义string中字符串的存储空间:
- 当字符串长度小于16时,使用内部固定的字符数组来存放
- 当字符串长度大于等于16时,从堆上开辟空间
union _Bxty
{ // storage for small buffer or pointer to larger one
value_type _Buf[_BUF_SIZE];
pointer _Ptr;
char _Alias[_BUF_SIZE]; // to permit aliasing
} _Bx;
这种设计也是有一定道理的,大多数情况下字符串的长度都小于16,那string对象创建好之后,内部已经有了16个字符数组的固定空间,不需要通过堆创建,效率高。
其次:还有一个size_t字段保存字符串长度,一个size_t字段保存从堆上开辟空间总的容量
最后:还有一个指针做一些其他事情。
故总共占16+4+4+4=28个字节。
g++下string的结构
g++下,string是通过写时拷贝实现的,string对象总共占4个字节,内部只包含了一个指针,该指针将来指向一块堆空间,内部包含了如下字段:
- 空间总大小
- 字符串有效长度
- 引用计数
struct _Rep_base
{
size_type _M_length;
size_type _M_capacity;
_Atomic_word _M_refcount;
};
好之后,内部已经有了16个字符数组的固定空间,不需要通过堆创建,效率高。
其次:还有一个size_t字段保存字符串长度,一个size_t字段保存从堆上开辟空间总的容量
最后:还有一个指针做一些其他事情。
故总共占16+4+4+4=28个字节。
[外链图片转存中…(img-kB9skccx-1685676392182)]
g++下string的结构
g++下,string是通过写时拷贝实现的,string对象总共占4个字节,内部只包含了一个指针,该指针将来指向一块堆空间,内部包含了如下字段:
- 空间总大小
- 字符串有效长度
- 引用计数
struct _Rep_base
{
size_type _M_length;
size_type _M_capacity;
_Atomic_word _M_refcount;
};
- 指向堆空间的指针,用来存储字符串。