C++ 类的复制控制
写了又删,删了又写,才发现这一章节不好描述。
那就假定个前提吧,假定已经知道:
① C++的类有构造函数。 ② 如果不提供任何构造函数,那编译器会生成默认的无参构造函数--默认构造函数只会进行成员变量的初始化。 ③ 如果提供了任何一个构造函数,那编译器就不会再生成默认的无参构造函数。 ④ 函数的形参都是实参的副本(引用类型除外)。 ⑤ 构造函数也是函数。 ⑥ 直接初始化是在括号()中,复制初始化则使用=赋值操作符---注意,是定义,不是赋值。 ⑦ 针对⑥,Person p2=p1;这是复制初始化! p2=p3;这才是赋值!!!
在此基础上,稍作推理:
一、如果构造函数是单形参、且形参类型为该类的类型的构造函数:
以 class Person 为例,
string name="anonymous";
int age=;
Person p1(name, age);
Person p2(p1); //note this
根据上面前提的④,p2 需要 p1 的一个副本(复制一个),这时就出现问题了:该怎么复制?
这就是复制构造函数的作用了--用于指明如何复制。
再推理一下,如果不能使用副本,那还能使用什么?必须是引用啊(不要说指针,囧)!所以复制构造函数就是单形参、且形参类型为该类类型的引用 (常const修饰)的构造函数。
class Person{
public:
Person(const Person&); //copy-constructor
....
};
需要说明的是,编译器会自动生成一个默认的复制构造函数,原则就是一一复制成员变量。这里面又存在一个问题,如果存在指针类型的成员变量,那么与其他成员变量不同的是,复制后的指针仍然指向同一个地址!这可能会导致很严重的bug。如下:
#include <iostream>
#include <string> using namespace std; //注意,有指针成员,但没有自定义复制构造函数。
//这时,其他成员复制时都是副本,而指针成员却指向同一个地址。
class HasPtr{
private:
int *ptr;
int val; public:
HasPtr(int *p, int i):ptr(p), val(i){} int *get_ptr() const { return ptr; }
void set_ptr(int *p){ ptr=p; } int get_int() const { return val; }
void set_int(int i){ val=i; } int get_ptr_val() const { return *ptr; }
void set_ptr_val(int i) const { *ptr = i; } //why not take a ref?
}; int main(){
int obj=; HasPtr ptr1(&obj, );
HasPtr ptr2(ptr1); // copy-constructor cout<<ptr1.get_ptr()<<"--"<<ptr1.get_ptr_val()<<endl;
cout<<ptr2.get_ptr()<<"--"<<ptr2.get_ptr_val()<<endl; ptr1.set_int();
ptr2.set_int(); cout<<ptr1.get_int()<<"--"<<ptr2.get_int()<<endl; ptr1.set_ptr_val();
cout<<ptr1.get_ptr()<<"--"<<ptr1.get_ptr_val()<<endl;
cout<<ptr2.get_ptr()<<"--"<<ptr2.get_ptr_val()<<endl; cout<<"-----------"<<endl; int *ip=new int();
HasPtr ptr(ip, );
delete ip;
ptr.set_ptr_val();//OOPS! error return ;
}
上面代码充分说明了有指针类型的成员变量时,类的对象的行为变得诡异。
此外,还有一个小知识点,虽然数组不能支持复制,但是复制构造函数中却可以一一复制数组的元素,从而做到复制数组。
推理一下,如果将复制构造函数设为private或者explicit会怎样?
二、复制初始化和赋值操作不是同一个概念!
上面这个前提必须搞清楚,因为二者是不同的行为!
Person p1("Henry", );
Person p2 = p1; //copy-constructor 复制初始化
Person p3("Jean", 22);
p3 = p2; // OK, it's assignment!!!
上面的代码,其执行过程分别是:调用构造函数创建p1;调用复制构造函数创建p2;调用构造函数创建p3;调用赋值操作符函数,给p3重新赋值!!!
这里还需要另外一个前提:“赋值”和“=”不是一回事!
对于C++来说,所有的操作符都是定义出来的,就是说,具体的行为是被定义好的!
同理,对类来说,这些操作符(+-*/等)也是被定义出来的。--定义操作符的函数叫做操作符函数,这个过程叫做重载操作符。
例如:
int val = ;
int val2; // 默认初始化或者声明,看所在作用域 val2 = val; // 赋值操作
事实上,你完全可以定义自己的操作符函数,例如混乱一点的,将+作为赋值操作~ 只是没有必要罢了。
这里不再介绍重载操作符了,有兴趣的新人可以查一下相关的资料。
下面重点说一下赋值操作符。
编译器也会生成默认的赋值操作符,默认执行一一赋值,并返回对左操作数的引用。
一般来说,可以使用默认复制构造函数的类,也可以使用默认赋值操作符。
对于类类型对象来说,直接初始化直接调用相应的构造函数;而复制初始化则先调用相应构造函数构造一个临时对象,再调用复制构造函数将临时对象复制到正在创建的对象。
如下:
string s1="abc"; //先是string tmp(const char*); 再s1(&tmp);
string s2=string(); //string()已经是string对象,所以直接调用s2(&string())
注意上面的代码,tmp 是临时对象,s1(&tmp) 则是调用复制构造函数!
三、所有对象都是有生命周期的,一般生命周期仅限于其作用域范围内---手动释放的除外。
C++能够指定当一个对象走向死亡的时候所进行的操作,这就是析构函数。--这个名字如果看英文就知道,和构造函数是相反的,constructor - destructor,一个创建,一个拆毁。
所以,顾名思义,析构函数应该是定义了如何销毁成员变量,虽然还可以定义其他的操作(如释放资源),但那不是我们关注的重点。
编译器总是会生成一个默认的析构函数,以成员变量声明顺序的逆序进行销毁,销毁每一个非static成员。
且,总是最后调用默认的析构函数。哪怕已经调用过自定义的析构函数!
需要注意的是,对于类的对象来说,①当超出作用域时,系统会自动调用析构函数。②当主动释放内存时,也会自动调用析构函数。
这点对于Javaer来说就是一个坑,极容易被带入坑中。例如 :
Person *p=new Person; // no ()
{
Person p2(*p);
delete p;
}
上面,超出花括号时,系统就会自动调用p2的析构函数,销毁p2! -- 注意,引用和指针不会。
另外,撤销一个容器(包括数组)时,也会一一调用其元素的析构函数,从最后一个开始撤销。
默认的析构函数并不会删除指针类型成员所指向的对象!
一个规则:如果类需要析构函数,则它也需要赋值操作符和复制构造函数!
C++ 类的复制控制的更多相关文章
- C++ 复制控制之复制构造函数
7月26日更新: 过了这么长的时间回过头来看,发现文章中有几个点说错(用红字标出): 构造函数不是只有唯一一个参数,它也可以是多参数形式,其第二参数及后继以一个默认值供应. 不是没有声明复制控制函数时 ...
- C++复制控制
1.复制构造函数可用于: (1)根据另一个同类型的对象显示或隐式初始化一个对象 string str1="test"; //隐式 string str2=str1; //显示 ...
- C++的那些事:类的拷贝控制
1,什么是类的拷贝控制 当我们定义一个类的时候,为了让我们定义的类类型像内置类型(char,int,double等)一样好用,我们通常需要考下面几件事: Q1:用这个类的对象去初始化另一个同类型的对象 ...
- C++ Primer 学习笔记_67_面向对象编程 --转换与继承、复制控制与继承
面向对象编程 --转换与继承.复制控制与继承 I.转换与继承 引言: 由于每一个派生类对象都包括一个基类部分,因此能够像使用基类对象一样在派生类对象上执行操作. 对于指针/引用,能够将派生类对象的指针 ...
- C++ Primer 随笔 Chapter 13 复制控制
1.复制控制包含的内容:复制构造函数.赋值操作符.析构函数 2.复制构造函数: a. 定义:只有单个形参,而且该形参是对本类类型的引用,这样的构造函数被成为复制构造函数 b. 适用情况: (1)根据一 ...
- C++Primer笔记之复制控制
复制控制这一节需要注意的地方不多,主要有以下几点: 1.定义自己的复制构造函数 什么时候需要定义自己的复制构造函数,而不用系统提供的,主要遵循以下的经验说明: 某些类必须对复制对象时发生的事情加以控制 ...
- 稍微深入点理解C++复制控制【转】
通过一个实例稍微深入理解C++复制控制过程,参考资料<C++ primer>,介绍点基本知识: 1.在C++中类通过特殊的成员函数:复制构造函数.赋值操作符和析构函数来控制复制.赋值和撤销 ...
- C++拾遗(六)——复制控制
年前忙了几天,到现在才算是有空休息下来.先祝大家新年快乐,心想事成:)我也会发笑脸o.o 这篇博文主要介绍定义一个类型的对象时的复制控制方式,这部分内容之前有一定的了解但又浅尝辄止,始终感觉没能找到要 ...
- C++继承与构造函数、复制控制
每个派生类对象由派生类中定义的(非static)成员加上一个或多个基类子对象构成,因此,当构造.复制.赋值和撤销派生类型对象时,也会构造.复制.赋值和撤销这些基类子对象. 构造函数和复制控制成员不能继 ...
随机推荐
- 推荐系统学习07-Waffles
介绍 Waffles 英文原意是蜂蜜甜饼,在这里却指代一个很强大的机器学习的开源工具包. Waffles里包括的算法特别多.涉及机器学习的方方面面,推荐系统位于当中的Waffles_recommend ...
- /usr/local/ssl/lib/libssl.a: error adding symbols: Bad value
一.背景 编译第三方库A的时候提示依赖openssl库. 二.编译openssl库 去官网下载最新版本的openssl-1.0.2l.tar.gztar -zxvf openssl-1.0.2l.ta ...
- Java调用Linux命令(cd的处理)
一.Java调用Linux系统的命令非常简单 这是一个非常常用的调用方法示例: public String executeLinuxCmd(String cmd) { System.out.print ...
- 最常用的五类CSS选择器
一些新手朋友对选择器一知半解,不知道在什么情况下运用什么样的选择器,这是一个比较头疼的问题,针对新手朋友,对CSS选择器作一些简单的说明,希望能对大家的学习工作有一定的帮助,更多的CSS知识请参考We ...
- python 中 with 用法
前言 with 语句适用于对资源进行访问的场合,确保不管使用过程中是否发生异常都会执行必要的“清理”操作,释放资源, 比如文件使用后自动关闭/线程中锁的自动获取和释放等. 问题引出 如下代码: fil ...
- MySQL 数据库 分页查询
在使用MySQL 进行数据库分页查询的时候最主要是使用LIMIT子句进行查询: 首先来看一下LIMIT: LIMIT子句可以用来限制由SELECT语句返回过来的数据数量,它有一个或两个参数,如果给出两 ...
- Nginx(三):日志文件管理
一.Nginx日志描述 通过访问日志,你可以得到用户地域来源.跳转来源.使用终端.某个URL访问量等相关信息: 通过错误日志,你可以得到系统某个服务或server的性能瓶颈等.因此,将日志好好利用,你 ...
- ny104 最大和
最大和 时间限制:1000 ms | 内存限制:65535 KB 难度:5 描述 给定一个由整数组成二维矩阵(r*c),现在需要找出它的一个子矩阵,使得这个子矩阵内的所有元素之和最大,并把这个子矩 ...
- singer页面点击歌手singer是跳转到singer-detail的设置
1.创建components/singer-detail/singer-detail.vue 2.配置动态路由: { path: ':id', name:'singer-detail', compon ...
- asp.net mvc 5 在没有外网win2008R2服务器部署方法
我在本地用最新的.net 4.5和asp.net mvc 5框架做了一个小应用.本地都测试打包成功. 现在要放到服务器上,这个应用只是内网用.服务器不允许连接外网.看到www.asp.net 没有mv ...