【C++初阶】STL之学习string的用法

目录

  • 前言:
  • 一、认识下string
    • 1.1 什么是string
    • 1.2 为什么要有string
  • 二、string 类的接口使用
    • 2.1 初始化与析构
      • 2.1.1 初始化
      • 2.1.2 析构
    • 2.2 容量操作
      • 2.2.1 长度大小——size和length
      • 2.2.2 空间总大小——capacity
      • 2.2.3 判空——empty
      • 2.2.4 清空——clear
      • 2.2.5 预留空间——reserve
      • 2.2.6 改变有效字符个数、填充多余空间——resize
    • 2.3 遍历访问
      • 2.3.1 下标遍历——operator[]
      • 2.3.2 范围for
      • 2.3.3 迭代器遍历——iterator
    • 2.4 修改操作
      • 2.4.1 尾插字符——push_back
      • 2.4.2 尾插字符串——append
      • 2.4.3 字符串追加字符串——operator+=
      • 2.4.4 赋值——assign
      • 2.4.5 插入——insert
      • 2.4.6 删除——erase
      • 2.4.7 查找——find
      • 2.4.8 替换——replace
      • 2.4.9 返回C格式字符串——c_str
      • 2.4.10 截取字符串——substr
    • 2.5 输入操作
      • 2.5.1 获取一行字符串——getline

前言:

STL是C++的标准模板库,里面包含了许多算法和数据结构,例如我们熟悉的顺序表、链表、栈和队列以及一些常见的算法等等,编程者想使用这些就可以直接从库中调用,不必再自己造轮子了。

下面为STL内容的一张图:

接下来,我们要学习STL中的string。

一、认识下string

1.1 什么是string

string 是C++的一个类模板,字符串的类模板。要定义一个对象为字符串,就可以用string类型,说明它是一个字符串,相当于字符数组。

int main()
{
	string s1("hello yss");
	return 0;
}

1.2 为什么要有string

以前我们用C语言写代码的时候,假如字符串如果要计算它的长度,必须要调用C标准库中的函数才行。但是这些函数与字符串本身是分开的,与C++面向对象的思想不契合,而且很容易出现越界等错误,所以C++提供了string类,类里就有我们想使用的函数来操作字符串,我们只需要调用类的接口(函数)即可,更加简洁。

二、string 类的接口使用

string类有许多接口,这里只介绍一些常见的

2.1 初始化与析构

2.1.1 初始化

1️⃣无参与有参

int main()
{
	string s1();//无参 空字符串

	string s2("hello yss");//有参
	cout << s2 << endl;
	return 0;
}

注意:括号里字符串也可以是一个字符,但是必须要 ” y ” 这样写,不能 ’ y ’ 这种写法,否则会报错。

2️⃣字符填充

前面的参数表示字符串的长度,后面的参数是要填充的字符。

int main()
{
	string s1(5, 'y');
	cout << s1 << endl;
	return 0;
}

3️⃣拷贝构造

int main()
{
	string s1("hello yss");
	string s2(s1);
	cout << s2 << endl;
	return 0;
}


另一种写法:

int main()
{
	string s1("hello yss");
	string s2 = s1;//用已经存在的对象去拷贝构造刚未存在的对象
	cout << s2 << endl;
	return 0;
}

4️⃣拷贝子字符串

pos是从这位置开始拷贝,len是拷贝的个数

int main()
{
	string s1("hello yss");
	string s2(s1, 3, 5);//如果最后一个参数的值大于后面的
	  //长度,那么就直接把从pos位置开始的字符串全部拷贝构造
	cout << s2 << endl;
	return 0;
}

一个空格也算一个字符

我们发现len=npos,说明这是一个半缺省,也就是说最后的参数可以不写。

string s2(s1, 3);

npos表示size_t的最大值,作用是返回一个字符串中不存在的位置,可以判断某个子字符串是否存在。

2.1.2 析构

了解下即可

作用就是清理。

2.2 容量操作

2.2.1 长度大小——size和length

有两个操作函数,都可以计算字符串的有效长度

int main()
{
	string s1("hello yss");
	cout << s1.size() << endl;
	cout << s1.length() << endl;
	return 0;
}


