【C++修炼之路】string 模拟实现

news2024/11/17 23:40:19

👑作者主页:@安 度 因
🏠学习社区:StackFrame
📖专栏链接:C++修炼之路

文章目录

  • 一、默认成员函数
    • 1、全缺省构造
    • 2、析构
    • 3、拷贝构造(深拷贝)
    • 4、赋值重载(深拷贝)
  • 二、访问
    • 1、[ ] 重载
    • 2、迭代器
  • 三、容量操作
    • 1、size
    • 2、clear
    • 3、reserve
    • 4、resize
  • 四、修改
    • 1、push_back
    • 2、append
    • 3、+=
    • 4、c_str
    • 5、find
    • 6、substr
    • 7、insert
    • 8、erase
  • 五、重载符号
  • 六、流插入/流提取

如果无聊的话,就来逛逛 我的博客栈 吧! 🌹

一、默认成员函数

1、全缺省构造

string(const char* str = "") // 缺省参数
{
    _size = strlen(str);
    _capacity = _size;
    _str = new char[_capacity + 1]; // 开辟空间
    // strcpy(_str, str);
    memcpy(_str, str, _size + 1); // 拷贝包括 \0 
}

2、析构

~string()
{
    if (_str)
    {
        delete[] _str;
        _str = nullptr;
        _size = _capacity = 0;
    }
}

delete 释放空是没有问题的。但是释放空也没有意义,所以可以加个条件。

3、拷贝构造(深拷贝)

  1. 自己开辟空间,拷贝数据,释放空间
string(const string& s)
{
    _str = new char[s._capacity + 1];
    // strcpy(_str, s._str);
    memcpy(_str, s._str, s._size + 1); // 需要完整拷贝
    _size = s._size;
    _capacity = s._capacity;
}
  1. 把开辟空间和拷贝数据给构造函数做
string(const string& s)
    :_str(nullptr)
    ,_size(0)
    ,_capacity(0)
{
    string tmp(s._str); // 构造函数调用
    swap(tmp);
}

初始化列表必须写,因为对于类中内置类型一般来说是不处理的, 这里虽然处理了,但是编译器不一定会每次处理,到时候交换可能会出现随机值的情况,析构会出问题。

缺点:当对象进行 += ‘\0’ ,后拷贝出错,建议用第一种写法。

4、赋值重载(深拷贝)

  1. 自己开辟空间,拷贝数据,释放空间
string& operator=(const string& s)
{
    // 防止自己给自己赋值
    if (this != &s)
    {
        char* tmp = new char[s._capacity + 1];
        memcpy(tmp, s._str, s._capacity + 1);
        delete[] _str;

        _str = tmp;
        _size = s._size;
        _capacity = s._capacity;
    }

    return *this;
}
  1. 拷贝构造 tmp 对象,交换数据
string& operator=(const string& s)
{
    if (this != &s)
    {
        string tmp(s); // 拷贝构造 s
        std::swap(_str, tmp._str); // 改变指向
        std::swap(_size, tmp._size);
        std::swap(_capacity, tmp._capacity);

        // err
        // std::swap(*this, tmp); // 无限递归调用赋值
    }
    return *this;
}

通过 tmp 完成数据交换。

注意:不可以直接交换 *thistmp ,会引起无限递归调用赋值。

  1. 参数为对象的拷贝构造,直接进行交换
// 自己写份 swap
void swap(string& s)
{
    std::swap(_str, s._str);
    std::swap(_size, s._size);
    std::swap(_capacity, s._capacity);
}

string& operator=(string tmp) // 拷贝构造 tmp ,交换 tmp h和 _str
{
    swap(tmp);
    return *this;
}

二、访问

1、[ ] 重载

const char& operator[](size_t pos) const
{
    assert(pos < _size); // 检查越界
    return _str[pos];
}

char& operator[](size_t pos)
{
    assert(pos < _size); // 检查越界
    return _str[pos];
}

2、迭代器

string 的迭代器就是原生指针,普通迭代器和 const 迭代器取法相同:

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

迭代器使用方法:

