当前位置: 首页 > news >正文

【C++】string类的常见接口的使用

欢迎拜访:Madison-No7个人主页
文章主题:string类的常见接口的使用
隶属专栏:我的C++成长日志
写作日期:2025年9月27号

目录

一、初识string

二、string类的常见接口的使用

2.1 string的构造函数

2.2 string 的析构函数

2.3 string的常见容量接口

2.4 遍历string中的元素

(1)operator[]

(2)Iterator 迭代器

(3)范围for(C++11支持):

补充知识:auto关键字

2.5 与对象修改有关的操作

2.6 与查找有关的接口

2.7 string类的非成员函数


一、初识string

  1. string是表示字符串的字符串类,可以理解为字符顺序表
  2.  该类的接口与常规容器的接口基本相同,再添加了一些专门用来操作string的常规操作。
  3.  string在底层实际是:basic_string模板类的别名,typedef basic_string<char, char_traits, allocator>string;
  4.  不能操作多字节或者变长字符的序列。

注意:使用string类时,必须包含#include<string>以及using namespace std;

二、string类的常见接口的使用

2.1 string的构造函数

 📖默认构造string()

说明:构造一个长度为0个字符的空字符串。

📖字符串构造string:string (const char* s)

说明:从c字符串复制以s为指向的以空结尾字符序列(C-string)。

📖拷贝构造string (const string& str)

说明: 使用一个string对象拷贝构造一个新的string对象             

📖string(const string & str, size_t pos, size_t len = npos)

说明:拷贝str中从字符位置pos开始并跨越len个字符的部分(如果str太短len为string::npos,复制到str的末尾)。

注意:string对象的第一个有效字符的下标是0。

其中string::npos是string类中的静态成员变量,默认为-1,因为它是size_t类型,实际上就是整型的最大值了,大概是42亿多字节,4G左右,一个string对象不会有4G那么大,所以npos完全够用。如果在调用时,不显示设置len的值,那就从pos开始复制到str的末尾。

不显示设置len:

显示设置len:

📖初始化字符串s前n个字符:string (const char* s, size_t n)

📖初始化n个字符C:string(size_t n, char c)

2.2 string 的析构函数

销毁字符串对象。

2.3 string的常见容量接口

📖size()/length()

返回字符串有效字符的长度(不包含'\0'),以字节为单位。

string s1("hello world");
cout << s1.size() << endl;//推荐使用size
cout << s1.length() << endl;//length也可以求字符串长度,为了更好的兼容C语言

📖capacity()

