strcpy函数_错误更正(拷贝赋值函数的正确使用姿势)

这是一篇对什么是C++的The Rule of Three的错误更正和详细说明。

阅读时间7分钟。难度⭐⭐⭐

d2ae8d894c9d5ed2f1ecbc94410cbc46.png

虽然上一篇文章的阅读量只有凄惨的两位数e8ed25c9ca7b7b4da45247666c85dae0.gif,但是怀着对小伙伴负责的目的,必须保证代码的正确性。这是大厨做技术自媒体的态度。

前文最后一段代码是这样的:

class Dog {
 private:
   char* name;
   int age;
 public:
   '...省略构造和拷贝构造函数...'
    //拷贝赋值函数
   Dog& operator=(const Dog& that) {
     name = new char[strlen(that.name)+1];
     strcpy(name, that.name);
     age = that.age;
   }
   '...省略析构函数...'
};

先不谈异常安全,这段拷贝赋值函数的代码本身有什么问题?

有3个问题:

  • 没有释放原对象指针成员指向的内容

  • 没有返回值

  • 没有自赋值检查

下面我们一个一个分析。

 1   没有释放原对象指针

这个问题很严重,因为一定会造成内存泄露

原因是指针所指的内存未被释放,而指针又指向了别处。

例子如下,我们写了一个main函数,长这样:

int main(int argc, char* argv[]) {
    Dog D1("Bobby", 2);
    Dog D2("Teddy", 3);
    Dog D2 = D1;
}

D1和D2分别是是Dog的对象。根据构造函数的定义,D2中的name指针指向了字符数组“Teddy”。而当进行D2 = D1操作时,name = new char[strlen(that.name)+1]这一步会在D2中重新创建一个名字为name且指向“Bobby”的指针。

这么做也许编译器不会报错,但是会有问题。

因为在new一个name指针之前,原本的name指针指向的内存并没有被释放。而新的name指针只对新创建的内存负责,老的内存已经变成无主之地。看来内存泄露是逃不掉了。

这个问题看着复杂,解决的办法倒是简单,只需要在拷贝赋值函数体第一行加上 delete[] name就可以了。

class Dog {
 private:
   char* name;
   int age;
 public:
   '...省略构造和拷贝构造函数...'
    //拷贝赋值函数
   Dog& operator=(const Dog& that) {
     delete[] name; //释放原对象指针成员指向的内容
     name = new char[strlen(that.name)+1];
     strcpy(name, that.name);
     age = that.age;
   }
   '...省略析构函数...'
};
2   没有返回值

第二个问题犯的错很低级b530a02792ba4a9a483e332040a47170.png,拷贝赋值函数的行为和普通函数一样需要一个返回值。而返回值的类型通常是类的对象的引用。

参照常用的写法,这里返回*this(this是C++类的隐藏成员,表示对象本身)。

class Dog {
 private:
   char* name;
   int age;
 public:
   '...省略构造和拷贝构造函数...'
    //拷贝赋值函数
   Dog& operator=(const Dog& that) {
     delete[] name; //释放原对象指针成员指向的内容
     name = new char[strlen(that.name)+1];
     strcpy(name, that.name);
     age = that.age;
     return *this; //返回对象引用
   }
   '...省略析构函数...'
};

另外大家可能有疑问为什么返回值是一个引用而不是一个值呢?

答案是只有引用才能进行连续赋值。

假设有3个Dog对象:D1、D2、D3,如果返回值不是引用,那么类似D1 = D2 = D3将不能通过编译。

 3   没有自赋值检查

什么叫做自赋值?

就是两个相同对象之间用等号连接,比如:

int main(int argc, char* argv[]) {
    Dog D1("Bobby", 2);
    Dog D1 = D1; //同一个D1相互赋值
}

当然,一般不会有人写出这样的代码来。这里只是举个简单的例子,但是如果在大型项目中不同开发者对同一对象取了不同的别名,那么自赋值的情况是有可能发生的。

对于上面的Dog类而言,如果执行D1 = D1,那么会发生下面的事情:

首先,对象D1中的name指针被析构,name指向的内存被释放;

然后,下一行中的strlen(that.name)又用到了D1的name所指向的内存。

重点来了:这时你会惊讶地发现编译器提示你name已经不存在了!!!

5561f274749912e9a46bc7730ffb8541.gif

因为在编译器看来,你在做对同一对象先释放了内存再使用的非法事情!

就好比你是拆迁大队的,你没有确认拆的是不是自己的房子就不管三七二十一直接拆了,然而你晚上还要回家住......

