c++class default function

类的四个默认函数

父子类构造函数问题
父子类析构函数问题
拷贝构造函数的深浅copy
赋值函数的深浅copy


C++类有四个默认函数
分别是:(Effective C++》中提到四个)
1、默认构造函数;
2、默认拷贝构造函数;
3、默认析构函数;
4、赋值运算符函数;
// 这两个类的效果相同
class Empty
{}
class Empty
{
public:
    Empty() {}   // deafault构造函数;
    Empty(const Empty&) {}  // 默认拷贝构造函数
    ~Empty() {...}  // 析构函数
    Empty& operator = (const Empty &) {}    // 赋值运算符
}


说明:
1) 这些函数只有在需要调用的时候,编译器才会生成。
2) 这些函数都是public的。
3) 这些函数都是inline的(即函数定义在类的定义中的函数)。
4) 如果你显式的声明了这些函数,那么编译器将不再生成默认的函数。


比如,当遇到下列语句时,函数会被编译器生成:
Empty e1; //默认构造函数 
 //对象销毁时,析构函数
Empty e2(e1); //拷贝构造函数
Empty e3 = e1;//拷贝构造函数
e2 = e1; //赋值运算符重载函数

取地址运算符函数?
    另外两种默认的函数:就是取地址运算符和取地址运算符的const版本,这两个函数在《Effective C++》中没有提及。
Empty *operator &() {} // 取值运算符
const Empty *operator &() const {} // 取值运算符const
    正如下面的代码可以正常工作:到底是通用的取地址符作用,还是这两个函数是确实存在呢?可能是默认的取地址吧。默认的取地址符就是做这个工作吧。
#include <stdio.h>
class Empty {};
int main(int argc, char** argv){
Empty a; 
const Empty *b = &a; printf("%p/n", &a); //调用取地址运算符
printf("%p/n", b); //调用const取地址运算符
}

构造函数
父子类构造函数
    构造方法用来初始化类的对象,与父类的其它成员不同,它不能被子类继承(子类可以继承父类所有的成员变量和成员方法,但不继承父类的构造方法)。因此,在创建子类对象时,为了初始化从父类继承来的数据成员,系统需要调用其父类的构造方法。
    构造原则如下:
      1、没有定义构造函数,编译器会给一个默认的构造函数(无参数,无内容)。

      2、定义了构造函数,不论是无参还是有参,编译器不再提供默认构造函数。

    在C++的类继承中,建立对象时,首先调用父类的构造函数,然后在调用下一个子类的构造函数,依次类推;析构对象时,其顺序正好与构造相反,先调用子类的析构函数,在调用下一个父类的构造函数,依次类推;

      1、子类未显示调用父类的构造函数,会调用父类的无参构造函数。此时如果父类只有有参数的构造方法,则会出错(如果      父类只有有参数的构造方法,则子类必须显示调用此带参构造方法)
      2、 子类显示调用父类的构造函数,使用初始化列表来调用父类构造函数


父子类析构函数

   在C++的类继承中,析构对象时,其构造对象时顺序正好与构造相反,先调用子类的析构函数,在调用下一个父类的构造函数,依次类推;
   如果是父类指针,指向子类对象,那么析构的时候有可能会出现不调用子类析构函数的情况(如果父类析构函数不是virtual的话),这种情况下可能会出现内存泄漏问题(如果子类在析构函数里面进行了内存资源释放)。
所以,如果一个类会被继承,那么这个类的析构函数就加上virtual,设为虚析构函数。
就是说 父类的析构函数,一定要加virtual,写成虚析构函数。这样可以避免可能出现的内存泄漏。


拷贝构造函数
    如果在类中没有显式地声明一个拷贝构造函数,那么,编译器将会自动生成一个默认的拷贝构造函数,该构造函数完成对象之间的位拷贝。位拷贝又称浅拷贝,后面将进行说明。
自定义拷贝构造函数是一种良好的编程风格,它可以阻止编译器形成默认的拷贝构造函数,提高源码效率。
    一个容易被忽略的问题: 自定义的拷贝构造函数不仅会覆盖默认的拷贝构造函数,也会覆盖默认的构造函数。下面的代码是编译不过的,用户必须再显式的定义一个无参的构造函数。