一般计算大小使用size更好,因为size表示大小即长度,length表示长度,链表、顺序表有长度的说法,但是后面要学习到的树就没有所谓的长度了,只有大小,所以,为了方便且统一,计算的大小都使用size更好些。

2.2.2 空间总大小——capacity


给对象预分配好一定大小的空间。如果字符串的有效长度小于预分配的空间,空间大小不变;否则再次分配一定大小的空间

int main()
{
	string s1("hello yss");
	cout << s1.size() << endl;
	cout << s1.capacity() << endl;
	string s2("hello yssxxxxxxxxxxxxxxxx");
	cout << s2.size() << endl;
	cout << s2.capacity() << endl;
	return 0;
}

2.2.3 判空——empty

int main()
{
	string s1("hello yss");
	cout << s1.empty() << endl;
	string s2;
	cout << s2.empty() << endl;
	return 0;
}

2.2.4 清空——clear


清空的是有效字符,总空间大小不变

int main()
{
	string s1("hello yss");
	cout << s1.size() << endl;
	cout << s1.capacity() << endl;
	s1.clear();
	cout << s1.size() << endl;
	cout << s1.capacity() << endl;
	return 0;
}

2.2.5 预留空间——reserve


1️⃣参数是缺省值,所以参数可写可不写,不写默认缺省值为0,小于空间大小(capacity()的预留空间大小),空间大小不变,不改变字符串中的内容。

int main()
{
	string s1("hello yss");
	cout << s1 << endl;
	cout << s1.size() << endl;
	cout << s1.capacity() << endl;
	s1.reserve();
	cout << s1 << endl;
	cout << s1.size() << endl;
	cout << s1.capacity() << endl;
	return 0;
}

2️⃣参数的值大于0,小于有效字符的个数

int main()
{
	string s1("hello yss");
	cout << s1 << endl;
	cout << s1.size() << endl;
	cout << s1.capacity() << endl;
	s1.reserve(6);
	cout << s1 << endl;
	cout << s1.size() << endl;
	cout << s1.capacity() << endl;
	return 0;
}


小于空间总大小,所以空间总大小不变,不影响字符串的内容。

3️⃣参数的值大于等于有效字符个数,小于空间总大小

int main()
{
	string s1("hello yss");
	cout << s1 << endl;
	cout << s1.size() << endl;
	cout << s1.capacity() << endl;
	s1.reserve(12);
	cout << s1 << endl;
	cout << s1.size() << endl;
	cout << s1.capacity() << endl;
	return 0;
}


还是因为预留的空间小于空间总大小,所以空间总大小不变,且不影响字符串的内容。

4️⃣参数的值大于空间总大小

int main()
{
	string s1("hello yss");
	cout << s1 << endl;
	cout << s1.size() << endl;
	cout << s1.capacity() << endl;
	s1.reserve(20);
	cout << s1 << endl;
	cout << s1.size() << endl;
	cout << s1.capacity() << endl;
	return 0;
}


当预留空间大于空间总大小时,就会再分配新的空间大小,字符串的内容还是不受影响。

5️⃣调用两次,第二次参数比第一次小

int main()
{
	string s1("hello yss");
	s1.reserve(111);
	cout << s1.capacity() << endl;
	s1.reserve(6);
	cout << s1.capacity() << endl;
	return 0;
}


第一次调用预留空间的大小扩容后,第二次比第一次要小,不会改变,说明reserve没有缩容的功能。

总结:
1.当reserve预留的空间小于等于空间总大小时,空间总大小不变;
2.reserve预留空间不影响字符串的内容
3.reserve不能缩容。

reserve预留的空间大于空间总大小就会重新分配新的空间总大小,那么这空间总大小的增长规律是怎样的呢,看一下代码:

int main()
{
	string s1("hello yss");
	size_t len = s1.capacity();
	for (int i = 0; i < 100; i++)
	{
		s1.push_back('x');
		while (len != s1.capacity())
		{
			len = s1.capacity();
			cout << len << endl;
		}
	}
	return 0;
}


大概是以1.5倍的大小增长,不同的编译器可能不同。