返回当前为字符串分配的存储空间(不包含'\0'的大小,表示能存多少个有效字符不算\0,以字节表示。

string s1("hello world");
cout << s1.capacity() << endl;

为什么s1的容量是15呢?就需要来看看capacity的扩容机制了。

capacity扩容机制:

写一个测试容量变化的代码,向一个对象中循环插入字符,只要容量变了就打印来看看。

VS2022下运行:

void testcapacitygrow()
{string s;size_t sc = s.capacity();cout << "0个字符的string容量:" << sc<<endl;cout << "容量变化的过程:" << endl;for (int i=0;i<100;i++){s.push_back('a');//向s对象中插入字符aif (sc!=s.capacity()){sc = s.capacity();cout << "目前容量:" << sc << endl;}}
}

实际上开的空间要多一个字节,多的一个字节是'\0','\0'不算作有效的空间。

所以我们不难发现,第一次是2倍扩,后续就是1.5倍扩了。

VS在底层做了特殊的处理,当所需要的空间小于16字节时,会把字符串存到栈上buf的数组里。大于16字节时,此时buf数组废弃不用了,会去堆上开辟一块空间,存到堆上。

Vs下,string的结构:

union _Bxty
{ // storage for small buffer or pointer to larger onevalue_type _Buf[_BUF_SIZE];pointer _Ptr;char _Alias[_BUF_SIZE]; // to permit aliasing
} _Bx;

string这种设计也是有一定道理的,大多数情况下字符串的长度都小于16,那string对象创建

好之后,内部已经有了16个字符数组的固定空间,不需要通过堆创建,效率高。

把同样的代码放在linux下用g++编译并运行:

可见在Linux下,是标准的2倍扩容。

小Tips:在不同的编译器下,扩容机制是不一样的。

📖reserve(size_t n=0)

为了避免频繁的扩容,用reserve()可以提前开辟空间,也就是在内存中预留n个空间,避免后续的频繁扩容,提高效率。

也就是我们中午去学校食堂吃饭,人很多,你去的时候,占下一个位置,别人看来就是这个位置有人了,当你打完饭,就直接可以到你占的位置就餐,就不需要找位置了,节约了一些时间,也就是提高了效率。

Vs下提前开100个字节的空间,但是开了比100大的空间。

g++下就是要100就给100,但是有些时候,为了内存对齐,会多一些空间。

小Tips:所以在不同的编译器下,所预留的空间大小是不确定的,但是一定大于你想要预留空间的大小n。

注意:返回string开辟或预留的空间的大小都不包含\0,实际在底层开的空间大小都要大一个字节,以存储'\0'。也就是说在Vs下预留空间111,实际上在底层是112字节。

那reserve()会不会缩容呢?

我们假设给一个字符串长度为20,容量为31,当reserve(n)中n<20时,是不会缩容的,因为这个函数不能影响string的长度和内容。当20<n<31,是不确定的,不同的编译器情况是不同的,当n>31时,会扩容。

在Vs2022编译器下调试缩容情况:以下是测试代码

void test_reserve()
{string s("12345678919876543219");cout << "size:" << s.size() << endl;cout <<"capacity:" << s.capacity() << endl;s.reserve(15);cout << "n<20时,容量为:" << s.capacity() << endl;s.reserve(25);cout << "20<n<31时,容量为:" << s.capacity() << endl;s.reserve(35);cout << "31<n时,容量为:" << s.capacity() << endl;
}

我们假设给一个字符串长度为20,容量为31,测试预留不同的空间(n),缩容的情况;

可见VS2022的策略是:预留空间(n)小于capacity时,容量就不变化,大于预留空间(n)时,就扩容。

来看看在Linux操作系统的g++编辑器下的情况:一样的测试代码

可见在g++编辑器下,reserve()不会缩容

小Tips:所以在VS和g++下,都不会缩容。

2.4 遍历string中的元素

有三种方法:

(1)operator[]

表示获取字符串的字符;

s1[0]会去调用operator[]函数,参数为字符串pos位置的下标,返回的是 pos位置字符的引用,因为string中的字符是存在堆上的,出函数不会销毁。operator[]能获取pos位置的字符,对于普通对象,指定字符串的下标能修改该位置的值。该函数的越界检查是断言检测。

既然operator[]能获取pos位置的字符,我们就能像用数组一样,遍历string中的内容。

(2)Iterator 迭代器

迭代器(Iterator) 是一种用于遍历容器(如vectorlistmap等)中元素的对象,它提供了统一的访问接口,使得开发者可以不依赖容器的具体实现来操作元素。

迭代器的作用类似于指针,但比指针更通用 —— 它可以适配不同的数据结构(数组、链表、树等),让遍历操作变得一致。

小Tips:迭代器用于遍历和访问容器的。掌握了迭代器,就可以访问所有的容器。

📖begin():

表示返回一个指向字符串第一个字符的迭代器。

📖end():

表示返回一个指向字符串末尾的下一个字符的迭代器。

📖rbegin():

表示返回一个指向字符串最后一个字符的反向迭代器(即它的反向开头)。

📖rend():

返回一个反向迭代器,指向字符串第一个字符前面的理论元素(被认为是字符串的反向结束)。

📖cbegin():

表示返回指向字符串第一个字符的const_iterator

📖cend():

表示返回一个const_iterator,指向字符串的后结束字符

📖crbegin():

表示返回一个const_reverse_iterator,指向字符串的最后一个字符。

📖crend():

表示返回一个const_reverse_iterator,指向字符串第一个字符前面的理论字符。

📖正向迭代器访问string:可读可写

string s1("hello world");
//it类似于指针
string::iterator it = s1.begin();
while (it!=s1.end())
{cout << *it << " ";it++;
}

it接收的是指向s1对象的字符串首字符的迭代器。*it类似于指针的解引用,it++类似于指针的移动,这里的*和++都做了运算符的重载,关于如何重载的,后续再做讲解。

此外,普通迭代器可以修改。但是const修饰的迭代器就只能读,不能写了。

📖反向迭代器访问string:(倒着遍历)可读可写

string s1("hello world");	
string::reverse_iterator rit = s1.rbegin();
while (rit != s1.rend())
{cout << *rit << " ";rit++;//注意这里还是++,反向即倒着走
}

📖const正向迭代器访问string:只能读,不能写

用于遍历常量字符串

const string s2(s1);
string::const_iterator cit = s2.cbegin();//s2.cbegin()返回const迭代器
while (cit!= s2.cend())
{cout << *cit << " ";cit++;
}

类似于const int * a,自己的指向能修改,指向的内容不能修改。

注意:cbegin()和cend()得匹配使用。

📖const反向迭代器访问string:只能读,不能写

用于遍历常量字符串

string::const_reverse_iterator crit = s2.crbegin();
while (crit != s2.crend())
{cout << *crit << " ";crit++;//注意这里还是++,反向即倒着走
}
cout << endl;

注意:const_reverse_iterator 是一个类型,他们之间使用_连接。

(3)范围for(C++11支持):

//自动复制,自动迭代,自动判断结束
string s1("hello world");
for (auto ch : s1)
{cout << ch << " ";
}

auto ch : s1 表示:从字符串s1中依次取出每个字符,赋值给变量chauto会自动推导出ch的类型为char),auto下面会具体讲解。

for循环后的括号由冒号分为两部分:第一部分是范围 内用于迭代的变量,第二部分则表示被迭代的范围。

📖范围for的特点:自动复制,自动迭代,自动判断结束

  1. 自动迭代:无需手动获取迭代器或控制索引,自动从容器第一个元素遍历到最后一个元素。
  2. 自动复制:自动将遍历到的字符拷贝给迭代变量(上面是ch)。
  3. 自动判断结束:当遍历字符串的\0时,自动结束。

若需要修改原字符串中的字符,应使用引用类型。

string s1("hello world");
for (auto& ch : s1)
{cout << ch << " ";
}

若只需读取元素(不修改),建议用 const 引用:for (const int& num : nums)避免不必要的拷贝,提高效率。

string s1("hello world");
for (const auto& ch : s1)
{cout << ch << " ";
}

📖范围for与迭代器的关系

范围 for 循环的底层实现依赖容器的迭代器(调用 begin() 和 end() 获取范围),因此自定义容器若要支持范围 for,需实现 begin() 和 end() 方法并返回合法迭代器。

📖建议使用情景:

范围 for 循环特别适合需要完整遍历容器无需手动控制索引 / 迭代器的场景,能显著简化代码,是 C++ 中推荐的遍历方式之一。

补充知识:auto关键字

  • auto 是一个类型说明符,主要作用是自动推导变量的类型,让编译器根据初始化表达式的类型来确定变量的具体类型,从而简化代码书写并提高灵活性。
int main()
{auto b = 10;auto c = 'a';cout << typeid(b).name() << endl;cout << typeid(c).name() << endl;return 0;
}

编译器在编译时期, 根据等号右侧的值推断变量类型。其中typeid().name() 是用于获取类型名称的机制。

  • auto声明指针类型时,用autoauto*没有任何区别,但用auto声明引用类型时则必须加&

  • 当在同一行声明多个变量时,这些变量必须是相同的类型,否则编译器将会报错,因为编译器实际只对第一个类型进行推导,然后用推导出来的类型定义其他变量
//编译器报错,变量q和w类型不同,"auto"必须推倒同一类型
//auto q = 20, w = 20.0;
  • auto不能作为函数的参数,可以做返回值,但是建议谨慎使用
  • auto不能直接用来声明数组

auto用武之地:简化代码

const string s2(s1);
//传统写法,类型冗长
//string::const_iterator cit = s2.cbegin();
// 使用auto:自动推导为迭代器类型
auto cit = s2.cbegin();
while (cit!= s2.cend())
{cout << *cit << " ";cit++;
}

2.5 与对象修改有关的操作

特别说明:operator+=在以后用的比较多,其他的要用的时候,查查文档即可。

(1)operator =

 表示为当前字符串赋一个新值,替换其当前内容。

int main()
{string s1("hello world");string s2("你好,世界");//将一个string对象赋值给另一个对象s2 = s1;cout << s2 << endl;//将一个字符串赋值给已存在的string对象s2 = "你好,C++";cout << s2 << endl;//将一个字符赋值给已存在的string对象s2 = 'c';cout << s2 << endl;return 0;
}

(2)void push_back(char c)

表示将字符c追加到字符串的末尾,使其长度增加1。

(3)append

表示追加一个字符串的拷贝到当前string对象

void test07()
{string s1("hello ");string s2("world!");cout << "追加前的string->" << s1 << endl;s1 = "hello";s1.append(s2);cout << "追加一个string对象的拷贝->" << s1 << endl;s1 = "hello";s1.append(s2,1,3);cout << "追加一个string对象的拷贝,从下标为1处开始,跨越3个字符->" << s1 << endl;s1 = "hello";s1.append("C++");cout << "追加一个字符串C++->" << s1 << endl;s1 = "hello";s1.append("C++",1);cout << "追加一个字符串C++的前1个字符->" << s1 << endl;s1 = "hello";s1.append(5, 's');cout << "追加5个字符s->" << s1 << endl;}

(4)operator+=

表示在当前对象的末尾追加字符串来扩展字符串。

void test08()
{string s1 = ("hello ");string s2 = ("world");cout << "追加前->" << s1 << endl;s1 = "hello ";s1 += s2;cout << "追加string对象的拷贝->" << s1 << endl;s1 = "hello ";s1 += "world";cout << "追加字符串s->" << s1 << endl;s1 = "hello ";s1 += 'c';cout << "追加字符c->" << s1 << endl;}

📖小Tips:

append()和operator+=都是追加字符串的,说明C++在这里设计的有点冗余了,追加字符使用operator+=相对来说更方便一些。

(5)erase

表示删除字符串的一部分,减少其长度。可间接实现头删,尾删,任意位置的删除。

void test09()
{string s1("hello");cout << "删除前->" << s1<<endl;s1.erase(0,4);cout << "删除从位置0开始,跨越4字符的部分->" << s1 << endl;s1 = "hello";s1.erase(s1.begin());cout << "删除第一个位置的字符->" << s1 << endl;s1 = "hello";s1.erase(++s1.begin(),--s1.end());cout << "删除[1,最后一个元素)范围的字符串->" << s1 << endl;}

📖小Tips:

频繁的插入和删除会影响效率,因为string就相当于动态顺序表,会涉及到挪动数据。

(6)push_back

void push_back (char c);

表示在字符串后尾插字符C;此外,与之相对的是pop_back,表示删除字符串的最后一个字符。

除了上面介绍的一些常用的字符串修改接口外,还有一些不太常用的,例如:assign(内容替换)、insert(指定位置插入)、erase(删除)、replace(部分替换)、swap(交换两个字符串),它们的使用方法都大同小异。

值得注意的是insert、erase、replace要谨慎使用,它们都是性能杀手。因为如果你频繁调用它们,可能会频繁扩容和挪动数据。

2.6 与查找有关的接口

(1)find

从字符串的pos位置开始往后查找字符或字符串,返回其在当前字符串中的位置

void test10()
{string s1("The secret of success is constancy to purpose.");string s2("success");size_t a=s1.find(s2,0);cout <<"从0位置开始找,第一次找到s2对象的内容的下标是:"<< a << endl;size_t b = s1.find("constancy",14);cout << "从下标为14位置开始找,第一次找到“constancy”的下标是:" << b << endl;size_t c = s1.find("constancy",14,5);cout << "从下标为14位置开始找,第一次找到“constancy”的前5个字符的下标是:" << c << endl;size_t d = s1.find('z',14);cout << "从下标为14位置开始找,第一次找到'z'的下标是:" << d << endl;
}

注意:该函数的返回值是第一个匹配的第一个字符的位置。如果没有找到匹配项,函数返回string::npos。npos表示整型最大值。

(2)c_str

const char* c_str() const;

返回一个指向字符串对象的指针,该字符串对象包含一个以空结尾的字符序列(即C-string),返回值类型是const char*。

void test11()
{string s1("hello C++");const char* str=s1.c_str();cout << str << endl;cout << (const void*)str << endl;printf("%p",str);
}

c_str的返回值是const char*指针类型,那str打印应该是地址呀?为什么是字符串呢?

原因是:C++ 的输出流std::cout对 char* 或 const char* 类型的指针做了重载处理,对于其他类型指针(如 int*void* 等),默认输出地址。当检测到指针指向的是字符类型时,它会默认将其视为C 风格字符串(以 \0 结尾的字符序列),并从指针指向的位置开始,依次输出字符,直到遇到字符串结束符 \0 为止。这是为了方便字符串的输出,符合日常使用习惯。

如果想打印指针的地址:需要通过强制类型转换,将 const char* 转为无类型指针const void*cout 对 void* 类型会输出地址。

c_str接口的作用:

许多 C 语言库函数(如 strlenstrcmpprintf 等)或遵循 C 风格的 API 要求传入以 \0 结尾的 const char* 类型字符串。c_str() 提供了从 C++ string 到 C 风格字符串的转换,实现了两种字符串类型的兼容。

#include<stdio.h>
#include<string>
using namespace std;
int main()
{string s1 = "hello C++";//使用C语言的printf输出,需要C风格字符串printf("%s\n",s1.c_str());//hello C++return 0;
}

(3)substr

string substr (size_t pos = 0, size_t len = npos) const;

表示在str中从pos位置开始,截取n个字符,然后将其返回。len不指定时,默认为整型最大值。

该接口常与find和rfind接口使用。

(4)rfind

表示从字符串pos位置开始往前查找字符或字符串,返回该字符或字符串在调用的字符串对象中的位置。

比如我们要在"C:\Users\Administrator\Desktop"这样一个路径中取出Desktop,就可以用到find。

但是得注意路径中的反斜杠,需要使用双反斜杠 \\(第一个 \ 用于转义第二个 \,使其被视为普通字符)。

void test12()
{string s1="C:\\Users\\Administrator\\Desktop";size_t pos=s1.rfind('\\');string SubStr = s1.substr(pos);cout << SubStr << endl;
}

📖小Tips:

除了上面介绍的一些常用接口,还有一些不常用的,比如:find_first_of(在字符串中搜索与其参数中指定的任何字符匹配的第一个字符)、find_last_of(查找最后一个匹配的)、find_first_not_of(查找第一个不匹配的)、find_last_not_of(查找最后一个不匹配的)。

个人认为把find_first_of改成find_any_of,find_last_of改为rfind_any_of这样好理解一点。

2.7 string类的非成员函数

(1)operator+(string)

返回一个新构造的字符串对象,其值是lhs中的字符与rhs中的字符的连接,即实现字符串加字符串。

此函数被重载成了全局函数,这样就能实现对象加字符串之间的顺序任意性。

(2)operator>>(string)     operator<< (string)

istream& operator>> (istream& is, string& str);

重载了>>和<<才能用cin和cout对string类的输入和输出。

(3)relational operators (string)

大小比较:

(4)getline (string)

从输入流的当前位置开始,持续读取字符,直到遇到换行符 \n 为止。

能读取完整的一行文本(包括空格),适合处理带空格的输入(如句子、地址等)。

📖补充知识:

cin>>读取数据的机制:

当用户从键盘输入数据时,输入的字符会先存入缓冲区,cin>>是从缓冲区读取数据的,而非键盘实时读取,读取过程中会自动先跳过空白字符空格 、制表符 \t、换行符 \n 等),直到遇到非空白字符才开始提取数据,之后遇到空白字符就停止读取。

