本人學習期間總結的所有C語言筆記:
1.運算符
一元運算符優先級大於二元運算符
,運算符優先級最低
[]大於*號運算符(用於指針數組時)
! > 算術運算符 > 關係運算符 > && > || > = > ,
2.字符型
char沒有明確的定義爲signed或unsigned,如果需要注意,要自己加上
'\'' '\"' '\\' '%%' '\?'
警報'\a' 回退'\b' 換頁'\f' 換行'\n' 回車'\r' 水平tab'\t' 垂直tab'\v'
getchar();從stdin緩衝區中讀取
getch();從鍵盤緩衝區中讀取,在win下不回顯,在Unix下回顯 在<curses.h>中
字符測試函數:【包含在<ctype.h>中】
1) int isalnum(int c) //測試c是否是字母或數字,是的話返回爲真,否則爲假
2) int isdigit(int c) //測試c是否是數字
3) int isalpha(int c) //測試c是否是字母
4) int islower(int c) //測試c是否是小寫字母
5) int isupper(int c) //測試c是否是大寫字母
6) int tolower(int c) //轉換爲小寫字母
7) int toupper(int c) //轉換爲大寫字母
8) int iscntrl(int c) //測試c是否是控制字符
9) int isspace(int c) //測試c是否是空格
10)int isgraph(int c) //測試c是否是除了空格外的可打印的字符
11)int isprint(int c) //測試c是否是可打印字符
12)int ispunct(int c) //測試c是否是除了空格,數字,字母外的可打印字符,即是否爲符號
3.字符串
字符串就是一個字符數組,結尾以'\0'作爲標誌
一個字符佔用一個字節(漢字佔用兩個字節),從左到右依次存放,在字符串的結尾自動加入一個字節的結束標誌'\0'
'a'與"a"的不同:'a'代表一個字符佔用一個字節;"a"代表一個字符串佔用兩個字節.
字符串初始化:char str[10]="abc123!@#";//不能寫成char str[10];str="dfh";(初始化聲明的時候的=不代表賦值操作,只是初始化)
gets()
puts()
字符串轉換函數:
1)int atoi(const char *str) //將字符串類型的數據轉換爲整型,參數爲字符串首地址
2)double atof(const char *str) //將字符串類型的數據轉換爲雙精度浮點數
字符串庫函數:
1) strlen()
2) strcpy() strncpy()【複製指定字符個數的字符串】char *strncpy(char *s1, const char *s2, size_t n);
3) strcmp() int strncmp(const char *s1, const char *s2, size_t n);【比較前面n個字符】
4) strcat() 可以用在這種情況下:char ping[8]="ping ",ip[32]="www.baidu.com"; strcat(ping,ip); system(ping);
5) strtok() 字符串分割函數以s2爲標準分割s1,char *strtok(char *s1, const char *s2);
6) bzero(void *s, size_t n) 字符串清理,參數1是要清空的字符串,參數2是要清空的位數
7) memset(void *s, int c, size_t n) 字符串填充,參數2爲填充進去的內容,參數3爲要填充的字節數
8) strchr(const char *s, int c) 在s中查找c第一次出現的地址
9) strrchr(const char *s, int c) 在s中查找c最後一次出現的地址
10)strstr(const char *s1, const char *s2) 在s1中查找s2第一次出現的地址
4.變量(Mac下)
int 4個字節 -2e15~2e15-1
float 4個字節 有效位數6~7位
double 8個字節 有效位數15~16位
char 1個字節 ASCII碼字符,或-128~127
定義一個變量時,系統就自動的分配了空間,未賦值前,是隨即放入的一個垃圾值
5.無符號、整形10進制、8進制、16進制
正常:u d o x
短: hu hd ho hx
長: lu ld lo lx
6.浮點數
默認情況下,浮點數是以雙精度存儲的,要明確使用的是單精度,要在數字後面加上字母f(如:57.0f),如果必須以long double格式存儲,要在數 字的末尾加上字母l(如:57.0l)。
當讀取double類型的數值時,在e、f、g前放置字母l,但是l只能在scanf中使用,不能在printf中使用,在printf中e、f、g就能表示double型
當讀或寫long double類型的值時,在e、f、g前放置字母L;
7.scanf函數和printf函數
scanf函數在讀入數字時,會自動跳過空白字符,但在讀入字符時,不會跳過空白字符
讀寫一個單獨的字符時,可以用getshar和putchar代替:[ch=getchar(); putchar(ch)]
8.sizeof(類型名)
sizeof返回的值是無符號的整數
可以用sizeof(a)/sizeof(a[0])計算數組長度
9.類型定義(typedef)
typedef int Bool; //(後面變量名的首字母最好大寫)
Bool flag; //類似與使用了int flag
C語言庫自身使用typedef定義了一些不同的類型名,這些類型名通常以_t結尾,比如clock_t;
10.宏定義
【定義宏】#define BOOL int
數組和指針不能用宏定義,宏定義中的替換列表爲空也是合法的,也可以包含對另一個宏的調用,但宏不能調用它本身.
一個數字常量,如果不是0和1,就最好定義成宏.
【帶參數的宏】 #define 標示符(x1,x1,…,xn) 替換列表 //參數在替換列表中都要放在圓括號中
#運算符將一個宏的參數轉換爲字符串字面量;##運算符可以將兩個記號(如標示符)粘在一起,成爲一個記號.
#define CONCAT(x,y) x##y //CONCAT(a,b)會得到ab,但CONCAT(a,CONCAT(b,c))不會得到abc,而是aCONCAT(b,c)
【在宏中放入語句】放在do{ … }while(0)中,這時while後可以不加分號,在調用宏的時候再加分號;如果定義的時候就有分號,在調用的時候就不能再加分號了.
【取消宏】#undef 標示符
【預定義宏】_DATE_宏(mm dd yyyy)和_TIME_宏(hh:mm:ss)指明程序編譯的時間,是字符串字面量.
_LINE_宏: 被編譯的文件的行數,是整型常量 _FILE_宏: 被編譯的文件的名字,是字符串字面量
_STDC_宏: 如果編譯器接受標準C,值爲1,是整型常量
11.數組
定義數組時,數組名不能和變量名相同,也不能和系統關鍵字一樣
數組的地址就是數組元素的首地址,使用數組名a,代表的就是數組a[]的地址
多維數組:不常用,因爲指針數組更加的靈活好用
無論是一維數組還是多維數組,通過單詞const作爲數組聲明的開始可以把數組變爲“常量”;
(如:const int months[]={31,28,31,30,31,30,31,31,30,31,30,31};)
12.函數
在調用函數前,都需要先聲明函數,函數聲明類似與函數定義的第一行,函數的聲明必須與函數的定義一樣。
【函數聲明】 返回類型 函數名(形式參數); 【編譯器只預處理函數,但是不分配內存】
【函數定義】 返回類型 函數名(形式參數) 【開闢內存空間,分配內存給函數,函數入口地址爲函數名所在地址】
{
聲明
語句
}
【函數調用】 函數名(實參);
函數無法返回數組;如果忽略返回類型,會假定爲int型;
如果返回爲空類型,必須寫明void,如果沒有形參,也要寫明爲void.如:void function(void)
數組做函數參數的時候,傳遞的是數組名,也就是地址
實參是通過值來傳遞的,在調用函數時,計算出實參的值然後傳遞(賦值)給對應的形參
函數分類:
1)從用戶角度分有:
系統函數:庫函數,不需要用戶定義,包含頭文件就可以直接使用的函數,printf();scanf();system();
自定義函數:用戶根據自己的需要,聲明函數的返回值類型,函數名,形參列表;但是必須符合函數聲明的規則。可以是有參、無參、有返回值、無返回 值、還可以是函數的重寫。
2)從返回值的角度:
有返回值:return,(函數有且只能返回一個返回值),可以是基本數據類型和指針等。
無返回值:void,在C89標準中可以忽略返回值類型,UNIX中默認爲int,Window VC中爲void;在C99標準中不可以忽略返回值類型。
3)從參數的角度:
帶參數:需要告訴編譯器參數的類型和數量
int main(int argc,char *argv[]);
int main(int argc,char **argv);
不帶參數:參數列表爲空,void,可以省略,即int main()或int main(void);就是不需要參數來參與運算。
13.返回語句
return:【return 表達式】如果return中表達式的類型和函數返回的類型不一致,系統會把表達式的類型轉換成返回類型
exit:任何函數中都可以使用,【exit(表達式)】;包含在#include <stdlib.h>頭文件中。
main函數的返回值是狀態碼,0表示正常結束,非0爲異常結束。
14.遞歸
遞歸是函數嵌套調用的一種特殊形式,就是當一個函數調用另外一個函數的過程時,另一個函數恰好是它本身。
遞歸產生的條件:直接或間接的調用它本身,有一個出口(即結束遞歸的條件),要有一個明顯的公理或公式,有規律可循。
1)直接遞歸調用
2)間接遞歸調用
注意:
1)遞歸調用在發生時,消耗了大量的系統資源,反覆讀寫棧內存,CPU資源,會降低系統性能,不可控或深層遞歸都會造成系統不穩定
2)我們在開發過程中使用的都是淺層遞歸,如Windows搜索,使用了多線程,淺層遞歸(一般不超過3層)
3)遞歸調用也被用於分治算法
4)能不用就不用遞歸,如:.(){ .|. };. (.本身是一個函數,()爲.函數的參數列表,;前爲.函數的定義,最後一個.是調用這個函數)
15.迭代
迭代法又叫做遞推,凡是能用遞歸來實現的迭代或遞推都能實現。
八皇后問題,水仙花束問題,兔子繁殖問題,約瑟夫出圈問題,快速排序,漢諾塔
16.變量作用域
全局變量:
局部變量:
17.指針
簡單的說,指針就是地址。
內存中的最小存儲單元是字節,一個字節有8位,每一位都有自己的地址,指針就是他們的首地址。
基本數據類型的指針,就是他們在內存中的首地址。
構造類型的指針,數組,他的位置就是,數組名稱所在內存的首地址,即a[0]的地址或者是數組名。
函數類型的指針,就是函數的入口地址,即函數名所在內存的首地址。
指針變量:和其他基本數據類型的變量類似,特殊的是他是能保存地址的變量。
【定義指針變量】類型標示符 *變量名(int *p);
指針變量初始化:
int *p=NULL;
int *q=&a;
int *s=100;
造成野指針的三種方式:
1.定義指針的時候沒有初始化
2.指針指向一個局部變量,當局部變量執行完畢釋放後,仍然指向該局部變量的時候(用完置NULL即可)
3.當指針指向一塊動態的內存空間時malloc(new)使用完畢被free(delete)釋放後,仍然指向該空間,(用完置NULL即可)
18.指針和常量的關係
1)常量指針:
指針指向了一個常量的指針變量,常量的內容不可以修改,但是地址可以修改。
int const *p; const在*號後,是p的值不能修改;const在*號前,是*p的內容不能修改
2)指針常量
是個常量,是指指針變量P的值不能被修改,p是個常量,而指針指向的對象的值是可以改變的。
int *const p;
19.指針和數組的關係
1)數組指針
指向一維數組的指針,即數組的首元素
int a[10];int *p=NULL;p=a;
是一個指向二維數組一行的指針變量,即二維數組的首行的地址
int (*p)[表達式];
2)指針數組
數組元素是指針變量
int *p[表達式];
20.指針和函數的關係
1)函數指針
就是指向函數名的指針變量
int (*p)(形參列表);
2)指針函數
它是一個返回指針類型的函數,即函數的返回類型是指針
int *p(形參列表);
如:int *adr(int x)
{
return &x;
}
這種情況,返回值還可以繼續作爲函數的參數來使用,如回調函數,比如Unix信號和信號量,signal(),malloc()
21.指向指針的指針
int a=0; int *p=&a; int **q=&p; //*p==a, **q==a
22.結構體(struct)
能夠保存不同數據類型的一種構造數據類型
【關鍵字】struct
【定義結構體】 struct 結構體名{
成員變量1;
成員變量2;
…
成員變量n;
}; //分號表示結構體定義完畢
【結構體變量初始化】(結構體變量就是包含結構體所有屬性的一個變量)
struct struct_name 變量名={ , , }; //struct struct_name 整體是變量的類型
或 struct struct_name{
成員變量1;
成員變量2;
}object_name={ , , };
【引用結構體成員變量】
. 或 ->(用於指針的時候)
【結構體和指針】
1)指向結構體的指針
struct struct_name *p; //p是指針變量名
p=&object_name; //&取地址符不能忘
【結構體的嵌套】
一個結構體中嵌套了另一個結構體
struct struct_name{
成員變量1;
成員變量2;
struct struct_name_a{
成員變量a;
成員變量b;
}object_name_a;
}object_name;
【引用】object_name.object_name_a.b;
【結構體變量(可以用指針)作爲函數參數使用】
void struct_function(struct struct_name object_name_temp);
void struct_function(struct struct_name *p);
【結構體變量作爲函數的返回值】
struct struct_name function_name()
{
return object_name;
}
【結構體數組】
數組中的每個元素都是一個結構體變量
struct struct_name{
成員變量1;
成員變量2;
}數組名[表達式]; //表達式可以爲空,爲空的時候是不定長度的數組
23.聯合體(union)
用戶自定義的數據類型,和結構體不同的是結構體變量獨立使用各自成員的內存空間,聯合體的所有成員共享存儲空間,即使用聯合體省內存.
定義聯合體和結構體一樣,只需要把struct改成union即可.
24.枚舉(enum)
枚舉的成員中只能用一種類型,成員內不指定序號的時候都以0開始,C語言把枚舉變量和常量作爲整數來處理
enum color{red, black, blue}a,b; //中間以逗號隔開
25.鏈表
鏈表是線性表的一種,是線性表的鏈式存儲;是一種使用動態內存分配的,能保存不同數據類型的一種數據結構.構成鏈表的最小單位稱爲節點.
1)單鏈表
有一個頭結點(非空)鏈表,結點之間有前驅和後繼,尾結點無後繼結點,指向空.節點有兩部分組成數據域和指針域.頭結點用於判斷鏈表是否爲空.
2)雙鏈表
3)循環鏈表
26.位操作
1)位與&
用於置0
2)位或|
用於置1
3)位異或^
用於加密和解密:
0000 1100 0000 0110 (用第一次異或得到的值,再進行異或)
^ 0000 1010 ^ 0000 1010 (用同一個值作爲異或的運算)
-------------- --------------
0000 0110 0000 1100 (重新得到了之前的值)
交換a,b的值: a=a^b; b=a^b; a=a^b;
4)位取反~
都是用補碼來取反的
連帶符號位一起取反,取反之後符號位爲1的話(補碼),減去1(反碼),除去符號位其它位取反(原碼)
5)左移<<
往左移,低位補0,左移一位相當於乘以2,左移n位相當於乘以2的n次方
6)右移>>
往右移,高位補符號位,右移一位相當於除以2,右移n位相當於除以2的n次方
Mac下左移右移符號位不變
27.內存管理
系統級內存管理(Unix系統提供的內存管理)
1)物理內存管理
2)虛擬內存管理
3)頁面文件getpage
4)內存映射mmap
5)共享內存
brk();//在分配內存時效率很高,只有3個值:大於0(申請該大小的內存空間),等於0(完全釋放內存),小於0(在當前的內存空間上釋放該大小的空間)
sbrk();
OC中用的內存管理,計數,當計數爲0的時候就自動釋放(OC brk)
用戶級內存管理
1)malloc
2)calloc
3)realloc
C語言執行過程中,在內存中的情況:
1)代碼段(靜態區域)
代碼段由程序中的機器碼組成,C語言中源代碼編譯後就形成了機器碼,執行的時候,CPU的程序計數器指向了代碼段的每一條代碼,並由CPU依次執行
2)數據段(靜態區域)
只讀數據段,是程序使用一些不會被修改的數據,使用這些數的方式類似與查表式的操作,放置在只讀存儲器中;
已初始化讀寫數據段,是在程序中聲明,有初值的變量,需佔用寄存器空間,在程序執行時,位於可讀寫的內存區域內,供程序運行時讀寫
3)BSS段(未初始化讀寫數據段)(靜態區域)
是在程序中聲明,沒有初始化的變量,這些變量在程序運行前不佔存儲器空間
4)堆空間(動態區域)
只在程序運行時出現,一般由程序員分配和釋放,在具有操作系統的情況下,若程序員忘記釋放,在程序結束後,系統會自動回收內存
5)棧內存(動態區域)
只在程序運行時出現,在函數內部使用的變量,函數參數及返回值,函數調用(遞歸)都將使用棧空間.棧空間是由編譯器自動分配和釋放的內存.
C語言提供的兩種內存分配的方式:
1).靜態內存分配(棧內存)
由編譯器自動爲我們分配的內存空間,一般棧內存的大小爲10M~30M
內存的對齊:是指整型佔4個字節,大於4個字節的數據類型,也按照4個字節來計算,而且是4的整數倍
內存的補齊:只在結構體中有,如果所佔字節數不足4個數據類型,比如char,需將不足的字節數補空,補夠4個字節
2).動態分配內存(malloc向堆申請內存)
malloc函數(分配後不初始化)
void *malloc(size_t size); //返回值是萬能指針,在Mac下佔8個字節,在Win下佔4個字節
用於動態的向堆內存申請內存空間,使用完後,要使用free()函數釋放該內存
一旦指針p指向動態分配的內存塊,就可以忽略p是指針的事實,並且把它用作數組的名字,p[i].
free函數
calloc函數(分配之後初始化爲0)(用於存儲構造類型或複合類型數據)
void *calloc(size_t nmemb, size_t size); //有nmemb個元素,每個元素佔size個字節,nmemb爲1表示爲任何的數據類型
realloc函數
void *realloc(void *ptr, size_t size); //ptr指向通過malloc或calloc或realloc分配的內存塊
28.預處理
預處理器的行爲是由指令控制的,這些指令是由#開頭的一些命令.指令總是在第一個換行符處結束,若想在下一行繼,在當前行末尾用\字符.
【宏定義】 #define指令定義一個宏, #undef指令刪除一個宏定義.
【文件包含】#include導致一個指定文件的內容被包含到程序中.
1)#include <文件名> //用於C語言自身庫的頭文件
2)#include "文件名" //用於所有其他頭文件,也包含任何自己編寫的文件
3)#import <文件名> "文件名" //被包含的頭文件只被編譯一次,不會重複
【條件編譯】#if, #ifdef, #ifndef, #elif, #else和#endif指令將一段文本塊包含到程序中或排除在程序之外.
1)#if 常量表達式 //當預處理器遇到#if時,如果表達式的值爲0,在#if和#endif之間的行會在預處理過程中從程序中刪除,
語句 否則,在它們之間的行會被保留在程序中,並繼續被編譯器處理,這時的#if和#endif對程序無任何影響.
#endif 對於沒有定義過的標示符,#if會把它當做是值爲0的宏對待
2)defined 僅用於預處理器,當defined應用於標示符時,若標示符是個定義過的宏返回1,否則返回0.常用在#if指令中
#if defined(BULL)
...
#endif
3)#ifdef 標示符 //等價於 #if defined(標示符)
#ifndef
4)#elif 表達式1
#else
【特殊】#error, #line, #pragma更爲特殊,較少用到.
1)#error 消息
2)#line n ("文件名") //line指令用於改變給程序行編號的方式,n在1和32767之間,之後的行編號爲n+1,文件名可以沒有
3)#pragma 記號 //對非常大的程序或需要使用指定編譯器的特殊功能的程序很有用
29.鏈表
由一連串的結構(結點)組成的,其中每個結點都包含指向下一個鏈中結點的指針,最後的一個結點包含一個空指針.
for (p = first; p!=NULL; p = p->next)
30.文件
C程序中的流的訪問是通過文件指針實現的,類型爲FILE *fp;文件結束符EOF(由宏定義的);
1)打開文件
FILE *fopen(const char *filename, const char *mode); //filename可以包含路徑信息,沒打開返回空指針
打開的模式(mode):
"r":只讀 "w":寫,文件不需要存在 "a":追加,文件不需要存在
"r+":讀和寫,從文件頭開始 "w+":讀和寫,文件存在就截取 "a+":讀和寫,文件存在就追加
如果打開的是二進制文件,需要在模式字符串中包含字母b, 即"rb"
2)關閉文件
int fclose(FILE *stream); //參數必須爲文件指針,指針來自fopen或freopen,成功關閉文件返回0,否則返回EOF
3)爲流附加文件
FILE *freopen(const char *filename, const char *mode, FILE *stream); //stream有stdin,stdout,stderr
4)格式化輸入/輸出
fscanf(fp,"%d", &i) //從fp中讀入 fscanf(stdin, "%d", &i)等價於scanf("%d", &i)
fprintf(fp, "%d", i) //從fp中讀出 fprintf(stdout, "%d", i)等價於printf("%d", i)
5)字符讀寫函數
fgetc(fp) getc(fp)
fputc(ch, fp) putc(ch, fp)
6)行的輸入/輸出
fgets(char *s, int n, FILE *stream) //n爲限制要讀入字符的數量, 爲字符串的時候要額外的加1, 表示爲'\0'
gets(char *s) //逐個讀取字符
fputs(const char *s, FILE *stream) //系統不會自己寫入換行符, 可以人爲的加入"\r\n"
puts(const char *s) //系統會自動寫入換行符
7)塊的輸入/輸出(主要用於操作二進制文件)
fread()
fwrite()
8)fflush(fp) //爲fp清洗緩衝區 fflush(NULL) //清洗全部緩衝區
remove("filename") //刪除文件,參數是文件名,而不是文件指針
rename("filename", "newfilename") //重命名文件名, 參數也是文件名,不是文件指針, 文件此時要是關閉狀態
fcopy f1.c f2.c //把文件f1.c複製給文件f2.c
feof(fp)
ferror(fp)
fseek //文件位置 文件開始: SEEK_SET 文件當前位置: SEEK_CUR 文件結尾: SEEK_END
31.gcc編譯的過程
預處理(Pre-processing):-E
編譯器將C源代碼中包含的頭文件如stdio.h編譯進來(.i)
編譯(Compliling):-S
編譯器首先檢查代碼的規範性, 是否有語法錯誤等, 以確定代碼實際要做的工作, 無誤後, 將代碼翻譯成彙編語言(.s)
彙編(Assembling):-c
把編譯階段生成的.s文件轉成二進制目標代碼(.o)
鏈接(Link):
將輸出文件.o鏈接成最終的可執行文件
32.函數庫
靜態庫(.a): 是指在編譯鏈接時, 把庫文件中的代碼全部加入到可執行文件中, 因此生成的文件比較大, 但在運行時就不再需要庫文件了.
動態庫(.so): 在編譯鏈接時, 並沒有把庫文件的代碼加入到可執行文件中, 而是在程序執行時由運行時鏈接文件加載庫, 節省了系統開銷.
gcc編譯時, 默認使用動態庫.