01 赋值运算符重载的需求

有时候希望赋值运算符两边的类型可以不匹配,比如:把一个 int 类型变量赋值给一个Complex(复数)对象,或把一个 char* 类型的字符串赋值给一个字符串对象,此时就需要重载赋值运算符‘=’。

需要注意的是:赋值运算符 = 只能重载为成员函数。


02 赋值运算符重载的例子

下面我们以自定义一个自己的字符串类代码的例子,讲解赋值运算符的重载函数。

class MyString // 字符串类
{
public:
// 构造函数,默认初始化1个字节的字符
MyString ():m_str(new char[1])
{
m_str[0] = 0;
} // 析构函数,释放资源
~MyString()
{
delete [] m_str;
} const char* c_str()
{
return m_str;
} // 赋值运算符重载函数
// 重载=号使得 obj = "Hello" 能够成立
MyString & operator= (const char *s)
{
// 释放旧字符串资源
delete [] m_str; // 生成新字符串的空间大小,长度多+1的目的是存放\0
m_str = new char[strlen(s) +1 ]; // 拷贝新字符串的内容
strcpy(m_str, s); // 返回该对象
return *this;
} private:
char * m_str; // 字符串指针
}; int main() { MyString s; s = "Hello~"; // 等价于s.operator=("Hello~");
std::cout << s.c_str() << std::endl; // MyString s2 = "Hello!"; // 这条语句要是不注释就会编译报错 s = "Hi~"; // 等价于s.operator=("Hi~");
std::cout << s.c_str() << std::endl; return 0;
}

输出结果:

Hello~
Hi~

重载=号运算符函数后,s = "Hello~"; 语句就等价于 s.operator=("Hello~");

需要注意的一点是:上面的MyString s2 = "Hello!";语句实际上是初始化语句,而不是赋值语句,因为是初始化语句,所以需要调用构造函数进行初始化,那么这时就需要有char*参数的构造函数,由于我们没有定义此构造函数,所以就会编译出错。


03 浅拷贝和深拷贝

还是依据上面的例子,假设我们要实现最后一个语句的方式:

MyString s1,s2;
s1 = "this"; // 调用重载的赋值语句
s2 = "that"; // 调用重载的赋值语句
s1 = s2; // 如何实现这个??

s1 = s2;语句目的希望是s1对象放的字符串和s2对象放的字符串象要一样,由于 = 号两边的类似都是对象,编译器会用原生的赋值运算符函数,但是这个原生的赋值运算符函数对于有指针成员变量的对象来说,是非常危险的!

浅拷贝

如果用原生的赋值运算符函数去赋值有指针成员变量的对象,就会使得两个对象的指针地址也是一样的,也就是两个对象的指针成员变量指向的地址是同一个地方,这种方式就是浅拷贝。

这时当一个对象释放了指针成员变量时,那么另外一个对象的指针成员变量指向的地址就是空的了,再次使用这个对象时,程序就会奔溃了,因为该对象的指针成员函数已经是个不合法的指针了!

深拷贝

如果对象里面有指针成员变量,则我们需要对原生的赋值运算符函数,防止出现程序出错现象的发生。

因此要在 class MyString 类里加上如下成员函数:

MyString & operator=(const MyString & s)
{
// 释放旧字符串资源
delete [] m_str; // 生成新字符串的空间大小,长度多+1的目的是存放\0
m_str = new char[strlen(s.m_str) +1 ]; // 拷贝新字符串的内容
strcpy(m_str, s.m_str); // 返回该对象
return *this;
}

这么做就够了吗?还有什么需要改进的地方吗?

我们在考虑下面的语句:

MyString s;
s = "Hello";
s = s; // 是否会有问题?

最后一个语句是否会有问题?

s = s;等价于s.operator=(s),由于s和s是相同的对象,那么就没必要完全执行重载的赋值 = 的函数了,我们再加个判断,当左右两边是相同对象时,就直接返回该对象就好:

MyString & operator=(const MyString & s)
{
// 当左右两边是相同对象时,就直接返回该对象就
if(this == &s)
return *this; delete [] m_str;
m_str = new char[strlen(s.m_str) +1 ];
strcpy(m_str, s.m_str);
return *this;
}