class Empty {
public: Empty(const Empty& e) { }
 //拷贝构造函数};
int main(int argc, char** argv){ Empty a;}
以下情况都会调用拷贝构造函数:
  1.  一个对象以值传递的方式传入函数体 
  2.  一个对象以值传递的方式从函数返回 
  3.  一个对象需要通过另外一个对象进行初始化
    c++的拷贝构造函数还有一处妙用,就是自定义拷贝构造函数,并设置为private属性,其实现体可以什么都不写,那么这个类将变成一个不可被复制的类了。


赋值运算符函数
    一般地,赋值运算符函数的参数是函数所在类的const类型的引用加const是因为:
    1、我们不希望在这个函数中对用来进行赋值的“原版”做任何修改。
    2、加上const,对于const的和非const的实参,函数就能接受;如果不加,就只能接受非const的实参。
    用引用是因为:
      这样可以避免在函数调用时对实参的一次拷贝,提高了效率。
    一般地,返回值是被赋值者的引用,即*this(如上面例1),原因是
    1、这样在函数返回时避免一次拷贝,提高了效率。
    2、更重要的,这样可以 实现连续赋值,即类似a=b=c这样。如果不是返回引用而是返回值类型,那么,执行a=b时,调用赋值运算符重载函数,在函数返回时,由于返回的是值类型,所以要对return后边的“东西”进行一次拷贝,得到一个未命名的副本(有些资料上称之为“匿名对象”),然后将这个副本返回,而这个副本是右值,所以,执行a=b后,得到的是一个右值,再执行=c就会出错。
赋值运算符函数限制
  1. 赋值运算符函数只能是类的非静态的成员函数
  2. 赋值运算符函数不能被继承
  3. 赋值运算符函数要避免自赋值: 对于赋值运算符重载函数,一定要先检查是否是自赋值,如果是,直接return *this。

浅拷贝和深拷贝
      拷贝构造函数和赋值函数都有深浅拷贝的问题
  深拷贝和浅拷贝可以简单理解为:如果一个类拥有资源,当这个类的对象发生复制过程的时候, 资源重新分配,这个过程就是深拷贝,反之,没有重新分配资源,就是浅拷贝(默认拷贝构造函数和赋值函数都是浅拷贝)。
 在某些状况下,类内成员变量需要动态开辟堆内存,如果实行位拷贝,也就是把对象里的值完全复制给另一个对象,如A=B。这时,如果B中有一个成员变量指针已经申请了内存,那A中的那个成员变量也指向同一块内存。这就出现了问题:当B把内存释放了(如:析构),这时A内的指针就是野指针了,出现运行错误。
    下面举个深拷贝的例子。
#include <iostream>
using namespace std;
class CA
{
 public:
  CA(int b,char* cstr)
  {
   a=b;
   str=new char[b];
   strcpy(str,cstr);
  }
  CA(const CA& C)
  {
   a=C.a;
   str=new char[a]; //深拷贝
   if(str!=0)
    strcpy(str,C.str);
  }
  ~CA()
  {
   delete str;
  }
 private:
  int a;
  char *str;
};


   拷贝构造函数和赋值函数的深拷贝例子,来源:https://www.cnblogs.com/zpcdbky/p/5027481.html
class MyStr
{
private:
    char *name;
    int id;
public:
    MyStr() {}
    MyStr(int _id, char *_name)   //constructor
    {
        cout << "constructor" << endl;
        id = _id;
        name = new char[strlen(_name) + 1];
        strcpy_s(name, strlen(_name) + 1, _name);
    }
    MyStr(const MyStr& str)
    {
        cout << "copy constructor" << endl;
        id = str.id;
        if (name != NULL)
            delete name;
        name = new char[strlen(str.name) + 1];
        strcpy_s(name, strlen(str.name) + 1, str.name);
    }
    MyStr& operator =(const MyStr& str)//赋值运算符
    {
        cout << "operator =" << endl;
        if (this != &str)
        {
            if (name != NULL)
                delete name;
            this->id = str.id;
            int len = strlen(str.name);
            name = new char[len + 1];
            strcpy_s(name, strlen(str.name) + 1, str.name);
        }
        return *this;
    }
    ~MyStr()
    {
        delete name;
    }
};


& 引用 取地址  
   c++中引用数据类型和取地址符的区别是左值和右值的区别,引用是左值,取地址运算符是右值,所谓左值指的是内存中映射的存储单元,右值是存储单元中所存的数据。int &a = b;和int *p = &a; 可以解释这两种不同的关系。
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值