C++STL详解(一):string类的介绍以及基本使用

C++STL详解(一):string类的介绍以及基本使用,第1张

C++STL详解(一):string类的介绍以及基本使用

文章目录

为什么要学习string类

C语言的字符 字符的一些补充知识标准库中的string类

string类string类对象的常见构造函数string的三种遍历方式string类对象的修改 *** 作string类对象的容量 *** 作string类对象的查找等一些其他 *** 作

为什么要学习string类 C语言的字符串

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

并且在OJ中,有关字符串的题目基本以string类的形式出现,而且在常规工作中,为了简单、方便、快捷,基本都使用string类,很少有人去使用C库中的字符串 *** 作函数。

字符的一些补充知识

众所周知英文字符等符号在计算机中采用ASCII码的方式存储。

那么对于中文,日文,其他国家的文字计算机又该如何存储呢?对于中文等其他字符,ASCII码则表示不了,因为一个字节存不下,对于这些字符,则采用Unicode编码标准:它为每种语言中的每个字符设定了统一并且唯一的二进制编码,以满足跨语言、跨平台进行文本转换、处理的要求。Unicode标准下每个中文是两个字节,这样就能存储大部分的中文了。

同时也有新的字符型wchar_t来存储两个字节的字符,它能更好的表示unicode编码

//char ascall编码->英文
//如何显示中文?日文?其他国家的文字计算机如何存储呢?unicode utf-8 utf-16 utf-32 gbk
int main()
{
	char ch1 = 'a';
	char ch2 = 97;
	cout << ch1 << endl;
	cout << ch2 << endl;

	char str1[] = "中国";
	cout << strlen(str1) << endl;


	wchar_t wch;//宽字节,2byte,能更好的表示unicode编码
	char ch;
	cout << sizeof(wch) << endl;
	cout << sizeof(ch) << endl;

	return 0;
}

在正式讲string之前我给大家推荐一个网站: string类

这个是C++的官方文档,如果对于C++STL容器或者其他不清楚的地方就可以去这上面查一下。

