【C++】—掌握STL string类:string的模拟实现

news2024/11/13 1:39:00

文章目录

    • 💞1.浅拷贝
    • 💞2.深拷贝
    • 💞3. string类的模拟实现
      • 💞3.1 string的构造函数
      • 💞3.2 string的析构函数
      • 💞3.3 string的拷贝构造
      • 💞3.4 string的size
      • 💞3.5 string的operator[]
      • 💞3.6 string的迭代器
      • 💞3.7 reserve
      • 💞3.8 push_back
      • 💞3.9 append
      • 💞3.10 operator+=
      • 💞3.11 insert
      • 💞3.12 erase
      • 💞3.13 find
      • 💞3.14 operator=
      • 💞3.15 比较(全局)
      • 💞3.16 流插入 (全局)
      • 💞3.16 流提取 (全局)
      • 💞3.17getline (全局)

请添加图片描述

💞1.浅拷贝

什么是浅拷贝

浅拷贝也称之为位拷贝,编译器只是将对象中的值拷贝过来。如果对象中管理资源,最后就导致多个对象公用同一份资源,当一个对象销毁时就会导致该资源释放掉,而此时的其他对象不知道该资源已经被释放掉,所以继续对资源进行操作时,就会导致访问违规。
可以使用深拷贝解决浅拷贝的问题即:每个对象都有一份独立的资源,不需要和其他对象共享。

浅拷贝的问题
当对象指向包含动态分配内存的指针时,浅拷贝可能会导致潜在的问题。由于像个对象公用同一块内存空间,因此任何一个对象对该内存的修改都会影响到另一个对象。此外当两个对象中的一个被销毁并释放它所占用的内存时,另一个对象将拥有一个悬垂指针(dangling pointer),即指向已经被释放的内存的指针。这可能导致未定义行为,包括程序崩溃。

浅拷贝的实现
c++中,如果没有显式定义拷贝构造函数或者赋值重载函数,编译器将自动生成默认的拷贝构造函数和赋值运算符重载函数,它们执行的是浅拷贝。这意味着,对于包含指向动态分配内存的指针的类,如果不显式的实现 深拷贝,那么使用编译器默认生成的拷贝构造函数和运算符重载将导致浅拷贝。

示例代码:

#include<iostream>
#include<cstring>
using namespace std;

class MyClass
{
public:
    //构造函数
    MyClass(const char* str = "")
    {
        data = new char[strlen(str) + 1];
        strcpy(data,str);
    }
    
    //没有显式定义拷贝构造函数和赋值运算符重载,因此使用默认的浅拷贝

    //析构函数
    ~MyClass()
    {
        delete[] data;
    }

    //打印数据
    void print() const
    {
        cout << data << endl;
    }
private:
    char* data;
};

int main()
{
    MyClass s1("hello,world!");
    MyClass s2 = s1;//使用默认的拷贝构造函数进行浅拷贝

    s1.print();//输出:hello,world!
    s2.print();//输出:hello,world!

    //修改s2的数据
    //delete[] s2.data;//这将导致未定义行为,因为s1和s2共享同一块内存
    //s2.data = new char[6];
    //strcpy(s2.data, "world");

    //尝试打印s1的数据(可能导致程序崩溃)
    s1.print();//未定义行为,因为s1的data指针现在指向的资源已经被释放

    return 0;
}

💞2.深拷贝

什么是深拷贝

深拷贝(deep copy)是一种对象复制的操作,它不仅复制对象本身的数据成员,还递归的复制对象内部所有指向动态分配内存的指针所引用的对象。这意味这,通过深拷贝创建的对象与原始对象是完全独立的,修改新对象的任何成员都不影响原始对象,反之亦然。

深拷贝的必要性

c++中,当对象包含指向动态分配内存的指针时,仅仅复制这些指针的值(即地址)时不够的。这是因为两个对象公用同一块内存,从而导致潜在的内存管理问题,如重复释放内存或者内存泄漏。为了避免这些问题,我们就需要深拷贝,以确保每个对象都有自己独立的内存空间。

深拷贝的实现

c++中,实现深拷贝通常涉及以下步骤:

