前言:

  小编在前文讲述了STL的相关知识,现在我们就要开始STL的容器讲解,今天小编讲述的是第一个类似容器的类——string类,这里小编说一下,string类并不属于STL的容器,但它可以看作为一个容器,对于这个我会在STL容器讲解中在提到,对于容器的讲述,小编也是第一次讲,所以可能讲的不好,各位读者朋友见谅,下面,代码时间到。


目录

1.string类是什么

1.1.C语言中的字符串

1.2.string类

2.标准库中的string类常用接口的说明

2.1.string类对象的常见构造

 1.string()

 2.string (const char*s)

 3.string(const char*s,size_t n)

 4.string(const string& str)

2.2.string类对象的容量操作

 1.size()

 2.length()

 3.capacity()

 4.empty()

 5.clear()

 6.reserve()

2.3.string类对象的访问以及遍历操作

 1.operator[]

 2.迭代器(Iterators)

 2.1.begin/end

​编辑 2.2.rbegin/rend

 3.范围for

2.4.string类对象的修改操作

 1.push_back

 2.append

 3.operator+=

 4.c_str

​编辑 5.find + npos

2.5.string类非成员函数

3.auto关键字

3.1.auto关键字的介绍

3.2.auto的特点

4.总结


正文:

1.string类是什么

1.1.C语言中的字符串

  C语言中,字符串通常是'\0'结尾的一些字符的集合(这句话在后面模拟实现中是很重要的,我就是忘记这个犯了一个致命错,当然这是后话了),为了方便操作,C标准库中提供了一些str系列的库函数(例如strstr,strcmp等等),但是这些库函数是与字符串是分离开的,不太符合OOP(面向对象)的思想,而且底层空间需要用户自己管理,稍不留神的话还会越界访问的。为了应对这种情况,C++推出了string类来帮助我们更好的去管理字符和字符串。

1.2.string类

  string类其实就是针对于字符和字符串的,为了方便我们去管理这些而专门设置的类,具体的介绍可以看一看这个网站:string - C++ Reference (cplusplus.com),这个网站详情的介绍了string类的相关内容,包括很多的接口,这些接口小编会在后面详细的讲的,包括模拟实现,这网站如下图所示:

  这个网站我已经介绍了很多回了,各位读者朋友如果想要去了解一些C++的标准库,函数什么的完全可以从这个网站了解用法,下面小编就介绍一下标准库中的string类的一些接口以及用法。

 

2.标准库中的string类常用接口的说明

2.1.string类对象的常见构造

  上图便就是string类对象的几种构造方式,小编会讲述其中比较重要的构造方式来教各位读者朋友如何去构造string类,下面小编制造了一份表格供读者快速知晓我们要讲的。

(constructor) 函数名称
功能说明
string() (重点)
构造空的 string 类对象,即空字符串
string(const char* s) (重点)
C-string 来构造 string 类对象
string(const char*s,size_t n)用C-string的n个字符来构造string类对象
string(const string&s) (重点)
拷贝构造函数,把s拷贝构造给string类对象
 1.string()

string s2();

  这一种构造方式其实是构造了一个空的对象, 上图就告知了构造对象是空的,其实真实情况是里面有一个字符'\0',只不过在string里面是认为里面是没有字符的。

 2.string (const char*s)

string s1("hello world");

  这个函数的功能是把括号里面的字符串拷贝一份给予string类对象,也就是说我们把s字符串给予了string类对象里面的字符串,在之后的打印环节中便会向各位读者朋友展示一下此时是否真的把s字符串拷贝给了string类对象。

 3.string(const char*s,size_t n)

string s3("hello world hello", 12);  //其实这个也是蛮容易理解的,这个的功能其实就是把s中的字符串中的n个字符拷贝给类对象。

  这个构造函数也是很容易理解的,因为讲述了2,所以可能读者朋友已经猜到这个函数的功能了,它其实就是把s中字符串的n个字符拷贝给类对象,同样的,对于其是否真的拷贝了,在一会的打印环节很多读者朋友就会知道了。

 4.string(const string& str)

