顯示具有 c 標籤的文章。 顯示所有文章
顯示具有 c 標籤的文章。 顯示所有文章

3月 03, 2011

物件導向的特性 (OOP)

如果人家問 OOP 的三大特性,那麼就寫:

1. 資料抽像化 (Data Abstraction)
將要處理的資料及作用在這些資料上的操作方法,封裝在一個語法單元中,例如像:類別(class),以達到資訊隱藏的功能。

2. 繼承(inheritance)
一個新的類別,可以定義為一個現有類別的子類別,這個現有類別稱為父類別,而子類別可以繼承父類別的資料與方法。

使用繼承的好處,父類別的程式碼可以在子類別中重複使用,可以節省開發時間。因為父類別為子類別的一般化,而子類別則是父類別的特殊化。

3. 多型 (動態繫結)
同一個函數呼叫,在執行時期可以繫結到不同的函數定義。

以動態繫結的方式來決定「程序呼叫」與「程序定義」之間的繫結。對於類別中的方法呼叫,與其方法的定義為動態繫結,是在程式執行時才能真正決定要叫用的方法為何。

在 c++ 中可透過「虛擬函數(virtual function)」來達到動態繫結的功能,
父類別的 virtual function 在子類別中重新定義後,則在呼叫虛擬函數時
會依照訊號的接收個體為何,決定執行哪個類別中定義的虛擬函數。
note: 透過繼承與動態繫結,可用來實現物件導向中的多型概念。
4. 抽象資料型態 (ADT,Abstract Data Type)
是使用資料抽象化的方法建立的自訂資料型態

5. 封裝
主要是將物件的內外部份分開,其他物件只能藉由外部界面,取得資料,
物件內部的細節資料則隱藏起來,其他物件無法瞭解此物件的內部細節。

6. 覆載 (Overloading) 屬於靜態繫結,編譯時就會知道
函式覆載是指,相同的函式名稱,但能有不同的函式定義。不過這同名函式,必須具有不同的參數個數,編譯程式才可以此為區分,決定哪一個 overloaded function 才是真正要執行的函數。

7. 覆寫 (Overriding)
子類別將由父類別繼承來的屬性變數、資料結構或方法重新定義的動作。

2月 13, 2011

c 語言中 #define 與 Preprocessor

#define 的用途:

1. 定義巨集 #define sqr(x) (x)*(x)
2. 定義符號 symbol 例如: #define PI 3.14

所以 #define PI 3.14 與 float PI=3.14 有何不同?

ans:

使用 define 只是定義了一個 symbol 不佔memory 空間
在編譯前,預先處理器(Preprocessor) 先進行處理,將程式碼中出現的 PI
代換成 3.14 後再丟給編譯器進行 compile。

而使用 float 宣告的變數,會佔記憶體空間(4bytes) 其值為 3.14

--

那麼談談 Preprocessor 的用途吧

1. 引入 inclusion file → #include
2. 處理 conditional compilation → #ifdef / #if / #endif ...
3. macro 代換成 code → #define PI 3.14

1月 17, 2011

reference and lvalue

int num [] = {1,2,3,4};
int *ptr = num;
cout << &(ptr++); /* ERROR: & need l-value */
cout << &(++ptr); /* 正確 */
ptr++ 會把 ptr 的原值塞到一個「暫存變數」之後 ptr 的值再往下一格。所以當使用 &(暫存變數) 要取位址就錯了。

++ptr 則是把 ptr 向下移一格後,去取 ptr 變數的位址,所以編譯可以通過。

使用 new 動態建立二維陣列



1. 使用 new 來建立出 data[m][n] 二維陣列
int **data;
data = new int* [m]; /* 宣告一個陣列,其元素都是指標 */
for(int i = 0; i< m ; i++)
data[i] = new int[n];
2. 亂塞一些值(1~100 亂數)到陣列中
for (int i=0; i<m; i++)
for (int j=0; j<n; j++)
data[i][j] = 1+ rand() % (100-1+1);
3. 不用時要記得歸還空間
for(i=0; i<m; i++)
delete [] data[i];
delete [] data;

6月 11, 2010

const 與 pointer 間的關係

底下幾個狀況描述 const 與 pointer 間位置擺放所代表不同的意義:

(1) const int *ptr
宣告一個 pointer 指向 const int,所以無法藉由指標更改變數值
const int foo = 5;
int *ptr = &foo;
*ptr = 10; /* 這是錯的 */
(2) int const *ptr
情況同上,宣告 pointer 指向 const int。

(3) int* const ptr
在這個用法中,const 用來描述 ptr 變數,所以是宣告一個 const pointer,表示 pointer 初始時就決定要指向誰,之後不能再更改。
int foo, bar;
int* const ptr = &foo; /* ptr 指向 foo 從一而終,不能再更改 */
ptr = &bar; /* 這是錯的 */

5月 16, 2010

Cyclomatic complexity

關於 Cyclomatic complexity 一詞可參考 Wikipedia 上的解說。

中譯名稱是「循環複雜度」或者又稱為「迴圈複雜度」。主要是用來描述一個程式「條件分支」的複雜度,因為愈單純的 If-condition 愈容易讀懂,除錯時也較好發現問題所在。所以複雜度的數值,愈低愈好。在下面兩個情形複雜度的值都會加一:
  1. if / while
  2. switch-case
下列程式的 Cyclomatic complexity 分數是:2
foo()
{
if (condition)
printf ("hello");
}
下列程式的 Cyclomatic complexity 分數是:3
foo()
{
if (condition-X || condition-Y)
printf ("hello");
}

12月 03, 2009

#if 與 #ifdef 的用法

