拷贝构造
拷贝构造函数 Copy Constructor
也是构造函数的一种
-  
当利用已存在的对象创建一个新对象时(类似于拷贝),就会调用新对象的拷贝构造函数进行初始化。
 -  
拷贝构造函数的格式是固定的,接收一个const引用作为参数
class Car { int m_price; int m_length;public: Car(int price = 0, int length = 0) :m_price(price), m_length(length) { cout << "Car(int price = 0, int length = 0)" << endl; } // 拷贝构造函数 Car(int price = 0, int length = 0) :m_price(price), m_length(length) { cout << "Car(int price = 0, int length = 0)" << endl; } void display() { cout << "price=" << m_price << ", length=" << m_length << endl; } }; int main() { Car car1; Car car2(100); Car car3(100, 5); // 利用已经存在的car3对象创建了一个car4对象 // car4初始化时会调用拷贝构造函数 // 就算不写拷贝构造函数也可以 Car car4(car3); // 相当于 /* car4.m_price = car3.m_price; car4.m_length = car3.m_length; */ } 
继承父类
class Person {
public:	
    int m_age;
    Person(int age = 0) :m_age(age) {
        
    }	
    Person(const Person &person) :m_age(person.m_age) {
        
    }
};
class Student :public Person {
public:
    int m_score;
    Student(int age = 0, int score = 0) :Person(age), m_score(score) {}
    Student(const Student &student) :m_score(student.m_score) {} 
    // 但Student的age并没有传进来 
    // 所以应该这样写  
    Student(const Student &student) :Person(student), m_score(student.m_score) {}  
    // 但如果不写的话,默认也会直接拷贝过去
};
 
一些注意点
class Car {
    int m_price;
    int m_length;
public:
    Car(int price = 0, int length = 0) :m_price(price), m_length(length) {
        cout << "Car(int price = 0, int length = 0)" << endl;
    }	
    Car(const Car &car) :m_price(car.m_price), m_length(car.m_length) {		
        cout << "Car(const Car &car)" << endl;	
    }	
    void display() {		
        cout << "price=" << m_price << ", length=" << m_length << endl;	
    }
};
int main() {
    Car car1(100, 5);
    // Car(int price = 0, int length = 0)
    Car car2(car1);
    // Car(const Car &car)
    Car car3 = car2;
    // Car(const Car &car)
    Car car4;
    // Car(int price = 0, int length = 0)     
    car4 = car3;	
    car4.display(); 
    // price=100, length=5 拷贝成功,但并没有调用拷贝构造函数   
    return 0;
}
 
第二种和第三种写法是等价的,都是调用拷贝构造函数
为什么car4 = car3;并没有调用拷贝构造函数?
- 因为拷贝构造函数是在初始化的时候用到的,此时car4和car3都是已经存在的对象
 - 所以这仅仅是赋值,并不是拷贝构造
 
子类的构造函数会默认调用父类无参的构造函数,如果子类的构造函数指明调用父类的拷贝构造函数,那就不会调用父类无参的构造函数。
浅拷贝、深拷贝
浅拷贝(shallow copy):指针类型的变量只会拷贝地址值
 深拷贝(deep copy):将指针指向的内容拷贝到新的存储空间
class Car {	
    int m_price;
    char *m_name;
public:
    Car(int price = 0, const char *name = NULL) :m_price(price) {	
        if (name == NULL) return;	
        // 申请新的堆空间	
        m_name = new char[strlen(name) + 1] {};
        // 拷贝字符串数据到新的堆空间	
        strcpy(m_name, name);	
    }
    ~Car() {	
        if (m_name == NULL) return;	
        delete[] m_name;	
        m_name = NULL;
    }	
    void display() {	
        cout << "price is " << m_price << ", name is " << m_name << endl;
    }
};
int main() {
    const char *name = "bmw";	
    char name[] = {'b', 'm', 'w', '\0'};	
    Car *car = new Car(100, "bmw");
    car->display();	
    delete[] name;	
    getchar();	
    return 0;
}
 
name的地址和m_name的地址是不一样的
因为m_name申请了新的堆空间并且把name的内容拷贝了过去。
-  
编译器默认提供的拷贝是浅拷贝
比如拷贝构造
- 是将一个对象中所有成员变量的值拷贝到另一个对象
 - 如果某个成员变量是指针,只会拷贝指针中存储的地址值,并不会拷贝指针指向的内存空间
 - 可能会导致堆空间多次free的问题
 
 -  
如果要实现深拷贝,就需要自定义拷贝构造函数
- 将指针类型的成员变量所指向的内存空间,拷贝到新的内存空间
 
 
堆空间指向栈空间很危险
野指针,堆空间指向栈空间的指针,当栈空间的内容被回收
所以应该堆空间指向堆空间
但如果使用拷贝构造
int main() {
    Car car1(100, "bmw");
    Car car2 = car1;	
    car2.display();
    getchar();	
    return 0;
}
 

