Tangwx

Tangwx

博客网站

C語言實現封裝繼承多態

一文搞懂怎麼用 C 實現封裝、繼承、多態#

1、介紹#

封裝、繼承、多態是面向對象的重要特性,有了它們再結合一些設計模式可以讓我們的代碼變得高內聚、低耦合,增加代碼的安全性、可讀性、可維護性。那么對於 C 語言這種面向過程的編程語言,能不能也實現封裝、繼承、多態,從而提升代碼的質量呢?答案是顯而易見的,我們在linux內核代碼的學習過程中,用心分析,你會發現裡面已經廣泛利用 C 語言實現封裝、繼承、多態。本篇文章會帶著大家分析一下,怎麼用 C 實現這些面向對象的特性,為大家後面看代碼、寫出高質量的代碼做鋪墊。

2、封裝#

封裝就是把一個抽象的事物的屬性和相應的操作方法打包到一個類中,通過內部的方法來改變內部狀態。封裝的本質是隱藏信息的過程,使對象的內部狀態不被外界直接訪問和修改。

封裝具有如下優點:

  • 提高了代碼的安全性,數據只能被規定的方式訪問,避免了誤操作和非法訪問。
  • 提高了代碼的復用性,相同或類似的數據類型可以被多次利用。
  • 提高了代碼的可維護性,當數據類型發生變化時,只需修改一個地方即可。

C 語言中沒有類的概念,但是可以使用結構體實現對事物的封裝。封裝的重要意義是,將函數(方法)和數據(屬性)整合在一起,數據(屬性)和數據(屬性)整合在一起。這樣,我們就可以通過簡單的一個結構指針訪問到所有的數據,操作所有的函數。

以下便是一個封裝的具體例子:

#include <stdio.h> 

typedef struct human
{ 
    int age; 
    char sex; 
    void (*set_age)(struct human *p, int age);
    int (*get_age)(struct human *p);
    void (*set_sex)(struct human *p, char sex);
    char (*get_sex)(struct human *p);
} Human; 

void set_age(Human *p, int age) 
{ 
    p->age = age; 
} 

int get_age(Human *p) 
{ 
    return p->age; 
} 

void set_sex(Human *p, char sex)
{ 
    p->sex = sex; 
} 

char get_sex(Human *p) 
{ 
    return p->sex; 
} 

int main() 
{ 
    Human p; 
    p.set_age = set_age; 
    p.set_age(&p, 18); 
    p.set_sex = set_sex; 
    p.set_sex(&p, 'M');
    p.get_age = get_age; 
    printf("age: %d\n", p.get_age(&p));
    p.get_sex = get_sex;  
    printf("sex: %c\n", p.get_sex(&p)); 

    return 0; 
}

我們定義了一個 “human” 的結構體,裡面包含了 “age”/“sex” 成員及 “set_age”/“get_age”/”set_sex”/”get_sex” 函數用於實現設置、獲取年齡及性別。這就是封裝,結構體的數據及函數就能實現 human 的屬性及方法操作,另外只有通過結構體中的操作函數才能實現結構體內的數據的更改。

3、繼承#

繼承就是基於一個已有的類(父類或者基類),再創建一個新的類,這個類被稱為子類或者派生類。子類或者派生類可以訪問父類的數據及函數,從而避免重複編寫代碼。子類也可以添加自己的屬性和數據。

繼承具有如下優點:

  • 提高了代碼的復用性,可以避免重複編寫相同的代碼。
  • 提高了代碼的可擴展性,可以支持基於現有類,然後再細微地調整。
  • 提高了代碼的可讀性,繼承使代碼更加簡潔明瞭。

在 C 語言裡,可以通過結構體嵌套的方式,實現類的繼承(這裡指的是單繼承,暫不考慮多繼承),但是需要確保父類結構體引用需要放在子類結構體成員的第一個位置。這樣,不論是數據的訪問,還是強轉都不會有什麼問題。

以下便是一個繼承的具體例子:

#include <stdio.h>
#include <stdlib.h>

typedef struct human {
    int age;
    char sex;
} Human;

typedef struct person{
    Human human;
    char *name;
} Person;

