C++学习之STL学习:string类使用

news2025/5/26 2:48:37

         在之前的学习中,我们初步了解到了STL的概念,接下来我们将深入学习STL中的string类的使用,后续还会结合他们的功能进行模拟实验

目录

为什么要学习string类?

          标准库中的string类

        string类(了解)

        auto和范围for

         auto关键字

关于    typeid(b).name()    的解析

auto真正的应用

范围for

     string常用接口说明

        string的构造方式

          string的析构 ​编辑

         string=运算符重载

string类对象的容量操作 

size

length

max-size

capacity

clear

empty

shrink_to_fit 

reserve

resize

迭代器

迭代器分类

  正向迭代器

 反向迭代器

const迭代器

const反向迭代器 

总源代码为:

迭代器失效问题

        迭代器的作用  

迭代器的意义:

string的遍历

一、运算符重载介绍

operator[]

at

back

front

二、迭代器

三、范围for (C++11)

string类对象的修改操作

push back 

pop back

operator+=

append

assign

insert 

erase

replace

swap

string类的操作

c_str 

data ​编辑​

get_allocator

copy

substr

find

rfind

find_first_of

find_last_of 

find_first_not_of 

find_last_not_of

​编辑​ compare

string的成员常量:npos 

string类非成员函数

operator+

operator>>(重点)

operator<<(重点)

getline(重点)

relation operators(重点)

swap

一些string类的例题

1、字符串相加

        2.仅仅反转字母

      3.查找字符串中第一个唯一的字符

4.字符串最后一个单词的长度

5.验证回文字符串

VS和g++下string结构的说明

        VS下string结构说明

        string下string结构说明


为什么要学习string类?

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

          标准库中的string类

        string类(了解)

        官方string类的介绍文档:

        <string> - C++ 参考

        string - C++ 参考

          在使用string类的时候,必须要包含头文件<string.h>以及

using namespace std

        auto和范围for

         auto关键字

         早期C/C++中auto的含义是:使用auto修饰的变量是具有自动储存器的局部变量,后来这个不重要了。在C++11 中auto有了全新的含义:auto不再是一个储存类型的指示符,而是作为一个新的类型指示符来指示编译器,auto声明的变量必须由编译器在编译时期推导而得。       

         用auto声明指针类型时,用auto和auto*没有任何区别,但是用auto声明引用类型时候必须加&,否则只是拷贝而无法通过它去改变指向的值

	int main()
    {
        int x = 10;
	    auto y = x;
	    auto *z= &x;
	    auto& m = x;
	    cout<<typeid(y).name()<<endl;
	    cout<<typeid(z).name()<<endl;
	    cout<<typeid(m).name()<<endl;
	    return 0;
    }

这么写也可以

auto p1=&x;
auto* p2=&x;

但是不可以这么写

auto *p2=x;

        当同一行声明多个变量的时候,这些变量必须是相同类型的,否则编译器会报错。因为编译器实际上只对第一个类型进行推导,然后用推导出来的类型去定义其他变量

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

#include <iostream>
using namespace std;
int func1()
{
	return 10;
}
//不能做参数
void func2(auto a)
{}
//可以做返回值但是谨慎使用
auto func3()
{
	return 10;
}
int main()
{
	int a = 10;
	auto b = a;
	auto c = 'a';
	auto d = func1();
	//编译错误:“e”包含类型“auto”必须要有初始值
	auto e;
	cout<<typeid(b).name()<<endl;
	cout<<typeid(c).name()<<endl;
	cout<<typeid(d).name()<<endl;
	return 0;
}

        auto不能直接声明数组

int main()
{
    auto aa = 1, bb = 2;
	//编译错误:声明符列表中,“auto”必须始终推到为同一类型
	auto cc = 3, dd = 4.0;
	//编译错误:不能将“auto[]”用作函数参数类型
	auto arr[] ={4,5,6};
	return 0;
}
关于    typeid(b).name()    的解析

typeid(b):
        使用 typeid 运算符获取变量 b 的类型信息,返回一个 std::type_info 对象的常量引用。

        作用:在运行时(RTTI,Run-Time Type Information)或编译时获取类型信息。

        头文件:需包含 <typeinfo>。

.name():
        std::type_info 的成员函数,返回一个表示类型名称的字符串(格式取决于编译器实现)。

        输出示例:

        GCC/Clang:i(表示 int)

        MSVC:intcout << ... << endl:
        输出 typeid(b).name() 返回的字符串,并换行

auto真正的应用
#include <iostream>
#include<string>
#include<map>
using namespace std;
int main()
{
	std::map<std::string, std::string> dict = { {"apple","苹果"},{"orange","橘子"},{"pear","梨"}};
	//auto的应用
	//std::map<std::string, std::string>::iterator it = dict.begin();
	auto it =dict.begin();
	while (it != dict.end())
	{
		cout<<it->first<<" "<<it->second<<endl;
		++it;
	}
	return 0;
}

范围for

        对于有范围的集合而言,程序员说明循环的范围是多余的,有时候还会容易犯错误。因此C++11引入了基于范围的for循环。for循环后括号由冒号“:”分为两部分:第一部分是范围用于迭代的变量,第二部分则表示被迭代的范围,自动迭代自动取数据,自动判断结束

        范围for可以作用到数组和容器对象上遍历。       

        范围for的底层很简单,容器遍历实际上就是替换为迭代器,从汇编也可以看到   

