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

string的模拟实现and友元

一、引言

“知所从来,方知其往。”只有了解一个物体的构造才能更好的使用它。正所谓“不入虎穴,焉得虎子。”那我们学会使用一个类了,可不可以建造一个简易的类和对象出来呢?答案显而易见。因为这是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;
}

相关文章:

  • 北京网站建设多少钱?
  • 辽宁网页制作哪家好_网站建设
  • 高端品牌网站建设_汉中网站制作
  • 政务安全体系构建中的挑战
  • Python的学习步骤
  • TDengine 与 SCADA 强强联合:提升工业数据管理的效率与精准
  • 物联网架构
  • steamdeck执行exe文件
  • 什么是Bean的循环依赖?解决方案是什么?
  • 基于STM32设计的水渠闸门远程控制系统(华为云IOT)(226)
  • PostgreSQL(PG)(二十二)
  • “从零到一:使用IntelliJ IDEA打造你的梦幻HTML项目“
  • 【Unity】 HTFramework框架(五十六)MarkdownText:支持运行时解析并显示Markdown文本
  • 前端 + 接口请求实现 vue 动态路由
  • QTreeView模糊查询
  • 建模杂谈系列256 规则函数化改造
  • 【机器学习】--- 自监督学习
  • 202409011在飞凌的OK3588-C的核心板跑Rockchip原厂的Android12时挂载触摸屏ft5x06之后使用i2c-tools检测
  • IDEA 插件开发入门教程
  • Java 11 发布计划来了,已确定 3个 新特性!!
  • Java 内存分配及垃圾回收机制初探
  • Java深入 - 深入理解Java集合
  • Java知识点总结(JDBC-连接步骤及CRUD)
  • leetcode-27. Remove Element
  • Less 日常用法
  • 百度小程序遇到的问题
  • 入职第二天:使用koa搭建node server是种怎样的体验
  • 使用 5W1H 写出高可读的 Git Commit Message
  • 使用SAX解析XML
  • 如何在招聘中考核.NET架构师
  • ​​​​​​​sokit v1.3抓手机应用socket数据包: Socket是传输控制层协议,WebSocket是应用层协议。
  • #NOIP 2014# day.1 生活大爆炸版 石头剪刀布
  • $.each()与$(selector).each()
  • $emit传递多个参数_PPC和MIPS指令集下二进制代码中函数参数个数的识别方法
  • %@ page import=%的用法
  • (09)Hive——CTE 公共表达式
  • (26)4.7 字符函数和字符串函数
  • (Bean工厂的后处理器入门)学习Spring的第七天
  • (PyTorch)TCN和RNN/LSTM/GRU结合实现时间序列预测
  • (备份) esp32 GPIO
  • (二十三)Flask之高频面试点
  • (十八)三元表达式和列表解析
  • (十六)Flask之蓝图
  • ... 是什么 ?... 有什么用处?
  • .net CHARTING图表控件下载地址
  • .NET Framework杂记
  • .net 打包工具_pyinstaller打包的exe太大?你需要站在巨人的肩膀上-VC++才是王道
  • .Net 代码性能 - (1)
  • .net 怎么循环得到数组里的值_关于js数组
  • .NET教程 - 字符串 编码 正则表达式(String Encoding Regular Express)
  • @requestBody写与不写的情况
  • @Value获取值和@ConfigurationProperties获取值用法及比较(springboot)
  • [145] 二叉树的后序遍历 js
  • [2019/05/17]解决springboot测试List接口时JSON传参异常
  • [Android Pro] AndroidX重构和映射
  • [BZOJ 2142]礼物(扩展Lucas定理)
  • [C#学习笔记]注释
  • [C++]运行时,如何确保一个对象是只读的