面向对象比较

我们将从封装,继承,多态这三个面向对象编程核心特点来展开。

封装

访问控制

C++ & PHP

我们可以根据访问权限总结出不同的访问类型,如下所示:

语言访问publicprotectedprivate
C++同一个类yesyesyes
PHP同一个类yesyesyes
C++派生类(不管是何种继承方式(public、protected或private))yesyesno
PHP派生类(重定义基类的公有和保护成员在派生类中是何种成员)yesyesno
C++外部的类yesnono
PHP外部的类yesnono

派生类可以访问基类中所有的非私有成员。若基类成员如果不想被派生类的成员函数访问,则应在基类中声明为 private。

Golang

只会根据是否在同一包与成员(成员变量与成员函数)的首字母是否大小写来进行控制:

语言访问成员首字母大写成员首字母小写
Golang同一个Packageyesyes
Golang不同Packageyesno

继承

C++

C++继承的时候,可以通过继承方式(public、protected或private)设置基类的公有和保护成员在派生类中是何种成员:

1
2
3
4
class <派生类名>:<继承方式1><基类名1>,<继承方式2><基类名2>,
{
<派生类类体>
};
  • 对于成员函数继承:一个派生类继承了所有的基类方法,除了:
    • 基类的构造函数、析构函数和拷贝构造函数。
    • 基类的重载运算符。
    • 基类的友元函数。

PHP

php继承的时候,没有类似继承方式, 而是使用public和protected进行重定义基类的公有和保护成员在派生类中是何种成员。 方法,属性和常量的 可见性 可以放宽,例如 protected 方法可以标记为 public, 但不能增加限制,例如标记 public 属性为 private。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14

class <基类名> {
    public $public = 'Public';
    protected $protected = 'Protected';
    private $private = 'Private';
}

class <派生类名> extends <基类名> {
    // 可以对 public 和 protected 进行重定义,但 private 而不能
    public $public = 'Public2';
    protected $protected = 'Protected2';

    <派生类类体>
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45

<?php

class FatherClass
{
    public $public = 'Public';
    protected $protected = 'Protected';
    private $private = 'Private';

    function printHello()
    {
        echo $this->public;
        echo $this->protected;
        echo $this->private;
    }
}

/**
 * Define MyClass2
 */
class SonClass extends FatherClass
{
    // 可以对 public 和 protected 进行重定义,但 private 而不能
    public $public = 'Public2';
    protected $protected = 'Protected2';

    function printHello()
    {
        echo $this->public;
        echo "\n";
        echo $this->protected;
        echo "\n=========================\n";
        // echo $this->private;
    }
}

$son = new SonClass();
echo $son->public; // 这行能被正常执行

echo "\n";

// echo $son->protected; // 这行会产生一个致命错误
// echo $son->private; // 未定义 private
$son->printHello(); // 输出 Public2、Protected2 和 Undefined
?>

多态

多态内部实现

C++

gdb编译器的一种实现: 20230205150439

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
#include<cstdio>

class Cup{
    protected:
        int capacity;
    public:
        Cup(int c){
            capacity = c;
        }
        virtual int Capacity(){
            printf("Parent class Capacity:%d \n", capacity);
            return 0;
        }
        virtual void Name(){
            printf("Cup\n");
        }
        void NonVirtualFun(){
            printf("NonVirtualFun\n");
        }
};

class PlasticCup : public Cup{
    public:
        PlasticCup(int c):Cup(c){}
        int Capacity(){
            printf("PlasticCup class Capacity:%d \n", capacity);
            return 0;
        }
        void Name(){
            printf("Plastic Cup\n");
        }
        virtual void Brand(){
            printf("wahaha\n");
        }
};

class Glass: public Cup{
    public:
        Glass(int c):Cup(c){}
        int Capacity(){
            printf("Glass class Capacity:%d \n", capacity);
            return 0;
        }
        //void Name(){
        //  printf("Glass \n");
        //}
        virtual void PlaceOfOrigin(){
            printf("China\n");
        }
};

void uncommonPrint(Cup* cup){
    printf("\n-----\n");

    typedef void (*NonVirtualFun_FUN)(void*);
    typedef int (*Capatity_FUN)(void*);
    typedef void (*Name_FUN)(void*);
    typedef void*ADRESS;

    NonVirtualFun_FUN foo=(NonVirtualFun_FUN)(&Cup::NonVirtualFun);//bar成员函数为非虚函数
    foo((void*)cup);

    ADRESS *vtable_addr=*((ADRESS**)(cup));
    Capatity_FUN fooCapacity=(Capatity_FUN)*(vtable_addr);
    fooCapacity((void*)cup);

    Name_FUN nameFoo=(Name_FUN)*(vtable_addr+1);
    nameFoo((void*)cup);

    printf("----\n\n");
}

void routinePrint(Cup*c){
    c->NonVirtualFun();
    c->Capacity();
    c->Name();
}

int main(){
    Cup* cup = new PlasticCup(3);
    routinePrint(cup);
    uncommonPrint(cup);
    delete cup;

    printf("\n===========================\n");

    cup=new Glass(5);
    routinePrint(cup);
    uncommonPrint(cup);
    delete cup;
    
    return 0;
}
  • https://onlinegdb.com/HORLICnnI

