首页 > 编程知识 正文

什么叫复制构造函数,拷贝构造函数和构造函数

时间:2023-05-06 13:52:29 阅读:157778 作者:4542

许多C的初学者可能知道什么是构造函数,但还不习惯复制构造函数。 对我来说,写代码时可以复制构造函数的机会并不多。 但是,这并不表示复制构造函数没有任何作用。 其实复制构造函数可以解决我们容易忽略的问题。 为了说明复制构造函数的作用,介绍了在编程中遇到的几个问题。 关于c的函数,我们应该很了解。 因为平时经常使用; 对于类的对象,我们也很了解。 因为我们也经常写各种类,使用各种各样的对象; 关于指针的操作,我们也不知道吧? 嗯,如果你还不知道上面三个概念的话,我觉得这篇文章不太适合你,但是看了也没关系^_^。 虽然我们经常使用函数将各种参数传递给函数,但很少会将对象(请注意,它是对象,而不是对象的指针或对对象的引用)作为参数传递给函数。 此外,此对象的构造函数还涉及内存分配操作。 嗯,这样有什么问题呢? 有三种方法可以将参数传递给函数:传递值、传递地址和传递引用。 前者与后者的区别在于,使用传递时,会在函数中生成传递参数的副本。 该复制的内容按位从原始参数复制,两者的内容相同。 如果原始参数是类的对象,则还会生成对象的副本,但请注意。 通常,在生成对象时会触发构造函数的执行,但在生成对象的副本时则不会。 在这种情况下,将执行对象的复制构造函数。 为什么会变成这样? 嗯,典型构造函数完成成员属性的初始化工作。 在将对象传递给某个函数之前,对象的某些属性可能已更改。 如果在生成对象的副本时执行对象的构造函数,则对象的属性将恢复为其原始状态。 这不是我们想要的。 因此,生成对象的副本时,不会执行构造函数,而是执行默认构造函数。 当函数执行完毕并返回时,对象副本将执行析构函数。 如果析构函数为空,则不会出现任何问题,但典型的析构函数会完成清理任务,例如释放指针指向的内存空间。 在这个时候有可能发生问题。 如果在构造函数中将内存分配给指针变量,并在构造函数中释放了指针指向的内存空间,则在将对象传递给函数并返回函数之前的过程中会发生什么? 首先产生了某个对象的副本。 该副本也有一个指针,它指向与原始对象的指针相同的内存空间。 返回函数时,执行了对象的析构函数。 这意味着释放了对象副本中指针指向的内存空间。 但是,这个内存空间对原始对象还是有用的。 对程序本身来说,这是一个严重的错误。 但是,当错误还没有结束,原始对象也被废弃时,析构函数再次执行,两次释放动态分配给同一系统的内存空间是未知的操作,会发生重大错误。 以上是我们面临的问题。 解决问题的方法是什么呢? 首先考虑的是不按价格传递参数。 可以使用地址传递和引用传递。 没错,这确实可以避免上述情况。 另外,如果允许的话,传递地址或传递引用是最好的方法。 但是,这并不适合所有情况,您可能不希望函数中的某些操作影响函数外部的变量。 我该怎么办? 要解决此问题,请使用复制构造函数。 在生成对象的副本时执行复制构造函数,并且可以定义自己的复制构造函数。

在构造函数中申请新的内存空间,以保存构造函数中指针指向的内容。 这样执行对象副本的析构函数时,释放的是复制构造函数中申请的存储器空间。 除了将对象传递给函数时存在上述问题外,函数返回对象时可能会生成与对象副本性质相同的临时对象。 复制构造函数通常称为x(x ),是一种从编译器调用的特殊构造函数,用于基于同一类中的其他对象初始化构件。 唯一的参数(对象的引用)是不变的(因为它是const类型)。 此函数常用于在函数调用期间传递或返回用户定义类型的值。 复制构造函数调用基类的复制构造函数和成员函数。 如果可能的话,可以用常数调用。 另外,也可以用非常量调用。

在c中,如果需要复制以下三个对象: 因此,将调用复制构造函数。

1 ) .某个对象通过值传递给函数主体

2 ) .对象通过值从函数返回

3 ) .需要用另一个对象初始化一个对象

在以上情况下,需要调用复制构造函数。 如果在前两种情况下不使用复制构造函数,则会出现指向已删除内存区域的指针。 在第三种情况下,初始化和赋值的不同含义是构造函数调用的原因。 实际上,复制构造函数是通过普通构造函数和赋值操作实现的。 有很多说明复制构造函数和赋值运算符之间差异的参考资料。

无法更改复制构造函数引用的对象。 这是因为当对象传递函数以传递值时,将自动调用复制构造函数来生成函数中的对象。 如果对象传递到其自己的复制构造函数,则调用该复制构造函数来复制对象。 这样复制可以传递给自己的复制构造函数,从而产生无限循环。

当对象传递给函数时,将隐式调用复制构造函数;当对象返回给函数时,也将类似地调用复制构造函数。 这意味着函数只返回对象的副本。 但是同样,正确调用了复制构造函数。 没必要担心。

如果没有在类中明确声明复制构造函数,编译器将创建一个用于在对象之间进行位复制(bitwise copy )的函数。 此隐式复制构造函数很容易与所有类成员相关联

