【C++】string类模拟实现过程中值得注意的点

2023-12-02 11:12

本文主要是介绍【C++】string类模拟实现过程中值得注意的点,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

👀樊梓慕:个人主页

 🎥个人专栏:《C语言》《数据结构》《蓝桥杯试题》《LeetCode刷题笔记》《实训项目》《C++》《Linux》

🌝每一个不曾起舞的日子,都是对生命的辜负


目录

前言

1.有关const的使用

(1)const修饰形参

(2)const修饰返回值

(3)const修饰成员函数

2.有关声明与定义分离可能出现的问题

(1)合并命名空间

(2) 指定类域

(3)当声明与定义分离时,缺省参数只需要在声明中体现

 (4)函数重定义问题

3.有关模拟实现的传统写法与现代写法

4.有关string类流插入和流提取操作符的实现思维定势陷阱


前言

本篇文章旨在记录博主在模拟实现string类中遇到的一些问题,希望与大家共勉,在模拟实现string类的过程中运用了之前所学的很多知识,包括一些问题的出现都是对之前所学知识的考验,为了更加深入的了解和掌握string类,大家不妨跟随博主的步伐一起,看看遇到这些问题时,你是否可以轻松应对,利用之前所学解决实际问题。


欢迎大家📂收藏📂以便未来做题时可以快速找到思路,巧妙的方法可以事半功倍。

=========================================================================

GITEE相关代码:🌟fanfei_c的仓库🌟

=========================================================================


1.有关const的使用

(1)const修饰形参

首先我们来看string类中这些函数的模拟实现:

//relational operators
bool operator>(const string& s);
bool operator==(const string& s);
bool operator<=(const string& s);
bool operator<(const string& s);
bool operator>=(const string& s);      
bool operator!=(const string& s);

 为什么用const修饰形参?

  • const修饰形参的意义主要体现在输入型参数上,尤其是指针类型和引用,const修饰某个变量,肯定是为了保护该变量的值不被改变。

那什么时候我们需要保护这个值不会被改变呢?

  • 当然是我们需要他作为函数的输入时,输出型参数我们需要他根据函数功能做出改变,所以肯定不需要用const修饰。

那我为什么又说const修饰输入型参数尤其在指针类型和引用呢?其他普通类型呢?

  • 因为我们知道形参是实参的一份临时拷贝,形参的改变不会影响实参,所以我们也就不需要保护它,因为它的改变不会影响到实参本身。

那为什么要用引用,我直接传不就得了,反正也只是拷贝,不会影响本身,这样也省得使用const来保护了。

  • 这里传递引用的价值就在于可以极大的优化节省调用函数的代价!

我们知道引用就是本身的一个别名,实际上就是本身,所以当我们传递引用时,就节省了拷贝的代价,为了效率我们值得做出这样的操作,但为了保护好我们本身不被修改,一定要用const修饰,这样可以增强程序的健壮性!防止我们的程序崩溃。


(2)const修饰返回值

 同样我们用模拟实现来引入。

//access
char& operator[](size_t index);
const char& operator[](size_t index)const;

这里为什么要实现两个版本的下标访问? 

类的有些对象可能可以改变,有的不能改变,举个例子来说,对于string对象来说,有的我们可能希望定义的string对象可以改变,有的则不能改变:

string str1= "hello world";

const string str2 = "hello world";

我们如果进行下标操作符运算,str1[0]='f'这是允许的,而str2[1]='f'则是不允许的。

并且这两个操作调用的成员函数也是不一样的,前一个调用的是string类的非const成员函数,后一个调用的是string类的const成员函数

如果我们没有定义const成员函数,则str2会调用相应的非const成员函数,从而导致我们本来不想其被修改的对象被意外修改。

const修饰返回值的意义何在? 

实际上,如果函数返回的是普通的变量,即值传递,那确实没有什么意义,因为根据前面的学习,函数是如何返回值的呢,实际上是拷贝,拷贝一份内容相同的常量返回,那它本来就是常量你再用const修饰那不多此一举了么。

是的,所以如果函数返回采用值传递的方式,那const这里就没有任何意义。

const修饰返回值真正的意义在于指针传递返回,用来赋值给const修饰的指针内容。

例如函数:

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

接收这个函数的返回值我们需要:

void test5()
{string s1 = "hello world!!!!!!!!!!!!!!!!";const char* tmp=s1.c_str();cout << tmp << endl;
}

 为什么要使用引用做返回值?

