說明:由于所有內容放在一個md文件中會非常卡頓,本文件將接續C_1.md文件的第三部分
整型存儲和大小端
引例:
int main(void) {// printf("%d\n", SnAdda(2, 5));// PrintDaffodilNum(10000);// PrintRhombus(3);int i = 0;int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };for (i = 0; i <= 12; i++) {arr[i] = 0;printf("Nihao\n");}return 0;
}
上述代碼會死循環,因為數組越界了
數據類型介紹
C語言的基本數據類型:
char // 字符數據類型
short // 短整型
int // 整型
long // 長整型
long long // 更長的整型(C99)
float // 單精度浮點型
double // 雙精度浮點型
-
類型的意義:
- 類型決定了內存空間的大小
- 類型決定了編譯器如何看待內存空間里的數據
-
整型家族
charunsigned charsigned char shortunsigned shortsigned short int unsigned intsigned int long unsigned longsigned long long unsigned long longsigned long long
-
浮點型家族
float double
-
構造類型
> 數組類型 > 結構體類型 struct > 枚舉類型 enum > 聯合類型 union
-
指針類型
int* pi; char* pc; float* pf; void* pv;
整型在內存中的存儲
- 正數 原碼、反碼、補碼相同
- 負數 原碼最高位符號位為1 || 原碼的符號位不變,其他位置按位取反就得到反碼 || 反碼末位加1就得到補碼,反碼的符號位也不變
- 整數在內存中都是按
補碼
存放的。因為使用補碼,可以將符號位和數值位統一處理,同時,加法和減法也可以統一處理(CPU只有加法器),此外,補碼和原碼相互轉換,其運算過程是相同的,不需要額外的硬件電路。
大小端
int a = 20; // 20的補碼為:0 000 0000 0000 0000 0000 0000 0001 0100// 對應的十六進制: 0x 00 00 00 14
大端字節序存儲
:把數據的高位字節序的內容存放在內存的低地址處,把低位字節序的內容放在內存的高地址處。小端字節序存儲
:把數據的高位字節序的內容存放在內存的高地址處,把低位字節序的內容放在內存的低地址處。
記:高位放在高地址是小端(高高小)
- 在VS中都是按照小端的形式存儲的
文件操作(I/O)
流表示任意輸入的源或任意輸出的目的地,C語言中對文件的訪問是通過文件指針(即:
FILE *
)來實現的。
文件名
文件名包含:文件路徑+文件名+文件后綴
如:c:\code\test.c
文件指針
每個使用的文件都在內存中開辟了一個相應的文件信息區,用來存放文件的相關信息(如:文件的名字,文件狀態,文件當前位置等等),這些信息是保存在一個結構體變量中的,該結構體類型在系統中的聲明為FILE。
FILE* fp;
fp是指向一個FILE類型數據的指針變量,可以使fp指向某個文件的文件信息區(是一個結構體變量),通過該文件信息區中的信息就能夠訪問該文件,也就是說,通過文件指針變量可以找到與他相關聯的文件。
標準流
<stdio.h>中提供了三個標準流,可以直接使用,不用聲明,也不用打開或關閉:
標準流 | 作用 | 文件指針 |
---|---|---|
標準輸入流 | 用于接收用戶輸入(通常來自鍵盤) | stdin |
標準輸出流 | 用于向屏幕打印輸出信息(屏幕) | stdout |
標準錯誤流 | 用于輸出錯誤信息(屏幕,通常不受緩沖影響) | stderr |
任何一個C程序,在運行時都會默認打開上述三個流,流的類型都是 FILE*
如使用stdin,gets(str, sizeof(str), stdin);
默認情況下,stdin表示鍵盤,而stdout和stderr表示屏幕,但是可以通過重定向修改輸入輸出的地方,輸入重定向(<),輸出重定向(>)。
文本文件和二進制文件
- <stdio.h>支持兩種類型的文件,包括文本文件和二進制文件。
- 在文本文件中,字節表示字符,C語言的源代碼就是存儲在文本文件中的,文本文件之后會給你的內容可以被檢查或編輯。
- 文本文件分為若干行,每一行通常都以一兩個特殊的字符結尾,Windows系統中行末的表示是回車符(‘\x0d’),其后緊接一個換行符(‘\x0a’)。
- 文本文件可以包含一個特殊的文件末尾標記(一個特殊的字節),Windows系統中標記為(‘\x1a’, 即Ctrl+z),這個標記不是必須的,但是如果存在該標記,它就標記著文件的結束,其后的所有字節的都會被忽略。
- 在二進制文件中,字節不一定表示字符。二進制文件不分行,也沒有行末標記或文件末尾標記,所有字節都是平等的。
- 文件操作可以節省更多的空間,但是要把文件內容輸出到屏幕顯示,還是要選擇文本文件。
打開文件
- 對于任何需要用文件作為流的地方,都必須先用fopen打開文件。也就是說使用文件前必須先打開文件
FILE *fopen(const char *filename,const char *mode
);
參數說明:
-
返回一個文件指針,通常將該文件指針存儲在一個變量中,以便接下來進行操作。無法打開文件時返回空指針,因為不能保證總是能打開文件,因此每次打開文件都要測試fopen函數的返回值是否為空!
FILE* fp = fopen(FILE_NAME, "r"); if(fp==NULL){printf("can not open %s\n", FILE_NAME);exit(EXIT_FAILURE); }
-
filename
:含有打開文件名的字符串,該文件名可能含有文件的路徑信息,如果含\,要用兩個\對其進行轉義。 -
mode
: 文件訪問模式文件訪問模式字符串 含義 解釋 若文件已存在的動作 若文件不存在的動作 “r” 讀 打開文件以讀取 從頭讀 打開失敗 “w” 寫 打開文件以寫入(文件無需存在) 銷毀內容 創建新文件 “a” 后附 打開文件以追加(文件無需存在) 寫到結尾 創建新文件 “r+” 讀擴展 打開文件以讀/寫 從頭讀 錯誤 “w+” 寫擴展 創建文件以讀/寫 覆蓋原內容 創建新文件 “a+” 后附擴展 打開文件以讀/寫 寫到結尾 創建新文件 文件訪問模式字符串 含義 解釋 若文件已存在的動作 若文件不存在的動作 “rb” 讀 以二進制模式打開文件以讀取 從頭讀 打開失敗 “wb” 寫 以二進制模式創建文件以寫入 銷毀內容 創建新文件 “ab” 后附 以二進制模式打開文件以追加 寫到結尾 創建新文件 “r+b” 或 “rb+” 讀擴展 以二進制模式打開文件以讀/寫 從頭讀 錯誤 “w+b” 或 “wb+” 寫擴展 以二進制模式創建文件以讀/寫 覆蓋原內容 創建新文件 “a+b” 或 “ab+” 后附擴展 以二進制模式打開文件以讀/寫 寫到結尾 創建新文件 -
總結:只有含 r 的打開模式,文件必須已經存在,其他模式打開文件時,文件可以不存在。
-
如果用w內容打開文件,如果文件里有內容,在fopen打開文件時,文件里的內容就被清理掉了
-
注意,當打開文件用于讀和寫時(模式字符串中含有+),有一些特定的規則。如果沒有調用文件定位函數,就不能從讀模式轉換成寫模式,除非遇到了文件的末尾。如果即沒有調用fflush函數,也沒有調用文件定位函數,也不能從寫模式轉換成讀模式。
關閉文件
- 必須及時關閉一個不會再使用的文件
int fclose( FILE* stream );
參數說明:
stream
: 需要關閉的文件流,必須是一個文件指針,該指針只能來自于fopen或freopen的調用。- 關閉成功返回0,否則返回EOF
基本的文件操作流程
#inlcude<stdio.h>
#include<string.h>
#include<errno.h>
int main(int argc, char* argv[])
{// 打開文件FILE* fp = fopen("test.txt", "r");if (fp == NULL) {printf("%s\n", strerror(errno)); // 這句話可以將錯誤信息輸出到屏幕上,包含在<errno.h>// 或者用下邊這句話perror("fopen:"); // 同樣是打印錯誤信息,但是會在錯誤信息前添加上自己寫的字符串 fopen,這樣可以提示自己哪里出錯了return 1;}// 操作文件(讀,寫)...// 關閉文件fclose(fp);fp = NULL;return 0;
}
文件的讀寫
-
int fputc(int c, FILE *stream)
每次寫入一個字符fputc('c', fp); char i; for (i = 'a'; i <= 'z'; i++) {fputc(i, fp); }
-
int fgetc(FILE *stream);
每次讀取一個字符fgetc
返回作為 int 讀取的字符或返回 EOF 指示錯誤或文件結尾char i; i = fgetc(fp); printf("%c\n", i);while ((i = fgetc(fp)) != EOF) {printf("%c", i); }
-
int fputs( const char *str, FILE *stream );
寫一行數據fputs("你好", fp); fputs("親愛的", fp); // 文件里實際上是在一行,如果需要在兩行上,需要手動加上 \nfputs("你好\n", fp); fputs("親愛的", fp); // 文件里就是在兩行
-
char *fgets( char *str, int n, FILE *stream );
讀一行數據fgets
讀取從當前流位置的字符,并且包括第一個字符,到流的末尾,或直至讀取 字符數 - 1 與 n 相等。也就是最多讀 n-1個字符,因為會在最后添加‘\0’- n-1 的合理性:
fgets
的設計目標是確保緩沖區不會溢出。通過最多讀取n-1
個字符,它留出最后一個位置給\0
,從而保證字符串始終有效終止。- 即使輸入包含換行符
\n
,它也被視為有效字符,占用n-1
中的一席之地。 - 若用戶輸入恰好
n-1
個字符(不含\n
),fgets
會讀取全部字符,并添加\0
,此時換行符仍留在輸入流中。
- 即使輸入包含換行符
char str[20]; fgets(str, 10, fp); printf("%s\n", str);
- n-1 的合理性:
-
int fprintf( FILE *stream, const char *format [, argument ]...);
struct STU s = { "張三豐", 25, 390.2f }; fprintf(fp, "%s\t %s\t %s\n", "姓名", "年齡", "分數"); fprintf(fp, "%s\t %d\t %.2f\n", s.name, s.age, s.score);
-
int fscanf( FILE *stream, const char *format [, argument ]...);
struct STU s = { 0 }; fscanf(fp, "%s %d %f", s.name, &s.age, &s.score); printf("%s\t %d\t %.2f\n", s.name, s.age, s.score);
-
size_t fwrite( const void *buffer, size_t size, size_t count, FILE *stream );
struct STU s = { "張三豐", 56, 96.25f }; fwrite(&s, sizeof(s), 1, fp);
-
size_t fread( const void *buffer, size_t size, size_t count, FILE *stream );
struct STU s = { 0 }; fread(&s, sizeof(s), 1, fp); printf("%s %d %.2f\n", s.name, s.age, s.score);
-
int sprintf( char *buffer, const char *format [, argument] ... );
把一個格式化的數據寫到字符串中,本質上是把一個格式化的數據轉換成字符串
struct STU s = { "張三", 25, 56.65f }; char buf[100]; sprintf(buf,"%s %d %.2f", s.name, s.age, s.score); printf("%s\n", buf); // buf中存的是這樣的字符串:"張三 25 56.65"
-
int sscanf( char *buffer, const char *format [, argument] ... );
從字符串中獲取一個格式化的數據
struct STU s = { "張三", 25, 56.65f }; struct STU temp = { 0 }; char buf[100]; sprintf(buf,"%s %d %.2f", s.name, s.age, s.score); // 把s中的格式化數據轉換成字符串當道buf中 sscanf(buf,"%s %d %.2f", temp.name, &temp.age, &temp.score); // 從buf中獲取一個格式化的數據到temp中
-
比較幾個函數的差異
scanf 是針對 標準輸入流(stdin) 的格式化 輸入 語句 printf 是針對 標準輸出流(stdout) 的格式化 輸出 語句fscanf 是針對 所有輸入流 的格式化 輸入 語句 fprintf 是針對 所有輸出流 的格式化 輸出 語句sscanf 從一個字符串中轉換成一個格式化的數據 sprintf 是把一個格式化的數據轉換成字符串
從命令行獲取到文件名給程序
當執行名為FileOperation.exe的程序時,可以通過把文件名放入命令行的方式為程序提供文件名:
FileOperation EnglishArticle.txt
這樣對于程序FileOperation的main函數來說:
int main(int argc, char* argv[]){...}
argc是命令行參數的數量,而argv是只想參數字符串的指針數組。argv[0]指向程序名,從argv[1]到argv[argc-1]都指向剩余的實際參數,而argv[argc]是空指針。在上述例子中:
例如,檢查文件是否可以被打開,只需在命令行執行:
FileOperation EnglishArticle.txt
int main(int argc, char* argv[])
{FILE* fp;if (argc != 2) {printf("usage: canopen filename\n");exit(EXIT_FAILURE);}if ((fp = fopen(argv[1], "r")) == NULL) {printf("%s can't be opened\n", argv[1]);exit(EXIT_FAILURE);}printf("%s can be opened\n", argv[1]);fclose(fp);return 0;
}
文件緩沖
int fflush( FILE* stream );
void setbuf( FILE* stream, char* buffer );
int setvbuf( FILE* stream, char* buffer, int mode, size_t size );
由于對磁盤的讀寫都是比較緩慢的操作,因此不能在程序想讀想寫的時候都去訪問磁盤,可以用緩沖
來獲得一個較好的性能。
- 將寫入流的數據存儲在內存的緩沖區內,當緩沖區滿(或者流被關閉)的時候,對緩沖區進行沖洗(將緩沖區的內容寫入實際的輸出設備)
- 緩沖區包含來自輸入設備的數據,從緩沖區讀取數據而不是從輸入設備本身讀取數據。
- <stdio.h>中的函數會在緩沖有用時,自動進行緩沖操作,緩沖是在后臺自己完成的,但是有時需要我們手動的去進行緩沖的相關操作。
int fflush( FILE* stream )
:
? - 對于輸出流(及最后操作為輸出的更新流),從 stream 的緩沖區寫入未寫的數據到關聯的輸出設備。
? - 對于輸入流(及最后操作為輸入的更新流),行為未定義。
? - 若 stream 是空指針,則沖入所有輸出流,包括操作于庫包內者,或在其他情況下程序無法直接訪問者。
指針的高級應用
數組指針
數組指針是指向數組的指針,本質上還是指針,存放的是地址。eg: int (*p)[]
char * arr[5] = {0}; // 指針數組
char * (*pc)[5] = &arr; // 指向指針數組的指針
-
示例:
int main(void) {int arr[] = { 1,2,3,4,5,6,7,8,9,10 };int(*p)[10] = &arr; // p指向數組arr, *p就相當于數組名,數組名又是數組首元素的地址int i = 0;int size = sizeof(arr) / sizeof(arr[0]);for (i = 0; i < size; i++) {printf("%d\n", (*p)[i]); // 也可以寫成*(*p+i)}return 0; }
上述例子說明用在一維數組上,數組指針非常的抽象而且很難用,一般地,數組指針至少都是用在二維數組上。
-
示例:對二維數組而言,數組名表示數組首元素的地址,二維數組的首元素是它的第一行
void my_printf(int(*p)[5], int r, int c) { // 參數是指向一維數組的指針,p指向一個含五個int元素的數組int i = 0;// p就指向傳入數組的第一行,p + 1 就是第二行, p + i 就是第 i 行// 解引用,*(p + i)就拿到第 i 行的地址,也就是第 i 行首元素的地址// *(p + i) + j 就是第 i 行第 j 個元素的地址// 再解引用,*(*(P + i) + j) 就拿到第 i 行的第 j 個元素for (i = 0; i < r; i++) {int j = 0;for (j = 0; j < c; j++) {// *(p + i) 相當于 arr[i]// *(*(p + i) + j) 相當于 arr[i][j]printf("%d ", *(*(p + i) + j)); // 相當于打印arr[i][j]// 也可以寫成 printf("%d ", arr[i][j]);}printf("\n");} }int main(void) {// arr表示第一行的地址,是一個一維數組的地址int arr[3][5] = { 1,2,3,4,5,21,22,23,24,25,31,32,33,34,35 };int i = 0;int row = sizeof(arr) / sizeof(arr[0]); // 獲取行數int col = sizeof(arr[0]) / sizeof(arr[0][0]); // 獲取列數my_printf(arr, row, col); return 0; }
-
解釋 int (*p)[5]:
- p 是一個數組指針
- p 的類型是 int (*)[5]
- p 指向的是一個含有 5 個元素的整型數組
- p + 1 就是跳過一個含有 5 個 int 元素的數組,指向下一個 含有 5 個 int 元素的地址
- int arr[5]; &arr 的類型就是 int (*)[5]
-
示例:
int arr[5]; // arr是整型數組 int *p[5]; // p 是整型指針數組,有5個元素,每個元素都是一個整型指針 int (*p)[5]; // p 是數組指針,即 p 是指向一個含有 5 個 int 元素的數組的指針 int (*p[10])[5]; // p 是一個存放數組指針的數組,數組有10個元素,每個元素是 int (*)[5]的指針,
數組參數
-
一維數組傳參
int arr[10] = {1,2,3,4,5,6,7,8,9,10}; void test(int arr[]){...} // right void test(int arr[10]){...} // right void test(int* arr){...} // right
int * arr[10] = {0};void test2(int* arr[10]){...} // right void test2(int** arr){...} // right
-
二維數組傳參
int arr[3][5] = {0};void test(int arr[3][5]){...} // right void test(int arr[][]){...} // wrong, 形參的二維數組行可以省略,列不可以省略 void test(int arr[][5]){...} // rightvoid test(int* arr){...} // wrong, 二維數組的數組名是首元素的地址,也就是第一行(一維數組)的地址,一維數組的地址不能放在一級指針里 void test(int* arr[5]){...} // wrong, 這個形參是一個指針數組,需要的是能夠接收地址的指針,而不是數組 void test(int (*arr)[5]){...} // right, 這個arr是指針(數組指針),指針指向的是一個含有五個元素的數組 void test(int** arr){...} // wrong, 這個arr是二級指針,是用來存放一級指針變量的地址
指針傳參
-
一級指針傳參
int arr[10] = {0}; int *p = arr; int size = sizeof(arr)/sizeof(arr[0]); int a = 10; int* pa = &a;void test(int* p){...}test(arr); // right test(&a); // right test(pa); // right
-
二級指針傳參
void test(int** p){...}int main(void){int n = 10;int* p = &n;int** pp = &p;test(pp); // righttest(&p); // rightreturn 0; }
如果函數的形式參數是二級指針,調用函數的時候可以調用的參數類型:
int *p; // test(&p);
一級指針的地址int** p; // test(p);
二級指針變量int* arr[10]; // test(arr);
指針數組的數組名
函數指針
-
函數指針是指向函數的指針,它里邊存放的是函數的地址
-
&函數名 - 取出的就是函數的地址, 每一個函數都有自己的地址,函數也是有地址的
-
對函數來說,&函數名和函數名都是函數的地址
-
函數指針的寫法:
int Add(int x, int y){return x + y;
}
int (*p)(int, int) = &Add; // 第一個 int 是函數的返回值類型,第二三個 int 是函數的參數類型
int (*p)(int, int) = Add; // 這樣寫也可以,因為 &函數名和函數名都是函數的地址
// 以下三個等價
Add(2,3);
(*p)(2,3);
p(2,3);
```
-
用法:
int my_add(int x, int y) { return x + y; }int main(void) {// &函數名 - 取出的就是函數的地址int (*p)(int, int) = &my_add; // 解引用指針 *p 相當于是函數名 my_addint res = (*p)(2, 3); // (*p)的括號必須寫,這里的 * 就是一個擺設,可以寫很多個* (***p)也可以// int res = p(2,3); // * 也可以不寫printf("%d\n", res);return 0; }
-
函數名作為函數參數
int Add(int x, int y){return x + y; }void calc(int (*P)(int, int)){int a = 3, b = 4;int res = p(a,b); // Add(a,b)printf("%d\n", res); }int main(void){calc(Add); }
回調函數
回調函數就是一個通過函數指針調用的函數。如果把函數的指針(函數的地址)作為參數傳遞給另一個函數,當這個指針被用來調用其所指向的函數時,我們就說這是一個回調函數。回調函數不是由該函數的實現方法直接調用,而是在特定的事件或條件發生是由另外一方調用的,用于對該事件或條件進行響應。
int Add(int a, int b) {return a + b;
}int Sub(int a, int b) {return a - b;
}int Mul(int a, int b) {return a * b;
}double Div(int a, int b) {if (b == 0) return 0;return 1.0 * a / b;
}// p 是一個函數指針,Calc就是一個回調函數
void Calc(int (*p)(int, int)) {int x = 0, y = 0, res = 0;printf("請輸入兩個操作數——>");scanf("%d %d", &x, &y);res = p(x, y);printf("Answer is %d\n", res);
}int main(void) {int mode = 1;while (mode) {printf("********Menu*********\n");printf("******* 1. 加法******\n");printf("******* 2. 減法******\n");printf("******* 3. 乘法******\n");printf("******* 4. 除法******\n");printf("*********************\n");scanf("%d", &mode);switch (mode){case 1:Calc(Add);break;case 2:Calc(Sub);break;case 3:Calc(Mul);break;case 4:Calc(Div);break;case 0:return;default:printf("非法!請重新輸入:");break;}}return 0;
}
函數指針數組(轉移表)
把許多函數指針放在一個數組中,就是一個函數指針數組
-
回顧函數指針的寫法:
int (*pf)(int, int) = Add;
-
函數指針數組的寫法:
int Add(int a, int b) {return a + b; }int Sub(int a, int b) {return a - b; }int Mul(int a, int b) {return a * b; }int Div(int a, int b) {return a / b; }// pfArr就是一個函數指針的數組 int (*pfArr[4])(int, int) = { Add, Sub, Mul, Div };int i = 0;for (i = 0; i < 4; i++) {int res = pfArr[i](6, 2); // 訪問函數指針數組的元素printf("%d: %d\n", i + 1, res); }
-
函數指針數組的示例:
int Add(int a, int b) {return a + b; }int Sub(int a, int b) {return a - b; }int Mul(int a, int b) {return a * b; }int Div(int a, int b) {return a / b; }int main(void) {int x = 0, y = 0, res = 0, input = 0;// pfArr就是一個函數指針的數組int (*pfArr[5])(int, int) = {0, Add, Sub, Mul, Div }; // 也可以叫做轉移表do {printf("********Menu*********\n");printf("******* 0. 退出******\n");printf("******* 1. 加法******\n");printf("******* 2. 減法******\n");printf("******* 3. 乘法******\n");printf("******* 4. 除法******\n");printf("*********************\n");printf("請選擇-->: ");scanf("%d", &input);if (input == 0) {printf("退出!\n");return 0;}if (input >= 1 && input <= 4) {printf("請輸入兩個操作數——>");scanf("%d %d", &x, &y);res = pfArr[input](x, y);printf("Answer is %d\n", res);}else {printf("輸入錯誤!\a\n");}} while (input);return 0; }
指向函數指針數組的指針
int Add(int a, int b) {return a + b;
}int Sub(int a, int b) {return a - b;
}int Mul(int a, int b) {return a * b;
}int Div(int a, int b) {return a / b;
}int main(void) {int x = 0, y = 0, res = 0, input = 0;// pfArr就是一個函數指針的數組int (*pfArr[5])(int, int) = {0, Add, Sub, Mul, Div }; // 也可以叫做轉移表&pfArr; // 對函數指針數組取地址// PpfArr 就是指向函數指針數組的指針// 1. PpfArr 首先和 * 結合,說明他是一個指針// 2. 再往外層看,[5], 說明該指針指向的是一個含有五個元素的數組// 3. 去掉1.2步分析了的東西,剩下:int (*)(int, int) ,這是函數指針類型, 說明數組元素是函數指針int (*(*PpfArr)[5])(int, int) = &pfArr; // 相較于函數指針數組,多了一個括號和*int (*pfArr[5])(int, int); // 對比函數指針數組的寫法return 0;
}
&y);
res = pfArr[input](x, y);
printf(“Answer is %d\n”, res);
}
else {
printf(“輸入錯誤!\a\n”);
}
} while (input);
return 0;
}
```
指向函數指針數組的指針
int Add(int a, int b) {return a + b;
}int Sub(int a, int b) {return a - b;
}int Mul(int a, int b) {return a * b;
}int Div(int a, int b) {return a / b;
}int main(void) {int x = 0, y = 0, res = 0, input = 0;// pfArr就是一個函數指針的數組int (*pfArr[5])(int, int) = {0, Add, Sub, Mul, Div }; // 也可以叫做轉移表&pfArr; // 對函數指針數組取地址// PpfArr 就是指向函數指針數組的指針// 1. PpfArr 首先和 * 結合,說明他是一個指針// 2. 再往外層看,[5], 說明該指針指向的是一個含有五個元素的數組// 3. 去掉1.2步分析了的東西,剩下:int (*)(int, int) ,這是函數指針類型, 說明數組元素是函數指針int (*(*PpfArr)[5])(int, int) = &pfArr; // 相較于函數指針數組,多了一個括號和*int (*pfArr[5])(int, int); // 對比函數指針數組的寫法return 0;
}
調試
Debug和Release
- Debug稱為調試版本,是程序員自己寫代碼過程中用的版本,它包含調試信息,并且不作任何優化,便于程序員調試程序,對應的exe文件更大
- Release稱為發布版本,它往往是進行了各種優化,使得程序在代碼大小和運行速度上都是最優的,以便用戶很好地使用,對應的exe文件更小
VS中的快捷鍵
-
F5 開始調試
-
F9 創建斷點 、取消斷點
-
F10 逐過程,通常用來處理一個過程,一個過程可以是一次函數調用,或者是一條語句。
-
F11 逐語句,一步一步的走,就是每次都執行一條語句,但是這個快捷鍵可以讓我們的執行進入到函數內部
-
CTRL+F5 開始執行,不調試
-
條件斷點:
在循環內部,比如有一個循環,我知道第五次后可能會出問題,可以右擊斷點,設置條件斷點:
? 當且僅當i==5時,才會觸發這個斷點
調試過程中的操作
F10啟動調試后,點擊VS上方工具欄,調試->窗口,會有許多功能:
-
自動窗口:會自動把程序運行過程中的變量信息在窗口中顯示,但是這些局部變量會自動調整,不方便觀察
-
監視:手動輸入變量,想觀察哪個變量就輸入哪個變量
void test(int a[]){... }int main(void){int arr[10] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};test(arr);return 0; }
這樣,程序進來過后,因為是傳的數組首地址,所以只能看一個元素,為了能夠看多個元素,可以用逗號:
-
查看內存狀態:
-
查看調用堆棧(當一個函數調用另一個函數,而該函數又調用其他函數時):
void test2() {printf("nihao\n");
}
void test(int a[]) {test2();
}
int main(void) {int a[10] = { 1,2,3,4,5,6,7,8,9,10 };test(a);return 0;
}
main函數調用了test函數,test函數調用了test2函數
-
查看匯編代碼:
-
查看寄存器信息