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

文章目录

    • 💞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;

}

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

详解基于C#开发Windows API的SendMessage方法的鼠标键盘消息发送

在C#中&#xff0c;SendMessage方法是一个强大的工具&#xff0c;它允许我们与Windows API交互&#xff0c;模拟键盘和鼠标事件。本文将详细介绍如何使用SendMessage方法来发送鼠标和键盘消息。 1. SendMessage方法概述 SendMessage是Windows API中的一个函数&#xff0c;它用…

15.UE5等级、经验、血条,魔法恢复和消耗制作

2-17 等级、经验、血条、魔法消耗_哔哩哔哩_bilibili 目录 1.制作UI&#xff0c;等级&#xff0c;经验&#xff0c;血条 ​2.为属性面板绑定角色真实的属性&#xff0c;实现动态更新 3.魔法的消耗和恢复 1.制作UI&#xff0c;等级&#xff0c;经验&#xff0c;血条 创建控…

<项目代码>YOLOv8 玉米地杂草识别<目标检测>

YOLOv8是一种单阶段&#xff08;one-stage&#xff09;检测算法&#xff0c;它将目标检测问题转化为一个回归问题&#xff0c;能够在一次前向传播过程中同时完成目标的分类和定位任务。相较于两阶段检测算法&#xff08;如Faster R-CNN&#xff09;&#xff0c;YOLOv8具有更高的…

现场工程师日记-MSYS2迅速部署PostgreSQL主从备份数据库

文章目录 一、概要二、整体架构流程1. 安装 MSYS2 环境2. 安装postgresql 三、技术名词解释1.MSYS22.postgresql 四、技术细节1. 创建主数据库2.添加从数据库复制权限3. 按需修改参数&#xff08;1&#xff09;WAL保留空间&#xff08;2&#xff09;监听地址 4. 启动主服务器5.…

堆排序与链式二叉树:数据结构与排序算法的双重探索

大家好&#xff0c;我是小卡皮巴拉 文章目录 目录 引言 一.堆排序 1.1 版本一 核心概念 堆排序过程 1.2 版本二 堆排序函数 HeapSort 向下调整算法 AdjustDown 向上调整算法 AdjustUp 二.链式二叉树 2.1 前中后序遍历 链式二叉树的结构 创建链式二叉树 前序遍历…

【LinuxC编程】06 - 守护进程,线程

进程组和会话 概念和特性 进程组&#xff0c;也称之为作业。BSD于1980年前后向Unix中增加的一个新特性。代表一个或多个进程的集合。每个进程都属于一个进程组。在waitpid函数和kill函数的参数中都曾使用到。操作系统设计的进程组的概念&#xff0c;是为了简化对多个进程的管…

【MongoDB】MongoDB的聚合(Aggregate、Map Reduce)与管道(Pipline) 及索引详解(附详细案例)

文章目录 MongoDB的聚合操作&#xff08;Aggregate&#xff09;MongoDB的管道&#xff08;Pipline操作&#xff09;MongoDB的聚合&#xff08;Map Reduce&#xff09;MongoDB的索引 更多相关内容可查看 MongoDB的聚合操作&#xff08;Aggregate&#xff09; 简单理解&#xff…

Python的函数(补充浅拷贝和深拷贝)

一、定义 函数的定义&#xff1a;实现【特定功能】的代码块。 形参&#xff1a;函数定义时的参数&#xff0c;没有实际意义 实参&#xff1a;函数调用/使用时的参数&#xff0c;有实际意义 函数的作用&#xff1a; 简化代码提高代码重用性便于维护和修改提高代码的可扩展性…

Unity常见问题合集(一)

PS&#xff1a;不定期更新...... 目录 &#xff08;1&#xff09;无法关闭自动编译&#xff08;Edit — Preference — General — Auto Refresh&#xff09; &#xff08;1&#xff09;无法关闭自动编译&#xff08;Edit — Preference — General — Auto Refresh&#xff0…

HTB:Sightless[WriteUP]

目录 连接至HTB服务器并启动靶机 使用nmap对靶机TCP端口进行开放扫描 继续使用nmap对靶机开放的TCP端口进行脚本、服务扫描 首先尝试对靶机FTP服务进行匿名登录 使用curl访问靶机80端口 使用浏览器可以直接访问该域名 使用浏览器直接访问该子域 Getshell 横向移动 查…

