C++初阶-string类1

目录

1.为什么学习string类

1.1C语言中的字符串

1.2一个面试题

2.标准库中的string类

2.1string类(简介)

2.2auto关键字和范围for

2.2.1auto关键字

2.2.2范围for

2.3string类对象的常见构造

3.string的构造函数

4.string的遍历及修改

5.总结



1.为什么学习string类

1.1C语言中的字符串

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

1.2一个面试题

字符串相加:

https://leetcode.cn/problems/add-strings/description/

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

2.标准库中的string类

2.1string类(简介)

string类的官方文档:https://cplusplus.com/reference/string/string/?kw=string

之后我们将围绕这个文档进行一系列string类的讲解。

2.2auto关键字和范围for

2.2.1auto关键字

这不是我们现在学习的重点,不过之后要用到的。

(1)在早期C/C++中auto的含义是:使用auto修饰的变量,是具有自动存储器的局部变量,后来这个不重要了。C++11中,标准委员会变废为宝赋予了auto全新的含义即:auto不再是一个存储类型指示符,而是作为一个新的类型指示符来指示编译器,auto声明的变量必须由编译器在编译时期
推导而得。
(2)用auto声明指针类型时,用auto和auto*没有任何区别,但用auto声明引用类型时则必须加&
(3)当在同一行声明多个变量时,这些变量必须是相同的类型,否则编译器将会报错,因为编译器实际只对第一个类型进行推导,然后用推导出来的类型定义其他变量。
(4)auto不能作为函数的参数,可以做返回值,但是建议谨慎使用
(5)auto不能直接用来声明数组

#include<iostream>
using namespace std;
class Date
{ };
int main()
{
	auto a = 10;//识别出a为整型
	auto c = 10.3;//识别出c为double类型
	auto d = 10.1f;//识别出d为float类型
	Date e;
	auto f = e;//识别出f为Date类型
	auto b;//必须初始化,因为要自动识别等号右边的类型
	auto g = 0, h = 1;//g和h都为整型
	auto i, j = 1;//能识别出j为整型,而不能识别i
	auto k = 1.0, l = 2;//推导出k为double类型,而l为int类型,所以会报错
	auto& m = a;//可以用引用,写
	return 0;
}

第五点在C++11是不支持的,但是在C++20后就支持了,所以现在作为函数参数时也是没问题的。

auto func3()
{
	//没有问题
	//可以通过返回值推导类型
	return 3;
}

但若用auto作为返回值来层层调用就会很恶心,因为如果每一个都用auto作为返回类型,那么我们就需要去一直找到返回值开始确定的地方,如果有报错就非常难受,所以建议还是自己写类型吧。

int main()
{
	auto x = 1;
	auto y = &x;
	auto* z = &x;
	auto& m = x;
	return 0;
}

我们可以推导出y、z都为指针类型,m是引用。但是z和y的定义是有区别的,如果像y定义的那种写法,等号右边可为任意类型,而若用z这种写法,那么等号右边就只能为指针类型了,且z也必须为指针。

如果我们auto arr[]={4,5,6};这种写法是错误的,不能推导数组的类型。

日常中我们并不是auto用来写这种简单的类型的,我们可以用在类型名很长的时候,如之后可能用到的类型名:std::map<std::string,std::string>dict={};这种写法就不需要全部写出来了,直接用auto即可。

2.2.2范围for

(1)对于一个有范围的集合而言,由程序员来说明循环的范围是多余的,有时候还会容易犯错误。因此C++11中引入了基于范围的for循环。for循环后的括号由冒号“ :”分为两部分:第一部分是范围内用于迭代的变量,第二部分则表示被迭代的范围,自动迭代,自动取数据,自动判断结束。
(2)范围for可以作用到数组和容器对象上进行遍历
(3)范围for的底层很简单,容器遍历实际就是替换为迭代器,这个从汇编层也可以看到。