#include<iostream>
#include<string>
#include<map>
using namespace std;
int main()
{
	int arr[] = {1,2,3,4,5};
	C++98的遍历
	//for (int i = 0;i < sizeof(arr) / sizeof(arr[0]);i++)
	//{
	//	cout<<arr[i]<<endl;
	//}
	//C++11的遍历
	for (auto& e : arr)
	{
		e *= 2;
	}
	for (auto&e : arr)
	{
		cout<<e<<" " << endl;
	}
	string str("hello world");
	for (auto ch : str)
	{
		cout<<ch<<" ";
	}
	cout<<endl;
	return 0;
}

     string常用接口说明

        string的构造方式

        

        他们的特点分别为: 

(constructor)构造函数名称功能说明
string(重点)构造空的string类,即空字符串
string(const char*s)(重点)用C-string来构造string类对象
string(size_t n,char c)string类对象包含n个字符c
string(const string &s)(重点)拷贝构造函数

         一个简单的string程序

#include <iostream>
#include<string>
using namespace std;
void test()
{
	//最常见的两种string构造方式
	string s1;                      //构造空字符串
	string s2 = "hello world";      //构造常量字符串
	string s3(s2);                  //拷贝构造
	string s4(s2, 1, 5);            //从s2中构造子串
	string s5(s2, 1, 50);
	string s6(s2, 1);
	const char* str = "hello world";//常量字符串
	string s7(str, 5);                  //从常量字符串构造
	string s8(100,'#');                    //从常量字符串构造
	cout << s1 << endl;
	cout << s2 << endl;
	cout << s3 << endl;
	cout << s4 << endl;
	cout << s5 << endl;
	cout << s6 << endl;
	cout << s7 << endl;
	cout << s8 << endl;
}
int main()
{
	test();
	return 0;
}

结果为:

          string的析构

         string=运算符重载

        string的=运算符重载有以下三种

        作用是字符串赋值,即为字符串分配一个新值,替换其当前内容。

        参数为:

        

        返回的是this指针

string类对象的容量操作 

函数名称功能说明
size(重点)返回字符串有效字符长度
length返回字符串有效字符长度
capacity返回空间总大小
empty(重点)检测字符串释放为空串,是返回true,否返回false
clear(重点)清空有效字符
reserve(重点)给字符串预留空间
resize(重点)讲有效字符的个数改成n个,多出的空间用字符c填充
shrink_to_fit 给字符串缩容以适配合适的大小

参考文献:string - C++ 参考

以下进行介绍

size

length

max-size

  max_size的结果取决于编译器,且其值在应用中意义不大,仅具有理论的价值

capacity

  size与capacity均不包含'\0'

代码举例:

#include<iostream>
using namespace std;
void string_test2()
{
	string s1("1234567");
    //size与capacity均不包含'\0'
	cout<<s1.size()<<endl;
	cout<<s1.length()<<endl;
}
int main()
{
	//string_test1();
	string_test2();
	return 0;
}

结果为: 

 

capacity的扩容

#include<iostream>
using namespace std;
void string_test2()
{
	string s2;
	size_t old = s2.capacity();
	cout<<"初始容量:"<<old<<endl;
	for (size_t i=0;i<50;i++)
	{
		s2.push_back('a');
		if (s2.capacity() != old)
		{
			cout<<"s2.capacity():" <<s2.capacity() << endl;
			old = s2.capacity();
		}
	}
}
int main()
{
	string_test2();
	return 0;
}

结果为:

结论:除了第一次扩容为2倍,后续均为1.5倍的扩容

但是在Linux系统的g++编译器上,该扩容均为2倍扩容

clear

-

#include<iostream>
using namespace std;
void string_test2()
{
	string s1("1234567");
	cout<<s1.size()<<endl;
	cout<<s1.length()<<endl;
	cout << s1  << endl;
	s1.clear();
	cout << s1 << endl;
}
int main()
{	
	string_test2();
	return 0;
}

结果为:

可以看到,s1 只打印了一次,第二次已经被清空了

clear只会清楚size的内存,一般不会改变capacity的内存

#include<iostream>
using namespace std;
void string_test2()
{
    string s2("hello world");
    size_t old = s2.capacity();
	cout << "s2.capacity():" << s2.capacity() << endl;
	cout << "s2.size():" << s2.size() << endl;
	//clear只清理size,一般不变capacity的内存
	s2.clear();
	cout << "s2.capacity():" << s2.capacity() << endl;
	cout << "s2.size():" << s2.size() << endl;
}
int main()
{	
	string_test2();
	return 0;
}

结果为:

empty

#include<iostream>
using namespace std;
void string_test2()
{
	string s1("1234567");
	cout<<s1.size()<<endl;
	cout<<s1.length()<<endl;
	cout << s1  << endl;
	s1.clear();
	cout << s1 << endl;
    if (s1.empty() == true)
	{
		cout<<"s1 is empty"<<endl;
	}
}
int main()
{	
	string_test2();
	return 0;
}

结果为:

shrink_to_fit 

        缩容的代价很大 

        缩容的本质是异地创建一个新的空间,放弃所有没有应用的空间。然后存入其中,是一种以时间换空间的行为

reserve

        扩容接口

#include<iostream>
using namespace std;
void string_test2()
{
	string s2("hello world");
	size_t old = s2.capacity();	
	cout << "s2.capacity():" << s2.capacity() << endl;
	cout << "s2.size():" << s2.size() << endl;
	s2.reserve(100);
	cout << "s2.capacity()经过reserve后:" << s2.capacity() << endl;
}
int main()
{	
	string_test2();
	return 0;
}

结果为:

避免扩容的做法