深度学习-神经网络基础-网络搭建-损失函数-网络优化-正则化方法

一. 神经网络搭建和参数计算 一个继承(nn.model), 两个方法(init, forward) 简介 在pytorch中定义深度神经网络其实就是层堆叠的过程&#xff0c;继承自nn.Module&#xff0c;实现两个方法&#xff1a; init方法中定义网络中的层结构&#xff0c;主要是全连接层&#xff0c;…

全彩LED显示屏有几种安装方式?

全彩LED显示屏的安装方式多样&#xff0c;根据不同的使用场景和安装环境&#xff0c;可以选择最合适的安装方法。以下是一些常见的全彩LED显示屏安装方式&#xff1a; 室内显示屏安装方式 吊装&#xff1a;适用于室内承重混凝土顶&#xff0c;可以使用标准吊件&#xff0c;吊杆…

ZISUOJ 2024算法基础公选课练习二

一、前言 先把代码丢上来&#xff0c;后续再补上思路 二、题目总览 三、具体题目 3.1 问题 A: 成绩排序1 参考代码 简单排序 #include <bits/stdc.h> using i64 long long;int main() {std::cin.tie(nullptr)->sync_with_stdio(false);int t 1;std::cin >&g…

阿里云智能语音交互产品试用,基于语音识别、语音合成、自然语言理解

VER&#xff1a;2024年1月25日 17:29:33 智能语音交互产品基于语音识别、语音合成、自然语言理解 新开通智能语音交互服务用户&#xff0c;可享有3个月免费试用期&#xff0c;试用期间将不会产生费用 智能语音交互产品基于语音识别、语音合成、自然语言理解等技术&#xff0c…

服务器被攻击排查记录

起因 我的深度学习的所有进程突然被killed&#xff0c;我以为是检修&#xff0c;后面发现好像简单的python代码可以正常运行。但是我的训练进程一启动就会被killed 第一时间没有用htop查看cpu&#xff0c;用top看着挺正常的&#xff0c;但是后面看htop&#xff0c;全是绿的&a…

安卓好软-----电脑端查看apk全部信息的工具 查看包名 名称以及权限等等

有时候从网络下载的应用很多是英文。时间久了会忘记到底是什么apk应用。这款工具可以方便的查看apk应用的名称 包名以及各种权限 图标 大小版本号等等。方便用户随时查看 APK Helper能够详细地获得安装包名、软件名称、APK证书、真实版本号、要求的手机版本、系统权限、以及证书…

算法每日双题精讲——双指针(移动零,复写零)

&#x1f31f;快来参与讨论&#x1f4ac;&#xff0c;点赞&#x1f44d;、收藏⭐、分享&#x1f4e4;&#xff0c;共创活力社区。 &#x1f31f; 别再犹豫了&#xff01;快来订阅我们的算法每日双题精讲专栏&#xff0c;一起踏上算法学习的精彩之旅吧&#xff01;&#x1f4aa;…

【SQL】在 SQL Server 中创建数据源是 MySQL 数据表的视图

背景&#xff1a;Windows系统已安装了mysql5.7和sqlServer数据库&#xff0c;现在需要在sqlServer创建视图或者查询来自mysql的数据&#xff0c;视图的数据来源mysql数据库。下面进行实现在sqlserver实现获取mysql数据表数据构建视图。 1、打开 ODBC 数据源管理器&#xff0c;…

Git 入门篇(一)

前言 操作系统&#xff1a;win11 64位 与gitee搭配使用 Git 入门篇&#xff08;一&#xff09; Git 入门篇&#xff08;二&#xff09; Git 入门篇&#xff08;三&#xff09; 目录 git下载、安装与配置 下载 安装 配置 git下载、安装与配置 下载 官网&#xff1a;git-…

【React】条件渲染——逻辑与运算符

条件渲染——逻辑与&&运算符 你会遇到的另一个常见的快捷表达式是 JavaScript 逻辑与&#xff08;&&&#xff09;运算符。在 React 组件里&#xff0c;通常用在当条件成立时&#xff0c;你想渲染一些 JSX&#xff0c;或者不做任何渲染。 function Item({ nam…