跳转至

Composition and Inheritance

约 958 个字 164 行代码 预计阅读时间 5 分钟

Composition

组合,类里边可以有其他类的对象,组合是“has-a”关系,初始化的时候用它们自己的构造函数初始化,但是都要放在初始化列表中进行。

Inheritance

在类的关系上扩展,继承是“is-a”关系,子类可以使用父类的成员函数和成员变量,父类的构造函数在子类的构造函数中调用。

被继承的东西叫做Base Class(基类),继承的叫做Derived Class(派生类),派生类可以重载基类的成员函数。

C++
#include <iostream>

using namespace std;

struct A {
    int x, y;
}

struct B {
    A a; // 组合, B 拥有一个 A
};

struct C : public A { // 继承,C 是 A
};

int main() {
    B b;
    b.a.x = 1;

    C c;
    c.x = 2; // A 里面有的东西 C 里边都有
}

在基类中是 privete 的话,派生类是不能访问的,但是可以通过基类的成员函数 setdata 访问。

但是如果我们想让这个函数只能在基类和派生类中使用的话,就需要 protected 关键字。

C++
#include <iostream>

using namespace std;

struct Base
{
private:
    int data;

public:
    Base(int d) : data(d) { cout << "Base::Base()" << endl; }
    ~Base() { cout << "Base::~Base()" << endl; }
    void print() { cout << "Base::print() " << data << endl; }

protected:
    void setdata(int d) { data = d; }
};

struct Derived : public Base
{

    Derived() : Base(0) { cout << "Derived::Derived()" << endl; }

    void foo()
    {
        setdata(10); // 只能在基类和派生类中使用
        print();     // 也可以访问基类的成员函数
    }
};

int main()
{
    Derived d;
    d.foo();       // 访问派生类的成员函数
    d.setdata(20); // 会出错,因为是在外界访问
    d.print();     // 访问基类的成员函数
    return 0;
}
specifiers Within same class in derived class outside the class
private Yes No No
protected Yes Yes No
public Yes Yes Yes

在执行构造函数的时候,一定是先执行基类的构造函数,然后才是派生类的构造函数。

Name Hiding

如果在派生类中定义了一个和基类同名的成员函数,那么在派生类中调用这个函数的时候,调用的是派生类的函数,而不是基类的函数,如果想要调用基类的函数,可以用作用域解析运算符来调用。

Access Protection

  • struct 默认所有的东西都是 public
  • class 默认所有的都是 private

friend

friend 可以打破前边的访问限制,可以把访问的控制权授权给别人。

授权的对象可以是:

  • a free function(普通的函数)
  • a member function of another class(对象的成员函数)
  • an entire class(甚至可以是整个对象)
C++
struct X {
    private :
        int i;
    public:
        void initialize();
        friend void g(X*, int); // Global friend
        friend void h();
        friend void Y::f(X*);   // Structure member friend
        friend struct Z;    // Entire struct
};

void X::initialize() {
    i = 0;
}

void g(X* x, int i) {
    x->i = i;
}

void h() {
    X x;
    x.i = 100;
}

void Y::f(X* x) {
    x->i = 47;
}

void Z::g(X* x) {
    x->i += j;
}

一个比较容易的点是,类的 private 是针对谁而言的。

考虑如下的代码:

C++
#include <iostream>

using namespace std;

struct base
{
public:
    base(int i) : data(i) { cout << "base::base()" << endl; }
    ~base() { cout << "base::~base()" << endl; }
    void print() { cout << "base::print()" << data << endl; }
    void foo(base *other) { cout << "base::foo(), other->data= " << other->data << endl; }

private:
    int data;

protected:
    void setdata(int i) { data = i; }
};

int main()
{
    base b1(11), b2(12);
    b1.foo(&b2);
}

我们会发现输出是

C++
1
2
3
4
5
base::base()
base::base()
base::foo(), other->data= 12
base::~base()
base::~base()