void test()
{	
    anduin::string s1("hello world");
    anduin::string::iterator it = s1.begin();
    while (it != s1.end())
    {
        *it += 1;
        cout << *it << ' ';
        it++;
    }
    cout << endl;

    for (auto& e : s1)
    {
        e -= 1;
        cout << e << ' ';
    }
}

对于 范围 for 其实就是在迭代器遍历上 “套了层壳子” ,本质还是迭代器的使用。

三、容量操作

1、size

对 string 的 size 进行操作:

size_t size() const
{
    return _size;
}

2、clear

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

3、reserve

提前设置容量:

void reserve(size_t n) // n 为容量大小
{
    if (n > _capacity)
    {
        char* tmp = new char[n + 1]; // 1 给 \0 
        // strcpy(tmp, _str);
        memcpy(tmp, _str, _size + 1); // 拷贝 \0 
        delete[] _str;
        _str = tmp;
        _capacity = n;
        cout << "reserve -> " << _capacity << endl;
    }
}

new 空间,拷贝数据,释放原空间,改变指向。

4、resize

void resize(size_t n, char ch = '\0')
{
    // 删
    if (n < _size)
    {
        _size = n;
        _str[_size] = '\0';
    }
    else // 增
    {
        reserve(n); // reserve 会自己检查是否需要扩容
        for (size_t i = _size; i < n; i++) // 从之前的 _size 开始,移一直到 n
        {
            _str[i] = ch;
        }
        _size = n;
        _str[_size] = '\0';
    }
}

两种情况:

  1. 设置 size 比之前小,不改变容量,把大小设定为 n ,补 ‘\0’ 。
  2. 设置 size 比之前大,重新 reserve ,从原先最后一个位置 _size 处填内容,填满 n 个,设定 _size ,补 ‘\0’ 。

四、修改

1、push_back

void push_back(char ch)
{
    // 插入一个字符
    if (_size + 1 > _capacity)
    {
        int newcapacity = _capacity == 0 ? 4 : (2 * _capacity);
        reserve(newcapacity);
    }
    _str[_size++] = ch;
    _str[_size] = '\0';
}

2、append

void append(const char* str)
{
    size_t len = strlen(str);
    if (_size + len > _capacity)
    {
        int newcapacity =  _size + len; // 至少扩到 _size  + len
        reserve(newcapacity);
    }

    // strcpy(_str + _size, str); // 会把 \0 拷贝过去
    memcpy(_str + _size, str, len + 1); 
    _size += len;
}

3、+=

string& operator+=(char ch)
{
    push_back(ch);
    return *this;
}

string& operator+=(const char* str)
{
    append(str);
    return *this;
}

复用

4、c_str

const char* c_str() const
{
    return _str; // 返回 _str 的地址
}

5、find

size_t find(char ch, size_t pos = 0)
{
    assert(pos < _size);
    for (size_t i = pos; i < _size; i++)
    {
        if (_str[i] == ch)
        {
            return i;
        }
    } 
    return npos; // 没找到
}


size_t find(const char* str, size_t pos = 0)
{
    assert(pos < _size);
    const char* ptr = strstr(_str + pos, str); // 在 _str 中查找 str
    if (ptr)
    {
        return ptr - _str; // 指针 - 指针,无论 pos 在哪里,都是显示的整体位置
    }
    else
    {
        return npos;
    }
}

6、substr

string substr(size_t pos = 0, size_t len = npos)
{
    assert(pos < _size);

    size_t n = len; // 取 len 个字符
    if (len == npos || len + pos >= _size) // 直接截完
    {
        n = _size - pos; // 实际长度
    }

    string tmp;
    tmp.reserve(n); // 开好空间
    for (size_t i = pos; i < n + pos; i++) 
    {
        tmp += _str[i];
    }
    return tmp; // 返回需要拷贝构造
}

7、insert