• 定义拷贝构造函数: 拷贝构造函数是一个特殊的构造函数,它接受一个同类型对象的引用作为参数。通过实现拷贝构造函数,我们可以定义对象如何被复制。在拷贝构造函数中,我们需要为新对象分配内存,并复制原始对象的数据成员,包括那些指向动态分配内存的指针所引用的对象。
• 实现赋值运算符重载: 与拷贝构造函数类似,复制运算符重载(operator=)也用于处理对象的复制。在赋值运算符重载中,我们需要确保在赋值之前释放新对象当前占用的内存,然后为新对象分配内存并复制原始对象的成员数据。
• 递归复制: 对于对象内部包含的任何指向动态分配内存的指针,我们需要递归地调用这些对象的拷贝构造函数或者运算符重载函数,以确保它们也被深拷贝。

示例代码:

#include<iostream>
#include<cstring>
using namespace std;

class MyClass
{
public:
    //构造函数
    MyClass(const char* str = "")
    {
        data = new char[strlen(str) + 1];
        strcpy(data, str);
    }
    //拷贝构造函数(实现深拷贝)
    MyClass(const MyClass& other)
    {
        data = new char[strlen(other.data) + 1];
        strcpy(data,other.data);
    }
    //复制运算符重载(实现深拷贝)
    MyClass& operator=(const MyClass& other)
    {
        if (this == &other)
        {
            return *this; //处理自我赋值
        }
        //释放当前对象的内存
        delete[] data;

        //分配新内存并复制数据
        data = new char[strlen(other.data) + 1];
        strcpy(data,other.data);

        return *this;
    }
    //析构函数
    ~MyClass()
    {
        delete[] data;
    }

    //打印数据
    void print() const
    {
        cout << data << endl;
    }

private:
    char* data;
};

int main()
{
    MyClass s1("hello,world!");
    MyClass s2 = s1; //使用拷贝构造函数
    MyClass s3;
    s3 = s1;//使用赋值运算符重载进行深拷贝

    s1.print();
    s2.print();
    s3.print();


    return 0;
}

💞3. string类的模拟实现

💞3.1 string的构造函数

string(const char* str)
	:_size(strlen(str))
{
	_capacity = _size;
	_str = new char[_size + 1];

	strcpy(_str, str);
}

💞3.2 string的析构函数

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

💞3.3 string的拷贝构造

string(const string& s)
{
	_str = new cahr[s._capacity + 1];
	strcpy(_str,s._str);
	_size = s._size;
	_capacity = s._capacity;
}

💞3.4 string的size

size_t size() const
{
	return _size;
}

💞3.5 string的operator[]

//普通版本
char& operator[](size_t i)
{
	assert(i < _size);

	return _str[i];
}
//const版本
const char& operator[](size_t i) const ·
{
	assert(i < _size);

	return _str[i];
}

💞3.6 string的迭代器

//普通迭代器
using iterator = char*;

iterator begin()
{
	return _str;
}
iterator end()
{
	return _str + _size;
}
//const迭代器
using const_iterator = const char*;
const_iterator begin() const   //为什么不是const iterator .因为不是说iterator不能修改,而是说指向的内容不能被修改
{
	return _str;
}
const_iterator end() const
{

	return _str + _size;//最后一个位置的下一个位置
}

💞3.7 reserve

void reserve(size_t n)
{
    if (n > _capacity)
    {
        char* tmp = new char[n + 1];//+1是给/0开的
        strcpy(tmp,_str);
    }
    delete[] _str;
    _str = tmp;

    _capacity = n;
}

💞3.8 push_back

//普通版本
void push_back(char ch)
{
    if (_size == _capacity)
    {
        reserve(_capacity == 0 ? 4 : _capacity * 2); 
    }

    _str[_size] = ch;
    _size++;
}
//复用insert版本
void push_back(char ch)
{
  insert(_size,ch);
}

💞3.9 append

//普通版本
void append(const char* str)
{   
    size_t len = strlen(str);
    if (_size + len > _capacity)
    {
        size_t newCapacity = 2 * _capacity;
        //扩2倍不够,则需要多少扩多少
        if (newCapacity < _size + len)
            newCapacity = _size + len;
        reserve(newCapacity);
    }

    strcpy(_str + _size, str)
        _size += len;
}
复用insert版本
void append(const char* str)
{   
    insert(_size,char);
}

💞3.10 operator+=

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

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

💞3.11 insert

