C++自学笔记_复制构造函数_《C++ Primer》

在内置数据类型中,一般可以用一个变量初始化另一个变量。同样,对于类类型的对象,也可以用一个对象初始化另一个对象,编译器会合成一个复制构造函数。

#include <iostream>

using namespace std;

class Point{
public:
    Point(int x=0,int y=0):xPos(x),yPos(y){}
    void printPoint(){
        cout<<"xPos:"<<xPos<<endl;
        cout<<"yPos:"<<yPos<<endl;
    }
private:
    int xPos;
    int yPos;
};

int main()
{
    Point M(10,20);
    Point N=M;
    N.printPoint();   //使用对象M初始化对象N
    return 0;
}

编译运行的结果:

xPos:10
yPos:20

Process returned 0 (0x0)   execution time : 0.191 s
Press any key to continue.
语句 Point N = M; 也可以写成 Point N(M); 的形式。在执行该句时就相当于将 M 中每个数据成员的值赋值到对象 N 中相对应的成员数据中。当然, 这只是表面现象, 实际上系统调用了一个复制构造函数来完成这部分的动作, 当类中没有显式定义该复制构造函数时, 编译器会默认为其生成一个默认复制构造函数, 也称拷贝构造函数, 该函数的原型如下:
Point::Point( const Point& );

也可以把复制构造函数看成一个普通的构造函数,只不过是函数的形参不同罢了。其复制构造函数的形参为本类型对象的引用类型。

默认复制构造函数的不足:

有些情况必须显示的去定义复制默认构造函数,举个例子:

#include <iostream>
#include <cstring>

using namespace std;

class Book{
public:
    Book(const char *name){
        bookName=new char[strlen(name)+1];  //使用new申请strlen(name)+1大小的空间
        strcpy(bookName,name);
    }
    ~Book(){
        delete []bookName;   //释放申请的空间
    }
    void showName(){
        cout<<"bookName="<<bookName<<endl;
    }
private:
    char *bookName;
};

int main()
{
    Book CPP("C++ Primer");
    Book T=CPP;   //使用CPP初始化T
    CPP.showName();
    CPP.~Book();  //手动删除CPP对象所占的内存空间
    T.showName();
    return 0;
}

编译运行后的结果:

bookName=C++ Primer
bookName=(U

Process returned 0 (0x0)   execution time : 0.317 s
Press any key to continue.
按照前面的思路, 使用 CPP 对象对 T 对象进行初始化后, 那么 T 对象的 bookName 属性理论上来说也是 "C++ Primer", 但是从输出结果来看在输出 CPP 对象的 bookName 属性时是正常的, 而 T 对象的 bookName 输出有问题, 正确的情况下应该也是 "C++ Primer", 不过此时输出的却是乱码。

这正是默认复制构造函数的不足之处:还原下编译器生成的默认复制构造函数的实现:
Book::Book(const Book& obj){
    bookName=obj.bookName;
}
可以看到, 实际上当用 CPP 对象来初始化 T 对象时, 默认复制构造函数只是简单的将 CPP 对象的 bookName 赋值给 T 对象的 bookName, 换句话说, 也就是只是将 CPP 对象的 bookName 指向的空间地址赋值给 T 的 bookName, 这样一来, T 对象的 bookName 和 CPP 对象的 bookName 就是指向同一处内存单元, 当 CPP 对象调用析构函数后, CPP 的 bookName 所指向的内存单元就会被释放, 由于 T 对象 bookName 与 CPP 对象的 bookName 指向的是同一处内存, 所以此时 T 对象的 bookName 指向的内存就变成了一处不可用的非法内容(因为已经释放), 所以在指向的内存被释放的情况下进行输出势必会造成了输出的错误。    一般来说, 当类中含有指针型的数据成员、需要使用动态内存的, 最好手动显式定义复制构造函数来避免该问题。

显示定义复制默认构造函数
#include <iostream>
#include <cstring>

using namespace std;

class Book{
public:
    Book(const char *name){
        bookName=new char[strlen(name)+1];  //使用new申请strlen(name)+1大小的空间
        strcpy(bookName,name);
    }
    Book(const Book& obj){
        bookName=new char[strlen(obj.bookName)+1];
        strcpy(bookName,obj.bookName);
    }
    ~Book(){
        delete []bookName;   //释放申请的空间
    }
    void showName(){
        cout<<"bookName="<<bookName<<endl;
    }
private:
    char *bookName;
};

int main()
{
    Book CPP("C++ Primer");
    Book T=CPP;   //使用CPP初始化T
    CPP.showName();
    CPP.~Book();  //手动删除CPP对象所占的内存空间
    T.showName();
    return 0;
}

编译运行结果:

bookName=C++ Primer
bookName=C++ Primer

Process returned 0 (0x0)   execution time : 0.315 s
Press any key to continue.
在该示例中我们显式定义了复制构造函数来代替默认复制构造函数, 在该复制构造函数的函数体内, 不是再直接将源对象所申请空间的地址赋值给被初始化的对象, 而是自己独立申请一处内存后再将源对象的属性复制过来, 此时 CPP 对象的 bookName 与 T 对象的 bookName 就是指向两处不同的内存单元, 这样即便是源对象 CPP 被销毁后被初始化的对象 T 也不会再受到影响。
 
 
时间: 2024-11-01 04:44:48

C++自学笔记_复制构造函数_《C++ Primer》的相关文章

C++ Primer笔记9_构造函数_拷贝构造(深拷贝与浅拷贝)

1.构造函数: >构造函数是一个特殊的.与类同名的成员函数,用于给每一个成员设置适当的初始值. >构造函数不能有返回值,函数名与类名同样. >缺省构造函数时,系统将自己主动调用该缺省构造函数初始化对象,缺省构造函数会将全部数据成员都初始化为零或       空.缺省构造函数是不带參数的构造函数. >创建一个对象时,系统自己主动调用构造函数. 构造函数的特点: 1.构造函数能够重载,传入什么实參决定调用不同版本号的构造函数. 2.构造函数不能声明为const .也不能声明为virtu

C++自学笔记_重载运算符_《C++ Primer》

#include <iostream> #include <stdexcept> using namespace std; class CheckedPtr{ public: CheckedPtr(int *b,int *e,int *c): beg(b),end(e),curr(c){ } CheckedPtr(const CheckedPtr &obj): //复制构造函数 beg(obj.beg),end(obj.end),curr(obj.curr){ } Chec

C++自学笔记_定义智能指针类_《C++ Primer》

包含指针的类要特别注意复制控制,原因是复制指针只复制指针中的地址,而不会复制指针所指向的对象. C++类采用以下3种方法之一管理指针成员: (1) 指针成员采取常规指针型行为.这样的类具有指针所有的缺陷但是无需特殊的复制控制. (2) 类可以是实现“智能指针”行为.指针所指向的对象是共享的,但类能够防止悬垂指针. (3) 类采取值型行为.指针所指向的对象是唯一的,由每个类对象单独管理. 这里总结第(2)种方法——采用定义智能指针类 智能指针类的思想在于: 第(1)种方法中,所有的对象中的指针都直

C++ Primer 学习笔记_54_类与数据抽象 --复制构造函数、赋值操作符

复制控制 --复制构造函数.赋值操作符 引言: 当定义一个新类型时,需要显式或隐式地指定复制.赋值和撤销该类型的对象时会发生什么– 复制构造函数.赋值操作符和析构函数的作用!      复制构造函数:具有单个形参,该形参(常用const修饰)是对该类类型的引用.当定义一个新对象并用一个同类型的对象对它进行初始化时,将显式的使用复制构造函数:当将该类型的对象传递给函数或者从函数返回该类型的对象时,将隐式使用复制构造函数.     析构函数:作为构造函数的互补,当对象超出作用域或动态分配的对象被删除

C++ Primer笔记8_动态内存_智能指针

1.动态内存 C++中,动态内存管理是通过一对运算符完成的:new和delete.C语言中通过malloc与free函数来实现先动态内存的分配与释放.C++中new与delete的实现其实会调用malloc与free. new分配: 分配变量空间: int *a = new int; // 不初始化 int *b = new int(10); //初始化为10 string *str = new string(10, ); 分配数组空间: int *arr = new int[10];//分配的

[terry笔记]GoldenGate_迁移同步_主库零停机

ogg根据scn同步数据,源库零停机时间 本次实验与上次的区别:更加注重细节,几乎包含所有步骤,把我越到的坑都作出了说明.并且同步是由10g向11g进行同步,更加符合升级迁移需求. 如下是主要步骤: 1. 配置好ogg源端的mgr.抓取和传送进程,并启动. 2. 配置好ogg目标端的mgr.复制进程,仅启动mgr. 3. 源端可自由进行交易,此时观察源与目标的trail文件是否都正常. 4. 查询源端此时的scn,并按照参数flashbask_scn进行expdp. 5. 目标端impdp导入.

C++ Primer笔记3_默认实参_类初探_名字查找与类的作用域

1.默认函数实参 在C++中,可以为参数指定默认值,C语言是不支持默认参数的,Java也不支持! 默认参数的语法与使用: (1)在函数声明或定义时,直接对参数赋值.这就是默认参数: (2)在函数调用时,省略部分或全部参数.这时可以用默认参数来代替. 注意事项: (1)函数默认值只能赋值一次,或者是在声明中,或者是在定义中,都可以. (2)默认参数定义的顺序为自右到左.即如果一个参数设定了缺省值时,其右边的参数都要有缺省值.比如int f(int a, int b=1,int c=2,int d=

C++ Primer笔记12_运算符重载_递增递减运算符_成员访问运算符

1.递增递减运算符 C++语言并不要求递增递减运算符必须是类的成员.但是因为他们改变的正好是所操作对象的状态,所以建议设定为成员函数. 对于递增与递减运算符来说,有前置与后置两个版本,因此,我们应该为类定义两个版本的递增与递减运算符. 问题来了,程序是如何区分前置和后置呢?因为都是++和-- 为了解决这个问题,后置版本的递增递减运算符接受一个额外的(不被使用)int类型的形参.当我们使用后置运算符时,编译器为这个形参提供一个值为0的实参.这个形参唯一的作用就是区分前置和后置运算符函数. 因为不会

C++ Primer笔记13_运算符重载_总结

总结: 1.不能重载的运算符: . 和 .* 和 ?: 和 ::  和 sizeof 和 typeid 2.重载运算符有两种基本选择: 类的成员函数或者友元函数, 建议规则如下: 运算符 建议使用 所有一元运算符 成员函数 = () [] -> 必须是成员函数 += -= /= *= ^= &= != %= >>= <<= , 似乎带等号的都在这里了. 成员函数 所有其它二元运算符, 例如: –,+,*,/ 友元函数 3.前几篇中的实例,现在汇总Person类的程序: