C++学习之STL学习:string类使用
在之前的学习中,我们初步了解到了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;//大于capacitys2.resize(20,'V');cout << "s2.size():" << s2.size() << endl;cout << "s2.capacity():" << s2.capacity() << endl;cout<<s2<<endl;//小于capacity大于sizes2.resize(13, 'V');cout << "s2.size():" << s2.size() << endl;cout << "s2.capacity():" << s2.capacity() << endl;cout << s2 << endl;//小于capacitys2.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;
}
迭代器失效问题
-
导致失效的操作:
-
修改字符串长度(如
append()
,insert()
,erase()
)。 -
重新分配内存(如
reserve()
不足时扩容)。
-
-
安全实践:
-
在修改操作后,避免使用旧的迭代器。
-
使用索引或重新获取迭代器。
-
迭代器的作用
迭代器主要有两个作用:
1.遍历修改容器中数据
2.将将容器以迭代器传递给算法(函数模板)
范围for底层上依然是迭代器
迭代器的意义:
1.统一类似的方式遍历修改容器
2.算法脱离了具体的底层结构,与底层结构解耦(降低耦合,降低关联关系)
算法独立模板实现,针对多个容器处理
下图就是相关的代码示例:(使用前要包含头文件<algorithm>)
string的遍历
函数名称 | 功能说明 |
operator[](重点) | 返回pos的位置,const string类对象调用 |
begin+end | begin获取一个字符的迭代器+end获取最后一个字符下一个位置的迭代器 |
rbegin+rend | begin获取一个字符的迭代器+end获取最后一个字符下一个位置的迭代器 |
范围for | C++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)
{//将所有空格替换为指定字符csize_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());//将所有空格替换为指定字符cfor (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常用接口的模拟实现,敬请期待
在这里求一个点赞,谢谢
封面自取:
相关文章:

C++学习之STL学习:string类使用
在之前的学习中,我们初步了解到了STL的概念,接下来我们将深入学习STL中的string类的使用,后续还会结合他们的功能进行模拟实验 目录 为什么要学习string类? 标准库中的string类 string类(了解) auto和范围…...
基于 STC89C52 的养殖场智能温控系统设计与实现
摘要 本文提出一种基于 STC89C52 单片机的养殖场环境温度智能控制系统,通过集成高精度温度传感器、智能执行机构及人机交互模块,实现对养殖环境的实时监测与自动调控。系统具备温度阈值设定、超限报警及多模式控制功能,可有效提升养殖环境稳定性,降低能耗与人工成本。 一…...
redis哨兵服务
配置主机Host67为master服务器配置主机host68为 slave服务器配置主机host69运行哨兵服务测试配置 IP地址主机名192.168.10.167redis167192.168.10.168redis168192.168.10.169redis169 步骤一:配置主机Host67为master服务器 [rootredis169 ~]# vim /etc/redis.c…...

5月24日day35打卡
模型可视化与推理 知识点回顾: 三种不同的模型可视化方法:推荐torchinfo打印summary权重分布可视化进度条功能:手动和自动写法,让打印结果更加美观推理的写法:评估模式 作业:调整模型定义时的超参数&#x…...
嵌入式<style>设计模式
每天分享一个web前端开发技巧。 今天分享的主题是,如何提升前端代码的内聚性。我们在写<style></style>的时候,往往把大量无关联的样式写在同一个<style>下,而且离相关的html元素很远,这样导致每次想修改某个元…...
Kotlin 中该如何安全地处理可空类型?
在 Kotlin 中,可空类型(如 String?)是语言设计的核心特性之一,旨在从编译时避免 NullPointerException(NPE)。 1 核心处理方式 1.1 安全调用操作符(?.) 直接调用可空对象的方法…...
基于大模型预测的视神经脊髓炎技术方案
目录 一、术前评估与预测1. 数据采集与预处理2. 大模型构建与训练3. 术前风险评估与预测二、术中监测与决策支持1. 实时数据采集与传输2. 术中决策支持系统三、术后管理与康复1. 术后早期预警与监测2. 康复效果预测与个性化方案四、并发症风险预测与防控1. 并发症风险预测模型2…...
使用防火墙禁止程序联网(这里禁止vscode)
everything搜一下Code.exe的安装路径:D:\downloadApp1\vscode\Microsoft VS Code\Code.exe 方法:使用系统防火墙(推荐) Windows 通过防火墙阻止 VS Code: 打开 Windows Defender 防火墙(控制面板 > 系统…...

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

前端流行框架Vue3教程:24.动态组件
24.动态组件 有些场景会需要在两个组件间来回切换,比如 Tab 界面 我们准备好A B两个组件ComponentA ComponentA App.vue代码如下: <script> import ComponentA from "./components/ComponentA.vue" import ComponentB from "./…...

Unity3D仿星露谷物语开发48之显示树桩效果
1、目标 砍完橡树之后会露出树桩,然后树桩可以用斧头收割,并将创建一个新的砍树桩的粒子效果。 这里有:一种作物收获后创造另一种作物的逻辑。 2、分析 在SO_CropDetailsList中,Harvested Transform Item Code可以指定收获后生…...

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

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

DB-GPT扩展自定义Agent配置说明
简介 文章主要介绍了如何扩展一个自定义Agent,这里是用官方提供的总结摘要的Agent做了个示例,先给大家看下显示效果 代码目录 博主将代码放在core目录了,后续经过对源码的解读感觉放在dbgpt_serve.agent.agents.expand目录下可能更合适&…...
基于SamOutV8的序列生成模型实现与分析
项目概述 本项目实现了基于SamOutV8架构的序列生成模型,核心组件包括MaxStateSuper、FeedForward和DecoderLayer等模块。通过结合自注意力机制与状态编码策略,该模型在处理长序列时表现出良好的性能。 核心组件解析 1. MaxStateSuper(状态编…...

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

前端框架token相关bug,前后端本地联调
今天我搭建框架的时候,我想请求我自己的本地!然后我自己想链接我自己的本地后端,我之前用的前端项目,都是链别人的后端,基本上很少情况会链接自己的后端!所以我当时想的是,我前后端接口一样&…...
PyQt学习系列05-图形渲染与OpenGL集成
PyQt学习系列笔记(Python Qt框架) 第五课:PyQt的图形渲染与OpenGL集成 一、图形渲染概述 1.1 为什么需要图形渲染? PyQt默认基于2D绘图(QPainter),但某些场景需要高性能3D图形或复杂视觉效果…...

卷积神经网络(CNN)可视化技术详解:从特征学到演化分析
在深度学习领域,卷积神经网络(CNN)常被称为“黑箱”,其内部特征提取过程难以直接观测。而 可视化技术 是打开这一“黑箱”的关键工具,通过可视化可直观了解网络各层学到了什么、训练过程中如何演化,以及模型…...
第十天的尝试
目录 一、每日一言 二、练习题 三、效果展示 四、下次题目 五、总结 一、每日一言 哈哈,十天缺了两天,我写的文章现在质量不高,所以我可能考虑,应该一星期或者三四天出点高质量的文章,同时很开心大家能够学到知识&a…...
WHAT - 兆比特每秒 vs 兆字节每秒
文章目录 Mbps 解释Mbps 和 MB/s(兆字节每秒)换算总结网络场景1. 在路由器设置中的 Mbps2. 在游戏下载时的 Mbps / MB/s总结 Mbps 解释 首先,Mbps 是一个常见的网络带宽单位,意思是: Megabits per second(…...
业务场景中使用 SQL 实现快速数据更新与插入
一、业务背景 在气象数据处理系统中,我们经常需要对分钟级的降水数据进行更新和插入操作。具体场景如下: • 数据源会定期发送分钟级的降水数据,包括降水值(PRECA)和质控码(PRECA_QC2)。 • …...

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中常见的几种排序算法: 冒泡排序(Bubble Sort)选择排序(Selection Sort)插入排序(Insertion Sort)希尔排序(Shell Sort)归并排序(Merge Sort)…...

WebRTC与RTSP|RTMP的技术对比:低延迟与稳定性如何决定音视频直播的未来
引言 音视频直播技术已经深刻影响了我们的生活方式,尤其是在教育、医疗、安防、娱乐等行业中,音视频技术成为了行业发展的重要推动力。近年来,WebRTC作为一种开源的实时通信技术,成为了音视频领域的重要选择,它使得浏览…...

spring cloud alibaba Sentinel详解
spring cloud alibaba Sentinel详解 spring cloud alibaba Sentinel介绍 Sentinel 是阿里巴巴开源的一款动态流量控制组件,主要用于保障微服务架构中的服务稳定性。它能够对微服务中的各种资源(如接口、服务方法等)进行实时监控、流量控制、…...
Kafka + Flink + Spark 构建实时数仓全链路实战
本文聚焦如何通过 Kafka + Flink + Spark 构建一套稳定、可扩展、可插拔的实时数仓体系。覆盖从数据接入、实时清洗、指标计算,到离线补数、数据一致性保障的完整链路设计,结合实践样例提供可复制的落地方法。 🧱 一、架构总览 ┌────────────┐│ 数据源 …...

React19源码系列之渲染阶段performUnitOfWork
在 React 内部实现中,将 render 函数分为两个阶段: 渲染阶段提交阶段 其中渲染阶段可以分为 beginWork 和 completeWork 两个阶段,而提交阶段对应着 commitWork。 在之前的root.render过程中,渲染过程无论是并发模式执行还是同…...
Redis中的事务和原子性
在 Redis 中,事务 和 原子性 是两个关键概念,用于保证多个操作的一致性和可靠性。以下是 Redisson 和 Spring Data Redis 在处理原子性操作时的区别与对比: 1. Redis 的原子性机制 Redis 本身通过以下方式保证原子性: 单线程模型…...