#include<iostream>
using namespace std;
void string_test2()
{
	string s2("hello world");
	s2.reserve(100);
	size_t old = s2.capacity();
	cout << "初始容量:" << old << endl;
	for (size_t i = 0;i < 50;i++)
	{
		s2.push_back('a');
		if (s2.capacity() != old)
		{
			cout << "s2.capacity():" << s2.capacity() << endl;
			old = s2.capacity();
		}
	}
	cout << "s2.capacity():" << s2.capacity() << endl;
	cout << "s2.size():" << s2.size() << endl;
}
int main()
{	
	string_test2();
	return 0;
}

 结果为;

应用场景:确定知道需要多少空间,提前开好,避免扩容,提高效率

但是reserve对缩容没有强制规定(g++可能缩容但是不按照预想的缩容)

resize

resize是针对length的改变

#include<iostream>
using namespace std;
void string_test2()
{
	string s2("hello world");
	cout<<"s2.size():" << s2.size() << endl;
	cout<<"s2.capacity():" << s2.capacity() << endl;
	cout << s2 << endl;
	//大于capacity
	s2.resize(20,'V');
	cout << "s2.size():" << s2.size() << endl;
	cout << "s2.capacity():" << s2.capacity() << endl;
	cout<<s2<<endl;
	//小于capacity大于size
	s2.resize(13, 'V');
	cout << "s2.size():" << s2.size() << endl;
	cout << "s2.capacity():" << s2.capacity() << endl;
	cout << s2 << endl;
	//小于capacity
	s2.resize(10, 'V');
	cout << "s2.size():" << s2.size() << endl;
	cout << "s2.capacity():" << s2.capacity() << endl;
	cout << s2 << endl;
}
int main()
{	
	string_test2();
	return 0;
}	

结果

 

当大于size的时候,补充字符;小于size的时候,打印到缩容位置即可

注意:

1. size()与length()方法底层实现原理完全相同,引入size()的原因是为了与其他容器的接口保持一致,一般情况下基本都是用size()。

2.size与capacity均不包含"\0'

3. clear()只是将string中有效字符清空,不改变底层空间大小。

4. resize(size_t n) 与  resize(size_t n, char c)都是将字符串中有效字符个数改变到n个,不同的是当字符个数增多时:resize(n)用0来填充多出的元素空间,resize(size_t n, charc)用字符c来填充多出的元素空间。注意:resize在改变元素个数时,如果是将元素个数增多,可能会改变底层容量的大小,如果是将元素个数减少,底层空间总大小不变。4. reserve(size_t res_arg=0):为string预留空间,不改变有效元素个数,当reserve的参数小于string的底层空间总大小时,reserver不会改变容量大小。

迭代器

        迭代器是STL中访问容器元素的通用接口。迭代器是一种类似指针一样的东西(但是它并不是指针且抽象程度更高)。在string类中迭代器有以下4类(2组一类)

正向迭代器、反向迭代器、const迭代器、const反向迭代器

迭代器分类

  正向迭代器

        正向迭代器主要有两个:begin和end

        他们的介绍分别是这样的

  代码举例:

#include<iostream>
using namespace std;
void string_test1()
{
	string s1 = ("hello world");
	const string s2 = ("hello world");
	string::iterator it1 = s1.begin();
	while (it1 != s1.end())
	{
		(*it1)--;
		++it1;
	}
	cout << s1 << endl;
	
}
int main()
{
	string_test1();
	return 0;
}

 结果为:

但是这么写会报错,因为涉及到了权限的放大

可以在迭代器上加const(不过指向的内容仍然不能修改)

 反向迭代器

        反向迭代器有两个:rbegin和rend

#include<iostream>
using namespace std;
void string_test1()
{
	string s1 = ("hello world");
	const string s2 = ("hello world");
	//倒着遍历
	string::reverse_iterator it3 = s1.rbegin();
	while (it3 != s1.rend())
	{
		cout << *it3 << " ";
		++it3;
	}
	cout<<endl;
}
int main()
{
	string_test1();
	return 0;
}

   结果如下:

const迭代器

const反向迭代器 

总源代码为:

#include<iostream>
using namespace std;
void string_test1()
{
	//正向迭代器
	string s1 = ("hello world");
	const string s2 = ("hello world");
	//遍历
	string::iterator it1 = s1.begin();
	while (it1 != s1.end())
	{
		(*it1)--;
		++it1;
	}
	cout << s1 << endl;
	//反向迭代器
	string::reverse_iterator it3 = s1.rbegin();
	while (it3 != s1.rend())
	{
		cout << *it3 << " ";
		++it3;
	}
	cout<<endl;
	//const迭代器
	string::const_iterator it2 = s2.begin();
	while (it2 != s1.end())
	{
		(*it2)--;
		++it2;
	}
	//const反向迭代器
	string::const_reverse_iterator it4 = s2.rbegin();
	while (it4 != s2.rend())
	{
		(*it4)--;
		++it4;
	}
}
int main()
{
	string_test1();
	return 0;
}

迭代器失效问题

  1. 导致失效的操作

    • 修改字符串长度(如append()insert()erase())。

    • 重新分配内存(如reserve()不足时扩容)。

  2. 安全实践

    • 在修改操作后,避免使用旧的迭代器。

    • 使用索引或重新获取迭代器。

        迭代器的作用  

        迭代器主要有两个作用:

        1.遍历修改容器中数据

        2.将将容器以迭代器传递给算法(函数模板)

        范围for底层上依然是迭代器

迭代器的意义:

1.统一类似的方式遍历修改容器

2.算法脱离了具体的底层结构,与底层结构解耦(降低耦合,降低关联关系)

