MyString 类是学习 C++ 的过程中一个很重要的例子,涉及到面向对象的封装、堆内存申请和释放、函数的重载以及 C++ 的 “Big Three”。本例子重点在于复习和理解上述的 C++ 特性,实现的功能并不多。

MyString 类的 Header

MyString 的声明中包含了一个带指针的 C++ 类应有的函数,并且包含了一些常用的功能。其中终点讨论一下用 friend 关键字修饰的两个函数,它们是用来提供 ostream 的输出的。声明为 friend 是为了使 ostream 的对象(例如 cout)可以访问到 MyString 的成员。而这两个函数不声明为内部方法的原因是我们一般写输入输出语句都是将流对象写在前,如果声明成了成员函数则调用的时候要写成这样:

MyString str;
str.operator<<(cout);

这看起来相当怪异,所以我们希望写成 cout << str 这样。这句实际上就是 cout 对象调用重载的运算符,也可以写在实例化 cout 对象的类 ostream 里,但是 ostream 一般来说我们并不会去改动这个文件,所以写了一个全局函数来将 ostream 和 MyString 联系起来。

class MyString {
friend std::ostream &operator<<(std::ostream &os, const MyString &str);
friend std::ostream &operator>>(std::ostream &os, MyString &str);
public:
MyString(const char *cstr = nullptr);
// Big Three
MyString(const MyString &str);
MyString &operator=(const MyString &str);
~MyString(); char *get_c_str() const { return m_data; } // operator reload
MyString &operator+(const MyString &str);
bool operator==(const MyString &str); size_t length() const;
private:
char *m_data;
};

C++ 的构造函数和 Big Three

C++ 的 Big Three 包括拷贝赋值、拷贝构造和析构函数。Big Three 主要针对带有指针的类,类中的指针一般指向类所管理的一些资源,可能是该类自己申请的内存或者创建的某种对象。类维护着这些资源,负责它们的生老病死——资源的创建、使用和销毁。

一个类中如果带有着指针,那么必须要自己重写有拷贝构造和拷贝赋值函数。如果不自己重写,那么会采取默认的行为,即逐位拷贝。采取逐位拷贝的对象内部的指针也被拷贝过去,称为浅拷贝。我们希望拷贝一个对象,其管理的资源也一并拷贝一份,称为深拷贝。为了实现深拷贝,重写的拷贝赋值、拷贝构造函数中应实现资源复制的行为。而在对象生命周期结束后,其管理的资源也应该关闭或者销毁,故也需要重写析构函数。

C++ 的这三个函数紧密地联系在了一起,具体体现为逻辑上如果需要重写其中任何一个函数,那么另外两个函数也应该被重写

inline
MyString::MyString(const char *cstr = nullptr) {
if (cstr) {
m_data = new char[strlen(cstr) + 1];
strcpy(m_data, cstr);
}
else {
m_data = new char[1];
*m_data = '\0';
}
} inline
MyString::~MyString() {
delete[] m_data;
} inline
MyString::MyString(const MyString &str) {
m_data = new char[strlen(str.m_data) + 1];
strcpy(m_data, str.m_data);
} inline
MyString &MyString::operator=(const MyString &str) {
if (this == &str) return *this;
delete[] m_data;
m_data = nullptr;
m_data = new char[strlen(str.m_data) + 1];
strcpy(m_data, str.m_data);
return *this;
}

在上面的实现中,有两个值得注意的地方,一个是在拷贝赋值运算符重载中的自赋值检查,在拷贝赋值运算符函数中,如果不进行检查,按照逻辑来说,会释放被赋值对象的资源,如果是同一个对象的自我赋值,则会产生严重的后果:字符串资源被删除,两个对象指向被回收了的内存,当访问这个字符串时会出现不可预料的后果。可见,防御式编程不仅要求程序对黑客行为的输入的那些很离谱的数据做防御,还要对用户可能出现的错误进行防御。

另外一个值得注意的地方就是重载的等于操作符,用户在使用的时候有时可能会对字符串连续赋值,就像使用 cout 连续输出一样,这种类似于函数的链式调用。实现的时候只要返回对象的引用就可以了。

输入输出运算符重载

除了链式调用需要返回对象引用的这个主意点外,输入输出函数的第二个参数的 const 属性也应该主意的。具体来说输出函数的第二个参数不会被改变,我们应该将它声明为 const 的;而输入函数的第二个对象正是我们希望改变的对象,希望将数据输入到这个对象中,它应该是非 const 引用。

std::ostream &operator<<(std::ostream &os, const MyString &str) {
os << str.m_data;
return os;
} std::ostream &operator>>(std::ostream &os, MyString &str) {
os >> str.m_data;
return os;
}

至此一个非常简单的 C++ MyString 类已经实现完成了,在 STL 中,string 类的实现更加复杂,包括了正则在内的高级功能。这个例子仅仅为了学习 C++ 的一些特性,还不够深入,希望未来能专门研究一下 string 的实现。

--------------------- 本文来自 ProJ7-Jeffy 的CSDN 博客 ,全文地址请点击:https://blog.csdn.net/u013040821/article/details/80455108?utm_source=copy