2.2.6 改变有效字符个数、填充多余空间——resize


可支持两种写法,一个参数的表示有效字符个数,两个参数的分别为有效字符个数和要填充的字符。

1.先来一个参数的写法:
1️⃣参数的值小于有效字符个数

int main()
{
	string s1("hello yss");
	cout << s1 << endl;
	cout << s1.size() << endl;
	cout << s1.capacity() << endl;
	s1.resize(3);
	cout << s1 << endl;
	cout << s1.size() << endl;
	cout << s1.capacity() << endl;
	return 0;
}


有效字符个数改变,字符串的内容也改变,空间总大小不变。

2️⃣参数的值大于有效字符个数、小于空间总大小

int main()
{
	string s1("hello yss");
	cout << s1 << endl;
	cout << s1.size() << endl;
	cout << s1.capacity() << endl;
	s1.resize(12);
	cout << s1 << endl;
	cout << s1.size() << endl;
	cout << s1.capacity() << endl;
	return 0;
}


有效字符个数改变,字符串内容不变(没有填充字符),空间总大小不变。

3️⃣参数的值大于空间总大小

int main()
{
	string s1("hello yss");
	cout << s1 << endl;
	cout << s1.size() << endl;
	cout << s1.capacity() << endl;
	s1.resize(20);
	cout << s1 << endl;
	cout << s1.size() << endl;
	cout << s1.capacity() << endl;
	return 0;
}


有效字符个数改变,字符串内容不变(没有填充字符),空间总大小改变。

2.有第二个参数(填充字符)的写法:
1️⃣参数的值小于有效字符个数

int main()
{
	string s1("hello yss");
	cout << s1 << endl;
	cout << s1.size() << endl;
	cout << s1.capacity() << endl;
	s1.resize(3, 'a');
	cout << s1 << endl;
	cout << s1.size() << endl;
	cout << s1.capacity() << endl;
	return 0;
}


有效字符个数改变,字符串内容受影响,空间总大小不变。

2️⃣参数的值大于有效字符个数、小于空间总大小

int main()
{
	string s1("hello yss");
	cout << s1 << endl;
	cout << s1.size() << endl;
	cout << s1.capacity() << endl;
	s1.resize(12, 'a');
	cout << s1 << endl;
	cout << s1.size() << endl;
	cout << s1.capacity() << endl;
	return 0;
}


有效字符个数改变,字符串增加的空间填充字符,空间总大小不变。

3️⃣参数的值大于空间总大小

int main()
{
	string s1("hello yss");
	cout << s1 << endl;
	cout << s1.size() << endl;
	cout << s1.capacity() << endl;
	s1.resize(20, 'a');
	cout << s1 << endl;
	cout << s1.size() << endl;
	cout << s1.capacity() << endl;
	return 0;
}


有效字符个数改变,字符串增加的空间填充字符,空间总大小增加。

总结:
1.resize影响有效字符个数,小于原来有效字符个数时,字符串内容随之改变;
2.当n的值(第一个参数)大于原来的有效字符个数会在后面填充字符;
3.n小于空间总大小,空间总大小不变;否则会扩容。

2.3 遍历访问

2.3.1 下标遍历——operator[]


有两种写法,一个是operator关键字+运算符,另一个是直接方括号下标。

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

2.3.2 范围for

int main()
{
	string s1("hello yss");
	for (auto e : s1)
	{
		cout << e << " ";
	}
	cout << endl;
	return 0;
}

2.3.3 迭代器遍历——iterator


它是可以检查容器内的元素并遍历元素的数据类型,写法先看以下代码:

int main()
{
	string s1("hello yss");
	string::iterator s = s1.begin();
	while (s != s1.end())
	{
		cout << *s << " ";
		s++;
	}
	cout << endl;
	return 0;
}

s相当于一个指针,首先得到的是字符串的第一个字符,只要它不等于斜杠0,循环打印出每个字符,等于斜杠0跳出。

那么begin()和end()又是什么呢?


begin返回容器的首元素,end返回容器的最后一个元素的下一个地址。

还有加const的写法,用于函数调用时为了不改变字符串的内容形参加const,对应的迭代器也要加const

