C++STL之string的使用

对于C语言中的字符串,我们只能使用char类型数组保存,并且是以'\0'结尾的.

操作起来非常不方便而且底层空间需要用户自己访问,非常造成容易越界访问.

这个时候,C++的STL中的string类就很好解决了这些.

目录

string的使用

1.string类对象的常见构造

 2. string类对象的容量操作

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

4. string类对象的修改操作

5.string类非成员函数


string的使用

string的使用主要是使用一些接口,下面将列出一些常用的接口使用.

1.string类对象的常见构造

当我们用string初始化对象时,可采用以下方法:

string()                                          构造空的string类对象,即空字符串

string(const char* s)                   用C-string来构造string类对象

string(size_t n,char c)                 string类对象中包含n个字符c

string(const string& s)                拷贝构造函数

看下列代码

void Teststring()
{
	string s1; // 构造空的string类对象s1
	string s2("hello hmylq"); // 用C格式字符串构造string类对象s2,即包含'\0'
	string s3(s2); // 拷贝构造s3
}

来分别看一下它们的值

这样就成功初始化了.

与从同时初始化的方法还有一种,就是赋值运算符重载"=",也可以进行初始化.

    string s = "lqhmy";

 2. string类对象的容量操作

size(重点)                                      返回字符串有效字符长度
length                                                 返回字符串有效字符长度
capacity                                             返回空间总大小
empty (重点)                                 检测字符串释放为空串,是返回true,否则返回false
clear (重点)                                    清空有效字符
reserve (重点)                               为字符串预留空间
resize (重点)                                  将有效字符的个数该成n个,多出的空间用字符c填充


void Teststring()
{
	// 注意:string类对象支持直接用cin和cout进行输入和输出
	string s("hello, hmylq!");
	cout << s.size() << endl;
	cout << s.length() << endl;
	cout << s.capacity() << endl;
	cout << s << endl; 
}

 capacity为当前底层空间容量,而size和length为当前字符串大小.

可以看到已经将字符串s的大小输出出来了.


void Teststring()
{
	string s("hello, hmylq!");
	// 将s中的字符串清空,注意清空时只是将size清0,不改变底层空间的大小
	s.clear();
	cout << s.size() << endl;
	cout << s.capacity() << endl;
}

此时clear是将字符串清空,大小变为0,但是容量不会改变.


void Teststring()
{
	string s("hello, hmylq!");
	// 将s中的字符串清空,注意清空时只是将size清0,不改变底层空间的大小
	s.clear();
	// 将s中有效字符个数增加到10个,多出位置用'a'进行填充
	// “aaaaaaaaaa”
	s.resize(10, 'a');
    cout << s.size() << endl;
	cout << s.size() << endl;
	cout << s.capacity() << endl;
}

 

 注意是将有效字符增加到10个,并不是容量!

可以看到resize已经成功把10个字符用’a'进行了填充.

resize还有注意的是,如果第二个参数不给值,会默认缺省值为'\0'

看下面的例子

void Teststring()
{
	string s("hello, hmylq!");
	// 将s中的字符串清空,注意清空时只是将size清0,不改变底层空间的大小
	s.clear();
	// 将s中有效字符个数增加到10个,多出位置用'a'进行填充
	// “aaaaaaaaaa”
	s.resize(10, 'a');
	// 将s中有效字符个数增加到15个,多出位置用缺省值'\0'进行填充
	// "aaaaaaaaaa\0\0\0\0\0"
	// 注意此时s中有效字符个数已经增加到15个
	s.resize(15);
	cout << s.size() << endl;
	cout << s.capacity() << endl;
	cout << s << endl;
}

 此时虽然字符串显示的是10个‘a',但是有效字符长度已经到了15,再向后插入字符将会在第16个字符进行插入.


void Teststring()
{
	string s("hello, hmylq!");
	// 将s中的字符串清空,注意清空时只是将size清0,不改变底层空间的大小
	s.clear();

	s.resize(10, 'a');
	// 将s中有效字符个数缩小到5个
	s.resize(5);
	cout << s.size() << endl;
	cout << s.capacity() << endl;
	cout << s << endl;
}

 可以看到有效长度已经缩小到了5个,但是总容量没有变化,而且这次是将有效字符长度缩小,所以会将多余的字符舍弃掉,只会留下前5个字符.


reserve

void Teststring()
{
	string s;
	// 测试reserve是否会改变string中有效元素个数
	s.reserve(100);
	cout << s.size() << endl;
	cout << s.capacity() << endl;
}

来看结果

可以看到reserve并没有改变size即有效元素的个数,只改变了容量大小,但我们开辟了100个空间,但为什么容量是111呢?

其实系统开辟空间默认是15个字节,当不满足需求时,将每次以1.5倍的速度扩容,直到大于或者等于指定的容量.


void Teststring()
{
	string s;

	s.reserve(100);
	// 测试reserve参数小于string的底层空间大小时,是否会将空间缩小
	s.reserve(50);
	cout << s.size() << endl;
	cout << s.capacity() << endl;
}

 结果如下:

可以看到空间并不会缩小,依然是111.

 与此同时有需要注意的几个点如下:

1. size()与length()方法底层实现原理完全相同,引入size()的原因是为了与其他容器的接口保持一致,一般情况下基本都是用size()。
2. clear()只是将string中有效字符清空,不改变底层空间大小。
3. resize(size_t n) 与 resize(size_t n, char c)都是将字符串中有效字符个数改变到n个,不同的是当字符个数增多时:resize(n)用0来填充多出的元素空间,resize(size_t n, char c)用字符c来填充多出的元素空间。注意:resize在改变元素个数时,如果是将元素个数增多,可能会改变底层容量的大小,如果是将元素个数减少,底层空间总大小不变。
4. reserve(size_t res_arg=0):为string预留空间,不改变有效元素个数,当reserve的参数小于string的底层空间总大小时,reserver不会改变容量大小。

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