。许多作者都会提及这个默认的拷贝构造函数。注意到这个隐式的拷贝构造函数和显式声明的拷贝构造函数的不同在于对于成员的关联方式。显式声明的拷贝构造函数关联的只是被实例化的类成员的缺省构造函数除非另外一个构造函数在类初始化或者在构造列表的时候被调用。
拷贝构造函数是程序更加有效率,因为它不用再构造一个对象的时候改变构造函数的参数列表。设计拷贝构造函数是一个良好的风格,即使是编译系统提供的帮助你申请内存默认拷贝构造函数。事实上,默认拷贝构造函数可以应付许多情况。 附另外一篇关于复制构造函数的文章:

对一个简单变量的初始化方法是用一个常量或变量初始化另一个变量,例如:
  int m = 80;
  int n = m;
  我们已经会用构造函数初始化对象,那么我们能不能象简单变量的初始化一样,直接用一个对象来初始化另一个对象呢?答案是肯定的。我们以前面定义的Point类为例:
  Point pt1(15, 25);
  Point pt2 = pt1;
后一个语句也可以写成:
  Point pt2( pt1);
它是用pt1初始化pt2,此时,pt2各个成员的值与pt1各个成员的值相同,也就是说,pt1各个成员的值被复制到pt2相应的成员当中。在这个初始化过程当中,实际上调用了一个复制构造函数。当我们没有显式定义一个复制构造函数时,编译器会隐式定义一个缺省的复制构造函数,它是一个内联的、公有的成员,它具有下面的原型形式:
  Point:: Point (const Point &);
可见,复制构造函数与构造函数的不同之处在于形参,前者的形参是Point对象的引用,其功能是将一个对象的每一个成员复制到另一个对象对应的成员当中。
  虽然没有必要,我们也可以为Point类显式定义一个复制构造函数:
  Point:: Point (const Point &pt)
  {
   xVal=pt. xVal;
   yVal=pt. yVal;
  }
  如果一个类中有指针成员,使用缺省的复制构造函数初始化对象就会出现问题。为了说明存在的问题,我们假定对象A与对象B是相同的类,有一个指针成员,指向对象C。当用对象B初始化对象A时,缺省的复制构造函数将B中每一个成员的值复制到A的对应的成员当中,但并没有复制对象C。也就是说,对象A和对象B中的指针成员均指向对象C,实际上,我们希望对象C也被复制,得到C的对象副本D。否则,当对象A和B销毁时,会对对象C的内存区重复释放,而导致错误。为了使对象C也被复制,就必须显式定义复制构造函数。下面我们以string类为例说明,如何定义这个复制构造函数。

例10-11 class String
{
 public:
  String(); //构造函数
  String(const String &s); //复制构造函数
  ~String(); //析构函数

  // 接口函数
  void set(char const *data);
  char const *get(void);

 private:
  char *str; //数据成员ptr指向分配的字符串
};

String ::String(const String &s)
{
 str = new char[strlen(s.str) + 1];
 strcpy(str, s.str);
}

我们也常用无名对象初始化另一个对象,例如:
  Point pt = Point(10, 20);
  类名直接调用构造函数就生成了一个无名对象,上式用左边的无名对象初始化右边的pt对象。
  构造函数被调用通常发生在以下三种情况,第一种情况就是我们上面看到的:用一个对象初始化另一个对象时;第二种情况是当对象作函数参数,实参传给形参时;第三种情况是程序运行过程中创建其它临时对象时。下面我们再举一个例子,就第二种情况和第三种情况进行说明:
  Point foo(Point pt)
  {
   …
   return pt;
  }
  void main()
  {
   Point pt1 = Point(10, 20);
   Point pt2;
   …
   pt2=foo(pt);
   …
  }
  在main函数中调用foo函数时,实参pt传给形参pt,将实参pt复制给形参pt,要调用复制构造函数,当函数foo返回时,要创建一个pt的临时对象,此时也要调用复制构造函数。

缺省的复制构造函数
  在类的定义中,如果没有显式定义复制构造函数,C++编译器会自动地定义一个缺省的复制构造函数。下面是使用复制构造函数的一个例子:

例10-12 #include <iostream.h>
#include <string.h>
class withCC
{
 public:
 withCC(){}
 withCC(const withCC&)
 {
  cout<<"withCC(withCC&)"<<endl;
 }
};

class woCC
{
 enum{bsz = 100};
 char buf[bsz];
public:
 woCC(const char* msg = 0)
 {
  memset(buf, 0, bsz);
  if(msg) strncpy(buf, msg, bsz);
 }
 void print(const char* msg = 0)const
 {
  if(msg) cout<<msg<<":";
  cout<<buf<<endl;
 }
};

class composite
{
 withCC WITHCC;
 woCC WOCC;
public:
 composite() : WOCC("composite()"){}
 void print(const char* msg = 0)
 {
  WOCC.print(msg);
 }
};

void main()
{
 composite c;
 c.print("contents of c");
 cout<<"calling composite copy-constructor"<<endl;
 composite c2 = c;
 c2.print("contents of c2");
}

  类withCC有一个复制构造函数,类woCC和类composite都没有显式定义复制构造函数。如果在类中没有显式定义复制构造函数,则编译器将自动地创建一个缺省的构造函数。不过在这种情况下,这个构造函数什么也不作。
  类composite既含有withCC类的成员对象又含有woCC类的成员对象,它使用无参的构造函数创建withCC类的对象WITHCC(注意内嵌的对象WOCC的初始化方法)。
  在main()函数中,语句:
  composite c2 = c;
通过对象C初始化对象c2,缺省的复制构造函数被调用。
  最好的方法是创建自己的复制构造函数而不要指望编译器创建,这样就能保证程序在我们自己的控制之下。

版权声明:该文观点仅代表作者本人。处理文章:请发送邮件至 三1五14八八95#扣扣.com 举报,一经查实,本站将立刻删除。