C 語言複雜知識點

haoke_cn發表於2020-12-03

編譯與巨集

# 開頭為預處理命令,包括 #include 引入檔案 #define 巨集,在預處理階段處理

函式宣告與語法檢測在編譯期檢查,函式定義在連結時檢測

__attribute__(constructor)) 有該巨集上標的函式先於 main 執行

#include <stdbool.h> 使得 c 支援 bool 型別

extern 引用其他檔案已定義過的全域性變數

__typeof() 巨集獲得變數型別,常用於定義巨集

#define swap(a, b) {\
	__typeof(a) __temp = a;\
	a = b; b = __temp;\
}
// 或者 a ^= b; b ^= a; a ^= b;

// 根據變數型別,獲得指定輸出
#define Type(a) _Generic((a), \
    int : "%d",\
    double : "%lf",\
    float : "%f",\
    long long : "%ld",\
    const char * : "%s",\
    char * : "%s"\
)

EOF 巨集常量為 -1, NULL 為 0

變參函式引入 stdarg.h

#include <stdarg.h>
int max(int num, ...) {
	va_list arg; // 宣告引數
	va_start(arg, num); // 使得 arg 指向起始引數
    while (num--) { // num 幫助判斷引數個數
        int temp = va_arg(arg, int); // 開始獲得改型別的引數
    }
    va_end(arg); // 釋放 va_list
}
// 可通過 myprintf(const char *frm, ...) 自己實現列印函式
// 防止多次引入標頭檔案巨集重複宣告
#ifndef _LOG_H
#define _LOG_H

// 通過標頭檔案編譯列印技巧
#ifdef DEBUG
#define log(frm, args...) { \
    printf("[%s : %s : %d]", __FILE__, __func__, __LINE__); \
    printf(frm, ##args); \
    printf("\n"); \
}
#else
#define log(frm, args...)
#endif
#define contact(a, b) a##b // 拼接合並 一個#變成字元常量
#endif

工程開發規範,.h 標頭檔案放函式宣告,.c 檔案放函式定義,標頭檔案和原始檔分離,可以解決函式定義引用函式找不到的問題

工程專案目錄編排:

  • src 原始碼目錄
  • lib 外部庫目錄 可用靜態庫檔案 lib*.a
  • include 標頭檔案 .h
  • bin 可執行檔案
  • build 構建過程中間檔案(可選)

makefile 檔案

.PHONY: clean // 虛擬 clean 檔案,不檢查
all: lib/lib1.a test.o //  依賴 
	gcc test.o -L./lib -llib1 -DDEBUG // 指定庫 開啟巨集宣告
test.o: test.cpp
	gcc -I./include -c test.cpp // -c 不連結
clean:
	rm -rf a.out test.o // 清理

語言特性與庫

c 語言中的 switch 只能判斷整型值變數

math.h 中返回和傳參都是高精度

需要了解的一個最大公約數函式

int gcd(int a, int b) {
	return b == 0 ? a : gcd(b, a % b);
}

printf scanf 返回值為讀入長度

對於 scanf 函式,我們可以通過 scanf("%[^\n]s", str) 來跳過換行輸入等

sprintf 以指定格式列印到字串,sscanf 從字串中讀出型別變數

c 語言中自帶 stdin stdout stderr 檔案符,對檔案進行操作

FILE *fout = fopen("output", "w");
fprintf(stdout, "stdout = %s\n", str);
fprintf(fout, "fout = %s\n", str); // 寫入檔案
fclose(fout); // 注意關閉檔案

使用隨機數,引入 time.h

#include <time.h>

srand(time(0));
int val = rand() % 100; // 0 - 100 的隨機數

使用動態記憶體函式,引入 stdlib.h

函式指標也是引數,可進行傳參呼叫指定方法函式

double newton(double (*F)(double, double), double(*f)(double), double n);
// main 傳入引數個數,引數陣列,環境變數
int argc, char *argv[], char **env

指標、堆疊空間、結構體

const int *x 指標常量 int * const x 常量指標

函式內宣告的變數在棧空間中,所以需要 malloc 指標等幫助下在堆空間建立,傳出去,棧空間內的變數會被銷燬

結構體成員描述關係會影響結構體構造的函式大小,比如 char 可能跟記憶體對齊 4 個位元組 或 8 位元組 (看系統)

自定義巨集,檢視結構體成員偏移

#define offset(T, a) (long) (&(((T *) (NULL))->a))

64 位系統指標變數大小是 8 位元組,32 位系統指標變數大小為 4 位元組

指標根據宣告型別去跳位,p++ char 型別跳 8 位,int 型別跳 32 位

float 佔 4 位元組,double 佔 8 位元組,long long 佔 8 位元組

對於函式指標,我們可以通過 typedef 函式提升至型別,方便呼叫

記憶體洩漏,宣告一塊記憶體後,對其訪問丟失,沒有釋放

函式系統棧空間不超過 8MB,函式結束時釋放

在程式碼中出現的所有同樣的字串,都是字串常量,指向同一靜態區

對於常量,全域性變數,靜態變數(即使在函式內)都存在靜態區,程式結束釋放

static 在 c 語言的作用主要是隱藏,不讓在檔案外匯出;唯一一次初始化(在程式啟動時,函式中不會初始化它),保持變數的持久;預設設定為 0

申請記憶體函式申請的都是虛擬記憶體,在虛地址上

malloc 申請後,在實際訪問情況下時若記憶體不夠才嘗試缺頁異常;不會對原記憶體內容進行修改

calloc 在申請記憶體後,立刻把原記憶體中的內容置 0 了,進行了訪問

realloc 在當前指標上,基於原記憶體地址擴充

共用體中所有元素公用一個空間,一般機器都是大端模式

union IP {
    struct {
        unsigned char a1;
        unsigned char a2;
        unsigned char a3;
        unsigned char a4;
    } ip;
    unsigned int num;
};

相關文章