 两个car会指向同一块堆空间(浅拷贝:没有产生新的地址空间)
而且可能会导致double free,同一块堆空间被释放两次
所以应该是再申请一份堆空间,然后将car2指向新的堆空间地址,一人一份就不会有问题。(深拷贝:产生新的地址空间)
Car(const Car &car) :m_price(car.m_price) { 
    if (car.m_name == NULL) return;    
    // 申请新的堆空间 
    m_name = new char[strlen(car.m_name) + 1]{};
    // 拷贝字符串数据到新的堆空间 
    strcpy(m_name, car.m_name);
}
 
现在完成了两个构造函数,分别都申请了新的堆空间,可以将申请堆空间这部分提取出来封装成函数。
class Car {
    int m_price;	
    char *m_name;
    void copy(const char *name = NULL) {
        if (name == NULL) return;
        // 申请新的堆空间	
        m_name = new char[strlen(name) + 1] {};	
        // 拷贝字符串数据到新的堆空间	
        strcpy(m_name, name);	
    }
public:	
    Car(int price = 0, const char *name = NULL) :m_price(price) {
        copy(name);
    }	
    Car(const Car &car) :m_price(car.m_price) {	
        copy(car.m_name);	
    }
 
如果类里的数据都是int类型,则不需要深拷贝。只有当类里有指针类型,需要指向某块内存空间的时候才使用深拷贝。
对象型参数和返回值
-  
使用对象类型作为函数的参数或者返回值,可能会产生一些不必要的中间对象
-  
作为参数
class Car { public: Car() { cout << "Car() - " << this << endl; } Car(const Car &car) { cout << "Car(const Car &) - " << this << endl; } ~Car() { cout << "~Car() - " << this << endl; } void run() { cout << "run()" << endl; } }; void test1(Car car) { } int main() { Car car1; test1(car1); getchar(); return 0; }
-  
运行后打印一个普通构造函数和一个拷贝构造函数
int main() { Car car1; // Car() test1(car1); // Car(const Car &car) getchar(); return 0; } 
为什么test1调用的是拷贝构造函数?
- 因为相当于
void test1(Car car = car1),使用一个已经存在的对象构建新的对象,就是拷贝构造 
但不需要重新创建一个新的对象,我们把对象传进去是为了在函数内部能够使用这个对象的内容,没有必要创建新对象。
那么可以改成引用
void test1(Car &car) { }这样就是直接引用传入的car的对象,就不会创建一个新的对象
 -  
 -  
作为返回值
Car test2() { Car car; // Car() return car; } int main() { Car car2; // Car() 调用普通构造函数 car2 = test2(); // 相当于car2 = car; return 0; }结果:

产生三个新的对象,为什么会有最后一次调用拷贝构造呢?
因为在调用test2之后,test2在栈空间的内存会被释放掉,说明生成的car对象也会被释放掉,但现实中并不会这样,会把在test2空间生成的car对象拷贝到main空间中(因为是在main中调用的)
所以会利用test2中生成的car对象拷贝构造出来一个新的car对象,这个car对象是在main函数的空间中。
就像是
car2 = Car(test2());所以会产生一些不必要的中间对象。
 
 -  
 
匿名对象(临时对象)
匿名对象:没有变量名, 没有被指针指向的对象,用完后马上调用析构
比如
int main() {
    Car();   
    return 0;
}
 
Car()生成匿名对象,没有名字,但还是会创造新的对象。
构造完就析构掉
隐式构造
C++中存在隐式构造的现象:某些情况下,会隐式调用单参数的构造函数。
class Person {	
    int m_age;
public:
    Person() {
        cout << "Person() - " << this << endl;
    }	
    Person(int age) :m_age(age) {	
        cout << "Person(int) - " << this << endl;	
    }	
    Person(const Person &person) {	
        cout << "Person(const Person &person) - " << this << endl;	
    }	
    ~Person() {
        cout << "~Person() - " << this << endl;
    }
    void display() {
        cout << "display() - age is " << m_age << endl;	
    }
};
int main() {
    /*
    Person p1;
    Person p2(10);	
    Person p3 = p2;
    */	
    Person p1 = 20;  
    getchar();	
    return 0;
}
 
这里Person p1 = 20;是在干什么?
运行发现调用的单参数的构造函数。等价于Person p2(20)。
如果再加一个函数,然后再main函数中调用。
void test1(Person person) {
    
}
int main() {
    /*
    Person p1;	
    Person p2(10);
    Person p3 = p2;
    */
    // Person p1 = 20;  
    test1(30);    
    getchar();
    return 0;
}
 
这里test1(30);相当于调用了test1(Person person = 30),和前面一样,也会调用单参数的构造函数。
再如果加一个函数,返回值是Person对象
Person test2() {  
    return 40;
}
int main() {  
    test2();    
    getchar();	
    return 0;
}
 
会将40传递给Person对象的单参数构造函数来构造Person对象。
这样就叫做隐式构造
如果是
int main() {    
    Person p1;
    p1 = 40;	
    getchar();	
    return 0;
}
 
会调用一次无参构造、一次单参数构造和一次析构函数
无参构造:Person p1;
单参数构造:因为如果要p1 = 40;左边是person对象,右边也得是person对象,所以右边调用单参数构造函数,但是隐式构造,将这个构造函数传递给p1后马上析构。
-  
可以通过关键字explicit禁止掉隐式构造
explicit Person(int age) :m_age(age) { cout << "Person(int) - " << this << endl; }不允许隐式构造
 










