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

C語言筆記 結構Struct

struct建立方式

struct student{
char name[20];
int id;
char phone[10];
};

宣告一個具有student結構的變數john
struct student john;

存取
john.phone

結構的初始化
struct student john = {"John", 931001, "0999123123"};

記憶體位址必須為4的倍數,因此,結構中若有field宣告不是4的倍數(Ex: 宣告char phone[10]),他的下一個filed會被配置在4的倍數的記憶體位置(alignment)。

struct student{

char name[20];
int id;
char phone[10];

};

以上結構size為36,非34。


指定struct中的欄位值
strcpy(john.name, "Jonh");
john.id  = 930001;

結構指標
struct student john;
struct student *ptr = &john;  //結構指標宣告方式
ptr->id = 930001;

參數傳遞
結構可當函式的參數或是當回傳值,當結構被定義了之後,就可以當作一種資料類別來使用。

使用傳遞結構指標會比傳遞整個結構來得有效率,結構指標只佔4bytes記憶體空間。

typedef 可將struct 語法簡化,Ex:
typedef struct complex Complex;
後續在程式中使用Complex就代表struct complex
由於可能跨檔案都會需要Complex這個sturct,可以將此定義在標頭檔,Ex: complex.h,只要引入comple.h就能用到此typedef

struct complex{
int real;
int imag;
};
typedef struct complex Complex;

可以簡化為

typedef struct complex{
int real;
int imag;
} Complex;


一個結構也可以使用在另一個結構當欄位

C 語言 檔案處理 File IO

二進制檔案與文字模式的差異在於 二進制直接以位元組作輸出入,而文字模式將位元組轉為ASCII碼,再作輸出入。


使用fread/fwrite進行讀寫
函式原型
int fread(void *buf, int size, int n, FILE *fp);
將n筆fp資料,以每筆size大小,讀入buf緩衝區

int fwrite(const void *buf, int size, int n, FILE *fp);
將buf中的n筆資料,以每筆size大小,寫入fp


二進制輸出入最常見的用途就是將結構陣列整個寫入檔案


透過fseek可以隨機存取想要存取的位置
函式原型
int fseek(FILE *fp, long int offset, int base);
offset以位元組為單位,可以為負數。
base可以為  SEEK_SET檔案起點、SEEK_CUR目前位置、SEEK_END檔案終點


使用rewind可以將讀寫位置移到檔案起點。
函式原型
void rewind(FILE *fp);


EOF是一種狀態,不是一種字元
判斷是否處於EOF狀態可以使用feof
函式原型 int feof(FILE *fp);
如果已經EOF傳回1,否則傳回0



C語言 資料結構DataStructure

以struct作為存資料的節點,以指標作為資料的連結

由於無法預期資料數,因此需透過malloc向系統要求記憶體空間。不用之後可透過free返還記憶體。使用malloc/free需引用 <stdlib.h>

函式原型
void *malloc(size_t size);
void free(void *ptr);

malloc 參數size代表要求的記憶體大小,回傳值為要到的記憶體位址,如果無法得到的記憶體會回傳NULL。

每次呼叫malloc後應該檢查回傳值是否為NULL

一個結構節點如何取得記憶體
struct node *ptr;
ptr = (struct node *)malloc(sizeof(struct node)); //取一塊與struct相同size的記憶體,將其由void*,轉型為struct node *,透過ptr指標來操作節點。

17.2 assert
assert是一個函式,後面接一個條件,若條件不為真,程式就會停止執行。使用assert需要include <assert.h>

17.3 鏈結序列
利用struct宣告鏈結序列中的節點,節點包含兩部分,一個欄位儲存資料,一個欄位儲存鏈結序列中的下一個節點位置。方法如下,
struct listnode{
int data; //節點資料
struct listnode *next; //指向下一個節點
};

連結的第一個節點稱為head,鏈結最後一個節點的next指向NULL,表示鏈結序列的結束。


逐一檢視鏈結序列的節點(遍歷 traverse)
ptr = head;
while(ptr != NULL){
process *ptr;
ptr = ptr->next;
}

or


for(ptr = head; ptr != NULL; ptr = ptr->next)
process *ptr;


保留上一個節點作法
ptr = head;
while(ptr != NULL){
process *ptr;
previous = ptr; //先利用previous紀錄節點位置
ptr = ptr->next; //將節點指向下一個

}


把新節點加到兩個舊節點的方法
current->next = last->next;
last->next= current;


17.3.2 遞迴處理

鏈結序列可用遞迴定義
NULL是鏈結序列
一個節點加上該節點指向的鏈結序列,也是鏈結序列



17.4 二元樹
一個節點有兩個指標,分別指向兩個節點
NULL是二元樹
一個節點加上該節點指向的左二元樹及右二元樹,也是一個二元樹

17.4.1 節點
如何宣告一個二元樹中的節點?
struct treenode{
int data;
struct treenode *left;
struct treenode *right;
};