概括一下流程:

跳过空白字符 → 从缓冲区提取匹配目标类型的字符 → 停止于第一个空白字符 → 残留未处理字符在缓冲区

#include <iostream>
#include <string>
using namespace std;int main() {int a;double b;string s;// 步骤1:读取整数cout << "请输入一个整数: ";cin >> a;  // 从缓冲区读取整数// 步骤2:读取浮点数cout << "请输入一个浮点数: ";cin >> b;  // 从缓冲区读取浮点数// 步骤3:读取字符串cout << "请输入一个单词: ";cin >> s;  // 从缓冲区读取字符串// 输出读取结果cout << "你输入的整数: " << a << endl;cout << "你输入的浮点数: " << b << endl;cout << "你输入的单词: " << s << endl;return 0;
}

📖小Tips:

若需读取单个数值不含空格的字符串,用 cin >> 更简洁.

若需读取包含空格的完整行(如用户输入的句子、地址),必须用 getline

📖例题:

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

#include <iostream>
using namespace std;
#include<string>
int main() {string str;getline(cin,str);size_t pos=str.rfind(' ');cout<<str.size()-(pos+1)<<endl;return 0;
}


完。

今天的分享就到这里,感谢各位大佬的关注,还请大家多多支持,你们的支持是我前进的最大动力!

