为什么要学习string类
C语言的字符串
C语言中,字符串是以’\0’结尾的一些字符的集合,为了操作方便,C标准库中提供了一些str系列的库函数,但是这些库函数与字符串是分离开的,不太符合OOP的思想,而且底层空间需要用户自己管理,稍不留神就有可能越界访问。
并且在OJ中,有关字符串的题目基本以string类的形式出现,而且在常规工作中,为了简单、方便、快捷,基本都使用string类,很少有人去使用C库中的字符串操作函数。
字符的一些补充知识
众所周知英文字符等符号在计算机中采用ASCII码的方式存储。
那么对于中文,日文,其他国家的文字计算机又该如何存储呢?对于中文等其他字符,ASCII码则表示不了,因为一个字节存不下,对于这些字符,则采用Unicode编码标准:它为每种语言中的每个字符设定了统一并且唯一的二进制编码,以满足跨语言、跨平台进行文本转换、处理的要求。Unicode标准下每个中文是两个字节,这样就能存储大部分的中文了。
同时也有新的字符型wchar_t来存储两个字节的字符,它能更好的表示unicode编码
int main()
{
char ch1 = 'a';
char ch2 = 97;
cout << ch1 << endl;
cout << ch2 << endl;
char str1[] = "中国";
cout << strlen(str1) << endl;
wchar_t wch;
char ch;
cout << sizeof(wch) << endl;
cout << sizeof(ch) << endl;
return 0;
}
在正式讲string之前我给大家推荐一个网站: string类
这个是C++的官方文档,如果对于C++STL容器或者其他不清楚的地方就可以去这上面查一下。
标准库中的string类
string类
- 字符串是表示字符序列的类
- 标准的字符串类提供了对此类对象的支持,其接口类似于标准字符容器的接口,但添加了专门用于操作单字节字符字符串的设计特性。
- string类是使用char(即作为它的字符类型,使用它的默认char_traits和分配器类型(关于模板的更多信息,请参阅basic_string)。
- string类是basic_string模板类的一个实例,它使用char来实例化basic_string模板类,并用char_traits和allocator作为basic_string的默认参数(根于更多的模板信息请参考basic_string)。
- 注意,这个类独立于所使用的编码来处理字节:如果用来处理多字节或变长字符(如UTF-8)的序列,这个类的所有成员(如长度或大小)以及它的迭代器,将仍然按照字节(而不是实际编码的字符)来操作。
总结:
-
string是表示字符串的字符串类 -
该类的接口与常规容器的接口基本相同,再添加了一些专门用来操作string的常规操作。 -
string在底层实际是:basic_string模板类的别名,typedef basic_string<char, char_traits, allocator> string; -
不能操作多字节或者变长字符的序列。 在使用string类时,必须包含#include头文件以及using namespace std;
string类对象的常见构造函数
在C++98中有以下7种构造函数
(constructor)函数名称 | 功能说明 |
---|
string() (重点) | 构造空的string类对象,即空字符串 | string(const char* s) (重点) | 用C-string来构造string类对象 | string(const stirng& s) (重点) | 拷贝构造函数 | string(size_t n,char c) | string类对象中包含n个字符c | string(const string& str, size_t pos, size_t len = npos) | 从str对象中的pos位置开始截取len个长度的字符,如果len>str的长度,那么截取完str就结束 | string(const char* s,size_t n) | 从s指向的字符数组中复制前n个字符 |
int main()
{
string s1;
string s2("hello world");
string s3(s2);
string s4 = "hello world!!!";
cout << s1 << endl;
cout << s2 << endl;
cout << s3 << endl;
string s5(s4, 3, 5);
cout << s5 << endl;
char* url = "http://www.cplusplus.com/reference/string/string/string/";
string s6(url, 4);
cout << s6 << endl;
string s7(10, 'x');
cout << s7 << endl;
s7 = s2;
cout << s7 << endl;
return 0;
}
string的三种遍历方式
一、下标[]
string s2("hello world");
for (size_t i = 0; i < s2.size(); i++)
{
cout << s2[i] << " ";
}
cout << endl;
这里的size()是一个公共成员函数,返回string类对象的长度,和length()相同
通过上面图片可以看到库里面重载了两个operator[]函数,一个是可读可写的,一个是只读的。因此这里不光可以遍历s2,还可以修改s2里面的值。
string s2("hello world");
for (size_t i = 0; i < s2.size(); i++)
{
cout << s2[i] << " ";
}
cout << endl;
for (size_t i = 0; i < s2.size(); i++)
{
s2[i] = 'x';
}
cout << endl;
for (size_t i = 0; i < s2.size(); i++)
{
cout << s2[i] << " ";
}
cout << endl;
二、迭代器遍历
迭代器iterator是string中的类,所以要加域作用限定符
string s2("hello world");
string::iterator it = s2.begin();
while (it != s2.end())
{
cout << *it << " ";
++it;
}
cout << endl;
[begin(),end()) end()返回的不是最后一个数据的位置,返回的是最后一个数据的下一个位置,需要要注意的是,C++中凡是给迭代器一般都是给的[)左闭右开的区间。
这个时候可能就会有人问了:那么迭代器的意义是什么呢?
迭代器意义:像string,vector支持[]遍历,但是list,map等等容器不支持下标[]我们就要用迭代器遍历,所以迭代器是一种统一使用的方式。
vector<int> v = { 1, 2, 3, 4 };
vector<int>::iterator vit = v.begin();
while (vit != v.end())
{
cout << *vit << " ";
++vit;
}
cout << endl;
list<int> lt = { 1, 2, 3, 4 };
list<int>::iterator ltit = lt.begin();
while (ltit != lt.end())
{
cout << *ltit << " ";
++ltit;
}
cout << endl;
反向迭代器
string s3("123456");
string::iterator it3 = s3.begin();
while (it3 != s3.end())
{
cout << *it3 << " ";
++it3;
}
cout << endl;
string::reverse_iterator rit = s3.rbegin();
while (rit != s3.rend())
{
cout << *rit << " ";
++rit;
}
cout << endl;
三、范围for
C++11中提供范围for,特点:写起来简洁
依次取容器中的数据,赋值给e,自动判断结束
范围for实现的本质就是迭代器,因此也支持其他容器的遍历
string s3("123456");
vector<int> v = { 1, 2, 3, 4 };
list<int> lt = { 1, 2, 3, 4 };
for (auto&e : s3)
{
e += 1;
}
cout << endl;
for (auto e : s3)
{
cout << e << " ";
}
cout << endl;
for (auto x : v)
{
cout << x << " ";
}
cout << endl;
for (auto x : lt)
{
cout << x << " ";
}
cout << endl;
string类对象的修改操作
函数名称 | 功能说明 |
---|
operator+=str(重点) | 在字符串末尾追加一个字符串 | push_back(ch) | 在字符串末尾插入一个字符 | append(str) | 在字符串末尾追加一个字符串 | insert(pos,str) insert(pos,n,ch) | 在pos位置插入一个字符串,在pos位置插入n个ch字符。尽量少用insert,因为底层实现是数组,头部或者中间插入需要挪动数据 | erase(pos,len) | 从pos位置开始删除len个长度的字符 如果不给pos与len则默认从开头的位置向后全删除 | pop_back(ch) | 在字符串末尾删除一个字符 |
int main()
{
string s1;
s1.push_back('h');
s1.push_back('e');
s1.push_back('l');
s1.push_back('l');
s1.push_back('o');
s1.append("world");
cout << s1 << endl;
string s2("!!!");
s1.append(s2.begin(), s2.end());
cout << s1 << endl;
s1 += ' ';
s1 += "凌峰";
s1 += s2;
cout << s1 << endl;
s1.insert(0, "x");
cout << s1 << endl;
s1.insert(3, "yyyy");
cout << s1 << endl;
s1.insert(0, "yyyy");
cout << s1 << endl;
s1.erase(0, 1);
cout << s1 << endl;
s1.erase(0, 3);
cout << s1 << endl;
s1.erase(3, 10);
cout << s1 << endl;
s1.erase(3);
cout << s1 << endl;
}
string类对象的容量操作
函数名称 | 功能说明 |
---|
size(重点) | 返回字符串有效字符长度 | length | 返回字符串有效字符长度 | capacity | 返回空间总大小 | empty (重点) | 检测字符串是否为空串,是返回true,否则返回false | clear (重点) | 清空有效字符(’\0’不是有效字符,它是标识字符) | reserve (重点) | 为字符串预留空间,不改变有效元素个数,当reserve的参数小于string的底层空间总大小时,reserver不会改变容量大小。 | resize (重点) | 将有效字符的个数变成n个,多出的空间用字符c填充,如果不给字符c的话默认是’\0’。如果有效元素的个数是变大的,那么此时capacity有可能会改变,如果是有效元素的个数是减少的,那么capacity大小不变。 |
string s1;
cout << "size:" << s1.size() << endl;
cout << "capacity:" << s1.capacity() << endl;
cout << s1 << endl;
s1.resize(20, 'x');
cout << "size:" << s1.size() << endl;
cout << "capacity:" << s1.capacity() << endl;
cout << s1 << endl;
string s2("hello world");
s2.resize(20, 'x');
cout << s2 << endl;
cout << "size:" << s2.size() << endl;
cout << "capacity:" << s2.capacity() << endl;
s2.resize(5);
cout << s2 << endl;
cout << "size:" << s2.size() << endl;
cout << "capacity:" << s2.capacity() << endl;
string s3;
s3.resize(10);
cout << "size:" << s3.size() << endl;
cout << "capacity:" << s3.capacity() << endl;
s3.reserve(40);
cout << "size:" << s3.size() << endl;
cout << "capacity:" << s3.capacity() << endl;
下面我们再来通过代码来看一下resize与reserve的区别
string s4;
s4.resize(127);
int oldCp = s4.capacity();
for (char ch = 0; ch < 127; ++ch)
{
s4 += ch;
if (oldCp != s4.capacity())
{
cout << "增容:" << oldCp << "->" << s4.capacity();
oldCp = s4.capacity();
}
}
cout << s4 << endl;
resize是把对象的有效字符个数变成n个,而+=ch是在对象的末尾追加字符ch,因此后面空间不够了还是需要增容的。
string s4;
s4.reserve(127);
int oldCp = s4.capacity();
for (char ch = 0; ch < 127; ++ch)
{
s4 += ch;
if (oldCp != s4.capacity())
{
cout << "增容:" << oldCp << "->" << s4.capacity();
oldCp = s4.capacity();
}
}
cout << s4 << endl;
reserve是为string对象预留空间,我们如果知道这次操作需要多大的空间,那么只需要reserve一下我们就不需要增容了。
string类对象的查找等一些其他操作
函数名称 | 功能说明 |
---|
find + npos(重点) | 从字符串pos位置开始从前往后找一个字符或者字符串,返回该字符或者字符串第一次在当前字符串中出现的位置 | rfind | 从字符串pos位置开始从后往前找一个字符或者字符串,返回该字符或者字符串第一次在当前字符串中出现的位置 | c_str(重点) | 返回C格式字符串 | substr | 在str中从pos位置开始,截取n个字符,然后将其返回 | getline(非成员函数) | 获取一行字符串 | operator>>(非成员函数) (重点) | 输入运算符重载 | operator<<(非成员函数) (重点) | 输出运算符重载 |
下面再来说一下string的成员函数c_str()
string s1("hello world");
cout << s1 << endl;
cout << s1.c_str() << endl;
s1.resize(20);
s1 += "!!!!";
cout << s1 << endl;
cout << s1.c_str() << endl;
cout << strlen(s1.c_str()) << endl;
cout << s1.size() << endl << endl;
- 调用内置类型的operator<<(cout,const char* str)输出字符串的时候遇到’\0’就停止了,
- 调用自定义类型的operator<<(cout,const string& s) 输出字符串的时候遇到‘\0’不会停止,它会将该字符串的内容全部都打印出来
大家可能会比较好奇string的find操作运用在哪些场景下呢?
一、假设要求取出文件名的后缀
string filename = "test.txt.zip";
size_t pos = filename.rfind('.');
if (pos != string::npos)
{
string suff(filename, pos);
cout << suff << endl;
}
二、要求写一个程序分别取出域名和协议名
string GetDomain(const string& url)
{
size_t pos = url.find("://");
if (pos != string::npos)
{
size_t start = pos + 3;
size_t end = url.find('/',start);
if (end != string::npos)
{
return url.substr(start, end - start);
}
else
{
return string();
}
}
else
{
return string();
}
}
string GetProtocol(const string& url)
{
size_t pos = url.find("://");
if (pos != string::npos)
{
return url.substr(0, pos - 0);
}
else
{
return string();
}
}
int main()
{
string ur11 = "http://www.cplusplus.com/reference/string/string/rfind/";
string ur12 = "https://tower.im/users/sign_in";
string ur13 = "tower.im/users/sign_in";
cout << GetDomain(ur11) << endl;
cout << GetProtocol(ur11) << endl;
cout << GetDomain(ur12) << endl;
cout << GetProtocol(ur12) << endl;
}
如果我们想输入"asadg bc"这样的字符串应该怎么办呢?
scanf,cin,gets这些好像都不能帮我们解决问题,因为当他们读到空格或者回车键的时候就会停止读取
那么有什么办法来帮我们读取它呢
下面再来说一个getline函数:获取一行字符串
int main()
{
string s1;
getline(cin,s1);
cout << s1 << endl;
}
可以看到通过使用getiline函数就达到了我们想要的结果
|