类与对象 浅拷贝和深拷贝

 结构与类的区别是:默认访问级别不同
类使用注意:
• 类的声明中的 private 和 public两个关键字可以按任意顺序出现任意次。为了使程序更加清晰,把所有私有成员和公有成员归类放在一起
• 除了 private 和 public 之外,还有 protected(保护性成员)关键字
• 数据成员可以是任何数据类型,但不能用auto、register或extern说明
• 不能在类的声明中给数据成员进行初始化
 
op.setPoint(1,2)实际上是 op.point::setPoint(1,2)的缩写形式,两者等价
• 外部函数不能引用对象的私有成员。
 
构造函数与析构函数
构造函数
调用构造函数的条件
1.定义对象直接调用
  Complex A(1.1,2.2);
2.动态分配对象空间时
  Complex *p = new Complex(3.0,4.0);
3.定义无名对象(没有名字的对象)
  Complex(2,4);
  new Complex(4,8);
构造函数也可以带缺省参数,
构造函数也可以被重载,以适应不同的场合。
注意: 一个类中既有重载构造函数,又有缺省参数
构造函数,有可能产生二义性
析构函数
析构函数也是一种特殊的成员函数 .它执行与构造函数相反的操作 ,通常用于 执行一些清理任务 ,如释放分配给对象的内存空间等
析构函数有以下一些特点:
• 析构函数与构造函数名字相同 ,但它前面必须加一个波浪号(~)
• 析构函数没有参数,也没有返回值,而且不能重载 ,因此在一个类中只能有一个析构函数
• 当撤消对象时,编译系统会自动地调用析构函数
调用析构函数的条件
1.对象自动退出生命周期
比如:全局对象、局部对象
  { Complex A(1.1,2.2);}
  void fun(Complex p){ };
2.程序员手动释放对象指针
  Complex *p = new Complex(5,6);
  delete p;
在同一作用域内类对象的构造和析构的执行顺序:先构造的后析构
 
拷贝构造函数
拷贝构造函数是一种特殊的构造函数。
它用于依据已存在的对象建立一个新对象。典型的情况是,将参数代表的对象逐域拷贝到新创建的对象中
每个类都有一个构造函数,它可以由用户根据需要自己定义,或者系统也可以为类产生一个缺省的拷贝构造函数
调用拷贝构造函数的条件
1.定义对象时
  Point p1(30,40);
  Point p2(p1);// Point p2 = p1;
2.函数的参数是对象时
  void test(Point p);
  test(p1);
3.函数的返回值是对象时
  Point test();
  test();
 
提示:利用无名对象初始化对象系统不会调用拷贝构造函数。调用的是构造函数
例如:
Point A = Point(4,5);
Point test(Point p){retrun p;} 
test(Point(4,5))
 
派生类构造、基类构造、成员对象构造顺序:
基类构造=》成员对象构造顺序=》派生类构造
#include<iostream>
#include<string>
using namespace std;
class Third {
public:
    Third() {
        cout<<"third constructor"<<endl;
    }
};
 
class Base {
public:
    Base(string name) {
        cout<<"base constructor"<<endl;
        this->name = name;
    }
    void disp1() {
        cout<<this->name<<endl;
    }
private:
    string name;
};
 
class Derived:public Base {
public:
    Derived(string name, int age):Base(name), third(new Third()) {
    // third(new Third()),Base(name)  这样写结果也一样
        cout<<"dervied constructor"<<endl;
        this->age = age;
    }
    void disp2() {
        cout<<this->age<<endl;
    }
private:
    int age;
    Third *third;
};
 
int main()
{
    Derived d("zhangsan", 10);
    d.disp1();
    d.disp2();
    return 0;
}
 
结果:
base constructor
third constructor
dervied constructor
zhangsan
10
 
const修饰的成员函数
由于成员函数可以任意访问类内的任何 数据成员,但是当我们不愿意让其修改 数据成员时,我们可以用const修饰类 的成员函数
Class class_name {
private:
......
public:
(type) function_name(...)const {
}
};
用const修饰的成员函数时,成员函数体内 不可以修改本结构体内的任何数据成员
但有一种情况是例外的就是当数据成员类型 符前用mutable修饰时,在const修饰的成员 函数体内该数据成员是可以改变的
 
对象赋值
#include <iostream>
using std::cin;
using std::cout;
using std::endl;
class MyClass{
private:
    int a,b;
    char *p;
public:
    void set(int i,int j){
        a = i;
        b = j;
    }
    void show(){
        cout<<a<<','<<b<<endl;
    }
   //~MyClass() {
    //    delete p;
    //}
};
 
int main()
{
    MyClass p1;
    MyClass p2;
    p1.set(20,5);
    p2 = p1;            //对象赋值
    p2.set(10, 1);
    p1.show();   // 20,5
    p2.show();  // 10,1
    return 0;
}
 
说明:
  1、在使用对象赋值语句进行对象赋值时,两个对象的类型必须相同,如对象的类型不同,
     编译时将出错。
  2、两个对象之间的赋值,仅仅使这些对象中数据成员相同,而两个对象仍是分离的。例如
     本例对象后,再调用p2.set设置p2的值,不会影响p1的值。
  3、对象赋值是通过默认赋值运算符函数实现的
  4、将一个对象的值赋给另一个对象时,多数情况下都是成功的,但当类中存在指针时,可能
     会产生错误。
 
