【C++程序员的自我修炼】string 库中常见的用法 (一)

唤起一天明月
照我满怀冰雪
浩荡百川流
鲸饮未吞海

剑气已横秋


目录

 string 库的简介

string 的一些小操作

构造函数的使用

拷贝构造的常规使用

指定拷贝内容的拷贝构造

拷贝字符串开始的前 n 个字符

用 n 个字符初始化 

计算字符串的长度

string 的三种遍历方式

常规的for循环

operator[]运算符重载

迭代器遍历

auto 自动类型推导

string 的追加字符或字符串

从 string 末尾插入一个字符

string 的追加一个字符串

 

契机 ✨ 

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

举个栗子~比如 strcpy C语言 中我们知道这个函数的作用是将一块空间拷贝到另一块空间

首先两个空间都需要自己提供,而且要保证目标的空间和拷贝空间是一样大的或者比它大的

以上的例子我们将 s1 拷贝到 s2 中,如果 s2 的空间小于 s1strcpy 是不会管的,这个时候就会发生越界访问,像这类我们既要管空间又要管方法的函数用起来是很麻烦的

为了方便、快捷的进行我们字符串的编程,在我们 C++ 中就提供了字符串类也就是 string

为了更好的学习 string ,我们最好要学会看 string 库的文档:string 文档


 string 库的简介

string 是表示字符串的字符串类                                                                                                   
该类的接口与常规容器的接口基本相同,再添加了一些专门用来操作 string 的常规操作           

通过以上的文档信息我们可以知道: 

string 其实就是一个模板,而 basic_string 就是模板类的别名:

typedef basic_string<char, char_traits, allocator> string

这里还有个点需要注意:

string 不能操作多字节或者变长字符的序列                                                                                      