标准库中的string类 string类
    字符串是表示字符序列的类标准的字符串类提供了对此类对象的支持,其接口类似于标准字符容器的接口,但添加了专门用于 *** 作单字节字符字符串的设计特性。string类是使用char(即作为它的字符类型,使用它的默认char_traits和分配器类型(关于模板的更多信息,请参阅basic_string)。string类是basic_string模板类的一个实例,它使用char来实例化basic_string模板类,并用char_traits和allocator作为basic_string的默认参数(根于更多的模板信息请参考basic_string)。注意,这个类独立于所使用的编码来处理字节:如果用来处理多字节或变长字符(如UTF-8)的序列,这个类的所有成员(如长度或大小)以及它的迭代器,将仍然按照字节(而不是实际编码的字符)来 *** 作。

总结:

    string是表示字符串的字符串类

    该类的接口与常规容器的接口基本相同,再添加了一些专门用来 *** 作string的常规 *** 作。

    string在底层实际是:basic_string模板类的别名,typedef basic_string string;

    不能 *** 作多字节或者变长字符的序列。

    在使用string类时,必须包含#include头文件以及using namespace std;

string类对象的常见构造函数

在C++98中有以下7种构造函数

(constructor)函数名称功能说明string() (重点)构造空的string类对象,即空字符串string(const char* s) (重点)用C-string来构造string类对象string(const stirng& s) (重点)拷贝构造函数string(size_t n,char c)string类对象中包含n个字符cstring(const string& str, size_t pos, size_t len = npos)从str对象中的pos位置开始截取len个长度的字符,如果len>str的长度,那么截取完str就结束string(const char* s,size_t n)从s指向的字符数组中复制前n个字符
int main()
{
	string s1;
	string s2("hello world");
	string s3(s2);
	string s4 = "hello world!!!";

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


	string s5(s4, 3, 5);
	cout << s5 << endl;

	char* url = "http://www.cplusplus.com/reference/string/string/string/";
	string s6(url, 4);
	cout << s6 << endl;


	string s7(10, 'x');
	cout << s7 << endl;

	s7 = s2;
	cout << s7 << endl;

	return 0;
}

string的三种遍历方式

一、下标[]

	string s2("hello world");
	
	//1.下标[]
	for (size_t i = 0; i < s2.size(); i++)
	{
		cout << s2[i] << " ";
	}
	cout << endl;

这里的size()是一个公共成员函数,返回string类对象的长度,和length()相同

通过上面图片可以看到库里面重载了两个operator[]函数,一个是可读可写的,一个是只读的。因此这里不光可以遍历s2,还可以修改s2里面的值。

	string s2("hello world");

	//三种遍历
	//1.下标[]
	for (size_t i = 0; i < s2.size(); i++)
	{
		cout << s2[i] << " ";
	}
	cout << endl;

	for (size_t i = 0; i < s2.size(); i++)
	{
		//s2.operator[](i)
		s2[i] = 'x';
	}
	cout << endl;

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

二、迭代器遍历

迭代器iterator是string中的类,所以要加域作用限定符

    string s2("hello world");
    string::iterator it = s2.begin();
	while (it != s2.end())
	{
		cout << *it << " ";
		++it;
	}
	cout << endl;

[begin(),end()) end()返回的不是最后一个数据的位置,返回的是最后一个数据的下一个位置,需要要注意的是,C++中凡是给迭代器一般都是给的[)左闭右开的区间。

这个时候可能就会有人问了:那么迭代器的意义是什么呢?

迭代器意义:像string,vector支持[]遍历,但是list,map等等容器不支持下标[]我们就要用迭代器遍历,所以迭代器是一种统一使用的方式。

	vector v = { 1, 2, 3, 4 };
	vector::iterator vit = v.begin();
	while (vit != v.end())
	{
		cout << *vit << " ";
		++vit;
	}
	cout << endl;

	list lt = { 1, 2, 3, 4 };
	list::iterator ltit = lt.begin();
	while (ltit != lt.end())
	{
		cout << *ltit << " ";
		++ltit;
	}
	cout << endl;

反向迭代器

    //反向迭代器
	string s3("123456");
	string::iterator it3 = s3.begin();
	while (it3 != s3.end())
	{
		cout << *it3 << " ";
		++it3;
	}
	cout << endl;

	string::reverse_iterator rit = s3.rbegin();
	while (rit != s3.rend())
	{
		cout << *rit << " ";
		++rit;
	}
	cout << endl;

三、范围for

C++11中提供范围for,特点:写起来简洁

依次取容器中的数据,赋值给e,自动判断结束

范围for实现的本质就是迭代器,因此也支持其他容器的遍历

    string s3("123456");
    vector v = { 1, 2, 3, 4 };
    list lt = { 1, 2, 3, 4 };
    for (auto&e : s3)
	{
		e += 1;
	}
	cout << endl;

	for (auto e : s3)
	{
		cout << e << " ";
	}
	cout << endl;

	for (auto x : v)
	{
		cout << x << " ";
	}
	cout << endl;
	
	for (auto x : lt)
	{
		cout << x << " ";
	}
	cout << endl;

string类对象的修改 *** 作

函数名称功能说明operator+=str(重点)在字符串末尾追加一个字符串push_back(ch)在字符串末尾插入一个字符append(str)在字符串末尾追加一个字符串insert(pos,str) insert(pos,n,ch)在pos位置插入一个字符串,在pos位置插入n个ch字符。尽量少用insert,因为底层实现是数组,头部或者中间插入需要挪动数据erase(pos,len)从pos位置开始删除len个长度的字符 如果不给pos与len则默认从开头的位置向后全删除pop_back(ch)在字符串末尾删除一个字符
int main()
{
	string s1;
	s1.push_back('h');
	s1.push_back('e');
	s1.push_back('l');
	s1.push_back('l');
	s1.push_back('o');

	s1.append("world");
	cout << s1 << endl;

	string s2("!!!");
	//s1.append(s2);
	s1.append(s2.begin(), s2.end());
	cout << s1 << endl;

	//实际中最喜欢用这个+=
	s1 += ' ';
	s1 += "凌峰";
	s1 += s2;
	cout << s1 << endl;

	//尽量少用insert,因为底层实现是数组,头部或者中间插入需要挪动数据
	s1.insert(0, "x");
	cout << s1 << endl;
	s1.insert(3, "yyyy");
	cout << s1 << endl;
	s1.insert(0, "yyyy");
	cout << s1 << endl;

	s1.erase(0, 1);
	cout << s1 << endl;

	s1.erase(0, 3);
	cout << s1 << endl;

	s1.erase(3, 10);
	cout << s1 << endl;

	s1.erase(3);
	cout << s1 << endl;

}

string类对象的容量 *** 作 函数名称功能说明size(重点)返回字符串有效字符长度length返回字符串有效字符长度capacity返回空间总大小empty (重点)检测字符串是否为空串,是返回true,否则返回falseclear (重点)清空有效字符(’’不是有效字符,它是标识字符)reserve (重点)为字符串预留空间,不改变有效元素个数,当reserve的参数小于string的底层空间总大小时,reserver不会改变容量大小。resize (重点)将有效字符的个数变成n个,多出的空间用字符c填充,如果不给字符c的话默认是’’。如果有效元素的个数是变大的,那么此时capacity有可能会改变,如果是有效元素的个数是减少的,那么capacity大小不变。
    string s1;
	cout << "size:" << s1.size() << endl;
	cout << "capacity:" << s1.capacity() << endl;
	cout << s1 << endl;

	s1.resize(20, 'x');
	cout << "size:" << s1.size() << endl;
	cout << "capacity:" << s1.capacity() << endl;
	cout << s1 << endl;

	string s2("hello world");
	s2.resize(20, 'x');
	cout << s2 << endl;
	cout << "size:" << s2.size() << endl;
	cout << "capacity:" << s2.capacity() << endl;

	s2.resize(5);
	cout << s2 << endl;
	cout << "size:" << s2.size() << endl;
	cout << "capacity:" << s2.capacity() << endl;


	string s3;
    s3.resize(10);
	cout << "size:" << s3.size() << endl;
	cout << "capacity:" << s3.capacity() << endl;

	s3.reserve(40);
	cout << "size:" << s3.size() << endl;
	cout << "capacity:" << s3.capacity() << endl;

下面我们再来通过代码来看一下resize与reserve的区别

	string s4;
	s4.resize(127);
	int oldCp = s4.capacity();
	for (char ch = 0; ch < 127; ++ch)
	{
		s4 += ch;
		if (oldCp != s4.capacity())
		{
			cout << "增容:" << oldCp << "->" << s4.capacity();
			oldCp = s4.capacity();
		}
	}
	cout << s4 << endl;

resize是把对象的有效字符个数变成n个,而+=ch是在对象的末尾追加字符ch,因此后面空间不够了还是需要增容的。

	string s4;
	s4.reserve(127);
	//s4.resize(127);
	int oldCp = s4.capacity();
	for (char ch = 0; ch < 127; ++ch)
	{
		s4 += ch;
		if (oldCp != s4.capacity())
		{
			cout << "增容:" << oldCp << "->" << s4.capacity();
			oldCp = s4.capacity();
		}
	}
	cout << s4 << endl;

reserve是为string对象预留空间,我们如果知道这次 *** 作需要多大的空间,那么只需要reserve一下我们就不需要增容了。

string类对象的查找等一些其他 *** 作 函数名称功能说明find + npos(重点)从字符串pos位置开始从前往后找一个字符或者字符串,返回该字符或者字符串第一次在当前字符串中出现的位置rfind从字符串pos位置开始从后往前找一个字符或者字符串,返回该字符或者字符串第一次在当前字符串中出现的位置c_str(重点)返回C格式字符串substr在str中从pos位置开始,截取n个字符,然后将其返回getline(非成员函数)获取一行字符串operator>>(非成员函数) (重点)输入运算符重载operator<<(非成员函数) (重点)输出运算符重载

下面再来说一下string的成员函数c_str()

    string s1("hello world");
	cout << s1 << endl; //调用operator<<(cout,s1)
	cout << s1.c_str() << endl;//调用operator<<(cout,const char* str)

	s1.resize(20);
	s1 += "!!!!";
	cout << s1 << endl;//调用operator<<(cout,s1)
	cout << s1.c_str() << endl;//调用operator<<(cout,const char* str)

	cout << strlen(s1.c_str()) << endl;
	cout << s1.size() << endl << endl;

调用内置类型的operator<<(cout,const char* str)输出字符串的时候遇到’’就停止了,调用自定义类型的operator<<(cout,const string& s) 输出字符串的时候遇到‘’不会停止,它会将该字符串的内容全部都打印出来

大家可能会比较好奇string的find *** 作运用在哪些场景下呢?

一、假设要求取出文件名的后缀

	//假设要求取出文件名的后缀
	//string filename = "test.txt";
	//size_t pos = filename.find('.');

	string filename = "test.txt.zip";
	size_t pos = filename.rfind('.');
	if (pos != string::npos)
	{
		//string suff(filename, pos, filename.size() - pos);
		string suff(filename, pos);
		cout << suff << endl;
	}

二、要求写一个程序分别取出域名和协议名

//取出域名
string GetDomain(const string& url)
{
    //查找"://",找到返回该字符串的起始下标位置
	size_t pos = url.find("://");
	if (pos != string::npos)
	{
		size_t start = pos + 3;
		size_t end = url.find('/',start);
		if (end != string::npos)
		{
            //由于是左闭右开区间[start,end),所以只需要end-start就能计算出域名的长度
			return url.substr(start, end - start);
		}
		else
		{
            //找不到则返回空串
			return string();
		}

	}
	else
	{
        //找不到则返回空串
		return string();
	}
}

//取出协议名
string GetProtocol(const string& url)
{
     //查找"://",找到返回该字符串的起始下标位置
	size_t pos = url.find("://");
	if (pos != string::npos)
	{
        //由于是左闭右开区间[0,pos),所以只需要pos - 0就能计算出协议名的长度
		return url.substr(0, pos - 0);
	}
	else
	{
        //找不到则返回空串
		return string();
	}
}

int main()
{
    //要求写一个程序分别取出域名和协议名
	string ur11 = "http://www.cplusplus.com/reference/string/string/rfind/";
	string ur12 = "https://tower.im/users/sign_in";
	string ur13 = "tower.im/users/sign_in";

	cout << GetDomain(ur11) << endl;
	cout << GetProtocol(ur11) << endl;

	cout << GetDomain(ur12) << endl;
	cout << GetProtocol(ur12) << endl;
}

如果我们想输入"asadg bc"这样的字符串应该怎么办呢?

scanf,cin,gets这些好像都不能帮我们解决问题,因为当他们读到空格或者回车键的时候就会停止读取

那么有什么办法来帮我们读取它呢

下面再来说一个getline函数:获取一行字符串

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

可以看到通过使用getiline函数就达到了我们想要的结果

欢迎分享,转载请注明来源:内存溢出

原文地址: https://outofmemory.cn/zaji/5713333.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-12-17
下一篇 2022-12-17

发表评论

登录后才能评论

评论列表(0条)

保存