考研:C語言複習筆記 [Hex Note]
C語言複習筆記
C語言編譯環境與發展史
C語言是一門編譯型的程式語言,原始碼要經過編譯器編譯成彙編程式碼,彙編程式碼經過彙編成機器碼之後才能被機器執行。編譯器(如下圖gcc)編譯包括四步:1.預處理(Preprocessing), 2.編譯(Compilation), 3.彙編(Assemble), 4.連結(Linking),下面這張圖已經夠詳細了。
C語言在不同環境下的編譯方式有很大不同,一大堆名詞可能會看得雲裡霧裡,所以對下列名詞進行解釋。在類Unix系統(Linux發行版、macOS)上主流的兩個編譯器,一個是gcc,一個是Clang。gcc是屬於GNU(免費UNIX軟體集合)專案的編譯器,Clang是LLVM專案的編譯器。在Windows上,微軟的整合開發環境(Visual Studio)上自帶了C語言的編譯器;或者選擇在Widnows上模擬Linux的編譯環境,可以選擇MinGW或Cygwin。
根據CPU的指令集不同,可以分為複雜指令集和簡單指令集,複雜指令集的代表是Intel,簡單指令集的代表是ARM。如果要在Intel CPU的機器上編譯能夠執行在ARM架構CPU的機器上,還需要用到交叉編譯工具鏈。做嵌入式的C語言開發離不開這個。
# 使用gcc編譯器編譯hello.c原始碼
# 編譯完成後會生成一個hello的可執行檔案
gcc hello.c -o hello
C語言主要的三個標準:C89(C90)、C99、C11,語言標準的命名是標準的公佈年份。
- C90是第一個C語言標準,其內容和ANSI C保持一致。
- C99是第二個C語言標準。在C90的基礎上增加了:1.國際化程式設計;2.解決明顯的缺陷;3.適應科學和工程專案中的關鍵數值計算。
- 增加了對編譯器的限制,比如源程式每行要求至少支援到 4095 位元組,變數名函式名的要求支援到 63 位元組(extern 要求支援到 31)。
- 增強了預處理功能。例如:
- 巨集支援取可變引數 #define Macro(…) __VA_ARGS__
- 使用巨集的時候,允許省略引數,被省略的引數會被擴充套件成空串。
- 支援 // 開頭的單行註釋(這個特性實際上在C89的很多編譯器上已經被支援了)
- 增加了新關鍵字 restrict, inline, _Complex, _Imaginary, _Bool
- 支援 long long, long double _Complex, float _Complex 等型別
- 支援不定長的陣列,即陣列長度可以在執行時決定,比如利用變數作為陣列長度。宣告時使用 int a[var] 的形式。不過考慮到效率和實現,不定長陣列不能用在全域性,或 struct 與 union 裡。
- 變數宣告不必放在語句塊的開頭,for 語句提倡寫成 for(int i=0;i<100;++i) 的形式,即i 只在 for 語句塊內部有效。
- 允許採用(type_name){xx,xx,xx} 類似於 C++ 的建構函式的形式構造匿名的結構體。
- 複合字面量:初始化結構的時候允許對特定的元素賦值,形式為:
- struct test{int a[3],b;} foo[] = { [0].a = {1}, [1].a = 2 };
- struct test{int a, b, c, d;} foo = { .a = 1, .c = 3, 4, .b = 5 }; // 3,4 是對 .c,.d 賦值的
- 格式化字串中,利用 \u 支援 unicode 的字元。
- 支援 16 進位制的浮點數的描述。
- printf scanf 的格式化串增加了對 long long int 型別的支援。
- 浮點數的內部資料描述支援了新標準,可以使用 #pragma 編譯器指令指定。
- 除了已有的 __line__ __file__ 以外,增加了 __func__ 得到當前的函式名。
- 允許編譯器化簡非常數的表示式。
- 修改了 /% 處理負數時的定義,這樣可以給出明確的結果,例如在C89中-22 / 7 = -3, -22% 7 = -1,也可以-22 / 7= -4, -22% 7 = 6。 而C99中明確為 -22 / 7 = -3, -22% 7 = -1,只有一種結果。
- 取消了函式返回型別預設為 int 的規定。
- 允許 struct 定義的最後一個陣列不指定其長度,寫做 [](flexible array member)。
- const const int i 將被當作 const int i 處理。
- 增加和修改了一些標準標頭檔案,比如定義 bool 的 <stdbool.h> ,定義一些標準長度的 int 的 <inttypes.h> ,定義複數的 <complex.h> ,定義寬字元的 <wctype.h> ,類似於泛型的數學函式 <tgmath.h>, 浮點數相關的 <fenv.h>。 在<stdarg.h> 增加了 va_copy 用於複製 … 的引數。裡增加了 struct tmx ,對 struct tm 做了擴充套件。
- 輸入輸出對寬字元以及長整數等做了相應的支援。
- C11是最近的一次C語言標準。主要是提高了對C++的相容性。
- 對齊處理(Alignment)的標準化(包括_Alignas標誌符,alignof運算子, aligned_alloc函式以及<stdalign.h>標頭檔案。
- Noreturn 函式標記,類似於 gcc 的 __attribute_((noreturn))。
- _Generic 關鍵字。
- 多執行緒(Multithreading)支援,包括:
- _Thread_local儲存型別識別符號,<threads.h>標頭檔案,裡面包含了執行緒的建立和管理函式。
- _Atomic型別修飾符和<stdatomic.h>標頭檔案。
- 增強的Unicode的支援。基於C Unicode技術報告ISO/IEC TR 19769:2004,增強了對Unicode的支援。包括為UTF-16/UTF-32編碼增加了char16_t和char32_t資料型別,提供了包含unicode字串轉換函式的標頭檔案<uchar.h>.
- 刪除了 gets() 函式,使用一個新的更安全的函式gets_s()替代。
- 增加了邊界檢查函式介面,定義了新的安全的函式,例如 fopen_s(),strcat_s() 等等。
- 增加了更多浮點處理巨集。
- 匿名結構體/聯合體支援。這個在gcc早已存在,C11將其引入標準。
- 靜態斷言(static assertions),_Static_assert(),在解釋 #if 和 #error 之後被處理。
- 新的 fopen() 模式,(“…x”)。類似 POSIX 中的 O_CREAT|O_EXCL,在檔案鎖中比較常用。
- 新增 quick_exit() 函式作為第三種終止程式的方式。當 exit()失敗時可以做最少的清理工作。
資料型別
基本資料型別:short、int、long、char、float、double。
注:在不同字長的機器上,資料型別佔據的位元組長度以實際為準。比如在32位機器上int佔據的記憶體大小是4 個byte;double佔據的記憶體大小是8 個byte;
基本資料型別的轉換隻能從高精度到低精度:
另外,在<limit.h>中規定了整型的上下限值。
型別 | 有符號最小值 | 有符號最大值 | 無符號最大值 |
---|---|---|---|
字元 | SCHAR_MIN | SCHAR_MAX | UCHAR_MAX |
短整型 | SHRT_MIN | SHRT_MAX | USHRT_MAX |
整型 | INT_MIN | INT_MAX | UINT_MAX |
長整型 | LONG_MIN | LONG_MAX | ULONG_MAX |
在<float.h>中規定了浮點數的上下限值。
型別 | 最小值 | 最大值 |
---|---|---|
float | FLT_MIN | FLT_MAX |
double | DBL_MIN | DBL_MAX |
long double | LDBL_MIN | LDBL_MAX |
列舉型別
//定義列舉型別
enum WEEKDAY {
MONDAY, TUESDAY, WEDNESDAY,
THURSDAY, FRIDAY, SATURDAY,
SUNDAY
};
enum SHORT_WEEKDAY {
MON = 1, TUE = 2, WED = 3,
THU = 4, FRI = 5, SAT = 6,
SUN = 0
};
printf("%d\n", MONDAY); //0
printf("%d\n", sizeof(MONDAY)); //4
enum WEEKDAY mon = MONDAY; //定義列舉變數
定義型別別名:typedef關鍵字
typedef int IntegerNumber;
IntegerNumber a;
控制流和函式
- if-else
- while
- do-while
- for
- switch-case-default
- break
- continue
- goto
switch
#include <stdio.h>
int main() {
enum {
SUN, MON, TUE, WED, THU, FRI, STA
};
int input;
while (1) {
printf("input=");
scanf("%d", &input);
switch (input) {
case 0:
printf("Today is SUN.\n");
break;
case 1:
printf("Today is MON.\n");
break;
case 2:
printf("Today is TUE.\n");
break;
case 3:
printf("Today is WED.\n");
break;
case 4:
printf("Today is THU.\n");
break;
case 5:
printf("Today is FRI.\n");
break;
case 6:
printf("Today is STA.\n");
break;
default:
printf("WRONG INPUT\n");
break;
}
}
return 0;
}
goto
#include <stdio.h>
int main() {
int number;
outer:
printf("input a number:");
scanf("%d", &number);
if (number < 0) {
printf("the input number can't be negative\n");
goto outer;
}
printf("input success:%d\n", number);
return 0;
}
可變長引數列表
#include <stdio.h>
#include <stdarg.h>
int calc_sum_of_numbers(int n, ...) {
int sum = 0;
va_list var_arg;//宣告結構體物件
va_start(var_arg, n);//初始化引數指標,分配記憶體
for (int i = 0; i < n; ++i) {//指標往後移動多少位需要傳參
sum += va_arg(var_arg, int);//通過指標間接訪問,並向後移動一位
}
va_end(var_arg);//銷燬指標,釋放記憶體
return sum;
}
int main() {
int res = calc_sum_of_numbers(3, 1, 2, 3);
printf("res=%d\n", res);
return 0;
}
操作符和表示式
- 算術操作符:算數運算 + - * / %
- 移位操作符:進行二進位制移位操作 << 左移直接丟棄 >> 右移分為邏輯右移(補0)和算數右移(符號不變)
- 位操作符:進行二進位制位運算 AND(按位與,&,相同為1)、OR(按位或,|,有1為1)、XOR(按位異或,^,不同為1)
- 賦值 = 從右到左 可以和其他操作符結合
- 單目操作符:只作用於一個變數 ! ++ - & sizeof ~ – + * (型別)?)表示強轉;a++和++a的區別
- 關係操作符:> >= < <= != ==
- 邏輯操作符:&& ||
- 條件操作符:表示式?表示式為真:表示式為假
- 逗號操作符:用逗號分隔多個表示式,最後一個表示式的結果就是整體的值
- 下標引用、函式呼叫、結構成員:array[下標]、*(array+下標);.用來訪問結構體的成員,->用指標操作符操作指標指向的物件(此物件非彼物件);
- 布林值:0為假,非0為真
- 左值和右值(L-Value、R-Value):左值只能是標識某個特定位置的值
位運算
#include <stdio.h>
int main() {
// 13 = 00001101
// 21 = 00010101
int a = 13, b = 21;
printf("%d\n", a & b); // 5 = 00000101
printf("%d\n", a | b); // 29 = 00011101
printf("%d\n", a ^ b); // 24 = 00011000
return 0;
}
移位運算
#include <stdio.h>
int main() {
printf("sizeof(int)=%d\n", sizeof(int)); //4Byte = 32bit
int num1 = 24;
int num2 = -24;
printf("24 = 00011000\n");
printf("24 << 2 = %d = 01100000\n", num1 << 2);
printf("24 >> 2 = %d = 00000110\n", num1 >> 2);
printf("-24 << 2 = %d\n", num2 << 2);
printf("-24 >> 2 = %d\n", num2 >> 2);
return 0;
}
結果:從結果上看移位並沒有收到正負號的影響,僅僅是對數字的二進位制串進行移位操作。
sizeof(int)=4
24 = 00011000
24 << 2 = 96 = 01100000
24 >> 2 = 6 = 00000110
-24 << 2 = -96
-24 >> 2 = -6
操作符優先順序
基本輸入輸出
格式化輸出
%[flags][width][.prec] type
- flag
- 不填或+ 輸出的內容右對齊
- - 輸出的內容左對齊
- width 控制輸出內容的寬度
- prec 如果輸出的內容是浮點數,它用於控制輸出內容的精度(四捨五入)
- type
- %hd、%d、%ld 以十進位制、有符號的形式輸出 short、int、long 型別的整數。
- %hu、%u、%lu 以十進位制、無符號的形式輸出 short、int、long 型別的整數
- %c 輸出字元。
- %lf 以普通方式輸出double(float棄用,long doube無用)。
- %e 以科學計數法輸出double。
- %s 輸出字串。
輸入/輸出
- scanf():控制格式的輸入
- getchar():輸入單個字元
- gets():獲取一行內容,作為字串處理
- putchar():輸出單個字元
- puts():輸出一行內容
示例:
#include <stdio.h>
int main() {
int n;
printf("input a number:");
scanf("%d", &n);
printf("n=%d\n", n);
fflush(stdin); //這裡一定要重新整理緩衝區,不然回車就會被視作第二個輸入
printf("input a char:");
char ch = getchar();
printf("ch=%c\n", ch);
fflush(stdin);
printf("input a string:");
char *s;
gets(s);
printf("s=%s\n", s);
fflush(stdin);
printf("output a char:");
putchar('A');
printf("\n");
printf("output a string:");
puts("Hello world!\n");
return 0;
}
緩衝區
緩衝區的意義:緩衝主要是為了解決IO裝置和CPU之間速度不匹配的問題。為了避免頻繁佔用CPU,先把資料讀取到緩衝,IO從緩衝去取資料。
以下四種情況會重新整理緩衝區,進行真正的IO操作。
- 緩衝區滿時;
- 執行flush語句;
- 執行endl語句;
- 關閉檔案。
附上一張ASCII碼錶:
指標與陣列
指標
- * 取指標的內容
- & 取變數的地址
- 要確保指標被初始化,然後再使用。至少要賦個NULL,不至於出現野指標
- NULL在C語言裡面是0,對空指標進行間接訪問,會報錯(msvc)
常量指標、指標常量、指向常量的指標常量
#include <stdio.h>
int main() {
/*
常量指標:指向一個常量的指標
int const *p;
const int *q;
*/
int a = 1, b = 2, c = 3, d = 4, e = 5;
int const *pta = &a;
int *ptb = &b;
printf("*pta=%d\t*ptb=%d\ta=%d\tb=%d\n", *pta, *ptb, a, b);
/* 報錯:常量指標指向的記憶體不允許通過指標修改 */
// *pta = c;
*ptb = c;
printf("*pta=%d\t*ptb=%d\ta=%d\tb=%d\n", *pta, *ptb, a, b);
/* 可以修改指標的指向 */
pta = &d;
ptb = &d;
printf("*pta=%d\t*ptb=%d\ta=%d\tb=%d\n", *pta, *ptb, a, b);
/* 可以通過變數自身修改值,常量指標只是限制了通過指標修改 */
a = e;
b = e;
printf("*pta=%d\t*ptb=%d\ta=%d\tb=%d\n", *pta, *ptb, a, b);
return 0;
}
#include <stdio.h>
int main() {
/*
指標常量:本質是一個常量,常量的值是一個指標
int * const pt;
*/
int a = 1, b = 2, c = 3;
int *const pt = &a;
printf("*pt=%d\ta=%d\tb=%d\n", *pt, a, b);
/* 報錯:常量不能被賦值 */
// pt = &b;
printf("*pt=%d\ta=%d\tb=%d\n", *pt, a, b);
/* 可以通過指標修改 */
*pt = c;
printf("*pt=%d\ta=%d\tb=%d\n", *pt, a, b);
return 0;
}
// 綜合二者的特點,指標指向不可修改,指標指向的內容也不可修改
const int * const pt = &a;
const關鍵字和#define的區別:#define可以用在所有允許字面值常量的地方,const只能用於允許變數的地方。
函式指標、指標函式、回撥函式
#include <stdio.h>
int function(int a, int b){
printf("a=%d,b=%d\n",a,b);
}
// 函式指標:指向函式的指標
int (*p)(int,int);
int main(){
p = function;
p(1,2);
(*p)(1,2);
return 0;
}
// 指標函式:返回值是指標的函式
List *addNode(List list);
// 回撥函式
#include <stdio.h>
void handle(void (*callback)(int, int), int n1, int n2) {
callback(n1, n2);
}
void add(int n1, int n2) {
printf("%d+%d=%d\n", n1, n2, n1 + n2);
}
void minus(int n1, int n2) {
printf("%d-%d=%d\n", n1, n2, n1 - n2);
}
int main() {
handle(add, 1, 2);
handle(minus, 3, 4);
return 0;
}
字串常量
其實就是在堆上開闢了一片記憶體,在棧上宣告一個指標,指向堆上記憶體。
#include <stdio.h>
int main(int argc, char **argv) {
printf("%c\n",*("abcdefg"+2));
printf("%c\n","abcdefg"[2]);
return 0;
}
陣列
// 陣列的宣告
int odd_number_array[5] = {1, 3, 5, 7, 9};
int even_number_array[5] = {2, 4, 5};
int nature_number_array[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
char char_array[5] = {'h', 'e', 'l', 'l', 'o'};
char string[] = "hello world!";//字元陣列
char *s = "Hex Studio.";//字串常量
int matrix[2][3] = {{1,2,3},{4,5,6}};
#include <stdio.h>
int main() {
int array[5] = {1, 3, 5, 7, 9};
//因為陣列的第一個元素的地址即陣列的地址,所以可以通過指標運算元組中的元素
printf("res=%d\n", *(array + 2));
return 0;
}
陣列儲存的首地址雖然看上去和指標差不多,也可以首地址直接做類似指標的表示式計算,但是陣列仍然不是指標。指標在分配記憶體的時候,只會為指標本身分配記憶體,而陣列要分配一大塊記憶體,陣列名是一個常量,儲存了這塊記憶體的起始地址。
假設a是陣列名(常量),b是指標變數。*a是合法的,等於取陣列第一個元素的值,*b就不知道是什麼東西了。b++可以通過編譯,a++卻不行,因為b是指標變數,b++等於是指標往後移動一位,a是常量,常量不能自增。(a++可以理解為a = a+1,這裡a是常量,a就不能做左值,所以賦值不成立)
#include <stdio.h>
int main() {
int array[10] = {100, 103, 105, 107, 109, 111, 113, 115, 117, 119};
int *ap = array + 2;//指標指向105
printf("res=%d\n", *(array + 2));//因為陣列的第一個元素的地址即陣列的地址,所以可以通過指標運算元組中的元素
printf("ap=%d\n", ap);//105的地址
printf("*ap=%d\n", *ap);//105
printf("ap[0]=%d\n", ap[0]);//105
printf("ap+6=%d\n", ap + 6);//117的地址
printf("*ap+6=%d\n", *ap + 6);//105+6=111
printf("*(ap+6)=%d\n", *(ap + 6));//117
printf("ap[6]=%d\n", ap[6]);//117
printf("&ap=%d\n", &ap);//ap的地址
printf("ap[-1]=%d\n", ap[-1]);//103
printf("ap[9]=%d\n", ap[9]);//?
return 0;
}
從下面這個例子可以知道,變數是值傳遞,陣列是地址傳遞。
(把陣列名作為引數傳遞給函式,實際上傳遞的是指標。可以用指標來運算元組,如果想要限制原陣列的修改,可以在形參宣告加const關鍵字限制)
#include <stdio.h>
void init_array(int arg_arr[], int len) {
printf("address[arg_arr]=%d\taddress[len]=%d\n", arg_arr, &len);
for (int i = 0; i < len; ++i) {
arg_arr[i] = 100;
}
}
void print_array(int arg_arr[], int len) {
printf("address[arg_arr]=%d\taddress[len]=%d\n", arg_arr, &len);
for (int i = 0; i < len; ++i) {
printf("arg_arr[%d]=%d\n", i, arg_arr[i]);
}
}
int main() {
int length = 10;
int arrar[10];
printf("address[array]=%d\taddress[length]=%d\n", arrar, &length);
init_array(arrar, length);
print_array(arrar, length);
return 0;
}
字串
字串的結束符NUL,但是NUL不算進字串的長度。所謂的NUL就是\0。
字串相關函式原型:
- 字串函式
- size_t strlen(char const *string);返回的是一個無符號整數,無符號整數永遠大於0,不注意這個可能會出現非預期效果
- char *strcpy(char *dst,char const *src);從一個字串複製到另一個字串
- char *strcat(char *dst,char const *src);連線兩個字串
- int strcmp(char const *s1,char const *s2);比較兩個字串,s1<s2,返回小於0的值,s1>s2,返回大於0的值
- char *strncpy(char *dst,char const *src,size_t len);向dst寫入len個字元,如果src的長度大於len,可能拷貝過去的就沒有結束符,就會出現非預期效果
- char *strncat(char *dst,char const *src,size_t len);
- int strncmp(char const *s1,char const *s2,size_t len);
- char *strchr(char const *str,int ch);查詢字元ch第一次出現的位置,不存在就返回NULL指標
- char *strrchr(char const *str,int ch);查詢字元ch最後一次出現的位置,不存在就返回NULL指標
- char *strpbrk(char const *str,char const *group);返回str中第一個匹配group中任意一個字元的指標,不存在就返回NULL指標
- cahr *strstr(char const *s1,char const *s2);查詢s2在s1中第一次出現的位置,不存在就返回NULL指標,s2是空字串就返回s1
- size_t strspn(char const *str,char const *group);返回str起始部分匹配group中任意字元的字元數
- size_t strcspn(char const *str,char const *group);返回str起始部分不與group中任意字元的字元數
- char *strtok(char *str,char const *sep);用分割不sep分割字串str,返回被分割出的片段的指標
- char *strerror(int error_number);返回描述錯誤的字串。錯誤碼儲存在error.h中,建議把該檔案包含進來
- 字元分類函式
- iscntrl();任何控制字元
- isspace();空白字元:空格’’、換頁’\f’、換行’\n’、回車’\r’、製表符’\t’、垂直製表符’\v’
- isdigit();十進位制數字0~9
- isxdigit();十六進位制數字,包括所有十進位制數字,小寫字母af,大寫字母AF
- islower();小寫字母a~z
- isupper();大寫字母A~Z
- isalpha();字母az,AZ
- isalnum();字母或數字,az,AZ,0~9
- ispunct();標點符號,任何不屬於數字或字母的圖形字元
- isgraph();任何圖形字元
- isprint();任何可列印字元,包括圖形字元和空白字元
- 字元轉換
- int tolower(int ch);
- int toupper(int ch);
- 記憶體操作
- void *memcpy(void *dst,void const *src,size_t length);從src的起始位置複製length個位元組到dst的記憶體起始位置
- void *memmove(void *dst,void const *src,size_t length);memcpy複製時不允許源地址塊和目標地址塊有重疊,memmove允許,因為它先複製到臨時位置
- void *memcmp(void const *a,void const *b,size_t length);對兩段記憶體的內容進行比較
- void *memchr(void const *a,int ch,size_t length);從a的起始位置開始查詢字元ch第1次出現的位置,並返回一個指向該位置的指標
- void *memset(void *a,int ch,size_t length);從a開始length個位元組都設定為字元ch
#include <stdio.h>
#include <string.h>
int main() {
char *s1 = "Hello world!";
char *s2 = "Hello world!\n";
printf("len(s1)=%d\n", strlen(s1));
printf("len(s2)=%d\n", strlen(s2));
char s3[] = "Hello world!", s4[] = "Hex Studio.";
char *s5 = "abcdefg", s6[10] = {0};
printf("s4=%s\ts6=%s\n", s4, s6);
strcpy(s4, s3);
strcpy(s6, s5);
//這裡要注意,不能把陣列複製給指標,因為指標沒有分配存放字串的記憶體
//但是可以把指標複製給陣列,也就是上面的s5複製給s6,陣列是已經開闢了記憶體的
//還有一點就是,複製的時候要保證接收者有足夠大的空間,不然資料還是會丟失,出現非預期效果
printf("s4=%s\ts6=%s\n", s4, s6);
char s7[10] = "He", *s8 = "Xin";
//這裡和前面copy一樣,第一個引數不能傳指標,因為指標不能表示實際的一塊記憶體
//而且拼接的時候要考慮第一個引數是否有足夠大的空間
printf("name=%s\n", strcat(s7, s8));
//strcpy和strcat返回的都是第一個引數的拷貝
char *s9 = "He", *s10 = "Xin";
printf("compare result:%d\n", strcmp(s9, s10));
return 0;
}
結構體和聯合體
結構體
- .操作符用於 結構體物件操作結構體的變數
- ->操作符用於 結構體物件的指標操作結構體的變數
#include <stdio.h>
struct People {
char *name;
int age;
bool gender;
} people = {"Hex", 24, true};
typedef struct Student {
struct People people;
char *student_number;
} Student;
int main() {
// 這個例子裡面包括了結構體變數初始化、給結構體取別名等細節。
Student student;
student.people = people;
student.student_number = "15190308";
Student *ps = &student;
printf("people.name=%s\n", people.name);
printf("people.age=%d\n", people.age);
printf("people.gender=%d\n", people.gender);
printf("student.people=%d\n", student.people);
printf("student.student_number=%s\n", student.student_number);
printf("student.people=%d\n", student.people);
printf("ps->people=%d\n", ps->people);
printf("student.student_number=%d\n", student.student_number);
printf("ps->student_number=%d\n", ps->student_number);
return 0;
}
邊界對齊
為了提高資料存取的速度以及其他原因,存在記憶體對齊機制,即資料的首地址必須是k(通常是4或8)的整數倍。所以結構體的結構直接影響了結構體在記憶體上佔的大小。像下面這種情況,都是一個int,兩個char,變數定義的先後順序直接影響了結構體總的大小。圖1佔了8個位元組,圖2佔了12個位元組。
位段(不適用於需要移植的專案)
位段的成員只能是int、signed int、unsigned int。位段可以把奇數長度的資料包裝在一起,這樣能夠最大限度地利用儲存。
struct CHAR {
unsigned ch :7;
unsigned font:6;
unsigned size :19;
};
struct CHAR ch1;
結構體CHAR的儲存如下圖所示,位段的記憶體分配不再依據記憶體對齊的原則。下圖黃色部分是變數ch,綠色部分是變數font,紅色部分是變數size。
位段的應用,在我有限的C語言開發經歷中沒遇到過。大多數軟體開發中已經不在乎這點記憶體的優化了,能做到記憶體對齊已經足矣。唯一一個我能想到的位段的應用就是計算機網路中的IP報文格式。(我覺得這麼設計的原因有兩點,一是設計網路協議的年代,記憶體吃緊;二是IP協議工作在網路層,大部分底層裝置本身的記憶體不大,只能將利用率最大化)。感覺知識都關聯起來了呢 ?
聯合體
聯合體:在不同時刻把不同的東西儲存到同一位置
//聯合體的宣告
union {
float f;
int i;
} fi;
//聯合體的初始化
union {
int a;
float b;
char c[4];
} x = {5};
動態記憶體分配
函式原型:
void *malloc(size_t size);分配一整塊連續的記憶體
void free(void *pointer);釋放記憶體
void *calloc(size_t,num_elements,size_t element_size);返回記憶體的指標之前先初始化為0
void *realloc(void *ptr,size_t new size);修改已經分配的記憶體塊大小,擴大或縮小
free()函式做的只是釋放記憶體操作,使指標指向一個未知(隨機)的地方。記得要在free之後加上一步賦值NULL的操作,以免後面誤操作指標。(具體問題具體分析)
下面這個例子說明,只要有指標,就可以通過指標變數隨意操作(讀寫)。讀可能讀到隨機值,也就是所謂的“野指標”,其內容是無法預判的。寫就更離譜了,直接在某塊沒有規則的記憶體上寫值,可能會引起更大的問題,比如程式崩潰。一般情況下不建議隨意操作記憶體,使用指標時需要處處小心!!!
指標如此之靈活也不是完全沒有好處,在某些場景下需要往指定的記憶體上寫值,比如作業系統的入口。
#include <stdio.h>
#include <malloc.h>
int main() {
int *a = (int *) malloc(sizeof(int));
*a = 10;
for (int i = 1; i <= 5; ++i) { //給指標後5個單位的記憶體賦值
*(a + i) = *a + i;
}
printf("a=%d\n", *a);
for (int i = 0; i < 20; i++) {
printf("*(a+%d)=%d\n", i, *(a + i)); //列印從a往後20個單位的記憶體儲存的值
}
return 0;
}
/*
a=10
*(a+0)=10
*(a+1)=11
*(a+2)=12
*(a+3)=13
*(a+4)=14
*(a+5)=15
*(a+6)=-659373408
*(a+7)=8107
*(a+8)=1900880
*(a+9)=0
*(a+10)=1928272
*(a+11)=0
*(a+12)=1766677612
*(a+13)=1936683619
*(a+14)=1551132271
*(a+15)=1684957527
*(a+16)=1098086255
*(a+17)=997421168
*(a+18)=1547322171
*(a+19)=1919251285
*/
其他
常用庫函式
-
div_t div(int numerator,int denominator); 除法
-
int rand(void); 返回0~32767(RAND_MAX)的隨機數
-
int atoi(char const *string); 字串轉數字
-
long int atol(char const *string);
-
long int strtol(char const *string, char **unused, int base); 如果值太大且為負數,返回LONG_MIN。如果值太大且為正數,返回LONG_MAX
-
double atof(char const *string); 字元轉浮點數
-
double sin(double angle); 正弦
-
double cos(double angle); 餘弦
-
double tan(double angle); 正切
-
double exp(double x); 返回e的x次冪
-
double log(double x); 返回以e為底的對數
-
double log10(double x); 返回以10為底的對數
-
double pow(double x,double y);
-
double sqrt(double x);
-
double floor(double x); 向下取整
-
double ceil(double x); 向上取整
-
int abs(int value); 返回絕對值
-
double fabs(double x); 絕對值
-
double fmod(double x,double y); 返回除法的餘數
-
check_t clock(void); 把這個值轉換為秒還要除以常量CLOCKS_PER_SEC
命令列引數
#include <stdio.h>
int main(int argc, char **argv) {
// argc:命令列引數的個數
// argv:類似一個指標陣列
for (int i = 0; i < argc; ++i) {
printf("%s\n", *(argv + i));
}
// 這兩種方式都可以,但是要注意第二種方式是移動指標,第一種是計算指標的位置
while (*++argv != NULL) {
printf("%s\n", *argv);
}
return 0;
}
預編譯
預定義符號
- __FILE__ 進行編譯的原始檔名
- __LINE__ 檔案當前行的行號
- __DATE__ 檔案被編譯的日期
- __TIME__ 檔案被編譯的時間
- __STDC__ 如果編譯器遵循ANSI C,其值就為1,否則未定義
#include <stdio.h>
int main(int argc, char **argv) {
printf("%s\n",__FILE__);
printf("%d\n",__LINE__);
printf("%s\n",__DATE__);
printf("%s\n",__TIME__);
printf("%d\n",__STDC__);
return 0;
}
巨集
#define name stuff //巨集定義:字面值替換
#undef name //移除巨集定義
條件編譯
#if //如果條件為真,則執行相應操作
#elif //如果前面條件為假,而該條件為真,則執行相應操作
#else //如果前面條件均為假,則執行相應操作
#endif //結束相應的條件編譯指令
#ifdef //如果該巨集已定義,則執行相應操作
#ifndef //如果該巨集沒有定義,則執行相應操作
#include <stdio.h>
#define DEBUG 1
int main(int argc, char **argv) {
#if DEBUG
printf("DEBUG ENV");
#else
printf("DEVELOP ENV");
#endif
return 0;
}
檔案包含
#include <filename> //庫函式檔案包含
#include "filename" //本地檔案包含
變數的儲存
變數根據宣告的位置和宣告識別符號分為:全域性變數、靜態全域性變數、區域性變數、靜態區域性變數、暫存器變數。全域性、區域性是根據變數宣告的位置區分的,register修飾的就是暫存器變數,static修飾的就是靜態變數。
記憶體上分為程式碼段(存放程式程式碼,不可修改)、資料段(.data段存放的是全域性變數和靜態變數;.bss段存放的未初始化的全域性變數和靜態變數)、堆疊段(堆是類似連結串列的線性結構;棧是後進先出結構,用於儲存區域性變數、函式形參、函式地址)。
連結屬性
- external:全域性變數,非靜態函式
- internal:靜態全域性變數,靜態函式
- none:函式形參,函式內的區域性變數(也包括靜態區域性變數)
關於外部變數的一個示例:
// 源1.cpp
#include <stdio.h>
#include "another.h"
int main() {
printf("var=%d\n",var);
func();
return 0;
}
// 源2.h
extern int var = 10;
extern void func();
// 源2.cpp
#include <stdio.h>
void func(){
printf("Hello world!\n");
}
注:未被初始化的變數不能被引用!!!
相關文章
- C 語言學習筆記筆記
- C語言學習筆記C語言筆記
- c語言複習C語言
- HQYJ嵌入式學習筆記——C語言複習day1筆記C語言
- HQYJ嵌入式學習筆記——C語言複習day2筆記C語言
- C語言學習筆記--C運算子C語言筆記
- c語言學習筆記===函式C語言筆記函式
- c語言筆記C語言筆記
- 初識C語言(01)—學習筆記C語言筆記
- C語言學習筆記——位運算C語言筆記
- c語言程式基礎學習筆記C語言筆記
- C語言學習筆記之變數C語言筆記變數
- C++複習筆記C++筆記
- 嵌入式C語言學習筆記2C語言筆記
- C語言期末複習資料C語言
- C語言指標筆記C語言指標筆記
- C語言例項解析精粹學習筆記——19C語言筆記
- C語言學習筆記之指標的運算C語言筆記指標
- C語言學習筆記:結構體與指標C語言筆記結構體指標
- C++學習筆記-C++對C語言的函式擴充C++筆記C語言函式
- C語言學習筆記01--C開源庫uthash的使用C語言筆記
- C語言筆記——自定義型別C語言筆記型別
- C語言學習記錄_2019.02.06C語言
- 《明解C語言》第三章學習筆記C語言筆記
- Solidity語言學習筆記————1、初識Solidity語言Solid筆記
- 2019考研複習:十個巧記的方法
- c語言筆記:楊輝三角C語言筆記
- Solidity語言學習筆記————36、 庫Solid筆記
- Solidity語言學習筆記————37、Using forSolid筆記
- Solidity語言學習筆記————4、常量Solid筆記
- 《JavaScript語言精粹》學習筆記一JavaScript筆記
- 《JavaScript語言精粹》學習筆記二JavaScript筆記
- 熱更新語言--lua學習筆記筆記
- C語言學習方法,怎麼學習C語言?C語言
- GO語言學習筆記之mac環境go語言配置Go筆記Mac
- 資訊學奧賽--C語言筆記(一)C語言筆記
- Go語言學習筆記(七)之方法Go筆記
- Solidity語言學習筆記————33、事件(Events)Solid筆記事件