剩下的就是别忘记包头文件哦(#include<string>) ~ 还有域限定符


string 的一些小操作

构造函数的使用

我们知道 string 是一个类,那么我们以前在类中的常规操作在 string 中能直接用吗?

-- 比如说:拷贝构造

我们发现 string 的设计其实是有点冗余的,单单一个拷贝构造其形式竟有 7 中之多

先来看一下我们熟悉的那几种的拷贝构造的用法

拷贝构造的常规使用
#include<iostream>
#include<string>
using namespace std;

void TestString()
{
	// 创建一个 string 类
	string s1;
	// 构造
	string s2("hello world");
	cout << s2 << endl;
	// 拷贝构造
	string s3(s2);
	cout << s3 << endl;
	// 赋值构造
	string s4;
	s4 = s2;
	cout << s4 << endl;
	// 隐式类型转化,将字符串类型传化成自定义类型
	string s5 = "hello world";
	cout << s5 << endl;
}

int main()
{
	TestString();
	return 0;
}

因为 string 库中已将帮我们写好了,我们放心用即可~


指定拷贝内容的拷贝构造
string (const string& str, size_t pos, size_t len = npos);

我们先来分析以上代码, 参数提供了一个字符串和两个无符号整型,结合文档我们不难理解:

str 字符串的第 pos 位置开始拷贝,直到拷贝完 npos 个字符结束

如果 npos 超出了 str 后面的字符长度就拷贝到结尾,如果不写 npos 默认拷贝考结尾

我们来验证一下: 

void TestString()
{
	string s1("I Love You");
	string s2(s1, 2, 4);
	cout << s2 << endl;
	string s3(s1, 2, 15);
	cout << s3 << endl;
	string s4(s1, 2);
	cout << s4 << endl;
}

先分析:
s1 是我们从第二个位置开始拷贝的,拷贝长度为 4 个字节也就是 Love

s2 是我们从第二个位置开始拷贝的,但是拷贝的字符长度超出了 str 后面的长度,所以是拷贝剩下的所有字符,也就是 Love You

s3 是我们从第二个位置开始拷贝的,没有写 npos 参数,所以是拷贝剩下的所有字符,也就是 Love You

注意:这里传的 pos 位置是下标哦 ~

这里可能有老铁会问,为什么 npos 不传参就是拷贝剩下的全部呢?

我们这里先看一下文档:

因为不传参,编译器就会给系统默认的值,也就是 -1
static const size_t npos = -1;

注意:这里的 -1 并不是真正的 -1,为什么无符号整会有负数?因为 -1 存的是它的补码就是全 F 。整型的范围大概是 21亿 多,而 size_t 就是 42亿 多。可想而知 npos 的值有多大!

我们简化来看其实不传 npos 的本质和传超过 str 的长度的 npos 是一样的,但是会比较方便


拷贝字符串开始的前 n 个字符
string (const char* s, size_t n);

 这个比较简单我们直接测试一下吧

void TestString()
{
	string s1("I Love You", 6);
	cout << s1 << endl;
}


用 n 个字符初始化 
string (size_t n, char c);

  这个比较简单我们直接测试一下吧

void TestString()
{
	string s1(10, 'x');
	cout << s1 << endl;
}

 string 设计的有些许冗余,我们只要掌握常规的构造就行,其他的方法最好也要了解一下


计算字符串的长度

为了更好的学习这两个函数,我们先参考一下文档:

我们发现这两个函数的功能都是一样的,都是返回 \0 之前的字符串长度:

void TestString()
{
	string s1("Hello World");
	cout << s1.size() << endl;
	cout << s1.length() << endl;
}

那么有两个都可以用,那么最好用哪一个呢? -- 建议用 size(与 STL 库对应)

在这里穿插讲个题外的小故事,C++ 委员初次设计 string 的时候是朝着 STL 方向去做的,当时惠普工作室已经有了一套模板库 -- STL,C++ 委员会觉得很好就规定为标准了。

大家可以看到 string 的许多设计和 STL 是一样的,比如说 -- 迭代器

length 算是 string 的专属,对于 STL 库中计算长度就只有 size

为了与 STL 同步,我们最好在 string 也用 size 


string 的三种遍历方式

常规的for循环
void TestString()
{
	string s1("hello world");
	for (int i = 0; i < s1.size(); i++)
	{
		cout << s1[i];
	}
}
operator[]运算符重载

这里我想问各位老铁一个问题为什么字符串能 [ ] 访问单个字符呢?

-- 因为 [ ] 符号重载了

其实本质是这样的

s1.operator[](i)

 这样就可以像数组一样访问

我们可以看看 operator[ ] 的底层逻辑

class string
{
public:
	char& operator[](size_t i)
	{
		assert(i < _size);
		return _str[i];
	}
private:
	char* _str;
	size_t _size;
	size_t _capacity;
};

 这里的 & 除了减少拷贝外,还有修改返回对象的良效

我们来看:

void TestString()
{
	string s1("hello world");
	for (int i = 0; i < s1.size(); i++)
	{
		s1[i]++;
		cout << s1[i];
	}
}

这样我们不仅能访问字符串的元素还能修改!!!

还有一点如果我们的数组发生了越界,string 是可以检查出来的

错误示范:

void TestString()
{
	string s1("hello world");
	s1[20];
}

string 会报错提示,如果是平时我们数组越界是检查不出来的访问的是其他空间的值

所以 string 有点香 ~


迭代器遍历

我们先来了解一下迭代器的基本原理(先来参考一下文档)

现阶段我们这样片面的理解为 :就是类似于两个指针,begin() 指向字符串的首字符,end()指向字符串的末字符 ~

注意:begin()、end() 并不是指针(只是用法类似),它们的主要内容我们在下篇 STL库 再来讲:

我们可以先看看它们的类型:(typeid().name()显示一个对象的类型

cout << typeid(it1).name() << endl;

 打印出来就是这一长串 “东西” ,别急以后会解释,接下来我们实现一下迭代器的遍历方法

void TestString()
{
	string s1("hello world");
	string::iterator it1 = s1.begin();
	while (it1 != s1.end())
	{
		cout << *it1;
		it1++;
	}
	cout << endl;
}

这样就写完了 ~ 感觉不如第一种写法简洁,但是以后的用处很大比如:可以遍历链表和树形等 

我们可以先来感受一下:

#include<iostream>
#include<list>
using namespace std;

void Test()
{
	list<int> lt1;
	lt1.push_back(1);
	lt1.push_back(2);
	lt1.push_back(3);
	list<int>::iterator it = lt1.begin();
	while (it != lt1.end())
	{
		cout << *it << " ";
		it++;
	}
}

int main()
{
	Test();
	return 0;
}

或者以后写快排的时候也可以写迭代器:

sort(a.begin(), a.end());

表示对整个数据元素进行排序

void TestString()
{
	string s1("hello world");
	string::iterator it1 = s1.begin() + 6;
	while (it1 != s1.end())
	{
		cout << *it1;
		it1++;
	}
	cout << endl;
}

 我们还可以通过改变迭代器的位置来达到我们想要的遍历效果

总结:

begin:任何容器返回第一个数据位置的iterator

end:任何容器返回最后数据的下一个位置的iterator

我们可以通过改变迭代器的始末位置来达到遍历需求

 


auto 自动类型推导
void TestString()
{
	string s1("hello world");
	for (auto i : s1)
	{
		cout << i;
	}
	cout << endl;
}

最简单的遍历方式,不过也有局限性 -- 只能从头遍历到尾(注意:底层是迭代器哦 ~ )


 

string 的追加字符或字符串

从 string 末尾插入一个字符

void TestString()
{
	string s1("hello world");
	s1.push_back('!');
	cout << s1 << endl;
}

相当于我们在链表中学的尾插,不过只能插入一个字符哦 ~

string 的追加一个字符串

个人感觉 string 的这个设计也有些冗余,其实 push_back 传个 char*s 就够了(疯狂吐槽)

我们来看一下 append 的用法:

void TestString()
{
	string s1("hello ");
	string s2("world");
	cout << s1.append(s2) << endl;
}

 讲到这里我再来介绍一下 另一种方式 (运算符重载 += )

我们来看一下,这里追加 字符 或者 字符串 都是可以的

void TestString()
{
	string s1("hello ");
	s1 += "world";
	cout << s1 << endl;
	s1 += '!';
	cout << s1 << endl;
}


 先介绍到这里啦~

有不对的地方请指出💞

评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

烟雨长虹,孤鹜齐飞

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

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

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

打赏作者

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

抵扣说明:

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

余额充值