目录
一,了解String类
string是C++标准库提供的动态字符串类型,定义在头文件<string>当中,完整的类型为:
std::string_basic<char>,日常使用的为别名std::string
string就是一个字符顺序表
引入String类的好处是:用自动化的内存管理 取代了手动危险的内存管理操作
其他的好处例如:
1,自动内存管理(告别缓冲区溢出)
2,操作简单直观(可以直接使用操作符)
3,功能丰富,可以直接使用(不用手动实现)
4,极好的调试和安全性
例:
std::string 始终知道自己有多长(.size() 方法 O(1) 复杂度),而 C 字符串需要用 strlen() 从头遍历计数(O(n) 复杂度)。
通过 .at() 访问字符时会做边界检查(越界抛异常),而 C 数组越界访问通常直接导致未定义行为(难以排查的崩溃)
.at() 是 std::string 提供的一个带边界检查的字符访问方法 。它的作用和 [] 运算符类似,都是用来访问字符串中某个位置的字符,但关键区别在于安全性。
使用\[\]不会检查边界,抛出异常,一旦越界就是未定义行为,而.at()会对字符串进行检查,一旦越界就会抛出异常:std::out_of_range
头文件:#include <string>

(了解即可)这张图片表明:这三种类型都是std::basic_string模版类的别名,区别在于底层存储的字符类型不同:
std::string :基于 char 类型(通常占 1 字节)。用于处理 ASCII/UTF-8 编码的文本,是最常用、兼容性最好的字符串类型。
std::u16string :基于 char16_t 类型(占 2 字节)。用于处理 UTF-16 编码,常见于 Windows 系统或某些需要处理非西欧字符(如中文、日文)的跨平台场景。
std::wstring :基于 wchar_t 类型(Windows 占 2 字节,Linux/macOS 占 4 字节)。用于处理"宽字符",在不同平台下编码和大小不固定,现代 C++ 推荐优先使用 string 和 u16string,以减少跨平台问题。
二,String类的构造函数和赋值重载函数
1,构造函数
常用的六种构造函数:

六种函数的运用实例:
cpp
#include <string>
int main()
{
//1,直接创建一个string类型的变量
string s1;
//2,创建并初始化为括号内的字符串
string s2("AAAABBBB");
//将s2字符拷贝构造给s3
string s3(s2);
//3,从给定字符串下标为4的位置开始,到15的位置结束进行拷贝
//超出就到给定的字符串的结尾停下,不写15,就到末尾停下
string s4(s3, 4, 15);
const char* data = "wwwc\0d1111";
//4,遇到/0就停止
string s5(data);
//5,遇到/0不会停止,即使原来的不够也会拷贝15个
string s6(data, 15);
//6,以4个相同的某字符进行构造
string s7(4, 'x');
cout << s1 << endl;//什么都没有
cout << s2 << endl;//AAAABBBB
cout << s3 << endl;//AAAABBBB
cout << s4 << endl;//BBBB
cout << s5 << endl;//wwwc
cout << s6 << endl;//wwwcd1111
cout << s7 << endl;//xxxx
return 0;
}
2,赋值重载函数

展示:
cpp
int main()
{
//1,用另一个字符串对象进行拷贝
string st1 = "wwwwaaaa";
string st2 = st1;
//2,以"\0结尾的字符串"进行赋值
string st3;
st3 = "dcccccc";
//以单个字符进行赋值
string st4 = "hello";
st4 = 'A';
return 0;
}
三,String类功能的使用
1,容量相关
cpp
//容量相关
void test01()
{
string s1("abcdef");
cout << s1.length() << endl;//6 //计算元素个数,不支持所有类型,例如树
cout << s1.size() << endl;//6 //计算元素的个数,支持所有类型
cout << s1.max_size() << endl;//计算string的size最大大小
cout << s1.capacity() << endl;//15 //计算容量
}
2,迭代器和范围for遍历字符串
迭代器是STL六大组件之一,他的本质:行为想指针的对象
下面是使用迭代器遍历字符串的例子:
其中还有auto和范围for遍历字符串
auto的作用在于当类型很长时,可以一键代替,减少代码量
end会指向字符串的后一个元素,rend则是第一个元素的前一个位置
迭代器共有4种:正向,反向,const正向,const反向
访问下标:

cpp
//迭代器和范围for-auto
int main()
{
string a1 = "aaabbb";
//使用下标进行循环打印
for (size_t i = 0;i < a1.size();i++)
{
a1[i] += 2; //ASCII码值增加2
cout << a1[i] << " ";
}
cout << endl;
cout << a1 << endl;
//使用迭代器进行打印
//迭代器是STL的六大组件之一
string::iterator g = a1.begin();
while (g != a1.end())
{
*g -= 2;
cout << *g << " ";
++g;
}
cout << endl;
cout << a1 << endl;
//使用auto和范围for auto可以自动识别数据的类型,自动迭代,自动判断结束
//auto的底层就是范围迭代器
for (auto s1 : a1) //auto就相当于一个拷贝,不会修改原来的字符串
{
s1 += 2;//但是迭代器可以修改
cout << s1 << " ";
}
cout << endl;
cout << a1 << endl;
//要想改变原来的字符串需要使用引用&
for (auto& s2 : a1)
{
s2 += 2;
cout << s2 << "";
}
cout << endl;
cout << a1 << endl;
return 0;
}
运行的结果:

反向迭代器:
cpp
void test2()
{
string s1("ab cdef");
string::iterator c1 = s1.begin();
while (c1 != s1.end())
{
cout << *c1 << " ";
c1++;
}
cout << endl;
//反向遍历--反向迭代器
string::reverse_iterator c2 = s1.rbegin();
while (c2 != s1.rend())
{
cout << *c2 << " ";
c2++; //依旧写++
}
cout << endl;
}
运行结果:

补充:auto可以做返回值,但是不建议使用,可能会出现一下问题:
cpp
//auto做返回值的缺点
//当多个函数嵌套--难以找到返回值的类型
auto k1()
{
return 3;
}
auto k2()
{
return k1();
}
auto k3()
{
return k2();
}
int main()
{
//要一路从k3->k2->k1才能找到a的类型是什么
auto a = k3();
return 0;
}
3.类型检查函数
语法:typeid(变量).name()
cpp
//类型检查函数
int main()
{
int a = 10;
auto b = a;
auto c = 'a';
//如果将''强行塞进两个字符
//就会变成多字符字面量 类型是int
//a的ASCII码值是97转化为16进制就是0x61 两个就是0x6161转化为十进制就是24929
auto d = 'aa';
auto e = "a";
auto f = "abc";
//auto d;
cout << typeid(a).name() << endl;//int
cout << typeid(b).name() << endl;//int
cout << typeid(c).name() << endl;//char
cout << typeid(d).name() << endl;//int
cout << d << endl;//24929
cout << typeid(e).name() << endl;//char const* __ptr64
cout << typeid(f).name() << endl;//char const* __prr64
//f作为一个指针打印的结果却是实际的字符串,这是因为C++对char*进行了重载
cout << f << endl;//abc
return 0;
}
4,const迭代器
cpp
//const迭代器 只可以进行读取不可以进行修改
void test3()
{
string a1("ab cdef");
//const正向迭代器
string::const_iterator s1 = a1.begin();
while (s1 != a1.end())
{
//*s1++; 会产生报错
cout << *s1 << " ";
s1++;
}
cout << endl;
//const反向迭代器
string::const_reverse_iterator s2 = a1.rbegin();
while (s2 != a1.rend())
{
cout << *s2 << " ";
s2++;
}
cout << endl;
}
运行结果:

5,String的扩容
cpp
//string的扩容
void StrPushBack()
{
string s1;
size_t sz = s1.capacity ();
cout << "first capacity:" << sz << endl;
for (int i = 0;i < 100;i++)
{
s1.push_back('c');
if (sz != s1.capacity())
{
sz = s1.capacity();
cout << "capacity:" << sz << endl;
}
}
}
运行结果:

解读:刚开始15是因为一个字符给了'\0',进行一次二倍扩,之后进行1.5倍扩。
使用reserve提前扩充:
cpp
void StrPushBack2()
{
string s1;
s1.reserve(100);//提前预留至少100个空间
size_t sz = s1.capacity();
cout << "first capacity:" << sz << endl;
for (int i = 0;i < 100;i++)
{
s1.push_back('c');
if (sz != s1.capacity())
{
sz = s1.capacity();
cout << "capacity:" << sz << endl;
}
}
}
运行结果:

6,String的大小sizeof
cpp
//string的结构类似是这样的
//class string
//{
//private:
// char* buff[16];//最开始开辟的空间不是在堆上开辟的,而是在buff上进行开辟的
// char* str; //当字符的个数<=15时,存在buff当中,否则就存在堆上,str指向堆中的内存
// size_t size;
// size_t capacity;
//
//};
void test4()
{
string s1("abc def");
string s2("aaaaaaaaaaaaaaaaa");
//按照内存对齐方法计算,类似结构体
cout << sizeof(s1) << endl;//32位下:28 64位下:40
cout << sizeof(s2) << endl;//32位下:28 64位下:40
}
7,容量改变测试


reserve和shrink_to_fit和resize和clear对容量的改变
以及判空的使用:
cpp
//reserve和shrink_to_fit和resize和clear对容量的改变
//以及判空的使用
void test5()
{
string s1("aggdfgdfdsfdffdasfdsd");
cout << s1.size() << endl;//21
cout << s1.capacity() << endl;//31
//小于size--不变
s1.reserve(15);
cout << s1.size() << endl;//21
cout << s1.capacity() << endl;//31
//介于size和 capacity之间--依旧不变
s1.reserve(25);
cout << s1.size() << endl;//21
cout << s1.capacity() << endl;//31
//大于capacity--进行扩容
s1.reserve(40);
cout << s1.size() << endl;//21
cout << s1.capacity() << endl;//47
cout << endl;
//进行缩小--capacity缩小到能够容纳size的大小
s1.shrink_to_fit();
cout << s1.size() << endl;//21
cout << s1.capacity() << endl;//31
cout << endl;
//resize进行缩小
//当resize小于size时会减少数据,数据丢失
s1.resize(15);
cout << s1.size() << endl;//15
cout << s1.capacity() << endl;//31
//介于size与capacity之间,用'\0'代替
s1.resize(25);
cout << s1.size() << endl;//25
cout << s1.capacity() << endl;//31
cout << endl;
s1.clear();//清理掉内容,使得size为0,capacity不变
cout << s1.size() << endl;//0
cout << s1.capacity() << endl;//31
cout << endl;
//判空
if (s1.empty() == true)
{
cout << "为空" << endl;
}
}
结果:

8,String类的插入操作
尾插单个字符:

尾插字符串:




9,string类的删除操作


10,string中字符的替换


11,find系列

cpp
void test3()
{
string str("fdwsfd.aaa.cc");
size_t pos = str.find(".");
//substr取子串
string d1 = str.substr(pos);
cout << d1 << endl;
//从后面开始查找
pos = str.rfind(".");
string d2 = str.substr(pos);
cout << d2 << endl;
string st1("fdsdfgdfdsa_aaakkkk56");
//从前向后找到字符集合中字符的下标
size_t p1 = st1.find_first_of("asg");
cout << st1 << endl;
while (p1 != st1.npos)
{
st1[p1] = '*';
p1 = st1.find_first_of("asg", p1 + 1);
}
cout << st1 << endl;
//从前向后找到不在字符集合中的下标
string str2("dfsafadfdffs56");
size_t p2 = str2.find_first_not_of("ad");
cout << str2 << endl;
while (p2 != str2.npos)
{
str2[p2] = '*';
p2 = str2.find_first_not_of("ad", p2 + 1);
}
cout << str2 << endl;
//从后向前找到字符集合中字符的下标
string str3("/uer/df/dd/a");
string str4("2026\\August\\test8.20\\Project1\\Project1");
replacestr(str3);
replacestr(str4);
//find_last_not_of:从后向前找不在字符集合范围之内的字符
}
运行结果:
12,非成员函数重载