  • 从这里可以看到,设计的缺点:

    • 每个子类对象都需要建立一张虚表,非常的冗余。
    • 多个虚表无法做缓存。

Golang

Golang的多态实现设计(点我查看)

Golang的多态设计解决了上述C++遗存的问题,当类与接口类型确定后,类如有多个对象只会保存全局唯一的itab,此时可以很方便进行缓存。

其他与对象编程相关的杂项

static关键字

static 除了可以声明静态成员变量,还可以声明静态成员函数

静态成员函数与普通成员函数的根本区别在于:普通成员函数有 this 指针,可以访问类中的任意成员;而静态成员函数没有 this 指针,只能访问静态成员(包括静态成员变量和静态成员函数)。

C++

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50

#include <iostream>
using namespace std;

class Student{
public:
    Student(char *name, int age, float score);
    void show();
public:  //声明静态成员函数
    static int getTotal();
    static float getPoints();
private:
    static int m_total;  //总人数
    static float m_points;  //总成绩
private:
    char *m_name;
    int m_age;
    float m_score;
};

int Student::m_total = 0;
float Student::m_points = 0.0;

Student::Student(char *name, int age, float score): m_name(name), m_age(age), m_score(score){
    m_total++;
    m_points += score;
}
void Student::show(){
    cout<<m_name<<"的年龄是"<<m_age<<",成绩是"<<m_score<<endl;
}
//定义静态成员函数
int Student::getTotal(){
    return m_total;
}
float Student::getPoints(){
    return m_points;
}

int main(){
    (new Student("小明", 15, 90.6)) -> show();
    (new Student("李磊", 16, 80.5)) -> show();
    (new Student("张华", 16, 99.0)) -> show();
    (new Student("王康", 14, 60.8)) -> show();

    int total = Student::getTotal();
    float points = Student::getPoints();
    cout<<"当前共有"<<total<<"名学生,总成绩是"<<points<<",平均分是"<<points/total<<endl;

    return 0;
}

PHP

由于静态方法不需要通过对象即可调用,所以伪变量 $this 在静态方法中不可用。

静态成员变量使用 范围解析操作符 ( :: )访问,不能通过对象操作符( -> )访问。

构造函数和析构函数

构造函数: __construct(mixed ...$values = ""): void 具有构造函数的类会在每次创建新对象时先调用此方法,所以非常适合在使用对象之前做一些初始化工作。

注意: 如果子类中定义了构造函数则不会隐式调用其父类的构造函数。要执行父类的构造函数,需要在子类的构造函数中调用 parent::__construct()。如果子类没有定义构造函数则会如同一个普通的类方法一样从父类继承(假如没有被定义为 private 的话)。

析构函数: __destruct(): void

PHP 有析构函数的概念,这类似于其它面向对象的语言,如 C++。析构函数会在到某个对象的所有引用都被删除或者当对象被显式销毁时执行。

和构造函数一样,父类的析构函数不会被引擎暗中调用。要执行父类的析构函数,必须在子类的析构函数体中显式调用 parent::__destruct()。此外也和构造函数一样,子类如果自己没有定义析构函数则会继承父类的。