继承相关概念

继承关系(public继承、protected继承、private继承)

  1. 基类的私有成员在派生类中不能被访问,如果一些基类成员不想被基类对象直接访问,但需要在派生类中访问,就定义为保护成员。保护成员限定符因继承而生

  2. public继承保持is-a原则,每个父类的成员子类也可以使用

  3. protected/private继承保持has-a原则,基类部分成员并未完全成为子类接口的一部分。(几乎很少使用)

  4. 不管是哪种继承关系,在子类中都可以访问父类的共有成员和保护成员,但父类的私有成员存在但在子类中不可见(不能访问)

  5. 使用关键字class时默认的继承方式是private,使用struct时默认的继承方式是public(最好显式写出)

  6. 实际应用中一般使用public继承

继承与转换——赋值兼容规则——public继承

  1. 子类对象可以赋值给父类对象(切片/切割)

  2. 父类对象不能赋值给子类对象

  3. 父类的指针/引用可以指向子类对象

  4. 子类的指针/引用不能指向父类对象(可以通过强制类型转换完成)

class Person
{
protected:
    string _name;
};

class Student : public Person
{
public:
    int _stunum;
};

int main()
{
    Person p;
    Student s;

    //1. 子类对象可以赋值给父类对象(切片 / 切割)
    p = s;
    //2. 父类对象不能赋值给子类对象
    //s = p;
    //3. 父类的指针 / 引用可以指向子类对象
    Person* p1 = &s;
    Person& r1 = s;
    //4. 子类的指针 / 引用不能指向父类对象(可以通过强制类型转换完成)
    Student* p2 = (Student*)&p;
    Student& r2 = (Student&)p;

    p2->_stunum = 10;    //程序崩溃
    r2._stunum = 20;
    return 0;
}

描述

继承体系的作用域

  1. 继承体系中基类和派生类都有独立作用域
  2. 子类和父类中有同名成员时,构成隐藏,子类成员会隐藏父类成员的直接访问。(在子类成员中,可以通过 基类::基类成员 来访问)
  3. 在实际继承体系中最好不要定义同名成员
class Person
{
public:
    string _name;
    int _stunum;
};

class Student : public Person
{
public:
    int _stunum;
};

int main()
{
    Person p;
    p._stunum = 10;
    Student s;
    //隐藏 重定义
    s._stunum = 20;
    s.Person::_stunum = 200;
    return 0;
}

描述

描述

知识拓展题:
第一题:

class Person
{
public:
    void f1()
    {
        cout << "Person::f1()" << endl;
    }
    string _name;
    int _stunum;
};

class Student : public Person
{
public:
    void f1(int i)
    {
        cout << "Student::f1()" << endl;
    }
    int _stunum;
};

int main()
{
    Student s;
    s.f1();
    return 0;
}
  • A.两个f1构成重载
  • B.两个f1构成隐藏
  • C.代码编不过
  • D.以上选项都不对

  此题正确答案应该是BC两个选项,Student类继承Person类,在Student类中出现两个同名的成员f1,两者构成隐藏,子类隐藏父类的同名成员,而重载是在同一作用域下的。所以此处的s.f1() 调用的应该是子类的f1(),但此处未给f1传参,所以代码编译不通过。

描述

第二题:

class Student : public Person
{
public:
    void f()
    {
        cout << "Student::f()" << endl;
    }

    void f1()
    {
        cout << "Student::f1()" << endl;
    }

    void f2()
    {
        cout << _stunum << endl;
        cout << "Student::f2()" << endl;
    }

    void f3()
    {
        _stunum = 3;
        cout << "Student::f3()" << endl;
    }

    void f4()
    {
        f();
        cout << "Student::f4()" << endl;
    }
    int _stunum;
};
int main()
{
    Student *p = NULL;
    p->f1();
    p->f2();
    p->f4();
    return 0;
}
  • A.代码编译不过
  • B.可以编译通过,程序会崩溃
  • C.可以编译通过,正常输出Student::f()
  • D.以上选项都不对

  1.p->f1() 选C,代码正常输出
  隐含的this指针虽然是空,但并未解引用,所以代码正常输出。
  2.p->f2() 选B,编译通过,程序会崩溃
  this指针指向空,this->_stunum 失败

描述

  3.p->f3() 选B,编译通过,程序会崩溃
  this指针指向空,this->_stunum 失败

描述

  4.p->f4() 选C,代码正常输出
  不会通过this指针去寻找f()这个函数,因为这个函数并不在对象上而是存在代码段上,同时隐含的this指针虽然是空,但并未解引用,所以代码正常输出。

描述

派生类默认成员函数