算法独立模板实现,针对多个容器处理

下图就是相关的代码示例:(使用前要包含头文件<algorithm>)

string的遍历

函数名称功能说明
operator[](重点)返回pos的位置,const string类对象调用
begin+endbegin获取一个字符的迭代器+end获取最后一个字符下一个位置的迭代器
rbegin+rendbegin获取一个字符的迭代器+end获取最后一个字符下一个位置的迭代器
范围forC++11支持更简洁的范围for新遍历方式

一、运算符重载介绍

operator[]

我们很明显地发现:s2是不能修改的

因为它们的调用关系是这样的

举例说明:

void test()
{
	string s1("hello world");
	const string s2("hello world");
	//遍历+修改
	//下标+[]
	s1[0]++;
	/*s2[0]++;*/
	cout<<s1<<endl;
	for (size_t i = 0;i < s1.size();i++)
	{
		s1[i]++;
	}
    cout << s1 << endl;
    s1[8];//函数调用相当于s1.1operator[](8)
    //越界检查
    s1[20];
}
int main()
{
	test();
	return 0;
}

结果为:

at

at与[]的区别是at失败后会抛出异常,[]则依赖于assert断言检查越界

两处均已经报错

at的异常可以通过以下方式捕获(异常内容后续讲解)

back

front

二、迭代器

迭代器是像指针一样的类型对象

void test()
{
	string s1("hello world");
	const string s2("hello world");
	//遍历+修改
	//下标+[]
	s1[0]++;
	/*s2[0]++;*/
	cout<<s1<<endl;
	for (size_t i = 0;i < s1.size();i++)
	{
		s1[i]++;
	}
	cout << s1 << endl;
	//begin() end()返回的是一段迭代器位置的区间,形式是这样的[        )
	//迭代器
	//s1--
	//iterator迭代器
	//迭代器使用起来像指针
	string::iterator it = s1.begin(); 
	while (it!= s1.end())
	{
		(*it)--;
		++it;
	}
	cout << s1 << endl;
 
}
int main()
{
	test();
	return 0;
}

结果为:

迭代器是所有容器的主流迭代方式,迭代器具有迁移性,掌握一个其他的也可以轻松上手

	vector<int> v;
	v.push_back(1);
	v.push_back(2);
	v.push_back(3);
	v.push_back(4);
	vector<int>::iterator it2 = v.begin();
	while (it2 != v.end())
	{
		cout<<*it2<<" ";
		++it2;
	}

三、范围for (C++11)

        (这里需要结合前面的auto和范围for的拓展知识内容)

#include<iostream>
#include<string>
#include<map>
using namespace std;
int main()
{
	int arr[] = {1,2,3,4,5};
	//C++11的遍历
	for (auto& e : arr)
        //从:开始自动,特点是:
        //自动取范围中的数据赋值给e,
        //自动判断结束
        //自动迭代
	{
		e *= 2;
	}
	for (auto&e : arr)
	{
		cout<<e<<" " << endl;
	}
	string str("hello world");
	for (auto ch : str)
	{
		cout<<ch<<" ";
	}
	cout<<endl;
	return 0;
}

        范围for可以遍历vector,list等其他容器。 

        范围for本质上底层也会替换为新迭代器,即e=*迭代器

string类对象的修改操作

函数名称功能介绍
push_back在字符串中后尾插字符c
pop_back擦除字符串最后一个字符,并使得字符串有效长度-1
operator+=(重点)在字符串后追加字符串str
append在字符串后追加一个字符串
assign给字符串赋值,替换字符串内容
erase取出字符串的一部分元素,减少其长度
insert将其他字符穿插到字符串中位置pos之前
replace将字符串中从pos开始的len长度的字符替换为新内容
swap将容器中的字符与str的内容进行交换

push back 

        

#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
#include <string>
using namespace std;
void string_test1()
{
	string s1 = ("hello world");
	cout << s1 << endl;
	s1.push_back( '!');
	s1.push_back('!');
	cout << s1 << endl;
}
int main()
{
	string_test1();
	return 0;
}

    结果为:

pop back

#include <iostream>
#include <string>
using namespace std;
void string_test1()
{
	string s1 = ("hello world");
	cout << s1 << endl;
	s1.push_back( '!');
	s1.push_back('!');
	cout << s1 << endl;
	s1.pop_back();
	s1.pop_back();
	cout << s1 << endl;
}
int main()
{
	string_test1();
	return 0;
}

结果为:

operator+=

 operator+=的返回值是this指针

#include <iostream>
#include <string>
using namespace std;
void string_test1()
{
	string s1 = ("hello world");
	cout << s1 << endl;
	s1.push_back( '!');
	s1.push_back('!');
	cout << s1 << endl;
	s1.pop_back();
	s1.pop_back();
	cout << s1 << endl;
	s1.operator+=( " I love C++");
	cout << s1 << endl;
	s1.operator+=("  C");
	cout << s1 << endl;
}
int main()
{
	string_test1();
	return 0;
}

结果为:

也可以这么写

#include <iostream>
#include <string>
using namespace std;
void string_test3()
{
	string s1 = ("The song of the end world");
	cout<<s1<<endl;
	s1 += " ";
	s1 += " singer ";
	cout<<s1<<endl;
}
int main()
{
	string_test3();
	return 0;
}

append

 operator+=接口应用层面上比append更加广泛

assign

        返回值是this指针 

#include <iostream>
#include <string>
using namespace std;
void string_test4()
{
	string s1 = ("The song of the end world");
	cout<<s1<<endl;
	s1.assign(10,'~');
	cout<<s1<<endl;
}
int main()
{
	string_test4();
	return 0;
}

