深入理解c++构造函数, 复制构造函数和赋值函数重载(operator=)
注
以下代码编译及运行环境均为 Xcode 6.4, LLVM 6.1 with GNU++11 support, Mac OS X 10.10.2
调用时机
看例子
//
// main.cpp
// test
//
// Created by dabao on 15/9/30.
// Copyright (c) 2015年 Peking University. All rights reserved.
// #include <iostream> class Base
{
public:
Base()
{
std::cout<<"constructor"<<std::endl;
} Base(Base ©)
{
std::cout<<"copy constructor"<<std::endl;
} const Base &operator=(Base ©)
{
std::cout<<"operator="<<std::endl;
return *this;
}
}; int main(int argc, const char * argv[])
{ Base a; // 1
Base b = a; // 2
Base c(a); // 3 Base d; // 4
d = a; return 0;
}
输出
constructor
copy constructor
copy constructor
constructor
operator=
1,2,3,4 是我们创建一个变量的最主要的方法(构造序列本文不讨论), 其中1,2,3是变量定义, 4是赋值. 因此很明显:
- 定义会调用构造函数, 赋值会调用赋值函数(operator=)
- 复制构造函数是一种特殊的构造函数, 参数是一个变量实例而已
- 2和3等价, 3不会调用赋值函数(手误) 2不会调用赋值函数, 出现等号未必就是赋值
- 如果没有重载以上函数, 3和4效果会一样, 但会少一次函数调用
const来捣乱
那么const又起到什么作用了呢?
继续来看例子
//
// main.cpp
// test
//
// Created by dabao on 15/9/30.
// Copyright (c) 2015年 Peking University. All rights reserved.
// #include <iostream> class Base
{
public:
Base()
{
std::cout<<"constructor"<<std::endl;
} Base(Base ©)
{
std::cout<<"copy constructor"<<std::endl;
} const Base &operator=(Base ©)
{
std::cout<<"operator="<<std::endl;
return *this;
}
}; Base creator()
{
Base ret;
return ret;
} int main(int argc, const char * argv[])
{ Base a = creator(); // 1 Base b;
b = creator(); // 2 return 0;
}
上述代码都会编译出错, 原因是 "No matching constructor". 看代码不难发现原因, creator函数返回的是Base类型, 在c++11里面, 这个称为右值(rvalue), 但是我们的复制构造函数和赋值函数的参数类型都是非const引用类型, 而右值是不允许做这种类型参数的, 所以就编译出错了. 解决方案有两个:
- 使用const引用类型
- 使用右值类型
如下所示
Base(const Base ©)
{
std::cout<<"copy constructor"<<std::endl;
} const Base &operator=(Base &©)
{
std::cout<<"operator="<<std::endl;
return *this;
}
其中, const引用类型是最通用的作法, 它可以兼容左值和右值, 也兼容古老的编译器, 右值类型则是c++11引进的新特性(使用&&表明), 可以针对左值和右值选择不同的实现, 比如使用std::move替代operator=, 从而减少内存的申请. 因此, 如果没有特殊需要, 使用const引用类型作为复制构造函数与赋值函数的参数类型.
至此, 构造函数的坑基本说完了, 因为不牵扯到返回值和函数类型的问题, 但是赋值函数(operator=)还有更多的坑来理一理.
const继续搅局
在一个类的成员函数中, const可以出现三个地方: 返回值, 参数, 函数.
const A& operator=(const A& a) const
因此一个函数可以有8个变种, 但是c++不允许参数类型相同,返回值类型不同的重载, 因此一个函数最多有4种实现.
我们先考虑返回const类型的情况
//
// main.cpp
// test
//
// Created by dabao on 15/9/30.
// Copyright (c) 2015年 Peking University. All rights reserved.
// #include <iostream> class A
{
public:
const A& operator=(const A& a) const
{
std::cout<<"const A& operator=(const A& a) const ["<<a.x<<" > "<<x<<"]"<<std::endl;
return *this;
} const A& operator=(const A& a)
{
std::cout<<"const A& operator=(const A& a) ["<<a.x<<" > "<<x<<"]"<<std::endl;
return *this;
} const A& operator=(A& a) const
{
std::cout<<"const A& operator=(A& a) const ["<<a.x<<" > "<<x<<"]"<<std::endl;
return *this;
} const A& operator=(A& a)
{
std::cout<<"const A& operator=(A& a) ["<<a.x<<" > "<<x<<"]"<<std::endl;
return *this;
} std::string x; A() : x(""){}
A(std::string x_) : x(x_) {}
}; int main(int argc, const char * argv[])
{ A a("a"), b("b");
const A c("const c"),d("const d"); c = d;
c = b;
a = d;
a = b; return 0;
}
输出结果
const A& operator=(const A& a) const [const d > const c]
const A& operator=(A& a) const [b > const c]
const A& operator=(const A& a) [const d > a]
const A& operator=(A& a) [b > a]
结果很明显, 被赋值变量决定函数, 赋值变量决定参数, a=b 等价于 a.operator(b), 这里没什么问题.
但是, 有一个很奇怪的地方, a=d 这一句, a是非const的, 调用了 const A& operator=(const A& a) [const d > a], 返回值是个const类型, 这怎么可以呢? 返回值的const是什么意思呢? 这是非常有迷惑性的. 这个问题的关键点在于:
a是这个函数的一部分, 并不是返回值的承接者. 因此 a=d 实际上是等价于 const A& ret = a.operator=(d), 也就是说, operator=的返回值类型和被赋值的变量是没有任何关系的!
加入以下代码
const A &m = (a = d); // 1
A &n = (a = d); // 2
2会编译错误, 原因就在于把 const A& 绑定给 A&, 这肯定是错误的. 因此再重复一遍, operator=的返回值和被赋值变量没有任何关系.
那么返回值有什么意义呢? 这就和iostream类似了, 是为了进行串联赋值, 亦即 a=b=c
来看最后的例子
//
// main.cpp
// test
//
// Created by dabao on 15/9/30.
// Copyright (c) 2015年 Peking University. All rights reserved.
// #include <iostream> class A
{
public:
const A& operator=(const A& a) const
{
std::cout<<"const A& operator=(const A& a) const ["<<a.x<<" > "<<x<<"]"<<std::endl;
return *this;
} const A& operator=(const A& a)
{
std::cout<<"const A& operator=(const A& a) ["<<a.x<<" > "<<x<<"]"<<std::endl;
return *this;
} const A& operator=(A& a) const
{
std::cout<<"const A& operator=(A& a) const ["<<a.x<<" > "<<x<<"]"<<std::endl;
return *this;
} const A& operator=(A& a)
{
std::cout<<"const A& operator=(A& a) ["<<a.x<<" > "<<x<<"]"<<std::endl;
return *this;
} std::string x; A() : x(""){}
A(std::string x_) : x(x_) {}
}; int main(int argc, const char * argv[])
{ A a("a"), b("b"); const A c("const c"),d("const d"); (a = b) = c; // 1 (a = c) = b; // 2 a = b = c; // 3 return 0;
}
输出
const A& operator=(A& a) [b > a]
const A& operator=(const A& a) const [const c > a]
const A& operator=(const A& a) [const c > a]
const A& operator=(A& a) const [b > a]
const A& operator=(const A& a) [const c > b]
const A& operator=(const A& a) [b > a]
可以得出如下结论:
- 1和3比较可以发现, 赋值的顺序是从右往左执行的
- 返回值是const类型, 那么再被赋值就会调用const函数了
总结
- 复制构造函数和赋值函数出现在两种不同的场景里, 不是出现等号就会调用赋值函数
- 赋值函数的返回值和被赋值变量是完全独立的
深入理解c++构造函数, 复制构造函数和赋值函数重载(operator=)的更多相关文章
- String的构造函数、析构函数和赋值函数
编写类String的构造函数.析构函数和赋值函数 已知类String的原型为: class String { public: String(const char *str = NULL); // 普通 ...
- 编写类String 的构造函数、析构函数和赋值函数
编写类String 的构造函数.析构函数和赋值函数,已知类String 的原型为:class String{public:String(const char *str = NULL); // 普通构造 ...
- C/C++面试题:编写类String的构造函数、析构函数和赋值函数。
转https://www.cnblogs.com/alinh/p/9636500.html 考点:构造函数.析构函数和赋值函数的编写方法出现频率:☆☆☆☆☆已知类String的原型为: ...
- 编写类String的构造函数、析构函数和赋值函数
已知类String的原型为: class String { public: String(const char *str = NULL); // 普通构造函数 String(const Str ...
- C++构造函数(复制构造函数)、析构函数
注:若类中没有显示的写如下函数,编译会自动生成:默认复制构造函数.默认赋值构造函数(浅拷贝).默认=运算符重载函数(浅拷贝).析构函数: 1.默认构造函数(默认值)构造函数的作用:初始化对象的数据成员 ...
- 编写类String的构造函数、拷贝构造函数、析构函数和赋值函数
一.题目: class String { public: String(const char *str = NULL); // 普通构造函数 String(const String &othe ...
- C++ 拷贝构造函数 copy ctor & 拷贝赋值函数 copy op=
类中含有 指针类型 的成员变量时,就必须要定义 copy ctor 和 copy op= copy ctor 请见: class Rectangle { public: Rectangle(Rec ...
- C++学习基础六——复制构造函数和赋值操作符
1.什么是复制构造函数 复制构造函数:是构造函数,其只有一个参数,参数类型是所属类的类型,且参数是一个const引用. 作用:将本类的成员变量赋值为引用形参的成员变量. 2.什么是赋值操作符 赋值操作 ...
- C++雾中风景6:拷贝构造函数与赋值函数
在进行C++类编写的过程之中,通常会涉及到类的拷贝构造函数与类的赋值函数.初涉类编写的代码,对于两类函数的用法一直是挺让人困惑的内容.这篇文章我们会详细来梳理拷贝构造函数与赋值函数的区别. 1.调用了 ...
随机推荐
- jQuery之ajax的跨域获取数据
如果获取的数据文件存放在远程服务器上(域名不同,也就是跨域获取数据),则需要使用jsonp类型.使用这种类型的话,会创建一个查询字符串参数 callback=? ,这个参数会加在请求的URL后面.服务 ...
- Java interview Advanced
1. Can you override private or static method in Java ? Read more: http://java67.blogspot.com/2012/09 ...
- Intellij_idea-14官方快捷键中文版
编辑类: Ctrl+Space 基本代码实例(类.方法.变量) Ctrl + Shift + Space 智能代码实例(根据需要的类型过滤方法和变量) Ctrl + Shift + Enter 完整的 ...
- [原创][LaTex]LaTex学习笔记之框架及宏包
0. 简介 LaTex在书写文档时的最基本单元就是首部的写作,变相的也可以说是头文件.本文章就来总结一下文档的基本格式和常用宏包. 1. 基本单元 基本单元需要对LaTex语法有一定的了解,这个很简单 ...
- background-size的两个属性:cover和contain
两种都不会造成图片失真,其中: (1)cover:相当于宽度等于元素的宽度,高度设为auto: (2)contain:相当于高度等于元素的高度,宽度设为auto: 例如:设置一个高度和宽度都为300p ...
- 第三十七章 springboot+docker(手动部署)
一.下载centos镜像 docker pull hub.c.163.com/library/centos:latest docker tag containId centos:7 docker ru ...
- SAP 如何查看用户登录信息
1.首先进入事务代码 SM19 配置审计参数文件 2.选择客户端,用户名,并且勾选过滤激活之后点击细节配置,进入如下界面: 配置完成之后,点击保存. 3.并且可以进入SM20界面,选择要查看的客户端 ...
- UIWebView使用时的问题,包含修改user agent
1.①像普通controller那样实现跳转到webview的效果,而不是直接加到当前controller②隐藏webview的某些元素③webview跳往原生app④给webview添加进度条 解决 ...
- 多媒体技术基础之---Come on!来点儿音乐吧
其实要说在Linux系统下播放音乐,确实是一件让人非常抓狂的事情,抛开各种音频格式的商业授权不说,即使提供给你相应的解码库,能玩儿得转的人那又是少之又少.可能有些盆友说ubuntu这方面确实做得不错, ...
- sam9x5 sam-ba
调试参考 http://www.docin.com/p-872951702.html AT91SAM9x5 EK Board SoC Features Kit Information Kit Ove ...