MyString类的实现--基础中的基础C语言的更多相关文章

  1. [.net 面向对象编程基础] (3) 基础中的基础——数据类型

    [.net 面向对象编程基础] (3) 基础中的基础——数据类型 关于数据类型,这是基础中的基础. 基础..基础..基础.基本功必须要扎实. 首先,从使用电脑开始,再到编程,电脑要存储数据,就要按类型 ...

  2. [.net 面向对象编程基础] (4) 基础中的基础——数据类型转换

    [.net面向对象编程基础] (4)基础中的基础——数据类型转换 1.为什么要进行数据转换? 首先,为什么要进行数据转换,拿值类型例子说明一下, 比如:我们要把23角零钱,换成2.30元,就需要把整形 ...

  3. [.net 面向对象编程基础] (5) 基础中的基础——变量和常量

    [.net面向对象编程基础]  (5) 基础中的基础——变量和常量 1.常量:在编译时其值能够确定,并且程序运行过程中值不发生变化的量. 通俗来说,就是定义一个不能改变值的量.既然不能变动值,那就必须 ...

  4. [.net 面向对象编程基础] (7) 基础中的基础——流程控制语句

    [.net 面向对象编程基础] (7) 基础中的基础——流程控制语句 本来没有这一节的内容,后来考虑到既然是一个系列文章,那么就尽可能写的详细一些,本节参考了网上朋友所写的例子,为的是让更多小伙伴学习 ...

  5. [.net 面向对象编程基础] (8) 基础中的基础——修饰符

    [.net 面向对象编程基础] (8) 基础中的基础——修饰符 在进入C#面向对象核心之前,我们需要先对修饰符有所了解,其实我们在前面说到变量和常量的时候,已经使用了修饰符,并且说明了变量和常量的修改 ...

  6. [.net 面向对象编程基础] (6) 基础中的基础——运算符和表达式

    [.net 面向对象编程基础] (6) 基础中的基础——运算符和表达式 说起C#运算符和表达式,小伙伴们肯定以为很简单,其实要用好表达式,不是一件容易的事.一个好的表达式可以让你做事半功倍的效果,比如 ...

  7. 从头开始学JavaScript 笔记(一)——基础中的基础

    原文:从头开始学JavaScript 笔记(一)--基础中的基础 概要:javascript的组成. 各个组成部分的作用 . 一.javascript的组成   javascript   ECMASc ...

  8. C++ 基础中的基础 ---- 引用

    C++ 基础中的基础 ---- 引用 引用的概念:引用变量是一个别名,也就是说,它是某个已存在变量的另一个名字.一旦把引用初始化为某个变量,就可以使用该引用名称或变量名称来指向变量.比如: int n ...

  9. python之基础中的基础(一)

    python是一个效率极高的语言,现在市面上的机器学习大部分是由python和R语言完成,所以在不久之前小仙心中便种下了学习python的想法.下面是这一个月多月以来学习的总结,都是基础中基础了. 1 ...

随机推荐

  1. DRF-->2序列化组件的使用和接口设计--get,post,put,delete&优化组件

    !!!!! !!!!! 记住这个图 !!!!! 上篇博客说道DRF序列化组件的get,只是简单的举一个实例,然而在现实生活中我们前后端进行交互的时候更多的用到了Json数据格式,这也就是说前后端交互的 ...

  2. 117、python MySQLdb在windows环境下的快速安装、问题解决方式

    使用Python访问MySQL,需要一系列安装 Linux下MySQLdb安装见 Python MySQLdb在Linux下的快速安装 http://blog.csdn.NET/wklken/arti ...

  3. C#多样式EXECl导出

    sing NPOI.HPSF; using NPOI.HSSF.UserModel; using NPOI.HSSF.Util; using System; using System.Collecti ...

  4. 显卡(GPU)的基础知识

    显卡的性能指标有: 流处理器(SP)数量 核心频率 流处理器的架构 显存容量 显存频率 显存带宽 1. 流处理器的数量 把一个GPU当成是一个画画的工厂,其中流处理器的数量就是画师的数量,其数量自然是 ...

  5. SpringBoot 项目打包后获取不到resource下资源的解决

    SpringBoot 项目打包后获取不到resource下资源的解决 在项目中有几个文件需要下载,然后不想暴露真实路径,又没有CDN,便决定使用接口的方式来获取文件.最初的时候使用了传统的方法来获取文 ...

  6. 设计模式之工厂模式详细读后感TT!(五)

    一如既往:原文 工厂方法(factory method)模式的意义是定义一个创建产品对象的工厂接口, 将实际创建工作推迟到子类当中. 核心工厂的创建, 这样核心类成为一个抽象工厂角色, 仅仅复制工厂子 ...

  7. 从零开始的全栈工程师——js篇2.18(js的运动)

    一.元素的 client offset scroll 三个系列 clientWidth / clientHeight / clientTop / clientLeftoffsetWidth / off ...

  8. SQL SERVER 错误代码 0x534

    解决办法就是修改一下登陆名: ALTER LOGIN [G-PC\zqwang]   WITH NAME=[新的机器名\zqwang]; 然后查询一下 Service Broker 队列, 里面已经有 ...

  9. SQL中的聚合函数

    聚合函数是对一组值执行计算并返回单一的值的函数,它经常与SELECT语句的GROUP BY子句一同使用,SQL SERVER 中具体的聚合函数如下:1. AVG 返回指定组中的平均值,空值被忽略. 例 ...

  10. ASP.NET 上传图片到FTP

    目录: 2.代码 3.参考资料 4.IIS环境FTP配置 5.使用虚拟目录注意Server.MapPath() 1. 项目介绍 建立FTP文件服务器与应用程序分开. 下面方法中的参数为Stream因为 ...