//插入一个字符
void 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 insert(size_t pos, char* str)
{
	assert(pos <= _size);
    size_t len = strlen(str);
    if (_size + len > _capacity)
    {
        size_t newCapacity = 2 * capacity;
        //扩二倍不够则需要多少扩多少
        if (newCpacity < _size + len)
            newCpacity = size + len; 

        reserve(newCapacity);
    }
    int end = _size;
    while (end >= (int)pos)
    {
        _str[end + len] = _str[len];
        --end;
    }

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

    _size += len;
}
//第二代
void insert(size_t pos, char* str)
{
	assert(pos <= _size);
    size_t len = strlen(str);
    if (_size + len > _capacity)
    {
        size_t newCapacity = 2 * capacity;
        //扩二倍不够则需要多少扩多少
        if (newCpacity < _size + len)
            newCpacity = size + len; 

        reserve(newCapacity);
    }
    size_t end = _size + len;
    while (end > pos + len - 1)
    {
        _str[end] = _str[end - len];
        --end;
    }

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

    _size += len;
}

💞3.12 erase

//先在类里面定义一个静态成员变量
//static const size_t npos = -1; 特殊处理,只有const整形成员可以在类里面给缺省值
void earse(size_t pos, size_t len)
{
	assert(pos < _size);
    if (len >= _size - pos)
    {
        _str[pos] = '\0';
        _size = pos;
    }
    else
    {
        //从后往前挪
        size_t end = pos + len;
        while (end <= _size)
        {
            _str[end - len] = _str[end];
            ++end;
        }
        _size -= len;
    }
}

💞3.13 find

size_t find(char ch,size_t pos = 0)
{
	assert(pos < _size);

	for (size_t i = pos; i < _size; i++)
	{
		if (ch == _str[i])
			return i;
	}
		
	return npos;
}
size_t find(const char* str, size_t pos = 0)
{
	assert(pos < _size);

	const char* ptr = strstr(_str + pos,str);
	if (ptr == nullptr)
	{
		return nops;
	}
	else
	{
		return ptr - _str;

💞3.14 operator=

string& operator=(const string& s)
{
	if (this != &s)
	{
		delete[] _str;
		_str = new char[s.capacity + 1];//+1预留给\0
		strcpy(_str, s._str);
		_size = s._size;
		_capacity = s._capacity;
	}

	return *this;

💞3.15 比较(全局)

bool operator==(const string& lhs, const string& rhs)
{
	return strcmp(lhs.c_str(), rhs.c_str) == 0;
}
bool operator!=(const string& lhs, const string& rhs)
{
	return !(lhs == rhs)
}
bool operator>(const string& lhs, const string& rhs)
{
	return !(lhs <= rhs);
}
bool operator<(const string& lhs, const string& rhs)
{
	return strcmp(lhs.c_str(),rhs.c_str) < 0;
}
bool operator>=(const string& lhs, const string& rhs)
{
	return !(lhs < rhs);
}

bool operator<=(const string& lhs, const string& rhs)
{
	return lhs < rhs || lhs == rhs;
}

💞3.16 流插入 (全局)

ostream& operator<<(ostream& os, const string& str)
{
	for (size_t i = 0; i < str.size(); i++)
	{
		os << str[i];
	}

	return os;
}

💞3.16 流提取 (全局)

void clear()
{
	_str[0] = '\0';
	_size = 0;
}
istream& operator>>(istream& is, string& str)
{
	str.clear();

	int i = 0;
	char buff[256];//栈上开空间比堆上开空间的效率高,函数结束buff也就销毁了

	char ch;
	ch = is.get();
	while (ch != ' ' && ch != '\n')
	{
		//遇到数据先放到buff里面
		buff[i++] = ch;
		if (i == 255)
		{
			buff[255] = '\0';
			str += buff;
			i = 0}
		ch = is.get();
	}

	if (i > 0)
	{
		buff[i] = '\0';
		str += buff;
	}
	return is;
}

💞3.17getline (全局)

istream& getline(istream& is, string& str,char delim = '\n')
{
	str.clear();

	int i = 0;
	char buff[256];//栈上开空间比堆上开空间的效率高,函数结束buff也就销毁了

	char ch;
	ch = is.get();
	while (ch != delim)
	{
		//遇到数据先放到buff里面
		buff[i++] = ch;
		if (i == 255)
		{
			buff[255] = '\0';
			str += buff;
			i = 0}
		ch = is.get();
	}

	if (i > 0)
	{
		buff[i] = '\0';
		str += buff;
	}
	return is;

}

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

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

相关文章

元器件篇——自恢复保险丝(PPTC)

1 定义 保险丝&#xff08;Fuse&#xff09;也被称为电流保险丝。根据IEC127标准&#xff0c;将保险丝定义为熔断体。主要的作用就是起过载保护。电路中正确布置保险丝&#xff0c;当保险丝在电流异常升高到一定时&#xff0c;或者热度升高到一定时&#xff0c;自身熔断&#x…

多媒体信息检索

文章目录 一、绪论二、文本检索 (Text Retrieval)(一) 索引1.倒排索引2.TF-IDF (二) 信息检索模型 (IR模型&#xff0c;Information Retrieval)1.布尔模型 (Boolean模型)(1)扩展的布尔模型 (两个词)(2)P-Norm模型 (多个词) 2.向量空间模型 (Vector Space Model&#xff0c;VSM)…

Node.js——fs模块-文件夹操作

1、借助Node.js的能力&#xff0c;我们可以对文件夹进行创建、读取、删除等操作 2、方法 方法 说明 mkdir/mkdirSync 创建文件夹 readdir/readdirSync 读取文件夹 rmdir/rmdirSync 删除文件夹 3、语法 其余的方法语法类似 本文的分享到此结束&#xff0c;欢迎大家评论区…

ABAP:SET CURSOR FIELD设置鼠标焦点

SET CURSOR FIELD <字段名>&#xff1a;设置鼠标焦点到该字段 SET CURSOR 设置到鼠标焦点列还是行 SET CURSOR LINE 设置鼠标焦点到行 GET CURSOR field <字段名> &#xff1a;这个相对应的获取鼠标焦点得到的字段

Gitlab-执行器为Kubetnetes时的注意事项,解决DNS解析问题

一、Gitlab-Runner 这里对于Runner的理解非常重要。 具体执行ci流水线的叫执行器。执行器可以部署是shell、docker、k8s的pod.执行完任务则生命周期结束。 管理执行器的叫Gitlab-Runner。Runner则是与Gitlab Server的Ci agent.(可以简单这么理解) 二、执行器为Kubetnetes时,DN…

双向链表(带头双向循环链表)巨详解!!!

概念 本文讲述的双向链表&#xff0c;全名叫做带头双向循环链表&#xff0c;我们学习的链表总共有八种 在前文讲述单链表时所讲到的单链表&#xff0c;其实就叫做不带头单向不循环链表&#xff0c;这里的带头、不带头才是真正的头结点&#xff0c;前文中的头结点其实叫做首元素…

时序预测 | Python基于CNN-transformer时间序列预测

时序预测 | Python基于CNN-transformer时间序列预测 目录 时序预测 | Python基于CNN-transformer时间序列预测预测效果基本介绍参考资料 预测效果 基本介绍 时序预测 | Python基于CNN-transformer时间序列预测 Cnn-transformer-自适应稀疏自注意力ASSA-对比归一化contranorm预…

网站架构知识之Ansible进阶(day022)

1.handler触发器 应用场景&#xff1a;一般用于分发配置文件时候&#xff0c;如果配置文件有变化&#xff0c;则重启服务&#xff0c;如果没有变化&#xff0c;则不重启服务 案列01&#xff1a;分发nfs配置文件&#xff0c;若文件发生改变则重启服务 2.when判断 用于给ans运…

Yolo11改进策略:上采样改进|CARAFE,轻量级上采样|即插即用|附改进方法+代码

论文介绍 CARAFE模块概述&#xff1a;本文介绍了一种名为CARAFE&#xff08;Content-Aware ReAssembly of FEatures&#xff09;的模块&#xff0c;它是一种用于特征上采样的新方法。应用场景&#xff1a;CARAFE模块旨在改进图像处理和计算机视觉任务中的上采样过程&#xff0…

常用的c++特性-->day02

c11新特性 可调用对象案例分析 可调用对象包装器语法案例可调用对象包装器作为函数参数补充&#xff1a;类型转换运算符案例 可调用对象绑定器语法格式绑定非类成员函数/变量案例1案例2案例3案例4 绑定类成员函数/变量 lambda表达式捕获列表案例1返回值案例2 --> 包装器绑定…

锐捷技能大赛—L2TP隧道与L2TP Over IPSec嵌套,并在隧道内运行OSPF

目录 总部与分支站点之间建立隧道 基础配置 配置L2TP VPN ​编辑配置L2TP Over IPSec L2TP Over IPSec隧道内运行OSPF协议 总部与分支站点之间建立隧道 拓扑如下 基础配置 R1 int g0/1 ip add 10.1.1.1 30 int g0/0 ip add 192.168.10.254 24 exit ip route 0.0.0.0 0.0…

python可视化将多张图整合到一起(画布)

这周有点事忙着&#xff0c;没时间重温刚结束的Mathurcup数学建模&#xff0c;这两天也是再看了下&#xff0c;论文还是赶紧挺烂的&#xff0c;但比国赛又有进步&#xff08;说起国赛又不得不抱怨了&#xff0c;基本其余省份都发了&#xff0c;但江西......哎&#xff09;。哎&…

网络编程、UDP、TCP、三次握手、四次挥手

一、初识网络编程 网络编程的概念&#xff1a;在网络通信协议下&#xff0c;不同计算机上运行的程序&#xff0c;进行的数据传输。 应用场景&#xff1a;即时通信、网游对战、金融证券、国际贸易、邮件等等。 不管是什么场景&#xff0c;都是计算机和计算机之间通过网络进行…

在 Jupyter Notebook 中使用 Matplotlib 进行交互式可视化的教程

在 Jupyter Notebook 中使用 Matplotlib 进行交互式可视化的教程 引言 数据可视化是数据分析的重要组成部分&#xff0c;能够帮助我们更直观地理解数据。Matplotlib 是 Python 中最流行的绘图库之一&#xff0c;而 Jupyter Notebook 则是进行数据分析和可视化的理想环境。本文…

[单例模式]

[设计模式] 设计模式是软件工程中的一种常见做法, 它可以理解为"模板", 是针对一些常见的特定场景, 给出的一些比较好的固定的解决方案. 不同语言适用的设计模式是不一样的. 这里我们接下来要谈到的是java中典型的设计模式. 而且由于设计模式比较适合有一定编程经…

STM32软件开发 —— STM32CudeMX使用优点

目 录 STM32CudeMX使用思路步骤详细 STM32CudeMX 在图形化工具STM32CudeMX出现之前&#xff0c;开发者通常是参考库驱动文件中的例程来配置芯片的&#xff0c;进行拷贝和修改等&#xff0c;为了提高开发效率&#xff0c;ST公司开发了STM32CudeMX工具&#xff0c;通过它简化了芯…

江西省补贴性线上职业技能培训管理平台(刷课系统)

江西省补贴性线上职业技能培训管理平台(刷课系统) 目的是为了刷这个网课 此系统有两个版本一个是脚本运行&#xff0c;另外一个是可视化界面运行 可视化运行 技术栈:flask、vue3 原理: 通过分析网站接口&#xff0c;对某些接口加密的参数进行逆向破解&#xff0c;从而修改请求…

Golang | Leetcode Golang题解之第546题移除盒子

题目&#xff1a; 题解&#xff1a; func removeBoxes(boxes []int) int {dp : [100][100][100]int{}var calculatePoints func(boxes []int, l, r, k int) intcalculatePoints func(boxes []int, l, r, k int) int {if l > r {return 0}if dp[l][r][k] 0 {r1, k1 : r, k…

es自动补全(仅供自己参考)

elasticssearch提供了CompletionSuggester查询来实现自动补全功能。这个查询会匹配以用户输入内容开头的词条并返回。为了提高补全查询效率&#xff0c;对于文档中字段的类型有一些约束&#xff1a; 查询类型必须是&#xff1a;completion 字段内容是多个补全词条形成的数组 P…

ANDROIDWORLD: A Dynamic Benchmarking Environment for Autonomous Agents论文学习

这个任务是基于androidenv的。这个环境之前学过&#xff0c;是一个用来进行强化学习的线上环境。而这篇文章的工作就是要给一些任务加上中间的奖励信号。这种训练环境的优点就是动态&#xff0c;与静态的数据集&#xff08;比如说我自己的工作&#xff09;不同&#xff0c;因此…