#include<iostream>
using namespace std;
int main()
{
	int arr[10] = { 0 };
	//普通写法
	for (size_t i = 0; i < 10; i++)
	{
		cout << i << " ";
	}
	cout << endl;
	//范围for写法
	for (size_t i : arr)
	{
		cout << i << " ";
	}
	cout << endl;
	return 0;
}

结果为:

这样能证明i是能作为一个局部变量来做arr[i]的打印的

若这样写:

#include<iostream>
using namespace std;
int main()
{
	int arr[10] = { 0 , 1,2,3,4,5,6,7,8,9 };
	//普通写法
	for (size_t i = 0; i < 10; i++)
	{
		cout << i << " ";
	}
	cout << endl;
	//范围for写法
	for (size_t i : arr)
	{
		cout << arr[i] << " ";
	}
	cout << endl;
	return 0;
}

则结果为:

证明这个i是一直在变化的,而且不会因为没指定范围而越界的问题,且i是一直在++的。

我们不能这样写(一般),若不修改,则需要加const;若修改则需要加引用。(要改变数组的情况下)

为什么不会修改的?

其实刚刚我说错了,就是说i是arr[i]的拷贝,i的改变不会影响arr中的值;故要这样写:

#include<iostream>
using namespace std;
int main()
{
	int arr[10] = { 0,1,2,3,4,5,6,7,8,9 };
	for (auto& e : arr)
	{
		e *= 2;
	}
	for (const auto e : arr)
	{
		cout << e << " ";
	}
	cout << endl;
	return 0;
}

这样写的打印结果是:

我们一般要加引用,防止我们在循环中修改时就不会影响arr了。

若我们把auto 改为具体类型也不会报错的,只是我想让你们记得用而已,不过建议用auto,因为之后定义的东西多了,就会很难找那个数组的定义时的开始类型了。

2.3string类对象的常见构造

这是三个默认构造函数和一个拷贝构造函数,一般我们需要注意的就是前面两个构造函数。

而我们需要的就是知道和了解这些构造函数,我们需要了解重要的string类里面的成员函数。当然我们需要根据下面的表格的函数来进行讲解:

当然这个文档不是比较好用,我们需要进入官方文档:https://legacy.cplusplus.com/reference/

搜索string 就可以看到搜索结果:

下面的内容与上张照片相同,我们需要点进

切换为:C++11版本才能看的更多重载函数(但现在我们还没学得深入,我们基本上看的是C++98版本的)。

虽然我们看到的是英文文档,我们需要看文档时用连蒙带猜的方式来得出一个函数的作用,虽然我们可以翻译,但是你看一下翻译后的界面就是很烦的:

虽然有些翻译的还行,但是这个确实看起来不是很舒服,建议还是看英文的。

进入主题吧!

3.string的构造函数

我们讲解构造函数前先说一下:不是每一个成员函数都会讲解,我们只讲重要的,有些我们只要知道它有就可以了,因为string设计的时候有些部分有重叠的地方,之后会体现的,所以有很多冗余的部分,也有一些用得不多的地方,这个是需要注意的。而且我现在是讲用法,讲完string的所有成员函数后,会自己实现那些比较重要的成员函数(只要在string类中用到的我们就简单称为成员函数,有些是相当于友元的,但是我们还是要学的)。

最常用的构造函数是1、2、3、4(2是拷贝构造)

这些构造函数重载得比较多,我们暂时只看这三个,第二个是拷贝构造,我们来猜一下第三个函数的功能,它看似是拷贝构造的重载,但因为它有额外的参数且没有给全缺省。我们需要看:

我们从(3)看出,这个函数的作用是:拷贝从pos位置开始取len个字符拷贝,len的缺省值为npos(先不管,是一个整型的最大值)。简单来说是从pos位置取len个字符拷贝到str里面。注意:虽然我们没有说是从哪个地方拷贝来的,因为成员函数第一个形参默认为this指针,而这个指针是不会显式给的,而在调用时我们是直接用this->_str(string类里面的数组)。

我们是来用的,底层如何实现我们需要到后面几篇博客里面的:string成员函数的手动实现里面再讲。

