(超详细 + 例子演示) C++的STL学习 2 :string类的学习, 接口的使用和认识

想必大家在学习C语言的时候都有被字符串和指针支配的恐惧吧, 在C语言中使用char* 表示的字符串, 一不留神丢了一个’\0’, 或者一不留神指针就会越界, 搞的我是苦不堪言. 在C++中引入string则不需要我们考虑这些问题, 极大的方便了我们的使用. 那么我们这次就来学习一下string类, 耐心看完, 你一定会有所收获~

文章中的所有代码都是调试过的代码, 并且测试代码中的注释也有许多知识点, 有兴趣的朋友不要错过, 可以拿去自己调试一下, 会有更清晰的理解

0. 先推荐一个网址

当然不是推销…而是C++的官方网站
C++的官方网站, 查阅各种C/C++资料
俗话说 授人以鱼不如授人以渔 , 这个网站有C/C++几乎所有的资料

在这里插入图片描述

有兴趣的同学一定要去看看这个网站, 有遗忘的接口或者函数直接去一查阅,
 不仅能巩固知识, 还能提高鹰语水平 (滑稽)
 或者有同学问你问题, 直接掏出来一查, 既能学习, 还能装杯, 可谓是一举两得
 妙啊~

1.string类了解

在这里插入图片描述

  • string是表示字符串的字符串类
  • 该类的接口与常规容器的接口基本相同,再添加了一些专门用来操作string的常规操作。
  • string在底层实际是:basic_string模板类的别名,typedef basic_string<char, char_traits, allocator> string;
  • 不能操作多字节或者变长字符的序列。
  • 在使用string类时,必须包含头文件以及using namespace std;

2. string类的常用接口说明

注: 这里只说明常用的接口, 不可能把所有的接口都说一遍, 这些接口也没有必要特意去记, 遇到没见过的或者遗忘的直接去官方查~

(1). string的构造函数

Construct功能说明
string()构造一个空的string对象
string (const string& str)拷贝构造: 用一个string对象构造一个对象
string (const char* s)用一个C风格字符串构造一个string对象
string (const string& str, size_t pos, size_t len = npos)用一个string对象, 从第pos个字符开始, len为长度进行构造
string (const char* s, size_t n)用C风格字符串的前n个字符构造
string (size_t n, char c)用n个字符c构造一个string对象

这里顺便说一下赋值:
operator=()
string& operator= (const string& str);
string& operator= (const char* s);
string& operator= (char c);
这三个重载, 分别是用string, char 和 char 进行赋值, 也是非常常用的接口*

下面给出测试代码:

//构造函数
void test() {

	string str1;
	string str2(str1);

	string str3("123456");
	string str4 = "12345678"; //单参构造的隐式类型转换

	string str5(str4, 3);   //45678
	string str6(str4, 3, 2); //45

	string str7("1234567989", 5); // 12345

	string str8(10, 'a'); //aaaaaaaaaa
}

调试结果如下:
在这里插入图片描述

其实构造函数常用的也没几个, 大家按照个人喜好选择即可

(2). string容量相关

Capacity接口说明
size返回字符串的有效字符长度
length返回字符串的有效字符长度 (等同于size, 一般不用)
capacity返回当前string对象能存放的最大字符个数(空间大小)
empty检测字符串是否为空, 为空返回true, 否则返回false
clear清空字符串
resize设置/修改有效字符长度size
reserve设置/修改容量capacity的大小
shrink_to_fit把容量缩小到合适

这里重点说明两个接口: resize 和 reserve

  •   void resize (size_t n) : 让size变为n, 且新增的位置用'\0'补充
      void resize (size_t n, char c) : 让size变为n, 且新增的位置用字符c补充
      	resize只会填充新增加的位置的内容, 不会修改已有内容
    
      	resize可以使size变大变小, 但不会改变capacity
      		修改之后size > 原始容量, 则string会增容
      		减小size时, 只会截断原有内容, 不改变capacity
    
  •   void reserve (size_t n = 0)
      reserve只会修改容量capacity , 不修改实际元素个数size
      	因为增容要释放原有空间, 开辟新的空间, 代价较大
      所以reserve一般用于:  知道字符串长度, 提前使用reserve开好空间, 减小后续增代价
    
      reserve如果要减小容量, 实际的容量大小会依据string对象内容, 
      进行适当优化(直到容量必须满足字符需要的空间)
      	说人话就是: 按情况减小容量, 但是不能比当前size还小
    