void Func(const string& ss)
{
	string::const_iterator s = ss.begin();
	while (s != ss.end())
	{
		cout << *s << " ";
		s++;
	}
	cout << endl;
}
int main()
{
	string s1("hello yss");
	Func(s1);
	return 0;
}

注意:是const_iterator,中间是下划线,与const iterator是不同的。const_iterator限制的指针指向的内容,所以指针指向的内容不能修改const iterator限制的是指针本身,也就是说地址不能修改。

如果要逆置字符串该怎样操作呢
1️⃣先用以前的方法:

int main()
{
	string s1("hello yss");
	string::iterator s = s1.begin();
	while (s != s1.end())
	{
		cout << *s << " ";
		s++;
	}
	cout << endl;
	size_t begin = 0;
	size_t end = s1.size() - 1;
	while (begin < end)
	{
		char tmp = s1[begin];
		s1[begin] = s1[end];
		s1[end] = tmp;
		begin++;
		end--;
	}
	s = s1.begin();
	while (s != s1.end())
	{
		cout << *s << " ";
		s++;
	}
	return 0;
}


这里交换两个元素时要自己写,感觉有点麻烦,可以使用模板

2️⃣交换模板:

template<class T>
void Swap(T& a, T& b)
{
	T t = a;
	a = b;
	b = t;
}
int main()
{
	string s1("hello yss");
	string::iterator s = s1.begin();
	while (s != s1.end())
	{
		cout << *s << " ";
		s++;
	}
	cout << endl;
	size_t begin = 0;
	size_t end = s1.size() - 1;
	while (begin < end)
	{
		Swap(s1[begin], s1[end]);
		begin++;
		end--;
	}
	s = s1.begin();
	while (s != s1.end())
	{
		cout << *s << " ";
		s++;
	}
	return 0;
}

不看顶上的交换模板函数,循环里的代码比前面简洁了一些,但是还是感觉挺繁琐的

3️⃣逆置算法函数:

int main()
{
	string s1("hello yss");
	string::iterator s = s1.begin();
	while (s != s1.end())
	{
		cout << *s << " ";
		s++;
	}
	cout << endl;
	reverse(s1.begin(), s1.end());
	s = s1.begin();
	while (s != s1.end())
	{
		cout << *s << " ";
		s++;
	}
	return 0;
}

不用逆置算法函数,也可以逆序遍历

int main()
{
	string s1("hello yss");
	string::reverse_iterator s = s1.rbegin();
	while (s != s1.rend())
	{
		cout << *s << " ";
		s++;
	}
	cout << endl;
	return 0;
}


这两个接口的也有const,与前面的同理

void Func(const string& ss)
{
	string::const_reverse_iterator s = ss.rbegin();///
	while (s != ss.rend())
	{
		cout << *s << " ";
		s++;
	}
	cout << endl;
}
int main()
{
	string s1("hello yss");
	Func(s1);
	return 0;
}

有没有发现其中一段代码太长了,所以这里就就可以用以前学过的auto来简化代码

auto s = ss.rbegin();//自动推导类型

2.4 修改操作

2.4.1 尾插字符——push_back

int main()
{
	string s1("hello yss");
	s1.push_back('x');
	cout << s1 << endl;
	return 0;
}

2.4.2 尾插字符串——append

这个接口的可实现方式比较多,所以这里只介绍常见的

int main()
{
	string s1("hello yss");
	s1.append("a");
	cout << s1 << endl;
	s1.append("vvvv");
	cout << s1 << endl;
	return 0;
}


因为是字符串,所以要双引号,但是双引号里也可以是一个字符。

2.4.3 字符串追加字符串——operator+=


这个比前面两个都要实用得多了,既可以尾插字符串,也可以尾插字符。而且写起来也方便。

int main()
{
	string s1("hello yss");
	s1 += "vvvv";
	cout << s1 << endl;
	string s2("hello yss");
	s2 += 'd';
	cout << s2 << endl;
	string s3("hello yss");
	s3 += s1;
	cout << s3 << endl;
	return 0;
}

2.4.4 赋值——assign


1️⃣用一个字符串赋给另一个字符串