在继承关系中,若子类没有显式定义如下6个成员函数,编译系统会默认合成这六个默认成员函数:

  • 构造函数
  • 拷贝构造函数
  • 析构函数
  • 赋值运算符重载
  • 取地址操作符重载
  • const修饰的取地址运算符重载。
class Person
{
public:
    Person(const char* name)
        :_name(name)
    {
        cout << "Person()" << endl;
    }
    Person(const Person& p)
        :_name(p._name)
    {
        cout << "Person(const Person& p)" << endl;
    }
    Person& operator=(const Person& p)
    {
        cout << "Person& operator=(const Person& p)" << endl;
        if (this != &p)
        {
            _name = p._name;
        }
        return *this;
    }
    ~Person()
    {
        cout << "~Person()" << endl;
    }
protected:
    string _name;   //姓名
};

//合成
class Student :public Person
{
public:
    Student()
        :Person("aaaaa")     //在派生类中如果要初始化基类成员,应该调父类的构造函数(若父类构造函数是缺省的,此处也可以省略)   
        , _stunum(0)
    {
        cout << "Student()" << endl;
    }
    Student(const Student& s)
        :Person(s)
        , _stunum(s._stunum)
    {
        cout << "Student(const Student& s)" << endl;
    }
    Student& operator=(const Student& s)
    {
        cout << "Student& opertator = (const Student& s)" << endl;
        if (this != &s)
        {
            Person::operator=(s);    //此处的赋值运算符重载与父类的构成隐藏,若不指定作用域,则表示递归导致栈溢出
            _stunum = s._stunum;
        }
        return *this;
    }
    ~Student()
    {
        //Person::~Person();   //~Person()和~Student()构成隐藏,出了作用域会自动调父类析构,所以此处不用显式写出,为了满足先构造的后析构
        cout << "~Student()" << endl;
    }
protected:
    int _stunum;
};

int main()
{
    Student s;
    Student s1(s);
    Student s3;
    s1 = s3;
    return 0;
}

描述

面试题:
实现一个不能被继承的类

class AA
{
private:
    AA()
    {}
public:
    static AA* GetAAObj()
    {
        return new AA;
    }
    static AA GetAAOBj()
    {
        return AA();
    }
protected:
    int _aa;
};

class BB : public AA
{

};

  所用知识:私有的成员变量在子类中是不可见的,子类的构造函数是合成的。

实现一个只能在栈上生成对象的类

//方法一
class AA
{
private:
    AA(int aa = 0)
        :_aa(aa)
    {}
public:
    static AA GetAAOBj()
    {
        return AA();
    }
protected:
    int _aa;
};

int main()
{
    AA aa = AA::GetAAOBj();
    return 0;
}
//方法二
class AA
{
private:
    void* operator new(size_t size);
    void operator delete(void* p);
protected:
    int _aa;
};

AA aa;     //此方法的缺陷在于防止不了在静态区定义对象

int main()
{
    //AA* p = new AA;   //失败(operator new+构造)
    AA aa;
    return 0;
}

实现一个只能在堆上生成对象的类

class AA
{
private:
    AA(int aa = 0)
        :_aa(aa)
    {}
    //类的防拷贝
    //1.只声明不定义
    //2.声明成私有
    AA(const AA& aa);
public:
    static AA* GetAAObj()
    {
        return new AA;
    }
protected:
    int _aa;
};

int main()
{
    AA* p = AA::GetAAObj();
    //AA aa(*p);    //栈上定义的对象,若想要其定义失败,应使用类的防拷贝
    return 0;
}

构造若不用缺省的:

static AA GetAAObj(int aa)
{
    return AA(aa);
}

单继承&多继承

  1. 单继承——一个子类只有一个直接父类时称为单继承
  2. 多继承——一个子类有两个或以上直接父类时称为多继承

虚继承

//1.什么是菱形继承
//2.菱形继承有什么问题?(性能损耗)
//3.如何解决数据冗余和二义性(指定作用域、虚继承)
//4.虚继承是如何解决数据冗余和二义性?——内存对象模型

class A
{
public:
    int _a;
};

class B : virtual public A
{
public:
    int _b;
};

class C : virtual public A
{
public:
    int _c;
};

class D : public B,public C
{
public:
    int _d;
};

int main()
{
    cout << sizeof(D) << endl;   //非虚继承20,虚继承24
    D d;
    d.B::_a = 1;
    d.C::_a = 2;
    d._b = 3;
    d._c = 4;
    d._d = 5;
    return 0;
}

描述

  虚基表指针表现在B b = d; 时能够找到d中的_a。当公共部分比较大时(1000个字节),可以节省空间(多用8个字节而节省1000个字节)。

猜你喜欢

转载自blog.csdn.net/adorable_/article/details/80049380
今日推荐