Overview of Content
這裡主要來看看 C 語言的細節部分
本文將探討在C語言中 void 類型
的意義以及 NULL 的意義
。我們將介紹 void 類型的用法和指針,以及 NULL 在不同上下文中的差異,包括與 '\0'、'0'、0、NULL 的比較
接著,我們將討論在C語言中傳回值的意義,包括 main 函數的標準寫法、接收參數、對 main 函數傳回值的意義。
我們也會探討在C語言中函數 function
傳回數值的習慣和規範
最後,我們將關注C語言中產生的臨時變量,包括強制類型轉換和臨時變量的運算。透過學習這些內容,讀者將能夠更好地理解C語言中的 void 類型、NULL 值以及傳回值的含義和使用方法
void 意義
void 是一種「類型」,稱為「無類型」,可理解成尚未定義的類型 (類似 Java 的 Object 類)
● 在指定類型後,編譯器才會按照該類型的解析方式去讀取該區塊的記憶體
void 指標
A. void 指標代表一段尚未確認(尚未定義類型)的數據,可以透過強制轉型,告訴編譯器該如何解析該段記憶體數據
typedef struct MyClz {
int a;
short b;
char c;
} MyClz_t;
void test_void() {
MyClz_t clz;
clz.a = 10000;
clz.b = 100;
clz.c = 'a';
void *p = &clz;
printf("analysis by MyClz_t: %d\n", ((MyClz_t *)p)->a);
printf("analysis by MyClz_t: %d\n", ((MyClz_t *)p)->b);
printf("analysis by MyClz_t: %c\n", ((MyClz_t *)p)->c);
}
B. 較常見的是使用 malloc
API(這個 API 來自於 C 語言提供的標準庫,用來動態申請記憶體空間),該 API 就是返回一個 void *
指標;測試範例如下
#include <stdlib.h>
typedef struct MyClz {
int a;
short b;
char c;
} MyClz_t;
void test_void_lib() {
void *p = malloc(sizeof(MyClz_t));
MyClz_t *pClz = (MyClz_t *)p;
pClz->a = 222333;
pClz->b = 111;
pClz->c = 'z';
printf("analysis by MyClz_t: %d\n", pClz->a);
printf("analysis by MyClz_t: %d\n", pClz->b);
printf("analysis by MyClz_t: %c\n", pClz->c);
free(pClz);
}
NULL 意義
NULL 並不是 C 語言的關鍵字,其定義如下,NULL 在 C/C 是不同的意思
#ifdef _cplusplus
#define NULL 0 // C++ 定義
#else
#define NULL (void *)0 // C 定義
#endif
這裡我們來解釋一下 C 語言的 NULL,它是一個 1. void* 類型的指標、2. 0 是指向位置 0x00000000
的記憶體空間
● 大部分 CPU 中的
0x00000000
記憶體是特殊段,不可隨意訪問
'\0'
、'0'
、0
、NULL
差異
目標 | 類型 | 說明 |
---|---|---|
'\0' | char | ASCII 的 0,在 C 中作為字符串的結尾 |
'0' | char | ASCII 的 48 |
0 | 數字 | 就是 0 |
NULL | void* | 也是 0,不過它是地址 0x00000000 的意思 |
返回值意義
main 函數:標準寫法
● main 是 C 語言的起始點,它有以下幾種寫法 (C99 版本),這幾種寫法都是可以的
A. main 入口函數 - 不帶參數
int main(void) {
printf("Hello World");
return 0;
}
B. main 入口函數 - 帶參數:argc
是數量,一個是參數指標;預設參數是該檔案的路徑
// 指標數組
int main(int argc, char *argv[])
printf("argument count: %d\n", argc);
// 以下兩種寫法相同
printf("argument value: %s\n", *argv);
printf("argument value: %s\n", argv[0]);
return 0;
}
以下另外一種寫法(二重指標)也可以,這兩種寫法都可以得到同樣的結果
// 二重指標
int main(int argc, char **argv)
printf("argument count: %d\n", argc);
// 以下兩種寫法相同
printf("argument value: %s\n", *argv);
printf("argument value: %s\n", argv[0]);
return 0;
}
● 編譯出 .out
執行檔後,在呼叫時需要傳參數就可以直接將參數加在後面
gcc return_test.c -o return_test.out
./return_test.out test 12345
如果有多個參數,那參數間要使用
空格
隔開
誰調用 main 函數 - main 返回值
● 誰調用 main 函數:這分為 兩種方式 (系統決定)
A. 無系統 MCU 系列:是由匯編 (組合) 語言來調用,先調用加載函數庫,初始化 Stack、Heap,最後會調用 main 函數
B. 有系統 PC 系列:由系統來調用,透過 fork
創建虛擬記憶體的新分頁、exec
覆蓋並執行 main 函數
● main 函數的返回值:
會由調用它(創建這個程序,可能是 PC 系統,也可能是 MCU)的 Parent Process 接收,而接收到返回值後如何操作則是 Parent Process 決定
C 語言 - 返回數的習慣
● C 語言 - 返回數的習慣有兩種 !(並非強制,建議在使用 API 時還是要詳細看看文件說明)
函數類型 | 返回 0 的含意 | 返回非 0 的含意 |
---|---|---|
判斷函數 | 成功 | 失敗 |
操作函數 | 失敗 | 成功 |
A. 操作函數:操作類型的函數,返回 0 代表成功,非 0(像是返回 -1)則是失敗
void use_string_cmp() {
char *p = "Hello";
char *pa = "Hello";
// 呼叫操作類型函數 show_on_console
int show_result = show_on_console(p, pa);
if(show_result == 0) {
printf("Success\n");
} else {
printf("Fail\n");
}
}
B. 判斷函數:邏輯判斷類型的函數,返回 0 代表判斷失敗,非 0(像是返回 1)則是成功
// 邏輯判類型的函數
int is_litte_endian() {
union Test t;
t.a = 1;
if(t.b == 1) {
printf("litte_endian\n");
return 1; // 系統為小端
} else {
printf("big_endian\n");
return 0; // 系統為大端
}
}
C 語言產生的臨時變量
臨時變量由 C 語言自己提供,並不會顯示顯現 (匿名),它們的顯示時機如下
強制轉型
● 在強制轉型成會產生一個臨時匿名變量,強制轉型的案例如下:
void force_change() {
float f = 10.023;
// 這裡會產生臨時變量 `x`
int a = f;
printf("a is %d\n");
}
A. 產生臨時變量 x
:就像是上面案例… 將 10.023
的整數部分 10
存起來
B. 將臨時變量 x
賦予 a
C. 在離開函數時,銷毀臨時變量 x
臨時變量運算
● 另外一個產生臨時變量的時機就是在運算時,運算的案例如下:
void cal() {
int a = 10;
// 這裡會產生臨時變量 `x`
float b = a / 3;
printf(" is %d\n", b);
}
A. 產生臨時變量 x
:就像是上面案例… 將 10 / 3
的結果 3.33333
存起來
B. 將臨時變量 x
賦予 b
C. 在離開函數時,銷毀臨時變量 x
更多的 C 語言相關文章
關於 C 語言的應用、研究其實涉及的層面也很廣闊,但主要是有關於到系統層面的應用(所以 C 語言又稱之為系統語言),為了避免文章過長導致混淆重點,所以將文章係分成如下章節來幫助讀者更好地從不同的層面去學習 C 語言
C 語言基礎
● C 語言基礎:有關於到 C 語言的「語言基礎、細節」
編譯器、系統開念
● 編譯器、系統開念:是學習完 C 語言的基礎(或是有一定的程度)之後,從編譯器以及系統的角度重新檢視 C 語言的一些細節
C 語言與系統開發
● C 語言與系統開發:在這裡會說明 C 語言的實際應用,以及系統為 C 語言所提供的一些函數、庫... 等等工具,看它們是如何實現、應用