Try   HackMD

研讀筆記: Achieving polymorphism in C

source

全文主要是翻譯原文,並記錄自己的閱讀。

Introduction

在這篇文章中我們的目的是建構一個指向 Shape 物件的指標陣列,每一個 Shape 指向物件 Circle、Square、Goat 等不同物件,皆以純 C 實現。

Airticle

先建立三個結構。

struct Square{ int width; int height; }; struct Circle{ float radius; }; struct Triangle{ int base; int height; };

印出來看看。

printf("size of square is %d\n", sizeof(struct Square)); printf("size of Circle is %d\n", sizeof(struct Circle)); printf("size of Triangle is %d\n", sizeof(struct Triangle));

結果如下。

//size of cube is 8 //size of circle is 4 //size of triangle is 8

可以注意到這邊的結構中的成員在記憶體中很巧妙的對齊。

以下的輸出結果是 1,印出了 Square 前面 4 個 bytes 的值。

struct Square square; square.width = 1; square.height = 2; printf("the first 4 bytes of square are %d", (*(int*)(&square)))

以下這個也是印出 1。

printf("the first 4 bytes of square are %d\n", square); //this works?

operand & 會給我們 square 這個結構一開始的記憶體位置,這個也能印出來,但是我們要把她轉成 int 的地址。

printf("the first 4 bytes of square are %d\n", &square);

這樣我們就可以印出來 int 用我們轉乘的 int 指標。

printf("the first 4 bytes of square are %d\n", (int*)&square);

這個也就是我們第一個範例做的事情,但是在我們印出來之前,可以選擇要移動指標。

printf("the second 4 bytes of square are %d\n", (*(int*)&square + 1));

接下來是 function pointers,C 對待 function pointer 就跟其他 member 一樣。底下是一個函式不用吃任何參數也不用回傳值。

void print_square( void ){ printf("Hello Square\n"); }

function pointer 宣告在結構當中。

struct Square{ int width; int height; //now for functions void (* print)( void ); float (* area)( struct Square * this ); };

就像剛剛提到的,我們要建立 Square 需要手動去設定每個成員對應的值,會需要 constructor。

void init_square( struct Square * square, int w, int h ){ (*square).print = print_square; (*square).width = w; (*square).height = h; (*square).area = calc_square_area; }

所有結構都要宣告。

struct Square square; struct Circle circle; struct Triangle triangle; init_square( &square, 2, 2 ); init_circle( &circle, 7.7 ); init_triangle( &triangle, 2, 3 ); square.print(); circle.print(); triangle.print(); printf("the area of the square is %f\n", square.area(&square)); printf("the area of the circle is %f\n", circle.area(&circle)); printf("the area of the triangle is %f\n", triangle.area(&triangle));

接下來,讓我們來建立結構 Shape,讓三個物件繼承他。

//abstract father class struct Shape{ void (* print)( void ); float (* area)( struct Shape * this ); };

如果幫剛剛的 Square 改成用 Shape 呼叫 print 函式,會發生甚麼事呢?

struct Shape * pointer = (struct Shape *)□ (*pointer).print(); //?? what is going to happen??

會得到 segmentation fault。那我們再回來討論這些結構的記憶體。

在 Shape 當中的 print 函式是在最開頭的 4 bytes,但是在 Square 當中是第三個 4 bytes。
當我們把一個指向 Square 指標轉成指向 Shape 的指標,記憶體是不變的(untouched),記住,轉型通常是不會改變內部記憶體的任何東西,除非是依些 dynamic casting。

現在讓我們來解決這個問題,填滿前面 8 bytes。

struct Shape{ char padd[8]; void (* print)( void ); float (* area)( struct Shape * this ); };

最後。

struct Shape * shapes[3]; shapes[0] = (struct Shape *)&square; shapes[1] = (struct Shape *)&circle; shapes[2] = (struct Shape *)&triangle; int i; for(i=0; i<3; ++i){ (*shapes[i]).print(); printf("%f\n\n", (*shapes[i]).area(shapes[i])); }

這就是很基本的用 OOP 觀念實作多形(polymorphism)。