结果为:

注意:

1. 在string尾部追加字符时,s.push_back(c) / s.append(1, c) / s += 'c'三种的实现方式差不多,一般情况下string类的+=操作用的比较多,+=操作不仅可以连接单个字符,还可以连接字符串。

2. 对string操作时,如果能够大概预估到放多少字符,可以先通过reserve把空间预留好。

void test()
{
	vector<int> v;
	v.push_back(1);
	v.push_back(2);
	v.push_back(3);
	v.push_back(4);
	vector<int>::iterator it2 = v.begin();
	while (it2 != v.end())
	{
		cout<<*it2<<" ";
		++it2;
	}
	reverse(v.begin(), v.end());
}
 
​

insert 

void string_test4()
{
	string s1 = ("The song of the end world");
	cout<<s1<<endl;
	s1.insert(11," I love you",0,7);
	cout<<s1<<endl;
    s1.insert(0," NAME: ");
    cout<<s1<<endl;
    s1.insert(0,"A");
    cout << s1 << endl;
}
int main()
{
	string_test4();
	return 0;
}

结果为:

insert要谨慎使用:

        底层涉及到了数据挪动,效率低下

erase

npos是整型的最大值 

#include <iostream>
#include <string>
using namespace std;
void string_test5()
{
	string s1 = ("The song of the end world");
	cout<<s1<<endl;
	//小于的时候删除到指定位置
	s1.erase(1,7);
	cout<<s1<<endl;
	//大于字符长度的时候有多少删除多少
	s1.erase(1,30);
	cout<<s1<<endl;
}
int main()
{
	string_test5();
	return 0;
}

 erase也要谨慎使用

因为底层原理也是涉及到数据移动,效率低下

replace

返回值是this指针

replace默认从0开始查找

replace也要谨慎使用

void string_test5()
{
	string s1 = ("The song of the end world");
	cout<<s1<<endl;
	s1.replace(5,1," #");
	cout<<s1<<endl;
	s1.replace(5,5," #");
	cout<<s1<<endl;
}
int main()
{
	string_test5();
	return 0;
}

结果为:

例题:将一串字符串中所有空格替换为百分号

题解1:

void replace_space1(string&s,char c)
{
	//将所有空格替换为指定字符c
	size_t pos=s.find(' ');
	while (pos != string::npos)
	{
		s.replace(pos,1,1,c);
		pos = s.find(' ',pos+1);
	}
	cout<<s<<endl;
}
int main()
{
	string s1 = ("The song of the end world");
	cout<<s1<<endl;
	replace_space1(s1,'%');	
	return 0;
}

结果为:

题解2:

#include <iostream>
#include <string>
using namespace std;
void replace_space2(string& s, char c)
{
	string s6;
	s6.reserve(s.size());
	//将所有空格替换为指定字符c
	for (auto ch:s)
	{
		if (ch == ' ')
		{
			s6 += c;
		}
		else
		{
			s6 += ch;
		}
	}
	cout << s6 << endl;
}
int main()
{
	string s1 = ("The song of the end world");
	cout<<s1<<endl;
	replace_space2(s1,'%');	
	return 0;
}

结果为;

swap

swap底层结构相对复杂,后续学习了底层会进行更深入的学习

string类的操作

字符串操作字符串操作功能
c_str(重点)获取等效的C字符串(在C++中调用C的接口)
data获取字符串数据

get_allocator

获取底层应用的内存池
copy从字符串中复制字符序列
substr生成一个子字符串
find(重点)在字符串中从前向后查找内容
rfind在字符串中从后向前查找内容

find_first_of

在字符串中搜索与其参数中指定的任何字符匹配的第一个字符。
find_last_of在字符串中搜索与参数中指定的任何字符匹配的最后一个字符。

find_first_not_of

在字符串中搜索与参数中指定的任何字符都不匹配的第一个字符。

find_last_not_of

在字符串中搜索与参数中指定的任何字符都不匹配的最后一个字符。

compare

比较字符串

c_str 

c_str主要的作用是在cpp文件中调用c的接口

比如打开该文件就是这样的结果

void string_test7()
{
	string filename=("string2.cpp");
	FILE* fp=fopen(filename.c_str(),"r"); //在CPP文件下调用C的接口
	if (fp == nullptr)
	{
		cout<<"Open file failed!"<<endl;
		return;
	}
	char ch=fgetc(fp);
	while (ch!= EOF)
	{
		cout<<ch; 
		ch=fgetc(fp);
	}
}
int main()
{
	string_test7();
	return 0;
}

结果为: (仅仅展示一部分)

data 

        同c_str类似,也是未来与C语言适配而做出来的接口

get_allocator

allocator是系统库提供的内存池,这个的作用是获取底层应用的内存池

函数参数传递的是对象,模板参数传递的是类型

copy

​ 

#include <iostream>
#include <string>
using namespace std;
void string_test8()
{
	char buffer[20];
	string str("The song of the end world");
	size_t length = str.copy(buffer, 4, 10);
	buffer[length] = '\0';
	std::cout << "buffer contains: " << buffer << '\n';
}
int main()
{
	string_test8();
	return 0;
}

结果为:

​ 

但是copy的应用相对较少,应用相对较多的是如下的substr

substr

#include <iostream>
#include <string>
using namespace std;
void string_test9()
{
	string filename = "string2.cpp";
	//范围大小
	string str= filename.substr(4,filename.size()-4);
	cout<<str<<endl;
}
int main()
{
	string_test9();
	return 0;
}

