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. 性能测试工具LoadRunner21-LR之Controller 常用函数

    1.事务函数: Lr_start_transaction();  //标记事务的开始 Lr_end_transaction();  //标记事务的结束,一般情况下,事务开始与结束联合使用 Lr_get ...

  2. 编译opencv python接口

    首先,安装依赖1 sudo apt-get install build-essential 2 sudo apt-get install cmake git libgtk2.0-dev pkg-con ...

  3. jenkins对测试脚本的构建步骤

    使用Jenkins定时执行脚本 Jenkins是基于Java开发的一种持续集成工具,用于监控持续重复的工作,所以可用于定时执行python脚本. 环境准备:jdk1.7及以上+Jenkins[+tom ...

  4. linux 编译安装php7

    1.下载php7安装包: php7 2.解压 .tar.gzcd php-7.1.5 3.可能需要的扩展 yum install libmcrypt libmcrypt-devel mcrypt mh ...

  5. activeMq 配置(一)

    基础知识补充 1.ActiveMQ从入门到精通(一)https://www.jianshu.com/p/ecdc6eab554c 2.ActiveMQ从入门到精通(二)https://www.jian ...

  6. Redis入门--(二)Redis的安装

    1.建议安装在Linux服务器上来运行测试的

  7. sqlserver门户设置

    ------ insert by wandz 20180918 门户模板表 start ------set identity_insert oa_portal_template on;begin de ...

  8. C++ Knowledge series Inheritance & RTTI & Exception Handling

    Inheritance The pointer or reference to base class can address/be assigned with any of the classes d ...

  9. UIWindow及程序启动的过程

    1.   UIWindow才有自发显示的功能, 一个程序之所以能显示东西,是因为有window !//  [self.window makeKeyAndVisible]; 2.   任何view的显示 ...

  10. 线程队列queue

    队列queue 队列用于线程之间安全的信息交换 队列和列表的区别:队列里的信息get()后就没了,而列表获取数据则是copy,原列表里的值还在 使用前先实例化队列 q = queue.Queue(ma ...