首先明确:

  • 之前我们说过,除了函数作用域,返回对象就会被销毁,不能用引用返回,否则结果是不确定的。 

那这里为什么可以这样使用呢?

  • 是因为string所使用的空间为动态内存new来的,我们说引用做返回值,可用静态变量或动态内存。

 引用做返回值的主要目的就是能够修改返回的内容,加const是为了适配const string str="abc"这种被const修饰了的字符串。


(3)const修饰成员函数

同样我们用string类的模拟实现引入:

// 返回c在string中第一次出现的位置
size_t find(char c, size_t pos = 0) const;
// 返回子串s在string中第一次出现的位置
size_t find(const char* s, size_t pos = 0) const;

 const修饰成员函数实际上修饰的是该成员函数隐含的this指针,表明在该成员函数中不能对类的任何成员进行修改。

任何不会修改数据成员的函数都应该声明为const 类型(这样const对象和非const对象都可以调用该函数)。如果在编写const 成员函数时,不慎修改了数据成员,或者调用了其它非const 成员函数,编译器将指出错误,这无疑会提高程序的健壮性

即,

  • const对象不可以调用非const成员函数;
  • 非const对象可以调用const成员函数;
  • const成员函数内部不能调用其他非const成员函数;
  • 非const成员函数内部可以调用其他const成员函数。

总结为:权限可以缩小,但是权限不可以放大!


2.有关声明与定义分离可能出现的问题

博主在实现string类时,统一的把函数定义都以内联的形式实现了,当然后期实现完成尝试着想要声明与定义分离下,但却出现了很多报错,这里我梳理一下,形成了以下内容。

(1)合并命名空间

首先分离后的第一个问题就是,由于我之前在头文件中类的实现部分都包含在了命名空间内部。

即,

所以首先需要解决这一问题,我们可以采用左图这种方式,当然右面的方式显然更好:


(2) 指定类域

由于声明定义分离,所以我们需要指定类域:


(3)当声明与定义分离时,缺省参数只需要在声明中体现


 (4)函数重定义问题

如果.h文件中存在函数定义(不包括成员函数),只要存在多个cpp文件,并且他们都包含了string.h头文件,那他们在展开时就会出现重定义的问题。


3.有关模拟实现的传统写法与现代写法

我们来观察一下两种实现方式的区别:

//传统写法
string::string(const string& s)
{_str = new char[s._capacity + 1];strcpy(_str, s._str);_size = s._size;_capacity = s._capacity;
}
//现代写法
string::string(const string& s)
{string tmp(s._str);swap(tmp);
}
//结合swap来观察
void string::swap(string& s)
{std::swap(_str, s._str);std::swap(_size, s._size);std::swap(_capacity, s._capacity);
}

那现代写法优越在哪呢?

现代写法非常巧妙,他完全利用了我们学习构造函数部分的知识。

首先完成用s._str完成构造tmp对象,然后交换*this与tmp,交换后tmp析构。


4.有关string类流插入和流提取操作符的实现思维定势陷阱

在之前的学习中,我们知道流插入与流提取的实现要放在全局从而保证第一个参数不为this指针,但当时面临一个问题,我们实现的是日期类,想要打印日期类就必须访问到类的成员变量,但我们知道成员变量一般都是私有的,所以我们引入了友元的概念用来解决这一问题。

=========================🐸这部分忘了的老铁可以戳🐸=========================

樊梓慕---类和对象(中)之拷贝构造与运算符、操作符重载【CSDN】icon-default.png?t=N7T8http://t.csdnimg.cn/2wbiI🐸🐸🐸🐸🐸🐸🐸🐸🐸🐸🐸🐸🐸🐸🐸🐸🐸🐸🐸🐸🐸🐸🐸🐸🐸🐸🐸🐸🐸🐸🐸


 但这里我们需不需要利用友元呢?

ostream& operator<<(ostream& _cout, const F::string& s)
{for (auto e : s){_cout << e;}return _cout;
}
istream& operator>>(istream& _cin, F::string& s)
{s.clear();char buff[128];char ch = _cin.get();int i = 0;while (ch != ' ' && ch != '\n'){buff[i++] = ch;if (i == 127){buff[i] = '\0';s += buff;i = 0;}ch = _cin.get();}if (i > 0){buff[i] = '\0';s += buff;}return _cin;
}

我们并没有访问string类的成员变量,所以这里不需要利用友元!