int main()
{
	string s1("hello yss");
	string s2 = s1.assign(s1);
	cout << s2 << endl;
	return 0;
}

2️⃣从一个字符串中提取子字符串赋给另一个字符串

int main()
{
	string s1("hello yss");
	string s2;
	s2.assign(s1,2,5);
	cout << s2 << endl;
	return 0;
}

3️⃣一个字符串赋给另一个字符串

int main()
{
	string s2;
	s2.assign("yyyyyyyy");
	cout << s2 << endl;
	return 0;
}

4️⃣一个字符串赋给另一个字符串,限定个数

int main()
{
	string s2;
	s2.assign("yyyyyyyy", 3);
	cout << s2 << endl;
	return 0;
}

5️⃣字符赋给一个字符串,有个数控制

int main()
{
	string s3;
	s3.assign(10, 'x');
	cout << s3 << endl;
	return 0;
}

2.4.5 插入——insert

1️⃣

从pos位置插入一个字符串,和可以限制插入字符串的个数。

int main()
{
	string s1("hello yss");
	s1.insert(1, "ccc");
	cout << s1 << endl;

	string s2("hello yss");
	s2.insert(3, "cccccc",3);
	cout << s2 << endl;
	return 0;
}


2️⃣

从pos位置插入一个字符串。第二个有点长,在第一个的基础上可以选择要插入的字符串的起始的位置,并且限制插入的个数。

int main()
{
	string s3("hello yss");
	string s4("hello world");
	s4.insert(5, s3);
	cout << s4 << endl;

	string s5("hello yss");
	string s6("hello world");
	s6.insert(5, s5,2, 3);
	cout << s6 << endl;
	return 0;
}


3️⃣

插入字符,可以控制个数

int main()
{
	string s7("hello yss");
	s7.insert(5, 4, 'x');
	cout << s7 << endl;
	return 0;
}

2.4.6 删除——erase


1️⃣从pos位置开始删除len个字符,这两个参数都是缺省值,如果不写参数,就是一个字符串全部删除;
2️⃣删除某个位置的字符;
3️⃣删除从一个位置到另一个位置的字符串。

int main()
{
	string s1("hello yss");
	s1.erase();
	cout << s1 << endl;

	string s2("hello yss");
	s2.erase(3,5);
	cout << s2 << endl;

	string s3("hello yss");
	s3.erase(s3.begin());
	cout << s3 << endl;

	string s4("hello yss");
	s4.erase(s4.begin() + 1, s4.end() - 1);
	cout << s4 << endl;

	return 0;
}

2.4.7 查找——find


查找有一个字符串或者字符,后面没有参数就默认从字符串的起始位置开始找,有写参数从pos位置开始找

int main()
{
	string s1("hello yss");
	size_t pos = s1.find(" ");
	cout << pos << endl;
	return 0;
}

2.4.8 替换——replace


用len个90#替换pos位置的字符

int main()
{
	string s1("hello yss");
	s1.replace(5, 1, "90#");
	cout << s1 << endl;
	return 0;
}

小练习:把一个字符串内的所有空格替换成20%
1️⃣做法1:

int main()
{
	string s1("hello yss hello world");
	size_t pos = s1.find(" ");
	while (pos != string::npos)
	{
		s1.replace(pos, 1, "20%");
		pos = s1.find(" ", pos + 3);
	}
	cout << s1 << endl;
	return 0;
}

假如一个字符串内有多个空格,先用pos记录第一个空格的位置,当pos不等于npos成立,说明在字符串中找到了这个pos位置,然后在这个位置替换成20%。因为前面的pos已经用过一次了,所以这里要重新定义新的pos位置找后面的空格。参数为pos+3是因为20%有3个字符,所以要跳过这3个字符开始查找。直到后面没有空格,找不到,返回整型的最大值,为-1,然后跳出循环。

find()接口的一个说明:找不到返回npos

这里再介绍下npos:

它是静态成员常量,值为-1,是无符号类型的,所以也是整型的最大值。

2️⃣做法2:

