【C++】深入理解string类(1)
目录
一 认识了解string类
1 为什么学习string类?
2. 标准库中的string类
二 string类的常用接口说明
1. string类对象的常见构造
2 赋值
3 遍历和修改
4 数据个数
5 迭代器
6 迭代器的分类
(1)begin
(2)rbegin
(3)cbegin
7 查找算法
三 auto和范围for
auto关键字
范围for
四 完整代码
在使用string类的时候,要加上头文件#include<string>
一 认识了解string类
1 为什么学习string类?
1.1string类的定义
在 C++ 中,string
类是标准库(STL 的一部分)提供的用于处理字符串的模板类,定义在 <string>
头文件中,属于 std
命名空间。它封装了字符串的存储和操作,相比 C 语言中的字符数组(char*
),string
类提供了更安全、便捷的字符串处理方式。
1.2 C语言中的字符串
C语言中,字符串是以'\0'结尾的一些字符的集合,为了操作方便,C标准库中提供了一些str系列
的库函数,但是这些库函数与字符串是分离开的,不太符合OOP的思想,而且底层空间需要用户
自己管理,稍不留神可能还会越界访问。
1.3 两个面试题(暂不做讲解)
https://www.nowcoder.com/practice/1277c681251b4372bdef344468e4f26e?tpId=13&&tqId=11202&rp=6&ru=/activity/oj&qru=/ta/coding-interviews/question-ranking
https://leetcode-cn.com/problems/add-strings
在OJ中,有关字符串的题目基本以string类的形式出现,而且在常规工作中,为了简单、方便、 快捷,基本都使用string类,很少有人去使用C库中的字符串操作函数。
2. 标准库中的string类
2.1 string类
string类的文档介绍:
http://www.cplusplus.com/reference/string/string/?kw=string
当我们在看这个文档的时候发现,里面几乎是全英文的界面,我们要自己学会看文档,当然也可以使用翻译软件,但是翻译出来的意思总是和原本要表达的意思差点“味道”,所以更推荐自己阅读英语的文档(上半年C++网站的官方中文版下线了)
在使用string类时,必须包含#include头文件以及using namespace std;
二 string类的常用接口说明
string类的接口有一百多个,我们不可能每一个都学习,在这里我们学习最常用的二十几个接口。
1. string类对象的常见构造
(constructor)函数名称 | 功能说明 |
string() (重点) | 构造空的string类对象,即空字符串 |
string(const char* s) (重点) | 用C-string来构造string类对象 |
string(size_t n, char c) | string类对象中包含n个字符c |
string(const string&s) (重点) | 拷贝构造函数 |
void Teststring()
{string s1; // 构造空的string类对象s1string s2("hello bit"); // 用C格式字符串构造string类对象s2string s3(s2); // 拷贝构造s3
}
我们来学习一下第三个:
使用方法翻译过来是这样的:复制从pos位置开始并跨越len字符的str部分,如果str太短或len是string::npos,则直到str的末尾。
我们来使用一下:
void test_string1()
{string s4(s2, 0, 5);cout << s4 << endl;// pos位置拷贝结尾string s5(s2, 6, 15);cout << s5 << endl;string s6(s2, 6);cout << s6 << endl;}
如果要复制的长度超过了字符串的长度,编译器不会报错也不会越界,只会拷贝到字符串的末尾。
如果确定了要拷贝到末尾,可以直接省去第三个参数,如上面所示的s6。
但是这个接口我们并不常用,只是通过这个接口,我们了解到如何去阅读文档。
再引入一下第五个:
void test_string1()
{//只取前六个字符string s7("hello world", 6);cout << s7 << endl;//给s8赋值为十个*string s8(10, '*');cout << s8 << endl;s7 = "xxxx";cout << s7 << endl;
}
运行结果为:
Hello
**********
2 赋值
可以用字符赋值,也可以用字符串赋值
3 遍历和修改
pos是下标,返回的是pos对应位置的引用,意味着既可以读也可以修改。
void test_string2()
{string s1("hello world");cout << s1 << endl;s1[0] = 'x';cout << s1 << endl;cout << s1[0] << endl;// 越界有严格断言检查//s1[12]; // 断言//s1.at(12); // 抛异常cout << s1.size() << endl; // 推荐cout << s1.length() << endl;// 下标+[]// 遍历 or 修改for (size_t i = 0; i < s1.size(); i++){s1[i]++;}cout << s1 << endl;
}
我们发现,[]的使用和数组下标的使用很相似
同样的:
at的使用和[]的使用类似,区别在于越界时at会抛异常
4 数据个数
这两个相比,我们更推荐使用size,有些时候例如链表,无法使用length,但是size在任何情况都适用。
cout << s1.size() << endl; // 推荐cout << s1.length() << endl;
5 迭代器
迭代也就是遍历的意思,迭代器是一种通用的访问所有容器的方式。
迭代器的常见类型:iterator
在 C++ 中,
iterator
(迭代器)是一种行为类似指针的对象,用于遍历容器(如vector
、list
、string
等)中的元素。它是连接容器和算法的桥梁,允许我们在不暴露容器内部实现细节的情况下访问元素。
iterator
的核心作用:
- 提供统一的接口遍历不同容器(无论容器底层是数组、链表还是树结构)
- 支持通过
*
运算符访问元素(类似指针解引用)- 支持通过
++
运算符移动到下一个元素- 支持比较操作(如
==
、!=
判断是否到达容器末尾)
注意:行为像指针,但是底层不一定是指针
使用实例:
// 行为像指针一样的东西string::iterator it1 = s1.begin();while (it1 != s1.end()){// (*it1)--; 修改值的内容cout << *it1 << " ";//解引用输出对应的值++it1;向下一位挪动}cout << endl;
使用范围:左闭右开[begin , end)
这一点和python中的range的使用范围很像
这个时候有些同学就有疑问了,那这个迭代器和下标+[ ]不是一样的吗?为什么还要多此一举用迭代器。
// 下标+[]// 遍历 or 修改for (size_t i = 0; i < s1.size(); i++){s1[i]++;}cout << s1 << endl;// 行为像指针一样的东西string::iterator it1 = s1.begin();while (it1 != s1.end()){// (*it1)--; 修改cout << *it1 << " ";++it1;}cout << endl;
是的,在这个应用场景我们使用下标会更方便简洁一点,但是 迭代器作为STL的一大组件,它是作为通用的访问所有容器的方式。底层是数据的结构(如顺序表)用下标+[ ],但是链表没有[ ],就需要借助迭代器。
我们来实现一下链表的迭代器:
list<int> lt;lt.push_back(1);lt.push_back(2);lt.push_back(3);list<int>::iterator lit = lt.begin();while (lit != lt.end()){cout << *lit << " ";++lit;}cout << endl;
代码解析:
list<int> lt;
定义了一个存储int
类型的双向链表。
lt.push_back(1);
等向链表尾部依次添加 1、2、3。
list<int>::iterator lit = lt.begin();
定义一个迭代器,指向链表的第一个元素。
while (lit != lt.end())
从当前位置开始遍历,直到到达链表末尾。
cout << *lit << " "; ++lit;
输出当前迭代器指向的值,然后移动到下一个元素。
迭代器的特点
1、提供统一的方式遍历容器;
2、算法可以泛型化,算法借助迭代器处理容器的数据。
6 迭代器的分类
(1)begin
分为普通的迭代器和const迭代器,普通的的迭代器我们前面已经讲过了,下面来学习const迭代器。
//const string::iterator it1 = s.begin();string::const_iterator it1 = s.begin();while (it1 != s.end()){// *it1 = 'x'; // 不能修改cout << *it1 << " ";++it1;}cout << endl;
注意const的位置:在类名之后。这样修饰的是迭代器所指向的对象不被修改,如果放在类名之前,就是迭代器不能修改,这样是错误的。
(2)rbegin
反向迭代器和const反向迭代器
它的遍历是从后往前遍历的
string::const_reverse_iterator it2 = s.rbegin();while (it2 != s.rend()){// *it2 = 'x'; // 不能修改cout << *it2 << " ";++it2;}cout << endl
(3)cbegin
是C++11之后定义的,就是const版本的begin
7 查找算法
注意 使用算法时必须包含头文件 #include<algorithm>
三 auto和范围for
在这里补充2个C++11的小语法,方便我们后面的学习
auto关键字
1. 在早期C/C++中auto的含义是:使用auto修饰的变量,是具有自动存储器的局部变量,后来这个 不重要了。C++11中,标准委员会变废为宝赋予了auto全新的含义即:auto不再是一个存储类型 指示符,而是作为一个新的类型指示符来指示编译器,auto声明的变量必须由编译器在编译时期 推导而得。
2. 用auto声明指针类型时,用auto和auto*没有任何区别,但用auto声明引用类型时则必须加&
3. 当在同一行声明多个变量时,这些变量必须是相同的类型,否则编译器将会报错,因为编译器实际 只对第一个类型进行推导,然后用推导出来的类型定义其他变量。
4. auto不能作为函数的参数,可以做返回值,但是建议谨慎使用
5 . auto不能直接用来声明数组
// C++11int i = 0;// 通过初始化表达式值类型自动推荐对象类型auto j = i;auto k = 10;auto p1 = &i;// 指定一定是指针auto* p2 = &i;cout << p1 << endl;cout << p2 << endl;// 引用int& r1 = i;// r2不是int&引用,是intauto r2 = r1;//r3是int& 引用auto& r3 = r1;cout << &r2 << endl;cout << &r1 << endl;cout << &i << endl;cout << &r3 << endl;
范围for
1 对于一个有范围的集合而言,由程序员来说明循环的范围是多余的,有时候还会容易犯错误。因此C++11中引入了基于范围的for循环。for循环后的括号由冒号“ :”分为两部分:第一部分是范围内用于迭代的变量,第二部分则表示被迭代的范围,自动迭代,自动取数据,自动判断结束。
2 范围for可以作用到数组和容器对象上进行遍历
3 范围for的底层很简单,容器遍历实际就是替换为迭代器,这个从汇编层也可以看到。
// C++11// 范围for// 自动取容器数据赋值,自动迭代++,自动判断结束//for (auto ch : s1)for (char ch : s1){cout << ch << ' ';}cout << endl;for (auto e : lt){cout << e << ' ';}cout << endl;
for (auto ch : s1):就是把当前s1的值赋值给ch
范围for的特点:自动取容器数据赋值,自动迭代++,自动判断结束
支持迭代器的容器,都可以使用范围for
数组也支持(特殊处理)
int a[10] = { 1,2,3 };for (auto e : a){cout << e << " ";}cout << endl;
}
-
根据 C++ 规则,未显式初始化的剩余元素会被自动初始化为 0,所以数组实际存储为
[1,2,3,0,0,0,0,0,0,0]
。 -
范围 for 循环:
for (auto e : a)
遍历数组a
的所有 10 个元素(包括未显式初始化的 0),依次输出每个元素的值。
四 完整代码
#define _CRT_SECURE_NO_WARNINGS 1#include<iostream>
#include<string>
#include<algorithm>
#include<list>
using namespace std;//int main()
//{
// // 净网行动
// // 卧槽
// // 10:48
// char buff1[] = "abcA";
// buff1[0]++;
//
// char buff2[] = "比特abc";
// cout << sizeof(buff2) << endl;
// buff2[1]++;
// cout << buff2 << endl;
//
// buff2[1]++;
// cout << buff2 << endl;
//
// buff2[3]++;
// cout << buff2 << endl;
//
// buff2[3]++;
// cout << buff2 << endl;
//
// return 0;
//}void test_string1()
{string s1;string s2("hello world");string s3(s2);cout << s1 << endl;cout << s2 << endl;cout << s3 << endl;string s4(s2, 0, 5);cout << s4 << endl;// pos位置拷贝结尾string s5(s2, 6, 15);cout << s5 << endl;string s6(s2, 6);cout << s6 << endl;string s7("hello world", 6);cout << s7 << endl;string s8(10, '*');cout << s8 << endl;s7 = "xxxx";cout << s7 << endl;
}//class string
//{
//public:
// char& operator[] (size_t pos)
// {
// return _str[pos];
// }
//private:
// char* _str;
// size_t _size;
// size_t _capacity;
//};void Print(const string& s)
{//const string::iterator it1 = s.begin();string::const_iterator it1 = s.begin();while (it1 != s.end()){// *it1 = 'x'; // 不能修改cout << *it1 << " ";++it1;}cout << endl;string::const_reverse_iterator it2 = s.rbegin();while (it2 != s.rend()){// *it2 = 'x'; // 不能修改cout << *it2 << " ";++it2;}cout << endl;
}void test_string2()
{string s1("hello world");cout << s1 << endl;s1[0] = 'x';cout << s1 << endl;cout << s1[0] << endl;// 越界有严格断言检查//s1[12]; // 断言//s1.at(12); // 抛异常cout << s1.size() << endl; // 推荐cout << s1.length() << endl;// 下标+[]// 遍历 or 修改for (size_t i = 0; i < s1.size(); i++){s1[i]++;}cout << s1 << endl;// 行为像指针一样的东西string::iterator it1 = s1.begin();while (it1 != s1.end()){// (*it1)--; 修改cout << *it1 << " ";++it1;}cout << endl;list<int> lt;lt.push_back(1);lt.push_back(2);lt.push_back(3);list<int>::iterator lit = lt.begin();while (lit != lt.end()){cout << *lit << " ";++lit;}cout << endl;Print(s1);//string::iterator ret1 = find(s1.begin(), s1.end(), 'x');auto ret1 = find(s1.begin(), s1.end(), 'x');if (ret1 != s1.end()){cout<<"找到了x"<<endl;}//list<int>::iterator ret2 = find(lt.begin(), lt.end(), 2);auto ret2 = find(lt.begin(), lt.end(), 2);if (ret2 != lt.end()){cout << "找到了2" << endl;}// C++11int i = 0;// 通过初始化表达式值类型自动推荐对象类型auto j = i;auto k = 10;auto p1 = &i;// 指定一定是指针auto* p2 = &i;cout << p1 << endl;cout << p2 << endl;// 引用int& r1 = i;// r2不是int&引用,是intauto r2 = r1;//r3是int& 引用auto& r3 = r1;cout << &r2 << endl;cout << &r1 << endl;cout << &i << endl;cout << &r3 << endl;// C++11// 范围for// 自动取容器数据赋值,自动迭代++,自动判断结束//for (auto ch : s1)for (char ch : s1){cout << ch << ' ';}cout << endl;for (auto e : lt){cout << e << ' ';}cout << endl;
}int main()
{try{test_string2();}catch (const exception& e){cout << e.what() << endl;}return 0;
}