string s1("hello world");
string s4(s1); 
//这个理解起来也是很容易的,无非就是把s1拷贝构造给s4

  这里就是运用到了拷贝构造的知识,所以小编在类和对象就说过,类和对象是我们学习后面知识的基础,各位读者朋友一定要掌握好,本文就用到了其中的拷贝构造的知识,此时这个函数操作无非就是把s1对象拷贝构造给了s4,也是很容易理解的,以上这四个构造函数便就是在string类里面小编认为比较常用的构造函数,各位读者朋友一定要熟悉它们的用法,当然,并不是说其余的构造函数不重要,全部都会用当然是最好的,但是小编建议先把这四个理解好了再去理解别的,下面我们就进入打印string类的下一个接口。

2.2.string类对象的容量操作

  上图便是string类里面关于一些容量的接口,容量,简单来说就是string类对象里面字符串中字符的个数,下面我们找其中几个重要的容量函数函数进行讲述,在讲述之前小编依然列出表格先让读者朋友知道接下来小编将会讲述的内容。

函数名称
功能说明
size (重点)
返回字符串有效字符长度
length
返回字符串有效字符长度
capacity
返回空间总大小
empty (重点)
检测字符串释放为空串,是返回 true ,否则返回 false
clear (重点)
清空有效字符
reserve (重点)
为字符串预留空间
 1.size()

string s1("hello world");
int n = s1.size();
cout << n << endl;

   这个函数的用法还是比较容易理解的,通过名字我们也知道这个函数是用来计算string类对象字符串中字符的个数的,当然可能很多读者朋友会疑惑,字符串的末尾不应该有'\0'结尾吗,这里为什么会是11个而不是12个?其实在string类中,严格意义上来说字符串结尾是没有'\0'结尾的,我们可以把它想象成隐藏的'\0',简单来说就是有这个字符但是没有展现出来罢了,这个特点读者朋友记住就好了,现在我们就要记住此时的string里面字符串中字符的数量明面上是字符的个数,我们知道它有'\0'就好了。

 2.length()

string s1("hello world");
int n = s1.length();
cout << n << endl;

  对于length()函数,它的功能和size()是一样的,它们都是返回字符串中有效字符长度的,这里我就不都说了,但是小编在使用习惯上还是觉着用size()比较好一点,但是这因人而异,你想用哪一个就用哪一个。

 3.capacity()

string s1("hello world");
int n = s1.capacity();
cout << n << endl;

  capacity这个词看过我前面文章的读者朋友可能并不会陌生,小编在写顺序表的时候就用过这个词,当时它的作用就是计算总空间的大小,此时它作为string类的接口,它的功能同样也是计算string类对象开辟空间的大小,所以小编在顺序表的时候可不是乱取名,一般开辟空间的大小是编译器决定的,有的编译器可能是2倍的开,有的可能是4倍的开,总之,此时这个函数的作用便是计算总空间大小的,各位读者朋友记住就好。

 4.empty()

string s1("hello world");
if (s1.empty())
	cout << 1 << endl;
else
	cout << 2 << endl;

   这个函数的功能也是很容易去理解的,通过名字我们知道这个单词的意思是空,所以自然而然的可以知道这个函数的功能就是判空,它是用来判断string类对象的字符窜是否是空串,倘若是空串,返回true;反之则返回false,上面的s1明显不是空串,所以才返回的是2,这个功能还是比较重要的,等以后我们做一些算法题的时候,可能就使用到这个函数了。

 5.clear()

string s1("hello world");
cout << s1.size() << endl;
s1.clear();
cout << s1.size() << endl;

   这个函数的功能也是很容易去理解的,clear有清理的意思,再结合上面的代码以及它的运行结果,我们可以知道这个函数的作用就是把string类对象中的有效字符个数给清空,这个功能理解起来是不难的,读者朋友要好好掌握。

 6.reserve()