但是据我多次试验发现, 使用reserve进行缩容的时候, 只有当size小于15时, 使用reserve(n <=15)时才能进行缩容, 其他情况都不会进行缩容
这里我不太清楚具体细节是什么, 只知道上述结果, 所以也不多做说明, 如果有明白的大佬可以教我一下, 在此谢过了~

下面给出几个测试代码

size的测试

void test2() {
	string str1 = "1234";
	const string str2 = "12345";

	//size: 有效字符的个数 --->  和 '\0' 有区别
	int ret = str1.size();
	ret = str2.size();

	char str[] = { '1', '\0', '\0', '\0' };
	ret = strlen(str);
	string str3(str);
	ret = str3.size();


	string str4("1234");
	//resize(n): 修改有效字符的个数
	// n > string对象中内容的长度, 则新增加的位置补'\0'
	str4.resize(10);
	const char* strarr = str4.c_str();

	ret = str4.size();
	ret = strlen(strarr);
	cout << str4 << "end" << endl;
	cout << strarr << "end" << endl;

}

调试结果如下:
在这里插入图片描述

resize的测试

void test3() {
	string str = "1234";
	str.resize(6, 'a'); // 1234aa
	int size = str.size();
	//capacity: string对象存放字符的最大个数
	int cap = str.capacity();

	//修改之后的size大于cap, 则修改有效字符的个数, 并增加容量
	str.resize(20, 'b');
	size = str.size();
	cap = str.capacity();

	//resize 减小size时, 只会截断内容, capacity不会变
	str.resize(2, 'c');
	size = str.size();
	cap = str.capacity();
}

调试结果:
在这里插入图片描述

reserve的测试

void test5() {
	string s;
	s.resize(10);
	cout << "size: " << s.size() << endl;
	cout << "capacity: " << s.capacity() << endl;

	//reserve: 只会修改容量capacity , 不修改实际元素个数size

	//n > size && n > capacity
	s.reserve(60);
	cout << "size: " << s.size() << endl;
	cout << "capacity: " << s.capacity() << endl;
	
	//n < capacity
	s.reserve(10);
	cout << "size: " << s.size() << endl;
	cout << "capacity: " << s.capacity() << endl;

	//n < size 不会有变化
	string s2 = "12345";
	s2.reserve(4);
	cout << "size: " << s2.size() << endl;
	cout << "capacity: " << s2.capacity() << endl;
}

运行结果:
在这里插入图片描述

看到这里可能细心的朋友发现了, string的capacity数字怎么这么奇怪?..那我这就来给大家看看

void print_capacity() {
	string s;
	int cap = s.capacity();
	cout << "capacity: " << cap << endl;

	//增容: 第一次2倍, 后面都是1.5倍 (大概)
	for (int i = 0; i < 1000; i++) {
		s.push_back(i);
		if (cap != s.capacity()) {
			cap = s.capacity();
			cout << "capacity: " << cap << endl;
		}
	}
}

调试和运行结果如下:
在这里插入图片描述

我们可以发现空的string, capacity为15
而且当空间不够需要增容的时候, 第一是大约二倍, 以后每次是大概1.5倍
这是vs, 也就是PJ版本的增容规则
原因也很简单, 因为增容涉及到开空间, 释放空间和拷贝的操作, 代价比较大, 所以编译器会提前给你设置一个空间, 增容的时候也会直接给你更大的空间, 不可能每次添加都给你增容吧...

clear 和 shrink_to_fit 测试

