string的模拟实现and友元

news2024/9/21 14:46:09

一、引言

“知所从来,方知其往。”只有了解一个物体的构造才能更好的使用它。正所谓“不入虎穴,焉得虎子。”那我们学会使用一个类了,可不可以建造一个简易的类和对象出来呢?答案显而易见。因为这是C++的内容,所以我们用C++更能贴近底层。

二、友元函数与内部类

“有朋自远方来,不亦乐乎。”既然是朋友从远道来,按照传统礼仪,一定是主随客便。身为客人自然可以提出自己的生活要求让主人尽量满足,主人为了以尽主客之宜自然会尽量满足。友元函数、友元类就像朋友在主人家做客一样。正常来说类中的私有是无法在类域外访问的,但是在友元函数、友元类中这并不是什么难事。友元函数、友元类在其他类域中申明,可以通过接受类对象访问类的私有。友元函数和友元类的关键字是friend。相当于主人家将自己陈年收藏的美酒(私有)拿出款待客人。内部类就更好理解了,内部类就好比外部类的孩子,孩子是未来家庭的当家人,自然可以对自己家陈年珍藏的宝物做主。

#include<iostream>
using namespace std;

//友元类的声明所在的类,一定要在友元类的上面,或是声明在所在类的上方
//因为友元类访问私有需要用到已定义的类
//而编译是从上至下进行的
//造成了编译错误
//就像客人突然到访,主人没有做好准备。
class A
{
public:
    //友元类申明
	friend class B;
    class C
    {
    public:
	    void fun(A& aa)
	    {
            std::cout << "inner class" << std::endl;
		    std::cout << aa.a << " ";
            std::cout << std::endl;
	    }
    };
private:
	int a = 11;
};

//友元类
struct B
{
	void func(A& aa)
	{
        std::cout << "friend class or friend function" << std::endl;
		std::cout << aa.a;
        std::cout << std::endl;
	}
};

int main()
{
	A aa;
	B b;
	b.func(aa);

	return 0;
}

三、        string的的模拟实现

三、一        string的构造函数、默认构造、拷贝构造、析构函数

我们由浅入深,从string的构造函数、默认构造、拷贝构造、析构函数开始。存储字符肯定是要用到字符类型(char)的变量,除此之外我们要在字符数组的基础上进行管理,那我们就必须在内存中创建属于我们自己的一块内存。那我们就要用到C++中的关键字new和delete,由于我们申请的内存比较小,就不进行抛异常(内存)的检查了。关于申请多大的内存,又怎么管理这些内存。我们需要定义一个_size(字符串的长度)和一个_capacity(字符数组的大小)。因为如果一改变字符串长度,字符数组的大小就跟着扩容、缩容,非常浪费效率,还很麻烦。

class string
{
private:
    //在类和对象中的变量前加上一个下划线(_),代表这是变量,
    //让程序可读性更强一些。
    //加上一些缺省参数,减少错误的访问。
    char* _str = nullptr;
    size_t _size = 0;
    size_t _capacity = 0;
}

接着我们来写默认构造,我们创建类对象时可能会直接对类对象进行初始化,所以我们可以在默认构造函数里加入一个参数来初始化,又因为默认构造函数可以不需要参数创建类对象,所以我们将这个默认构造函数中的参数中加入一个缺省参数。

//定义一定是在类中定义或是在类中声明类外定义
//这里把它单独拿出来为了好看一些。
string(const char* str = "")
//这里的缺省值空字符串代表的就是字符末尾'\0'
//毕竟C语言是用'\0'来作为字符串末尾的
//C++要兼容C语言,自然要对C语言内容有所保留。
{
   	size_t len = strlen(s);
	_str = new char[len + 1];
	strcpy(_str,s);
	_size = _capacity = len;
}

string的拷贝构造和operator = 就简单多了。只需要获取字符长度,开好空间,在做好复制粘贴。operator = 也是一样。

string(const string& s)
{
	size_t len = strlen(s._str);
    //_size和_capacity不将'\0'计算在内
    //毕竟strlen()也不计算'\0'。
    //计算有效字符。
    //所以len加一是多一个空间存放'\0'
	_str = new char[len + 1];
    strcpy(_str,s._str);
	_size = _capacity = len;
}

接下来就是析构,析构就非常简单了直接new,然后再对_size和_capacity进行归零就行了。