Person* create_person(int age, char sex, char *name) {
    Person* cperson = (Person*) malloc(sizeof(Person));
    cperson->human.age = age;
    cperson->human.sex = sex;
    cperson->name = name;
    return cperson;
}

int main() {
    Person* cperson = create_person(18, 'w', "lucy");
    printf("(%d, %c) - name: %s\n", cperson->human.age, cperson->human.sex, cperson->name);
    free(cperson);
    return 0;
}

上述代碼中,我們定義了兩個結構體”Human” 和”Person”。“Person” 包含 “Human” 結構體以及成員變量 “name”。通過函數 “create_person ()” 構造一個 “Person” 類型的結構體,並為其中的 “Human” 成員以及 “name” 成員賦值。當需要在其他地方使用繼承的特性時,可以使用類似的嵌套結構體的方式來實現。

4、多態#

多態是面向對象編程中最為核心的概念,它允許我們在不同的對象上執行相同的操作,從而實現靈活的操作處理。

多態具有如下優點:

  • 提高了代碼的可擴展性,可以支持不同類型的對象,使程序更加靈活。
  • 提高了代碼的可維護性,當類型發生變化時,只需要對相應的類進行修改即可。
  • 提高了代碼的可讀性,多態使代碼更加簡潔易讀。

C 語言中對於多態的實現,我們可以借助函數指針,利用同一的接口處理不同的數據。具有不同功能的函數可以用同一個函數名,這樣可以用一個函數名調用不同功能的函數。

以下便是一個多態的具體例子:

#include <stdio.h>

typedef struct shape {
    void (*draw)(struct shape*);
} Shape;

typedef struct {
    int x;
    int y;
    int radius;
    Shape base;
} Circle;

typedef struct {
    int x1;
    int y1;
    int x2;
    int y2;
    Shape base;
} Line;

void drawCircle(Shape* shape) {
    Circle* circle = (Circle*) shape;
    printf("Circle at (%d, %d) with radius %d\n", circle->x, circle->y, circle->radius);
}

void drawLine(Shape* shape) {
    Line* line = (Line*) shape;
    printf("Line from (%d, %d) to (%d, %d)\n", line->x1, line->y1, line->x2, line->y2);
}

int main() {
    int i; 

    Circle circle = {
        .x = 1,
        .y = 5,
        .radius = 10,
        .base = { .draw = drawCircle }
    };

    Line line = {
        .x1 = 2,
        .y1 = 3,
        .x2 = 7,
        .y2 = 9,
        .base = { .draw = drawLine }
    };
    
    /* //上述兩段代碼與下面操作是一致的
	Circle circle;
    circle.x = 1;
    circle.y = 5;
    circle.radius = 10;
    circle.base.draw = drawCircle;

    Line line;
    line.x1 = 2;
    line.y1 = 3;
    line.x2 = 7;
    line.y2 = 9;
    line.base.draw = drawLine; 
	*/

    Shape* shapes[2];
    shapes[0] = (Shape*)&(circle.base);
    shapes[1] = (Shape*)&(line.base);

    for (i = 0; i < 2; i++) {
        shapes[i]->draw(shapes[i]);
    }

    return 0;
}

在上面的例子中,我們定義了一個 “Shape” 結構體和它的一個成員函數 “draw”,此外還定義了兩個從 “Shape” 派生的子結構體 “Circle” 和 “Line”,並分別實現了它們自己 “draw” 函數。

通過聲明讓”Shape” 中的”draw” 函數成為了一個函數指針,指向一個參數為”void*” 類型的函數。這讓我們可以在”draw” 函數中動態地處理不同的子結構體類型。

我們用”typedef” 將結構體命名為”Shape”,以便在”Circle” 和”Line” 結構體中使用它們。最後,我們在主函數中創建了一個”Circle” 和一個”Line” 對象,並將它們存儲在”Shape*” 類型的數組中。使用一個循環來遍歷數組,對每個形狀對象調用它們自己的”draw” 函數進行處理,這樣便實現了多態的效果。

載入中......
此文章數據所有權由區塊鏈加密技術和智能合約保障僅歸創作者所有。