目录
find_first_not_of &find_last_not_of
一、string类简介
string原型
最基本的string类
由于编码方式不同,又衍生出不同的string类
由此可见,string是一个模板
string类——管理字符串的类,是用字符的顺序表实现的
在使用string类时,必须包含#include <string>以及using namespace std;
注意不是<string.h>!
二、string类的基本成员变量
class string
{
private:
char _buff[16];
char* _str;
size_t _size;
size_t _capacity;
};
三、string构造
string类共有7中构造方式(C++98):
其中,最为重要的是(1)默认构造函数——构造长度为零的空字符串。
(2)拷贝构造(4)带参构造
string s1;//无参
string s3(s2);//拷贝构造
string s2("hello world");//带参
(3)给一个str,从str的pos处开始拷贝len个字符进行拷贝构造
如果len太大超出范围,则拷贝范围终点为字符串末尾,不会报错。
第三个值len有缺省值npos,如果不给参数,也是拷贝范围终点为字符串末尾。
string s4(s2, 6, 15);
//从s2的下标为6处开始拷贝15个字符进行拷贝构造
(5) 从 s 指向的字符数组中复制前 n 个字符用于构造。
string s5(s2, 6);
(6) 用n个字符 c 构造字符串。
string s7(10, 'X');
四、string析构
析构函数会自动调用
五、string赋值重载
最常用的是(1)
六、string类对象的访问操作
6.1operator[]⭐
通过使用str[pos]可以访问到pos位置的字符,,返回的是元素的引用,可进行读和修改
string s2("hello world");
s2[0] = 'x';//xello world
与at的区别:[ ]越界断言,at越界抛异常
七、string类遍历操作
方法1:下标+[]
// 1、下标 + []
for (size_t i = 0; i < s2.size(); i++)
{
cout << s2[i] << " ";
}
cout << endl;
方法2:迭代器——像指针的东西(但底层实现不一定是指针)
string::iterator it = s2.begin();
while (it != s2.end())
{
*it += 2;
cout << *it << " ";
++it;
}
注意:
任何容器的迭代器,都属于类域 eg.vector<int>::iterator
s2.begin()返回的是第一个字符的地址!
s2.end()返回的是最后一个字符的后一个位置的地址!
迭代器的牛逼之处——所有的容器都可以用这种类似的方式访问
//链表
list<int> lt = { 1,2,3,4,5,6,7 };
list<int>::iterator lit = lt.begin();
while (lit != lt.end())
{
cout << *lit << " ";
++lit;
}
cout << endl;
方法3:范围for
for (auto& ch : s2)
{
ch -= 2;
cout << ch << " ";
}
特点:自动赋值,自动迭代,自动判断结束
底层就是迭代器
思考:为什么范围for内修改了s2,但遍历结束后打印s2却没有改变呢?
void test_string1()
{
string s1;
string s2("hello world");
// 2、迭代器
//string::iterator it = s2.begin();
cout << "迭代器" << endl;
auto it = s2.begin();
while (it != s2.end())
{
*it += 2;
cout << *it << " ";
++it;
}
cout << endl;
cout << s2 << endl << endl;
cout << "范围for" << endl;
for (auto ch : s2)
{
ch -= 2;
cout << ch << " ";
}
cout << endl;
cout << s2 << endl;
}
因为上面的ch仅仅是s2中每个字符的拷贝,这里的修改修改的是局部变量,并没有实际完成对s2中每个字符的修改
而迭代器中,我们可以简单理解为*it就是字符串s2中的字符本身
如果我们想要在范围for中修改s2,应当使用传引用传参。
for (auto& ch : s2)
这三种遍历方式在性能上并没有太大区别,应当根据实际场景作判断。
*补充语法:auto和范围for
auto关键字
map<string, string> dict;
//map<string, string>::iterator mit = dict.begin();
auto mit = dict.begin();
auto y = &x;
auto* z = &x;
auto& m = x;
auto aa = 1, bb = 2;
// 编译报错:error C3538: 在声明符列表中,“auto”必须始终推导为同一类型
auto cc = 3, dd = 4.0;
// 不能做参数
void func2(auto a)
{}
// 可以做返回值,但是建议谨慎使用
auto func3()
{
return 3;
}
// 编译报错:error C3318: “auto []”: 数组不能具有其中包含“auto”的元素类型
auto array[] = { 4, 5, 6 };
// 编译报错:rror C3531: “e”: 类型包含“auto”的符号必须具有初始值设定项
auto e;
范围for
for (auto& e : array)
e *= 2;
八、迭代器及相关函数
函数名称 | 功能说明 |
begin | 获取一个字符的迭代器 |
end |
获取最后一个字符下一个位 置的迭代器
|
rbegin | 获取一个字符的迭代器 |
rend |
获取最后一个字符下一个位 置的迭代器
|
string s2("hello world");
string::reverse_iterator rit = s2.rbegin();
while (rit != s2.rend())
{
cout << *rit << " ";
++rit;
}
cout << endl;
string::reverse_iterator rit = s2.rbegin();
while (rit != s2.rend())
{
cout << *rit << " ";
++rit;
}
cout << endl;
const_iterator
这里使用的begin()便是下方的函数,end(),rbegin(),rend()同理。
const string s3("hello world");
//string::const_iterator cit = s3.begin();
auto cit = s3.begin();
while (cit != s3.end())
{
//*cit += 2;
cout << *cit << " ";
++cit;
}
auto rcit = s3.rbegin();
while (rcit != s3.rend())
{
// *rcit += 2;
cout << *rcit << " ";
++rcit;
}
cout << endl;
九、string类对象的容量操作
函数名称 | 功能说明 |
size(重点) | 返回字符串有效字符长度 |
length | 返回字符串有效字符长度 |
capacity | 返回空间总大小 |
empty (重点) |
检测字符串释放为空串,是返回
true
,否则返回
false
|
clear (重点) | 清空有效字符 |
reserve (重点) | 为字符串预留空间 |
resize (重点) | 将有效字符的个数改成n个,多出的空间用字符c填充 |
size⭐&&lenth
string s2("hello world");
cout << s2.length() << endl;
cout << s2.size() << endl;
capacity
void TestPushBack()
{
string s;
size_t sz = s.capacity();
cout << "capacity changed: " << sz << '\n';
cout << "making s grow:\n";
for (int i = 0; i < 100; ++i)
{
s.push_back('c');
if (sz != s.capacity())
{
sz = s.capacity();
cout << "capacity changed: " << sz << '\n';
}
}
}
class string
{
private:
char _buff[16];
char* _str;
size_t _size;
size_t _capacity;
};
Linux环境下
reserve⭐
// 提前开空间,避免扩容,提高效率
s.reserve(100);
会开大于等于n的空间
开辟的空间不包括'\0'
当n大于字符串当前容量时,肯定会扩容
当n小于字符串当前容量时,会发起一个不具有约束力的请求,去缩小容量,且该函数不能修改字符串的内容。具体情况如下图所示:
g++(注意g++环境下进行了内存对齐)
vs
clear ⭐
string s2("hello worldxxxxxxxxxxxxx");
cout << s2.size() << endl;
cout << s2.capacity() << endl << endl;
s2.clear();
cout << s2.size() << endl;
cout << s2.capacity() << endl << endl;
empty ⭐
resize ⭐
如果n比size小,会删除数据,如果n比size大,则多余空间会插入字符c,默认为'\0'
十、string类对象的修改操作
函数名称 | 功能说明 |
push_back | 在字符串后尾插字符c |
append | 在字符串后面追加一个字符串 |
operator+=()(⭐) | 在字符产后面追加字符串str |
insert(⭐) | 在指定位置插入字符或字符串 |
erase (⭐) | 删除字符串中的一部分 |
replace | 替换指定区间的字符串 |
push_back
在字符串后尾插字符c
注意:只能尾插一个字符,不能尾插字符串
string s2("hello world");
s2.push_back('?');
cout << s2 << endl;
append
在字符串后追加一个字符串
string& append (const string& str); // 追加一个string对象
// 追加一个string对象中的指定字符串长度
string& append (const string& str, size_t subpos, size_t sublen);
string& append (const char* s); // 追加一个字符串
string& append (const char* s, size_t n); // 追加字符串中的前n个字符
string& append (size_t n, char c); // 追加n个字符
string s2("hello world");
cout << s2 << endl;
//追加一个string对象
string s1("00000");
s2.append(s1);
// 追加一个string对象中的指定字符串长度
string s3("54321");
s2.append(s3,0,2);
//追加一个字符串
s2.append(" Yes!");
//追加字符串中的前n个字符
s2.append("haha",2);
//追加n个字符
s2.append(3,'!');
cout << s2 << endl;
operator+= ⭐
string s2("hello world");
cout << s2 << endl;
string s1("@@@");
s2 += s1;
s2 += " hahahahaha";
s2 += "~";
cout << s2 << endl;
insert⭐
在指定位置插入字符或字符串
注意:根据C语言数据结构的基础,可知插入时会大量挪动数据,使得时间复杂度提升,程序性能下降,因此需要谨慎使用insert。
//在指定位置插入一个string对象
string& insert (size_t pos, const string& str);
// 在指定位置插入一个string对象里的一部分
string& insert (size_t pos, const string& str, size_t subpos, size_t sublen);
// 在指定位置插入一个字符串
string& insert (size_t pos, const char* s);
// 在指定位置插入一个字符串的前n个字符
string& insert (size_t pos, const char* s, size_t n);
// 在指定位置插入n个字符
string& insert (size_t pos, size_t n, char c);
// 在指定迭代器的位置插入n个字符
void insert (iterator p, size_t n, char c);
// 在指定迭代器的位置插入一个字符,并且返回该位置的迭代器
iterator insert (iterator p, char c);
这里只演示最常用的几种:
string s2("hello world");
cout << s2 << endl;
//在指定位置插入一个string对象
string s1("aa");
s2.insert(0, s1);
//在指定位置插入一个字符串
s2.insert(10, "_mid_ ");
// 在指定位置插入n个字符
char ch1 = '*';
s2.insert(0,4,ch1);
// 在指定迭代器的位置插入n个字符
char ch2 = '+';
s2.insert(s2.begin(), ch2);
cout << s2 << endl;
erase
删除字符串中的一部分
//从指定位置开始,删除指定长度个字符(默认删除到结尾)
string& erase (size_t pos = 0, size_t len = npos);
//给一个指定位置的迭代器,删除该位置的字符
iterator erase (iterator p);
//给两个指定位置的迭代器,删除在此之间的所有字符
iterator erase (iterator first, iterator last);
代码示例:
string s2("hello world");
cout << s2 << endl;
s2.erase(0, 3);//从0位置开始向后删除3个字符
cout << s2 << endl;
//头删
s2.erase(0, 1);
cout << s2 << endl;
s2.erase(s2.begin());
cout << s2 << endl;
//尾删
s2.erase(--s2.end());
cout << s2 << endl;
s2.erase(s2.size()-1,1);
//从某个位置后全部删
s2.erase(2);//个数的默认缺省值为npos
cout << s2 << endl;
replace
替换指定区间的字符串
replace的相关接口非常多,但是我们只需要掌握最常用的几种即可,其它的可以在必要之时翻阅文档学习。
string s2("hello world");
cout << s2 << endl;
//从第0个位置开始替换,替换5个字符,替换为:"Embrace"
s2.replace(0, 5, "Embrace");
cout << s2 << endl;
//左闭右开,删除闭,保留开
string s1("life");
s2.replace(s2.begin() + 8, s2.end(), s1);
cout << s2 << endl;
十一、string类对象的其他字符串操作
函数名称 | 功能说明 |
c_str | 返回 C语言格式字符串 |
substr(⭐) | 在str中从pos位置开始往,截取n个字符,然后将其返回 |
find(⭐) | 从字符串pos位置开始往后找字符/字符串,返回该字符在字符串中的位置 |
rfind | 从字符串pos位置开始往前找字符/字符串,返回该字符在字符串中的位置 |
find_frist_of | 从前往后找第一个匹配的字符 |
find_last_of | 从后往前找第一个匹配的字符 |
find_first_not_of | 从前往后找第一个不匹配的字符 |
find_last_not_of | 从后往前找第一个不匹配的字符 |
c_str⭐
string file;
cin>>file;
FILE* fout=fopen(file.c_str(),"r");
fclose();
substr
在str中从pos位置开始,截取n个字符,然后将其返回
string str("hello world");
cout<<str.substr(0, 5)<<endl;
find & rfind
find: 从字符串pos位置开始往后找第一次出现的字符/字符串,返回该字符在字符串中的位置rfind:从字符串pos位置开始往前找第一次出现的字符/字符串,返回该字符在字符串中的位置
string str("hello world");
string substr("world");
size_t pos1 = str.find(substr);
cout << pos1 << endl;
//从前往后找
size_t pos2 = str.find('l');
//从后往前找
size_t pos3 = str.rfind('l');
cout << pos2 << " " << pos3 << endl;
//从指定位置开始找
size_t pos4 = str.find('l', 5);
cout << pos4 << endl;
find_frist_of &find_last_of
find_frist_of:从指定位置开始(默认从头),从前往后在当前的string对象中寻找匹配的任何字符,返回位置
find_last_of : 从指定位置开始(默认从尾),从后往前在当前的string对象中寻找匹配的任何字符,返回位置
(小声bb:这里的取名有一些迷惑,如果按照功能取名的话,其实应该叫find_any_of才更为恰当呀)
string str("hello world");
size_t pos1=str.find_first_of("you");//找到str[4]的o满足条件
size_t pos2=str.find_last_of("you");//倒着找
size_t pos3 = str.find_first_of("you",5);//从指定位置开始找
cout << pos1 << " " << pos2 << " " << pos3<< endl;
find_first_not_of &find_last_not_of
find_first_not_of:从指定位置开始(默认从头),从前往后在当前的string对象中寻找不匹配的任何字符,返回位置
find_last_not_of :从指定位置开始(默认从尾),从前往后在当前的string对象中寻找不匹配的任何字符,返回位置
这两个操作与上面的操作相同。
综合练习:分割文件名
十二、string类对象的非成员函数重载
getline
在学习C语言的过程中我们发现scanf()在读取字符串的时候读到空格就结束了,无法读取后面内容。那时,我们通常使用gets()函数来解决问题。
现在,又有了一种新的解决方法,getline()函数!
它只有在遇到换行符时才停止读取,遇到空格是不会停止的。
并且,它是可以自定义间隔符的。
string str;
getline(cin,str);
cout<<str<<endl;
getline(cin,str,'*');//指定*为间隔符
cout<<str<<endl;
十三、string类的模拟实现
string.h
#pragma once
#define _CRT_SECURE_NO_WARNINGS 1
#include <iostream>
#include <assert.h>
using namespace std;
namespace A {
class string
{
public:
/*string()
:_str(new char[1]{'\0'})
,_size(0)
,_capacity(0)
{}*/
typedef char* iterator;
typedef const char* const_iterator;
string(const char* str="") {
_size = strlen(str);
_str = new char[_size+1];
_capacity = _size;
strcpy(_str, str);
}
string(const string& s) {
_str = new char[s._capacity+1];
strcpy(_str,s._str);
_size = s._size;
_capacity = s._capacity;
}
~string()
{
delete[] _str;
_str = nullptr;
_size = _capacity = 0;
}
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;
}
const char* c_str()const {
return _str;
}
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];
}
iterator begin() {
return _str;
}
const_iterator end()const {
return _str + _size;
}
const_iterator begin() const {
return _str;
}
iterator end() {
return _str + _size;
}
void push_back(char ch);
void append(const char* str);
string& operator+=(char ch);
string& operator+=(const char* str);
void reserve(size_t n);
void insert(size_t pos, char ch);
void insert(size_t pos, const char* str);
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);
void clear();
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);
}
string.cpp
#include "string.h"
namespace A {
const size_t string::npos = -1;
void string::reserve(size_t n) {
if (n > _capacity) {
//多开一个给\0
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';
}
void string::append(const char* str) {
size_t len = strlen(str);
if (_size + len > _capacity) {
reserve(_size + len > 2 * _capacity ? _size + len : 2 * _capacity);
}
strcpy(_str + _size, str);
_size += len;
}
string& string::operator+=(char ch) {
push_back(ch);
return *this;
}
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* str) {
size_t len = strlen(str);
if (_size + len > _capacity) {
reserve(_size + len > 2 * _capacity ? _size + len : 2 * _capacity);
}
size_t end = _size + len;
while (end >= pos + len) {
_str[end] = _str[end - len];
end--;
}
for (int i = 0; i < len; i++) {
_str[pos + i] = str[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 (int i = pos + len; i <= _size; i++) {
_str[i - len] = _str[i];
}
_size -= len;
}
}
size_t string::find(char ch, size_t pos) {
for (int i = pos; i < _size; i++) {
if (ch == _str[i]) {
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 strcmp(s1.c_str(), s2.c_str()) <= 0;
}
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 strcmp(s1.c_str(), s2.c_str()) >= 0;
}
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 strcmp(s1.c_str(), s2.c_str()) != 0;
}
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;
ch = in.get();
while (ch != ' ' && ch != '\n') {
buff[i++] = ch;
if (i == N - 1) {
buff[i] = '\0';
s += buff;
i = 0;
}
ch = in.get();
}
if(i > 0){
buff[i] = '\0';
s += buff;
}
return in;
}
void string::clear() {
_str[0] = '\0';
_size = 0;
}
}
test.cpp
#include "string.h"
namespace A {
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;
}
void test_string2()
{
string s1("hello world");
s1 += 'x';
s1 += '#';
cout << s1.c_str() << endl;
s1 += "hello life";
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() {
A::test_string5();
return 0;
}
十四、编码
内存和磁盘中只能储存二进制数字,那么文字和符号是如何储存的呢?这里我们就要提出编码这一概念。
编码——值和符号的映射关系
ASCII编码表
本质:英文符号和值的映射关系
统一码(Unicode)
普适性:也叫万国码,顾名思义,所有国家的文字符号都可以用该编码方式表示。
兼容性:UTF-8兼容ASCII,它的前128个字符与ASCII码完全一致
扩展性:Unicode设计了多种编码方案(如UTF-8、UTF-16、UTF-32)来适应不同的存储和传输需求。其中最为常用的是UTF-8.
UTF8
变长性:下图UTF8的后缀表示:要用几个字节来代表符号。UTF8可以使用1~4个字节来表示符号。
UTF16
用2个或4个字节表示每个字符。
UTF32
用4个字节为单位表示字符。
GBK
中日韩汉字符号和值的映射关系。(它比utf-8在中日韩汉字方面收录更全面)
windows系列支持该编码方式。
这时我们就可以理解为什么string要实现成模板了。
支持UTF-8
支持UTF-16
支持UTF-32
支持宽字符
十五、浅拷贝&深拷贝
浅拷贝
首先,我们来看一个例子:
深拷贝
十六、String类的传统写法和现代写法
传统写法
String& operator=(const String& s)
{
if (this != &s)
{
char* pStr = new char[strlen(s._str) + 1];
strcpy(pStr, s._str);
delete[] _str;
_str = pStr;
}
现代写法
String& operator=(String s)
{
swap(_str, s._str);
return *this;
}
观察swap模板的底层可以知道,这里进行swap会进行3次深拷贝,是非常低效的!
但其实,有专门定义一个针对string类的swap函数解决了该问题:
通过前面的学习,我们可以知道,优先使用最匹配的