void test6() {
	string s = "123456789";
	cout << "size: " << s.size() << endl;
	cout << "cap: " << s.capacity() << endl;

	//clear: size清0, cap不变
	s.clear();
	cout << "size: " << s.size() << endl;
	cout << "cap: " << s.capacity() << endl;


	s = "123456";
	s.reserve(15); // n > size :  n <= 15 cap始终为15
	cout << "size: " << s.size() << endl;
	cout << "cap: " << s.capacity() << endl;

	//shrink_to_fit :  在满足 > size的情况下, 把容量减小到合适的大小 
	s.shrink_to_fit();
	cout << "size: " << s.size() << endl;
	cout << "cap: " << s.capacity() << endl;
}

调试结果如下:
在这里插入图片描述

(3). string的访问和遍历

Acess接口说明
operator[]获取pos位置的元素
at获取pos位置的元素
back获取最后一个元素
front获取第一个元素

迭代器

Iterators接口说明
begin迭代器, 指向第一个元素的位置
end迭代器, 指向最后一个元素的下一个位置
rbegin反向迭代器, 指向最后一个元素
rend反向迭代器, 指向第一个元素前一个位置
cbegin
cend
crbegin下面这四个都是const迭代器, 与上面对应, 无法修改内容
crend

这里都是左闭右开的区间, 所以会有元素下一个位置等等...

容器中的许多接口都设有两种, 一种是普通接口, 一种是const接口,
const接口是const string对象调用的, 无法通过const接口修改对象内容, 保证了对象的安全性

下面给出几个测试代码

[] 和 at 的使用

//访问相关
void test7() {
	string s = "1234";

	//front: 获取第一个字符 (可读可写)  
	s.front() = 'a'; // a234
	cout << s << endl;

	//获取最后一个字符  (可读可写)
	s.back() = 'b'; // a23b
	cout << s << endl;

	//const string 时接口为  const front/back 为只读接口, 不能修改
	const string s2 = "1234";
	//s2.front() = 'a';
	//s2.back() = 'b';

	s = "1234";
	s.at(0) = 'a';
	s.at(1) = 'b';
	//位置越界, 抛异常
	s.at(5) = 'e';

	//const接口, 只读
	//s2.at(0) = 'a';


	s = "1234";
	//[] 访问, 是 []的运算符重载函数
	//s[]  <--->  s.operator[]
	s[0] = 'a';
	s[1] = 'b';
	s.operator[](2) = 'c';//完整形式

	// []越界访问, Debug版本报assert错误, Release版本不报错(危)
	char c = s[10];


	//const接口
	//s2[3] = 'd';
}

上面的代码一直在变, 调试结果不好发…有兴趣的小伙伴可以自己拿去看一看

string 的3种遍历方式

下面注释中写了迭代器的相关信息, 不要看代码长, 其实都是一个模式, 很简单的, 不要错过~