对operator=返回值类型的讨论

  • void 好不好?
  • MyString 好不好?
  • 为什么是MyString &?

当我们重载一个运算符的时候,好的风格应该是尽量保留运算符原本的特性

考虑:

  • a = b = c; 这个赋值语句的顺序是先b = c,然后在a = (b = c)。如果返回的void类型,那么a = (void)显然是不成立的;
  • (a = b) = c; 这个赋值语句会修改a的值,如果返回的类型是MyString对象,那么就无法修改a的值了。

分别等价于:

  • a.operator=(b.operator=(c));
  • (a.operator=(b)).operator=(c);

所以综上考虑,operator=返回值类型是MyString &是比较好的。

04 复制(拷贝)构造函数

上面的MyString类是否就没有问题了?

MyString s;
s = "Hello";
MyString s1(s); // 要考虑这种情况,那就要重载复制(拷贝)构造函数

如果使用默认的复制(拷贝)构造函数,那就对有指针成员变量的对象会有问题,因为会默认的复制(拷贝)构造函数会导致两个对象的指针成员变量指向同一个的空间。

所以需要对复制(拷贝)构造函数重载,并实现深拷贝的方式:

MyString (const MyString &s)
{
m_str = new char[strlen(s.m_str) + 1];
strcpy(m_str, s.m_str);
}

05 小结

最后的所有代码,如下:

class MyString // 字符串类
{
public:
// 构造函数,默认初始化1个字节的字符
MyString ():m_str(new char[1])
{
m_str[0] = 0;
} // 复制(拷贝)构造函数
MyString (const MyString &s)
{
m_str = new char[strlen(s.m_str) + 1];
strcpy(m_str, s.m_str);
} // 析构函数,释放资源
~MyString()
{
delete [] m_str;
} const char* c_str()
{
return m_str;
} // 赋值运算符重载函数
// 重载=号使得 obj = "Hello" 能够成立
MyString & operator= (const char *s)
{
// 释放旧字符串资源
delete [] m_str; // 生成新字符串的空间大小,长度多+1的目的是存放\0
m_str = new char[strlen(s) +1 ]; // 拷贝新字符串的内容
strcpy(m_str, s); // 返回该对象
return *this;
} // 赋值运算符重载函数
// 重载=号使得 obj1 = obj2 能够成立
MyString & operator=(const MyString & s)
{
// 当左右两边是相同对象时,就直接返回该对象就
if(this == &s)
return *this; delete [] m_str;
m_str = new char[strlen(s.m_str) +1 ];
strcpy(m_str, s.m_str);
return *this;
} private:
char * m_str; // 字符串指针
}; int main()
{ MyString s1,s2; s1 = "Hello~"; // 等价于s1.operator=("Hello~");
std::cout << s1.c_str() << std::endl; s2 = "Hi~"; // 等价于s2.operator=("Hi~");
std::cout << s2.c_str() << std::endl; s1 = s2; // 等价于s1.operator=(s2);
std::cout << s1.c_str() << std::endl; MyString s3(s1); // 复制构造函数
std::cout << s3.c_str() << std::endl; return 0;
}

输出如下:

Hello~
Hi~
Hi~
Hi~

