C++ 6 :string类:高效处理字符串的秘密

为什么学习string类?

C 语言中,字符串是以 '\0' 结尾的一些字符的集合,为了操作方便, C 标准库中提供了一些 str 系列 的库函数,但是这些库函数与字符串是分离开的,不太符合OOP 的思想,而且底层空间需要用户 自己管理,稍不留神可能还会越界访问。

标准库中的string

推荐文档:string - C++ Reference
使用 string 类时,必须包含 #include 头文件以及 using namespace std ;

在推荐文档中,我们可以看到成员功能有三个

constructor(创建)

包含7种用法

实操

cpp 复制代码
#include <iostream>
#include <string>
using namespace std;
int main()
{
	//1
	string s1;//无内容
	//4
	string s2("hello worle");
	//2
	string s3(s2);//拷贝构造

	cout << s1 << endl << s2 << endl << s3 << endl;

	cin >> s1;
	cout << s1 << endl;
	//3
	string s4(s2, 6, 10);//中间数字是字符的下标,最后一个数字是读取结束的下标吗,注意,如果没有,编译器自动填充 size_nops=-1
	string s5(s2, 6);
	cout << s4 << endl << s5 << endl;

	//5
	string s6("hello world", 7);
	cout << s6 << endl;

	//6
	string s7(10, 'a');
	cout << s7 << endl;

}

结果

destructor(析构)

不需要自己析构

operator=(赋值)

auto和范围for

auto

auto 声明指针类型时,用 auto auto* 没有任何区别,但用 auto 声明引用类型时则必须加 &
当在同一行声明多个变量时,这些变量必须是相同的类型,否则编译器将会报错,因为编译器实际 只对第一个类型进行推导,然后用推导出来的类型定义其他变量

cpp 复制代码
int main()
{
	string s1("hello world");
	auto a = 1;
	auto b = 's';
	auto c = "sjlf";
	auto e = 1, f = 22.2;//不可,必须为同一类型
	
	return 0;
}
注意事项

auto 不能作为函数的参数,可以做返回值,但是建议谨慎使用
auto 不能直接用来声明数组

范围for

对于一个有范围的集合而言,由程序员来说明循环的范围是多余的,有时候还会容易犯错误。因此 C++11中引入了基于范围的for循环。**for循环后的括号由冒号""**分为两部分:第一部分是范围

cpp 复制代码
int main()
{
	string s1("hello world");
	for (auto ch1 : s1)
		cout << ch1;
	for (auto& ch2 : s1)//加引用可以修改s1
		cout << ch2;
	return 0;
}

string类的常用接口

constructor在上述已讲
string类对象的容量操作

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

cpp 复制代码
string s1("hello world");
cout<<s1.size();//字符串有效长度
cout<<s1.length();//字符串有效长度  两个功能相同的接口是历史原因
cout<<s1.capacity();

s1.clear();//清理字符串,一般只清数据,不清容量
s1.reserve(100);//预留空间,避免频繁扩容,影响效率
s1.resize(100,'x');//调整字符串大小多出的空间用字符x填充

访问字符串

operator[]

C++重载了[],使得我们可以像数组一样访问字符串

cpp 复制代码
	string s1("hello world");
	//1
	for (int i = 0; i < s1.size(); i++)
		cout << s1[i] ;
begin+end

begin 获取一个字符的迭代器 + end 获取最后一个字符下一个位 置的迭代器

cpp 复制代码
string s1("hello world");
auto b = s1.begin();//b指向h
auto c = s1.end();//c是最后一位有效字符的下一位
rbegin+rend

rend获取一个字符的迭代器 + rbegin 获取最后一个字符下一个位 置的迭代器

cpp 复制代码
string s1("hello world");
auto b = s1.rbegin();
auto c = s1.rend();

string的对象修改操作

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

cpp 复制代码
#include <iostream>
#include <string>
using namespace std;
int main()
{
	//push_back
	string s1("hello");
	s1.push_back('x');//在s1后面加上字符x
	
	//append
	s1.append(" world");//在s1后面加上字符串

	//operator+=
	s1 += 'x';
	s1 += "world";
	
	//c_str
	const string ch = s1.c_str();
	const  char*ch1 = s1.c_str();
	cout << ch << endl;
	cout << ch1 << endl;

	//find
	size_t pos = s1.find("world");
	cout << pos << endl;
	while (size_t pos =s1.find("x") != std::string::npos)
	{
		cout << pos << endl;
		s1.find("x", pos + 1);//从上一个找到x的下标的下一个位置开始找
	}

	//rind 
	size_t pos1 = s1.rfind("x");
	cout << pos1 << endl;

	//substr
	cout << s1.substr(0, 5) << endl;
	return 0;
}