operator[] (重点)            返回pos位置的字符,const string类对象调用
begin+ end                        begin获取一个字符的迭代器 + end获取最后一个字符下一个位置的迭代器
rbegin + rend                     rbegin获取最后一个字符的迭代器 + rend获取第一个字符前一个位置的迭代器


范围for C++11支持更简洁的范围for的新遍历方式


void Teststring()
{
	string s1("hello lq");
	const string s2("Hello hmy");
	cout << s1 << " " << s2 << endl;
	cout << s1[0] << " " << s2[0] << endl;
	s1[0] = 'H';
	cout << s1 << endl;

	// s2[0] = 'h'; 代码编译失败,因为const类型对象不能修改
}

 可以看到我们直接用下标访问到string类中的第0个字符,当然还可以是别的下标位置.

获取到之后不仅可以使用,还可以进行修改.

例如我们将s1中的第一个字符'h'改成了'H'.

 非常的方便.


下面说string的遍历.

一共有三种方法:

需要注意的以下三种方式除了遍历string对象,还可以遍历时修改string中的字符,
另外以下三种方式对于string而言,第一种使用最多

1.for+operator[]

	for (size_t i = 0; i < s.size(); ++i)
		cout << s[i] << endl

2.迭代器

    string::iterator it = s.begin();
    while(it != s.end())
    {
        cout<<*it<<endl;
        ++it;
    }
    string::reverse_iterator rit = s.rbegin();
    while(rit != s.rend())
        cout<<*rit<<endl;

3.范围for

    for(auto& ch : s)
    {
        cout << ch << endl;
    }

4. string类对象的修改操作

push_back                   在字符串后尾插字符c
append                        在字符串后追加一个字符串
operator+= (重点)        在字符串后追加字符串str
c_str(重点)                  返回C格式字符串
find + npos(重点)        从字符串pos位置开始往后找字符c,返回该字符在字符串中的位置
rfind                            从字符串pos位置开始往前找字符c,返回该字符在字符串中的位置
substr                         在str中从pos位置开始,截取n个字符,然后将其返回


接下来看各接口的使用

void Teststring()
{
	string str;
	str.push_back(' '); // 在str后插入空格
	str.append("hello"); // 在str后追加一个字符"hello"
	str += 'h'; // 在str后追加一个字符'h'
	str += "lq"; // 在str后追加一个字符串"lq"
	cout << str << endl;
	cout << str.c_str() << endl; // 以C语言的方式打印字符串
}

 看输出结果

 可以发现都已经成功插入了

在string中push_back只能插入字符,append只能追加字符串,当然字符串可以是一个字母.

而+=既可以插入字符也可以插入字符串,所以我们平常使用+=居多.


void Teststring()
{
	// 获取file的后缀
	string file("string.cpp");
	size_t pos = file.rfind('.');
	string suffix(file.substr(pos, file.size() - pos));
	cout << suffix << endl;
}

我们都知道文件后缀是以 . 结尾

我们想获取文件的后缀名,首先要先找到.的位置,利用find或者rfind(这里使用rfind因为.一般都是偏后,所以是从后向前找),然后如果找到了,会返回这个位置的下标,否则返回npos,然后我们想获取.后面的内容,可以利用substr切取子串,从第6个位置开始,向后截取(总大小-6)个长度,即.后面的长度,这样就可以获得了.

其中的npos 是string中的一个静态成员变量        

   static const size_t npos = -1;

void Teststring()
{
	// 取出url中的域名
	string url("http://www.cplusplus.com/reference/string/string/find/");
	cout << url << endl;
	size_t start = url.find("://");
	//如果start等于了npos,说明没有找到,即这个url是非法的.
	if (start == string::npos)
	{
		cout << "invalid url" << endl;
		return;
	}
	//如果找到了,因为此时位置是在':'这个位置,所以必须将这个位置+=3跳过这个位置,此时的位置在"://www"中的第一个w位置
	start += 3;
	//然后开始寻找第一个/位置
	size_t finish = url.find('/', start);
	//此时从start到finsh之间的东西就是我们所需要的url域名
	string address = url.substr(start, finish - start);
	cout << address << endl;
}

上面的就是取出url域名的操作了

在这个操作中需要注意的是:

1. 在string尾部追加字符时,s.push_back(c) / s.append(1, c) / s += 'c'三种的实现方式差不多,一般情况下string类的+=操作用的比较多,+=操作不仅可以连接单个字符,还可以连接字符串。
2. 对string操作时,如果能够大概预估到放多少字符,可以先通过reserve把空间预留好。

5.string类非成员函数

operator+                                        尽量少用,因为传值返回,导致深拷贝效率低
operator>>                                   (重点) 输入运算符重载
operator<<                                   (重点) 输出运算符重载
relational operators(>,>=,<,<=,==)                     (重点) 大小比较

这里前三个前面已经说过了,主要说一下最后一个,就是字符串可以利用大于、小于、等于号等进行比较(按字典序比较)

void Teststring()
{
	string s1("abcde");
	string s2("bcdef");
	if (s1 > s2)
		cout << "s1 > s2" << endl;
	else if (s1 < s2)
		cout << "s1 < s2" << endl;
	else
		cout << "s1 == s2" << endl;
		
}

结果应当是s1<s2

符合了我们预期的结果.

这里想说明字符串可以直接利用运算符进行比较大小.

stl的string的使用就到此结束了,下一章将详细讲述它的模拟实现.

如果有疑问或者错误的地方,欢迎提出或指正哦.

猜你喜欢

转载自blog.csdn.net/weixin_47257473/article/details/128426556