在现阶段,我只需要演示用法,先了解一下,之后会讲底层的,所以现在的内容要讲解很多。

记得包含头文件string来用这个类里面的函数。

#include<iostream>
using namespace std;
#include<string>
int main()
{
	//(1)
	string s1;
	//(4)
	//常量字符串初始化
	string s2("Hello world");
	//(2)
	string s3(s2);
	//(3)
	string s4(s2, 6, 10);
    //(3)
    string s5(s2,6);
	cout << s1 << endl;
	cout << s2 << endl;
	cout << s3 << endl;
	cout << s4 << endl;
    cout << s5 << endl;
	return 0;
}

打印结果为:

所以这就是我们的用法。

第五个函数的意思是取s的前n个字符到_str里面(初始化)。

第六个用n个c字符取初始化。

这都是根据那个文档来推出来的。

析构函数就是简单的把那些东西全部置为空等等作用,这个就不讲了。

赋值运算符重载函数:

有三个重载函数:

分别意思是:用str(string类型)去赋值给等号左边的string对象;用字符串去给string对象赋值;用一个字符取赋值。一般情况下是用第一个,。第一个也是标准的,不写也会自动生成的。

如:

#include<iostream>
using namespace std;
#include<string>
int main()
{
	string a("abcdefg");
	//(1)
	string b;
	b = a;
	//(2)
	string c ;
	c = "abcdefghijk";
	//(3)
	string d ;
	d = 'a';
	cout << a << endl;
	cout << b << endl;
	cout << c << endl;
	cout << d << endl;
	return 0;
}

运行结果如下:

4.string的遍历及修改

一般我们使用的是下标+[]的形式进行遍历的,这种方式我们不仅可以遍历还可以修改,如下使用方式:

#include<iostream>
using namespace std;
#include<string>
int main()
{
	string s2("hello world");
	//遍历方式1
	//下标加[]
	for (size_t i = 0; i < s2.size(); i++)
	{
		s2[i] += 1;
	}
	for (size_t i = 0; i < s2.size(); i++)
	{
		cout << s2[i] << endl;
	}
	return 0;
}

其中size()获取string的字符个数。当然我们在string中可以直接打印s2(<<运算符重载)cout<<s2;

而这种方式在string中用了[]运算符重载的知识,所以一般我们可以视为如下形式来实现[]的运算符重载:

#include<iostream>
using namespace std;
#include<string>
#include<assert.h>
namespace td
{
	class string
	{
		//[]运算符重载
		char& operator[](size_t pos)
		{
			assert(pos < _size);
			return _str[pos];
		}
	private:
		char* _str;
		size_t _size;
		size_t _capacity;
	};
}

在不考虑模板的情况下底层大概是这样的形式来写这个[]的运算符重载。因为返回的是引用,所以导致了[]可以修改(修改会影响string对象)。

本质上是把s2+=[i]转换为s2.operator[](i) += 1;这样是让代码可读性更高了。

但是在真正string类里面定义了两个[]的运算符重载:

若调用第一个[]的重载,则可读可写;而第二个[]的重载,则是对于const的string对象用的。
否则就涉及到权限放大了,这样不会让const string对象修改了。

如:

#include<iostream>
using namespace std;
#include<string>
#include<assert.h>
int main()
{
	//普通成员对象调用第一个[]的
	string s2("hello world");
	for (size_t i = 0; i < s2.size(); i++)
	{
		s2[i] += 1;
	}
	for (size_t i = 0; i < s2.size(); i++)
	{
		cout << s2[i] << " ";
	}
	cout << endl;
	//const成员对象调用第二个[]的
	const string s5("xxxxxxxx");
	s5[0] = 'y';
	return 0;
}

那么结果就为:

因为const的string对象我们只可以读不可以写。

5.总结

这篇博客讲了string的一些基本知识和一些成员函数的用法,但是由于string的成员函数比较多,所以需要下讲继续讲了,喜欢的可以一键三连哦!

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值