前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >C++类自动提供的特殊成员函数

C++类自动提供的特殊成员函数

原创
作者头像
Alan_1
发布2023-04-30 12:53:17
6980
发布2023-04-30 12:53:17
举报
文章被收录于专栏:Alan的blog
  1. 默认构造函数:
  • 如果未提供任何构造函数,c++会自动生成默认构造i函数。创建对象时会调用。

默认样式:

代码语言:javascript
复制
  className()
   {
   }//初始化值随机
  • 若想创建对象时不显式的对它进行初始化,则必须显式的定义默认构造函数:

//例如:

代码语言:javascript
复制
   Klunk::Klunk()
   {
   klunk_ct=0;//可以用其设置特定的值
   ...
   }
  • 带参的构造函数也可以是默认构造函数,只要所有参数都有默认值:
代码语言:txt
复制
 ```c++
代码语言:txt
复制
 Klunk(int n=0)
代码语言:txt
复制
 {
代码语言:txt
复制
     klunk_ct=n;
代码语言:txt
复制
 }
代码语言:txt
复制
 ```
代码语言:txt
复制
 - 只能有一个默认构造函数,否则会引发二义性
例子:
代码语言:javascript
复制
   String::String()
   {
   len=0;
   str=new char[l];//与下面析构函数相匹配
   str[0]='\0';//default string
   }
  • 析构函数中包含如下代码:
代码语言:javascript
复制
   delete [] str;
delete[]与使⽤new[]初始化的指针和空指针都兼容。
   String::String()
   {
   len=0;
   //str=new char[l];//与下面析构函数相匹配
   //str[0]='\0';
   //更改为
   str=0;
   //default string
   }
  1. 默认析构函数:
  2. 复制构造函数:
  • 复制构造函数⽤于将⼀个对象复制到新创建的对象中。⽤于初始化过程中(包括按值传递参数)。
  • 原型:
代码语言:txt
复制
 ```c++
代码语言:txt
复制
 Class_name(const Class_name&);
代码语言:txt
复制
 ```
代码语言:txt
复制
 它接受⼀个**指向类对象的常量引⽤**作为参数。
代码语言:txt
复制
 例如:StringBad(const StringBad&);
  • 新建⼀个对象并将其初始化为同类现有对象时,复制构造函数都将被调⽤。最常⻅的情况是将新对象显式地 初始化为现有的对象。
代码语言:txt
复制
 例子:
代码语言:txt
复制
 ```c++
代码语言:txt
复制
 StringBad ditto(motto);
代码语言:txt
复制
 StringBad metto=motto;
代码语言:txt
复制
 StringBad also=StringBad(motto);
代码语言:txt
复制
 StringBad * pStringBad=new StringBad(motto);
代码语言:txt
复制
 ```
代码语言:txt
复制
 - 其中中间的2种声明可能会使⽤复制构造函数直接创建metoo和 also,也可能使⽤复制构造函数⽣成⼀个临时对象,然后将临时对象的 内容赋给metoo和also,这取决于具体的实现。
 - 最后⼀种声明使⽤motto **初始化⼀个匿名对象**,并将新对象的地址赋给pstring指针。
  • 每当程序⽣成了对象副本时,编译器都将使⽤复制构造函数。
代码语言:txt
复制
 - 当函数**按值传递对象**(如程序清单12.3中的callme2())或**函数返回对象**时,都将使⽤复制构造函数。
 - 按值传递意味着创建原始变量的⼀个副本。**编译器⽣成临时对象时,也将使⽤复制构造函数。**
  • 由于按值传递对象将调⽤复制构造函数,因此应该按引⽤传递对象。这样可以节省调⽤构造函数的时间以及存储新对象的空间
复制构造函数的功能:
  • 默认的复制构造函数逐个复制⾮静态成员(成员复制也称为浅复制),复制的是成员的值
  • 静态函数(如num_strings)不受影响,因为它们属于整个类,⽽不是各个对象。
引例:
  • 让程序准确地记录对象计数。解决办法是提供⼀个对计数进⾏更新的显式复制构造函数:
代码语言:txt
复制
 ```c++
代码语言:txt
复制
 StringBad::StringBad(const StringBad&)
代码语言:txt
复制
 {
代码语言:txt
复制
     num_strings++;
代码语言:txt
复制
     ...
代码语言:txt
复制
 }
代码语言:txt
复制
 ```
代码语言:txt
复制
 - 如果类中包含用于记录对象数的**静态成员**,且其值会在新对象被创建时发生变化,则应提供一个显式复制构造函数来处理计数问题。
  • 隐式复制构造函数是按值进⾏复制的。
代码语言:txt
复制
 - 隐式复制构造函数的功能相当于:
代码语言:txt
复制
   ```c++
代码语言:txt
复制
   sailor.str=sport.str;
代码语言:txt
复制
   //复制的是指向字符串的指针,而不是字符串本身。
代码语言:txt
复制
   //当调用析构函数时将产生问题,可能对同一块内存区域进行两次删除,这将导致程序异常终止。
代码语言:txt
复制
   ```
代码语言:txt
复制
 - 解决方案:定义一个显式复制构造函数。
代码语言:txt
复制
   - 解决类设计中这种问题的⽅法是进⾏深度复制(deep copy)。
代码语言:txt
复制
   - 复制构造函数应当复制字符串并将副本的地址赋给str成员,⽽不 仅仅是复制字符串地址。
