1. 程式人生 > >Y分鐘系列 C語言

Y分鐘系列 C語言

// 單行註釋以//開始。(僅適用於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       | 從右到左      //
// * / %                             | 從左到右      //
// + -                               | 從左到右      //
// << >>                             | 從左到右      //
// < <= > >=                         | 從左到右      //
// == !=                             | 從左到右      //
// &                                 | 從左到右      //
// ^                                 | 從左到右      //
// |                                 | 從左到右      //
// &&                                | 從左到右      //
// ||                                | 從左到右      //
// ?:                                | 從右到左      //
// = += -= *= /= %= &= ^= |= <<= >>= | 從右到左      //
// ,                                 | 從左到右      //
//---------------------------------------------------//