Y分鐘系列 C語言
阿新 • • 發佈:2018-12-14
// 單行註釋以//開始。(僅適用於C99或更新的版本。) /* 多行註釋是這個樣子的。(C89也適用。) */ // 常數: #define 關鍵詞 #define DAYS_IN_YEAR 365 // 以列舉的方式定義常數 enum days {SUN = 1, MON, TUE, WED, THU, FRI, SAT}; // MON自動被定義為2,TUE被定義為3,以此類推。 // 用#include來匯入標頭檔案 #include <stdlib.h> #include <stdio.h> #include <string.h> // <尖括號>間的檔名是C標準庫的標頭檔案。 // 標準庫以外的標頭檔案,使用雙引號代替尖括號。 #include "my_header.h" // 函式的簽名可以事先在.h檔案中定義, // 也可以直接在.c檔案的頭部定義。 void function_1(char c); void function_2(void); // 如果函數出現在main()之後,那麼必須在main()之前 // 先宣告一個函式原型 int add_two_ints(int x1, int x2); // 函式原型 // 你的程式的入口是一個返回值為整型的main函式 int main() { // 用printf列印到標準輸出,可以設定格式, // %d 代表整數, \n 代表換行 printf("%d\n", 0); // => 列印 0 // 所有的語句都要以分號結束 /////////////////////////////////////// // 型別 /////////////////////////////////////// // 在使用變數之前我們必須先宣告它們。 // 變數在宣告時需要指明其型別,而型別能夠告訴系統這個變數所佔用的空間 // int型(整型)變數一般佔用4個位元組 int x_int = 0; // short型(短整型)變數一般佔用2個位元組 short x_short = 0; // char型(字元型)變數會佔用1個位元組 char x_char = 0; char y_char = 'y'; // 字元變數的字面值需要用單引號包住 // long型(長整型)一般需要4個位元組到8個位元組; 而long long型則至少需要8個位元組(64位) long x_long = 0; long long x_long_long = 0; // float一般是用32位表示的浮點數字 float x_float = 0.0; // double一般是用64位表示的浮點數字 double x_double = 0.0; // 整數型別也可以有無符號的型別表示。這樣這些變數就無法表示負數 // 但是無符號整數所能表示的範圍就可以比原來的整數大一些 unsigned short ux_short; unsigned int ux_int; unsigned long long ux_long_long; // 單引號內的字元是機器的字符集中的整數。 '0' // => 在ASCII字符集中是48 'A' // => 在ASCII字符集中是65 // char型別一定會佔用1個位元組,但是其他的型別卻會因具體機器的不同而各異 // sizeof(T) 可以返回T型別在執行的機器上佔用多少個位元組 // 這樣你的程式碼就可以在各處正確運行了 // sizeof(obj)返回表示式(變數、字面量等)的尺寸 printf("%zu\n", sizeof(int)); // => 4 (大多數的機器字長為4) // 如果`sizeof`的引數是一個表示式,那麼這個引數不會被演算(VLA例外,見下) // 它產生的值是編譯期的常數 int a = 1; // size_t是一個無符號整型,表示物件的尺寸,至少2個位元組 size_t size = sizeof(a++); // a++ 不會被演算 printf("sizeof(a++) = %zu where a = %d\n", size, a); // 列印 "sizeof(a++) = 4 where a = 1" (在32位架構上) // 陣列必須要被初始化為具體的長度 char my_char_array[20]; // 這個陣列佔據 1 * 20 = 20 個位元組 int my_int_array[20]; // 這個陣列佔據 4 * 20 = 80 個位元組 // (這裡我們假設字長為4) // 可以用下面的方法把陣列初始化為0: char my_array[20] = {0}; // 索引陣列和其他語言類似 -- 好吧,其實是其他的語言像C my_array[0]; // => 0 // 陣列是可變的,其實就是記憶體的對映! my_array[1] = 2; printf("%d\n", my_array[1]); // => 2 // 在C99 (C11中是可選特性),變長陣列(VLA)也可以宣告長度。 // 其長度不用是編譯期常量。 printf("Enter the array size: "); // 詢問使用者陣列長度 char buf[0x100]; fgets(buf, sizeof buf, stdin); // stroul 將字串解析為無符號整數 size_t size = strtoul(buf, NULL, 10); int var_length_array[size]; // 宣告VLA printf("sizeof array = %zu\n", sizeof var_length_array); // 上述程式可能的輸出為: // > Enter the array size: 10 // > sizeof array = 40 // 字串就是以 NUL (0x00) 這個字元結尾的字元陣列, // NUL可以用'\0'來表示. // (在字串字面量中我們不必輸入這個字元,編譯器會自動新增的) char a_string[20] = "This is a string"; printf("%s\n", a_string); // %s 可以對字串進行格式化 /* 也許你會注意到 a_string 實際上只有16個位元組長. 第17個位元組是一個空字元(NUL) 而第18, 19 和 20 個字元的值是未定義。 */ printf("%d\n", a_string[16]); // => 0 // byte #17值為0(18,19,20同樣為0) // 單引號間的字元是字元字面量 // 它的型別是`int`,而 *不是* `char` // (由於歷史原因) int cha = 'a'; // 合法 char chb = 'a'; // 同樣合法 (隱式型別轉換 // 多維陣列 int multi_array[2][5] = { {1, 2, 3, 4, 5}, {6, 7, 8, 9, 0} } // 獲取元素 int array_int = multi_array[0][2]; // => 3 /////////////////////////////////////// // 操作符 /////////////////////////////////////// // 多個變數宣告的簡寫 int i1 = 1, i2 = 2; float f1 = 1.0, f2 = 2.0; int a, b, c; a = b = c = 0; // 算數運算直截了當 i1 + i2; // => 3 i2 - i1; // => 1 i2 * i1; // => 2 i1 / i2; // => 0 (0.5,但會被化整為 0) f1 / f2; // => 0.5, 也許會有很小的誤差 // 浮點數和浮點數運算都是近似值 // 取餘運算 11 % 3; // => 2 // 你多半會覺得比較操作符很熟悉, 不過C中沒有布林型別 // 而是用整形替代 // (C99中有_Bool或bool。) // 0為假, 其他均為真. (比較操作符的返回值總是返回0或1) 3 == 2; // => 0 (false) 3 != 2; // => 1 (true) 3 > 2; // => 1 3 < 2; // => 0 2 <= 2; // => 1 2 >= 2; // => 1 // C不是Python —— 連續比較不合法 int a = 1; // 錯誤 int between_0_and_2 = 0 < a < 2; // 正確 int between_0_and_2 = 0 < a && a < 2; // 邏輯運算子適用於整數 !3; // => 0 (非) !0; // => 1 1 && 1; // => 1 (且) 0 && 1; // => 0 0 || 1; // => 1 (或) 0 || 0; // => 0 // 條件表示式 ( ? : ) int a = 5; int b = 10; int z; z = (a > b) ? a : b; // 10 “若a > b返回a,否則返回b。” // 增、減 char *s = "iLoveC" int j = 0; s[j++]; // "i" 返回s的第j項,然後增加j的值。 j = 0; s[++j]; // => "L" 增加j的值,然後返回s的第j項。 // j-- 和 --j 同理 // 位運算 ~0x0F; // => 0xF0 (取反) 0x0F & 0xF0; // => 0x00 (和) 0x0F | 0xF0; // => 0xFF (或) 0x04 ^ 0x0F; // => 0x0B (異或) 0x01 << 1; // => 0x02 (左移1位) 0x02 >> 1; // => 0x01 (右移1位) // 對有符號整數進行移位操作要小心 —— 以下未定義: // 有符號整數位移至符號位 int a = 1 << 32 // 左移位一個負數 int a = -1 << 2 // 移位超過或等於該型別數值的長度 // int a = 1 << 32; // 假定int32位 /////////////////////////////////////// // 控制結構 /////////////////////////////////////// if (0) { printf("I am never run\n"); } else if (0) { printf("I am also never run\n"); } else { printf("I print\n"); } // While迴圈 int ii = 0; while (ii < 10) { // 任何非0的值均為真 printf("%d, ", ii++); // ii++ 在取值過後自增 } // => 列印 "0, 1, 2, 3, 4, 5, 6, 7, 8, 9, " printf("\n"); int kk = 0; do { printf("%d, ", kk); } while (++kk < 10); // ++kk 先自增,再被取值 // => 列印 "0, 1, 2, 3, 4, 5, 6, 7, 8, 9, " printf("\n"); // For 迴圈 int jj; for (jj=0; jj < 10; jj++) { printf("%d, ", jj); } // => 列印 "0, 1, 2, 3, 4, 5, 6, 7, 8, 9, " printf("\n"); // *****注意*****: // 迴圈和函式必須有主體部分,如果不需要主體部分: int i; for (i = 0; i <= 5; i++) { ; // 使用分號表達主體(null語句) } // 多重分支:switch() switch (some_integral_expression) { case 0: // 標籤必須是整數常量表達式 do_stuff(); break; // 如果不使用break,控制結構會繼續執行下面的標籤 case 1: do_something_else(); break; default: // 假設 `some_integral_expression` 不匹配任何標籤 fputs("error!\n", stderr); exit(-1); break; } /////////////////////////////////////// // 型別轉換 /////////////////////////////////////// // 在C中每個變數都有型別,你可以將變數的型別進行轉換 // (有一定限制) int x_hex = 0x01; // 可以用16進位制字面量賦值 // 在型別轉換時,數字本身的值會被保留下來 printf("%d\n", x_hex); // => 列印 1 printf("%d\n", (short) x_hex); // => 列印 1 printf("%d\n", (char) x_hex); // => 列印 1 // 型別轉換時可能會造成溢位,而且不會丟擲警告 printf("%d\n", (char) 257); // => 1 (char的最大值為255,假定char為8位長) // 使用<limits.h>提供的CHAR_MAX、SCHAR_MAX和UCHAR_MAX巨集可以確定`char`、`signed_char`和`unisigned char`的最大值。 // 整數型和浮點型可以互相轉換 printf("%f\n", (float)100); // %f 格式化單精度浮點 printf("%lf\n", (double)100); // %lf 格式化雙精度浮點 printf("%d\n", (char)100.0); /////////////////////////////////////// // 指標 /////////////////////////////////////// // 指標變數是用來儲存記憶體地址的變數 // 指標變數的宣告也會告訴它所指向的資料的型別 // 你可以使用得到你的變數的地址,並把它們搞亂,;-) int x = 0; printf("%p\n", &x); // 用 & 來獲取變數的地址 // (%p 格式化一個型別為 void *的指標) // => 列印某個記憶體地址 // 指標型別在宣告中以*開頭 int* px, not_a_pointer; // px是一個指向int型的指標 px = &x; // 把x的地址儲存到px中 printf("%p\n", (void *)px); // => 輸出記憶體中的某個地址 printf("%zu, %zu\n", sizeof(px), sizeof(not_a_pointer)); // => 在64位系統上列印“8, 4”。 // 要得到某個指標指向的內容的值,可以在指標前加一個*來取得(取消引用) // 注意: 是的,這可能讓人困惑,'*'在用來宣告一個指標的同時取消引用它。 printf("%d\n", *px); // => 輸出 0, 即x的值 // 你也可以改變指標所指向的值 // 此時你需要取消引用上新增括號,因為++比*的優先順序更高 (*px)++; // 把px所指向的值增加1 printf("%d\n", *px); // => 輸出 1 printf("%d\n", x); // => 輸出 1 // 陣列是分配一系列連續空間的常用方式 int x_array[20]; int xx; for (xx=0; xx<20; xx++) { x_array[xx] = 20 - xx; } // 初始化 x_array 為 20, 19, 18,... 2, 1 // 宣告一個整型的指標,並初始化為指向x_array int* x_ptr = x_array; // x_ptr現在指向了陣列的第一個元素(即整數20). // 這是因為陣列通常衰減為指向它們的第一個元素的指標。 // 例如,當一個數組被傳遞給一個函式或者繫結到一個指標時, //它衰減為(隱式轉化為)一個指標。 // 例外: 當陣列是`&`操作符的引數: int arr[10]; int (*ptr_to_arr)[10] = &arr; // &arr的型別不是`int *`! // 它的型別是指向陣列的指標(陣列由10個int組成) // 或者當陣列是字串字面量(初始化字元陣列) char arr[] = "foobarbazquirk"; // 或者當它是`sizeof`或`alignof`操作符的引數時: int arr[10]; int *ptr = arr; // 等價於 int *ptr = &arr[0]; printf("%zu, %zu\n", sizeof arr, sizeof ptr); // 應該會輸出"40, 4"或"40, 8" // 指標的增減多少是依據它本身的型別而定的 // (這被稱為指標算術) printf("%d\n", *(x_ptr + 1)); // => 列印 19 printf("%d\n", x_array[1]); // => 列印 19 // 你也可以通過標準庫函式malloc來實現動態分配 // 這個函式接受一個代表容量的引數,引數型別為`size_t` // 系統一般會從堆區分配指定容量位元組大小的空間 // (在一些系統,例如嵌入式系統中這點不一定成立 // C標準對此未置一詞。) int *my_ptr = malloc(sizeof(*my_ptr) * 20); for (xx=0; xx<20; xx++) { *(my_ptr + xx) = 20 - xx; // my_ptr[xx] = 20-xx } // 初始化記憶體為 20, 19, 18, 17... 2, 1 (型別為int) // 對未分配的記憶體進行取消引用會產生未定義的結果 printf("%d\n", *(my_ptr + 21)); // => 誰知道會輸出什麼 // malloc分配的區域需要手動釋放 // 否則沒人能夠再次使用這塊記憶體,直到程式結束為止 free(my_ptr); // 字串通常是字元陣列,但是經常用字元指標表示 // (它是指向陣列的第一個元素的指標) // 一個優良的實踐是使用`const char *`來引用一個字串字面量, // 因為字串字面量不應當被修改(即"foo"[0] = 'a'犯了大忌) const char* my_str = "This is my very own string"; printf("%c\n", *my_str); // => 'T' // 如果字串是陣列,(多半是用字串字面量初始化的) // 情況就不一樣了,字串位於可寫的記憶體中 char foo[] = "foo"; foo[0] = 'a'; // 這是合法的,foo現在包含"aoo" function_1(); } // main函式結束 /////////////////////////////////////// // 函式 /////////////////////////////////////// // 函式宣告語法: // <返回值型別> <函式名稱>(<引數>) int add_two_ints(int x1, int x2){ return x1 + x2; // 用return來返回一個值 } /* 函式是按值傳遞的。當呼叫一個函式的時候,傳遞給函式的引數 是原有值的拷貝(陣列除外)。你在函式內對引數所進行的操作 不會改變該引數原有的值。 但是你可以通過指標來傳遞引用,這樣函式就可以更改值 例子:字串本身翻轉 */ // 型別為void的函式沒有返回值 void str_reverse(char *str_in){ char tmp; int ii = 0; size_t len = strlen(str_in); // `strlen()`` 是C標準庫函式 for(ii = 0; ii < len / 2; ii++){ tmp = str_in[ii]; str_in[ii] = str_in[len - ii - 1]; // 從倒數第ii個開始 str_in[len - ii - 1] = tmp; } } /* char c[] = "This is a test."; str_reverse(c); printf("%s\n", c); // => ".tset a si sihT" */ // 如果引用函式之外的變數,必須使用extern關鍵字 int i = 0; void testFunc() { extern int i; // 使用外部變數 i } // 使用static確保external變數為原始檔私有 static int i = 0; // 其他使用 testFunc()的檔案無法訪問變數i void testFunc() { extern int i; } //**你同樣可以宣告函式為static** /////////////////////////////////////// // 使用者自定義型別和結構 /////////////////////////////////////// // Typedefs可以建立類型別名 typedef int my_type; my_type my_type_var = 0; // struct是資料的集合,成員依序分配,按照 // 編寫的順序 struct rectangle { int width; int height; }; // 一般而言,以下斷言不成立: // sizeof(struct rectangle) == sizeof(int) + sizeof(int) //這是因為structure成員之間可能存在潛在的間隙(為了對齊)[1] void function_1(){ struct rectangle my_rec; // 通過 . 來訪問結構中的資料 my_rec.width = 10; my_rec.height = 20; // 你也可以宣告指向結構體的指標 struct rectangle *my_rec_ptr = &my_rec; // 通過取消引用來改變結構體的成員... (*my_rec_ptr).width = 30; // ... 或者用 -> 操作符作為簡寫提高可讀性 my_rec_ptr->height = 10; // Same as (*my_rec_ptr).height = 10; } // 你也可以用typedef來給一個結構體起一個別名 typedef struct rectangle rect; int area(rect r){ return r.width * r.height; } // 如果struct較大,你可以通過指標傳遞,避免 // 複製整個struct。 int area(const rect *r) { return r->width * r->height; } /////////////////////////////////////// // 函式指標 /////////////////////////////////////// /* 在執行時,函式本身也被存放到某塊記憶體區域當中 函式指標就像其他指標一樣(不過是儲存一個記憶體地址) 但卻可以被用來直接呼叫函式, 並且可以四處傳遞迴調函式 但是,定義的語法初看令人有些迷惑 例子:通過指標呼叫str_reverse */ void str_reverse_through_pointer(char *str_in) { // 定義一個函式指標 f. void (*f)(char *); // 簽名一定要與目標函式相同 f = &str_reverse; // 將函式的地址在執行時賦給指標 (*f)(str_in); // 通過指標呼叫函式 // f(str_in); // 等價於這種呼叫方式 } /* 只要函式簽名是正確的,任何時候都能將任何函式賦給某個函式指標 為了可讀性和簡潔性,函式指標經常和typedef搭配使用: */ typedef void (*my_fnp_type)(char *); // 實際宣告函式指標會這麼用: // ... // my_fnp_type f; // 特殊字元 '\a' // bell '\n' // 換行 '\t' // tab '\v' // vertical tab '\f' // formfeed '\r' // 回車 '\b' // 退格 '\0' // null,通常置於字串的最後。 // hello\n\0\. 按照慣例,\0用於標記字串的末尾。 '\\' // 反斜槓 '\?' // 問號 '\'' // 單引號 '\"' // 雙引號 '\xhh' // 十六進位制數字. 例子: '\xb' = vertical tab '\ooo' // 八進位制數字. 例子: '\013' = vertical tab // 列印格式: "%d" // 整數 "%3d" // 3位以上整數 (右對齊文字) "%s" // 字串 "%f" // float "%ld" // long "%3.2f" // 左3位以上、右2位以上十進位制浮 "%7.4s" // (字元串同樣適用) "%c" // 字母 "%p" // 指標 "%x" // 十六進位制 "%o" // 八進位制 "%%" // 列印 % /////////////////////////////////////// // 演算優先順序 /////////////////////////////////////// //---------------------------------------------------// // 操作符 | 組合 // //---------------------------------------------------// // () [] -> . | 從左到右 // // ! ~ ++ -- + = *(type)sizeof | 從右到左 // // * / % | 從左到右 // // + - | 從左到右 // // << >> | 從左到右 // // < <= > >= | 從左到右 // // == != | 從左到右 // // & | 從左到右 // // ^ | 從左到右 // // | | 從左到右 // // && | 從左到右 // // || | 從左到右 // // ?: | 從右到左 // // = += -= *= /= %= &= ^= |= <<= >>= | 從右到左 // // , | 從左到右 // //---------------------------------------------------//