我们的 b1 可以通过 foo() 函数访问到 b2data 私有成员变量。

这就表示 private 是针对整个类而说的,而不是某个对象,所以类的成员函数可以自由的访问其他对象的 private 成员变量。

还有一件关于对象需要了解的事情:

C++
#include <iostream>

using namespace std;

struct Base
{
    public:
        int data;
        Base() : data(10) {}
};

struct derived : public Base {

};

int main()
{
    Base b;
    derived d;
    cout << b.data << "," << d.data << endl;
    cout <<sizeof(b) << "," << sizeof(d) << endl;

    int * p = (int *)&b;
    cout << p << ", " << *p << endl;

    p = (int *)&d;
    cout << p << ", " << *p << endl;
}

得到输出:

Bash
1
2
3
4
10,10
4,4
0x16fd62ebc, 10
0x16fd62eb8, 10

能看到两个对象的大小,数据都是一样的,地址相邻。

如果我们在后边加几行代码:

C++
1
2
3
*p = 200;
cout << d.data << endl;
cout << b.data << endl;

我们发现 d.data 变成了 200, b.data 还是 10。

如果我们在派生类 Derived 中增加一个字段

C++
struct derived : public Base
{
    private :
        int i;
    public:
        derived() : i(30) {}
        void printi() {
            cout << "Derived :: i = " << i << endl;
        }
};

然后输出,我们可以发现 i = 30, 同时 d 的大小变成 8 了,因为多了一个 int

如果我们做 p++, p 会指到 i 的地址上,然后我们进行一些操作:

C++
1
2
3
4
5
p++;
cout << p << ", " << *p << endl;

*p = 50;
p.printi();

发现 i 的值被改成了 50。但是 iprivate 的,我们却在类的外部修改了它,这个东西在 C/C++ 中是管不住的,只要有指针就可以对它修改。

同时我们在继承的时候是可以在基类前加关键字的:

C++
1
2
3
class derived1 : private Base {}
class derived2 : protected Base {}
class derived3 : public Base {}

这三个点效果是不同的:

inheritance type public members in A protected members in A private members in A
class B : private A {} private in B private in B not accessible
class B : protected A {} protected in B protected in B not accessible
class B : public A {} public in B protected in B not accessible

其中最常用的是 public 继承。public 在语义上是 is-a 关系,如果 B is a A,那么就可以用 public 继承。

Upcasting

向上转型:假如有一个 student,他是一个 human_begin,那么我们就可以把他向上转型成 human_begin

C++
1
2
3
Manager pete("Pete", "444-55-6666", "Bakery");
Employee * ep = &pete; // Upcasting
Employee & er = pete; // Upcasting

我们有两种方法实现向上转型,一种是通过指针,拿到 pete 的地址,赋给一个 Employee 的指针,另一种是通过引用,直接赋给一个 Employee 的引用。

如果此时调用 ep 的成员函数,调用的是 Employee 的成员函数,而不是 Manager 的成员函数。

C++
#include <iostream>

using namespace std;

class Manager {
public:
    Manager () {
        cout << "Manager::Manager()" << endl;
    }
    ~Manager () {
        cout << "Manager::~Manager()" << endl;
    }
    void print() {
        cout << "Manager::print()" << endl;
    }
};

class Employee {
public:
    Employee () {
        cout << "Employee::Employee()" << endl;
    }
    ~Employee () {
        cout << "Employee::~Employee()" << endl;
    }
    void print() {
        cout << "Employee::print()" << endl;
    }
};

int main()
{
    Manager m;
    //Upcasting
    Employee *e = (Employee *)&m; // Upcasting
    e->print(); // Calls Employee::print()
}

我们会发现程序输出的是 Employee::print(),而不是 Manager::print()

如果我们希望调用的是实际的对象的成员函数,而不是指针类型的成员函数,这就要说到多态了。

评论