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

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[…...

软考 中级软件设计师 考点知识点笔记总结 day05

文章目录 4、栈和队列4.1、栈的定义4.2、队列定义 5、串、数组、矩阵和广义表5.1、串5.2、 数组5.3、稀疏矩阵5.4、广义表 4、栈和队列 4.1、栈的定义 线性表是具有相同数据类型的n个数据元素的有限序列&#xff0c; n为表厂。n0时 线性表是一个空表 L &#xff08;a1,a2,a3…...

【Linux】system V消息队列,信号量

&#x1f525;个人主页&#xff1a;Quitecoder &#x1f525;专栏&#xff1a;linux笔记仓 目录 01.消息队列System V 消息队列接口 02.信号量System V 信号量接口 03.OS对system V ipc的管理消息队列管理结构共享内存管理结构信号量管理结构 01.消息队列 消息队列提供了一个…...

【新能源汽车“心脏”赋能:三电系统研发、测试与应用匹配的恒压恒流源技术秘籍】

新能源汽车“心脏”赋能&#xff1a;三电系统研发、测试与应用匹配的恒压恒流源技术秘籍 在新能源汽车蓬勃发展的浪潮中&#xff0c;三电系统&#xff08;电池、电机、电控&#xff09;无疑是其核心驱动力。而恒压源与恒流源&#xff0c;作为电源管理的关键要素&#xff0c;在…...

在 Vue.js 中使用递归组件:轻松处理嵌套数据结构

在开发前端应用时&#xff0c;我们经常会遇到需要处理嵌套数据结构的场景&#xff0c;比如树形菜单、评论列表、文件夹结构等。Vue.js 提供了一种优雅的方式来解决这类问题——递归组件。通过递归组件&#xff0c;我们可以轻松地渲染嵌套数据&#xff0c;并保持代码的简洁和可维…...

飞腾2000+/64核加固服务器

在当今信息化高速发展的时代&#xff0c;数据中心作为信息技术的核心支撑&#xff0c;其稳定性、安全性和高效性成为了各行各业关注的焦点。特别是在国防、金融、电信等关键领域&#xff0c;对服务器的性能、可靠性和安全性提出了前所未有的高要求。正是在这样的背景下&#xf…...

AutoMQ x OSS 的 Iceberg 数据入湖的最佳实践

背景 在数字化转型进程中&#xff0c;用户交互行为产生的多维度数据已成为企业的重要战略资产。以短视频平台为例&#xff0c;基于用户点赞事件的实时推荐算法能显著提升用户活跃度和平台粘性。这类实时数据主要通过 Apache Kafka 流处理平台进行传输&#xff0c;通过其扇出&a…...

深度学习大模型补充知识点

文章目录 VIT用途处理方法与CNN区别 多模态LLM&#xff1a;大语言模型预训练指令微调强化学习 总结 VIT ViT&#xff08;Vision Transformer&#xff09; 首次将 Transformer架构成功应用于计算机视觉领域&#xff08;尤其是图像分类任务&#xff09;。传统视觉任务主要依赖卷…...

定义模型生成数据表

1. 数据库配置 js import { Sequelize, DataTypes } from sequelize; // 创建一个 Sequelize 实例&#xff0c;连接到 SQLite 数据库。 export const sequelize new Sequelize(test, sa, "123456", { host: localhost, dialect: sqlite, storage: ./blog.db })…...

C++与C的基本不同

文章目录 变量定义规则1. 基本语法2. 初始化3. 作用域4. 存储类别 函数定义规则1. 基本语法2. 函数声明和定义3. 默认参数4. 内联函数 解析输出流void BluetoothA2DPSink::start(const char* name)class BluetoothA2DPSink : public BluetoothA2DPCommon C是在C语言基础上发展而…...

React19源码系列之createRoot的执行流程是怎么的?

2024年12月5日&#xff0c;react发布了react19版本。后面一段时间都将学习它的源码&#xff0c;并着手记录。 react官网&#xff1a;react19新特性 https://react.dev/blog/2024/12/05/react-19 在用vite创建react项目的使用&#xff0c;main.tsx主文件都会有以下代码。 //i…...

【CXX-Qt】1.5 使用CMake构建

在本示例中&#xff0c;我们将演示如何使用CMake将CXX-Qt代码集成到C应用程序中。Cargo将CXX-Qt代码构建为静态库&#xff0c;然后CMake将其链接到C可执行文件中。 我们首先需要修改项目结构&#xff0c;以分离项目的不同部分。 tutorial cpp qml rust将Rust项目移动到rust文…...

前端面试项目拷打

Axios相关 1.在Axios二次封装时&#xff0c;具体封装了哪些内容&#xff0c;如何处理请求拦截和响应拦截&#xff1f; axios二次封装的目的&#xff1a;为了统一处理请求和响应拦截器、错误处理、请求超时、请求头配置等&#xff0c;提高代码可维护性和复用性。 首先创建axios…...

“Ubuntu禁止root用户通过SSH直接登录”问题的解决

目录 1 前言 2 问题的解决 2.1 修改sshd_config文件 2.2 重启 SSH 服务 1 前言 最近在做毕设的时候&#xff0c;由于使用普通用户&#xff0c;在MobaXterm的图形界面上&#xff0c;无法正常查看/root文件夹内容&#xff0c;如下图所示&#xff1a; 于是我就想直接想用oot…...

Kafka的零拷贝

Kafka的零拷贝&#xff08;Zero-Copy&#xff09;技术是其实现高吞吐量的关键优化之一&#xff0c;主要通过减少数据在内核空间和用户空间之间的冗余复制及上下文切换来提升性能。以下是其核心要点&#xff1a; 1. 传统数据拷贝的问题 多次复制&#xff1a;传统文件传输需经历…...

《大语言模型》学习笔记(三)

GPT系列模型的技术演变 2022 年11月底&#xff0c;OpenAI推出了基于大语言模型的在线对话应用—ChatGPT。由于具备出色的人机对话能力和任务解决能力&#xff0c;ChatGPT一经发布就引发了全社会对于大语言模型的广泛关注&#xff0c;众多的大语言模型应运而生&#xff0c;并且…...

华为OD机试 - 最长回文字符串 - 贪心算法(Java 2024 E卷 100分)

题目描述 如果一个字符串正读和反读都一样(大小写敏感),则称之为一个「回文串」。例如: level 是一个「回文串」,因为它的正读和反读都是 level。art 不是一个「回文串」,因为它的反读 tra 与正读不同。Level 不是一个「回文串」,因为它的反读 leveL 与正读不同(因大小…...

K8S-etcd服务无法启动问题排查

一、环境、版本信息说明 k8s&#xff1a;v1.19.16 etcdctl version: 3.5.1 3台etcd&#xff08;10.xxx.xx.129、10.xxx.xx.130、10.xxx.xx.131&#xff09;组成的集群。 二、问题根因 129节点的etcd数据与其他两台数据不一致&#xff0c;集群一致性校验出错导致无法加入集…...

基于WebRTC的嵌入式音视频通话SDK:EasyRTC跨平台兼容性技术架构实时通信的底层实现

EasyRTC的核心架构围绕WebRTC技术构建&#xff0c;同时通过扩展信令服务、媒体服务器和NAT穿透机制&#xff0c;解决了WebRTC在实际部署中的痛点。其架构可以分为以下几个核心模块&#xff1a; 1&#xff09;WebRTC基础层 媒体捕获与处理&#xff1a;通过getUserMediaAPI获取…...

SpringBoot-已添加并下载的依赖,reload和mvn clean 后还是提示找不到jar包问题

背景&#xff1a; 添加spring-jdbc依赖时&#xff0c;原来是指定版本的&#xff0c;担心版本冲突&#xff0c;就改成依赖托管&#xff0c;悲剧的是反复reload和mvn clean&#xff0c;import到类的该包一直标红&#xff0c;提示jar包找不到。。。 解决方案&#xff1a; Idea左上…...

HTML5扫雷游戏开发实战

HTML5扫雷游戏开发实战 这里写目录标题 HTML5扫雷游戏开发实战项目介绍技术栈项目架构1. 游戏界面设计2. 核心类设计 核心功能实现1. 游戏初始化2. 地雷布置算法3. 数字计算逻辑4. 扫雷功能实现 性能优化1. DOM操作优化2. 算法优化 项目亮点技术难点突破1. 首次点击保护2. 连锁…...

机器学习——数据清洗(缺失值处理、异常值处理、数据标准化)

数据清洗(缺失值处理、异常值处理、数据标准化) 在数据处理与分析流程中,数据清洗占据着极为关键的地位。原始数据往往充斥着各种问题,如缺失值、异常值,且数据的尺度和分布也可能存在差异,这些问题会严重影响后续数据分析和机器学习模型的准确性与性能。因此,有效的数据…...

【综述】An Introduction to Vision-Language Modeling【一】

介绍 发表在预印本上的综述&#xff0c;长达76页&#xff0c;其中正文46页。 来自Meta 在Meta工作期间完成 ‡蒙特利尔大学, Mila ♡麦吉尔大学, Mila †多伦多大学 ♠卡内基梅隆大学 ♣麻省理工学院 ∧纽约大学 △加州大学伯克利分校 ▽马里兰大学 ♢阿卜杜拉国王科技大学 •…...

MySQL常用函数详解及SQL代码示例

MySQL常用函数详解及SQL代码示例 引言当前日期和时间函数字符串函数数学函数聚合函数结论 引言 MySQL作为一种广泛使用的关系型数据库管理系统&#xff0c;提供了丰富的内置函数来简化数据查询、处理和转换。掌握这些函数可以大大提高数据库操作的效率和准确性。本文将详细介绍…...

Unity教程(二十二)技能系统 分身技能

Unity开发2D类银河恶魔城游戏学习笔记 Unity教程&#xff08;零&#xff09;Unity和VS的使用相关内容 Unity教程&#xff08;一&#xff09;开始学习状态机 Unity教程&#xff08;二&#xff09;角色移动的实现 Unity教程&#xff08;三&#xff09;角色跳跃的实现 Unity教程&…...

‌RTSPtoWeb, 一个将rtsp转换成webrtc的开源项目

RTSPtoWeb是一个开源项目&#xff0c;旨在将RTSP流转换为可在现代web浏览器中消费的格式&#xff0c;如Media Source Extensions &#xff08;MSE&#xff09;、WebRtc或HLS。该项目完全使用golang编写&#xff0c;不依赖于ffmpeg或gstreamer&#xff0c;确保了高效的性能和轻量…...

AIAgent有哪些不错的开源平台

AIAgent领域有许多优秀的开源平台和框架&#xff0c;以下是一些值得推荐的开源平台&#xff1a; AutoGPT AutoGPT 是一个基于 OpenAI 的 GPT-4 和 GPT-3.5 大型语言模型的开源框架&#xff0c;能够根据用户给定的目标自动生成所需提示&#xff0c;并利用多种工具 API 执行多步骤…...

Java---JavaSpringMVC解析(1)

Spring Web MVC 是基于 Servlet API 构建的原始 Web 框架&#xff0c;从⼀开始就包含在 Spring 框架中。它的正式名称“Spring Web MVC”来⾃其源模块的名称(Spring-webmvc)&#xff0c;但它通常被称为"Spring MVC" 1.MVC MVC是Model View Controller的缩写&#…...

Vector 的模拟实现:从基础到高级

文章目录 1. 引言2. vector的核心设计3. vector的常用接口介绍3.1 构造函数和析构函数3.1.1 默认构造函数3.1.2 带初始容量的构造函数3.1.3 析构函数 3.2 拷贝构造函数和拷贝赋值运算符3.2.1 拷贝构造函数3.2.2 拷贝赋值运算符 3.5 数组长度调整和动态扩容3.5.1 调整大小&#…...

【大模型科普】大模型:人工智能的前沿(一文读懂大模型)

【作者主页】Francek Chen 【专栏介绍】 ⌈ ⌈ ⌈人工智能与大模型应用 ⌋ ⌋ ⌋ 人工智能&#xff08;AI&#xff09;通过算法模拟人类智能&#xff0c;利用机器学习、深度学习等技术驱动医疗、金融等领域的智能化。大模型是千亿参数的深度神经网络&#xff08;如ChatGPT&…...

[漏洞修复]用yum update修openssh漏洞

[漏洞修复]用yum update修openssh漏洞 1. 需求2. 更新Yum仓库2.1 生成本地仓库2.2 生成内网仓库2.3 将Openssh的包更新到仓库 3. 客户端升级3.1 客户端repo文件配置3.2 升级Openssh3.3 升级后的确认 1. 需求 最近经常有朋友问Openssh 漏洞修复的问题,我也在自己的gitee仓库里更…...

[RH342]iscsi配置与排错

[RH342]iscsi配置与排错 1. 服务端配置1.1 安装targetcli1.2 准备磁盘1.3 服务端配置1.4 防火墙配置 2. 客户端配置2.1 安装客户端软件2.2 配置客户端2.3 连接登录服务端2.4 挂载使用 3. 安全验证扩展3.1 服务端3.2 客户端 4. 常见的排错点4.1 服务端常见错误4.2 客户端常见错误…...

Nginx 代理访问一个 Web 界面时缺少内容

1. 资源路径问题 Web 页面中的静态资源&#xff08;如图片、CSS、JavaScript 文件&#xff09;可能使用了相对路径或绝对路径&#xff0c;而这些路径在代理后无法正确加载。 解决方法&#xff1a; 检查资源路径&#xff1a;打开浏览器的开发者工具&#xff08;按 F12&#xf…...

HOVER:人形机器人的多功能神经网络全身控制器

编辑&#xff1a;陈萍萍的公主一点人工一点智能 HOVER&#xff1a;人形机器人的多功能神经网络全身控制器HOVER通过策略蒸馏和统一命令空间设计&#xff0c;为人形机器人提供了通用、高效的全身控制框架。https://mp.weixin.qq.com/s/R1cw47I4BOi2UfF_m-KzWg 01 介绍 1.1 摘…...

SEO新手基础优化三步法

内容概要 在网站优化的初始阶段&#xff0c;新手常因缺乏系统性认知而陷入技术细节的误区。本文以“三步法”为核心框架&#xff0c;系统梳理从关键词定位到内容布局、再到外链构建的完整优化链路。通过拆解搜索引擎工作原理&#xff0c;重点阐明基础操作中容易被忽视的底层逻…...

遨游科普:三防平板是哪三防?有哪些应用场景?

在工业智能化与数字化转型的浪潮中&#xff0c;电子设备的耐用性和环境适应性成为关键需求。普通消费级平板电脑虽然功能强大&#xff0c;但在极端环境下往往“水土不服”。而三防平板凭借其独特的防护性能&#xff0c;正逐步成为“危、急、特”场景的核心工具。 AORO P300 Ult…...

Etcd 服务搭建

&#x1f4a2;欢迎来到张胤尘的开源技术站 &#x1f4a5;开源如江河&#xff0c;汇聚众志成。代码似星辰&#xff0c;照亮行征程。开源精神长&#xff0c;传承永不忘。携手共前行&#xff0c;未来更辉煌&#x1f4a5; 文章目录 Etcd 服务搭建预编译的二进制文件安装下载 etcd 的…...

C++《红黑树》

在之前的篇章当中我们已经了解了基于二叉搜索树的AVL树&#xff0c;那么接下来在本篇当中将继续来学习另一种基于二叉搜索树的树状结构——红黑树&#xff0c;在此和之前学习AVL树类似还是通过先了解红黑树是什么以及红黑树的结构特点&#xff0c;接下来在试着实现红黑树的结构…...

Axios 请求取消:从原理到实践

Axios 请求取消&#xff1a;从原理到实践 在现代前端开发中&#xff0c;网络请求是不可或缺的一部分。Axios 是一个基于 Promise 的 HTTP 客户端&#xff0c;广泛应用于浏览器和 Node.js 环境中。然而&#xff0c;在某些场景下&#xff0c;我们可能需要取消正在进行的请求&…...

【css酷炫效果】纯CSS实现照片堆叠效果

【css酷炫效果】纯CSS实现照片堆叠效果 缘创作背景html结构css样式完整代码基础版进阶版(增加鼠标悬停查看) 效果图 想直接拿走的老板&#xff0c;链接放在这里&#xff1a;https://download.csdn.net/download/u011561335/90492022 缘 创作随缘&#xff0c;不定时更新。 创…...

论文精度:Transformers without Normalization

前言 论文题目:Transformers without Normalization 作者:Jiachen Zhu 1,2 , Xinlei Chen 1 , Kaiming He 3 , Yann LeCun 1,2 , Zhuang Liu 1,4,† 论文地址:https://arxiv.org/pdf/2503.10282 摘要 这篇论文探讨了现代神经网络中广泛使用的归一化层是否是必不可少的。…...

基于香橙派 KunpengPro学习CANN(3)——pytorch 模型迁移

通用模型迁移适配可以分为四个阶段&#xff1a;迁移分析、迁移适配、精度调试与性能调优。 迁移分析 迁移支持度分析&#xff1a; 准备NPU环境&#xff0c;获取模型的源码、权重和数据集等文件&#xff1b;使用迁移分析工具采集目标网络中的模型/算子清单&#xff0c;识别第三方…...

微软远程桌面即将下架?Splashtop:更稳、更快、更安全的 RDP 替代方案

近日&#xff0c;Windows 官方博客宣布&#xff1a;将于2025年5月27日起&#xff0c;在 Windows 10 和 Windows 11 应用商店中下架“Microsoft 远程桌面”应用&#xff0c;建议用户迁移至新的 Windows App。这一变动引发了广大用户对远程访问解决方案的关注。作为全球领先的远程…...

【Python】Python与算法有应用关系吗?

李升伟 整理 是的&#xff0c;Python与算法有着密切的应用关系。Python作为一种高级编程语言&#xff0c;因其简洁的语法和强大的库支持&#xff0c;被广泛应用于算法设计、实现和应用中。以下是Python与算法之间的一些主要应用关系&#xff1a; 1. 算法学习与教学&#xff1…...

js,html,css,vuejs手搓级联单选

<!DOCTYPE html> <html lang"zh"><head><meta charset"UTF-8" /><meta name"viewport" content"widthdevice-width, initial-scale1.0" /><title>级联选择器</title><script src"h…...

将Django连接到mysql

将Django连接到mysql 文章目录 将Django连接到mysql一.按照我的文章 在Django模型中的Mysql安装 此篇 的步骤完成mysql的基础配置二.Django配置 一.按照我的文章 ‘在Django模型中的Mysql安装’ 此篇 的步骤完成mysql的基础配置 基础配置具体内容 1.打开PowerShell 安装mysql的…...

每天五分钟深度学习框架pytorch:基于pytorch搭建循环神经网络RNN

本文重点 我们前面介绍了循环神经网络RNN,主要分析了它的维度信息,其实它的维度信息是最重要的,一旦我们把维度弄清楚了,一起就很简单了,本文我们正式的来学习一下,如何使用pytorch搭建循环神经网络RNN。 RNN的搭建 在pytorch中我们使用nn.RNN()就可以创建出RNN神经网络…...

【力扣刷题实战】无重复的最长字串

大家好&#xff0c;我是小卡皮巴拉 文章目录 目录 力扣题目&#xff1a; 无重复的最长字串 题目描述 解题思路 问题理解 算法选择 具体思路 解题要点 完整代码&#xff08;C&#xff09; 兄弟们共勉 &#xff01;&#xff01;&#xff01; 每篇前言 博客主页&#x…...

vulhub/joker 靶机----练习攻略

1. 靶机下载地址 https://download.vulnhub.com/ha/joker.zip 下载下来是ova文件&#xff0c;直接双击&#xff0c;在VMware打开&#xff0c;选择保存位置&#xff0c;点击导入。 2. 设置网卡模式为NAT&#xff0c;打开靶机 3.老规矩&#xff0c;打开kali&#xff0c;扫同C…...

Nuxt2 vue 给特定的页面 body 设置 background 不影响其他页面

首先认识一下 BODY_ATTRS 他可以在页面单独设置 head () {return {bodyAttrs: {form: form-body}};},设置完效果是只有这个页面会加上 接下来在APP.vue中添加样式...