void insert(size_t pos, size_t n, char ch) // pos 位置 n 个字符
{
    assert(pos <= _size); // 断言
    if (_size + n > _capacity)
    {
        int newcapacity = _size + n; // 至少扩容到 _size + n 
        reserve(newcapacity);
    }
    // 挪动数据
    // plan 1
    size_t end = _size;
    while (end >= pos && end != npos) // 一直挪到 pos,判断是否等于 npos
    {
        _str[end + n] = _str[end];
        --end;
    }
    for (size_t i = pos; i < n + pos; i++)
    {
        _str[i] = ch;
    }

    // plan2: 将 end 位置改变
    /*size_t end = _size + n;
      while (end > pos)
                {
                    _str[end] = _str[end - n];
                    --end;
                }
                for (size_t i = pos; i < n + pos; i++)
                {
                    _str[i] = ch;
                }
                */

    _size += n;
}

void insert(size_t pos, const char* str)
{
    assert(pos <= _size);
    size_t len = strlen(str);
    if (_size + len > _capacity)
    {
        int newcapacity = _size + len;
        reserve(newcapacity);
    }

    size_t end = _size;
    while (end >= pos && end != npos)
    {
        _str[end + len] = _str[end];
        --end;
    }

    for (size_t i = 0; i < len; i++)
    {
        _str[pos + i] = str[i];
    }

    _size += len;
}

第一个 insert 有两种版本:

第一个版本是将 end 设定在 _size 位置,每次移动会把数据从后往前移动到指定位置,当 end >= pos 时,循环继续,当 end == npos ,即 -1 时,循环需要停止。npos 为 size_t 类型的 -1 。

第二个版本是将 end 往后偏移,将数据移动到指定位置。当还剩 n 个数据时,其实数据已经移动完成。之后移动的都是随机值,虽然越界,但是是越界读,也不会报错,这时当 end > pos 时就可以停止。

第二个 insert 重载第一个类似。

8、erase

void erase(size_t pos, size_t len = npos)
{
    assert(pos < _size);

    if (len == npos || len + pos >= _size)
    {
        _str[pos] = '\0';
        _size = pos;
    }
    else
    {
        size_t  end = pos + len;
        while (end <= _size) // == 是为了移动 \0
        {
            _str[pos++] = _str[end++];
        }
        _size -= len;
    }
}

end 从 pos 开始偏移 len 个位置,将数据从后往前移动,直到移动到 ‘\0’ 。

五、重载符号

// 自己写
//bool operator<(const string& s)
//{
//	size_t i1 = 0, i2 = 0;
//	while (i1 < _size && i2 < s._size)
//	{
//		if (_str[i1] < _str[i2])
//		{
//			return true;
//		}
//		else if (_str[i1] > s._str[i2])
//		{
//			return false;
//		}
//		else
//		{
//			i1++, i2++;
//		}
//	}

//	// hello hello
//	// helloxx hello
//	// hello helloxx
//	return i1 == _size && i2 != s._size;
//	return _size < s._size;
//}

// 复用 memcmp
bool operator<(const string& s) const
{
    int ret = memcmp(_str, s._str, _size < s._size ? _size : s._size);
    return ret == 0 ? _size < s._size : ret < 0;
}

bool operator==(const string& s) const
{
    return _size == s._size && memcmp(_str, s._str, _size) == 0;
}

bool operator<=(const string& s) const
{
    return *this < s || *this == s;
}

bool operator>(const string& s) const
{
    return !(*this <= s);
}

bool operator>=(const string& s) const
{
    return !(*this < s);
}

bool operator!=(const string& s) const
{
    return !(*this == s);
}

六、流插入/流提取

ostream& operator<<(ostream& out, const string& s)
{
    /*for (size_t i = 0; i < s.size(); i++)
		{
			out << s[i];
		}*/

    for (auto ch : s)
    {
        out << ch;
    }

    return out;
}

istream& operator>>(istream& in, string& s)
{
    s.clear();

    char ch = in.get();
    while (ch == ' ' || ch == '\n')
    {
        ch = in.get();
    }

    char buf[128];
    int i = 0;
    while (ch != ' ' && ch != '\n')
    {
        buf[i++] = ch;
        if (i == 127) // 满了
        {
            buf[i] = '\0';
            s += buf;
            i = 0; // 从头开始覆盖
        }
        ch = in.get();
    }
    
    // 若 i 不为 0,则去尾,并且加到 s 中
    if (i != 0)
    {
        buf[i] = '\0';
        s += buf;
    }

    return in;
}