🚨请不要形成思维定势,要活学活用🚨


=========================================================================

如果你对该系列文章有兴趣的话,欢迎持续关注博主动态,博主会持续输出优质内容

🍎博主很需要大家的支持,你的支持是我创作的不竭动力🍎

🌟~ 点赞收藏+关注 ~🌟

=========================================================================

这篇关于【C++】string类模拟实现过程中值得注意的点的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/445114

相关文章

使用Java解析JSON数据并提取特定字段的实现步骤(以提取mailNo为例)

《使用Java解析JSON数据并提取特定字段的实现步骤(以提取mailNo为例)》在现代软件开发中,处理JSON数据是一项非常常见的任务,无论是从API接口获取数据,还是将数据存储为JSON格式,解析... 目录1. 背景介绍1.1 jsON简介1.2 实际案例2. 准备工作2.1 环境搭建2.1.1 添加

Java实现任务管理器性能网络监控数据的方法详解

《Java实现任务管理器性能网络监控数据的方法详解》在现代操作系统中,任务管理器是一个非常重要的工具,用于监控和管理计算机的运行状态,包括CPU使用率、内存占用等,对于开发者和系统管理员来说,了解这些... 目录引言一、背景知识二、准备工作1. Maven依赖2. Gradle依赖三、代码实现四、代码详解五

java如何分布式锁实现和选型

《java如何分布式锁实现和选型》文章介绍了分布式锁的重要性以及在分布式系统中常见的问题和需求,它详细阐述了如何使用分布式锁来确保数据的一致性和系统的高可用性,文章还提供了基于数据库、Redis和Zo... 目录引言:分布式锁的重要性与分布式系统中的常见问题和需求分布式锁的重要性分布式系统中常见的问题和需求

SpringBoot基于MyBatis-Plus实现Lambda Query查询的示例代码

《SpringBoot基于MyBatis-Plus实现LambdaQuery查询的示例代码》MyBatis-Plus是MyBatis的增强工具,简化了数据库操作,并提高了开发效率,它提供了多种查询方... 目录引言基础环境配置依赖配置(Maven)application.yml 配置表结构设计demo_st

python使用watchdog实现文件资源监控

《python使用watchdog实现文件资源监控》watchdog支持跨平台文件资源监控,可以检测指定文件夹下文件及文件夹变动,下面我们来看看Python如何使用watchdog实现文件资源监控吧... python文件监控库watchdogs简介随着Python在各种应用领域中的广泛使用,其生态环境也

el-select下拉选择缓存的实现

《el-select下拉选择缓存的实现》本文主要介绍了在使用el-select实现下拉选择缓存时遇到的问题及解决方案,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的... 目录项目场景:问题描述解决方案:项目场景:从左侧列表中选取字段填入右侧下拉多选框,用户可以对右侧

深入理解C++ 空类大小

《深入理解C++空类大小》本文主要介绍了C++空类大小,规定空类大小为1字节,主要是为了保证对象的唯一性和可区分性,满足数组元素地址连续的要求,下面就来了解一下... 目录1. 保证对象的唯一性和可区分性2. 满足数组元素地址连续的要求3. 与C++的对象模型和内存管理机制相适配查看类对象内存在C++中,规

最新版IDEA配置 Tomcat的详细过程

《最新版IDEA配置Tomcat的详细过程》本文介绍如何在IDEA中配置Tomcat服务器,并创建Web项目,首先检查Tomcat是否安装完成,然后在IDEA中创建Web项目并添加Web结构,接着,... 目录配置tomcat第一步,先给项目添加Web结构查看端口号配置tomcat    先检查自己的to

Python pyinstaller实现图形化打包工具

《Pythonpyinstaller实现图形化打包工具》:本文主要介绍一个使用PythonPYQT5制作的关于pyinstaller打包工具,代替传统的cmd黑窗口模式打包页面,实现更快捷方便的... 目录1.简介2.运行效果3.相关源码1.简介一个使用python PYQT5制作的关于pyinstall

使用Python实现大文件切片上传及断点续传的方法

《使用Python实现大文件切片上传及断点续传的方法》本文介绍了使用Python实现大文件切片上传及断点续传的方法,包括功能模块划分(获取上传文件接口状态、临时文件夹状态信息、切片上传、切片合并)、整... 目录概要整体架构流程技术细节获取上传文件状态接口获取临时文件夹状态信息接口切片上传功能文件合并功能小