代码语言:txt
复制
     函数实现:
代码语言:txt
复制
     ```
代码语言:txt
复制
     StringBad::StringBad(const StringBad& st)
代码语言:txt
复制
     {
代码语言:txt
复制
         num_strings++;
代码语言:txt
复制
         len=st.len;
代码语言:txt
复制
         str=new char[len+1];
代码语言:txt
复制
         std::strcpy(str,st.str);
代码语言:txt
复制
         cout<<num_strings<<":\"<<str<<"\"object created"\n";
代码语言:txt
复制
         ...
代码语言:txt
复制
     }
代码语言:txt
复制
     ```
代码语言:txt
复制
     必须定义复制构造函数的原因在于,⼀些类成员是**使⽤new初始化**的、指向数据的指针,⽽不是数据本⾝。
代码语言:txt
复制
   - 如果类中包含了**使⽤new初始化的指针成员**,应当定义⼀个复制构造函数,**以复制指向的数 据,⽽不是指针**,这被称为深度复制。复制的另⼀种形式(成员复制或浅复制)只是复制指针 值。浅复制仅浅浅地复制指针信息,⽽不会深⼊“挖掘”以复制指针引⽤的结构。
  1. 赋值运算符:

ANSI C允许结构赋值,⽽C++允许类对象赋值,这是通过⾃动为类重载赋值运算符实现的。

  • 原型:
代码语言:txt
复制
 ```c++
代码语言:txt
复制
 Class_name & Class_name::operator=(const Class_name &);
代码语言:txt
复制
 //它接受并返回⼀个指向类对象的引⽤
代码语言:txt
复制
 //例子:
代码语言:txt
复制
 StringBad & StringBad::operator=(const StringBad &);
代码语言:txt
复制
 ```
赋值运算符的功能及何时调用:
  • 已有的对象赋给另⼀个对象时,将使⽤重载的赋值运算符:
代码语言:javascript
复制

   StringBad headline1("Celery Stalks at Midnight");
   ...
   StringBad knot;
   knot=headline1;
  • 初始化对象时,并不⼀定会使⽤赋值运算符。
代码语言:javascript
复制
   StringBad metoo=knot;

metoo是⼀个新创建的对象,被初始化为knot的值,因此使⽤复制构造函数

实现时也可能分两步来处理这条语句:

使⽤复制构造函数创建⼀个临时对象,然后通过赋值将临时对象的值复制到新对象中。

初始化总是会调⽤复制构造函数, ⽽使⽤=运算符时也可能调⽤赋值运算符。

  • 赋值运算符的隐式实现也对成员进⾏逐个复制浅复制将导致相同地址重复删除,造成数据受损
    • 如果操作结果是不确定的,则执⾏的操作将随编译器⽽异,包括显⽰独⽴声明 (Declaration of Independence)或释放隐藏⽂件占⽤的硬盘空间。当 然,编译器开发⼈员通常不会花时间添加这样的⾏为。
  • 如果成员本⾝就是类对象,则程序将使⽤为这个类定义的赋值运算符来复制该成员,但静态数据成员不受影响。
解决赋值的问题:

解决办法是提供赋值运算符(进⾏深度复制)定义

  • 由于⽬标对象可能引⽤了以前分配的数据,所以函数应使⽤delete 来释放这些数据。(即程序运行后,该目标对象将不再指向此内存位置,这将导致内存浪费。)
  • 函数应当避免将对象赋给⾃⾝;否则,给对象重新赋值前,释放内 存操作可能删除对象的内容。
  • 函数返回⼀个指向调⽤对象的引⽤
    • 通过返回⼀个对象,函数可以像常规赋值操作那样,连续进⾏赋 值,即如果S0、S1和S2都是StringBad对象,则可以编写这样的代码:

S0=S1=S2;

//使用函数表示法时,转换为;

S0.operator=(S1.operator=(S2));

//,S1.operator=(S2)的返回值是函数S0.operator=()的参数。

//返回值是⼀个指向StringBad对象的引⽤,因此参数类型是正确的。

例子:为StringBad类编写赋值运算符:
代码语言:javascript
复制
   StringBad & StringBad::operator=(const StringBad & st)
   {
   if(this==&st)
       return *this;
   delete [] str;
   len=st.len;
   str=new char[len+1];
   std::strcpy(str,st.str);
   return *this;
   }
  • 代码⾸先检查⾃我复制,这是通过查看赋值运算符右边的地址 (&s)是否与接收对象(this)的地址相同来完成的。如果相同,程序 将返回*this,然后结束。
  • 如果地址不同,函数将释放str指向的内存,这是因为稍后将把⼀个 新字符串的地址赋给str。如果不⾸先使⽤delete运算符,则上述字符串将保留在内存中。由于程序中不再包含指向该字符串的指针,因此这些内存被浪费掉。
  • 接下来为新字符串分配⾜够的内存 空间,然后将赋值运算符右边的对象中的字符串复制到新的内存单元中。
  • 程序返回*this并结束。
  • 赋值操作并不创建新的对象,因此不需要调整静态数据成员 num_strings的值。
  1. 地址运算符:

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 例子:
  • 复制构造函数的功能:
  • 引例:
  • 赋值运算符的功能及何时调用:
  • 解决赋值的问题:
  • 例子:为StringBad类编写赋值运算符:
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档