0080f35ac95f4c86aed8d1df71ba601b.gif

C++真的烧脑,仅仅是不小心把自己赋值给了自己就把自己的一部分给搞丢了,这在其他语言中似乎是天方夜谭。但是C++似乎很情愿把事情搞复杂。

幸好,自赋值问题也很容易修复,只需要在delete指针之前做一个自赋值的判断。

完整代码如下:

class Dog {
 private:
   char* name;
   int age;
 public:
   '...省略构造和拷贝构造函数...'
    //拷贝赋值函数
   Dog& operator=(const Dog& that) {
     if(this != &that) { //判断是否自赋值
         delete[] name; //释放原对象的指针指向的内容
         name = new char[strlen(that.name)+1];
         strcpy(name, that.name);
         age = that.age;
     }
     return *this;
   }
   '...省略析构函数...'
};

this != &that这个判断的写法看上去莫名其妙,大厨来给大家分析一下:

this代表D1=D1中等号左边的D1,&that代表等号右边的D1的引用(本质上还是D1)。this和&that二者如果相等就说明是同一个对象,那么拷贝赋值函数就直接返回对象的引用。

至此,三个问题终于都解决了ec71b8812d70d7797fba6e819f193a5c.png

 4   总结时刻

通过以上问题的剖析可以发现,C++一大半奇奇怪怪行为的背后都有一个处理不当的指针。

另外,写一个正确的类真的一点都不简单,需要考虑内存泄露,返回值类型,自赋值等等情况。

打住,再说下去大厨真的转行成C++专业劝退师了。

98bd8ddea5826b57827661d8ef0864e4.png

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

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

相关文章

Spring Batch中面向TaskletStep的处理

许多企业应用程序需要批处理才能每天处理数十亿笔交易。 必须处理这些大事务集,而不会出现性能问题。 Spring Batch是一个轻量级且强大的批处理框架,用于处理这些大数据集。 Spring Batch提供了“面向TaskletStep”和“面向块”的处理风格。 在本文中&a…

布局中常见的居中问题

说到布局除了浮动以及定位外还有一个不得不提的点,那就是居中,居中问题我们在网页布局当中经常遇到,那么以下就是分为两部分来讲,一部分是传统的居中,另一种则是flex居中,每个部分又通过分为水平垂直居中来…

排序算法——桶排序

把数据放进若干个桶,然后在桶里用其他排序,近乎分治思想。从数值的低位到高位依次排序,有几位就排序几次。例如二位数就排两次,三位数就排三次,依次按照个十百...的顺序来排序。 第一次排序:50 12 …

原型设计模式:创建另一个小车

创建对象确实是一个耗时的过程,也是一件昂贵的事情。 因此,我们现在正努力节省时间和金钱。 我们该怎么做? 克隆奇迹多莉 有人记得多莉吗? 是的,是绵羊,是第一个被克隆的哺乳动物。 好吧,我不想…

java实现周期任务_java定时任务的实现方式

