关于C++的一点点总结(其三)

第五章 类的四君子

  • 在学习C语言的时候看山是山,但是在学习C++的时候看山不是山,它是坑。看似简单的一个类其实里边默认就生成了六个函数
class A
{
    A(); //构造函数
    A(const A &a) //拷贝构造函数
    A& operator=(const A &a); //重载赋值
    ~A(); //析构函数
    A* operator&(); //重载取地址操作符
    const A* operator&(); //const修饰的取地址操作符的重载
};
  • 在大多数时候我们只需要弄清楚前四个默认生成的成员函数,即构造函数,拷贝构造函数,重载的赋值操作符和析构函数。接下来将详细探讨这四个默认的成员函数。

一、构造函数

  1. 什么是构造函数?
    • 数据成员多为私有的,要对它们进行初始话,必须要有一个公有的成员函数来进行。同时这个函数应该在且仅在定义对象的时候自动执行一次。这样的函数就是构造函数。
  2. 构造函数的特点:
    • 它是公有的。
    • 它没有返回值,注意是没有返回值类型,而不是返回值为void,它们有本质的区别。
    • 函数名和类名相同。
    • 构造函数可以重载。
class A
{
public:
    A() //这就是它的构造函数,它是公有的,函数名和类名相同,没有返回值类型
    {
        cout<<"create obj!"<<endl;
        m_data = 0;
    }
private:
    int m_data; //数据成员
};
  1. 构造函数的实现
  • 构造函数的实现有两种方式,它可以在类中实现,也可以在类外实现。
  1. 在类中实现:
class A
{
public:
    A() //在类中实现它的构造函数
    {
        cout<<"create obj!"<<endl;
        m_data = 0;
    }
private:
    int m_data; //数据成员
};
  1. 在类外实现:
class A
{
public:
    A (); //在类外实现它的构造函数
};

A::A() //返回值 类名::函数名(参数列表)
{
    cout<<"create obj!"<<endl;
}
  1. 构造函数的初始化列表
  • 为什么要使用初始化列表呢?应为类的数据成员可能是const的和引用的,我们都知道const对象和引用对象是不可以对它进行赋值操作的。
class A
{
public:
    A(int i)
    {
        this->i = i;
        j = i; //error
        k = i; //error
    }
private:
    int i;
    const int j;
    int &k;
};
  • 这个时候我们就需要初始化列表发威了
class A
{
public:
    A(int i = 0):i(i),j(i),k(i) //初始化成员列表,其中i=0的作用是给他一个默认值,就是你不给i传值它也会对i,j,k初始化为0
    {
    }
private:
    int i;
    const int j;
    int &k;
};
  • 成员初始化的顺序是什么?
    • 它的初始化顺序严格遵守数据成员的声明顺序,而不是按照初始化列表的顺序对其进行初始化
class A
{
public:
    // 不按照初始化列表的顺序对其进行初始化
    A(int i = 0):m_i(i),m_j(i),m_k(i) 
    {
    }
private:
    // 严格按照声明顺序去初始化
    int m_i;
    float m_j;
    int m_k;
};
  1. 构造函数的三个作用
  • 构造对象
class B
{
    B(){}
};
int main()
{
    B b;
}
  • 初始化对象
class A
{
public:
    A(int i = 0):m_i(i),m_j(i),m_k(i){}
private:
    int m_i;
    float m_j;
    int m_k;
};
  • 类型转换
    • 观察下边的代码
class A
{
public:
    A(int i = 0)
    {
        std::cout<<this<<std::endl;
    }
    ~A()
    {
        std::cout<<this<<std::endl;  
    }
private:
   int m_i;
};
int main()
{
    A a;
    a = 100;
}
  • 为什么100可以给a对象赋值?它是个什么原理呢?
    • 我们可以想想double类型的对象给int类型的对象赋值,它们之间的赋值是先将double类型的对象转换成一个int类型的对象,然后再由int类型给int类型赋值。

        double ---> int 在这里int就是double转换为int的桥梁
      
        int ------> int 
      
    • 同理,将int 转化我 A 类型我们也只需要找到那个桥梁就可以了,这时构造函数就起作用了,在执行a = 100的时候就是将100生成一个临时对象,然后再用这个临时对象给a对象赋值。

            100 ----> temp 
            a <------ temp
               m_i       m_i
      