~string()
{
    //delete后加上[]是因为_str是一个数组,不是一个变量
    //我们定义数组时也会加[]
	delete[] _str;
	_size = _capacity = 0;
}

三、二        string中的reserve()

reserve()是存储的意思。因为每次申请空间都要改变_capacity。所以我们自己单独写一个申请内存的函数。

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

三、三        string中的c_str()

  string中的c_str()函数返回一个记载私有变量_str中字符的字符串。因为类对象可能为const类型,加上返回的字符串无需改值,就前后各加了一个const。

	const char* string:: c_str()const
	{
		return _str;
	}

三、四        string中的insert()和erase()函数

insert()insert()和erase()函数分别是在指定位置插入字符和删除字符函数。删除函数就比较好理解,将指定位置之后的字符向前移动一个位置,对指定位置的字符进行覆盖。指定位置插入字符则与之相反,指定位置的字符向后移动一个位置,空出来的位置就是指定位置。同时还涉及到迭代器失效。我们可以设置在insert()函数中一个返回插入位置的迭代器,也可以直接返回对string对象的引用(因为引用更简单,所以我们偷点懒),在erase()函数中放入一个返回删除位置的下一个位置。至于造成迭代器失效的原因则是扩容和缩容导致的插入、删除位置的变化与原先迭代器指向的位置不符。

// 在pos位置上插入字符c/字符串str,并返回该字符的位置

string& insert(size_t pos, char c)
{
	assert(pos < _size);
	_str[pos] = c;
	return (*this);
}

string& insert(size_t pos, const char* str)
{
	size_t len = strlen(str);

	if (_size + len > _capacity)
	{
		reserve(_size + len > 2 * _capacity ? _size + len : _capacity);
	}
	
	for (int i = 0; i < len; i++)
	{
		_str[pos + i] = str[i];
	}

	_size += len;
	_str[_size] = '\0';

	return (*this);
}

string& erase(size_t pos, size_t len)
{
	for (int i = pos + len; i <= _size; i++)
	{
		_str[i - len] = _str[i];
	}
	_size -= len;
	return (*this);
}

string中的push_back()函数也可以用insert()函数来嵌套实现。

三、五        string中的size()和capacity()函数

返回_size和_capacity的值。

size_t size()
{
    return _size;
}

size_t capacity()
{
    return _capacity;
}

三、六        string中的operator []         和        operator        +=

operator []是通过下标访问,需要用到引用,当然也可以用指针。operator        +=则是在字符串末尾末尾的添加字符和字符串,也可以用iinsert()嵌套调用。

string& operator+=(char c)
{
	if (_size == _capacity)
	{
		reserve(2 * _capacity);
	}
	_str[_size] = c;
	++_size;
	return (*this);
}

string& operator+=(const char* str)
{
	(*this).insert(_size,str);
	return (*this);
}

char& operator[](size_t index)
{
	assert(index < _size);
	return _str[index];
}

三、七        string中的find()函数和clear()函数

find()是查找字符和字符串,查找字符只需遍历一遍_str,一个一个字符地进行比较。如果是字符串,由于KMP算法比较复杂,所以我们建议用<string.h>中的中的strstr()函数。至于clear()函数就更简单了。直接改变_size,将_str的第一个位置赋值为'\0'。

size_t find(char c, size_t pos) const
{
	for (int i = pos; i < _size; i++)
	{
		if (_str[i] == c)
		{
			return i;
		}
	}
	return npos;
}

// 返回子串s在string中第一次出现的位置

size_t find(const char* s, size_t pos) const
{
	char* tmp = strstr(_str + pos, s);
	return tmp - _str;
}

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

三、八       string中的operator        <<        和        operator        >>

C++规定流插入、流提取运算符重载时需为全局函数那我们就可以在string中声明友元函数访问string中的私有变量。

std::ostream& operator << (std::ostream& _cout, const word::string& s)
{
    //范围for()通过类中的begin()和end()函数的返回值访问
    //将s中的字符插入在流中
	for (auto& ch : s)
	{
	   _cout << ch;
   	}
	return _cout;
}

