赋值运算符函数

对于定义一个赋值运算符函数时,需要注意一下几点:

(1)函数的返回类型必须是一个引用,因为只有返回引用,才可以连续赋值

(2)传入的参数声明为常量引用,可以提高代码效率,同时赋值运算函数内不会改变传入的实例状态

(3)一定要记得释放实例自身已有的内存,否则程序容易出现内存泄露

(4)注意传入的参数和当前的实例是不是同一个实例,如果是同一个,则不用进行赋值操作,直接返回即可。

复制构造函数

如果一个构造函数的第一个参数是自身类类型的引用,且任何额外参数都有默认值,则此构造函数是复制构造函数。

什么时候调用复制构造函数?

(1)当用类的一个对象初始化该类的另一个对象时;

(2)将一个对象作为实参传递给一个非引用类型的形参时;

(3)从一个返回类型为非引用类型的函数返回一个对象时;

深拷贝和浅拷贝的区别:

1. 默认拷贝构造函数

很多时候在我们都不知道拷贝构造函数的情况下,传递对象给函数参数或者函数返回对象都能很好的进行,这是因为编译器会给我们自动产生一个拷贝构造函数,这就是“默认拷贝构造函数”,这个构造函数很简单,仅仅使用“老对象”的数据成员的值对“新对象”的数据成员一一进行赋值,它一般具有以下形式:

Rect::Rect(const Rect& r)
{
width = r.width;
height = r.height;
}

2. 浅拷贝

所谓浅拷贝,指的是在对象复制时,只对对象中的数据成员进行简单的赋值,默认拷贝构造函数执行的也是浅拷贝。大多情况下“浅拷贝”已经能很好地工作了,但是一旦对象存在了动态成员,那么浅拷贝就会出问题了。

当然,这不是我们所期望的结果,在销毁对象时,两个对象的析构函数将对同一个内存空间释放两次,这就是错误出现的原因。我们需要的不是两个p有相同的值,而是两个p指向的空间有相同的值,解决办法就是使用“深拷贝”。

3. 深拷贝

在“深拷贝”的情况下,对于对象中动态成员,就不能仅仅简单地赋值了,而应该重新动态分配空间,如上面的例子就应该按照如下的方式进行处理:

深拷贝主要解决的问题是指针成员变量浅拷贝的问题。

1. 防止默认拷贝(也能够禁止复制)

  有一个小技巧可以防止按值传递——声明一个私有拷贝构造函数。甚至不必去定义这个拷贝构造函数,这样因为拷贝构造函数是私有的,如果用户试图按值传递或函数返回该类对象,将得到一个编译错误,从而可以避免按值传递或返回对象。如下程序:

#include <iostream>
using namespace std; class CExample
{
private:
int value; public:
//构造函数
CExample(int val)
{
value = val;
cout << "creat: " << value << endl;
} private:
//拷贝构造,只是声明
CExample(const CExample& C); public:
~CExample()
{
cout << "delete: " << value << endl;
} void Show()
{
cout << value << endl;
}
}; //全局函数
void g_Fun(CExample C)
{
cout << "test" << endl;
} int main()
{
CExample test();
// g_Fun(test); // 按值传递将出错 return ;
}

而根据《C++ Primer》第四版13.1.3节,要禁止类的复制, 类必须显示声明其复制构造函数为private。

小问题:一个类中可以有多个拷贝构造函数吗?

  解答:类中可以存在超过一个拷贝构造函数。

1 class X {
2 public:
3 X(const X&); // const 的拷贝构造
4 X(X&); // 非const的拷贝构造
5 };

关于拷贝构造函数与拷贝赋值操作符的区别:

两都都是用已存在的对象A来初始化另一个对象B。不同之处在于:

复制构造函数是针对一个未存在的对象进行初始化;赋值是针对已存在的对象进行初始化。

