此篇文章主要是讲解的容器之一—string
,内容分为对象构造,容量操作,访问遍历,修改操作及其非成员函数,下面博主就一一介绍.
不是说今天的主角是string
吗,为什么要介绍basic_string
呢?因为我们需要知道string
到底是什么,以及怎么来的.现在我们打开C++文档,发现basic_string
是一个类模板,定义如下:
template < class charT,
class traits = char_traits<charT>, // basic_string::traits_type
class Alloc = allocator<charT> // basic_string::allocator_type
> class basic_string;
也就是说,basic_string
有三个模板参数,后两个都有缺省值,然后我们再打开string
的文档,查看发现如下:
typedef basic_string<char> string;
这说明了什么呢?原来string
是通过basic_string
类模板进行实例化出来的一个类,其显示调用的时候,告诉编译器charT
类型是char
,另外两个类型就是默认缺省值,然后编译器通过该类型进行推演出了一个对应的类,而该类就是string
.
也就是说,string
只是basic_string
的一个推演实例之一.
我们现在已经知道了string是通过模板实例化出来的类,那么也就会定义一个string类对象了,比如:
string s1;
string s2;
注意点:使用string必须要包头文件,即写上#include
,此外如果不写using namespace std;
,定义对象会有差别.
有全局空间展开时候:
#include
using namespace std;
string s1;
无全局空间展开时候:
#include
std::string s1;
知道了怎么定义string对象,那怎么对其进行初始化呢?C++目前提供了8种以上的接口,博主这里挑最常用的4个给大家进行讲解,分别是:
构造函数样例 | 功能说明 |
---|---|
string() | 构造空的string类对象,即空字符串 |
string(const char s)* | 用常量字符串或字符数组 来构造string类对象 |
string(const string& s) | 拷贝构造函数 |
string(size_t n,char c) | string类对象中包含n个字符c |
空对象,即创造对象时候,没有任何值.
string s; //s就是一个空字符串
即可以传入常量字符串或者char数组.
string s1("123456"); //s1的内容就是123456
char str[20] = "hello you~";
string s2(str); //s2的内容就是hello you~
即还可以传同类型的 string对象.
string s("hello~");
string s1(s); //s1 的内容和s一模一样
把n个字符c组成的字符串赋给对象.
string s0(6,'a'); //s0的内容就是"aaaaaa"
string s1(4,'p'); //p的内容就是"pppp"
什么叫容量操作呢?顾名思义,就是对string的大小进行操作,比如访问,缩减,增加等,同样的,博主还是选取最常用的接口进行讲解,主要如下:
函数名称 | 功能说明 |
---|---|
size() | 返回有效字符串长度 |
capacity() | 返回string对象的容量 |
empty() | 检测string对象是否为空 |
clear() | 情况对象内容 |
resize() | 改变长度 |
reserve() | 预留容量 |
文档定义分别为:
size_type size() const;
和size_type capacity() const;
string s("12345"); //定义对象s,并初始化为12345
cout<<"size为:"<<s.size()<<endl; //返回有效字符串大小
cout<<"capacity为:"<<s.capacity()<<endl; //返回对象的容量
测试:
能够发现capacity和size是不一样的,那侧面来说我们就能够知道string的底层实现是用的顺序表.
官方文档定义分别为:
bool empty() const;
和void clear();
string s("12345");
cout<<"string对象s是否为空?"<<s.empty()<<endl; //判空
s.clear();
cout<<"清空以后的s内容是否为空?"<<s.empty()<<endl; //判空
测试:
注意哦~,clear清理的是size,即有效字符长度,而capacity并没有变.
对于resize来说,官方给了其两个重载函数,分别定义如下:
void resize (size_t n);
让对象有效字符串大小变为n.void resize (size_t n, char c);
让对象有效字符串大小变为n.
咦~,怎么是一样的功能呢?其实博主觉得可以分情况讲上述两种定义进行综合,什么意思呢?
void resize (size_t n);
,即只会缩短到n长度void resize (size_t n, char c = '\0');
,即多出来的部分,用c初始化注意哦~,博主上面说的分情况是指可以这样理解,并不是真的变成这样了,只是为了把官方文档的所有功能进行综合一下.
string s("1234567");
cout <<"原字符串: "<< s << endl;
s.resize(5); //小于原长度7,说明s变成了12345;
cout <<"缩减成5个长度后: " << s << endl;
s.resize(9); //大于原长度5,且只输入了一个参数,说明多出来的4个空间,全部用'\0'初始化了.
cout <<"增加长度到9,但是用的\\0初始化: " << s << endl;
s.resize(13, 'x'); //大于原长度9,且传入了两个参数,多出来的4空间则用字符'x'初始化.
cout <<"增加长度到13,用x初始化: " <<s << endl;
s.resize(3,'p'); //小于原长度13,即相当于缩减,那么传入的字符'p'无效.
cout <<"缩减后的字符串: "<<s<<endl;
测试:
注意哦,resize()该表的大小并不是capacity哦,而是size,即有效字符串大小,至于capacity是否会改变则是根据实际情况而定.
官方文档定义如下:
void reserve (size_t n = 0);
其作用是预先改变
capacity
,记住哦~,这里和resize不一样,并且只有n大于原来capacity才起效果,效果指的是编译器可能会把capacity调整到大于等于n的地步.
string s("123456");
cout<<"原capacity为:"<<s.capacity()<<endl;
s.reserve(8); //8小于原来的capacity(一般默认为15或20),则没有效果.
cout<<"现capacity为:"<<s.capacity()<<endl;
s.reserve(60); //60大于原来的capacity,起效果
cout<<"现capacity为:"<<s.capacity()<<endl;
cout<<"现在的s为:"<<s<<"----说明capacity的改变并不会引起有效字符串的改变"<<endl;
测试:
说了对象的定义初始化,以及其容量操作后,那么我们怎么进行访问其单个元素呢?C++也提供了3种方法:分别是迭代器
,[]
,以及范围遍历
;
大概操作有以下:
函数名称 | 功能说明 |
---|---|
operator[n] | 获取第n个字符 |
begin + end | begin获取第一个字符位置迭代器,end获取最后一个字符的下一个位置迭代器 |
rbegin + rend | rbegin获取最后一个字符的下一个位置迭代器,rend获取第一个字符位置迭代器 |
范围for | 一种新的C++遍历形式 |
官方文档定义了两个重载:
char& operator[] (size_t pos);
const char& operator[] (size_t pos) const;
大部分时候我们调用的是第一个,但如果是const
修饰的string对象,则会调用第二个.
string s("123456");
const string s1("123456");
for (int i = 0; i < s.size(); i++) {
cout << s[i] << " "; //调用的第一个
}
cout << endl;
s1[0] = '9'; //这里就会报错,以为s1[0]调用的是第二个,不允许对象修改.
由于我们才刚开始接触迭代器,对于迭代器的概念理解有点困难,因此在目前阶段我们可以将其理解成指针.
目前我们最常用的迭代器有4个,分别是begin(),end(),rbegin(),rend()
;,我称作为pp
;
string
迭代器语法:
string::iterator name = object.pp; //创建迭代器,并初始化
*name //通过迭代器访问
示例:
string s("123456");
string::iterator itb = s.begin();
string::iterator ite = s.end();
while(itb != ite)
{
cout<<*itb<<" ";
itb++;
}
cout<<endl;
测试:
对于const修饰的string对象,则需要给迭代器类型加上const_
,例如:
const string s("123456");
string::const_iterator it = s.begin();
如果我们赋值的是rbegin()
和rend()
,则需要给迭代器类型加上revese_
string s("123456");
string::reverse_iterator it = s.rbegin();
而reverse迭代器主要用来倒着遍历,如下:
string s("123456");
string::reverse_iterator itb = s.rbegin();
string::reverse_iterator ite = s.rend();
while(itb!=ite)
{
cout<<*itb<<" ";
itb++;
}
测试:
范围遍历的格式如下:
for(type i : object)
{}
其中i
可以是任何名字,而i就是object中的单个元素,object
是对象,因为博主比较懒,type博主喜欢用auto
示例:
string s("123456");
for(auto i : s)
{
cout<<i<<' ';
i += 1; //这一步并没有用,因为i的类型不是auto&
}
cout<<endl;
cout <<s<<endl;
for(auto& i : s)
{
i += 1; //现在有用了
}
cout <<s<<endl;
测试:
在c语言中,我们知道字符串的查找和修改操作在生活中使用比较频繁,那么到了C++这里,怎么能少掉这些操作呢?
我们就看看string有哪些主要操作:
函数名称 | 作用说明 |
---|---|
push_back() | 在对象末尾插入字符 |
append() | 在对象末尾添加字符串 |
operator+= | 给对象拼接字符串或字符 |
c_str() | 把string对象转为c格式字符串,如char* |
find + npos | 从某一位置开始向后查找 |
rfind() | 从某一位置开始向前查找 |
substr() | 获取子串 |
官方文档定义:
void push_back (char c);
,即在原对象末尾添加一个字符.
string s("abcde");
s.push_back('1');
s.push_back('2');
s.push_back('3');
cout<<s<<endl;
结果:
对于
append
,官方文档给出了7个重载,博主这里便挑选出最常用的几个,分别是:
string& append (const string& str);
末尾添加string对象.string& append (const char* s);
末尾添加c格式字符串string& append (const char* s, size_t n);
末尾添加字符串的前n个字符.string& append (size_t n, char c);
末尾添加n个字符c
示例:
string s1("123456");
string s2("abcd");
s1.append(s2); // 末尾添加string对象
char str[10] = "++2233++";
s1.append(str); //末尾添加c格式字符串
s1.append(str,3); //末尾添加str的前3个字符给s1
s1.append(5,'0');
cout<<s1<<endl;
结果:
官方文档定义如下:
string& operator+= (const string& str);
string& operator+= (const char* s);
string& operator+= (char c);
往简单的说,就是其支持拼接字符和字符串
string s("abcd");
string s1("qqq");
s += ' '; // 拼接一个空格
s += "1234 "; // 拼接字符串
s += s1; // 拼接string对象,其实也是字符串
cout<<s<<endl;
结果:
有时候函数形参并不是string对象,而是c格式字符串,那么传入string对象则会报错,为了解决这种麻烦,便提供了该接口.
示例:
string s("abcdef");
char* str = new char[s.size()+1];
strcpy(str,s.c_str());
cout<<str<<endl;
测试:
在介绍该接口之前,我们先介绍string中的一个值npos
,其定义为static const size_t npos = -1;
,也就是说npos是整型最大值.
而find()虽然有很多重载,但是其返回值为,如果找到目标,就返回目标出现的第一个位置.如果找不到就返回size_t
类型的-1,也就是npos.
官方文档定义的几种最常见的重载:
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 (char c, size_t pos = 0) const;
上面3种大家可以记忆成,只有3个参数,一个是传字符串或者字符,一个是传开始查找的位置,如果不穿,默认从0开始.
size_t find (const char* s, size_t pos, size_t n) const;
传C格式的字符串,从pos位置开始,查找s的前n个字符.
string s("hello world");
string s1("rld");
cout<<s.find("llo")<<endl; //从0位置开始查找
cout<<s.find(s1,4)<<endl; //从4位置开始查找
cout<<s.find('h',3)<<endl; //从3位置开始查找
cout<<s.find("world",5,2)<<endl; //从5位置开始,查找是否存在"wo"
结果:
rfind的用法和find一模一样,只是大家需要在注意一点的是,rfind()查找方向是右向左.
官方文档定义如下:
string substr (size_t pos = 0, size_t len = npos) const;
即返回对象从pos位置开始,长度为len个的子串.len如果不写,则默认是从pos位置开始,一直到末尾.
string s("123456789");
string s1 = s.substr(3,4); //从位置3开始,向后获取长度为4的子串
string s2 = s.substr(); //从0位置开始,获取整个字符串
string s3 = s.substr(6); //从6位置开始获取后面所有与字符串
cout<<s1<<endl;
cout<<s2<<endl;
cout<<s3<<endl;
结果: