2024年C C++最新【C++修炼之路】8(1),从思维图到基础再到深入

img
img

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化的资料的朋友,可以添加戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

1.1 string的由来

之所以抛弃char*的字符串而选用C++标准程序库中的string类,是因为他和前者比较起来,不必担心内存是否足够、字符串长度等等,而且作为一个泛型类出现,他集成的操作函数足以完成我们大多数情况下(甚至是100%)的需要。我们可以用 = 进行赋值操作,== 进行比较,+ 做串联(是不是很简单?)。我们尽可以把它看成是C++的基本数据类型。

C++中对于string的定义为:typedef basic_string<char> string; 也就是说C++中的string类是一个泛型类,由模板而实例化的一个标准类,本质上不是一个标准数据类型。

在我们的程序中使用string类型,我们必须包含头文件 。如下:
#include<string> using namespace std; 此语句必不可少,否则有的编译器无法识别

1.2 string函数列表

begin得到指向字符串开头的Iterator
end得到指向字符串结尾的Iterator
rbegin得到指向反向字符串开头的Iterator
rend得到指向反向字符串结尾的Iterator
size得到字符串的大小
length和size函数功能相同
max_size字符串可能的最大大小
capacity在不重新分配内存的情况下,字符串可能的大小
empty判断是否为空
operator[]取第几个元素,相当于数组
c_str取得C风格的const char* 字符串
data取得字符串内容地址
operator=赋值操作符
reserve预留空间
swap交换函数
insert插入字符
append追加字符
push_back追加字符
operator+=+= 操作符
erase删除字符串
clear清空字符容器中所有内容
resize重新分配空间
assign和赋值操作符一样
replace替代
copy字符串到空间
find查找
rfind反向查找
find_first_of查找包含子串中的任何字符,返回第一个位置
find_first_not_of查找不包含子串中的任何字符,返回第一个位置
find_last_of查找包含子串中的任何字符,返回最后一个位置
find_last_not_of查找不包含子串中的任何字符,返回最后一个位置
substr得到字串
compare比较字符串
operator+字符串链接
operator==判断是否相等
operator!=判断是否不等于
operator<<//td>判断是否小于
operator>>从输入流中读入字符串
operator<<字符串写入输出流
getline从输入流中读入一行

对于这些函数,我们在下面将会逐个讲解:

2.string常用接口

对于这些用法来说,下面传入的参数个数在同一个用法中都有所差异,这是因为每一个成员函数都支持了重载。

在介绍下述接口之前,需要知道的是,由于每一个函数都有重载,因此有的有const的重载,有的没有,其实这是以函数的需求从而判断其有无const类型的重载函数,这里提前进行总结:

  1. 只读功能函数 const版本
  2. 只写功能函数 非const版本
  3. 读写功能的函数 const+非const版本

1. 初始化

初始化有两种方式,其中使用等号的是拷贝初始化,不使用等号的是直接初始化。(注释后面是打印的结果)

但对于使用等号的和str(str1),即一个变量通过另一变量初始化的,都是拷贝构造。(深拷贝)

string str1 = "hello world";      // str1 = "hello world"
string str2("hello world");       // str2 = "hello world"
string str3 = str1;               // str3 = "hello world"
string str4(str2);                // str4 = "hello world"
string str5(10,'h');              // str5 = "hhhhhhhhhh"
string str6 = string(10,'h');     // str6 = "hhhhhhhhhh"
string str7(str1,6);              // str7 = "world" 从字符串str1第6个字符开始到结束,拷贝到str7中
string str\_7(str1,6,3); // str\_7 = "wor" 从字符串str1第6个字符开始的三个字符,拷贝到str7中
string str8 = string(str1,6);     // str8 = "world"
string str9(str1,0,5);            // str9 = "hello" 从字符串str1第0个字符开始,拷贝5个字符到str9中
string str10 = string(str1,0,5);  // str10 = "hello"
char c[] = "hello world";
string str11(c,5);                // str11 = "hello" 将字符数组c的前5个字符拷贝到str11中
string str12 = string(c,5);       // str12 = "hello"


2. string::npos

我们观察一下上面str7与str_7的区别,想必大家已经看出,这里的str7和str_7是同一个重载函数,并且这个函数具有缺省值,当我们不传入最后一个参数时,其就会一直拷贝到字符串的末尾为止。那这个缺省参数是什么呢?我们查阅文档得知,是npos:image-20221103131936946image-20221103132229293我们发现,npos的值规定为-1,但实际上因为是size_t类型,所以这是一个无符号的数字,即此-1并不是十进制的-1,而是:4294967295image-20221103132447610

因此,此位置重载在不输入数值时默认为此值,也就能够遍历到字符串的末尾了。

此外,对于内置的string类,是支持运算符重载的,因此同样的也支持流的相关重载,即cin、cout:

string str1 = "hello world";     
cout << "str1 = " << str1;    //输出: str1 = hello world

所以我们可以把string这个内部类当成内置类型使用。

3. c_str()

image-20221103154600994

对于string类来说,其内部有这么一个成员变量,c_str,正如此图,c_str本身和指向的值均不能改变,返回值是char*实际上返回的就是string类中的内容的地址,也就是字符串的地址。

image-20221103154953223

那c_str有什么作用呢?事实上对于一些线程,网络,Linux内核等都是通过C实现的,因此c_str很好的充当了一个C++中string与C之间的互通,因为我们知道,对于string定义的变量名,不是内部字符串的地址,因此就出现了c_str()返回内容的地址,从而解决这个问题。

演示:

image-20221103155916051

结果:

image-20221103155949557

这样就将其内容正确的打开了。

4. 获取长度(length、size)

length()函数与size()函数均可获取字符串长度。但除了string,其他类型就只有size()。

string str = "hello world";
cout << str.length() << str.size();     // 11 11


当str.length()与其他类型比较时,建议先强制转换为该类型,否则会意想之外的错误。
比如:-1 > str.length() 返回 true。

5. 容量(size、capacity)

对于size和capacity来说,大家在学了顺序表之后并不陌生,size是实际长度,而capacity代表着容量的大小,对于string类来说,其也具有这样的成员变量(对应值C语言顺序表中结构体内部的的size、capacity),而这里的扩容规则在每一个平台也是不一样的,比如我的linux和vs2019二者之间就有很大的区别,不过我们并不需要关心他,因为string作为内部类,其扩容的机制已经被写在该类之中。

string str;     
str += "hello world hello world";

image-20221103124701051

image-20221103124804439

可以看出,其大概是是扩容了二倍的大小。

此外,还有其他函数也属于容量的范畴:

image-20221103143927322

resize可以改变成员的size()的大小。

reserve可以改变成员的capacity()的大小。

image-20221103144426584

而对于max_size(),这里我们只打印结果就知道其具体的含义:

image-20221103160219800

6. 插入(insert)

image-20221103150409036

string str = "hello world";
string str2 = "hard ";
string str3 = "it is so happy wow";

//s.insert(pos,n,ch) 在字符串s的pos位置上面插入n个字符ch
str.insert(6,4,'z');        // str = "hello zzzzworld"

//s.insert(pos,str) 在字符串s的pos位置插入字符串str
str.insert(6,str2);         // str = "hello hard world"

//s.insert(pos,str,a,n) 在字符串s的pos位置插入字符串str中位置a到后面的n个字符
str.insert(6,str3,6,9);     // str = "hello so happy world"

//s.insert(pos,cstr,n) 在字符串s的pos位置插入字符数组cstr从开始到后面的n个字符
//此处不可将"it is so happy wow"替换为str3
str.insert(6,"it is so happy wow",6);       // str = "hello it is world"


虽然有这样的接口,但是我们知道对于类似于顺序表的结构来说,这样的插入,实际上底层都会将后面的数据进行挪动,因此效率难免会低一些。

7. 替换(replace)

替换与插入对应,对比理解更为简单。

image-20221103150138419

string str = "hello world";
string str2 = "hard ";
string str3 = "it is so happy wow";

//s.replace(p0,n0,n,ch) 删除p0开始的n0个字符,然后在p0处插入n个字符ch
str.replace(0,6,4,'z');           // str = "zzzzworld"

//s.replace(p0,n0,str) 删除从p0开始的n0个字符,然后在p0处插入字符串str
str.replace(0,6,str2);            // str = "hard world"

//s.replace(p0,n0,str,pos,n) 删除p0开始的n0个字符,然后在p0处插入字符串str中从pos开始的n个字符
str.replace(0,6,str3,6,9);        // str = "so happy world"

//s.replace(p0,n0,cstr,n) 删除p0开始的n0个字符,然后在p0处插入字符数组cstr的前n个字符
//此处不可将"it is so happy wow"替换为str3
str.replace(0,6,"it is so happy wow",6);        // str = "it is world"


8. 添加(append、push_back、+=)

append函数用在字符串的末尾添加字符和字符串。(同样与插入、替换对应理解)而push_back只适用于添加单个字符,此外,对于添加来说,如果是在末尾添加字符或者字符串我们仍然可以像初始化中的拷贝构造一样,即通过+=进行添加。

image-20221103150246351

image-20221103150309397

image-20221103150328171

string str = "hello world";
string str2 = "hard ";
string str3 = "it is so happy wow";
string str4 = "hello world"

//s.append(n,ch) 在当前字符串结尾添加n个字符c
str.append(4,'z');         // str = "hello worldzzzz"

//s.append(str) 把字符串str连接到当前字符串的结尾
str.append(str2);          // str = "hello worldhard "

//s.append(str,pos,n) 把字符串str中从pos开始的n个字符连接到当前字符串的结尾
str.append(str3,6,9);      // str = "hello worldso happy "

//append(cstr,int n) 把字符数组cstr的前n个字符连接到当前字符串结尾
//此处不可将"it is so happy wow"替换为str3
str.append("it is so happy wow",6);      // str = "hello worldit is "

str4.push\_back('c'); // str4 = "hello worldc"
str4 += 'c';         // str4 = "hello worldcc"
st4 += "cdef";       // str4 = "hello worldcccdef"

9. 赋值(assign)

image-20221103150026160

赋值也是一种初始化方法,与插入、替换、添加对应理解较为简单。

string str;
string temp = "welcome to my blog";

//s.assign(n,ch) 将n个ch字符赋值给字符串s
str.assign(10,'h');          // str = "hhhhhhhhhh"

//s.assign(str) 将字符串str赋值给字符串s
str.assign(temp);            // str = "welcome to my blog"

//s.assign(str,pos,n) 将字符串str从pos开始的n个字符赋值给字符串s
str.assign(temp,3,7);        // str = "come to"

//s.assaign(cstr,n) 将字符数组cstr的前n个字符赋值给字符串s
//此处不可将"it is so happy wow"替换为temp
str.assign("welcome to my blog",7);     // str = "welcome"


10. 删除与判空(erase、clear、empty)

对于clear,其具有清空的功能,也就是从任意的字符串使用clear,都会将其清空至空字符串;对于erase来说,其可以指定的删除,也就是说可以删除一部分,也可以删除全部。

string str = "welcome to my blog";

//s.erase(pos,n) 把字符串s从pos开始的n个字符删除
str.erase(11,3);           // str = "welcome to blog"
str.clear();               // str = ""
str.empty();               // 返回1

clear()实际上不会将capacity的空间也清除掉,即size会改变,但capacity并不会改变。

对于empty来说,实际上是判断是否为空的函数,是空就返回1,不是空就返回0,因此empty是根据size()是否为0判断的。

11. 剪切(substr)

image-20221103150054578

string str = "The apple thinks apple is delicious";

//s.substr(pos,n) 得到字符串s位置为pos后面的n个字符组成的串
string s1 = str.substr(4, 5);           // s1 = "apple"

//s.substr(pos) 得到字符串s从pos到结尾的串
string s2 = str.substr(17);            // s2 = "apple is delicious"

string s3 = str.substr(4, 100);        // s3 = "apple thinks apple is delicious"
string s4 = str.substr(4, string::npos);// s4 = "apple thinks apple is delicious"

12. 比较(compare)

两个字符串自左向右逐个字符相比(按ASCII值大小相比较),直到出现不同的字符或遇’\0’为止。若是遇到‘\0’结束比较,则长的子串大于短的子串,如:“9856” > “985”。如果两个字符串相等,那么返回0,调用对象大于参数返回1,小于返回-1。

image-20221103150438655

string str1 = "small leaf";
	string str2 = "big leaf";

	//s.compare(str) 比较当前字符串s和str的大小
	cout << str1.compare(str2);                   // 1

	//s.compare(pos,n,str) 比较当前字符串s从pos开始的n个字符与str的大小
	cout << str1.compare(2, 7, str2);               // -1

	//s.compare(pos,n0,str,pos2,n) 比较当前字符串s从pos开始的n0个字符与str中pos2开始的n个字符组成的字符串的大小
	cout << str1.compare(6, 4, str2, 4, 4);           // 0

	//s.compare(pos,n0,cstr,n) 比较当前字符串s从pos开始的n0个字符与字符数组cstr中前n个字符的大小
	//此处不可将"big leaf"替换为str2
	cout << str1.compare(6, 4, "big leaf", 4);       // 1

13. 交换(swap)

string str1 = "small leaf";
string str2 = "big leaf";

//或者str1.swap(str2) ,输出结果相同
swap(str1,str2);        // str1 = "big leaf" str2 = "small leaf"
swap(str1[0],str1[1]);  // str1 = "ibg leaf"