typedef struct treenode Treenode;

17.4.2 二元搜尋樹
定義
NULL是一個二元樹
一個節點如果
左右子樹都是二元搜尋樹
所有左子樹節點的資料都不大於該節點的資料
所有右子樹節點的資料都不小於該節點的資料
那麼這個節點加上左右子樹也是一個二元搜尋樹


17.4.3 二元樹遍歷
前序 preoder  根, 左子樹, 右子樹
中序 inorder 左子樹, 根, 右子樹
後序 postorder 左子樹, 右子樹, 根




C 語言 物件Object

一個物件把相關的資料和操作包裝起來
以堆疊stack來說明物件

18.2.1堆疊結構

第一個物件元素 - 結構
struct stack{
int top;
char elements[100];
};
typedef struct stack Stack;


18.2.2 堆疊函式宣告

第二個物件元素 - 函式
void init_stack(Stack *s)
int stack_empty(Stack *s)
int stack_full(Stack *s)
void push_stack(Stack *s, char c)
int pop_stack(Stack *s);


標頭檔<xxx.h> 與 "xxx.h"的差異
以<>標示者,編譯器會到系統標頭檔目錄尋找
以""標示者,編譯會到引入""的程式的目錄尋找

18,2.3 堆疊函式實作

C 語言 Chapter19 前置處理Preprocessor

前置處理是編譯器開始編譯程式碼之前所做的處理。可透過前置處理將程式碼動態調整成我們想要的形式,前置處理指令有#include, #define, #if #ifdef #ifndef,  #, __LINE__, __FILE__, ##

19.1 #include

直接將標頭檔引入要編譯的程式碼。
標頭檔詳細定義資料結構,以及操作資料結構所需使用的函式名稱、參數及回傳值。
系統標頭檔由 <> 括起
使用者標頭檔由 "" 括起來


19.2 #define

將程式碼中所定義的字串換成另一字串。
一般習慣用 #define定義常數
用typedef定義資料類別,但也可用#define定義資料類別 Ex: #define Data int

透過#defien也可以定義巨集函式,Ex:  #define marco(x)  x * x
巨集函式非一個完整函式,只是進行字串的代換。

透過 gcc -E 指令可以將前置處理的結果送到螢幕。

例如程式碼

#define square(x) x * x

int main(void){

int i,j;
scanf("%d", &i);
scanf("%d", &j);
printf("%d\n", square(i+j));

執行gcc -E xxx.c 後得到的結果為

int main(void){

int i,j;
scanf("%d", &i);
scanf("%d", &j);
printf("%d\n", i+j * i+j);
return 0;
}

透過此方法可以驗證,巨集的使用是否得到預期的結果,以上的算式 i+j * i + j顯然不是我們想要的。

因此,可將巨集修正為 #define square(x) ((x) * (x))


int main(void){

int i,j;
scanf("%d", &i);
scanf("%d", &j);
printf("%d\n", ((i+j) * (i+j)));
return 0;

}

若傳進巨集的為一個函式,而且會有回傳值,使用巨集不是一個好方法。


19.3 條件編譯 #if, #ifdef, #ifndef
依據前置處理指令#if來決定程式碼要不要編譯Ex:
#if condition
source code
#endif

只有condition為true時,上述的source code才會執行,常用於是否列印除錯資訊,例如:

#define DEBUG 0
...
#if DEBUG == 1
printf("This is debug message. \n");
#endif

透過控制DEBUG定義值,決定是否印出除錯資訊。

在編譯時使用-DSYMBOL=VALUE語法可以動態改變#define值,而不需要修改程式。Ex:
gcc -DDEBUG_LEVEL=8 program.c
如此在編譯時會將DEBUG_LEVEL 的值給定為8,但編譯器define,程式碼也define造成重複定義(redefine)。此時要透過#ifndef來處理。如果#ifndef後面的符號未被定義,則#ifndef到#endif中間的程式碼會被編譯,如果已經有定義,就不會被編譯。

透過#ifndef,我們使用下面的程式碼,來避免透過-D造成的重複定義問題,當編譯時未使用-D則DEBUG_LEVEL預設為4
#ifndef DEBUG_LEVEL
#define DEBUG_LEVEL 4
#endif

若程式中只是要單純的開關,透過前置處理指令#ifdef可以達成。
#ifdef DEBUG
source code
#endif

在編譯器中可以透過 gcc -DDEBUG program.c 來開關
在程式中可以透過 #define DEBUG來開關
使用#undef則可以將某符號取消定義。#undef DEBUG


19.4 __LINE__, __FILE__
__LINE__ 知道前置處理目前的行數
__FILE__ 知道前置處理目前的檔名

printf("file %s, line %d", __FILE__, __LINE__);


19.5 ##
##可以將前後的名字連在一起,例如: S_IR ## USR 會成為S_IRUSR