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

【C++】深入理解string类(5)

目录

一 模拟实现string类的补充

1 resize

2 find

3 substr

4 流提取和流输出

5  getline

6 operator =

 clear

8 其他运算符重载

9 三种swap

​编辑

二 string拷贝构造的传统写法和现代写法

1 传统写法

2 现代写法

三 运算符重载(=)的传统写法和现代写法

1 传统写法

2 现代写法

3 二者使用有什么区别?


一 模拟实现string类的补充

之前写的string类如下:【c++】深入理解string类(4)

1 resize

功能是将字符串的有效长度(_size)修改为指定值 n,并根据需要填充新字符或截断字符串

​void string::resize(size_t n, char ch){if (n <= _size){// 删除,保留前n个_size = n;_str[_size] = '\0';}else{reserve(n);//调用 reserve(n) 预分配至少能容纳 n 个字符的内存(避免后续填充时频繁扩容)for (size_t i = _size; i < n; i++){_str[i] = ch;}_size = n;_str[_size] = '\n';}}​

2 find

查找字符或子串出现的位置:

1 查找字符:

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;}

2 查找子串:

strstr 是 C 语言标准库(string.h)中的一个字符串处理函数,用于在一个字符串(主串)中查找另一个字符串(子串)的首次出现位置。

// 在当前字符串中查找子串 str 的第一次出现位置
// 参数:
//   str:待查找的 C 风格字符串(以 '\0' 结尾)
//   pos:起始查找位置(从 pos 开始向后查找,默认从 0 开始)
// 返回值:找到时返回子串首字符在当前字符串中的位置;未找到返回 npos(通常定义为 -1)
size_t string::find(const char* str, size_t pos)
{assert(pos < _size);// strstr(a, b) 功能:在字符串 a 中查找子串 b 第一次出现的位置,返回指向该位置的指针;// 若未找到,返回 nullptr。// 这里从 _str + pos 开始查找(即从当前字符串的 pos 位置向后)const char* ptr = strstr(_str + pos, str);if (ptr){// 若找到子串,计算其在当前字符串中的位置:// 指针 ptr 减去当前字符串的起始地址 _str,得到偏移量(即位置)return ptr - _str;  // 注意:原代码中写的是 ptr - str,这里修正为 ptr - _str(原代码可能笔误)}else{// 未找到子串,返回 npos(表示“不存在的位置”)return npos;}
}

3 substr

功能:从 pos 位置开始,提取长度为 len 的子串(若 len 过长则提取到字符串末尾)

// 从当前字符串中提取子串(从 pos 位置开始,长度为 len)
// 参数:
//   pos:子串的起始位置(必须小于当前字符串长度 _size)
//   len:子串的长度(默认或传入 npos 时,表示提取到字符串末尾)
// 返回值:提取出的子串(string 对象)
string string::substr(size_t pos, size_t len)
{assert(pos < _size);// 处理 len 过长或为 npos 的情况:// 若 len 是 npos,或 len 超过从 pos 到末尾的剩余字符数,则将 len 修正为剩余字符数if (len == npos || len > _size - pos){len = _size - pos;  // 剩余字符数 = 总长度 - 起始位置}// 创建一个空的 string 对象,用于存储子串string sub;// 提前为子串预留足够的空间(容量设为 len),避免循环中频繁扩容sub.reserve(len);// 从 pos 位置开始,拷贝 len 个字符到子串中for (size_t i = 0; i < len; i++){sub += _str[pos + i];  // 逐个字符追加到子串(利用 operator+= 操作)}// 返回提取到的子串return sub;
}

4 流提取和流输出

不一定必须写成友元函数


//   out:输出流对象(如 cout)
//   s:要打印的自定义 string 对象(加 const 确保不修改原对象)
std::ostream& operator<<(std::ostream& out, const string& s)
{for (auto ch : s){out << ch;  // 将每个字符逐个输出到流中}return out;  // 返回输出流,支持链式操作
}

此处的s不能加const

//   in:输入流对象(如 cin)
//   s:接收输入的 string 对象(非 const,需修改其内容)
// 返回值:输入流对象(支持链式调用,如 cin >> s1 >> s2)
std::istream& operator>>(std::istream& in, string& s)
{s.clear();  // 先清空原字符串,避免输入内容追加到原有数据后char buff[256];  // 临时缓冲区,每次最多存 255 个字符(+1 个 '\0')int i = 0;       // 缓冲区当前填充位置char ch;// 用 in.get() 读取字符(相比 >> 运算符,get() 会读取空格和换行符,此处用于自定义终止条件)ch = in.get();// 循环读取字符,直到遇到换行符 '\n' 或空格 ' '(默认以空白字符作为输入结束标志)while (ch != '\n' && ch != ' '){buff[i++] = ch;  // 将字符存入缓冲区// 若缓冲区已满(i=255,此时 buff[0..254] 已填满)if (i == 255){buff[i] = '\0';  // 手动添加字符串结束符s += buff;       // 将缓冲区内容追加到 s 中i = 0;           // 重置缓冲区索引,准备接收下一批字符}ch = in.get();  // 继续读取下一个字符}// 循环结束后,若缓冲区中还有未处理的字符(i > 0)if (i > 0){buff[i] = '\0';  // 添加结束符s += buff;       // 追加到 s 中}return in;  // 返回输入流,支持链式操作
}

5  getline

// 从输入流中读取一行字符串,直到遇到分隔符 delim 为止
// 参数:
//   in:输入流对象(如 cin)
//   s:接收读取内容的 string 对象
//   delim:自定义分隔符(默认通常是 '\n',即换行符)
// 返回值:输入流对象(支持链式操作)
std::istream& getline(std::istream& in, string& s, char delim)
{s.clear();  // 清空目标字符串,确保读取的是新内容(而非追加到原有数据后)char buff[256];  // 临时缓冲区,每次最多存储 255 个字符(预留 1 个位置给 '\0')int i = 0;       // 缓冲区当前填充的字符索引char ch;// 用 in.get() 读取单个字符(包括空格、制表符等空白字符,不会像 >> 那样自动跳过)ch = in.get();// 循环读取字符,直到遇到指定的分隔符 delim 为止while (ch != delim){// 将读取到的字符存入缓冲区buff[i++] = ch;// 若缓冲区已满(i=255,此时 buff[0] 到 buff[254] 已存满)if (i == 255){buff[i] = '\0';  // 手动添加字符串结束符,确保 buff 是合法的 C 风格字符串s += buff;       // 将缓冲区内容追加到目标 string 对象 s 中i = 0;           // 重置缓冲区索引,准备接收下一批字符}// 继续读取下一个字符ch = in.get();}// 循环结束后,若缓冲区中还有未处理的字符(i > 0)if (i > 0){buff[i] = '\0';  // 添加结束符s += buff;       // 将剩余字符追加到 s 中}return in;  // 返回输入流对象,支持链式调用(如 getline(cin, s1) >> s2)
}// 定义 string 类的静态成员 npos(表示“无效位置”或“到末尾”)
// npos 通常被定义为 size_t 的最大值(-1 转换为无符号类型后即为最大值)
const size_t string::npos = -1;

6 operator =

赋值操作

将s3赋值给s1,此处是深拷贝。开辟一个和s3一样大的空间,s1指向该空间,释放s1原本的旧空间,将s3的内容拷贝给s1

string& string::operator=(const string& s){if (this != &s){char* tmp = new char[s._capacity + 1];//strcpy(tmp, s._str);memcpy(tmp, s._str, s._size + 1);delete[] _str;_str = tmp;_size = s._size;_capacity = s._capacity;}return *this;}

注意区分拷贝构造函数和赋值运算符重载:

但是上面的代码还是有bug:

如果在strcpy拷贝的时候,遇到\0,就会直接停止拷贝(例如:hello world\0yyy\0),如果是在字符串的中间有\0,那么就会造成拷贝的不完全,所以不能使用strcpy,而是用memcpy

相应的,在使用strcpy的部分都换成memecpy 

所以,上面的拷贝构造就优化为:


 clear

清除数据

void clear(){_str[0] = '\0';_size = 0;}

8 其他运算符重载

实现了一个后面的就可以复用

bool string::operator<(const string& s) const{return strcmp(_str, s._str) < 0;}bool string::operator<=(const string& s) const{return *this < s || *this == s;}bool string::operator>(const string& s) const{return !(*this <= s);}bool string::operator>=(const string& s) const{return !(*this > s);}bool string::operator==(const string& s) const{return strcmp(_str, s._str) == 0;}bool string::operator!=(const string& s) const {return !(*this == s);}

9 三种swap

1. std::string::swap(成员函数)

  • 形式:void swap(string& str);
  • 特点:string 类自身的成员函数,用于交换当前 string 对象与参数 str 的内容
  • 效率:极高。因为内部只需交换 string 的 “指针、大小、容量” 等成员(类似 “指针交换”),无需深拷贝字符数据。

2. std::swap(string)(针对 string 的特化函数)

  • 形式:void swap(string& x, string& y);
  • 特点:标准库为 string 专门优化的全局 swap 函数(属于对通用 swap 模板的 “特化”)。
  • 效率:与 string::swap 一致(内部通常直接调用 string::swap 实现,因此效率相同)。

3. 通用 std::swap 模板(早期实现,非专用于 string

  • 形式:template <class T> void swap(T& a, T& b);

第三个是标准库里的swap,在实现的时候可能会造成三次深拷贝(string使用时),代价太大

前两个的模拟实现:

template <class T>
void swap(T& a, T& b) {T c(a); // 1. 拷贝构造临时对象 c,存储 a 的值a = b;  // 2. 把 b 的值赋给 ab = c;  // 3. 把临时对象 c(原 a 的值)赋给 b
}inline void swap(string& a, string& b) {a.swap(b); // 调用 string 自身的 swap 成员函数
}

二 string拷贝构造的传统写法和现代写法

1 传统写法

string::string(const string& s)
{_str = new char[s._capacity + 1];       // 分配新内存memcpy(_str, s._str, s._size + 1);      // 拷贝字符串(包括'\0')_size = s._size;                        // 同步长度_capacity = s._capacity;                // 同步容量
}

2 现代写法

string::string(const string& s)
{string tmp(s._str);  // 先构造临时对象(利用已有的构造逻辑)swap(tmp);           // 与临时对象交换资源(指针、大小、容量)
}

代码解析:

(1)

string tmp(s._str);  // 构造临时对象 tmp

这里的逻辑是:

  1. 利用源对象的底层字符串s._str 是源对象 s 中存储字符串的字符数组指针(C 风格字符串,以 '\0' 结尾)。
  2. 调用 const char* 构造函数string 类通常有一个接收 const char* 类型参数的构造函数(带参构造函数),其作用是根据 C 风格字符串初始化 string 对象(分配内存、拷贝字符串内容、设置 _size 和 _capacity)。
  3. 临时对象 tmp 的状态:通过 s._str 构造的 tmp,其内部的 _str 指针指向一块新分配的内存,存储的字符串内容与 s._str 完全相同,且 _size 和 _capacity 也与 s 一致(因为拷贝了相同的字符串)。

为什么要这一步?

  • 复用已有逻辑const char* 构造函数已经实现了 “根据字符串内容分配内存、拷贝数据、初始化成员变量” 的完整逻辑。通过构造 tmp,可以直接复用这部分代码,避免在拷贝构造函数中重复编写相同的逻辑(减少冗余,降低出错风险)。
  • 为后续交换做准备tmp 此时已经是一个与源对象 s 内容完全一致的 “副本”,接下来只需要通过 swap 函数,将 tmp 的资源(新分配的内存、大小、容量)转移给当前正在构造的对象即可。

(2)

string 类的 swap 成员函数的作用是将当前对象(this 指向的对象)与另一个对象的资源进行交换。它的函数原型通常是:

void swap(string& other);  // 成员函数,仅需一个参数
  • 调用者是当前对象(*this),参数 other 是要交换的另一个对象。
  • 函数内部通过交换两者的 _str 指针、_size_capacity 等成员,完成资源互换。


三 运算符重载(=)的传统写法和现代写法

1 传统写法

string& string::operator=(const string& s)
{if(*this != &s){char* tmp = new char[s.capacity+1];memcpy(tmp,s._str,s._szie+1);detlete[] _str;_str = tmp;_size = s._size;_capacity = s._capacity;}return *this;
}

2 现代写法

(1)

string& string::operator=(const string& s)
{if (this != &s) // 防止自赋值(如 s1 = s1){string tmp(s._str); // 用 s 的 _str 构造临时对象 tmpswap(tmp); // 交换当前对象与 tmp 的资源}return *this;
}

(2)

string& string::operator=(string tmp) // 注意:参数是值传递,会先拷贝构造 tmp
{swap(tmp); // 交换当前对象与 tmp 的资源return *this;
}
  • 逻辑
    • 利用 值传递的特性:调用这个函数时,编译器会自动拷贝一份实参(即要赋值的对象)到 tmp 中(这一步是 “拷贝构造”)。
    • 直接交换当前对象与 tmp 的资源,tmp 销毁时会带走当前对象原来的旧资源,当前对象则获得 tmp 拷贝来的新资源。
    • 这种写法连 “自赋值判断” 都省略了:因为值传递的 tmp 是独立拷贝,即使自赋值,交换后也不影响正确性(只是多一次拷贝)。

3 二者使用有什么区别?

传统写法和现代写法的算法效率是一样的,只是现代写法的代码较短,代码写法不同,充分利用了复用,本质上区别不大



四 面试中实现string类

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

相关文章:

  • 六、Hive的基本使用
  • 铜陵网站建设推广江苏核酸检测机构
  • 电子商务网站建设含义如果做车站车次查询的网站需要什么消息信息
  • 【JETSON+FPGA+GMSL】实测分享 | 如何实现激光雷达与摄像头高精度时间同步?
  • 建网站权威公司dw怎么做打开网站跳出提示
  • 阅读:REACT: SYNERGIZING REASONING AND ACTING INLANGUAGE MODELS(在语言模型中协同推理与行动)
  • 语义三角论对AI自然语言处理中深层语义分析的影响与启示
  • SpringBoot 启动时执行某些操作的 8 种方式
  • Cloud IDE vs 本地IDE:AI编程时代的“降维打击“
  • RocketMQ 事务消息
  • 做网站的不肯给ftp企业163邮箱登录
  • reactNative 遇到的问题记录
  • 使用 Cloudflare Turnstile 实现 Java 后端的人机验证
  • 【论文阅读】Knowledge Circuits in Pretrained Transformers
  • SpringBoot3集成Mybatis(开启第一个集成Mybatis的后端接口)
  • 论文阅读 (2) :Reducing Divergence in GPGPU Programs with Loop Merging
  • React 01
  • 建设开发网站潍坊百度网站优化
  • AI 在数据库操作中的各类应用场景、方案与实践指南
  • ASTM C615/C615M-23 花岗石检测
  • 用php做的网站论文抖音的商业营销手段
  • 子数组/子串问题
  • 办公空间设计网站浙江恒元建设网站
  • 银河麒麟 aarch64 linux 里面的 qt 怎么安装kit
  • 2025电脑价格数据集/构建电脑价格预测模型/数据量为 10 万行
  • Linux 系统下 MySQL 的安装配置
  • 16、Docker Compose 安装Kafka(含Zookeeper)
  • QT(c++)开发自学笔记:2.TCP/IP
  • C语言基础语法进阶
  • 池州网站建设公司好的网站你知道