C語言學習筆記(第三部份)

說明:由于所有內容放在一個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);
    
  • 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函數

  • 查看匯編代碼:

    在這里插入圖片描述

  • 查看寄存器信息

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/bicheng/73657.shtml
繁體地址,請注明出處:http://hk.pswp.cn/bicheng/73657.shtml
英文地址,請注明出處:http://en.pswp.cn/bicheng/73657.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

Cortical Labs公司CL1人腦芯片:開啟生物智能計算新時代

Cortical Labs公司CL1人腦芯片&#xff1a;開啟生物智能計算新時代 在科技飛速發展的今天&#xff0c;人工智能已經深入到我們生活的各個角落&#xff0c;但隨著其發展&#xff0c;也面臨著能耗高、效率有限等諸多挑戰。為了突破這些瓶頸&#xff0c;科學家們開始探索將生物學…

Python學習第十八天

Django模型 定義&#xff1a;模型是 Django 中用于定義數據庫結構的 Python 類。每個模型類對應數據庫中的一張表&#xff0c;類的屬性對應表的字段。 作用&#xff1a;通過模型&#xff0c;Django 可以將 Python 代碼與數據庫表結構關聯起來&#xff0c;開發者無需直接編寫 S…

Windows 圖形顯示驅動開發-WDDM 3.0功能- 硬件翻轉隊列(一)

WDDM 3.0 之前的翻轉隊列模型 許多新式顯示控制器支持對按順序顯示的多個幀排隊的能力。 從 WDDM 2.1 開始&#xff0c;OS 支持將在下一個 VSync 中顯示的多個未完成的翻轉覆蓋請求。 顯示微型端口驅動程序 (KMD) 通過 DXGK_DRIVERCAPS 中的 MaxQueuedMultiPlaneOverlayFlipVS…

《Python深度學習》第二講:深度學習的數學基礎

本講來聊聊深度學習的數學基礎。 深度學習聽起來很厲害,其實它背后是一些很有趣的數學原理。本講會用簡單的方式解釋這些原理,還會用一些具體的例子來幫助你理解。 2.1 初識神經網絡 先從一個簡單的任務開始:識別手寫數字。 想象一下,你有一堆手寫數字的圖片,你想讓計算…

車載DoIP測試 --- CANoe DoIP中如何配置路由激活請求中的 OEM 特定場(RoutingActivationWithOEMSpecific)

我是穿拖鞋的漢子,魔都中堅持長期主義的汽車電子工程師。 老規矩,分享一段喜歡的文字,避免自己成為高知識低文化的工程師: 簡單,單純,喜歡獨處,獨來獨往,不易合同頻過著接地氣的生活,除了生存溫飽問題之外,沒有什么過多的欲望,表面看起來很高冷,內心熱情,如果你身…

JDBC數據庫連接池技術詳解——從傳統連接方式到高效連接管理

1. 引言 在開發數據庫應用時&#xff0c;我們通常需要與數據庫建立連接并執行SQL語句。傳統的JDBC連接方式雖然簡單直接&#xff0c;但在高并發場景下容易帶來性能問題&#xff0c;甚至導致系統崩潰。因此&#xff0c;引入數據庫連接池&#xff08;Connection Pool&#xff09…

【工具類】PDF文件轉圖片

PDF文件轉文件 1. 引入Maven依賴 主要使用了 pdfbox 包與 hutool 包。 pdfbox 負責 pdf 到圖片的轉換&#xff1b; hutool 負責文件讀取轉換。 <dependency><groupId>org.apache.pdfbox</groupId><artifactId>pdfbox</artifactId><version…

使用DeepSeek,優化斐波那契數函數,效果相當不錯

下面這段代碼定義了一個遞歸函數 fibonacci&#xff0c;用于計算第 n 個斐波那契數。 def fibonacci(n):if n < 1:return nelse:return fibonacci(n - 1) fibonacci(n - 2)雖然代碼邏輯正確&#xff0c;但其性能較差&#xff0c;尤其是對于較大的 n 值&#xff0c;其復雜度…

Forward Looking Radar Imaging by Truncated Singular Value Decomposition 論文閱讀

Forward Looking Radar Imaging by Truncated Singular Value Decomposition and Its Application for Adverse Weather Aircraft Landing 1. 論文的研究目標與意義1.1 研究目標1.2 實際問題與意義2. 論文的創新方法與公式解析2.1 信號建模與問題轉化2.2 截斷奇異值分解(TSVD)…

provide/inject源碼實現

在 Vue 3 中&#xff0c;provide 和 inject 是通過 Vue 的響應式系統和組件實例機制實現的&#xff0c;底層是依賴 Vue 3 中的 Proxy 和 Reactive 來實現跨層級的數據傳遞和響應式綁定。以下是一個簡化版的實現邏輯&#xff0c;幫助理解 Vue 3 中 provide 和 inject 是如何實現…

Unix時間戳BKP備份寄存器RTC實時時鐘

Unix時間戳 Unix時間戳&#xff0c;也稱為POSIX時間或Epoch時間&#xff0c;是一種在Unix和類Unix操作系統中使用的時間表示方法。它表示的是自1970年1月1日00:00:00 UTC&#xff08;協調世界時&#xff09;至當前時間經過的秒數&#xff0c;不考慮閏秒。Unix時間戳通常以秒為…

【Linux內核系列】:進程板塊與文件板塊的綜合

&#x1f525; 本文專欄&#xff1a;Linux &#x1f338;作者主頁&#xff1a;努力努力再努力wz &#x1f4aa; 今日博客勵志語錄&#xff1a; 人生中成功只是一時的&#xff0c;失敗卻是人生的主旋律&#xff0c;但是如何面對失敗卻把人分成了不同的樣子&#xff0c;有的人會被…

CellOracle|基因擾動研究基因功能|基因調控網絡+虛擬干預

在gzh“生信小鵬”同步文章 論文來源: 發表期刊:Nature發表時間:2023年2月23日論文題目:Dissecting cell identity via network inference and in silico gene perturbation研究團隊:Kenji Kamimoto 等,華盛頓大學醫學院1. 研究背景與問題提出 細胞身份(Cell Identit…

專線、云 和 物聯網(IoT)

專線、云 和 物聯網&#xff08;IoT&#xff09; 是現代信息與通信技術&#xff08;ICT&#xff09;領域的三大重要組成部分&#xff0c;它們在企業和個人的數字化轉型中扮演著關鍵角色。以下是對這三者的詳細介紹及其相互關系&#xff1a; 1. 專線&#xff08;Leased Line&…

[Lc14_priority_queue] 最后一塊石頭重量 | 數據流中的第 K 大元素 | 前K個高頻單詞 | 數據流的中位數

目錄 1.最后一塊石頭的重量 題解 2.數據流中的第 K 大元素 題解 3.前K個高頻單詞 題解 代碼 ?4.數據流的中位數 題解 在C中&#xff0c;使用標準庫中的priority_queue&#xff0c;默認情況下它是一個最大堆&#xff08;即大堆排序&#xff09;&#xff0c;這意味著最…

XSS漏洞靶場---(復現)

XSS漏洞靶場—&#xff08;復現&#xff09; 反射型 XSS 的特點是攻擊者誘導用戶點擊包含惡意腳本的 URL&#xff0c;服務器接收到請求后將惡意腳本反射回響應頁面&#xff0c;瀏覽器執行該腳本從而造成攻擊&#xff0c;惡意腳本不會在服務器端存儲。 Level 1(反射型XSS) 此漏…

2025/3.17 郭院安排會議與南京銀行參訪

目錄 *郭院會議&#xff1a;服務外包*1.會遇到的問題以及解決方案2.考慮行業目前會碰到的瓶頸3.后端應該呈現處理圖像的過程4.記得做報告、文檔說明和視頻等工作 *南京銀行&#xff08;鑫合易家&#xff09;參訪記錄*1. 風險評分業務流程筆記![在這里插入圖片描述](https://i-b…

Cloud Ace 宣布成為 Langfuse 亞太地區首個代理商,提供 LLM 全鏈路解決方案

Cloud Ace 宣布正式代理 Langfuse 產品&#xff0c;是 Langfuse 在亞太地區唯一的官方授權經銷商&#xff0c;全面負責其商用許可證的銷售、部署與技術支持服務。通過此次合作&#xff0c;Cloud Ace 將充分發揮 Langfuse 的先進技術能力與行業專業知識&#xff0c;為企業級客戶…

Helm 的倉庫管理與 Chart 搜索

在使用 Helm 管理 Kubernetes 應用的過程中&#xff0c;倉庫管理與 Chart 搜索是兩個核心功能。通過 Helm 倉庫&#xff0c;用戶可以方便地存儲、分享和獲取 Helm Chart&#xff0c;而搜索功能則幫助用戶快速找到所需的 Chart。本文將詳細介紹 Helm 倉庫的概念、管理方法以及如…

Matlab 汽車振動多自由度非線性懸掛系統和參數研究

1、內容簡介 略 Matlab 169-汽車振動多自由度非線性懸掛系統和參數研究 可以交流、咨詢、答疑 2、內容說明 略 第二章 汽車模型建立 2.1 汽車懸架系統概述 2.1.1 懸架系統的結構和功能 2.1.2 懸架分類 2.2 四分之一車輛模型 對于車輛動力學&#xff0c;一般都是研究其懸…