本文列举常见的java定时任务实现方式,并做一定比较。1. 循环内部sleep实现周期执行创建一个thread,run() while循环里sleep()来实现周期性执行; 简单粗暴,作为一个初学者很容易想到。public class Task1 {public static void main(String[] a…

HTTPS协议在Tomcat中启用的配置

本文将讲解HTTPS协议在Tomcat中启用是如何配置的。 概念简介 Tomcat 服务器是一个免费的开放源代码的Web 应用服务器,属于轻量级应用服务器,在中小型系统和并发访问用户不是很多的场合下被普遍使用,是开发和调试 JSP 程序的首选。 HTTP 超文本…

CSS3实现烟花特效 --web前端

烟花特效&#xff0c;比较简单&#xff0c;直接贴代码了……<!DOCTYPE html><html lang"en"><head> <meta charset"UTF-8"> <title>css3实现烟花特效</title> <style> * { margin: 0; padding: 0; } html{ widt…

java.lang.ClassNotFoundException:如何解决

本文适用于当前面临java.lang.ClassNotFoundException挑战的Java初学者。 它将为您提供此常见Java异常的概述&#xff0c;这是一个示例Java程序&#xff0c;可支持您的学习过程和解决策略。 如果您对与更高级的类加载器相关的问题感兴趣&#xff0c;我建议您复习有关java.lang…

小程序实践(三):九宫格实现及item跳转

效果图&#xff1a; 实现效果图红色线包含部分的九宫格效果&#xff0c;并附带item点击时间。 ------------------------------------------------------------------------------------------------------ 具体实现&#xff1a; 1、首先添加图片资源文件 在项目根目录新建一个…

用JavaFX编写图块引擎

随着JavaFX嵌入式版本的问世&#xff0c;我们的框架对于游戏开发变得越来越有趣&#xff0c;因为我们现在可以瞄准平板电脑和智能手机等小型消费类设备。 因此&#xff0c;我决定对JavaFX进行更多的游戏编写实验。 这次&#xff0c;我想使用Canvas对渲染进行更多控制&#xff0…

HTML5--应用网页模板

因为刚开始写博客,只想着把知识点记录在这,也想给你们一些参考,在布局上有些没有思考太多;回过头来看,实在是不忍直视,对不住之前阅读的100 ,既然昨天的事无法挽回,那就从现在开始从新整改吧!也希望大家看了,能对你们有所帮助 1.先给大家看看效果图,好让大家有点兴趣 2.大家再来…

企业集成模式简介

在此博客文章中&#xff0c;我们将介绍一些企业集成模式。 这些是旨在解决集成挑战的已知设计模式。 阅读此书后&#xff0c;您将可以设计集成解决方案。 EIP&#xff08;简而言之&#xff09;是已知的设计模式&#xff0c;可为应用程序集成过程中遇到的问题/问题提供解决方案…

手把手教你Chrome浏览器安装Postman(含下载云盘链接)【转载】

转载自&#xff1a;http://www.ljwit.com/archives/php/278.html 说明&#xff1a; Postman不多介绍&#xff0c;是一款功能强大的网页调试与发送网页HTTP请求的Chrome插件。本文主要介绍下安装过程。 本文使用的是解压文件直接进行安装。是比较快速有效的安装方式&#xff0c;…

C语言博客作业--数据类型

题目1&#xff1a;7-4 打印菱形图案 1. 本题PTA提交列表 2. 设计思路 1.定义变量i,j,k,n;且声明i为要打印的行数&#xff0c;j是控制输出打印空格和星星&#xff0c;n是菱形为菱形的高 2.输入n 3.i1&#xff0c;j1 4.先打印上半部分&#xff0c;第一行到n/21行&#xff0c;输出…

信息隐藏将txt文件合并到jpg文件中_使用Kali Linux在图像内隐藏机密消息—可在任何Linux发行版使用

欢迎回到“Esn技术社区”&#xff01;今天&#xff0c;我们将演示如何使用Steghide(一种可在Kali Linux上使用的流行隐写工具)在图像内隐藏消息。在计算机科学中&#xff0c;将信息隐藏在文件内(例如图像&#xff0c;文档&#xff0c;程序&#xff0c;有用数据&#xff0c;消息…

java方法调用机制_Java方法调用机制 - osc_bkdv2it5的个人空间 - OSCHINA - 中文开源技术交流社区...

最近在编程时&#xff0c;修改方法传入对象的对象引用&#xff0c;并没有将修改反映到调用方法中。奇怪为什么结果没有变化&#xff0c;原因是遗忘了Java对象引用和内存分配机制。本文介绍3个点&#xff1a;① 该问题举例说明② 简要阐述Java内存区域③ 介绍JVM中方法调用的机制…

CSS染色图标(图片)

之前一直以为用background引入的图标无法染色&#xff08;非字体图标&#xff09;&#xff0c;现在才知道有黑科技可以用&#xff0c;就是利用drop-shadow。 代码示例 <!DOCTYPE html> <html> <head lang"en"><meta charset"UTF-8"&…

eclipse安装java web插件

1 查看eclipse版本 找到eclipse的安装目录&#xff0c;找到readme文件&#xff0c;打开其中的html文件&#xff0c;我的是4.6版本的,代号是oxygen 2 安装 打开eclipse,点击help-Install new software-单击add&#xff0c;在弹出窗口中输入网址&#xff1a; http://download.ecl…

实现输入框小数多 自动进位展示,编辑时实际值不变

今天遇到个业务需求&#xff0c;要求输入框&#xff0c;输入数字的小数位数可以很多位&#xff0c;但移开后显示&#xff0c;只显示小数点后两位 &#xff08;四舍五入&#xff09;&#xff0c;当要编辑的时候&#xff0c;展现其原来的输入数据。 闲话不多说&#xff0c;当时也…

使用Jasper Reports以Java创建报告

上周&#xff0c;我试图使用Jasper创建报告。 在这篇文章中&#xff0c;我将记录一些资源和链接&#xff0c;以便对任何寻求类似信息的人都有用。 我将介绍Jasper报告&#xff0c;示例和Dynamic Jasper的生命周期。 Jasper Reports是世界上最受欢迎的开源报告引擎。 它完全用…