C++ 赋值运算符'='的重载(浅拷贝、深拷贝)的更多相关文章

  1. JS中有关对象的继承以及实例化、浅拷贝深拷贝的奥秘

    一.属性的归属问题 JS对象中定义的属性和方法如果不是挂在原型链上的方法和属性(直接通过如类似x的方式进行定义)都只是在该对象上,对原型链上的没有影响.对于所有实例共用的方法可直接定义在原型链上这样实 ...

  2. $.extend()浅拷贝深拷贝

    参考网址:http://bijian1013.iteye.com/blog/2255037 jQuery.extend() 函数用于将一个或多个对象的内容合并到目标对象. 注意:1. 如果只为$.ex ...

  3. Python__学习路上的坑之--引用,浅拷贝,深拷贝

    copy : 相当于只是拷贝表面一层,如果里面还有深层次的引用,那么也是直接拷贝引用的地址,而且如果拷贝对象是不可变类型比如元组,那么也是直接拷贝引用. deepcopy: 无论是拷贝可变类型还是不可 ...

  4. C++本质:类的赋值运算符=的重载,以及深拷贝和浅拷贝

    关键词:构造函数,浅拷贝,深拷贝,堆栈(stack),堆heap,赋值运算符摘要:    在面向对象程序设计中,对象间的相互拷贝和赋值是经常进行的操作.    如果对象在申明的同时马上进行的初始化操作 ...

  5. C++ 中赋值运算符重载以及深拷贝浅拷贝解析

    转载自:http://blog.csdn.net/business122/article/details/21242857 关键词:构造函数,浅拷贝,深拷贝,堆栈(stack),堆heap,赋值运算符 ...

  6. [置顶] 运算符重载,浅拷贝(logical copy) ,vs, 深拷贝(physical copy),三大件(bigthree problem)

    一般的我们喜欢这样对对象赋值: Person p1;Person p2=p1; classT object(another_object), or    A a(b); classT object = ...

  7. [置顶] operator overloading(操作符重载,运算符重载)运算符重载,浅拷贝(logical copy) ,vs, 深拷贝(physical copy)

    operator overloading(操作符重载,运算符重载) 所谓重载就是重新赋予新的意义,之前我们已经学过函数重载,函数重载的要求是函数名相同,函数的参数列表不同(个数或者参数类型).操作符重 ...

  8. 运算符重载,浅拷贝(logical copy) ,vs, 深拷贝(physical copy),三大件(bigthree problem)

    一般的我们喜欢这样对对象赋值: Person p1;Person p2=p1; classT object(another_object), or    A a(b); classT object = ...

  9. C++的拷贝构造函数、operator=运算符重载,深拷贝和浅拷贝、explicit关键字

    原文地址:https://blog.csdn.net/shine_journey/article/details/53081523 1.在C++编码过程中,类的创建十分频繁. 简单的功能,当然不用考虑 ...

随机推荐

  1. distinct关键字对执行计划的影响

    一.前言 最近看到一段话,"count(distinct 列名)若列上有索引,且有非空约束或在where子句中使用is not null,则会选择索引快速全扫描.其余情况则选择全表扫描&qu ...

  2. 分析Jackson的安全漏洞CVE-2019-12086

    CVE-2019-12086 Description A Polymorphic Typing issue was discovered in FasterXML jackson-databind 2 ...

  3. Customize the Application UI and Behavior 自定义应用程序UI和行为

    In XAF, the business model defines the database structure and UI appearance. Changes to your persist ...

  4. ASP.NET Core 设置默认起始页(如default.html)

    测试页面foo.html 在Startup.cs内使用middleware 代码如下: DefaultFilesOptions defaultFilesOptions = new DefaultFil ...

  5. CSS学习笔记-盒子阴影及文字阴影

    盒子阴影:    1.格式:        box-shadow:h-shadow v-shadow blur spread color insert;        box-shadow:水平偏移 ...

  6. 一些有用的Function或Class Method,持续更新

    一些有用的Function 或 Class Method Function/Class Method 类型 作用 模块 备注 co_cost_show_order_costs Func 显示生产订单. ...

  7. SAP MCH1表和MCHA表更新逻辑

    SAP MCH1表和MCHA表更新逻辑 笔者所在的A项目里,批次是在material level 唯一, 意味着不同物料号可以有相同的批次号,只要物料号+批次号组合是唯一的即可. SE11 看MCH1 ...

  8. Java反射04 : 通过Array动态创建和访问Java数组

    java.lang.reflect.Array类提供了通过静态方法来动态创建和访问Java数组的操作. 本文转载自:https://blog.csdn.net/hanchao5272/article/ ...

  9. U盘的几种分类及格式

    u盘常见的几种分类: 1.按u盘材质来分,可以分为金属u盘.塑料u盘.软胶u盘.皮革u盘.木质u盘.珠宝u盘等.这些主要是考虑u盘本身外壳所用材质的. 2.从u盘容量来分,就是8GB.16GB.32G ...

  10. SQL Server 通过游标重新定义单据数据的单据编号

    DECLARE @Index INTSET @Index=100DECLARE UpdateCursor  CURSOR  FOR (SELECT DISTINCT AA.Id FROM dbo.表 ...