流插入需要完整打印出 string 对象,所以用循环打印。

流提取注意点:

  1. clear 每次输入前清空数据
  2. get() 可以读取空白字符
  3. 需要去前导空白字符
  4. 设定 buf 数组,可以避免频繁扩容

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/734212.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Langchain 新手完全指南

Langchain 可能是目前在 AI 领域中最热门的事物之一&#xff0c;仅次于向量数据库。 它是一个框架&#xff0c;用于在大型语言模型上开发应用程序&#xff0c;例如 GPT、LLama、Hugging Face 模型等。 它最初是一个 Python 包&#xff0c;但现在也有一个 TypeScript 版本&…

Git gui教程---第五篇 Git gui的使用 查看提交历史

查看提交历史 1.点击菜单栏的“版本库”&#xff0c;选择“图示master分支的历史” 2.出现的界面就是显示当前分支的提交历史了

Java基础---Java中创建对象方式

目录 使用new关键字 使用反射机制 使用clone方法 使用反序列化 使用方法句柄 使用Unsafe分配内存 使用new关键字 这是最常见的也是最简单的创建对象的方式通过这种方式还可以调用任意的构造函数&#xff08;无参的和有参的&#xff09; 使用反射机制 运用反射手段&#…

单个电源模块带电感的直流压降仿真(一)

单个电源模块带电感的直流压降仿真(一) 下面实例分析单个电源模块带电感的直流压降仿真分析,以下图为例 具体操作如下 创建新的workspaceLoad a New/Different layout(把PCB文件加载进来)

【滑动窗口】209. 长度最小的子数组