浅拷贝和深拷贝
对于拷贝构造函数来说的  默认情况下是浅拷贝
浅拷贝:位拷贝,拷贝构造函数,赋值重载
多个对象共用同一块资源,同一块资源释放多次,崩溃或者内存泄漏
深拷贝:每个对象共同拥有自己的资源,必须显式提供拷贝构造函数和赋值运算符。
深浅拷贝的区别:
    浅拷贝是将原始对象中的数据型字段拷贝到新对象中去将引用型字段的“引用”复制到新对象中去,不把“引用的对象”复制进去,所以原始对象和新对象引用同一对象,新对象中的引用型字段发生变化会导致原始对象中的对应字段也发生变化。
    深拷贝是在引用方面不同,深拷贝就是创建一个新的和原始字段的内容相同的字段,是两个一样大的数据段,所以两者的引用是不同的,之后的新对象中的引用型字段发生改变,不会引起原始对象中的字段发生改变。
#include<iostream>
#include<string.h>
using namespace std;
class Student {
public:
    Student(char *name, int age): age(age) {
        this->name = new char[strlen(name) + 1];
        strcpy(this->name, name);
    }
    void disp() {
        cout<<name<<endl;
        cout<<age<<endl;
    }
    void setAge(int age) {
        this->age = age;
    }
    void deleteName() {
        delete name;
    }
private:
    char *name;
    int age;
};
int main()
{
    Student stu1("zhangsan", 1);
    Student stu2 = stu1;
    stu2.setAge(2);
    stu1.disp();  //zhangsan 1
    stu2.disp();  //zhangsan 2
    stu1.deleteName();  // name引用删除
    stu2.disp();  // “ “ 2
    return 0;
}
 
解决办法: 自己实现拷贝构造
Student(const Student &stu) {
        this->name = new char[strlen(stu.name) + 1];
        strcpy(this->name, stu.name);
}
在类中有指针数据成员时,拷贝构造函数的使用?
如果不显式声明拷贝构造函数的时候,编译器也会生成一个默认的拷贝构造函数,而且在一般的情况下运行的也很好。但是在遇到类有指针数据成员时就出现问题 了:因为默认的拷贝构造函数是按成员拷贝构造,这导致了两个不同的指针(如ptr1=ptr2)指向了相同的内存。当一个实例销毁时,调用析构函数 free(ptr1)释放了这段内存,那么剩下的一个实例的指针ptr2就无效了,在被销毁的时候free(ptr2)就会出现错误了, 这相当于重复释放一块内存两次。这种情况必须显式声明并实现自己的拷贝构造函数,来为新的实例的指针分配新的内存。        
 
拷贝构造函数里能调用private成员变量吗?
解答:其时从名子我们就知道拷贝构造函数其时就是一个特殊的构造函数,操作的还是自己类的成员变量,所以不受private的限制。
 
以下函数哪个是拷贝构造函数,为什么?  可重载
X::X(const X&);   //拷贝构造函数
X::X(X);
X::X(X&, int a=1);   //拷贝构造函数
X::X(X&, int a=1, int b=2);  //拷贝构造函数
解答:对于一个类X, 如果一个构造函数的第一个参数是下列之一:
   a) X&
   b) const X&
   c) volatile X&
   d) const volatile X&
  那么这个函数是拷贝构造函数.
 
一个类中可以存在多于一个的拷贝构造函数吗?
   解答:类中可以存在超过一个拷贝构造函数。
class X {
public:
  X(const X&); // const 的拷贝构造
  X(X&); // 非const的拷贝构造
};
注意,如果一个类中只存在一个参数为 X& 的拷贝构造函数,那么就不能使用const X或volatile X的对象实行拷贝初始化.
如果一个类中没有定义拷贝构造函数,那么编译器会自动产生一个默认的拷贝构造函数。
这个默认的参数可能为 X::X(const X&)或 X::X(X&),由编译器根据上下文决定选择哪一个。
 
拷贝构造函数不能由成员函数模版生成.
struct X {   
    template<typename T>   
    X( const T& );    // NOT copy ctor, T can't be X   
  
    template<typename T>   
    operator=( const T& );  // NOT copy ass't, T can't be X   
};   
原因很简单, 成员函数模版并不改变语言的规则,而语言的规则说,如果程序需要一个拷贝构造函数而你没有声明它,那么编译器会为你自动生成一个. 所以成员函数模版并不会阻止编译器生成拷贝构造函数, 赋值运算符重载也遵循同样的规则.
 
解决切割问题:
#include<iostream>
#include<string.h>
using namespace std;
 
class Base {
public:
    virtual void disp() {
        cout<<"Base::disp"<<endl;
    }
};
class Dervied:public Base {
public:
     virtual void disp() {
        cout<<"Dervied::disp"<<endl;
    }
};
void print(Base b)  // 修改Base &b  引用ok
{
    cout<<"print"<<endl;
    b.disp();
}
int main(int argc, char *argv[])
{
    Dervied d;
    print(d);  //print  Base::disp
    return 0;
}
 
posted @ 2019-07-25 16:13  小兵07  阅读(593)  评论(0编辑  收藏  举报