string s1("hello world");
cout << s1.capacity() << endl;
s1.reserve(100);
cout << s1.capacity() << endl;
return 0;

  通过代码的运行结果以及reserve这个单词的意思是,我们也可以知道这个函数的功能其实是用来预留空间的,就是我们在创建完一个string类对象以后我们可以先给它预留一块空间,从而避免过多的去进行扩容操作,再写一些比较大型的项目或者算法题的时候,这个函数往往可以发挥很大的作用的。

  以上便就是关于容量的一些接口的详解,下面我们继续进行其他接口的讲述。

2.3.string类对象的访问以及遍历操作

  对于string类对象的访问以及遍历操作,其实目前小编已知的就三种方式,下面小编将会对这三种方式依次介绍。

函数名称
功能说明
operator[] (重
点)
返回 pos 位置的字符, const string 类对象调用
begin + end
begin 获取一个字符的迭代器 + end 获取最后一个字符下一个位
置的迭代器
rbegin + rend
begin 获取一个字符的迭代器 + end 获取最后一个字符下一个位
置的迭代器
范围 for
C++11 支持更简洁的范围 for 的新遍历方式
 1.operator[]

   在展示如何使用之前,小编先简单一下这个接口,这个接口是把[]运算符进行重载了,使得我们在访问类对象中字符串中每一个字符的时候,可以采用数组访问下标的方式进行字符串每一个字符的遍历,此时我们也可以借助这个操作把类对象里面的字符串中的字符给打印出来,此时我们还需要借助for循环以及size()接口来进行打印操作,需要借助size()的原因自然是它可以作为for循环的条件,此时我们的[ ]运算符重载是拿数组下标进行的重载,所以此时第一个字符所在的位置是0,最后一个所在的位置自然是size() - 1了,下面小编给出打印字符串的代码:

