C++类中拷贝构造函数详解

 

a. C++标准中提到“The default constructor, copy constructor and copy assignment operator, and destructor are special member functions.[Note: The implementation will implicitly declare these member functions for some class types when the program does not explicitly declare them. The implementation will implicitly define them if they are used.]”。即缺省构造函数、拷贝构造函数、拷贝赋值操作符和析构函数是特殊成员函数。

b. “Constructors do not have names. A special declarator syntax using an optional sequence of function- specifiers(inline, virtual and explicit) followed by the constructor’s class name followed by a parameter list is used to declare or define the constructor.” 构造函数没有名称。

c. 构造函数不能有返回类型,也不能由virtual, const, static 和 volatile来修饰。但可以由inline来修饰,事实上隐式构造函数就是用inline来修饰的。inline表示编译时展开,通常速度块;virtual表示运行时绑定,通常意味着灵活。

d. 类中存在虚函数或者有虚基类的情况下需要显式声明构造函数。拷贝构造函数也是如此。

f. 构造函数是一种特殊函数,而拷贝构造函数是一种特殊的构造函数。类X的构造函数的第一个参数必须为X&,或者const X&;除了第一个参数外,构造函数要么不存在其他参数,如果存在其他参数,其他参数必须有默认值。一个类可以有多个拷贝构造函数。它的形式如下:

(Lostyears:f. 构造函数是一种特殊函数,而拷贝构造函数是一种特殊的构造函数。类X的拷贝构造函数的第一个参数必须为X&,或者const X&;除了第一个参数外,拷贝构造函数要么不存在其他参数,如果存在其他参数,其他参数必须有默认值。一个类可以有多个拷贝构造函数。它的形式如下:)

X::X(X& x)
X::X(const X& x)
X::X(X& x, int a = 0, int b = 1…)

g. 什么时候会调用拷贝构造函数?
    以下三种情况出现时,会调用一个类的拷贝构造函数:
    1) 用一个已经实例化了的该类对象,去实例化该类的另外一个对象;
    2) 用该类的对象传值的方式作为一个函数的参数;
    3) 一个函数返回值为该类的一个对象。

 

运行下面代码以验证之:

#include <iostream>
using namespace std;

class CA
{
public:
         int a;
         int b;
public:
         inline CA()
         {
                   a = 1;
                   b = 1;
         }

         inline CA(int A, int B)
         {
                   a = A;
                   b = B;
         }

         inline CA(CA& x)
         {
                   a = x.a;
                   b = x.b;
                   cout << "copy constructor is called." << endl;
         }

         void printInfo()
         {
                   cout << "a = " << a << ", b = " << b << endl;
         }
};

int someFun1(CA x)
{
         return x.a + x.b;
}

CA someFun2(int a, int b)
{
         CA ca(a, b);
         return ca;                                     
}

int main(void)
{
         CA a;
         // CA b();                                 // 不能用这种方式声明CA的对象b!
         CA c(10, 10);
         CA d(c);                                   // 情况1) -> 调用拷贝构造函数
         int anInt = someFun1(c);           // 情况2) -> 调用拷贝构造函数
         CA e = someFun2(11, 11);        // 情况3) -> 调用拷贝构造函数
 
         return 0;
}

运行结果:
copy constructor is called.
copy constructor is called.
copy constructor is called.

运行结果表明,上述结论是正确的。

h. 什么时候必须要显式声明拷贝构造函数?
    拷贝构造函数的作用就是用一个已经实例化了的该类对象,去实例化该类的另外一个对象。

1) 下面的代码并没有显式声明一个构造函数,编译器会自动为类CExample1生成一个缺省的隐式拷贝构造函数:
#include <iostream>
using namespace std;

class CExample1
{
private:
         int a;

public:
         CExample1(int b){a = b;}
         void SetValue(int a){this->a = a;}
         void Show(){cout << a << endl;}
};

int main(void)
{
         CExample1 A(100);
         CExample1 B = A;       // 调用了缺省的隐式拷贝构造函数
         CExample1 C(B);         // 调用了缺省的隐式拷贝构造函数

         B.Show();                    // 输出应该是100
         B.SetValue(90);
         B.Show();                    // 输出应该是90
         A.Show();                    // 输出应该是100
         C.Show();                    // 输出应该是100

         return 0;
}

输出为:

100
90
100
100

2) 如果有成员变量以指针形式存在,涉及动态内存分配等情况下,一定要显式声明拷贝构造函数。要注意到,如果需要显式定义拷贝构造函数,那么通常都是需要同时定义析构函数(因为通常涉及了动态内存分配),至于是否必须重载操作符“=”,要视情况而定。

#include <iostream>
using namespace std;

class CSomething
{
public:
         int a;
         int b;

public:
         CSomething(int a, int b)
         {this->a = a;  this->b = b;}
};

class CA
{
private:
         CSomething* sth;              // 以指针形式存在的成员变量

public:
         CA(CSomething* sth){this->sth = new CSomething(sth->a, sth->b);}
         ~CA()
         {
                   cout << "In the destructor of class CA..." << endl;
                   if (NULL != sth) delete sth;
         }
         void Show(){cout << "(" << sth->a << ", " << sth->b << ")" << endl;}
         void setValue(int a, int b){sth->a = a; sth->b = b;}
         void getSthAddress()
         {
                   cout << sth << endl;
         }
};
 