二、 拷贝构造

  1. 什么是拷贝构造函数?
  • 拷贝构造函数是一种特殊的构造函数,它有一个形参,该形参是对该类类型的引用。
  1. 拷贝构造函数的调用时机
  • 当函数的参数为类的对象时
class A
{
public:
    A(int i = 0)
    {
    }
    A(A &a)
    {
        std::cout<<"copy"<<std::endl;
    }
    void Fun(A c)
    {
        std::cout<<"fun"<<std::endl;
    }
    ~A()
    { 
    }
private:
   int m_i;
};
int main()
{
    A a;
    a.fun(a);
}
- (1). a对象传入形参时,会先会产生一个临时变量,就叫c 
- (2). 然后调用拷贝构造函数把a的值给c。有点像:A c(a);
- (3). 等fun()执行完后, 析构掉c对象。
  • 当一个对象需要另一个对象来初始化的时候
class A
{
public:
    A(int i = 0)
    {
        std::cout<<this<<std::endl;
    }

    A(A &a)
    {
        m_i = a.m_i;
        std::cout<<"copy"<<std::endl;
    }

    ~A()
    {
        std::cout<<this<<std::endl;  
    }
private:
   int m_i;
};

int main()
{
    A a;
    /*
    有‘=’它不一定就是赋值操作符,它很有可能是调用的拷贝构造函数。如果是旧对象与新对象之间就是调用的拷贝构造函数,如果是旧对象和旧对象之间就是赋值操作。
    */
    A b(a); //调用拷贝构造
    A c = b; //调用拷贝构造初始化
}
  1. 深拷贝与浅拷贝
  • 浅拷贝和深拷贝是拷贝构造函数里边的重点。打个比方,浅拷贝就是给文件建了一个快捷方式,当我们把源文件删除的时候,那个快捷方式就没有作用了;深拷贝就是将那个源文件重新备份了,两个文件互不影响。
  • 在使用拷贝构造函数的过程中,我们什么时候用浅拷贝,什么时候用深拷贝?
    • 浅拷贝:当类的数据成员没有在堆区的时候,使用浅拷贝是没有问题的;
    • 深拷贝:一旦数据成员有在堆上分配的内存,那么就必须使用深拷贝。比如说指针。
  • 下边书写一个深拷贝的列子:
class A
{
public:
    A(int i = 0,int *ptr = NULL)
    {
        m_i = i;
        m_p = new int(*ptr);
    }

    A(const A &a)
    {
        if(this != &a) //防止自己给自己拷贝
        {
            m_i = a.m_i;
            this->m_p = new int(*a.m_p);
            std::cout<<"copy"<<std::endl;
        }
    }
    void show()
    {
        std::cout<< m_i<<std::endl;
        std::cout<< m_p<<std::endl;
        std::cout<< *m_p<<std::endl;
    }
    ~A()
    {
        delete m_p;
        m_p = NULL;
    }
private:
   int m_i;
   int *m_p;
};

int main()
{
    int j = 0;
    int *p = &j;
    A a(50,p);
    A b = a;
    a.show();
    b.show();
    return 0;   
}
  • 在做深拷贝的时候一定要给他重新分配空间,不能让他们共用同一块内存空间。
    总结:
  1. 浅拷贝:它只是对值的拷贝,并没有开辟新的空间,当它的成员存在资源(动态内存,堆)申请的情况时,会出现指向同一块内存的指针被释放了两次,造成内存泄露;
  2. 深拷贝:它有他自己独立的空间,这样就解决了同一块内存被释放两次的问题,在写拷贝构造函数的时候要注意判断是不是自己给自己拷贝,然后再申请新的空间,返回*this。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值