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

从零学STL:string类常用接口一篇吃透

目录

1、STL的介绍

(1)容器(存数据)就是各种装东西的盒子

(2)迭代器(遍历)

(3)算法(功能函数)

2、string类

(1)auto和范围for

(2)string类的相关接口(最常见的接口)

1.string常见的构造函数

2.string类对象的容器操作函数

3.string类对象的访问及遍历操作

4.string类对象的修改操作

5.string 类非成员函数


1、STL的介绍

STL=C++标准模板库。就是官方提前写好的一大堆现成容器+算法,你直接拿来用,不用自己手写

STL分为三个部分

(1)容器(存数据)就是各种装东西的盒子

string 字符串、vector 动态数组(最常用)、list 链表、stack 栈、queue 队列、map键值对。

(2)迭代器(遍历)

就是容器的通用指针,统一遍历所有的容器

(3)算法(功能函数)

排序、查找、去重、交换等,其中sort排序,find查找都属于STL

一句话:以前需要我们自己造轮子去实现数组的增删查改,现在我们学习了STL之后直接用现成的,一行代码就搞定。

C语言:自己造轮子,C++:现成的轮子全套配齐。

2、string类

为啥要学习string类呢?

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

(1)auto和范围for

一、前言
在现代C++11及以后语法中, auto 类型推导与范围for循环是日常开发、STL遍历、代码精简最常用两大语法。
不管是遍历 string 、 vector 还是其他容器,熟练使用这两个语法能极大简化代码,也是校招笔试、日常写业务必备知识点。


二、auto 关键字详解
1. 什么是auto?
auto 是自动类型推导关键字,编译器根据右边赋值内容,自动推导出变量真实类型,无需手动写冗长类型。

2.基础用法

#define _CRT_SECURE_NO_WARNINGS 1 #include<stdio.h> #include<iostream> #include<string> using namespace std; int main() { //auto的使用,自动推到类型 auto a1= 10;//自动识别a1是int类型 auto a2 = 10.0;//自动识别a2是double类型 return 0; }

(2)搭配STL中的迭代器

STL迭代器中的类型极长,直接用auto简化

//传统的迭代器 string::iterator it=s.begin(); //auto推导 auto it=s.begin();

auto使用注意:定义必须初始化,不给值无法推到类型。不能作为函数形参。

三、范围for循环

1、作用:自动遍历整个容器,无需下标,无需迭代器,极简遍历string,vector等容器所有STL。

2、标准语法格式