int main()
{
	string s1("hello world");
	for (int i = 0; i < s1.size(); i++)
	{
		cout << s1[i];
	}
	cout << endl;
	return 0;
}

 2.迭代器(Iterator

  对于第二种方式,是通过迭代器的方式进行遍历,可能很多读者朋友不知道迭代器是个是什么东西,简单来说,迭代器是一个类似指针的玩意,但千万不要把迭代器和指针混为一谈,迭代器不一定是指针,只不过迭代器的用法和指针是很像的,所以我才说迭代器是一个类似指针的玩意,而不是说迭代器就是指针,对于迭代器,小编决定抽其中四个接口给读者朋友进行讲解。

 2.1.begin/end

   上面这两个接口是搭配着迭代器进行使用的,对于第一个begin接口,小编用自己的话来介绍一下,它就类似指针,指向了string类对象字符串中第一个字符的位置,而end接口也是如他的名字一般,只不过它是指向字符串最后一个字符的下一个位置,这两个接口搭配使用,也可以实现字符串的遍历操作,还记着小编上面说过的吗,迭代器是一个类似指针的玩意,所以它的使用也和指针类似,此时我们仅需通过用指针用法使用迭代器即可,只不过此时的类型是迭代器类型,下面小编给出遍历代码:

int main()
{
	string s1("hello world");
	string :: iterator it = s1.begin();  //由于很多的容器都支持迭代器,所以在使用迭代器的时候一定要表明它属于哪个域
	while (it != s1.end())
	{
		cout << *it;
		it++;      //迭代器的用法和指针是很像的,但他俩不要混为一谈。
	}
	return 0;
}
 2.2.rbegin/rend

  上面的代码读者朋友应该就理解了小编所说的迭代器是一个类似指针的玩意这句话的意思了,它的用法和指针不能说很像,简直就是一模一样,下面我们继续说两个类似它们的接口,对于这两个接口,小编一句话就可以给读者朋友概括出来,他俩的功能其实就是把begin/end()反着来的,rbegin()指向的是字符串最后一个字符的位置,rend()指向的是字符串第一个位置前一个位置,此时我们遍历字符串的话其实就是把字符串反着读了,下面小编直接展示代码带领读者朋友认识这个比较有趣的功能:

int main()
{
	string s1("wang zi");
	string::reverse_iterator it = s1.rbegin();  //这里的reverse_要和上面的reserve区分开,前者是相反的意思,后者是预保留的意思
	while (it != s1.rend())
	{
		cout << *it;
		it++;   //这里虽然it指向的是最后一个字符的位置,但实际上并不是指向最后一个字符的位置,而是类似先把字符串倒过来,最后一个字符的位置反倒是第一个字符了,原理是这么个原理,至于为啥是这样,等我以后在研究研究吧
	}
	return 0;
}

 3.范围for

  范围for是C++11的一种支持更简洁的范围for的遍历方式,它的底层使用迭代器来书写的,并且据小编所了解的,最开始范围for是Python支持的一个功能,之后C++,Java等各种编程语言也把这个功能借鉴过来来帮助程序员更好的去书写代码,对于范围for的使用,小编直接给出代码:

int main()
{
	string s1("hello world");
	for (auto it : s1)
	{
		cout << it;
	}
	return 0;
}

  可能很多读者朋友不了解我写的代码里面的auto是用来干嘛的,不要着急,auto关键字我在讲完string一些接口之后就会说它的用法,现在我先提前铺垫一下,auto关键字可以自动识别变量的类型,它可以搭配着范围for来进行使用,对于范围for的原理,小编对此也不是那么的清楚,我先简单说明一下按照我自己的理解,对于范围for里面的it,它就类似于迭代器的begin(),此时它指向的是字符串的第一个位置(类似),此时我们可以直接提供过it访问其对应的字符,在进行完一次循环后,it会自动往后走,知道走到end()位置,此时范围for停止,我们也顺利的打印完了字符串中所有的字符,这就是我对于范围for的理解。

  以上便就是string类的三种遍历方式,读者朋友们一定要熟记这些遍历方式的用法。

2.4.string类对象的修改操作

  下面就进入修改的环节了,这些修改操作包括但不限于尾插一个字符,尾插一个字符串,返回字符串等等一系列的功能,下面开始讲解。

函数名称
功能说明
push_back
在字符串后尾插字符 c
append
在字符串后追加一个字符串
operator+= (
)
在字符串后追加字符串 str
c_str ( 重点 )
返回 C 格式字符串
fifind + npos (
)
从字符串 pos 位置开始往后找字符 c ,返回该字符在字符串中的 位置
rfind
从字符串 pos 位置开始往前找字符 c ,返回该字符在字符串中的 位置
 1.push_back

  对于push_back函数,用法其实是很简单的,通过上面的介绍,我们可以知道他的用法主要是在字符串后面尾插上一个字符的,所以小编就不多说了,直接上代码:

string s1("hello world");
s1.push_back('c');

 2.append

  对于append接口,它的用法还是比较复杂的,就如上图所示,它有很多的用法,但是小编建议读者朋友知道它的是尾插字符串的接口就好,其他的可以自行去了解,我在这里写这个接口主要是为了和上面那个只能尾插字符的接口作比较,下面我通过代码展现它用法:

string s2("heihei");
string s1("hello world");
s1.append(" hello wang zi");//可以这么写
s1.append(" hello bite", 2);  //还可以这么写
s1.append(s2);  //甚至可以这么写。
s1.append(s2, 2);  //用法很多我就不一一展示了
 3.operator+=

   对于+=运算符的重载,其实这个才是我们比较常用的插入函数,因为通过上面的代码可以清晰的知道,这个运算符不仅可以尾插字符,还可以尾插字符串,尾插string类对象,主打一个全能,小小编还是很推荐读者朋友去使用这个函数来完成插入操作的,下面给出使用方法:

string s1("hello world");
s1 += 'c';
s1 += " hello wang";
 4.c_str

  这个函数也是一个比较重要的函数,它的功能通过上面图片我们便可以大致的了解,这个函数是用来获取string类对象中的字符串的,我们可以通过它来获取到对象里面的字符串,通过它我们也可以直接打印出string类对象字符串的内容,它的使用方法如下图所示:

string s1("hello world");
cout << s1.c_str() << endl;
 5.find + npos

  小编决定先讲述一下npos这个接口,它代表着一个无效的数,类似于NULL一样,通过上面的解释,我们可以清楚的知道此时的npos是-1,但它是无符号类型的-1,这就代表着它会是一个很大的数,因为-1会被强制类型转换成一个有符号的整型,所以会是一个很大的数,所以我称之为无效的数,它搭配着find函数是很用的,find函数如它的名字一样,它是有着查询功能的,类似于小编在顺序表那篇博客的查找指定数的函数,它的功能也是查询,如果找到了要查询的东西,则返回这个字符所在的位置,如果没有查询到,那么返回nops,即返回一个无意义的数,下面小编给出这个函数的用法:

string s1("hello world");
string s2("hello world");
if (s1.find('e'))
	cout << 1 << endl;
if (s1.find("hello",0)) //此时这个字符串的位置是0  
	cout << 2 << endl;
if (s1.find("hello", 2, 2)) 
	cout << 3 << endl;
if (s1.find(s2))
	cout << 4 << endl;

   以上便就是小编所要讲述类对象的修改操作,可能很多读者朋友发觉小编少写了一个函数,那就是rfind,我认为,只要知道了find的用法,rfind自然不在话下,所以我没写,但不代表不重要,我只是觉着太重复懒的写了,读者朋友还是要知道它的用法的。下面我们进入下一个环节的讲解。

2.5.string类非成员函数

  对于这些函数,小编就先不深入讲了,我将会在string类的模拟实现中,实现出非成员函数的,下面小编先给各位列一列之后我模拟实现的大纲。

函数
功能说明
operator>> (重点)
输入运算符重载
operator<< (重点)
输出运算符重载
getline (重点)
获取一行字符串
relational operators (重点)
大小比较

3.auto关键字

3.1.auto关键字的介绍

  auto关键字是在C++11推出来的语法知识,它的作用就是自动帮我们去认识右边变量的类型,它可以帮我们减少识别右边的变量类型,有的时候我们不清楚右边的类型,有了auto之后,妈妈再也不用担心我不知道变量类型啦~(狗头),下面我来介绍一下auto的特点

3.2.auto的特点

  1.在早期C/C++中auto的含义是:使用auto修饰的变量,是具有自动存储器的局部变量,后来这个不重要了。在C++11中,标准委员会变废为宝赋予了auto全新的含义即:auto不再是一个存储类型指示符,而是作为一个新的类型指示符来知识编译器,auto声明的变量必须由编译器在编译时期推导而成的。

  2.用auto声明指针变量时,用auto和auto*没有任何区别(因为auto也是会识别指针类型的),但是auto声明引用类型的时候一定要加&。对于这个特点读者朋友要记住,引用类型算是一个特殊的类型,通过下面的代码便可以知道区别:

int a = 1;
int& b = a;
auto c = a;
c = 2;
cout << a << endl;
auto& d = a;
d = 2;
cout << a << endl;

   通过上面的代码以及运行结果,便告知了我们auto关键字对于引用类型的限制,只有auto&才可以声明引用类型,这个特点读者朋友一定要记住。

 3.当在同一行声明多个变量的时候,这些变量一定是相同类型,否则编译器是会报错的,因为编译器只对第一个类型进行推导,然后用推导出来的类型定义其他变量。

 4.auto不能作为函数的参数,可以做返回值,但是建议谨慎使用。

 5.auto不能直接用来声明数组。

4.总结

  本篇文章到这里也就结束了,小编认为我写的这一篇不是很完美,有一些不该写的函数写的很啰嗦,一些该写的却是一笔带过了,以后我写的关于容器的博客会慢慢改善的,下一篇我将要会对string类进行模拟实现,如果文章有错误,请在评论区指出,我一定会及时改正,那么,我们下一篇文章见啦!

Logo

开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!

更多推荐