当前位置: 首页 > article >正文

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函数

  • 查看汇编代码:

    在这里插入图片描述

  • 查看寄存器信息

相关文章:

C语言学习笔记(第三部份)

说明&#xff1a;由于所有内容放在一个md文件中会非常卡顿&#xff0c;本文件将接续C_1.md文件的第三部分 整型存储和大小端 引例&#xff1a; int main(void) {// printf("%d\n", SnAdda(2, 5));// PrintDaffodilNum(10000);// PrintRhombus(3);int i 0;int arr[…...

C语言经典代码题

1.输入一个4位数&#xff1a;输出这个输的个位 十位 百位 千位 #include <stdio.h> int main(int argc, char const *argv[]) {int a;printf("输入一个&#xff14;位数&#xff1a;");scanf("%d",&a);printf("个位&#xff1a;%d\n"…...

深入理解蒸馏、Function Call、React、Prompt 与 Agent

AI基础概念与实操 一、什么是蒸馏二、如何理解Function Call、React、Prompt与Agent&#xff08;一&#xff09;Function Call与Agent&#xff08;二&#xff09;Agent中的React概念&#xff08;三&#xff09;Prompt与Agent的关联 实操演练function callprompt 一、什么是蒸馏…...

CVPR2025自动驾驶端到端前沿论文汇总

自动驾驶 文章目录 自动驾驶前言自动驾驶的轨迹预测论文端到端自动驾驶论文 前言 汇总CVPR2025自动驾驶前沿论文 自动驾驶的轨迹预测论文 Leveraging SD Map to Augment HD Map-based Trajectory PredictionModeSeq: Taming Sparse Multimodal Motion Prediction with Seque…...

Qt6.8实现麦克风音频输入音频采集保存wav文件

一.本文目的 实现在Qt中接收麦克风数据并保存为WAV文件,使用QAudioInput来录音,并使用QFile来保存数据到WAV文件。 开发环境:QT6.8 本文用极简代码实现,核心代码只需不到100行。 二.代码实现...

记录一个SQL自动执行的html页面

在实际工作场景中&#xff0c;需要运用到大量SQL语句更新业务逻辑&#xff0c;对程序员本身&#xff0c;写好的sql语句执行没有多大问题&#xff08;图1&#xff09;&#xff0c;但是对于普通用户来说还是有操作难度的。因此我们需要构建一个HTML页面&#xff08;图2&#xff0…...

分布式唯一ID

微服务 分布式唯一主键ID生成方案_微服务主键生成-CSDN博客 uid-generator-spring-boot-starter 教程-CSDN博客 https://github.com/baidu/uid-generator/blob/master/README.zh_cn.md GitCode - 全球开发者的开源社区,开源代码托管平台...

在图像/视频中裁剪出人脸区域

1. 在图像中裁剪人脸区域 import face_alignment import skimage.io import numpy from argparse import ArgumentParser from skimage import img_as_ubyte from skimage.transform import resize from tqdm import tqdm import os import numpy as np import warnings warni…...

LuaJIT 学习(5)—— string.buffer 库