int main()
{
	string s1("hello yss hello world");
	string s2;
	for (auto e : s1)
	{
		if (e == ' ')
		{
			s2 += "20%";
		}
		else
		{
			s2 += e;
		}
	}
	s1.swap(s2);//交换
	cout << s1 << endl;
	return 0;
}


创建一个临时字符串,遍历原来的字符串,如果是空格,在临时的字符串+=空格,否则+=原来字符串中的字符。如果不想用临时字符串打印出来,可以交换。

2.4.9 返回C格式字符串——c_str


可以将 const string* 类型 转化为 const char* 类型,因为在c语言中没有string,所以要把它转变成C语言中字符串的形式。

在文件操作中C语言和C++混着用:

int main()
{
	string filename("test.cpp");
	FILE* pf = fopen(filename.c_str(), "r");
	char ch = fgetc(pf);
	while (ch != EOF)
	{
		cout << ch;
		ch = fgetc(pf);
	}
	return 0;
}

2.4.10 截取字符串——substr


因为两个参数都有缺省值,两个都不写,就相当于直接取整个字符串了;后面的参数不写,从pos位置开始截取剩下的全部字符串。

1️⃣截取 . 和它后面的字符串

int main()
{
	string s1("test.cpp");
	size_t pos = s1.find('.');
	if (pos != string::npos)
	{
		string s2 = s1.substr(pos);
		cout << s2 << endl;
	}
	return 0;
}

2️⃣如果一个字符串内有多个点,要截取最后一个点以及后面的字符串

int main()
{
	string s1("test.cpp.xxx");
	size_t pos = s1.rfind('.');
	if (pos != string::npos)
	{
		string s2 = s1.substr(pos);
		cout << s2 << endl;
	}
	return 0;
}


用原来的find()要从头开始找,比较麻烦。所以string还提供了另一个查找的函数——rfind(),可以逆向查找,即从后向前。

3️⃣分割网址

int main()
{
	string s("https://cplusplus.com/reference/string/string/substr/");
	string s1, s2, s3;
	size_t pos1 = s.find(':');
	if (pos1 != string::npos)
	{
		s1 = s.substr(0, pos1);
		cout << s1 << endl;
	}
	size_t pos2 = s.find('/', pos1 + 3);
	if (pos2 != string::npos)
	{
		s2 = s.substr(pos1 + 3, pos2 - pos1 - 3);
		cout << s2 << endl;
	}
	s3 = s.substr(pos2 + 1);
	cout << s3 << endl;

	return 0;
}

网址的格式:
<协议>://<服务器名称>.<域名>/<目录>/<文件名>

我们是想分割成3个部分,所以定义3个string 对象,一个一个分割。首先分割出协议,先要找到冒号的位置,冒号的位置的pos1的值即为前面字符串的有效个数,所以截取字符串从起始位置开始截取pos1个。第二个分割出服务器名称和域名,要找的截至位置是 / ,但是要注意从pos1+3的位置开始找,找到后也是从pos1+3的位置开始截取,截取pos2 – pos1 – 3个。最后一个不必多说。

2.5 输入操作

2.5.1 获取一行字符串——getline


istream& is表示一个输入流,例如cin;char delim表示终止符,例如回车,或者别的符号。

1️⃣自己定义终止符

int main()
{
	string s1;
	getline(cin, s1,'#');
	cout << s1 << endl;
	return 0;
}

2️⃣自己没有定义终止符

int main()
{
	string s1;
	getline(cin, s1);
	cout << s1 << endl;
	return 0;
}


自己没有写终止符,默认的终止符为回车

那么这个有什么用呢?在输入一行字符串的时候,如果我们使用的是cin,它的默认终止符为空格或者换行,所以如果我们输入的字符串中有空格,打印出来的结果就不会是一行。

看以下代码:

int main()
{
	string s1;
	cin >> s1;
	cout << s1 << endl;
	return 0;
}


所以如果在做题中还是在其他写代码的场景要求输出一整行字符串,getline就是一个好的选择。

文章出处登录后可见!

已经登录?立即刷新

共计人评分,平均

到目前为止还没有投票!成为第一位评论此文章。

(0)
乘风的头像乘风管理团队
上一篇 2023年12月12日
下一篇 2023年12月12日

相关推荐