209. 长度最小的子数组 解题思路 滑动窗口设置前后指针滑动窗口内的元素之和总是大于或者等于s滑动窗口的起始位置: 如果窗口的值大于等于s 窗口向前移动窗口结束位置:for循环的j class Solution {public int minSubArrayLen(int target, int[] nums) {int left 0;// 滑动窗口…

UDS统一诊断服务【七】DTC控制0X85服务

文章目录 前言一、DTC控制服务介绍二、数据格式2.1 请求报文2.2 子功能2.3响应格式 三、举例总结 前言 大家好&#xff0c;我是嵌入式老林&#xff0c;从事嵌入式软件开发多年&#xff0c;今天分享的内容是UDS诊断故障码控制0X85服务介绍&#xff0c;希望能对你有所帮助 一、D…

[LeetCode周赛复盘] 第 353 场周赛20230709

[LeetCode周赛复盘] 第 353 场周赛20230709 一、本周周赛总结6451. 找出最大的可达成数字1. 题目描述2. 思路分析3. 代码实现 6899. 达到末尾下标所需的最大跳跃次数1. 题目描述2. 思路分析3. 代码实现 6912. 构造最长非递减子数组1. 题目描述2. 思路分析3. 代码实现 6919. 使…

人工智能与Chat GPT

一本书全面掌握ChatGPT&#xff0c;既有向ChatGPT提问的技巧&#xff0c; 也有构建自己的ChatGPT模型的方法&#xff0c;涵盖开发背景、关联技术、使用方法、应用形式、实用案例等 人工智能是我们这个时代最热门的话题&#xff0c;人们既希望它能代替我们做一些工作&#xff0c…

Python使用SQLAlchemy

Python使用SQLAlchemy 1 安装SQLAlchemy 备注&#xff1a;本文适用于SQLAlchemy>2.0 # 安装SQLAlchemy pip install SQLAlchemy# 安装pymysql pip install pymysql参考文档&#xff08;SQLAlchemy>2.0&#xff09; https://docs.sqlalchemy.org/en/20/创建数据库 # …

什么是敏捷测试?

目录 前言&#xff1a; 敏捷测试的定义 敏捷测试的特点 为什么要敏捷测试 缩短价值交付周期 强调质量属于大家 化繁为简节省成本 敏捷测试VS. 传统测试 传统测试如何迁移到敏捷测试 1. 组织文化的转变 2. 组织架构的调整 3. 人员培训与指导 4. 轻流程 敏捷测试成…

电视访问Samba

文章目录 问题描述方案一&#xff1a;当贝播放器方案二&#xff1a;nPlayer方案三&#xff1a;Kodi 问题描述 本人使用小米 AX9000 路由器 移动硬盘组了个轻 NAS&#xff0c;想通过电视访问 Samba 看视频&#xff08;也可以电脑开 SMB&#xff09; 开启 Samba 功能 文件夹开…

SpringMVC第一讲:SpringMVC基础知识

一、MVC设计模式 MVC的全名是Model View Controller&#xff0c;是模型(Model)&#xff0d;视图(view)&#xff0d;控制器(controller)的缩写&#xff0c;是一种设计模式。它是用一种业务逻辑、数据与界面显示分离的方法来组织代码&#xff0c;将众多的业务逻辑聚集到一个部件…

SIFT(尺度不变特征变换)

Sift&#xff08;尺度不变特征变换&#xff09;&#xff0c;全称是Scale Invariant Feature Transform Sift提取图像的局部特征&#xff0c;在尺度空间寻找极值点&#xff0c;并提取出其位置、尺度、方向信息。 Sfit的应用范围包括物体辨别、机器人地图感知与导航、影像拼接、…

OpenCV 入门教程:膨胀和腐蚀操作

OpenCV 入门教程&#xff1a;膨胀和腐蚀操作 导语一、膨胀操作二、腐蚀操作三、示例应用3.1 图像增强3.2 边缘检测 总结 导语 膨胀和腐蚀是图像处理中常用的形态学操作&#xff0c;用于改变图像的形状和结构。在 OpenCV 中&#xff0c;膨胀和腐蚀是基于结构元素的像素操作&…

一零六三、进程调度算法模拟 (C语言实现)

目录 背景 程序实现 实现效果 背景 1、根据算法要求从键盘输入不少于四个进程信息&#xff08;包括进程名称、进程到达时间、估计运行时间&#xff09;&#xff0c;RR算法输入不少于两种时间片的大小 2、输出进程的调度顺序、完成时间、周转时间、平均周转时间、平均带权周转…

Openlayers实战:点击某点,overlay显示经纬度坐标

在之前的实战中,我们在某个固定的位置显示鼠标位置的经纬度, 今天改变一种形式,即采用overlay的方式,点击某处,获得到经纬度坐标,显示在overlay层上面。 效果图 源代码 /* * @Author: 大剑师兰特(xiaozhuanlan),还是大剑师兰特(CSDN) * @此源代码版权归大剑师兰特…

23款迈巴赫S480升级原厂主动式氛围灯+电动后门+前后手势感应

23款迈巴赫S480升级原厂主动式氛围灯原厂电动后门原厂手势控制 新车刚提&#xff0c;原厂配置升级&#xff0c;全新配件 配件齐全&#xff0c;准备上车 全套主动式氛围灯配件 氛围灯模块 前排驾驶位顶棚的手势感应 手势感应电脑模块 后排顶棚的手势感应 电动后门配件有锁机、电…

基于simulink使用光流法跟踪汽车(附源码)

一、前言 此示例演示如何使用光流估计在视频序列中检测和跟踪汽车。 二、模型 下图显示了使用光流跟踪汽车模型&#xff1a; 三、用光流结果跟踪汽车 该模型使用光流估计技术来估计视频序列的每一帧中的运动矢量。通过阈值化运动矢量&#xff0c;该模型创建包含移动对象斑点…

mysql学习--使用navicat查看数据库密码

数据库通常分为两种&#xff1a;关系型数据库和非关系型数据库,关系型数据库通常会建立很多二维数据表&#xff0c;形成一对一、一对多、多对多等关系&#xff1b;之后利用SQL语句查询我们所需要的数据&#xff1b;非关系型数据库基于Key-Value的对应关系&#xff0c;并且查询的…

机器学习 day24(多类分类模型,Softmax回归算法及其损失函数)

多类分类 多类分类问题仍然是分类问题&#xff0c;所以预测y的可能结果是少量的&#xff0c;而不是无穷多个&#xff0c;且对于多类分类它&#xff1e;2 如上图&#xff1a;左侧为二分类&#xff0c;右侧为多分类&#xff0c;可以通过决策边界来划分区域 Softmax回归算法 …