原文:https://www.linuxidc.com/Linux/2016-12/138789.htm
有關:《C語言:過年回家 發現只有我沒有對象》
一、基礎研究
觀察如下兩個程序a.c和b.c:
A.c
#define screen ((char far*)0xb8000000) typedef strct c { char chr; char color; void (*put)(struct c*, int,int ); }ch; void f(ch*, int, int); int main(void) { int n; ch a; a.chr='c'; a.color = 2; a.put =f; a.put(&a,10,40); return; } void f(ch* p, int row, int col) { screen[(row-1)*160+(col-1)*2] = p->chr; screen[(row-1)*160+(col-1)*2+1] = p->color; return; }
B.c
#define screen ((char far*)0xb8000000) typedef strct c { char chr; char color; void(*setch)(struct c*, char); void(*setcolor)(struct c*, char); void (*put)(struct c*, int,int ); }ch; void f(ch*, int, int); void f1(ch*, int, int); void f2(ch*, int, int); int main(void) { int n; ch a; a.put =f; a.setch =f1; a.setcolor =f2; a.setch(&a,'c'); a.setcolor(&a,2); a.put(&a,10,40); return; } void f(ch* p, int row, int col) { screen[(row-1)*160+(col-1)*2] = p->chr; screen[(row-1)*160+(col-1)*2+1] = p->color; return; } void f1(ch* p, char) { p->chr = a; return; }
這兩個程序都是要實現在屏幕上第10行40列打印一個綠色的字符c:
這兩個程序的數據組織方式是一樣的,都是使用結構體,而且對共性和個性的分離的思路也是一樣的,都是將共性封裝在main函數里,將個性實現在子函數里。
但是a.c和b.c封裝和分離的角度是不一樣的:
a.c沒有將字符和顏色的屬性賦值分離出來,而只是將顯示功能分離出來,
b.c將字符、顏色的賦值和顯示功能都分離了出來,用三個子函數實現,並將相對應的函數指針封裝到結構體里去。
面向對象程序設計的一條基本原則是計算機程序是由單個能夠起到子程序作用的單元或對象組合而成,也就是說我們要盡量把功能以子函數的形式實現。
所以在這里雖然a和b的設計思想是相同的,但是b.c的封裝性要比a.c的封裝性更好。
再來看下一個程序:
現在要在?處添加語句,使程序能夠實現功能。
這里ch * a=new (ch);的功能應該與ch a;相同,即定義一個struct c型的結構體變量a。但是我們用ch a;是開辟了一個ch大小的空間並把它命名為a,而這里ch * a=?只是對一個指針進行了賦值,我們一般對指針賦值只是把一個地址給它,並沒有開辟空間,但是我們要實現ch a;的功能,必須要在這一句里對該地址開辟空間。現在的問題就是:怎么在給指針賦值時開辟內存空間?
我們知道數組在定義時可以開辟空間,但是數組定義需要單獨的一句,而這里需要直接作為右值使用,所以這里需要動態地開辟空間。我們最常用的動態內存分配方法就是使用malloc函數,這個函數有一個參數,是要開辟的空間字節數,在這里我們要開辟的空間大小是結構體a的大小,但是我們不知道結構體a的大小,所以我們要用sizeof得出它的大小。用malloc開辟空間后再將其轉換成結構體指針賦給a,程序如下:
我們之前使用過宏定義,但是在程序中是宏名直接替換掉后面的東西的,而這里宏有參數x,所以它是帶參宏定義,它的格式為:#define 宏名(形參表) 字符串。這里的x就是一個形參。所以我們要在使用時在宏名后面傳入實參。
這里的宏名是new,學過java我們會發現java里初始化對象也是使用new,這里的new其實也是實現一個相似的功能。我們可以把結構體ch理解成一個類,用new對它進行實例化,這樣就可以實現面向對象的程序設計思想。其實java里實例化對象也是開辟一個內存空間並給這個空間取一個名字即對象名。結構體為什么可以實現類的功能呢?我們知道,類里面可以定義變量、數組、函數,並進行一些操作如賦值、調用函數之類的,只是在java中類里面程序員不能定義和使用指針進行操作。而結構體里面也可以進行定義變量、數組、函數指針等的操作,所以如果我們要用c語言編寫具有面向對象思想的程序,我們可以用結構體來實現類似“類”的功能,並用帶參宏定義來實現實例化的功能,或者可以直接用malloc函數來實現實例化,只不過這樣語句比較重復。
雖然我們可以在c語言里面用這種方法實現面向對象的程序設計,但是這樣畢竟不如用java之類的比較適合面向對象的語言來寫有面向對象思想的程序。因為java的類里可以進行賦值、調用函數等功能而c里的結構體不能。java取消了程序員使用指針的權限,因為如果在這種高度封裝的語言里使用指針很可能造成很多錯誤。
從這里看,面向對象和面向過程程序設計思想的區別在哪里呢?面向對象的程序可能需要更多的封裝,它的每一個對象都是為執行特定的功能而封裝的,對象與對象之間相對比較獨立,關系清晰,便於程序的功能細化、管理維護,但是也會造成程序的代碼量增大。面向過程的程序封裝的主要是一些數據結構,一個函數、變量可以被以多種角度來使用,這樣使程序變得十分精簡短小,但是不容易修改和補充。
我們寫程序是用來解決問題的,而且要解決的是現實中的問題,所以我們需要將現實問題轉化為符號化的問題,而現實中的問題是由個體所組成的,所以我們將數據和處理數據的方法封裝起來形成一個個體,這個個體在問題里面有專門的功能,比如一張紙可以折疊,一支筆可以寫,這樣有助於我們以自身的角度進行思考分析,這就是面向對象。如果用面向過程的思路,會導致問題與程序之間的轉化不好處理,可能使解決問題出現偏差。
二、擴展研究
1、動態開辟內存空間的函數有哪些?
答:c語言有三個函數可以動態開辟數組:malloc函數、calloc函數、realloc函數。
c語言提供了malloc函數和free函數用來執行動態內存分配和釋放,這些函數維護一個可用內存池,malloc函數可以從內存池中提取一塊合適的內存,free函數用來釋放這塊內存以供別的程序使用。Malloc函數分配的是一塊連續的內存,返回值是一個指向被分配的內存塊起始位置的指針。Malloc實際分配的內存可能比你請求的的多一點,也可能不會,這是由編譯器決定的。但是malloc也可能分配失敗,如果操作系統無法向malloc函數提供足夠的可用內存,那么它會返回一個NULL指針。Malloc返回的指針類型為void *型。Free的參數必須要么是NULL,要么是malloc函數、calloc函數、realloc函數返回的值。
Calloc函數的參數是所需元素的數量和每個元素的字節數,而不是總的字節數。Calloc會把分配的內存都初始化為0,而malloc不會初始化。
Realloc函數用於修改一個原先已分配的內存塊的大小,如果原先的內存塊大小無法改變,那么realloc會分配另一塊正確大小的內存,並把原先那塊內存的內容復制到新的塊上。如果realloc的第一個參數為NULL,那么它的作用和malloc一樣。
三、研究總結
這一章里我們學習了動態分配內存的方法,以及怎么使用宏定義,其實它們都是為了更好地進行封裝。為了對程序進行更好地封裝,人們使用了各種方式,甚至開發了封裝性更強的高級語言,這使我們解決專門問題的能力更強了。這樣我們編程只是將共性實現為個性。因為語言只是工具,程序員應該更專注地研究算法而不是把時間花在語言上,所以現在的語言都是為了簡化程序員的工作所造成的。
我們封裝的過程,是對事物進行抽象的過程,也是對事物進行認識的過程,我們從開始到現在,封裝的層次越來越深,處理的問題也越來越復雜。因為我們需要理清復雜問題的內部規律,從而找出解決問題的辦法,而深層次的封裝使問題恢復成本來的樣子就是一種解決辦法,當封裝的程度達到了一定的水平,就是面向對象的程序設計思想。