C++学习笔记day44-----C++98-再谈拷贝构造、拷贝赋值操作符、静态成员

再谈拷贝构造
在day42中,提到大多数情况下都不需要用户自己编写拷贝构造函数,只有在特殊情况下才需要自行编写拷贝构造函数。这里的特殊情况,指的就是成员函数包含指针类型。通过以下代码来解释:

#include 
using namespace std;
class Integer{
public:
    Integer(int data = 0)
        :m_p_data(new int(data)){}
    ~Integer(void){
        cout << "m_p_data:" << m_p_data << endl;
        delete m_p_data;
        m_p_data = NULL;
    }
    void print(void) const{
        cout << *m_p_data << endl;
    }
    //浅拷贝
    /*
    Integer(const Integer &that){
        m_p_data = that.m_p_data;
    }
    */
    //深拷贝
    Integer(const Integer &that){
        m_p_data = new int(*(that.m_p_data));
    }
    //编译器缺省提供的拷贝复制操作附函数
    /*
    Integer& operator=(const Integer &that){
        m_p_data = that.m_p_data;
        cout << "拷贝复制" << m_p_data << endl;
        return *this;
    }
    */
    //自定义深拷贝
    Integer& operator=(const Integer &that){
        if(&that != this){
            delete m_p_data;//释放旧内存
            m_p_data = new int; //分配新内存
            *m_p_data = *(that.m_p_data);
            cout << "拷贝复制" << m_p_data << endl;
        }
        return *this;
    }
private:
    int *m_p_data;
};
int main(void){
    Integer i1(100);
    i1.print();
    Integer i2(i1);
    i2.print();
    Integer i3;
    //i3.operator=(i2);//拷贝赋值关键字,左调右参
    i3 = i2;//拷贝赋值
    i3.print();
    return 0;
}

当使用i1对象初始化i2的时候,编译器调用拷贝构造函数。如果编译器调用的是缺省的拷贝构造函数,那么缺省的拷贝构造函数会将i1的成员变量m_p_data的内容拷贝到i2。m_p_data的内容是一个堆区的地址,这个时候i1和i2都持有了这个地址。当程序结束的时候,i1和i2都调用析构函数,将同一个地址释放了两次,这种错误,在编译期间是不会体现的,只有在运行到程序结束的时候,才会报错”double free”。很显然缺省的拷贝构造函数并不适用于带有指针类型的成员变量的类。这种拷贝方式称为浅拷贝。
需要自定义拷贝构造函数,自定义的拷贝构造函数,要明确目的。要将指针所指向的地址上的内容进行拷贝,而不是对指针的内容进行拷贝。

拷贝赋值操作符
编译器为每一个类都提供了一个缺省的拷贝赋值操作符”=”,它的具体实现是在类中增加了一个成员函数”operator=”。这个成员函数将右边对象中的所有成员函数,赋值给左边对象。它具有和拷贝构造函数同样的问题,也需要改写它。
在改写的时候要比较注意,由于对象是先创建再赋值的,那么在创建对象的时候,它已经拥有了一个指向一个堆区地址的指针,需要把这个地址释放掉,然后重新申请一块内存,这块内存的大小,由右边的对象决定,要和右边对象的指针所指地址上的数据的数据类型同样大小。还要考虑自赋值的问题。

静态成员
类中可以包含静态的成员函数和静态的成员变量。
要了解静态成员的本质。静态成员变量,是放在全局区的,它和普通的成员变量不同(普通成员变量在内存中存放的位置由对象的声明方式决定,通过局部声明方式声明的对象,它的成员变量是放在栈区;new出来的对象,其成员变量放在堆区;在全局区声明的对象,其成员变量放在全局区)。可以将静态成员理解成是一种全局属性,生命周期是进程级的,但是它的作用域被限定在类中。我们以下例子来看以下静态成员的语法形式:

#include 
using namespace std;
class A{
public:
    A(int data):m_data(data){}//普通的成员变量在构造时进行定义和初始化
    int m_data;
    static int s_data;//声明
};
int A::s_data = 200;//定义和初始化
int main(void){
    A a(100);
    cout << "size = " << sizeof(a) << endl;
    cout << a.m_data << endl;

    cout << A::s_data << endl;
    cout << a.s_data << endl;//两者等价
    A a2(a);
    a2.m_data = 101;
    a2.s_data = 201;

    cout << a.s_data << endl;
    return 0;
}
--------------------------------------------
#include 
using namespace std;
class A{
public:
    A(int data):m_data(data){}//普通的成员变量在构造时进行定义和初始化
    static void fun1(void){
        cout << "静态成员函数" << endl;
    }
    void fun2(void){
        cout << "非静态成员函数" << endl;
    }
    int m_data;
    static int s_data;//声明
};
int A::s_data = 200;//定义和初始化
int main(void){
    A a(100);
    a.fun2();
    A::fun1();
    return 0;
}

十七、拷贝构造和拷贝赋值
在使用拷贝构造和拷贝复制的时候,要非常的注意,如果成员变量存在指针 ,那么极有可能会造成内存泄露和double free

深浅拷贝,是因为C++允许用户操作地址,所以才有这样的问题。

1、
1) 缺省拷贝函数:浅拷贝,只复制指针本身
深拷贝:拷贝指针指向的内容,而不是指针本身

如果一个类中包含指针形式的成员变量,缺省的拷贝构造只是复制了指针本省,没有复制指针所指向的内容,这种拷贝方式成为浅拷贝。
浅拷贝将导致不同的对象间的数据共享,如果数据在堆区,析构时还可能会发生"double free"的错误,所以必须自定义一个支持复制指针所指向内容的拷贝构造函数,即深拷贝。

2、 拷贝赋值
1) 当两个对象进行赋值操作时,比如”i3 = i2”,编译器会将其处理成i3.operator=(i2)成员函数调用形式,其中”operator=”就是成员函数 ,成为拷贝赋值操作附韩寒素,该函数的返回结果就是表达式的结果.

2) 由编译器缺省提供的拷贝赋值函数和缺省拷贝构造函数类似,也是浅拷贝,只是复制了指针变量的自身,没有复制指针所指向的内容,有double free和内存泄露的问题。
3) 为了得到深拷贝的效果,就必须自己定义一个支持拷贝指针指向内容拷贝赋值函数:
类名& operator=(const 类名& that){
if(&that != this)//防止自赋值
{
释放旧内存
分配新内存
拷贝新数据
}
return *this;

}

练习:实现String类型

十八、静态成员
1、静态成员变量
class 类名{
static 数据类型 变量名;

};
数据类型 类名::变量名 = 初值;

1) 普通的成员变量属于对象,是对象的一部分,静态成员变量不属于对象。
2) 静态全局变量在全局区,可以把静态的成员变量可以理解成被限制在类中使用的全局变量。(而成员变量是由对象创建的位置所决定的。)
3) 可以通过”类名::”直接访问静态成员,也可以通过对象访问静态成员。
(对象访问静态成员的时候,并不是说明这个静态成员数据这个对象,而是用对象告诉编译器,这个静态变量是对象的类类型里面的静态成员)

2、静态成员函数
(重点!静态成员函数是不拥有this指针,很多行为都可以通过这一点来排查)
class 类名{
static 返回类型 函数名 (形参表){函数体}
}
1) 静态成员函数没有this指针,所以也没有const属性(即不可能是常成员函数)
2) 使用方法和静态成员变量一致
可以通过”类名::”直接访问静态成员,也可以通过对象访问静态成员。
(对象访问静态成员的时候,并不是说明这个静态成员数据这个对象,而是用对象告诉编译器,这个静态变量是对象的类类型里面的静态成员)
3) 在静态成员函数中,只能访问静态成员,不能访问非静态的成员。普通的成员函数既可以访问静态成员,也可以访问非静态成员。

你可能感兴趣的:(c++)