14. 反转(reverse)

反转字符串。

string str = "abcdefghijklmn";
	string::iterator it1 = str.begin();
	string::iterator it2 = str.end();
	reverse(str.begin(), str.end());       // str = "nmlkjihgfedcba"
	reverse(it1, it2);                      // 又反转了一次:str = "abcdefghijklmn"


对于str.begin()和str.end()类型,其返回值可以看成指针,但实际上并不是指针,因此我们用char* 定义的it1和it2是不对的,其在不同的平台上有不同的含义,事实上其有着迭代器的作用。(下面讲解迭代器的使用)

15. 迭代器(iterator)

迭代器实际上是一个像指针一样的东西,这是对行为来说的。需要注意的是,这里不能用char*,虽然对于vs这个平台可以使用char*,但难保其他的平台是char*,因此迭代器的底层不一定是char*,这在模拟实现中将会详细介绍。(对于迭代器来说,只要会用string类,那么vector、list等容器与其方法是一样的)

15.1 正向迭代器
//迭代器 -- 通用的访问形式
string s1("1234");
string::iterator it1 = s1.begin();//s.begin() 返回字符串s第一个字符的位置
while (it1 != s1.end())//s.end() 返回字符串s最后一个字符串的后一个位置
{
    \*it1 += 1;
    ++it1;
}
it1 = s1.begin();
while (it1 != s1.end())
{
    cout << \*it1 << " ";
    ++it1;
}           // 输出:2 3 4 5 

15.2 反向迭代器

当然,还有反向迭代器,其访问顺序与默认的迭代器相反。

string s1("1234");
string::reverse_iterator rit = s1.rbegin();
while (rit != s1.rend())
{
    cout << \*rit << " ";
    ++rit;
} // 输出: 4 3 2 1

此外,我们发现此定义名过长,因此我们也可以用auto去定义变量rit接收rbegin()。

15.3 const迭代器

当我们需要只读的时候,为了避免改变其中的值,在迭代器使用时我们就会选择const迭代器,顾名思义const迭代器能够保护迭代指向的变量不被改变,那我们实际来看一下const迭代器如何使用:

void Print(const string& s)
{
	string::const_iterator it = s.begin();//正向
	while (it != s.end())
	{
		cout << \*it << " ";
		it++;
	}
	cout << endl;
	string::const_reverse_iterator rit = s.rbegin(); //反向 可以用auto代替,即:auto rit = s.rbegin();
	while (rit != s.rend())
	{
		cout << \*rit << " ";
		rit++;
	}
	cout << endl;
}
int main()
{
	string s1("1234");
	Print(s1);
	return 0;
}

image-20221103141131313

总结:迭代器经过上述的描述,一共有四种,即正向、反向、正向const、反向const,其不能混合定义,否则会出现错误。

16. 搜索与查找(find等函数)

16.1 find()函数

image-20221103145925583

string str = "The apple thinks apple is delicious";     //长度34
string key = "apple";

//s.find(str) 查找字符串str在当前字符串s中第一次出现的位置
int pos1 = str.find(key);                  // 4

//s.find(str,pos) 查找字符串str在当前字符串s的[pos,end]中第一次出现的位置
int pos2 = str.find(key, 10);              // 17

//s.find(cstr,pos,n) 查找字符数组cstr前n的字符在当前字符串s的[pos,end]中第一次出现的位置
//此处不可将"delete"替换为str2(如果定义str2 = "delete")
int pos3 = str.find("delete", 0, 2);       // 26

//s.find(ch,pos) 查找字符ch在当前字符串s的[pos,end]中第一次出现的位置
int pos4 = str.find('s', 0);               // 15


16.2 rfind函数

image-20221103160547001

img
img

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化的资料的朋友,可以添加戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

int pos3 = str.find(“delete”, 0, 2); // 26

//s.find(ch,pos) 查找字符ch在当前字符串s的[pos,end]中第一次出现的位置
int pos4 = str.find(‘s’, 0); // 15


#### 16.2 rfind函数


![image-20221103160547001](https://img-blog.csdnimg.cn/img_convert/e356684b4da1fa1bb8fa6beeb5216bc6.png)


[外链图片转存中...(img-6JGaTNFI-1715556618923)]
[外链图片转存中...(img-8krFPdVf-1715556618924)]

**网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。**

**[需要这份系统化的资料的朋友,可以添加戳这里获取](https://bbs.csdn.net/topics/618668825)**


**一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!**

  • 15
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值