http://www.dtcms.com/a/414733.html

相关文章:

  • 网站建设制作设计营销公司杭州亚马逊雨林探险作文
  • 东莞圆心科技网站开发哪里有做网站系统
  • 网站定位要点 有哪些方面大航母网站建设费用
  • iServer 启动端口冲突
  • 大连网站建设佳熙科技湖南专业seo优化公司
  • 单词配对记忆游戏小程序V1.1.0-“太空霓虹“视觉升级版
  • 如何解决 pip install 安装报错 ModuleNotFoundError: No module named ‘onnxruntime’ 问题
  • 设备租赁结算软件有哪些
  • 全网首先 Docker Compose 启动Postgresql18
  • 公司网站非响应式模板网站建设包括内容
  • 180课时吃透Go语言游戏后端开发5:Go语言中的条件语句
  • 企业信息化建设总体规划设计方案
  • 淮北做网站的公司网站建设荣茂
  • Redis 核心知识体系总结
  • 网站图片速度临沂市经济开发区建设局网站
  • React第四天——hooks
  • 地方网站如何做网页版梦幻西游好玩吗
  • 无人机,无人车等机器人系统分布式集群技术难点
  • 企业网站硬件方面建设如何做移动端网站
  • 【代码随想录day 28】 力扣 1005. K次取反后最大化的数组和
  • Python 2025:云原生与容器化技术的新浪潮
  • 上网出现危险网站wordpress批量修改文章内链接
  • 速通ACM省铜第十六天 赋源码(Sigma Cubes和Find Permutation 2和Rotate and Sum Query)
  • 算法题(219):纪念品
  • Cybersecurity AI (CAI) - 轻量级网络安全AI框架
  • 接网站建设_网站设计交换链接的其它叫法是
  • 计算机视觉(opencv)——基于 dlib 的实时摄像头人脸检测
  • qq空间网站开发商延期交房可以退房吗?
  • 装潢设计与制作是学什么seo超级外链
  • 鹤庆县公路建设网站汕头网站建设 网络服务