int main(void)
{
         CSomething sth(1, 2);
         CA ca(&sth);
         ca.Show();

         CA cb(ca);                                      // 调用缺省的隐式拷贝构造函数
         cb.Show();
 
         cb.setValue(2, 3);
         ca.Show();
         cb.Show();
 
         ca.getSthAddress();
         cb.getSthAddress();

         return 0;
}
上面的程序没有显式声明拷贝构造函数,运行结果如下:

可见,ca和cb中的指针成员变量sth指向的是同一个内存地址(Console输出的第5、6行),这就是为什么在cb.setValue(2, 3)后,ca对应的内容也发生了改变(Console输出的第3、4行),而这不是我们所期望的;其次,我们生成了两个对象ca和cb,因此对两次调用析构函数,第一次调用析构函数的时候没有问题,因为此时sth里面有内容,第二次调用析构函数时,sth里面的内容由于在第一次调用析构函数的时候已经被delete了,所以会出现如上的错误提示。

保持其他代码不变,现在我们增加一个拷贝构造函数如下:
CA(CA& obj)
{
         sth = new CSomething((obj.sth)->a, (obj.sth)->b);
}
再运行上面的程序,所得到的结果如下:

这次,ca和cb中的指针成员变量sth指向的不是同一个内存地址(Console输出的第5、6行)了,这就是为什么在cb.setValue(2, 3)后,ca对应的内容保持不变,而cb的内容该如愿地改为(2, 3)(Console输出的第3、4行);其次,析构函数也不会报告错误了。

3) 关于拷贝构造函数另外一个完整的例子,其中包含了copy constructor,destructor 和copy assignment operator。
#include <iostream>
using namespace std;

class Point
{
public:
         int _x;
         int _y;
 
public:
         Point();
         Point(int, int);
};
 
Point::Point()
{
         _x = 0;
         _y = 0;
}
 
Point::Point(int x, int y)
{
         _x = x;
         _y = y;
}
 
class CA
{
public:
         Point* _point;
 
public:
         CA(const Point*);
         void setPointValues(int, int);
         void printCoordinates();
 
         // 需要增加的拷贝构造函数
         CA(const CA&);
         // 需要增加的析构函数
         virtual ~CA();
         // 需要增加的拷贝赋值函数
         CA& operator = (const CA&);
};
 
CA::CA(const Point* point)
{
         _point = new Point();                   // 发生了动态内存分配!因此不能缺少析构函数。
         _point->_x = point->_x;
         _point->_y = point->_y;
}
 
// 需要增加的拷贝构造函数的实现
CA::CA(const CA& ca)
{
         _point = new Point();
         _point->_x = (ca._point)->_x;
         _point->_y = (ca._point)->_y;
}
 
// 需要增加的析构函数的实现
CA::~CA()
{
         if(!_point) delete _point;
}
 
// 需要增加的拷贝赋值函数的实现
CA& CA::operator = (const CA& ca)
{
         _point = new Point();
         _point->_x = (ca._point)->_x;
         _point->_y = (ca._point)->_y;
 
         return *this;
}
 
void CA::setPointValues(int x, int y)
{
         _point->_x = x;
         _point->_y = y;
}
 
void CA::printCoordinates()
{
         cout << "Coordinates = (" << _point->_x << ", " << _point->_y << ")" << endl;
}
 
int main(void)
{
         Point apoint(1, 2);
         CA ca(&apoint);
         ca.printCoordinates();
 
         CA cb(ca);                                               // 调用拷贝构造函数
         cb.printCoordinates();
 
         cb.setPointValues(12, 12);
         cb.printCoordinates();
         ca.printCoordinates();
 
         CA cc = cb;                                              // 调用拷贝赋值函数
         cc.printCoordinates();
         cc.setPointValues(13, 13);
       
         ca.printCoordinates();
         cb.printCoordinates();
         cc.printCoordinates();
 
         return 0;
}

 

转:http://patmusing.blog.163.com/blog/static/1358349602009113061024796/

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
移动构造函数是C++11引入的一个新特性,通过移动构造函数,可以将一个对象的资源所有权转移到另一个对象,避免了不必要的资源拷贝,提高了程序的运行效率。 移动构造函数的定义如下: ```cpp class ClassA { public: ClassA(ClassA&& other) noexcept { // 移动构造函数的实现 } }; ``` 其,`ClassA&&`表示移动构造函数的参数为右值引用。移动构造函数通常使用`noexcept`关键字进行修饰,表示该函数不会抛出异常。 移动构造函数的实现需要将另一个对象的资源转移到当前对象,并将另一个对象的资源置为无效。常见的实现方式是通过移动构造函数的`std::move()`函数来完成。例如,对于一个包含动态分配内存的类: ```cpp class MyClass { public: MyClass(int n) : data(new int[n]), size(n) {} MyClass(MyClass&& other) noexcept : data(other.data), size(other.size) { other.data = nullptr; other.size = 0; } ~MyClass() { delete[] data; } private: int* data; int size; }; ``` 在移动构造函数,将另一个对象的指针和大小赋值给当前对象,并将另一个对象的指针置为nullptr,大小置为0,表示另一个对象的资源已经被移动到了当前对象。 使用移动构造函数可以大大提高程序的性能,特别是在涉及到大量的资源拷贝时。移动构造函数可以通过`std::move()`函数进行调用,例如: ```cpp MyClass a(10); MyClass b(std::move(a)); // 调用移动构造函数 ``` 在这个例子,对象a的资源被移动到了对象b,对象a的资源已经无效了。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值