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

《告别 “会用不会讲”:C++ string 底层原理拆解 + 手撕实现,面试 / 开发都适用》

🔥草莓熊Lotso:个人主页

❄️个人专栏:《C++知识分享》《Linux 入门到实践:零基础也能懂》

生活是默默的坚持,毅力是永久的享受。


 🎬博主简介:


目录

前言:

 一. 别再停留在 “会用”!深挖string底层让你真正懂字符串

1.1 只会调用string接口的痛点

1.2 探究string的价值

二.  0基础手撕:从0搭建string核心底层逻辑(附实现代码)

2.1 底层构造逻辑:string类的成变量与构造逻辑

2.1.1  构造与析构:对象的 “创建” 与 “销毁”

2.1.2 c_str与辅助接口:兼容C风格

2.1.3 容量管理:resize和reserve的协同使用

2.1.4 代码测试:验证当前模块功能

2.2 迭代器与下标:string遍历的"两大高效工具"

2.2.1 迭代器的基本框架与实现

2.2.2 operator[] 的底层逻辑与实现

2.2.3 代码测试:验证当前模块功能

2.3 字符串修改:push_back,append,insert与+=的实现

2.3.1 尾插单个字符:push_back的实现

2.3.2 追加字符串:append的实现

2.3.3 任意位置插入:insert的实现(插入字符/字符串)

2.3.4 运算符重载:+=实现字符 / 字符串追加

2.3.5 代码测试:验证当前模块功能

2.4 字符串删减与截取:erase,clear与substr的实现

2.4.1 任意位置删除:erase的实现(删字符/删区间)

2.4.2 清空字符串:clear的实现

2.4.3 截取子串:substr的实现

2.4.4 代码测试:验证当前模块功能

2.5 字符串查找:find的实现(找字符/子串)

2.5.1 查找单个字符:find(char)的实现

2.5.2 查找子串:find(const char*)的实现

2.5.3 代码测试:验证当前模块功能

2.6 字符串的比较和输入输出:operator<<,operator>>,getline与比较运算符的实现

2.6.1 字符串比较:operator==,operator< 等一系列比运算符的实现

2.6.2 输入输出:operator<<和operator>>与getline的实现

2.6.3 代码测试:验证当前模块功能

结尾:


前言:

用 C++ 时天天碰  string,但你是否遇过:尾插字符突然变慢、拷贝后程序崩溃,面试被问手写时卡壳?其实问题都在底层。本文带新手拆 3 个核心成员,手把手撕构造、拷贝等关键代码,跟着敲就能 “会用又会讲”。


 一. 别再停留在 “会用”!深挖string底层让你真正懂字符串

1.1 只会调用string接口的痛点

  • 在我们日常写代码时,多数人对 string 的认知停留在 "调用接口",但是面试官在面试时有时会让你手动实现一个string类,很多人就傻掉了。还有就是在把 string 对象当参数传进函数,返回后程序竟然直接崩溃,没有意识到其实是因为 浅拷贝 出现的内存冲突问题。

1.2 探究string的价值

  • 搞懂 string 的底层,远远不止应对面试那么简单,更是在日常开发中提高效率的关键,了解了 reserve 预分配容量的原理,就能降低扩容的次数;理解深拷贝的逻辑,就能避免传参,赋值时的内存错误。以及为何 string 可以使用的有三种 swap 函数。最为重要的是,string的底层逻辑是C++容器设计的”缩影“---吃透它,再学 vector, list 等容器会轻松很多。

声明:本篇博客会将最终实现代码的gitee链接放在底下;在讲解期间会附上当前部分代码并注明是那个文件,大家可以自己跟着实现一下,但是不一定会是完整的可运行代码,中间涉及命名空间是为了区分和库里的string,大家跟着一步步往对应文件里加就可以了。

  • Gitee仓库:string类的实现代码

二.  0基础手撕:从0搭建string核心底层逻辑(附实现代码)

2.1 底层构造逻辑:string类的成变量与构造逻辑

string 的底层本质上是靠三个成员变量支撑,先搭建好基本类框架,再逐步实现功能,新手也能一步步自己手撕出一个基本的string类。

  • string.h:
#pragma once
#include<iostream>
#include<assert.h>
#include<algorithm>
#include<string.h>
using namespace std;namespace Lotso
{class string{ public:string(const char* str = "");string(const string& s);//先实现这个是为了在还没实现cout前方便测试打印观察const char* c_str()const{return _str;}string& operator=(const string& s);~string();void resize(size_t n, char c = '\0');void reserve(size_t n);size_t size()const{return _size;}size_t capacity()const{return _capacity;}private:char* _str;size_t _capacity;size_t _size;public://这里比较特殊,const static整型可以这么用,特殊处理//当然也可以声明和定义分离const static size_t npos = -1;//const static double npos=-1;//这个是不行的};};

2.1.1  构造与析构:对象的 “创建” 与 “销毁”

  • 构造函数负责为 string 对象分配初始内存,初始化状态;析构函数则在对象生命周期结束时,回收动态分配的内存,避免泄漏。同时,拷贝构造和赋值重载要实现深拷贝,确保多个对象间内存的独立,这个在之前类和对象中讲过,这里也会把链接放上,大家如果不理解这里深浅拷贝区别的一定要去看看,当然在后续的创作中博主也会再详细解析一下这个问题的。
  • 参考往期博客《吃透 C++ 类和对象(中):拷贝构造函数与赋值运算符重载深度解析》-CSDN博客

代码演示:(注意看注释)

  • string.h:
public:string(const char* str = "");string(const string& s);string& operator=(const string& s);~string();
  • string.cpp:
 //构造string::string(const char* str)//.h里面给缺省值:_size(strlen(str)){//_size = strlen(str);//这个写里面也可以//初始化列表初始化顺序跟声明顺序有关//所以这里写在函数体里比较好,可以灵活使用size_str = new char[_size + 1];//多开一个给\0;_capacity = _size;strcpy(_str, str);}//析构string::~string(){delete[] _str;_str = nullptr;_size = 0;_capacity = 0;}//拷贝构造:深拷贝,避免内存共享//string s2(s1);string::string(const string& s){_str = new char[s._capacity + 1];//strcpy(_str, s._str);//这里可以用strcpy,但是memcpy更好memcpy(_str, s._str, s._size + 1);_size = s._size;_capacity = s._capacity;}//赋值运算符重载;先释放旧内存,再深拷贝新内容//s1=s3string& string::operator =(const string& s){if (this != &s){char* tmp = new char[s._capacity + 1];//strcpy(_str,s._str);//这里用memcpy是因为处理串里中间有\0的情况;memcpy(tmp, s._str, s._size + 1);delete[] _str;_str = tmp;_size = s._size;_capacity=s._capacity;}return *this;}

--这里需要注意,拷贝构造和赋值运算符重载都采用了深拷贝的方式,即新对象会分配独立的内存空间来存储字符串内容,而不是简单的直接复制指针,这样可以避免多个对象共享同一块内存导致的"重复释放"等问题。

2.1.2 c_str与辅助接口:兼容C风格

c_str 函数的作用是返回string对象内部存储的C风格字符串(以'\0'结束),方便与C语言的字符串处理进行交互。

  • string.h:
public:
// 返回C风格字符串,方便兼容C语言接口
const char* c_str() const 
{return _str;
}// 获取有效字符数
size_t size() const 
{return _size;
}// 获取容量
size_t capacity() const 
{return _capacity;
}

2.1.3 容量管理:resize和reserve的协同使用

reserve 用于提前预留内存空间,避免频繁扩容;resize 则用于调整字符串的有效长度,在需要时还会调用 reserve 进行扩容,还可以指定填充字符。

  • string.h:
public:void resize(size_t n, char c = '\0');void reserve(size_t n);
  • string.cpp:
  // 预留内存空间,只改变容量,不改变有效字符数void string::reserve(size_t n){if (n > _capacity){char* tmp = new char[n + 1];//strcpy(tmp, _str);memcpy(tmp, _str, _size + 1);delete[] _str;_str = tmp;_capacity = n;}}// 调整有效字符长度,可指定填充字符void string::resize(size_t n, char ch){if (n <= _size){//删除,保留前n个_size = n;_str[_size] = '\0';}else {reserve(n);for (size_t i = _size; i < n; i++){_str[i] = ch;}_size = n;_str[_size] = '\0';}}

--当 resize 的目标长度 n 超过当前容量时,会调用 reserve 来扩容,可以指定字符(默认为’\0‘)填充新的位置,最后更新有效字符个数 _size 并在末尾补上'\0';

2.1.4 代码测试:验证当前模块功能

  • test.cpp:
namespace Lotso
{void test_string1(){string s1;cout << s1.c_str() << endl;string s2("Hello Lotso");cout << s2.c_str() << endl;s2[0] = 'h';for (size_t i = 0; i < s2.size(); i++){s2[i]++;}cout << s2.c_str() << endl;string s3 = "hello world";//隐式类型转换,构造+拷贝构造->优化为构造string s4("hello world");string s5;s5.resize(100, '*');cout << s5.c_str() << endl;s5.resize(10);cout << s5.c_str() << endl;s5.resize(20, '#');cout << s5.c_str() << endl;}
};int main()
{try {Lotso::test_string1();/*	cout << typeid(Lotso::string::iterator).name() << endl;cout << typeid(std::string::iterator).name() << endl;*/}catch (const exception& e){cout << e.what() << endl;}return 0;
}

补充测试:这个运行结果没放出来

--测试结果符合预期,退出码为0,该模块功能可以正常使用。

2.2 迭代器与下标:string遍历的"两大高效工具"

2.2.1 迭代器的基本框架与实现

迭代器是遍历容器元素的抽象机制,对于string,可以通过封装指针来实现简单迭代器,结合下标访问可以覆盖不同遍历场景。

  • string.h:
typedef char* iterator;
typedef const char* const_iterator;// iterator
iterator begin()
{return _str;
}const_iterator begin() const
{return _str;
}iterator end()
{return _str + _size;
}const_iterator end() const
{return _str + _size;
}

--这里将迭代器 typedef char*begin 函数返回指向字符串起始位置的指针,end 函数返回指向字符串有效字符结尾的下一个位置('\0'所在的位置)的指针,这样就可以利用指针的算术运算和解引用操作来实现迭代器的功能。

2.2.2 operator[] 的底层逻辑与实现

下标访问是string最常用的操作之一,通过重载operator[ ],可以像访问数组一样操作string的字符,底层本质是对 _str 指针的索引访问,同时也需要确保访问不会越界(这个可以加断言)

  • string.h:
public:
char& operator[](size_t index)
{assert(index < _size);return _str[index];
}const char& operator[](size_t index)const
{assert(index < _size);return _str[index];
}

operator[ ]支持两种核心场景:读取字符和修改字符,配合循环可实现字符串遍历,比迭代器更直观

关键说明:

  • 两个版本的重载:非const版本返回char&,支持修改字符(如s[1]='i');const版本返回const char&,仅允许读取(用于const对象)。
  • 越界检查:通过assert(pos<=_size)在调试阶段拦截越界访问, Release 模式下断言会失效,若需严格检查可改为抛异常。
  • 与迭代器的对比:operator[ ]更适合已知索引的场景(如随机访问第i个字符),迭代器更适合范围遍历,两者底层都是通过指针访问内存,效率一致。

2.2.3 代码测试:验证当前模块功能

  •  test.cpp:
namespace Lotso
{void test_string2(){string s2("Hello Lotso");cout << s2.c_str() << endl;s2[0] = 'h';for (size_t i = 0; i < s2.size(); i++){s2[i]++;}cout << s2.c_str() << endl;string s4("hello world");const string s5("hello Lotso");for (size_t i = 0; i < s5.size(); i++){//s5[i]++;不可以写,但可以读cout << s5[i] << "-";}cout << endl;for (auto ch : s4){cout << ch << " ";}cout << endl;string::iterator it4 = s4.begin();while (it4 != s4.end()){*it4 += 1;cout << *it4 << " ";++it4;}cout << endl;for (auto ch : s5){cout << ch << " ";}cout << endl;string::const_iterator it5 = s5.begin();while (it5 != s5.end()){//*it5+=1;//这个不行cout << *it5 << " ";++it5;}cout << endl;}
};int main()
{try {	//Lotso::test_string1();Lotso::test_string2();/*	cout << typeid(Lotso::string::iterator).name() << endl;cout << typeid(std::string::iterator).name() << endl;*/}catch (const exception& e){cout << e.what() << endl;}return 0;
}

--测试结果符合预期,退出码为0,该模块功能可以正常使用。

2.3 字符串修改:push_back,append,insert与+=的实现

  • 字符串的修改操作是string的核心功能,push_back用于尾插单个字符,append用于追加字符串,insert支持指定位置插入,但是三者的底层实现都需要处理内存扩容和数据迁移,确保高效的操作

2.3.1 尾插单个字符:push_back的实现

push_back的作用是在字符串的末尾添加一个字符,核心逻辑是"先检查容量,不足就扩容",再插入字符并更新_size

string.h:

public:
void push_back(char ch);

string.cpp:

void string::push_back(char ch)
{if (_size == _capacity){reserve(_capacity == 0 ? 4 : _capacity * 2);}_str[_size] = ch;_size++;_str[_size] = '\0';
}

关键逻辑

  • 扩容策略采用 “2 倍增长”(空串特殊处理为 1),平衡内存利用率和扩容次数;
  • 每次插入后强制补'\0',确保C_str()返回的字符串始终有效

2.3.2 追加字符串:append的实现

append支持追加C风格的字符串和另一个string对象,这里主要展示字符串。底层需要计算追加的长度,并检查容量是否足够,再拷贝字符。

string.h:

public:
void append(const char* str);

string.cpp:

void string::append(const char* str)
{size_t len = strlen(str);if (_size + len > _capacity){//这样扩容比较好,每次插入的短就会2倍扩,多就会直接扩_size+lenreserve(max(_size + len, 2 * _capacity));}//strcpy(_str + _size, str);memcpy(_str + _size, str, len + 1);_size += len;
}

关键逻辑:

  • 直接复用reserve和strcpy,减少代码冗余;
  • 批量追加比循环调用push_back更高效(避免多次扩容)。
  • 扩容方案合理,避免频繁扩容。

2.3.3 任意位置插入:insert的实现(插入字符/字符串)

insert支持在指定位置插入单个字符或者字符串,核心是"先挪到原有字符,再插入新内容",需要特别处理扩容和内存重叠问题。

  • string.h:
public:
void insert(size_t pos, char ch);
void insert(size_t pos, const char* str);
  • string.cpp:
void string::insert(size_t pos, char ch)
{if (_size == _capacity){reserve(_capacity == 0 ? 4 : _capacity * 2);}//移动数据//int end = _size;//不能用size_t//while (end >= (int)pos)//强转一下//{//    _str[end + 1] = _str[end];//    --end;//}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){//这样扩容比较好,每次插入的短就会2倍扩,多就会直接扩_size+lenreserve(max(_size + len, 2 * _capacity));}size_t end = _size + len;while (end > pos + len - 1){_str[end] = _str[end - len];--end;}//strncpy(_str + pos, str, len);memcpy(_str + pos, str, len);_size += len;
}

--这里都有两种挪动数据的方式,大家可以按照自己的习惯来选择

2.3.4 运算符重载:+=实现字符 / 字符串追加

+=push_backappend"语法糖",支持追加单个字符或者字符串,底层直接复用已有函数逻辑,简化代码的书写。

  • string.h:
public:string& operator+=(char ch){push_back(ch);return *this;}string& operator+=(const char* str){append(str);return *this;}

优势

  • +=本质是对push_back和append的封装,避免重复编写扩容和字符拷贝逻辑;
  • 返回*this(对象引用)是实现链式操作的核心,确保每次调用后仍能继续操作当前对象;
  • 与append相比,+=更适合简单场景,代码可读性更高,两者底层效率一致。

2.3.5 代码测试:验证当前模块功能

  • test.cpp:
namespace Lotso
{void test_string3(){string s1;cout << s1.c_str() << endl;string s2("Hello Lotso");cout << s2.c_str() << endl;s2.push_back('x');cout << s2.c_str() << endl;string s3("hello");s3.append("********************");cout << s3.c_str() << endl;string s4("hello");s4 += '*';s4 += "hello Lotso";cout << s4.c_str() << endl;string s5("hello world");cout << s5.c_str() << endl;s5.insert(5,'x');cout << s5.c_str() << endl;}
};int main()
{try {//Lotso::test_string1();//Lotso::test_string2();Lotso::test_string3();/*	cout << typeid(Lotso::string::iterator).name() << endl;cout << typeid(std::string::iterator).name() << endl;*/}catch (const exception& e){cout << e.what() << endl;}return 0;
}

--测试结果符合预期,退出码为0,该模块功能可以正常使用。

2.4 字符串删减与截取:erase,clear与substr的实现

  • 字符串的删减(erase,clear)截取(substr)是高频操作。底层实现需处理字符挪动,内存状态的重置或者是子串拷贝,确保字符串的完整和正确性

2.4.1 任意位置删除:erase的实现(删字符/删区间)

erase支持两种场景,删除指定位置的单个字符,或删除从指定位置开始的连续n个字符,核心逻辑就是'挪动后续的字符覆盖掉待删除内容",无需释放内存(容量不变,仅修改有效长度)

string.h:

public:
// 删除pos位置上的元素,并返回该元素的下一个位置
void erase(size_t pos = 0, size_t len = npos);

string.cpp:

void string::erase(size_t pos, size_t len)
{assert(pos <= _size);if (len == npos || len >= _size - pos){//删完_size = pos;_str[_size] = '\0';}else {//删部分//strcpy(_str + pos, _str + pos + len);memcpy(_str + pos, _str + pos + len, _size - (pos + len) + 1);_size -= len;}
}

2.4.2 清空字符串:clear的实现

clear用于快速清空所有有效字符,底层无需释放内存(保留容量,便于后续复用),仅需重置_size和结束符

  • string.h:
public:
void clear()
{_str[0] = '\0';_size = 0;
}

优势:

  • 相比erase更加高效,仅修改状态即可。
  • 保留原有容量,后续插入字符可以避免重新扩容。

2.4.3 截取子串:substr的实现

substr用于指定位置截取连续n个字符,返回一个新的string对象,底层拷贝目标子串到新内存

  • string.h:
public:
string substr(size_t pos=0, size_t len=npos);
  • string.cpp:
string string::substr(size_t pos, size_t len)
{assert(pos < _size);if (len == npos || 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;
}

2.4.4 代码测试:验证当前模块功能

  • test.cpp:
namespace Lotso
{void test_string4(){string s1("hello world");cout << s1.c_str() << endl;s1.erase(4, 3);cout << s1.c_str() << endl;string s2("hello world");cout << s2.c_str() << endl;s2.erase(4);cout << s2.c_str() << endl;string s3("hello world");cout << s3.c_str() << endl;s3.erase(4,100);cout << s3.c_str() << endl;string s4 = s1.substr(2);cout << s4.c_str() << endl;string s5 = s1.substr(2, 2);cout << s5.c_str() << endl;}
};int main()
{try {//Lotso::test_string1();//Lotso::test_string2();//Lotso::test_string3();Lotso::test_string4();/*	cout << typeid(Lotso::string::iterator).name() << endl;cout << typeid(std::string::iterator).name() << endl;*/}catch (const exception& e){cout << e.what() << endl;}return 0;
}

--测试结果符合预期,退出码为0,该模块功能可以正常使用。

2.5 字符串查找:find的实现(找字符/子串)

  • find是字符串查找的核心接口,支持从指定位置开始查找单个字符或子串,返回首次出现的位置(未找到就返回npos),底层通过遍历比对实现,逻辑清晰使用场景广。

2.5.1 查找单个字符:find(char)的实现

查找单个字符时,从指定起始位置遍历字符串,逐个比对字符,找到则返回位置,遍历结束仍未找到则返回npos。

  • string.h:
public:
// 返回ch在string中第一次出现的位置
size_t find(char ch, size_t pos = 0) const;
  • string.cpp:
size_t string::find(char ch, size_t pos) const
{assert(pos < _size);for (size_t i = pos; i < _size; i++){if (_str[i] == ch){return i;}}return npos;
}

关键逻辑:

  • 起始位置pos默认从0开始(整个字符串查找),也可以指定位置开始。
  • 遍历范围限制在[pos,_size),避免越界。

2.5.2 查找子串:find(const char*)的实现

查找子串时,需从起始位置开始,逐个匹配子串的每个字符,全部匹配则返回起始位置,否则继续向后移动比对,直到主串剩余长度不足子串长度为止。

  • string.h:
public:
// 返回子串str在string中第一次出现的位置
size_t find(const char* str, size_t pos = 0) const;
  • string.cpp:
size_t string::find(const char* str, size_t pos) const
{assert(pos < _size);//大家也可以看看一个算法,我这里挂上链接//https://www.bilibili.com/video/BV1UL411E7M8/?spm_id_from=333.1387.list.card_archive.click&vd_source=e76166931683eb6cd68b7efecd0cdfc0const char* ptr = strstr(_str + pos, str);if (ptr){return ptr - str;}else {return npos;}
}

2.5.3 代码测试:验证当前模块功能

  • test.cpp:
namespace Lotso
{void test_string7(){string url = "https://legacy.cplusplus.com/reference/string/string/rfind/";size_t i1 = url.find(':');if (i1 != string::npos){string protocol = url.substr(0, i1);cout << protocol << endl;size_t i2 = url.find('/', i1 + 3);if (i2 != string::npos){string domain = url.substr(i1 + 3, i2 - (i1 + 3));cout << domain << endl;string uri = url.substr(i2 + 1);cout << uri << endl;}}}
};int main()
{try {//Lotso::test_string1();//Lotso::test_string2();//Lotso::test_string3();//Lotso::test_string4();Lotso::test_string7();/*	cout << typeid(Lotso::string::iterator).name() << endl;cout << typeid(std::string::iterator).name() << endl;*/}catch (const exception& e){cout << e.what() << endl;}return 0;
}

--测试结果符合预期,退出码为0,该模块功能可以正常使用。

2.6 字符串的比较和输入输出:operator<<,operator>>,getline与比较运算符的实现

字符串的比较(大小关系、相等性)和输入输出是基础且高频的操作。通过重载比较运算符,实现字符串大小判断,结合流插入 / 提取运算符,可让自定义的string类完全适配 C++ 的操作习惯。

2.6.1 字符串比较:operator==,operator< 等一系列比运算符的实现

  • string.h:
public://relational operatorsbool operator<(const string& s) const;bool operator<=(const string& s) const;bool operator>(const string& s) const;bool operator>=(const string& s) const;bool operator==(const string& s) const;bool operator!=(const string& s) const;
  • string.cpp:
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);
}

关键逻辑:

  • 字典序规则:如同查字典,先比第一个字符,不同则直接判断;若相同则比第二个,以此类推;若前 n 个字符全相同,长度短的字符串更小(如 "app" < "apple");
  • 复用设计:仅需实现==和<,其他运算符通过逻辑反转或交换参数推导,避免重复编写比对逻辑,减少出错概率。

2.6.2 输入输出:operator<<和operator>>与getline的实现

我们前面都是使用c_str进行打印观察的,这里还是实现一下流插入和流提取。

  • string.h:
//string类外面
std:: ostream& operator<<(ostream& _cout, const string& s);
std:: istream& operator>>(istream& _cin, string& s);
std::istream& getline(std::istream& in, string& s, char delim = '\n');
  • string.cpp:
    //流插入(输出):将字符串内容写入输出流std::ostream& operator<<(std::ostream& out, const string& s){for (auto ch : s){out << ch;}return out;}//流提取(输入):从输入流读取到空白字符为止std::istream& operator>>(std::istream& in, string& s){s.clear();//先清空原有内容char buff[256];int i = 0;char ch;//in>>ch;//这个不行,读不了空格ch = in.get();while (ch != '\n' && ch != ' '){buff[i++] = ch;if (i == 255){buff[i] = '\0';s += buff;i = 0;}ch = in.get();}//要是没有255就这样处理if (i > 0){buff[i] = '\0';s += buff;}return in;}//整行读取:读取到指定分隔符(默认'\n')为止std::istream& getline(std::istream& in, string& s, char delim){s.clear();char buff[256];int i = 0;char ch;//in>>ch;//这个不行,读不了空格ch = in.get();while (ch != delim){buff[i++] = ch;if (i == 255){buff[i] = '\0';s += buff;i = 0;}ch = in.get();}//要是没有255就这样处理if (i > 0){buff[i] = '\0';s += buff;}return in;}

输入输出细节:

  • operator>> 会自动跳过前导空白,且遇到空白字符停止,适合读取单词;
  • getline不跳过前导空白,会读取包括空格在内的所有字符,直到遇到delim(默认换行符),适合读取整行文本;

2.6.3 代码测试:验证当前模块功能

  • test.cpp:
#include <iostream>
#include <cctype>
using namespace std;int main() {// 测试比较运算符Lotso::string s1("apple"), s2("app"), s3("banana");cout << "s1 == s2? " << (s1 == s2 ? "是" : "否") << endl; // 否(长度不同)cout << "s1 < s3? " << (s1 < s3 ? "是" : "否") << endl;   // 是('a' < 'b')cout << "s2 <= s1? " << (s2 <= s1 ? "是" : "否") << endl; // 是(s2更短)// 测试输入输出Lotso:: string s4, s5;cout << "\n请输入两个单词(空格分隔):";cin >> s4 >> s5;cout << "读取结果:s4=" << s4 << ", s5=" << s5 << endl;cin.ignore(); // 忽略输入流中剩余的换行符Lotso::string s6;cout << "请输入一行话(含空格):";getline(cin, s6);cout << "整行读取结果:" << s6 << endl;return 0;
}

--这里我测试是没问题的,大家可以自己试试,涉及输入我就不展示我的了

说明:

  • 比较运算符完全遵循字典序,结果符合预期;
  • operator>>正确读取两个单词(以空格为分隔),getline正确读取包含空格的整行内容;
  • cin.ignore()用于清楚未读取的换行符,避免getline直接读取空行。

结尾:

往期回顾:

《从崩溃到精通:C++ 内存管理避坑指南,详解自定义类型 new/delete 调用构造 / 析构的关键逻辑》

别再用函数重载堆代码了!C++ 模板初阶教程:原理 + 实例 + 避坑,新手也能秒懂

C++ 开发者必看!STL 库 + 字符编码一篇通,告别乱码与重复造轮子

C++ string 类使用超全攻略:从入门到高效避坑,日常开发直接使用

结语:当你手撕string类后会发现,它本质是 “动态数组 + 内存管理”。扩容倍数、深拷贝等设计,都是效率与安全的权衡。这些逻辑不仅能避坑,更是学容器的通用思路。评论区可交流,后续也会分享更多容器实现。技术学习,“懂原理” 才是底气。

✨把这些内容吃透超牛的!放松下吧✨
ʕ˘ᴥ˘ʔ
づきらど

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

相关文章:

  • 手机怎样设计网站建设无锡连夜发布最新通告
  • 18互联网站做网站程序黑马大数据培训
  • Redis面试问题集
  • Qt常用控件之QLineEdit
  • 在docker里面安装nextcloud
  • 3.6、操作系统
  • 2025最新centos7安装mysql8 相关 服务器配置 纯命令行操作 保姆级教程
  • Hadess入门到实战(4) - 如何管理Docker制品
  • Python 数学公式构建海洋不明生物(好像是水母)动画 - 傅里叶合成模拟复杂波形
  • 泰州建设企业网站福州企业免费建站
  • 美妆网站模板html5网站开发语言
  • 深度剖析OpenHarmony AI Engine:开发板端侧大模型推理插件机制全链路拆解
  • 链表的核心思想
  • Matlab通过GUI实现点云的坡度滤波(附最简版)
  • GESP8级——AT_icpc2013spring_e 最小生成树题解
  • Qt窗口与对话框开发指南
  • 湖北平台网站建设哪家好有经验的邵阳网站建设
  • 33.Linux iCSCI 服务
  • 国内优秀网页设计网站网站建设流程共有几个阶段
  • 基于物联网的个人健康管理系统(论文+源码)
  • 一个网站可以做几个关键词网站建设济南
  • Linux日志分析入门:使用grep和awk发现服务器异常访问
  • 435. 无重叠区间
  • 2025制造业研发流程提效指南:从审批卡顿到协同闭环,3类系统选型全解析
  • 文件夹随机分配 把文件夹随机分配到指定的文件夹中
  • 104.二叉树的最大深度(二叉树算法题)
  • 宿迁做网站的怎样制作h5
  • 电子商务网站建设结业论文seo教程seo官网优化详细方法
  • 使用 Three.js 和本地 Draco Loader 高效加载压缩 GLB 模型
  • Nginx-Proxy-Manager配置SSL泛域名证书教程