getline

获取一行字符串

在正式讲解此题之前,我们先看一道题

有上面的find和rfind,此时你可能想到用rfind查找最后一个空格,然后有效长度减去似乎就可以了

但我们要知道cin先是存储在缓存区的,它默认以换行符或空格结束,所以此处使用cin只能输入一个字符

此处再看getline


在上图中

getling第二个使用形式是默认以换行符为结束

第一个使用形式则是可以自定义结束标志

cpp 复制代码
getline(cin, s1);//默认以换行符为结束
getline(cin, s1,'x');//自定义x为结束标志

string的模拟实现

浅拷贝

浅拷贝:也称位拷贝,编译器只是将对象中的值拷贝过来 。如果 对象中管理资源 ,最后就会 导致 多个对象共享同一份资源,当一个对象销毁时就会将该资源释放掉,而此时另一些对象不知道该 资源已经被释放,以为还有效,所以当继续对资源进项操作时,就会发生发生了访问违规

cpp 复制代码
	string(const char* str="")//无参数,自动补上\0
	{
		_size = _capacity = strlen(str);//此处走函数构造不使用初始化列表是为了在不改变私有成员顺序的前提下,可以先初始化_size为后面使用
		//_capacity不包括\0
		_str = new char[_capacity + 1];//为\0预留空间
		strcpy_custom(_str, str);
	}

深拷贝

如果一个类中涉及到资源的管理,其拷贝构造函数、赋值运算符重载以及析构函数必须要显式给 出。一般情况都是按照深拷贝方式提供。

cpp 复制代码
	string(const char* s)//深拷贝
	{
		char* _str = new char[strlen(s)];
		strcpy(_str, s);
	}
cpp 复制代码
// s2 = s1 //特殊情况,s2空间小于s1 s2的空间远大于s1
string& string::operator=(const string& s)
{
	if (this != &s)
	{
	delete []_str;
	//此处清除s2再进行赋值就算为了应对上面的特殊情况
	_str = new char[s._capacity+1];
	strcpy(_str,s._str);
	_size = s._size;
	_capacity = s._capacity;
	}
	return *this;
}

现代写法

cpp 复制代码
void swap(string& s)
{
	std::swap(_str, s._str);
	std::swap(_size, s._size);
	std::swap(_capacity, s._capacity);
}

// s2(s1)
// 现代写法
string(const string& s)
{
	//建议此处要自己对私有成员进行赋值,要不然编译器一般不会对内置类型赋值,或者赋值为随机值

	string temp(s._str);//此处是拿s里面的字符串进行赋值,调拷贝构造函数,此处temp既有s的字符串,也有size capacity这些的
	swap(temp);
}
cpp 复制代码
	string& string::operator=(const string& s)
	{
		string temp(s);
		swap(temp);
	}

但现代写法的运行效率与之前的写法之间没有区别

课后建议

自己实现string的类模拟,包含常用的重点接口

去力扣上做有关字符的练习,巩固知识和加强理解

相关推荐
蓝色汪洋6 小时前
luogu迷宫寻路
算法
xu_yule6 小时前
算法基础-(数据结构)
数据结构
武帝为此6 小时前
【字典树 C++ 实现】
开发语言·c++
玩转数据库管理工具FOR DBLENS6 小时前
DBLens:开启数据库管理新纪元——永久免费,智能高效的国产化开发利器
数据结构·数据库·测试工具·数据库开发
悟能不能悟6 小时前
java 设置日期返回格式的几种方式
java·开发语言
未来之窗软件服务6 小时前
幽冥大陆(四十八)P50酒店门锁SDK 苹果object c语言仙盟插件——东方仙盟筑基期
c语言·开发语言·酒店门锁·仙盟创梦ide·东方仙盟·东方仙盟sdk
YJlio6 小时前
桌面工具学习笔记(11.1):BgInfo——给服务器桌面“刻”上关键信息
服务器·笔记·学习
爱倒腾的老唐6 小时前
00、Altium Designer 23 使用问题记录
笔记·php
while(1){yan}6 小时前
基于IO流的三个小程序
java·开发语言·青少年编程