在c语言中,我们想要记录字符串需要创建一个字符串的数组,而c++则提供了另一种方式;
也就是这篇博客所说的string类;
目录
size() / length() / capacity() 函数
reserve(size_t n) / resize(size_t n, char c) 函数
string类
#include<string>
作为字符串数组的升级版,string类自然也有它的独特之处——可变长数组;
c语言中的字符串数组只能在创建的时候定好初始的长度,数据长度不能超过数组长度;
而string类则没有这样的顾虑,虽然string变量都有自己的初始长度,但是它内部的成员函数会在输入数据超过初始长度的时候,会自动开辟空间,因此可以不用考虑数组长度;
此外,c++考虑到需要兼容c语言,也给string类重载了一个类似于c语言创建字符串数组的构造方式
但是这里有一点需要注意的是,虽然string类这里的创建方式和c语言的字符串数组一样;
但是它还是有很多不同的;
首先就是它并不是以 ‘\0’ 作为结束的标志;
我们可以看下它内部:
可以看到 s1 并不是以 '\0’ 作为结束标志的;
好,说了那么多 string类和 c语言数组的不同,那么来讲讲string类特有的功能吧;
string类的成员函数
string类中实现了许多成员函数,并且每种函数根据不同的情况有不同的重载;
这里我们就认识一下用的比较多的函数吧;
string的构造函数
函数名称 | 功能 |
string() | 创造一个空的string对象 |
string(const char* s) | 用一个字符串创建string对象 |
string(size_t n,char c) | 用 n 个 c 字符创建string对象 |
string(const string& s) | 用string对象拷贝构造一个string对象 |
void test3()
{
string s1;
string s2("abcd");
string s3(5, 'c');
string s4(s3);
cout << "s1 = " << s1 << endl;
cout << "s2 = " << s2 << endl;
cout << "s3 = " << s3 << endl;
cout << "s4 = " << s4 << endl;
}
我们能够看到,使用不同方式都成功创建了对应的字符串;
string的容量操作函数
函数名 | 功能 |
size() | 返回字符串有效字符长度 |
length() | 返回字符串有效字符长度 |
capacity() | 返回空间总大小 |
empty() | 检测是否为空 |
clear() | 清空有效字符 |
reserve(size_t n = 0) | 为字符串开辟空间 |
resize(size_t n)/(size_t n ,char c) | 更改有效字符为n,多出的用字符c填充 |
c++中对于string的容量操作的函数可不少,我们先来一个一个学习吧;
size() / length() / capacity() 函数
void test4()
{
string s1 = "hello world!";
cout << s1.size() << endl;
cout << s1.length() << endl;
cout << s1.capacity() << endl;
}
首先是string的检查容量大小的函数;
我们发现 size() 函数和 length() 函数的返回值其实是一样的;
这是因为c++中,其他容器中的检查容量大小的函数都是 size() ,因此又引入了 size() 函数;
此外,我们发现 capacity() 函数的返回值和其他两个函数不一样,这是由string类的实现形成的;
string类的实现可以看做是一个char类型的指针(实际上比这更复杂一点,但是为了好理解可以视为是char类型的指针),并且内部除了 char* 指针,还有一个 size 和 capacity 变量;
而它们三者的关系类似下图:
实际上string对象开辟的大小为 capacity 这么大,而内部存储的字符串大小为size大小;
这就是为什么两个函数返回值不同的原因;
而 clear() 函数和empty()函数只在乎size()的大小;
而 size 和 capacity 的大小也是有成员变量可以修改的;
reserve(size_t n) / resize(size_t n, char c) 函数
这两个函数都是针对 capacity 变量和 size 变量而出现的函数;
我们先看看 resize 函数;
void test5()
{
string s1 = "hello world!";
cout <<"s1.size:"<< s1.size()<<' ' <<"s1.capacity:"<<s1.capacity()<<' ' << s1 << endl;
s1.resize(5);
cout << "s1.size:" << s1.size() << ' ' << "s1.capacity:" << s1.capacity()<<' ' << s1 << endl;
}
我们发现,resize() 更改了有效字符的长度,而 capacity 并未改变;
并且就算我们将 size 改回原来的长度,也无法恢复原来的数据;
而 reserve() 函数则不用多说,自然是修改 capacity 的大小了,但是它改变得比较奇怪;
void test7()
{
string s1 = "hello world!";
cout << "s1.capacity : " << s1.capacity() << ' ' << s1 << endl;
s1.reserve(20);
cout << "s1.capacity : " << s1.capacity() << ' ' << s1 << endl;
s1.reserve(10);
cout << "s1.capacity : " << s1.capacity() << ' ' << s1 << endl;
}
我们发现,命名我们用 reserve 函数将 capacity 更改到20,但是实际上它的大小为31;
而更改到10则没变化;
这是为什么呢?
我们先去官网上看看reserve函数的描述:
大致意思是:
如果 n 比string 的capacity 大,那么编译器就会扩大容器的大小到 n 或者更大,而其他情况下,就根据容器的实现进行优化;
也就是说,reserve 函数并非根据你输入的数据来扩大容量,而是根据编译器内部的实现来进行扩大;
因此这就是为什么我们扩大到20的时候,会扩大到31;
而至于为什么我们想要缩小到10的时候,则没变呢?
虽然官网描述中说自由优化,实际上大部分编译器的实现当 n 小于 capacity 的时候,并不会对capacity 进行修改;
此外,官网的描述中,规定这个函数不会影响字符串的内容,因此当 n 小于 capacity 的时候就不作修改了;
string类对象的访问及遍历操作
函数名称 | 功能 |
operator[] (size_t pos) | 返回 pos 位置的字符 |
begin()+end() | 迭代器访问 |
rbegin()+end() | 反向迭代器访问 |
范围for | c++支持的新型for循环访问 |
通过之前的实践,我们都知道 string 类对象都可以直接通过 cout 直接输出;
但是c++还提供了其他方法遍历 string 类对象;
operator[] (size_t pos)
首先就是运算符重载的 operator[] 访问了;
void test8()
{
string s1 = "hello world!";
for (int i = 0; i < s1.size(); i++)
{
cout << s1[i];
}
cout << endl;
}
这种访问方式就和 c 语言中访问字符数组一样了;
而这样的访问方式实际上是在类内部实现的;
begin()/end()/rbegin()/rend()
c++中有各种各样的容器,由于底层结构不同,所以各种容器的访问方式也不同;
但是c++为了保证容器的统一性,而创造出了迭代器——iterator,来保证容器访问方式的一致性;
迭代器——iterator
每个容器里面都有 iterator ,它是一个类似于指针的东西,用户通过迭代器能够访问容器的内容;
而每一个容器的 begin() 和 end() 函数都是为迭代器服务的;
接下来我们看看迭代器的使用方式吧!
void test9()
{
string s1 = "hello world!";
string::iterator is1 = s1.begin();
while (is1 != s1.end())
{
cout << *is1;
is1++;
}
cout << endl;
}
迭代器并非通过下标访问那样,只用比较 i 和 size 之间的大小;
迭代器只能通过和容器内部的 end() 返回值来比较,看是否到达变量的尾部;
并且,迭代器也有 const 类型的迭代器,用于被 const 修饰容器变量;
void test9()
{
const string s1 = "hello world!";
string::const_iterator is1 = s1.begin();
while (is1 != s1.end())
{
cout << *is1;
is1++;
}
cout << endl;
}
rbegin() 和 rend()
前面说过,这两个函数属于反向迭代器,而它的用法和普通迭代器一模一样;
void test9()
{
string s1 = "hello world!";
string::reverse_iterator is1 = s1.rbegin();
while (is1 != s1.rend())
{
cout << *is1;
is1++;
}
cout<<endl;
}
通过反向迭代器,我们能够反向遍历容器内容的内容,其使用方式和普通迭代器是一样的;
范围for
范围for应该算是比较通用的,就不用过多讲解了;
void test9()
{
string s1 = "hello world!";
string::reverse_iterator is1 = s1.rbegin();
for (auto e : s1)
{
cout << e;
}
cout<<endl;
}
string类对象修改操作
函数名 | 功能 |
push_back(char c) | 在字符串后面添加c |
append() //有多种重载 | 在字符串后面追加字符串 |
operator+= | 在字符串后面追加字符串str |
c_str() | 返回c格式字符串 |
find()+npos | 在pos位置往后找字符c,并返回所在位置 |
rfind() | 在pos位置往前找字符c,并返回所在位置 |
erase() | 删除对应位置的元素,并且返回该元素的下一个位置 |
substr | 在 str 中从pos位置截取 n 个字符,并返回 |
push_back(char c)
这个函数一次只能在字符串尾端放一个字符;
可以直接放,也能够放char类型的变量;
void test10()
{
string s1 = "hello world!";
char a = 'a';
cout << s1 << endl;
s1.push_back('!');
s1.push_back('!');
s1.push_back('!');
s1.push_back('!');
s1.push_back(a);
cout << s1 << endl;
}
这里可以看到成功的在尾端放入了字符;
append(const char * s)
append(const char* s ,size_t n)
append(const string & s)
append(const string& s,size_t subpos,size_t sublen);
append(size_t n ,char c)
template<class InputIterator>
append(InputIterator first,InputIterator last)
append这个函数有比较多的重载,接下来我们看看这些重载都是怎么用的吧:
void test10()
{
string s1 = "hello world!";
cout << s1 << endl;
//append(const char * s)
s1.append("!!!");//添加!!!到s1尾部
cout << s1 << endl;
//append(const char* s ,size_t n)
s1.append("++++++",5);//有6个 + ,但只加了5个上去
s1.append("@", 5);//n为5,但是由于字符串只有一个@,因此只加一个@
cout << s1 << endl;
string s2 = "how are you?";
//append(const string & s)
s1.append(s2);//将s2添加上去
cout << s1 << endl;
//append(const string& s,size_t subpos,size_t sublen);
s1.append(s2, 4,3);//在 s2 中从 subpos位置开始添加sublen长度的字符串到s1中
cout << s1 << endl;
//append(size_t n ,char c)
s1.append(5, '*');//添加n个c到s1中
cout << s1 << endl;
//template<class InputIterator>
//append(InputIterator first,InputIterator last)
string s3 = "goodbye!";
s1.append(s3.begin(), s3.begin() + 3);//将s3中从begin()位置开始到begin()+3位置的字符添加到s1中
cout << s1 << endl;
}
用法如上
operator+=
相比于 append ,+= 的重载就比较少;
它的用法就是单纯的将 += 右边的字符串添加到左边字符串的尾端;
也是比较常用的一种接口;
void test11()
{
string s1 = "hello world!";
s1 += "!!";
cout << s1 << endl;
string s2 = "how are you?";
s1 += s2;
cout << s1 << endl;
s1 += '#';
cout << s1 << endl;
}
const char* c_str() const
c_str()是一个普通的函数,就是返回string对象中的c字符串;
void test11()
{
string s1 = "hello world!";
cout << s1.c_str() << endl;
}
find(const string& s,size_t pos = 0)
find(const char* s,size_t pos = 0)
find(const char* s,size_t pos,size_t n)
find(char c,size_t pos = 0)
该函数就是在对象找寻找对应字符的位置,并且返回,若是没找到就会返回-1;
void test11()
{
string s1 = "hello world!";
string s2 = " wo";
//find(const string& s,size_t pos = 0)
int pos = s1.find(s2);//默认从0位置开始找
cout << pos << endl;
pos = s1.find(s2, 3);//从3位置开始找
cout << pos << endl;
//find(const char* s,size_t pos = 0)
pos = s1.find("llo", 0);//从0位置开始找
cout << pos << endl;
//find(const char* s,size_t pos,size_t n)
pos = s1.find("ld!",0,5);//从0位置开始找,只找5个位置
cout << pos << endl;
//find(char c,size_t pos = 0)
pos = s1.find('d', 0);//从0位置开始找
cout << pos << endl;
}
rfind(const string& s,size_t pos = npos)
rfind(const char* s,size_t pos = npos)
rfind(const char* s,size_t pos,size_t n)
rfind(char c,size_t pos = npos)
rfind 和 find 的使用方式一样,只不过是向前寻找,就不做多解释了;
而上面的 npos 实际上就是表示 string 字符串的尾部,因为 rfind 是向前寻找,因此缺省值给的尾部值;
erase(iterator p)
该函数会删除p位置的元素,然后将后面的元素一个一个往前移,然后返回p元素的下一个元素的位置;
void test1()
{
string s1 = "abcd";
string::iterator is1 = s1.begin();
is1 = s1.erase(is1);
cout << *is1 << endl;
}
当使用erase函数的时候,若是在erase函数前面容器扩容了,就会导致迭代器失效
因此需要更新迭代器位置;
substr(size_t pos = 0,size_t len = npos)
void test12()
{
string s1 = "hello world!";
cout<<s1.substr(0,5)<<endl;
}
这个函数就是在字符串中从 pos 位置截取 len 长度的字符并返回;
不过原字符串不会消失;
string的其他函数
函数名 | 功能 |
operator+ | 将两个string或者字符串加起来并返回 |
operator>> | 整体输入string类 |
operator<< | 整体输出string类 |
getline() | 以行接收string类 |
relational_operators | 比较大小 |
operator+
void test13()
{
string s1 = "hello world!";
cout << s1 << endl;
s1 = s1 + "!!!";
cout << s1 << endl;
}
这是一个运算符重载,返回一个 + 左边的字符串在前,右边的字符串在后组合而成的字符串;
当然,它也可以多个字符串相加;
void test13()
{
string s1 = "hello world!";
cout << s1 << endl;
s1 = s1 + "!!!";
cout << s1 << endl;
s1 = "!!!" + s1 + "!!!";
cout << s1 << endl;
}
operator>> / operator<<
这两个其实在前面就已经用过多次了:
void test13()
{
string s1;
cin >> s1;
cout << s1;
}
其中的 operator>> 和普通的输入一样,遇见空格或者换行就会中断,如上图;
getline(istream& in,string& s,char delim)
getline(istream& in,string& s)
为了能够按行输入string,c++有 getline 函数;
一种形式是遇到和 delim 相同的字符就停止
void test13()
{
string s1;
//getline(istream& in,string& s,char delim)
getline(cin, s1,'o');//按行接收字符串,遇到delim就结束,这里遇到o就不接收
cout << s1 << endl;
}
一种解释单纯的按行接收
void test13()
{
string s1;
//getline(istream& in,string& s)
getline(cin, s1);//按行接收字符串
cout << s1 << endl;
}
以上就是string类常用的几种函数。