//3种遍历方式  ([], 迭代器, 范围for)
void test8() {
	cout << "operator[]" << endl;
	string s = "12345";

	for (int i = 0; i < s.size(); i++) {
		cout << s[i] << " ";
	}
	cout << endl;

/*
	迭代器: 一种元素的通用访问机制, 是一种设计模式
	begin迭代器: 指向容器中第一个元素的位置
	end迭代器: 指向容器中最后一个元素的下一个位置
	表示范围: 左闭右开 --> [begin, end)

	使用方式: 类似于指针
		操作: 解引用, ++
*/
	cout << "iterator: " << endl;
	//可读可写迭代器, 可以修改内容
	string::iterator it = s.begin();
	while (it != s.end()) {
		//迭代器解引用
		cout << *it << " ";
		//通过迭代器修改内容
		*it = 'a';
		//迭代器++, 移动到下一个元素的位置
		++it;
	}
	cout << endl;

	it = s.begin();
	while (it != s.end()) {
		cout << *it << " ";
		++it;
	}
	cout << endl;

	/*
		begin 和 end 都有两种接口, 非const和const接口
		非const对象: 调用 begin 和 end 会返回 非const迭代器(可读可写)
		const对象: 调用 begin 和 end 会返回 const迭代器 (只读)

		cbegin 和 cend 是const接口, 返回const迭代器
		const和非const对象: 都能调用这俩接口, 返回只读迭代器
	*/

	cout << "const_iterator: " << endl;
	s = "12345";
	//const迭代器
	string::const_iterator cit = s.cbegin();
	while (cit != s.cend()) {
		cout << *cit << " ";
		//const迭代器是只读迭代器, 不能通过解引用修改内容
		//*cit = 'a';
		++cit;
	}
	cout << endl;

	/*
		反向迭代器:  
			rbegin: 指向最后一个元素的位置 
			rend: 指向第一个元素的前一个位置

		从后往前遍历,  ++操作: 移动到前一个元素的位置
	*/

	cout << "reverse_iterator: " << endl;
	s = "12345";
	string::reverse_iterator rit = s.rbegin();
	while (rit != s.rend()) {
		cout << *rit << " ";
		//移动到前一个位置
		++rit;
	}
	cout << endl;

	//注意: end, rend, cend, crend: 返回的迭代器都是有效元素之外的位置, 不能接引用(非法访问)

	/*
		rbegin, rend: 返回的都是可读可写迭代器
		crbegin, crend: 返回的都是只读迭代器
	*/
	cout << "const_reverse_iterator: " << endl;
	string::const_reverse_iterator crit = s.crbegin();
	while (crit != s.crend()) {
		cout << *crit << " ";
		++crit;
	}
	cout << endl;


	//范围for
	cout << "范围for: " << endl;
	s = "12345";
	//for(当前要查看的变量: 需要遍历的容器)   可读可写
	//底层实现就是迭代器
	for (auto& ch : s) {
		cout << ch << " ";
		//修改内容, 必须用引用类型接收, 不然修改的是拷贝的值, 原来的值没有被修改
		ch = 'a';
	}
	cout << endl;

	//只读, 效率最高      auto --> 自动类型推导
	for (const auto& ch : s) {
		cout << ch << " ";
	}
	cout << endl;

	/*
		常见的范围for书写形式:
			1. 只读: for(const auto& 变量 : 容器)
			2. 可读可写: for(auto& 变量 : 容器)
	*/
}

(4). string类对象的修改

Modifiers接口说明
push_back尾插一个字符
pop_back尾删一个字符
append追加插入内容
operator+=追加插入内容
insert在任意位置插入
earse在任意位置删除
assign赋值
replace替换对象中的内容
swap交换两个string对象的内容

string实际也是一个顺序表, 一般情况下都使用尾插尾删, 时间复杂度为O(1)
而其他地方插入删除都是O(n),
所以 insert, erase接口, assign接口就不在这一一说明, 下面例子中的注释中会写具体插入了什么, 一看就可以明白

