Linux C 的編譯流程
C 編譯器
- gcc GNU
- msvc windows
- clang 蘋果
- intel
- …
- cc 默認Linux c語言編譯器
程序設計語言分類
- 編譯型 C、C++、java
- 把源代碼轉換成機械指令(X86 電腦、ARM 手機)
- 編譯做了類型安全檢查,安全
- 性能高
- 靈活差
- 解釋型 Python、JS、java
- 源代碼直接在解釋器上運行
- 靈活性強、建模與語義的表達高效 ‘*’
- 易學習 ‘*’
- 性能差
編譯流程
c語言源文件:
.h 頭文件 包含函數和變量聲明、宏和數據類型定義
.c 源文件 包含函數定義和實現,預處理語句
gcc hello.c -o hello
以上語句執行過程可拆分為一下流程:
-
匯編,生成匯編代碼
gcc -S hello.c -o hello.s -
編譯,生成目標文件.o/.obj
gcc -c hello.s -o hello.o -
鏈接 目標文件、庫文件(.so/.dll)和操作系統系統執行相關代碼 生成可執行文件 hello/hello.exe
gcc hello.o … -o hello
TCC 編譯器
tcc 優化編譯器,生成可執行文件較小
C 語言學習
設計者:丹尼斯·里奇 & 肯·湯普森 共同設計實現 UNIX,設計C語言
UNIX/C、C/Linux、Linux/互聯網共生相互成就
推薦紀錄片《操作系統革命》
CGI 用C動態生成HTML頁面
Web/PHP 基于c改進的
編程范式(方法學、建模時思維模式)
- 面向過程:過程、操作,C、Pascal
- 面向對象 OOP:C++、java、python
- 函數式:Lisp
學習內容
- 變量與類型系統
- 流程控制:順序、分支、循環
- 數組
- 函數:程序基本單位;C++中為類、對象
- 指針 ‘*’ 信任開發者,駕馭起來有難度 unix/linux root用戶
- 結構體
- 庫:文件、網絡、進程、線程(提高性能,如找一個億以內的質數)、GUI、安全、MySQL、Opencv …
推薦書:《C語言程序設計 現代方法》
C變量與類型
程序,為完成特定任務,數據與指令的集合
變量與常量
標識符,定義了內存中的一個區域,替代內存物理地址
變量:空間中數據隨時間會發生變化
常量:程序中隨時間不發生變化 ‘const’
變量聲明:類型 標識符| |空間分配的大小 字母下劃線開頭空間格式 包含字母、下劃線、數字不能使用關鍵字、保留字不能包含空格以及下劃線以外的其他符號變量小寫、常量大寫有意義
類型系統
- 基本類型
- 數值
- 整數
short 2 2^16 = 65536 int 2 4 2^32 = 46億 10位 long 4 8 long long 8
- 浮點數
float 4
double 8
- 字符
- char 1
- 邏輯
- bool 1
- 數值
- 復合類型
- 數組
復合數據類型,存儲多個值,大小不可變
int a[6]; // 6個int元素char c[128]; // 128個char字符char c[] = {'a', 'b', 'c'}; // 3個字符int a[6] = {1, 2, 3, 4, 5, 6}; // 6個int元素int n; int a[n]; // n個int元素,n改變a大小不變, c++中n必須是const
下標可以越界但是結果不確定
- 結構體
- 聯合
- 類
- 數組
靜態與動態類型
- 靜態類型 C、C++、java
- 動態類型 Python、JS
運算
-
算術:+ - * / %
-
位(二進制):& | ^ << >> ~
-
邏輯(比較):
- || 或 左邊為假才執行右邊,兩個假為假
- && 與 左邊為真才執行右邊,兩個真為真
- ! 非 真變假,假變真
-
關系(真假):== != > < >= <=
-
賦值:從右往左執行
-
位運算
數值轉換成二進制進行運算,效率更高-
& 與:兩1得1,否則為0
- 掩碼運算 ip地址掩碼
- 奇數偶數判斷(n & 1 == 1 為奇)
…
-
| 或:兩0得0,否則為1
- 狀態疊加
-
^ 異或:兩1得0,兩0得0,否則為1
- 在所有成對出現的數字中,找只出現一次的數字
- 交換兩個變量的值 a = a ^ b; b = a ^ b; a = a ^ b;
-
~ 非:0變1,1變0
-
'>>'右移:右移n位,相當于除以2的n次方 比除法性能高
-
<< 左移:左移n位,相當于乘以2的n次方
-
流程控制
程序結構
-
順序
-
分支(判斷選擇)
- if
if (條件) {}
- if else (三元運算符可替代 '? : ')
if (條件) {}else {}
- if else if else (多路分支,匹配范圍、枚舉值/精確值)
if (條件) {}else if (條件) {}else {//邏輯閉環}
- switch case (多路分支、有限:只能匹配枚舉值/精確值)
switch (變量) {case 值:break;......default://邏輯閉環}
-
循環
- for 循環次數確定
//1 初始化 //2 循環條件 //3 步長 //4 循環題 //執行順序1->2->4->3->2->4->3... for (1; 2; 3){//4 }
- while 循環次數不確定, 先判斷再執行
while (條件){}
- do while 循環次數不確定,先執行再判斷
do {}while (條件);
重構
不增加代碼功能,對代碼結構進行改變
函數
命名的代碼塊,預定義的,可復用的功能單元
函數是c程序的基本單元,c程序有若干個函數組成
函數也叫方法
函數大小:建議不要超過100行/一個屏幕
函數的作用
- 封裝:將功能封裝成一個函數,便于復用,開放閉合原則
- 可復用
- 模塊化:將一個大的功能分解成若干個小功能,便于理解,一般在不同的文件中定義函數
開放閉合原則:對擴展開放,對修改關閉
定義
//函數名 functionName//返回值類型 returnType void int double ptr...//參數列表 parameter list int a, int b double c, int d...returnType functionName (parameter list){//函數體//返回值return returnValue;}
遞歸
一個函數調用自身
部分遞歸實現的算法可以使用循環實現,遞歸代碼更簡潔
部分循環無法實現的只能使用遞歸實現 文件遍歷、圖、漢諾塔
- 遞歸函數必須有收斂條件
- 包含自身的調用
模塊化
/*
* f.h
* 聲明函數
*/
int jc(int);
int fib(int);
long long fib2(int);/*
* f.c
* 定義函數
*/
#include "f.h"
//菲波那切數列
int fib(int n){//
}long long fib2(int n){//
}
//
指針
概念
指針:一種數據類型,存儲變量的地址
指針可以運算
定義
int i; //64位 4字節 32位 2字節double d; //8字節// 64位 8字節 32位 4字節int *p = &i; //& 取地址符 p保存首地址,int類型規定管轄范圍double *p1 = &d;// %p打印地址 *解引用運算符,可以取得指針指向的數據,在聲明時表示變量是一個指針printf("int\t%ld %ld %p %d\n", sizeof(i), sizeof(p1), p1, *p1);printf("double\t%ld %ld %p %lf\n", sizeof(d), sizeof(p3), p3, *p3);int a[] = {100, 200, 300, 400}; //數組是復合類型,數組名就是一個指針,指向數組的首地址,不被復制 (a = b 錯)int *p;p = &a; //指針指向數組的首地址p = a; //數組名稱轉化成數組首地址p = &a[0]; //指向數組首元素的地址,就是數組首地址p[i] = 233; //p[i] == *(p+i)printf("%d\n", *(p + i)); //輸出233
字符串
定義
字符串:字符構成的一個有‘序列表’,以‘\0’結尾的字符序列
//字符
char c;
//字符數組,字符串中出現特殊字符需要用 \ 轉義
char s[] = {'a', 'b', 'c', '\0'};
char s[4] = "abc"; //由于隱含地包含'\0',容量(sizeof(s))要大于表面上的‘長度’(strlen(s)),這里大于4
//字符串,數組,元素內容可變(s1[0] = 'a'),數組不可變(s1 = "abc" X)
char s1[] = "abc";
//字符指針,不改可變元素內容(s2[0] = 'a' X),指針變量本身可變(s2 = "abc")
char *s2 = "abc";
//拼接,初始化的時候可以
char s3[] = "abc""def""ghi\
get";
//拼接,賦值時也可以
char *s2 = "abc""def"
s2 = "abc""get";
sizeof(s):返回字符數組容量,包含‘\0’
strlen(s):返回字符數組長度,不包含‘\0’
數據形式:
- 數值
- 字符串:文本
- 二進制
- 圖形
- 音頻
- 視頻
- …
操作
頭文件:string.h
- strlen(s):返回字符串長度
- strcat(s1, s2)、strncat(s1, s2):拼接字符串,s2拼接到s1
- strcmp(s1, s2):比較字符串, 返回值:0(相等),-1(s1 < s2),1(s1 > s2), 其實就是返回最后一個不相等的字符做減法運算后的結果
- strcpy(s1, s2)、strncpy(s1, s2):復制字符串, s2復制到s1
- puts(s)、fputs(s, fp):輸出字符串到標準輸出或文件,回車結束
- gets(s)、fgets(s, size, fp):從鍵盤讀取字符串,回車結束,最多取size - 1個字符(包括\0)
應用
- 進制轉換
char base[] = "0123456789ABCDEF";char res[100];int n;printf("請輸入一個正整數:");scanf("%d", &n);int len = 0;while(n > 0){char tmp[100];tmp[0] = base[n % 16];tmp[1] = '\0';strncat(tmp, res, len);++len;strncpy(res, tmp, len);n /= 16;}printf("%s\n", res);
- 大數值計算
- 加法
- 從低位到高位每位相加再加進位,模10的余數放進結果字符數組,除10的結果保存為進位,字符數組長度就是較大數長度 + 1(最多進一位)
- 為方便操作,對不等長的兩個數,將較短的數補充0,使其與較長數長度+1相等,較長數也要在最高位之后補充一個0,從低位到高位加,要將字符數組翻轉
- 結果要刪除多余的0
- 減法
- 不管兩個數誰大誰小,用大數做減數,小數做被減數,最后根據大小處理符號
- 從低位到高位每位減,結果小于0要向高位借位(結果加10,高位a[i + 1]減1)
- 為方便操作,對不等長的兩個數,將較短的數補充0,使其與較長數長度相等,同時要將字符數組翻轉
- 結果要刪除多余的0
- 乘法
- 對于被乘數每一位b[i],乘數a累加b[i]次存入tmp,然后tmp后面補i個零,將tmp累加到結果數組中
- 除法
方法一: 大數減小數,能減多少次除數商就是多少
方法二: 模擬短除法的過程- 先處理特殊情況:1- 被除數比除數小,直接輸出0 2- 被除數為0,打印錯誤信息
- 從高位開始模擬,先取除數a與被除數b長度相等的一部分tmp
- 從高位開始tmp減被除數b,結果放tmp,能減的次數count就是結果數組這一位的值,最后不夠減的數保存在tmp
- 將a剩余拼接一位到tmp末尾,直到大于除數,或者拼接的是被除數最后一位之后的‘\0’(退出條件),每拼接一位就保存一位0到結果數組,但是結果數組的第一位不能是0,所以如果時一開始的結果,結果沒有內容不需要補0,同時遇到退出條件也不用補0
- 重復2-3步,共執行 除數長度減被除數長度 + 1 次
- 為了刪除結果多余的0,如果count是0且結果數組是第一位,則不要加到結果中,如果更新結果數組后已經是最后一次循環直接退出
- 算法過程要多次比較兩個數的大小,可以定義一個比較函數比較兩個字符串表示的數的大小
示例代碼:
//交換兩個字符
void swap(char *a, char *b)
{int tmp = *a;*a = *b;*b = tmp;
}
//字符串翻轉
void reverse(char *s)
{int len = strlen(s);for (int i = 0; i < len / 2; ++i){swap(&s[i], &s[len - i - 1]);}
}
//字符串表示的數字相加運算
void add(const char *a, const char *b, char *res)
{int llen = strlen(a);int rlen = strlen(b);//記錄最后結果是正是負int flag = 0;if (a[0] == '-' && b[0] == '-'){flag = 1;--llen;--rlen;}//如果一正一負則委托減法進行運算else{if (a[0] == '-' && b[0] != '-'){sub(b, a + 1, res);return;}if (a[0] != '-' && b[0] == '-'){sub(a, b + 1, res);return;}}//兩個都是整數則正常計算//拷貝到臨時變量中,避免影響原字符串char tmpa[100];char tmpb[100];if (flag){strncpy(tmpa, a + 1, llen + 1);strncpy(tmpb, b + 1, rlen + 1);}else{strncpy(tmpa, a, llen + 1);strncpy(tmpb, b, rlen + 1);}//計算出最較大的和較小的數字以及其長度int size, minc;char *max, *min;if (llen > rlen){max = tmpa;size = llen + 1;min = tmpb;minc = rlen;}else{max = tmpb;size = rlen + 1;min = tmpa;minc = llen;}//翻轉數組reverse(tmpa);reverse(tmpb);//填充0使原字符串最大數字的長度加1for (int i = minc; i < size; ++i){min[i] = '0';}max[size - 1] = '0';//逐位加int cur = 0;for (int i = 0; i < size; i++){cur = cur + max[i] - '0' + min[i] - '0';res[i] = cur % 10 + '0';cur /= 10;}//去除多余的0while (res[size - 1] == '0'){--size;}if (flag){res[size++] = '-';}//補充'\0'res[size] = '\0';//翻轉位正常循序reverse(res);
}
//比較兩個字符串表示的數組
int compare(const char *a,const char *b)
{int llen = strlen(a);int rlen = strlen(b);if (llen > rlen){return 1;}else if (llen == rlen){return strcmp(a, b);}else{return -1;}
}//字符串表示的數字相減運算
void sub(const char *a, const char *b, char *res)
{int flag = 0;int llen = strlen(a);int rlen = strlen(b);//使用臨時數組,不影響原字符串char tmpa[100], tmpb[100];strncpy(tmpa, a, llen + 1);strncpy(tmpb, b, rlen + 1);//比較兩個數大小,獲取長度,用較大減較小數,最后處理符號//分別獲取較大數和較小數char *max, *min;int minc, maxc;//根據符號處理if (a[0] == '-' && b[0] == '-'){--llen;--rlen;if (compare(a + 1, b + 1) >= 0){flag = 1;max = tmpa + 1;min = tmpb + 1;maxc = llen;minc = rlen;}else{max = tmpb + 1;min = tmpa + 1;maxc = rlen;minc = llen;}}//如果一正一負則委托加法法進行運算else if (a[0] != '-' && b[0] != '-'){if (compare(a, b) >= 0){max = tmpa;min = tmpb;maxc = llen;minc = rlen;}else{flag = 1;max = tmpb;min = tmpa;maxc = rlen;minc = llen;}}else{//-a - b =-a + -bif (a[0] == '-' && b[0] != '-'){tmpb[0] = '-';strncpy(tmpb + 1, b, rlen + 1);add(a, tmpb, res);return;}//a - -b = a + bif (a[0] != '-' && b[0] == '-'){add(a, b + 1, res);return;}}//翻轉數組reverse(max);reverse(min);//較小數填充0for (int i = minc; i < maxc; ++i){min[i] = '0';}//逐位減int t = 0;for (int i = 0; i < maxc; ++i){t = max[i] - min[i];if (t < 0){t += 10;--max[i + 1];}res[i] = t + '0';}//去除多余0while(res[maxc - 1] == '0'){--maxc;}//添加\0和符號if (flag){res[maxc++] = '-';}res[maxc] = '\0';//翻轉成正常順序reverse(res);
}
//大數乘法
void mul(char *a, char *b, char *res){int llen = strlen(a);int rlen = strlen(b);//初始化結果int size = llen + rlen + 1;memset(res, '0', size);res[1] = '\0';//不改變原數組使用臨時變量char tmpa[llen + 1], tmpb[rlen + 1];strncpy(tmpa, a, llen + 1);strncpy(tmpb, b, rlen + 1);int min = llen > rlen ? rlen : llen;char *minc = llen > rlen ? tmpb : tmpa;int max = llen > rlen ? llen + 1 : rlen + 1;char *maxc = llen > rlen ? tmpa : tmpb;//翻轉較短字符串reverse(minc);//對較短字符串每一位n,較長位自加n次, 再添加i個0后加到結果中for (int i = 0; i < min; ++i){char tmp[size];strncpy(tmp, maxc, max);int n = minc[i] - '0';//自加n次for (int j = 0; j < n - 1; ++j){add(tmp , maxc, tmp);}//在末尾添加i個零int pos = strlen(tmp);memset(tmp + pos, '0', i);tmp[pos + i] = '\0';//累加到結果中add(res, tmp, res);}
}
//大數除法
void divi(char *a, char *b, char *res){//特殊情況if (b[0] == '0'){printf("除數不能為0\n");exit(-1);}if (compare(a, b) < 0){res[0] = '0';res[1] = '\0';return;}//從高位模擬,高位減被除數,能減多少次就是商這一位的值//取除數與被除數長度相等的一部分int llen = strlen(a);int rlen = strlen(b);char tmp[rlen + 1];strncpy(tmp, a, rlen);tmp[rlen] = '\0';int t = 0;for (int i = rlen; i <= llen; ++i){//記錄能減幾次int count = 0;while (compare(tmp, b) >= 0){//減去被除數sub(tmp, b, tmp);++count;}//count是0 且是結果第一位不用加到結果if (count != 0 || t != 0){res[t++] = count + '0';}//到達最后一位,直接退出if (i == llen){break;}//拼接剩余數字while(1){strncat(tmp, a + i, 1);++i;//直到大于除數,或者拼接的是被除數最后一位之后的‘\0'if (compare(tmp, b) >= 0 || i == llen + 1){break;}//不是結果的第一位才要補零if (t == 0){continue;}res[t++] = '0';}}res[t] = '\0';
}
結構體
C結構體與C++類的區別
結構體:封裝了屬性(字段)
類:封裝了屬性和相關操作
結構體 | 類 | |
---|---|---|
方法學 | 面向過程 | 面向對象 |
字段 | + | + |
函數/方法 | - | + |
繼承 | - | + |
賦值操作 | 拷貝 | 指針 |
c++中結構體和類沒有區別,結構體是成員的默認訪問級別為公有的類
結構體定義
// 把多個相關的字段封裝成一個整體
struct student
{char name[16];char tel[11];int score;double height;double weight;struct student *friend;
}struct student s1[40];
// 不僅封裝數據還封裝方法
class Student {int age;int score;void talk() {}void run() {}
}
結構體填充原則
- 各字段字節對齊
- 結構體整體大小是最長字段的大小
結構體操作
- 賦值
struct date d1;
struct date d2 ={2024, 7, 3};
struct date d3 = {.month = 7, .day = 4};
d1.year = 2024;
d1.mouth = 7;
d1.day = 5;
- 使用
//訪問,通過指針(d->year (*d).year)或對象訪問(d.year)
void display(struct date *d){printf("%d年%d月%d日\n", d->year, d->month, d->day);
}
void display2(struct date d){printf("%d年%d月%d日\n", d.year, d.month, d.day);
}
內存管理相關
內存管理概念
int a;int *p; //8字節double *d; //8字節void *v; //8字節,類型未知的一個指針變量,不可以直接使用,通過類型轉換或者賦值給具體類型指針使用
void*:類型未知的一個指針變量,可以存儲任何類型的地址,不可以直接使用,通過類型轉換或者賦值給具體類型指針使
內存管理方式
- 系統管理
函數中聲明的普通變量,內存空間在棧空間
由系統自動分配和銷毀,在函數聲明時分配,結束時銷毀 - 手動管理
基于內存管理函數創建的,或全局的變量,存儲在堆空間
需要手動分配和銷毀,若不釋放會引起內存泄漏
相關函數
stdlib.h
- malloc(size_t size) 分配
//靜態數組,大小不變 int a[4] = {100, 200, 300, 400}; //動態數組,大小可變 int *b = malloc(sizeof(int) * 4);
- free(void *ptr) 釋放
- calloc(size_t num, size_t size) 分配并初始化
- realloc(void *ptr, size_t size) 重新分配 如果有剩余空間,則在原地址中擴展,否則開辟新的空間并拷貝至新空間
- memset(void *ptr, int value, size_t num) 初始化
- memcpy(void *dest, void *src, size_t num) 內存拷貝
- …