#include<iostream>
#include<cstring>
using namespace std;
class CMyString
{
private:
//int value;
char *m_pdata;
public:
CMyString(char *pdata=NULL);
CMyString(const CMyString &str);//复制构造函数
CMyString & operator = (const CMyString &str);//赋值运算符函数
~CMyString(void);
void print();
};
CMyString::CMyString(char *pdata)
{
if(pdata==NULL)
{
m_pdata=new char[];
m_pdata[]='\0';
}
else
{
int len=strlen(pdata);
m_pdata=new char[len+];
strcpy(m_pdata,pdata);
}
}
CMyString::CMyString(const CMyString &str)//深拷贝 默认的是浅拷贝
{
// cout<<"hello xiaoming"<<endl;
int len=strlen(str.m_pdata);
m_pdata=new char[len+];
strcpy(m_pdata,str.m_pdata);
}
CMyString::~CMyString()
{
delete []m_pdata;
}
CMyString& CMyString::operator =(const CMyString&str)
{
if(this==&str)
return *this;
delete []m_pdata;
m_pdata=NULL; m_pdata=new char[strlen(str.m_pdata)+];
strcpy(m_pdata,str.m_pdata); return *this;
}
void CMyString::print()
{
cout<<m_pdata<<endl;
} void test1()
{
cout<<"test() begin"<<endl;
char text[]="hello world";
CMyString str1(text);
CMyString str2,str3;
str3=str2=str1;//调用赋值运算操作符
cout<<"The expected result is: "<<text<<endl;
cout<<"the str2 actual result is: "<<endl;
str2.print();
cout<<endl;
cout<<"The expected result is: "<<text<<endl;
cout<<"the str3 actual result is: "<<endl;
str3.print();
cout<<endl;
}
void test2()
{
cout<<"test4() begin"<<endl;
char text[]="hello world";
CMyString str1(text); //初始化操作 调用构造函数
CMyString str2=str1; //标准写法CMyString str2(str1); 调用复制构造函数而不是赋值
str2.print();
cout<<endl;
}
int main()
{
test1();
test2();
return ;
}

代码

class CMyString
{
public:
CMyString(char *ptr = nullptr);
CMyString(const CMyString &str);
~CMyString();
CMyString& operator=(const CMyString& str); private:
char *pData;
}; CMyString& CMyString::operator=(const CMyString& str)
{
pData = str.pData;
return *this;
}

存在问题:

这个赋值运算符重载函数存在的问题如下:

  1)浅拷贝;

  2)没有(检查)释放实例自身已有的内存。如果我们忘记在分配新内存之前释放自身已有的空间,程序将出现内存泄漏;

  3)没有判断传入的参数和当前的实例(*this)是不是同一个实例。如果是同一个,则不进行复制操作,直接返回。如果事先不判断就进行赋值,那么在释放实例自身的内存的时候就会导致严重问题:当*this和传入的参数是同一个实例时,那么一旦释放了自身的内存,传入的参数的内存也同时被释放了,因此再也找不到需要赋值的内容了。

  修改之后的赋值运算符重载函数如下:

CMyString& CMyString::operator=(const CMyString& str)
{
if (this == &str)
return *this; delete []pData;
pData = nullptr; pData = new char[strlen(str.pData) + ];
strcpy(pData, str.pData); return *this;
}

上述代码现在的问题在于4)异常安全性,即new可能会抛出异常,而我们却没有处理!所以我们可以将程序继续修改:

CMyString& CMyString::operator=(const CMyString& str)
{
if (this == &str)
return *this; char *tmp = new(nothrow) char[strlen(str.pData) + ];
if (tmp == nullptr)
return *this; strcpy(tmp, str.pData); delete []pData;
pData = tmp;
tmp = nullptr; return *this;
}

除了前边提到的4个点,赋值运算符重载还有两点需要注意:

  5)是否把返回值的类型声明为该类型的引用,并在函数结束前返回实例自身的引用(即*this)。只有返回一个引用,才可以允许连续赋值。否则如果函数的返回值是void,应用该赋值将不能做连续赋值。假设有3个CMyString对象:str1、str2和str3,在程序中语句str1=str2=str3将不能通过编译。

  6)是否把传入的参数的类型声明为常量引用。如果传入的参数不是引用而是实例,那么从形参到实参会调用一次拷贝构造函数。把参数声明为引用可以避免这样的无谓消耗,从而提高代码效率。同时,我们在赋值运算符函数内不会修改传入的实例的状态,因此应该为传入的引用参数加上const关键字。