莫名奇妙就是會忘記 #if 跟 #ifdef 的用法,留一個 example 當參考好了。
#include <stdio.h>
#define SYSTEM unix
#define DBG_FLAG

int main()
{
#if (SYSTEM == unix) /* #if usage */
printf ("Unix!!");
#endif

#ifdef DBG_FLAG /* #ifdef usage */
printf ("DBG mode on!!");
#endif
return 0;
}

9月 20, 2009

藉由 XOR 運算來實現 swap 功能

Keyword:XOR swap algorithm

這真是一個非常 tricky 的方法,藉由 XOR 運算就能將兩值互換。輸入的格式不限,包含 int、char 均可。
#define  swap(X, Y)  (X^=Y, Y^=X, X^=Y)
注意:當輸入兩相同運算元(operator)時,其運算結果會為零。
swap(X,X),則 X 結果為零。
當 X=10, Y=10, swap(X,Y) 結果會是正常 X=10, Y=10。

9月 16, 2009

不定量參數(Variable arguments in C)

Keyword:stdarg.h、va_list、va_start、va_arg、va_end。

中文講起來真饒舌。大概是指傳入函式的引數,其數量不固定。因為在某些狀況下無法預知參數量的多寡為何,要解決這個問題可透過 stdarg.h 這個標頭檔提供的巨集來達成。

va_start
va_arg
va_end

9月 12, 2009

const char* 的兩三事

一整個大中招,完全錯誤的寫法如下:
char *str = "hello" ;
*str = "world" ; //想要改變 string 的內容
第一行的宣告,系統會配置空間存放 "hello" 字串。這筆資料是以常態(const)字串儲存,內容不允許更改。所以在第二行指令,希望透過 str 指標來改寫字串內容時,就會有問題。

比較好的方式,應該改採一維陣列來存放資料。當陣列在宣告時,系統會配置一段連續空間以供使用:
char str[30];
strcpy(str, "hello");
strcpy(str, "world");
另外也能使用「二維陣列」來存放資料。使用二維陣列時,因為系統是採 Row-Major 的方式存放資料,所以在函式中務必要提供陣列的維度(Row Dimension,下例中的 SIZE),這樣編譯器才知道如何存取資料。
#define SIZE 20
void set_string(char str[][SIZE])
{
strcpy(str[0], "hello");
}

main()
{
char str[SIZE][SIZE];
set_string(str);
}
另一個更具彈性的做法是,動態分配記憶體(malloc),如此一來參數傳遞時就不用考量到維度的資訊。
void set_string(char **ptr) //直接傳入雙重指標即可
{
strcpy(ptr[0], "hello");
strcpy(ptr[1], "world");
}
main()
{
char *buf[SIZE];
for (int idx=0;idx<SIZE;idx++)
buf[idx]= (char *) malloc(sizeof(char) * SIZE)
set_string(buf);
}
另一個非常重要的觀念,也是回到這篇的主題。在 set_string() 中的指令:
比較下列兩者,不要誤用了:
strcpy(ptr[0], "hello");
ptr[0] = "hello"
使用 strcpy() 是將字串寫到陣列中,陣列空間是當初系統配置的,所以其內容可以任意更改。而後者只是單純「將指標,指向字串」,字串的性質是常數(const),內容不允許修改。

continue 與 break 的差別

continue 與 break 通常放在迴圈裡頭。在使用時 continue 是直接跳過迴圈的「某一回合」,而 break 是直接離開迴圈。

下列迴圈搭配 contine 執行結果: 0 1 2 4 5
for (i=0; i<5; i++)
{
if (i==3) //當 i=3 時,後面指令均省略
continue; //直接跳 i=4 的狀況。
printf ("%d",i);
}
下列迴圈搭配 break 執行結果:0 1 2
for (i=0; i<5; i++)
{
if (i==3)
break;
printf ("%d",i);
}

9月 10, 2009

strtok 字串切割函式

UPDATE 9/12:
範例中 gets 宜用 fgets 取代,避免 Buffer Overflow。

將使用者輸入的 String 切割成單一 token 可用 strtok() 這個函式。定義如下:
char * strtok ( char * str, const char * delimiters );
// delimiters 表示以該字元為區隔。
程式使用範例:
char buf[32], token[10][32] , *str;
int idx=0;
gets (buf); //gets 允許輸入的字串帶有空白 (重點)
str = strtok(buf," ");
strcpy (token[0], str);
while ( str != NULL )
{
str = strtok(NULL," "); //此處用 NULL (技巧)
if (str!=NULL)
{
idx++;
strcpy(token[idx], str);
}
}
若輸入:「This is my Blog」,則得到結果:
token[0] = This
token[1] = is
token[2] = my
token[3] = Blog

9月 08, 2009

函數指標(Function pointer example)

Keyword:Function pointer, Callback function

程式在執行時,函式在記憶體中會有一個位置。函式名稱,其實就是指向該位置。同理仿照,可以建立一函式指標(Function pointer)指向該函式,宣告方式如下:
傳回值型態 (*指標名稱)(傳入參數);

int (*FP)(); /* FP 指向的函式傳入void,回傳int。 */
實際使用狀況如下,透過 FP 函式指標來呼叫 foo()
#include <stdio.h>
void foo()
{
printf ("Hello World!!");
}

int main()
{
void (*FP)(); /* void (*FP)() = foo; */
FP = foo ;
(*FP)(); /* same as using FP(); */
return 0;
}

9月 07, 2009

首發

今天提到幾個比較關鍵的程式技術:
  • function pointer
  • callback function
  • register function
程式 IDE 部份是用: 程式在撰寫時要遵守相關的 Code convention 以利維護。