四,String的模拟实现
代码链接:https://gitee.com/codelsj-w/test8.29.git
头文件:String.h 定义string类以及函数声明
cpp
#pragma once
#include <iostream>
#include <string>
#include <assert.h>
using namespace std;
namespace stt
{
class string
{
public:
//iterator的本质就是一个重命名
typedef char* iterator;
typedef const char* const_iterator;
iterator begin()
{
return _str;
}
iterator end()
{
return _str + _size;
}
const_iterator begin()const
{
return _str;
}
const_iterator end()const
{
return _str + _size;
}
//初始化
//C风格字符串初始化
string(const char* str = "")//默认值设置一个空,包含斜杠零,防止使用strlen直接报错
{
_size = strlen(str);
_capacity = _size;
_str = new char[_capacity + 1];
strcpy(_str, str);
}
//string类初始化--深拷贝
string(const string& st1)
{
_str = new char[st1._capacity + 1];
strcpy(_str, st1._str);
_capacity = st1._capacity;
_size = st1._size;
}
//s1 = s2
string& operator=(const string& st2)
{
if (this != &st2) //为了避免s1 = s1这种情况
{
delete[] _str;
_str = new char[st2._capacity + 1];
strcpy(_str, st2._str);
_capacity = st2._capacity;
_size = st2._size;
}
return *this;
}
//函数进行析构
~string()
{
delete[] _str;
_str = nullptr;
_size = _capacity = 0;
}
//短小频繁调用的函数直接定义在类当中,默认为内联inline
const char* c_str()const
{
return _str;
}
size_t size()const
{
return _size;
}
size_t capacity()const
{
return _capacity;
}
//清除空间
void clear()
{
_str[0] = '\0';
_size = 0;
}
//下标访问
char& operator[](size_t n)
{
assert(n < _size);
return _str[n];
}
const char& operator[](size_t n)const
{
assert(n < _size);
return _str[n];
}
//函数的声明
void reserve(size_t n);//提前预留空间
//在结尾插入字符或者字符串
void push_back(char s);
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);
//从某位置开始删除len个字符
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& sin, string& s);
}
源文件:String.cpp 函数的实现
cpp
#include "String.h"
namespace stt
{
const size_t string::npos = -1;
//函数的声明
void string::reserve(size_t n)
{
if (n > _capacity)
{
char* tmp = new char[n + 1];
strcpy(tmp, _str);
delete[] _str;
_str = tmp;
_capacity = n;
}
}
//在结尾插入字符或者字符串
void string::push_back(char s)
{
//满容--扩容
if (_size == _capacity)
{
reserve(_capacity == 0 ? 4 : 2 * _capacity);
}
_str[_size++] = s;
_str[_size] = '\0';
}
void string::append(const char* str)
{
size_t len = strlen(str);
//开辟空间
if (len + _size > _capacity)
{
reserve(len + _size > 2 * _capacity ? len + _size : 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 : 2 * _capacity);
}
//数据向后挪动
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)
{
assert(pos <= _size);
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-len+1 > pos)
{
_str[end] = _str[end - len];
end--;
}
//数据插入
const char* ch = str;
for (int i = 0;i < len;i++)
{
_str[pos + i] = str[i];
}
_size += len;
}
//从某位置开始删除len个字符
void string::erase(size_t pos, size_t len)
{
assert(pos < _size);//删除最后一个'\0'没有意义,之后还要加上
if (len >= _size-pos) //pos以及后面的全部删除
{
_str[pos] = '\0';
_size = pos;
}
else
{
//将后面没有删除的元素向前移动
for (size_t i = pos + len;i <= _size;i++)
{
_str[i-len] = _str[i];
}
_size = _size - len;
}
}
//查找字符或者字符串
size_t string::find(char ch, size_t pos)
{
assert(pos < _size);
for (size_t i = 0;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是否大于剩余字符,大于的话就进行更新
if (len > _size - pos)
{
len = _size - pos;
}
//取子串
string s1;
s1.reserve(len);
for (size_t i = 0;i < len;i++)
{
s1 += _str[pos + i];
}
return s1;
}
//全局比较函数的实现
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);
}
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 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& sin, string& s)
{
s.clear();//清理掉原有的内容
//s.reserve(1024); //空间很容易开辟过大或者过小
//开辟一个数组进行储存
//这样不用提前开辟空间,同时以一个str的大小为基准,容易控制
const size_t N = 256;
char str[N];
int i = 0;
char ch;
ch = sin.get();
while (ch != '\0' && ch != '\n')
{
str[i++] = ch;
//当数组满了,就将内容添加s当中
if (i == N - 1)
{
str[i] = '\0';
s += str;
i = 0; //重新开始
}
ch = sin.get(); //读取下一个字符
}
//这种表示最后一次没有充满数组str,将里面的值直接拷贝出来即可
if (i > 0)
{
str[i] = '\0';
s += str;
}
return sin;
}
}
源文件:test.cpp 功能的测试
cpp
#include "String.h"
namespace stt
{
//遍历,下标访问
void test01()
{
string s1;
string s2("abcdefg");
cout << s1.c_str() << endl;
cout << s2.c_str() << endl;
for (int i = 0;i < s2.size();i++)
{
s2[i] += 2;
}
cout << s2.c_str() << endl;
for (auto ch : s2)
{
cout << ch << " ";
}
cout << endl;
string::iterator a = s2.begin();
while (a != s2.end())
{
cout << *a << " ";
a++;
}
cout << endl;
}
//插入和删除
void test02()
{
string s2("asdfgh");
s2 += '!';
s2 += "&&&";
cout << s2.c_str() << endl;
s2.insert(0, 'G');
cout << s2.c_str() << endl;
s2.insert(3, "EEE");
cout << s2.c_str() << endl;
s2.insert(7, 'Q');
cout << s2.c_str() << endl;
s2.insert(0, "AAA");
cout << s2.c_str() << endl;
s2.erase(6);
cout << s2.c_str() << endl;
string s3("adcdeeegggg");
s3.erase(6, 54);
cout << s3.c_str() << endl;
s2.erase(2, 2);
cout << s2.c_str() << endl;
}
//下标查找和访问子串
void test3()
{
string s1("app.cgg.www");
string s2(s1);
string s3 = s2;
cout << s3.c_str() << endl;
size_t pos = s3.find('.');
string s4 = s3.substr(pos);
cout << s4.c_str() << endl;
}
//比较和流插入,流提取
void test4()
{
string s1("abccccccd");
string s2("agccccc");
cout << (s1 < s2) << endl;
cout << (s1 == s2) << endl;
cout << (s1 != s2) << endl;
cout << (s1 >= s2) << endl;
cout << ("agccccc" >= s2) << endl;
cout << s1 << s2 << endl;
string s3;
cin >> s3;
cout << s3 << endl;
}
}
int main()
{
//stt::test01();
//stt::test02();
//stt::test3();
stt::test4();
return 0;
}
_2,模拟实现补充
借助第三方的实现 :
深拷贝的另一种写法 :

赋值的另一种写法:


五,编码部分
计算机只能存储二进制010101,那么要如何表示各种符号的呢?各个国家的文字呢?
针对这个问题最开始发明的是ASCII码值

ASCII只能表示128个符号
之后针对各个国家的文字发明了万国码(Unicode)
Unicode是为了解决传统的字符编码方案的局限而产生的,它为每种语言中的每个字符设定了统一并且唯一的二进制编码,以满足跨语言、跨平台进行文本转换、处理的要求。1990年开始研发;
在中文中我们主要关注UTF-8这一可变长度字符编码

在1个字节时可以表示上面的ASCII码表中的各种符号
一般2~3个字节就可以表示绝大部分的文字了
两个字节就可以表示一些中文字符

变化展示:

六,Bug解析