for(auto 类型 :容器) { //逻辑代码; }

比如我们想要遍历一个字符串

我们可以看到这里我们没有写任何下标,就用了一个auto和范围for循环遍历就能很好的将这个字符串一个个的打印出来。

四、范围for的底层就是迭代器遍历的语法糖,编译器底层自动转换成

auto it=s.begin(); while(it !=s.end()) { cout<<(*it)<<" "; it++; }

(2)string类的相关接口(最常见的接口)

1.string常见的构造函数

string() 构造空的string类现象,即空字符串

string s1();//无参构造

string(const char *s) 用c_str来构造string类

string s2("xx");//带参数构造

string(const string& s)拷贝构造函数

string s3(s2);//拷贝构造

扩充:

1.对于拷贝构造函数来说,还有一些接口是从第几个字符开始拷贝

格式:

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

我们直接上例子

这两种写法一样,当我们不写后面的拷贝字符串的长度时,它会自动拷贝后面所有的字符。

2.拷贝字符串的前几个字符

string(const char* str,size_t n);

3.把字符串的前几个字符初始化

string(size_t n,char c);

2.string类对象的容器操作函数

(1)size 和 length 返回字符串的有效长度,不包括\0

string s1="hello world"; cout<<s1.size()<<endl; //打印出的结果是11 cout<<s1.length<<endl; //打印出的结果也是11

(2)capacity 返回空间的总大小

string s1 = "hello world"; cout << s1.capacity() << endl; //输出的结果是15

为啥是15呢?capacity()里面不包括\0,它只能表示最多能存放多少个有效字符,实际上应该是16,多出来的一个位置存放\0.

(3)empty 检测字符串释放为空串,是返回true,否则false.

string s1(""); cout << s1.empty() << endl; //字符串为空,true,输出1 string s2("hello world"); cout << s2.empty() << endl; //字符串不为空,false,输出0

(4)clear 清空有效字符

string s1("hello world"); cout << s1.size() << endl;//11 cout << s1.capacity() << endl;//15 cout << s1.empty() << endl;//0 s1.clear(); cout << endl; cout << s1.size() << endl;//0 cout << s1.capacity() << endl;//15 cout << s1.empty() << endl;//1

我们可以清晰的看到clear清除的是字符串的有效长度,它的字符串的容量并没有改变。

(5)reserve 为字符串预留空间

string s1("hello world"); s1.reserve(50); cout << s1.capacity() << endl; //输出的结果是63

为啥是63呢?这是标准库为了性能做的内存对齐/按块分配优化:我们所写的reserve(50)会触发分配,标准库会直接分配一大块、对齐更友好的内存块,比如64字节(其中一个字节用来存放\0)所以会显示63。

(6)resize 将有效字符的个数改成n个,多出来的空间用字符c填充

string s1 = "hello world"; cout << s1.size() << endl;//输出11 s1.resize(15); cout << endl; cout << s1 << endl;//hello world,当我们没有填充字符时,编译器默认补充\0,ASCII为0 cout << s1.size() << endl;//输出15

我们也可以添加几个字符,比如我们想把原来的11个有效字符改成15个,那么多出来的4个字符就要用字符填充,下面的我们用字符x进行填充。

string s1 = "hello world"; cout << s1.size() << endl;//11 s1.resize(15,'x'); cout << endl; cout << s1 << endl;//hello worldxxxx cout << s1.size() << endl;//15

3.string类对象的访问及遍历操作

(1)operator[] 返回pos位置的字符,const string 类对象的调用

这个简单来说就是和数组的访问是一样的,我们也同样做一个例子

string s1 = "hello world"; cout << s1[0] << endl;//输出h cout << s1[1] << endl;//输出e cout << s1[2] << endl;//输出l

不应该是operator[]吗?operator[]是个运算符的名字,而s1[]是它的实际调用,s1[0]是s1.operator[](0)的语法糖,所以它们是完全等价的。

(2)begin+end begin获取的是字符串的第一个字符,end获取的是最后一个字符的下一个位置的迭代器,也就是\0的位置。

s我们直接举一个遍历字符串的例子来直观看一下这个用法

string s1 = "hello world"; string ::iterator it = s1.begin(); while (it != s1.end()) { cout << *it << " "; it++; } //输出h e l l o w o r l d

我们在写这个迭代器的通用的指针类型的时候,我们会好奇为啥要写这个::类作用域的,这是因为这个迭代器指针适用于所有的容器类型,所以我们使用时需要标明这个指针是哪个类型的,比如我们有的时候是vector中的迭代器,有时候是list当中的迭代器,所以我们使用的需要表明这个迭代器的出处。

(3)rbegin和rend 这个和begin和rend恰恰相反,rbegin指向的最后一个字符,rend指的是第一个字符的前一个位置

这个我们也可以用遍历来实现,这个就是把这个字符串倒过来遍历一遍,这个和正向遍历有所不同的是这个是反向遍历,这里是反向迭代器需要使用reverse_iterator来实现遍历

string s1 = "hello world"; string::reverse_iterator it = s1.rbegin(); while (it != s1.rend()) { cout << *it << " "; it++; } //输出结果:d l r o w o l l e h

4.string类对象的修改操作

(1)push_back 在字符串后面尾插字符c

string s1("hello world"); s1.push_back('x'); cout << s1 << endl; //打印出hello worldx

(2)append 在字符串后面追加一个字符串

写法一:在s1后面追加字符串s2

string s1 = "hello"; string s2 = "world"; cout << s1.append(s2) << endl; //输出:helloworld

写法二:从s2下标是5后面的字符追加到s1的后面

string s1 = "hello world"; string s2 = "abcdefg"; cout << s1.append(s2, 5) << endl; //输出结果:hello worldfg

写法三:在字符串后面追加一个字符串

string s1("hello world"); cout << s1.append("jiayou") << endl; //输出:hello worldjiayou

写法四:创建一个对象,取一字符串的前三个追加到这个创建的对象当中

string s2; cout << s2.append("jiayou",3) << endl; //输出:jia

写法五:在字符串后面添加五个字符

string s1("hello world"); cout << s1.append(5, 'x'); /输出:hello worldxxxxx

(3)operator+= 在字符串后面追加字符串str,必须有一个是string类类型

例子1:

string s1 = "hello"; string s2 = "world"; s1 += s2; cout << s1 << endl; //输出:helloworld

例子2:

string s1 = "hello"; s1 += "nihao"; cout << s1 << endl; //输出:hellonihao

例子3;

string s1("hello"); s1 += 'x'; cout << s1 << endl; //输出:hellox

(4)c_str 返回C语言风格的字符串

为啥需要引用c_str呢?我们可以直接上一个例子来对比一下,就知道了。因为在C语言当中,输出的结果只认const char*,而string是类类型,需要转换为C语言的风格,所以需要转换一下。

string s1("hello world"); //错误写法 printf("%s", s1); cout << endl; //正确写法 printf("%s", s1.c_str()); //输出: ?訅? hello world

(5)find+npos 从字符串pos的位置开始往后找字符c,返回该字符在字符串当中的下标位置

例子1:在已知的字符串当中找另一个字符串,有两种写法

string s1 = "hello world"; string s2 = "world"; cout << s1.find(s2) << endl;//输出6
string s1 = "hello world"; cout << s1.find("llo") << endl;//输出2 cout << s1.find("lo") << endl;//输出3

例子2:从s1的下标为3开始找wo出现的位置

string s1="helloworld"; cout << s1.find("world",3,2) << endl;//输出5

例子3:找一个字符

string s1 = "helloworld"; cout << s1.find('l') << endl; //输出2

npos:本质是当在C++当中没找到所在位置的下标,就会返回-1

static const size_t npos=-1;

(6)rfind 从字符串pos位置开始往前找字符c,返回该字符在字符串所在的位置。

例子1:已知两个字符串,在一个字符串找另一个字符串出现的位置。

写法1:

string s1("hello world"); string s2("llo"); int key = s1.rfind(s2); cout << key << endl; //输出2

写法2:

string s1("hello world"); int key = s1.rfind("llo"); cout << key << endl; //输出2

例子二:在已知字符串当中找某个字符

string s1("hello world"); int key = s1.rfind("w"); cout << key << endl; //输出6

例子三:在已知的字符串第pos个位置开始找某个字符串的n个子字符串,下面这个就是我们要找的字符串是llo,从s1的下标为3的位置开始找,也就是倒着的下标为3,也就是o,找目标字符串的前两个字符串ll。

string s1("hello world"); int key = s1.rfind("llo",3,2); cout << key << endl; //输出2

(7)substr 从str的pos位置开始,截取n个字符,让后将其返回

string s1("hello world"); string s2 = s1.substr(5); cout << s2 << endl; //输出:空格world //从空格开始往后打印,这里没有写打印几个字符,编译器默认打印到最后 string s3 = s1.substr(5, 3); cout << s3 << endl; //输出:空格wo

5.string 类非成员函数

(1) operator >> 输入运算符重载

string name; cout << "Please, enter your name: "; cin >> name; cout << "Hello, " << name << "!\n";

我们可以在这里输入我们的名字,比如我输入张三它就会输出 hello 张三 !

(2) operator<< 输出运算符重载

string str = "Hello world!"; cout << str << '\n'; //输出:hello world!

(3) getline 获取一行字符串

string str; getline(cin, str); cout << str << endl; //当我们输入hello world //它就是输出hello world

为啥我们不直接用上面的operator>>运算符重载呢?

我们可以将上面的getline这段代码替换成cin>>str;看看会发生什么

string str; cin >> str; cout << str << endl; //当我们输入hello world //它会输出hello

为啥后面的world为啥没有打印出来呢?下面我把两种的结束条件都说明一下

getline(cin,str)和cin>>的区别

1.getline(cin,str) :

结束条件:遇到换行符\n就停止读取。

空格处理:会读取并保留所有的空格,只有换行符会被丢弃。

缓冲区残留:会把结束用的换行符从缓冲区读走并丢弃。

适用场景:读取整行文本,带空格的句子。

2.cin>>:

结束条件:当读取字符串时,只要遇到空格,换行符,制表符这些空白字符,就会立刻停止读取。

空格处理:不会读取空格,会把空格当成结束标记。

缓冲区残留:结束标记(空格/缓冲区)会留在缓冲区

适用场景:读取单个单词,无空格的输入

(4) relational operator 大小比较

比较字符串之间的大小,需要注意的是,比较的两个对象之间,必须有一个是类类型。因为这里的操作符是类类型的运算符重载。

我们可以简单的举几个类型

1.string 和string之间的比较

string s1 = "hello world"; string s2 = "hello xorld"; cout << (s1 < s2) << endl; //这里应该是返回的是真,返回1,这里前6个字符都相等,第7个比较的是ASCII值,x在w的后面,比较大

2.string 和char之间的比较

string s1 = "hello world"; cout << (s1 < "hello abcd") << endl; //这里同样是前6个字符都相等,s1的第7个字符比下面要比较的字符串要大,所以应该是s1>s2,所以是假,返回0

3.char和string之间的比较

string s1 = "hello world"; cout << ("hello abcd"<s1) << endl; //这里同样是前6个字符都相等,s1的第7个字符比下面要比较的字符串要大,所以应该是s1>s2,所以是真,返回1

上面就是我们在日常常用的接口以及算法题的常用接口,我们只掌握如何适用就可以了,当然了,当我们在面试过程当中,面试官总是让我们手撕这些接口的底层逻辑,我们也是需要掌握的,那么我们在下次博客当中也会手写一下string的模拟实现。

3、string类的模拟实现

(1)string.h头文件

#define _CRT_SECURE_NO_WARNINGS 1 #pragma once #include<iostream> #include<string> #include<assert.h> using namespace std; namespace myself { class string { public: //这里是迭代器版本,相当于把char*指针换了个名字 typedef char* iterator; iterator begin() { return _str; } iterator end() { return _str + _size; } /*string() :_str(new char[1]{'\0'}) ,_size(0) ,_capacity(0) {}*/ // 短小频繁调用的函数,可以直接定义到类里面,默认是inline string(const char* str = "") { _size = strlen(str); // _capacity不包含\0 _capacity = _size; _str = new char[_capacity + 1]; strcpy(_str, str); } // 深拷贝问题 // // s2(s1) string(const string& s) { _str = new char[s._capacity + 1]; strcpy(_str, s._str); _size = s._size; _capacity = s._capacity; } // s2 = s1 // s1 = s1 string& operator=(const string& s) { if (this != &s) { delete[] _str; _str = new char[s._capacity + 1]; strcpy(_str, s._str); _size = s._size; _capacity = s._capacity; } return *this; } ~string() { delete[] _str; _str = nullptr; _size = _capacity = 0; } const char* c_str() const { return _str; } void clear() { _str[0] = '\0'; _size = 0; } size_t size() const { return _size; } size_t capacity() const { return _capacity; } char& operator[](size_t pos) { assert(pos < _size); return _str[pos]; } const char& operator[](size_t pos) const { assert(pos < _size); return _str[pos]; } //保留空间大小 void reserve(size_t n); //尾插字符 void push_back(char ch); //追加字符串 void append(const char* str); string& operator+=(char ch); string& operator+=(const char* str); //在字符串后面+一个字符 void insert(size_t pos, char ch); //在字符串后面+一个字符串 void insert(size_t pos, const char* str); //从pos位置开始删除字符 void erase(size_t pos, size_t len = npos); size_t find(char ch, size_t pos = 0); size_t find(const char* str, size_t pos = 0); string substr(size_t pos = 0, size_t len = npos); private: char* _str; size_t _size; size_t _capacity; static const size_t npos; }; bool operator<(const string& s1, const string& s2); bool operator<=(const string& s1, const string& s2); bool operator>(const string& s1, const string& s2); bool operator>=(const string& s1, const string& s2); bool operator==(const string& s1, const string& s2); bool operator!=(const string& s1, const string& s2); ostream& operator<<(ostream& out, const string& s); istream& operator>>(istream& in, string& s); }

(2)string.cpp文件

#include"string.h" namespace myself { const size_t string::npos = -1; void string::reserve(size_t n) { if (n > _capacity) { //cout << "reserve:" << n << endl; char* tmp = new char[n + 1]; strcpy(tmp, _str); delete[] _str; _str = tmp; _capacity = n; } } void string::push_back(char ch) { if (_size == _capacity) { reserve(_capacity == 0 ? 4 : _capacity * 2); } _str[_size] = ch; ++_size; _str[_size] = '\0'; } string& string::operator+=(char ch) { push_back(ch); return *this; } void string::append(const char* str) { size_t len = strlen(str); if (_size + len > _capacity) { // 大于2倍,需要多少开多少,小于2倍按2倍扩 reserve(_size + len > 2 * _capacity ? _size + len : 2 * _capacity); } strcpy(_str + _size, str); _size += len; } string& string::operator+=(const char* str) { append(str); return *this; } void string::insert(size_t pos, char ch) { assert(pos <= _size); if (_size == _capacity) { reserve(_capacity == 0 ? 4 : _capacity * 2); } // 挪动数据 size_t end = _size + 1; while (end > pos) { _str[end] = _str[end - 1]; --end; } _str[pos] = ch; ++_size; } void string::insert(size_t pos, const char* s) { assert(pos <= _size); size_t len = strlen(s); if (_size + len > _capacity) { // 大于2倍,需要多少开多少,小于2倍按2倍扩 reserve(_size + len > 2 * _capacity ? _size + len : 2 * _capacity); } size_t end = _size + len; while (end > pos + len - 1) { _str[end] = _str[end - len]; --end; } for (size_t i = 0; i < len; i++) { _str[pos + i] = s[i]; } _size += len; } void string::erase(size_t pos, size_t len) { assert(pos < _size); if (len >= _size - pos) { _str[pos] = '\0'; _size = pos; } else { for (size_t i = pos + len; i <= _size; i++) { _str[i - len] = _str[i]; } _size -= len; } } size_t string::find(char ch, size_t pos) { assert(pos < _size); for (size_t i = pos; i < _size; i++) { if (_str[i] == ch) { return i; } } return npos; } size_t string::find(const char* str, size_t pos) { assert(pos < _size); const char* ptr = strstr(_str + pos, str); if (ptr == nullptr) { return npos; } else { return ptr - _str; } } string string::substr(size_t pos, size_t len) { assert(pos < _size); // len大于剩余字符长度,更新一下len if (len > _size - pos) { len = _size - pos; } string sub; sub.reserve(len); for (size_t i = 0; i < len; i++) { sub += _str[pos + i]; } return sub; } bool operator<(const string& s1, const string& s2) { return strcmp(s1.c_str(), s2.c_str()) < 0; } bool operator<=(const string& s1, const string& s2) { return s1 < s2 || s1 == s2; } bool operator>(const string& s1, const string& s2) { return !(s1 <= s2); } bool operator>=(const string& s1, const string& s2) { return !(s1 < s2); } bool operator==(const string& s1, const string& s2) { return strcmp(s1.c_str(), s2.c_str()) == 0; } bool operator!=(const string& s1, const string& s2) { return !(s1 == s2); } ostream& operator<<(ostream& out, const string& s) { for (auto ch : s) { out << ch; } return out; } istream& operator>>(istream& in, string& s) { s.clear(); const int N = 256; char buff[N]; int i = 0; char ch; //in >> ch; ch = in.get(); while (ch != ' ' && ch != '\n') { buff[i++] = ch; if (i == N - 1) { buff[i] = '\0'; s += buff; i = 0; } //in >> ch; ch = in.get(); } if (i > 0) { buff[i] = '\0'; s += buff; } return in; } }

(3)test.cpp文件

namespace myself { void test_string1() { string s1; string s2("hello world"); cout << s1.c_str() << endl; cout << s2.c_str() << endl; for (size_t i = 0; i < s2.size(); i++) { s2[i] += 2; } cout << s2.c_str() << endl; for (auto e : s2) { cout << e << " "; } cout << endl; string::iterator it = s2.begin(); while (it != s2.end()) { //*it += 2; cout << *it << " "; ++it; } cout << endl; } void test_string2() { string s1("hello world"); s1 += 'x'; s1 += '#'; cout << s1.c_str() << endl; s1 += "hello bit"; cout << s1.c_str() << endl; s1.insert(5, '$'); cout << s1.c_str() << endl; s1.insert(0, '$'); cout << s1.c_str() << endl; string s2("hello world"); cout << s2.c_str() << endl; s2.insert(5, "$$$"); cout << s2.c_str() << endl; s2.insert(0, "$$$&&&&&&&&&&&&&&&&&&&&&&&&&&&&&"); cout << s2.c_str() << endl; } void test_string3() { string s1("hello world"); s1.erase(6, 100); cout << s1.c_str() << endl; string s2("hello world"); s2.erase(6); cout << s2.c_str() << endl; string s3("hello world"); s3.erase(6, 3); cout << s3.c_str() << endl; } void test_string4() { string s("test.cpp.zip"); size_t pos = s.find('.'); string suffix = s.substr(pos); cout << suffix.c_str() << endl; string copy(s); cout << copy.c_str() << endl; s = suffix; cout << suffix.c_str() << endl; cout << s.c_str() << endl; s = s; cout << s.c_str() << endl; } void test_string5() { string s1("hello world"); string s2("hello world"); cout << (s1 < s2) << endl; cout << (s1 == s2) << endl; cout << ("hello world" < s2) << endl; cout << (s1 == "hello world") << endl; //cout << ("hello world" == "hello world") << endl; cout << s1 << s2 << endl; string s0; cin >> s0; cout << s0 << endl; } } int main() { myself::test_string5(); char str[] = "牛马"; cout << strlen(str) << endl; str[1]++; cout << str << endl; str[3]--; cout << str << endl; str[1]++; cout << str << endl; str[3]--; cout << str << endl; return 0; }
http://www.cnnetsun.cn/news/3553369.html

相关文章:

  • RoPE旋转位置编码:原理、实现与大模型长度外推实践
  • C2000 eCAP模块实战:从信号捕获到多路同步PWM生成
  • 南京站 meetup 下周六开启!赶快报名吧!
  • 委员访谈筹备与传播策略全解析
  • PotPlayer百度翻译插件完整教程:三步实现视频字幕实时翻译
  • Mac CPU使用率优化指南:诊断与解决方案
  • C#调用C++类实战:P/Invoke封装与内存管理详解
  • 河北高考一分一档表解析与志愿填报指南
  • 低温环境下单工通信设备的可靠性优化方案与测试验证
  • Redis 五大数据类型精讲(Set 集合)
  • 提示工程核心技能与实战应用解析
  • 2026年AI Agent技术全景与应用趋势分析
  • 工业级C++项目auto使用规范:平衡简洁与可维护性的最佳实践
  • Python环境管理利器:Anaconda安装与使用全指南
  • LangChain架构解析与AI应用开发实践
  • 一文讲清 PUSCH DM-RS 的 FD-OCC、TD-OCC 与端口关系
  • C++20协程实战:从原理到异步IO编程的三大应用案例
  • Python编程入门:从零基础到实战项目的学习路径
  • LangChain核心解析:LLM应用开发的标准化工具
  • 黑苹果EFI工具详解:OpenCore配置与硬件兼容性指南
  • 汽车图像缩放技术:从YUV格式到TI Jacinto RSZ硬件实现
  • C++数据类型深度解析:从内存布局到实战避坑指南
  • Jetson TK1无线网卡实战:Intel 7260 AC双频WiFi适配指南
  • 企业信用与工商信息采集:构建企业关系网络图谱与智能风控平台
  • C++实现Tamura纹理特征:从原理到工程实践
  • 大模型学习路线:从零基础到工业级部署
  • 基于 SpringBoot 的智慧柳州旅游景点导游平台
  • AI短剧系统私有化部署与零代码开发指南
  • 南洋理工大学Advanced Science:花粉增强仿生触觉感受器,助力新一代感知增强假肢
  • PAM360:现代企业特权访问管理的核心技术与实践