剑指offer:赋值运算符函数和复制构造函数的更多相关文章

  1. 剑指Offer:面试题26——复制复杂的链表(java实现)

    问题描述: 输入一个复杂链表(每个节点中有节点值,以及两个指针,一个指向下一个节点,另一个特殊指针指向任意一个节点). 思路1: 1.先复制链表节点,并用next链接起来. 2.然后对每一个结点去修改 ...

  2. 剑指offer 复杂链表的复制 (有向图的复制)

    时间复杂度O(3N) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 ...

  3. 剑指Offer——复杂链表的复制

    题目描述: 输入一个复杂链表(每个节点中有节点值,以及两个指针,一个指向下一个节点,另一个特殊指针指向任意一个节点),返回结果为复制后复杂链表的head.(注意,输出结果中请不要返回参数中的节点引用, ...

  4. 用js刷剑指offer(复杂链表的复制)

    题目描述 输入一个复杂链表(每个节点中有节点值,以及两个指针,一个指向下一个节点,另一个特殊指针指向任意一个节点),返回结果为复制后复杂链表的head.(注意,输出结果中请不要返回参数中的节点引用,否 ...

  5. 赋值运算符函数__from <剑指Offer>

    前段时间忙于项目,难得偷得几日闲,为即将到来的就业季做准备.在面试时,应聘者要注意多和考官交流,只有具备良好的沟通能力,才能充分了解面试官的需求,从而有针对性地选择算法解决问题. 题目来源于<剑 ...

  6. 【Java】 剑指offer(35) 复杂链表的复制

    本文参考自<剑指offer>一书,代码采用Java语言. 更多:<剑指Offer>Java实现合集   题目 请实现函数ComplexListNode* Clone(Compl ...

  7. 【剑指offer】包括min函数的栈

    转载请注明出处:http://blog.csdn.net/ns_code/article/details/26064213 剑指offer上的第21题,之前在Cracking the Coding i ...

  8. 剑指Offer——常用SQL语句、存储过程和函数

    剑指Offer--常用SQL语句.存储过程和函数 常用SQL语句 1.在MySQL数据库建立多对多的数据表关系 2.授权.取消授权 grant.revoke grant select, insert, ...

  9. 【Java】 剑指offer(30) 包含min函数的栈

    本文参考自<剑指offer>一书,代码采用Java语言. 更多:<剑指Offer>Java实现合集   题目 定义栈的数据结构,请在该类型中实现一个能够得到栈的最小元素的min ...

随机推荐

  1. (转)我如何利用前端技术得到 XXOO 网站的 VIP

    网页如图,这里只是说明整个网站的一些技术点,所以不该看的地方我都打上马赛克了,让我们揭开这些网站的整个前端工作原理首先刚进去的时候显示一堆乱七八糟的东西,点进去其中一个页面,下面各种虚假评论,然后每隔 ...

  2. redis3.2 Jedis java操作

    package com.util; import java.util.HashSet; import java.util.List; import java.util.Map; import java ...

  3. android studio 2.0 GPU Debugger使用说明

    GPU Debugger GPU Debugging Tools The GPU debugging tools are an experimental feature intended to hel ...

  4. createjs学习二之flash转canvas学习1

    设置发布 或者 导出参数设置 c_lib_main c_images_main/ 导出之后的文件 打开导出的html文件可以看到如下,,可以正常显示的,,只是stop了还不能动 给舞台上某一元素添加点 ...

  5. Java this关键字

    this 关键字有三个应用: 1.this调用本类中的属性,也就是类中的成员变量 2.this调用本类中的其他方法 3.this调用本类中的其他构造方法,调用时要放在构造方法的首行 来看下面这段代码: ...

  6. spring-amqp 动态创建queue、exchange、binding

    pom.xml <!-- mq 依赖 --> <dependency> <groupId>com.rabbitmq</groupId> <arti ...

  7. Centos下ACL(访问控制列表)介绍(转)

    我们知道,在Linux操作系统中,传统的权限管理分是以三种身份(属主.属組以及其它人)搭配三种权限(可读.可写以及可执行),并且搭配三种特殊权限(SUID,SGID,SBIT),来实现对系统的安全保护 ...

  8. 通过代码自定义cell(cell的高度不一致)

  9. Git Cheat Sheet

    Merge Undo git merge with conflicts $ git merge --abort Archive $ git archive --format zip --output ...

  10. 彻底弄懂css中单位px和em,rem的区别

    国内的设计师大都喜欢用px,而国外的网站大都喜欢用em和rem,那么三者有什么区别,又各自有什么优劣呢? PX特点 -1. IE无法调整那些使用px作为单位的字体大小: -2. 国外的大部分网站能够调 ...