结果为:

#include <iostream>
#include <string>
using namespace std;
void string_test9()
{
	string filename = "string2.cpp";
	//范围大小
	string str= filename.substr(4,2);
	cout<<str<<endl;    
	string str= filename.substr(4,filename.size()-4);
	cout<<str<<endl;
}
int main()
{
	string_test9();
	return 0;
}

结果为:

pos不能越界,否则会抛出异常

find

​ 

#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
#include <string>
using namespace std;
string string_find(const string&filename)
{
	size_t i=filename.find('.');
	if (i != string::npos)
	{
		return filename.substr(i);
	}
	else
	{
		string empty;
		return empty;
		//方式二:
		//return string();
		//方式三:
		//return "";
		//本质上是隐式类型转换(const char*->string)
	}
}
void string_test10()
{
	string filename1 = "string2.cpp";
	string filename2 = "string2.c";
	string filename3 = "string2";
	cout<< string_find(filename1)<<endl;
	cout << string_find(filename2) << endl;
	cout << string_find(filename3) << endl;
}
int main()
{
	string_test10();
	return 0;
}

结果为:

​ 

void spilt_url(const string&url)
{
	size_t pos1 = url.find(":");
	if (pos1 != string::npos)
	{
		cout<< url.substr(0,pos1)<<endl;
	}
	size_t pos2 = pos1 + 3;
	size_t pos3 = url.find("/",pos2);
	if (pos3 != string::npos)
	{
		cout<< url.substr(pos2,pos3-pos2)<<endl;
		cout<< url.substr(pos3+1)<<endl;
	}
}
void string_test11()
{
	string ur1 = "https://legacy.cplusplus.com/reference/string/string/compare/";
	//string ur2 = "https://en.cppreference.com/w/";
	spilt_url(ur1);
	//spilt_url(ur2);
}
int main()
{
	//string_test1();
	//string_test2();
	//string_test3();
	//string_test4();
	//string_test5();
	/*string s1 = ("The song of the end world");
	cout<<s1<<endl;
	replace_space2(s1,'%');	*/
	//string_test6();
	//string_test7();
	//string_test8();
	//string_test9();
	//string_test10();
	string_test11();
	return 0;
}

rfind

与find的区别是rfind是逆置寻找 

find_first_of

​ 

#include <iostream>
#include <string>
using namespace std;
void string_test12()
{
	string s1 = ("The song of the end world");
	size_t pos = s1.find_first_of("eo");
	while (pos != string::npos)
	{
		s1[pos] = '*';
		pos = s1.find_first_of("eo",pos+1);
	}
	cout<<s1<<endl;
}
int main()
{
	string_test12();
	return 0;
}

结果为:

​ 

find_last_of 

​ 

#include <iostream>
#include <string>
using namespace std;
void string_test12()
{
	string s1 = ("The song of the end world");
	size_t pos = s1.find_last_of("eo");
	while (pos != string::npos)
	{
		s1[pos] = '*';
		pos = s1.find_first_of("eo",pos+1);
	}
	cout<<s1<<endl;
}
int main()
{
	string_test12();
	return 0;
}

结果为:

 

find_first_not_of 

#include <iostream>
#include <string>
using namespace std;
void string_test12()
{
	string s1 = ("The song of the end world");
	size_t pos = s1.find_first_of("eo");
	while (pos != string::npos)
	{
		s1[pos] = '*';
		pos = s1.find_first_not_of("eo",pos+1);
	}
	cout<<s1<<endl;
}
int main()
{
	string_test12();
	return 0;
}

结果为:

 

find_last_not_of

​ compare

​重载了比较大小 

以非成员函数的形式重载函数,重载全局

与strcmp类似,按ASCII比

string的成员常量:npos 

string类非成员函数

函数功能说明
operator+尽量少用,因为传值返回,导致深拷贝效率降低
operator>>(重点)输入运算符重载
operator<<(重点)输出运算符重载
getline(重点)获取一行字符串
relation operators(重点)大小比较
swap交换两个字符串的值

operator+

operator>>(重点)

operator<<(重点)

getline(重点)

getline只有遇到换行才会结束

delim是定界,即可以自己规定边界

举例说明:

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

int main()
{
    string s;
    getline(cin, s,'!');
    cout << s << endl;
    return 0;
}

结果为:

持续输入输出:

int main()
{
    string s;
    while (cin>>s)
    {
        cout<<s<<endl;
    }
    return 0;
}

relation operators(重点)

swap

同之前的swap函数一样,后续学习底层后进行学习

一些string类的例题

1、字符串相加

本质上属于大数运算 的应用

答案:

误区,这个解题方法的时间复杂度不是O(N)而是O(N^2)。为什么会这样呢?

为什么这道题中应用了insert。insert的底层是数据的移动,类似于顺序表,时间复杂度会更高

更好的方法:

时间复杂度O(N)

        2.仅仅反转字母

题目:

    这道题本质上是类似于快速排序算法

        这道题不适合用范围for,也不适合迭代器。用下标法求解这道题:

class Solution
{
public:
    string reverseOnlyLetters(string s) 
    {
        if(s.empty())
        return s;
        size_t begin=0,end=s.size()-1;
        while(begin<end)
        {
            while(begin<end&&!isalpha(s[begin]))
            {
                ++begin;
            }
            while(begin<end&&!isalpha(s[end]))
            {
                --end;
            }
            swap(s[begin],s[end]);
            ++begin;
            --end;
        }
        return s;
    }
};

或者

class Solution
{
public:
    bool isletter(char ch)
    {
        if(ch>='a'&&ch<='z')
        {
            return true;
        }
        else if(ch>='A'&&ch<='Z')
        {
            return true;
        }
        else 
        {
            return false;
        }
    }
    string reverseOnlyLetters(string s) 
    {
        if(s.empty())
        return s;
        size_t begin=0,end=s.size()-1;
        while(begin<end)
        {
            while(begin<end&&!isletter(s[begin]))
            {
                ++begin;
            }
            while(begin<end&&!isletter(s[end]))
            {
                --end;
            }
            swap(s[begin],s[end]);
            ++begin;
            --end;
        }
        return s;
    }
};

      3.查找字符串中第一个唯一的字符

        题目:

  思路:哈希

        哈希就是映射,即建立数字与位置的映射,就像下图这样

class Solution
{
public:
    int firstUniqChar(string s) 
    {
        int count[26]={0};
        //每个字符出现的次数
        for(auto e:s)
        {
            count[e-'a']++;
        }
        for(size_t i=0;i<s.size();++i)
        {
            if(count[s[i]-'a']==1)
            {
                return i;
            }
            
        }
        return -1;
    }
};

4.字符串最后一个单词的长度

题目:

初始做法(不通过)

在VS上调试可以发现

s并没有包括T

这是为什么呢?

因为scanf和cin输入多个值的时候,默认空格和换行是多个数值的分隔,无法读取空格之后的内容

当字符串有空格的时候需要用到getline

getline(cin,s);

答案:

5.验证回文字符串

题目:

答案为:

VS和g++下string结构的说明

        VS下string结构说明

        string总共28个字节。内部结构相对复杂,先是一个联合体,联合体来定义string中字符串的存储空间:

        当字符串长度小于16时,使用内部固定的字符数组来存放

        当字符串长度大于16时,从堆上申请空间

union _Bxty
{
	//小的字符串长度
	value_type _Bxty[_BUF_SIZE];
	pointer _Ptr;
	char _Alias[_BUF_SIZE]
}_Bx;

        这种设计也是有道理的。大多数情况下字符串长度小于16,那么string对象创建好之后,内部已经有了16个字符数组的固定空间,不需要通过堆创建,效率高

        其次还有一个size_t字段保存字符串长度,一个size_t字段保存从堆上开辟空间的总容量

        最后还有一个指针做其他的事情

        故总共16+4+4+4=28字节

        string下string结构说明

        g++下string是通过写时拷贝实现的。string对象总共占4个字节,内部只包含一个指针,该指针将用来指向一块堆空间,南北部包含了如下字段:

        1、空间总大小

        2、字符串长度

        3、引用计数

        4、指向堆空间的指针,用来存储字符串  

struct _ReP_base
{
	size_type			_M_length;
	size_type			_M_capacity;
	_ATomic_word		_M_refcount;
};

  

以上就是本次的博客内容,接下来我们将着手于string常用接口的模拟实现,敬请期待

在这里求一个点赞,谢谢      

封面自取:

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2385784.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

5月24日day35打卡

模型可视化与推理 知识点回顾&#xff1a; 三种不同的模型可视化方法&#xff1a;推荐torchinfo打印summary权重分布可视化进度条功能&#xff1a;手动和自动写法&#xff0c;让打印结果更加美观推理的写法&#xff1a;评估模式 作业&#xff1a;调整模型定义时的超参数&#x…

Linux(7)——进程(概念篇)

目录 一、基本概念 二、描述进程——PCB 1.task_struct——PCB的一种 2.task_struct的内容分类 三、查看进程 1.通过系统目录查看 2.通过ps命令查看 四、通过系统调用获取进程的PID和PPID 五、通过系统调用创建进程 1.fork函数创建子进程 2.使用if来引出问题 六、L…

前端流行框架Vue3教程:24.动态组件

24.动态组件 有些场景会需要在两个组件间来回切换&#xff0c;比如 Tab 界面 我们准备好A B两个组件ComponentA ComponentA App.vue代码如下&#xff1a; <script> import ComponentA from "./components/ComponentA.vue" import ComponentB from "./…

Unity3D仿星露谷物语开发48之显示树桩效果

1、目标 砍完橡树之后会露出树桩&#xff0c;然后树桩可以用斧头收割&#xff0c;并将创建一个新的砍树桩的粒子效果。 这里有&#xff1a;一种作物收获后创造另一种作物的逻辑。 2、分析 在SO_CropDetailsList中&#xff0c;Harvested Transform Item Code可以指定收获后生…

[Datagear] 实现按月颗粒度选择日期的方案

在使用 Datagear 构建数据分析报表时,常常会遇到一个问题:如果数据的目标颗粒度是“月”,默认的日期控件却是精确到“日”的,这在用户交互和数据处理层面会带来不必要的复杂度。本文将分享两种解决方案,帮助你更好地控制日期控件的颗粒度,实现以月为单位的日期筛选功能。…

漏洞检测与渗透检验在功能及范围上究竟有何显著差异?

漏洞检测与渗透检验是确保系统安全的重要途径&#xff0c;这两种方法各具特色和功效&#xff0c;它们在功能上有着显著的差异。 目的不同 漏洞扫描的主要任务是揭示系统内已知的安全漏洞和隐患&#xff0c;这就像是对系统进行一次全面的健康检查&#xff0c;看是否有已知的疾…

DB-GPT扩展自定义Agent配置说明

简介 文章主要介绍了如何扩展一个自定义Agent&#xff0c;这里是用官方提供的总结摘要的Agent做了个示例&#xff0c;先给大家看下显示效果 代码目录 博主将代码放在core目录了&#xff0c;后续经过对源码的解读感觉放在dbgpt_serve.agent.agents.expand目录下可能更合适&…

家政维修平台实战09:推送数据到多维表格

目录 1 API调试2 创建云函数3 前端调用整体效果总结 上一篇我们搭建了服务分类的后台功能&#xff0c;对于分类的图标通过集成TOS拿到了可以公开访问的地址&#xff0c;本篇我们将写入的数据推送至多维表格中。 1 API调试 要想推送多维表格的数据&#xff0c;首先要利用官方的…

前端框架token相关bug,前后端本地联调

今天我搭建框架的时候&#xff0c;我想请求我自己的本地&#xff01;然后我自己想链接我自己的本地后端&#xff0c;我之前用的前端项目&#xff0c;都是链别人的后端&#xff0c;基本上很少情况会链接自己的后端&#xff01;所以我当时想的是&#xff0c;我前后端接口一样&…

卷积神经网络(CNN)可视化技术详解:从特征学到演化分析

在深度学习领域&#xff0c;卷积神经网络&#xff08;CNN&#xff09;常被称为“黑箱”&#xff0c;其内部特征提取过程难以直接观测。而 可视化技术 是打开这一“黑箱”的关键工具&#xff0c;通过可视化可直观了解网络各层学到了什么、训练过程中如何演化&#xff0c;以及模型…

QT之INI、JSON、XML处理

文章目录 INI文件处理写配置文件读配置文件 JSON 文件处理写入JSON读取JSON XML文件处理写XML文件读XML文件 INI文件处理 首先得引入QSettings QSettings 是用来存储和读取应用程序设置的一个类 #include "wrinifile.h"#include <QSettings> #include <QtD…

微信小程序调用蓝牙API “wx.writeBLECharacteristicValue()“ 报 errCode: 10008 的解决方案

1、问题现象 问题:在开发微信小程序蓝牙通信功能时,常常会遇到莫名其妙的错误,查阅官方文档可能也无法找到答案。如在写入蓝牙数据时,报了这样的错误: {errno: 1500104, errCode: 10008, errMsg: "writeBLECharacteristicValue:fail:system error, status: UNKNOW…

【Java基础笔记vlog】Java中常见的几种数组排序算法汇总详解

Java中常见的几种排序算法&#xff1a; 冒泡排序&#xff08;Bubble Sort&#xff09;选择排序&#xff08;Selection Sort&#xff09;插入排序&#xff08;Insertion Sort&#xff09;希尔排序&#xff08;Shell Sort&#xff09;归并排序&#xff08;Merge Sort&#xff09…

WebRTC与RTSP|RTMP的技术对比:低延迟与稳定性如何决定音视频直播的未来

引言 音视频直播技术已经深刻影响了我们的生活方式&#xff0c;尤其是在教育、医疗、安防、娱乐等行业中&#xff0c;音视频技术成为了行业发展的重要推动力。近年来&#xff0c;WebRTC作为一种开源的实时通信技术&#xff0c;成为了音视频领域的重要选择&#xff0c;它使得浏览…

spring cloud alibaba Sentinel详解

spring cloud alibaba Sentinel详解 spring cloud alibaba Sentinel介绍 Sentinel 是阿里巴巴开源的一款动态流量控制组件&#xff0c;主要用于保障微服务架构中的服务稳定性。它能够对微服务中的各种资源&#xff08;如接口、服务方法等&#xff09;进行实时监控、流量控制、…

React19源码系列之渲染阶段performUnitOfWork

在 React 内部实现中&#xff0c;将 render 函数分为两个阶段&#xff1a; 渲染阶段提交阶段 其中渲染阶段可以分为 beginWork 和 completeWork 两个阶段&#xff0c;而提交阶段对应着 commitWork。 在之前的root.render过程中&#xff0c;渲染过程无论是并发模式执行还是同…

DL00987-基于深度学习YOLOv11的红外鸟类目标检测含完整数据集

提升科研能力&#xff0c;精准识别红外鸟类目标&#xff01; 完整代码数据集见文末 针对科研人员&#xff0c;尤其是研究生们&#xff0c;是否在鸟类目标检测中遇到过数据不够精准、处理困难等问题&#xff1f;现在&#xff0c;我们为你提供一款基于深度学习YOLOv11的红外鸟类…

黑马程序员C++2024新版笔记 第4章 函数和结构体

1.结构体的基本应用 结构体struct是一种用户自定义的复合数据类型&#xff0c;可以包含不同类型的成员。例如&#xff1a; struct Studet {string name;int age;string gender; } 结构体的声明定义和使用的基本语法&#xff1a; struct 结构体类型 {成员1类型 成员1名称;成…

数据仓库,扫描量

有五种通用技术用于限制数据的扫描量&#xff0c;正如图3 - 4所示。第一种技术是扫描那些被打上时戳的数据。当一个应用对记录的最近一次变化或更改打上时戳时&#xff0c;数据仓库扫描就能够很有效地进行&#xff0c;因为日期不相符的数据就接触不到了。然而&#xff0c;目前的…

Vue3性能优化: 大规模列表渲染解决方案

# Vue3性能优化: 大规模列表渲染解决方案 一、背景与挑战 背景 在大规模应用中&#xff0c;Vue3的列表渲染性能一直是开发者关注的焦点。大规模列表渲染往往会导致卡顿、内存占用过高等问题&#xff0c;影响用户体验和系统整体性能。 挑战 渲染大规模列表时&#xff0c;DOM操作…