文章目录 Using the String Buffer LibraryBuffer ObjectsBuffer Method Overview Buffer Creation and Managementlocal buf buffer.new([size [,options]]) local buf buffer.new([options])buf buf:reset()buf buf:free() Buffer Writersbuf buf:put([str|num|obj] [,……...

qt介绍图表 charts 一

qt chartsj基于Q的Graphics View框架&#xff0c;其核心组件是QChartView和QChart.QChartView是一个显示图表的独立部件&#xff0c;基类为QGraphicsView.QChar类管理图表的序列&#xff0c;图例和轴示意图。 绘制一个cos和sin曲线图&#xff0c;效果如下 实现代码 #include…...

Transformer:GPT背后的造脑工程全解析(含手搓过程)

Transformer&#xff1a;GPT背后的"造脑工程"全解析&#xff08;含手搓过程&#xff09; Transformer 是人工智能领域的革命性架构&#xff0c;通过自注意力机制让模型像人类一样"全局理解"上下文关系。它摒弃传统循环结构&#xff0c;采用并行计算实现高…...

S32K144入门笔记(十):TRGMUX的初始化

目录 1. 概述 2. 代码配置 1. 概述 书接上回&#xff0c;TRGMUX本质上是一个多路选择开关&#xff0c;根据用户手册中的描述&#xff0c;它可以实现多个输入的选择输出&#xff0c;本篇文章将验证如何通过配置工具来生成初始化配置代码。 2. 代码配置 笔者通过配置TRGMUX实现…...

有了大模型为何还需要Agent智能体

一、什么是Agent&#xff1f; Agent&#xff08;智能体&#xff09; 是一种能感知环境、自主决策、执行动作的智能实体&#xff0c;当它与大语言模型&#xff08;如通义千问QWen、GPT&#xff09;结合时&#xff0c;形成一种**“增强型AI系统”**。其核心架构如下&#xff1a;…...

DNS主从服务器

1.1环境准备 作用系统IP主机名web 服务器redhat9.5192.168.33.8webDNS 主服务器redhat9.5192.168.33.18dns1DNS 从服务器redhat9.5192.168.33.28dns2客户端redhat9.5192.168.33.7client 1.2修改主机名和IP地址 web服务器 [rootweb-8 ~]# hostnamectl hostname web [rootweb-8…...

Flume详解——介绍、部署与使用

1. Flume 简介 Apache Flume 是一个专门用于高效地 收集、聚合、传输 大量日志数据的 分布式、可靠 的系统。它特别擅长将数据从各种数据源&#xff08;如日志文件、消息队列等&#xff09;传输到 HDFS、HBase、Kafka 等大数据存储系统。 特点&#xff1a; 可扩展&#xff1…...

一个简单的 **猜数字游戏** 的 C 语言例程

一个简单的 猜数字游戏 的 C 语言例程&#xff0c;代码包含详细注释&#xff0c;适合学习和练习基础语法&#xff1a; #include <stdio.h> #include <stdlib.h> #include <time.h> // 用于生成随机数种子int main() {int target, guess, attempts 0;srand…...

解决diffusers加载stablediffusion模型,输入prompt总是报错token数超出clip最大长度限制

1. StableDiffusion1.5 在加载huggingface中的扩散模型时&#xff0c;输入prompt总是会被报错超过clip的最大长度限制。 解决方案&#xff1a;使用compel库 from diffusers import AutoPipelineForText2Image import torch import pdb from compel import Compeldevice torc…...

mysql-查看binlog日志

mysql目前binlog_format默认是row格式&#xff0c; 找到binlog日志文件&#xff0c;通过命令查看 >mysqlbinlog binlog日志路径内容大致如下&#xff1a; /*!*/; # at 1163 #250317 14:13:43 server id 1 end_log_pos 1194 CRC32 0x09c8bcfd Xid 14 COMMIT/*!*…...

【Linux系列】文件压缩

&#x1f49d;&#x1f49d;&#x1f49d;欢迎来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 推荐:kwan 的首页,持续学…...

微服务架构中10个常用的设计模式

​在当今的微服务架构中&#xff0c;常见的十种设计模式&#xff0c;分别是服务发现模式、API网关模式、断路器模式、边车模式、负载均衡模式、Saga事务模式、CQRS模式、分片模式、分布式日志跟踪模式、熔断与降级模式 。其中&#xff0c;服务发现模式十分关键&#xff0c;通过…...

Vue3组件+leaflet,实现重叠marker的Popup切换显示

一、前言 GIS开发过程中&#xff0c;经常需要绘制marker&#xff0c;这些marker很大概率会有坐标相同导致的叠加问题&#xff0c;这种情况下会降低使用体验感。所以我们可以将叠加的marker的popup做一个分页效果&#xff0c;可以切换显示的marker。 二、技术要点 我们以leaf…...

将COCO格式的物体检测数据集划分训练集、验证集和测试集

目录 导入所需库 定义数据集路径 创建输出目录 读取JSON注释文件 随机打乱图像列表 计算划分大小 复制图像到相应文件夹 完整代码 导入所需库 我们需要以下Python库&#xff1a; os&#xff1a;处理文件路径。 json&#xff1a;读取和写入JSON文件。 numpy&#xff…...

机器学习之距离度量方法

常见的距离度量方法及相关函数、图示如下: 1. 欧几里得距离(Euclidean Distance) 函数公式:对于两个 ( n ) 维向量 ( x = ( x 1 , x 2 , ⋯   ,...

3.1 在VisionPro脚本中添加CogGraphicLabel

本案例需要实现如下功能&#xff1a; 1.加载toolBlock 2.加载图片&#xff0c; 3.运行Block 4.VisionPro中添加脚本显示数值。 见下图&#xff1a;详细代码&#xff08;C#以及visionPro&#xff09;见下面链接&#xff1a; https://download.csdn.net/download/qq_340474…...

自动化APP测试APPium的元素等待

在使用Appium进行移动应用自动化测试时&#xff0c;有三种等待。 隐式等待driver.implicitly_wait() 显式等待&#xff08;常用&#xff09; time.sleep() 隐式等待&#xff08;Implicit Wait&#xff09; 应用场景&#xff1a; 当你希望对所有元素定位操作设置统一的超时…...

AI:Machine Learning Data Science

机器学习与数据科学 左侧 机器学习 Machine Learning 机器学习是一门多领域交叉学科&#xff0c;涉及概率论、统计学、逼近论、凸分析、算法复杂度理论等多门学科。专门研究计算机怎样模拟或实现人类的学习行为&#xff0c;以获取新的知识或技能&#xff0c;重新组织已有的知…...

软件需求分类、需求获取(高软46)

系列文章目录 软件需求分类&#xff0c;需求获取 文章目录 系列文章目录前言一、软件需求二、获取需求三、真题总结 前言 本节讲明软件需求分类、需求获取的相关知识。 一、软件需求 二、获取需求 三、真题 总结 就是高软笔记&#xff0c;大佬请略过&#xff01;...

vue3vue-elementPlus-admin框架中form组件的upload写法

dialog中write组件代码 let ImageList reactive<UploadFile[]>([])const formSchema reactive<FormSchema[]>([{field: ImageFiles,label: 现场图片,component: Upload,colProps: { span: 24 },componentProps: {limit: 5,action: PATH_URL /upload,headers: {…...

嵌入式Linux | 什么是 BootLoader、Linux 内核(kernel)、和文件系统?

01 什么是 BootLoader 呢&#xff1f; 它是个引导程序&#xff0c;也就是硬件复位以后第一个要执行的程序&#xff0c;它主要工作就是初始化操作系统运行的环境&#xff0c;比如说内存、定时器、缓冲器等&#xff0c;当这个工作做完以后&#xff0c;再把操作系统的代码加载…...

【ARM中R0寄存器】

ARM中R0寄存器 1 RO介绍1.1 R0 的主要作用1 函数返回值2 函数参数3 通用寄存器4 与其他寄存器的区别 1.2 示例 1 RO介绍 在ARM架构中&#xff0c;R0寄存器是一个通用寄存器&#xff1b;是16 个通用寄存器&#xff08;R0 到 R15&#xff09;中的第一个&#xff0c;通常用于存储…...