//插入删除相关
void test9() {
	string s;
	//尾插
	s.push_back('1');
	s.push_back('2');
	s.push_back('3');
	s.push_back('4');

	string s2 = "abc";

	//追加插入
	s.append(s2); // 1234abc
	s.append(s2, 2, 1); //1234abcc
	s.append("xyz");  //1234abccxyz
	s.append("opq", 2);  //1234abccop
	s.append(3, '1');  //1234abccop111
	s.append(s2.begin(), s2.end()); //1234abccop111abc
	char arr[] = "56789";
	s.append(arr, arr + 4);//1234abccop111abc5678


	//string +=运算符重载函数: operator+=  --> 完成字符串换的拼接 (最常用)
	s = "";
	s2 = "789";

	s += '1';
	s += "abc";
	s += s2;


	//insert: 在任意位置插入
	s = "";
	s2 = "123";
	//在pos的前面插入
	s.insert(0, s2); //123      头插s2
	s.insert(1, s2); //112323   在s[1]前面插入s2

	string s3 = "abc";
	s.insert(2, s3, 1, 2); //11bc2323	在s[2]前插入s3对象从位置[1]开始的两个字符

	s.insert(4, "def"); //11bcdef2323	在位置[4]前插入字符串
	s.insert(5, "ghi", 1); //11bcdgef2323	在位置[5]前插入字符串"ghi"的1个字符
	s.insert(9, 3, 'f'); //11bcdgef2fff323		在位置[9]前插入3个字符'f'
	s.insert(s.begin(), 2, '0'); //0011bcdgef2fff323	头插2个'0'
	s.insert(s.end(), 3, 'g'); //0011bcdgef2fff323ggg	尾插3个'g'
	s.insert(s.begin() + 1, 'a'); //0a011bcdgef2fff323ggg	在位置[1]前插入一个'a'

	s.insert(s.end() - 1, s2.begin(), s2.end()); //0a011bcdgef2fff323gg789g		在倒数第二个位置前插入s2


	s = "abc";
	s2 = "123";

	//assign 赋值, 等价于operator= (所以用=就可)
	s.assign(s2); //123
	s.assign(s2, 1, 2); //23
	s.assign("789"); //789
	s.assign("abcdefg", 5); // abcde
	s.assign(5, '1'); //11111
	s.assign(s2.begin() + 1, s2.end() - 1);// 2


	s = "123456789";
	//尾删
	s.pop_back(); //12345678

	s.erase(0, 1); // 2345678
	s.erase(3, 2); // 23478
	s.erase(s.size() - 1, 1); //尾删 2347

	s.erase(s.end() - 1);//尾删 234
	s.erase(s.begin()); //头删 34
	s.erase(s.begin(), s.end());//全删 ""
}

replace和swap的测试

void testa() {
	string s = "123";
	string s2 = "abc";
	
	//替换
	s.replace(1, 1, s2); //1abc3   s从位置[1]开始的1个字符, 用s2替换

	s = "123";
	s.replace(1, 2, s2); //1abc


	s = "123";
	s2 = "abc";

	//交换 (成员函数)
	s.swap(s2);

	//非成员函数  等价于上面
	swap(s, s2);
}

(5). string的其他操作

String operations接口说明
c_str返回C风格字符串
find从pos位置向后查找
rfing反向查找
getline接受一行字符, 遇到换行结束

下面给出使用栗子:

c_str的使用

void testb() {
	string s = "123";
	cout << s << endl;
	cout << s.c_str() << endl;
	//c_str 返回const char*
	const char* ptr = s.c_str();
	//和上面一样
	ptr = s.data();


	//copy: 把string对象的内容拷贝到s指向的数组中
	char arr[100];
	s.copy(arr, 3);
}

find的具体使用例子

void testc() {
	string s = "1234561234abc123a123456789";
	string s2 = "123456798";

	size_t pos = s.find(s2);
	pos = s.find("123");
	pos = s.find("abcxyz", 0, 3);
	pos = s.find('a');

	pos = s.find("000"); // 找不到返回-1  因为是size_t 无符号 ---> 4G的大小


	//找   cplusplus.com
	//substr: 截取字符串
	s = "http://cplusplus.com/reference/stringstring//substr/";
	pos = s.find("://");
	if (pos != string::npos) {
		size_t pos2 = s.find("/", pos + 3);
		if (pos2 != string::npos) {
			pos += 3;
			string sub = s.substr(pos, pos2 - pos);
			cout << sub << endl;
		}
	}


	// rfind: 反向查找  
	//查看文件格式
	s = "test.txt.tar.gz.cpp";
	pos = s.rfind(".");
	if (pos != string::npos) {
		cout << s.substr(pos + 1) << endl;
	}

}

getline的使用

void teste() {
	string s;
	
	//cin: 不能读入带空格的字符串
	cin >> s;
	operator>>(cin, s);  //完整形式

	getline(cin, s);
	getline(cin, s, ','); //遇到 , 结束  不写第三个参数默认遇到 \n 结束
}

到这里大部分的接口都认识了一遍, 不过肯定不是所有

相信大家对string已经有了基本的认识, 那么就赶快用起来吧~

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

殇&璃

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值