std::istream& operator >> (std::istream& _cin, word::string& s)
{
    //在提取之前先清除一下s中的所有字符
    //用一个字符数组充当缓冲区,减少扩容次数,增加效率
	s.clear();
	const int N = 255;
	char buf[N + 1] = { 0 };
	int i = 0;

	char ch = '\0';

    //因为输入输出默认用空格和换行做分隔符。
	while (ch != ' ' && ch != '\n')
	{
		// get()函数一个字符一个字符地去读
		ch = _cin.get();
		buf[i++] = ch;
		if (i == N - 1)
		{
			buf[i] = '\0';
			i = 0;
			s += buf;
		}
	}
	if (i > 0)
	{
		buf[i] = '\0';
		s += buf;
	}
	return _cin;
}

三、九        string中的begin()和end()

这两个函数都是返回迭代器(iterator,就相当于字符地址,为了更加简单就用字符指针)的函数。begin()是返回string中_str的起始位置,end()是返回string中_str的末尾位置的下一个地址。

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

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

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

相关文章

C++八股文之STL篇

&#x1f916;个人主页&#xff1a;晚风相伴-CSDN博客 思维导图链接&#xff1a;STL 持续更新中…… &#x1f496;如果觉得内容对你有帮助的话&#xff0c;还请给博主一键三连&#xff08;点赞&#x1f49c;、收藏&#x1f9e1;、关注&#x1f49a;&#xff09;吧 &#x1f64…

找搭子是什么意思?有没有找搭子的平台?靠谱找搭子软件推荐!

“找搭子” 指寻找在特定活动或兴趣方面有共同爱好的伙伴。比如饭搭子一起吃饭&#xff0c;运动搭子共同健身。它满足人们在特定场景下的社交需求&#xff0c;让生活更丰富有趣&#xff0c;是一种新型社交方式。以下是国内排名靠前的找搭子平台 1. 咕哇找搭子小程序&#xff1a…

Mac下nvm无法安装node问题

背景 最近换用mac开发&#xff0c;然后使用nvm&#xff08;版本0.40.1&#xff09;进行node安装的时候出现了一些问题 使用 nvm ls-remote发现只有 iojs 版本 原因可能是nodejs升级了某个协议导致的 解决方案 可以使用 NVM_NODEJS_ORG_MIRRORhttp://nodejs.org/dist nvm ls-re…

Cartographer源码理解

一、前言 最近一个半月&#xff0c;利用空余时间对Cartographer源码进行了简单的阅读&#xff0c;在这里做了个简单梳理&#xff0c;和大家分享交流。 cartographer源码量其实是有点大的&#xff0c;逐行逐句去解释实在是有心无力了&#xff0c;而且已经有大佬做了类似的事情…

分治算法归并排序

分治算法 基本概念 把一个复杂的问题分成两个或更多的相同或相似的子问题&#xff0c;再把子问题分成更小的子问题…直到最后子问题可以简单的直接求解&#xff0c;原问题的解即子问题的解的合并。 分治法的基本步骤 分治法在每一层递归上都有三个步骤&#xff1a; step1分…

单细胞CCA整合流程学习(SeuratV5/V4)

CCA&#xff08;Canonical Correlation Analysis&#xff09;和 Harmony 是两种常用于单细胞 RNA 测序&#xff08;scRNA-seq&#xff09;数据整合和批次效应校正的方法。 CCA 通过计算两个&#xff08;或多个&#xff09;数据集的线性组合&#xff0c;使这些组合之间的相关性…

1、vectorCast单元测试常用操作

一、自动创建测试工程 1、设置工作目录 进入软件主页面,点击file,选择set working directory,随便选择一个保存该项目的目录即可。 2、创建一个空工程 编译器选择vector自带的编译器,vectorCast MinGW C。 此时项目工程就创建好了 2.1、配置编译器节点 点击编译器节点…

JAVA毕业设计173—基于Java+Springboot+vue3的酒店民宿管理系统(源代码+数据库)

毕设所有选题&#xff1a; https://blog.csdn.net/2303_76227485/article/details/131104075 基于JavaSpringbootvue3的酒店民宿管理系统(源代码数据库)173 一、系统介绍 本项目前后端分离(可以改为ssm版本)&#xff0c;分为用户、员工、管理员三种角色 1、用户&#xff1a…

Nowcoder—链表的回文结构

题目描述 题目分析 1.回文结构是指一个序列或字符串从前往后读或从后往前读都是相同的。 2.时间复杂度为O(n)&#xff0c;说明循环只能有一层&#xff1b;空间复杂度为O(1)&#xff0c;说明不能额外的申请空间。 3.链表的结点最多不会超过900个。 思路 1 思路1&#xff1a;…

2024年数字中国创新赛-MISC

1 wireshark-1 可以看到在theanswerishere.php执行了sql注入&#xff0c;所以存在漏洞的PHP页面名称是theanswerishere.php 2 wireshark-2 从以上漏洞测试我们发现在一共有三列 3 wireshark-3 从这里我们可以看到注入的列名是th1sfI4g 4 wireshark-4 从这里回显包我们能看到…

鸿蒙next web组件和h5 交互实战来了

前言导读 鸿蒙next web组件这个专题之前一直想讲一下 苦于没有时间&#xff0c;周末把代码研究的差不多了&#xff0c;所以就趁着现在这个时间节点分享给大家。也希望能对各位读者网友工作和学习有帮助&#xff0c;废话不多说我们正式开始。 效果图 默认页面 上面H5 下面ArkU…

深度学习--------------序列模型

目录 序列数据统计工具&#xff08;方案一&#xff09;马尔可夫假设&#xff08;方案二&#xff09;潜变量模型总结 序列模型基于马尔可夫假设方式该部分总代码 单步预测多步预测k步预测该部分总代码 序列数据 实际中数据是有时序结构的。 统计工具 在时间t观察带 x t x_t xt…

二叉搜索树(Java实现)

博主主页: 码农派大星. 数据结构专栏:Java数据结构 数据库专栏:MySQL数据库 JavaEE专栏:JavaEE 关注博主带你了解更多数据结构知识 1.概念 二叉搜索树又称二叉排序树,或者它是一棵空树,或者是具有以下性质的二叉树: 若它的左子树不为空&#xff0c;则左子树上所有节点的值都…

【kafka-01】kafka安装和基本核心概念

Kafka系列整体栏目 内容链接地址【一】afka安装和基本核心概念https://zhenghuisheng.blog.csdn.net/article/details/142213307【二】kafka集群搭建https://zhenghuisheng.blog.csdn.net/article/details/142253288 kafka安装和基本核心概念 一&#xff0c;kafka安装和基本核心…

MoneyPrinterTurbo 安装使用流程

项目地址&#xff1a; https://github.com/harry0703/MoneyPrinterTurbo 开发环境&#xff1a;mac 1 git 下载 # 下载代码到本地 git clone https://github.com/harry0703/MoneyPrinterTurbo.git cd MoneyPrinterTurbo2 docker 配源 在 docker 安装目录执行以下命令显示隐藏…

Spring模块详解Ⅳ(Spring ORM和Spring Transaction)

目录 Spring ORM&#xff08;Object-Relational Mapping&#xff09;作用核心组件使用步骤事务管理代码演示优点挑战总结 Spring Transaction&#xff08;Spring事务管理&#xff09;事务的基本概念Spring事务管理的类型声明式事务管理事务的传播行为&#xff08;Propagation&a…

【LabVIEW学习篇 - 25】:JKI状态机

文章目录 JKI状态机JKI状态机安装JKI状态机的基本了解状态机的运行原理示例 JKI状态机 JKI状态机的核心就是队列消息状态机用户事件处理器模式&#xff0c;JKI状态机采用指定格式的字符串来描述状态。 JKI状态机并没有采用队列而是采用指定的字符串进行存储&#xff0c;它封装…

【论文阅读】PERCEIVER-ACTOR: A Multi-Task Transformer for Robotic Manipulation

Abstract transformers凭借其对大型数据集的扩展能力&#xff0c;彻底改变了视觉和自然语言处理。但在机器人操作中&#xff0c;数据既有限又昂贵。通过正确的问题表述&#xff0c;操纵仍然可以从变形金刚中受益吗&#xff1f;我们使用peract来研究这个问题&#xff0c;peract…

图解Redis 02 | String数据类型的原理及应用场景

介绍 在 Redis 中&#xff0c;String 是一种重要的数据类型&#xff0c;是最基本的 key-value 结构&#xff0c;在这个结构中&#xff0c; value 是一个字符串。value 所能容纳的数据最大长度为512M。 需要注意的是&#xff0c;这里的字符串不只指文本数据&#xff0c;它还可…

Https AK--(ssl 安全感满满)

免责声明&#xff1a;本文仅做分享&#xff01; 目录 https探测 openssl Openssl连接服务器获取基本信息 连接命令&#xff1a; 指定算法连接: 测试弱协议连接是否可以连接: 得到的内容包括&#xff1a; sslscan 在线查询证书 https AK type 中间人AK sslsplit 工具…