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); - 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){...} // rightint * 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语言学习笔记(第三部份)
说明:由于所有内容放在一个md文件中会非常卡顿,本文件将接续C_1.md文件的第三部分 整型存储和大小端 引例: int main(void) {// printf("%d\n", SnAdda(2, 5));// PrintDaffodilNum(10000);// PrintRhombus(3);int i 0;int arr[…...
C语言经典代码题
1.输入一个4位数:输出这个输的个位 十位 百位 千位 #include <stdio.h> int main(int argc, char const *argv[]) {int a;printf("输入一个4位数:");scanf("%d",&a);printf("个位:%d\n"…...
深入理解蒸馏、Function Call、React、Prompt 与 Agent
AI基础概念与实操 一、什么是蒸馏二、如何理解Function Call、React、Prompt与Agent(一)Function Call与Agent(二)Agent中的React概念(三)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页面
在实际工作场景中,需要运用到大量SQL语句更新业务逻辑,对程序员本身,写好的sql语句执行没有多大问题(图1),但是对于普通用户来说还是有操作难度的。因此我们需要构建一个HTML页面(图2࿰…...
分布式唯一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框架,其核心组件是QChartView和QChart.QChartView是一个显示图表的独立部件,基类为QGraphicsView.QChar类管理图表的序列,图例和轴示意图。 绘制一个cos和sin曲线图,效果如下 实现代码 #include…...
Transformer:GPT背后的造脑工程全解析(含手搓过程)
Transformer:GPT背后的"造脑工程"全解析(含手搓过程) Transformer 是人工智能领域的革命性架构,通过自注意力机制让模型像人类一样"全局理解"上下文关系。它摒弃传统循环结构,采用并行计算实现高…...
S32K144入门笔记(十):TRGMUX的初始化
目录 1. 概述 2. 代码配置 1. 概述 书接上回,TRGMUX本质上是一个多路选择开关,根据用户手册中的描述,它可以实现多个输入的选择输出,本篇文章将验证如何通过配置工具来生成初始化配置代码。 2. 代码配置 笔者通过配置TRGMUX实现…...
有了大模型为何还需要Agent智能体
一、什么是Agent? Agent(智能体) 是一种能感知环境、自主决策、执行动作的智能实体,当它与大语言模型(如通义千问QWen、GPT)结合时,形成一种**“增强型AI系统”**。其核心架构如下:…...
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 是一个专门用于高效地 收集、聚合、传输 大量日志数据的 分布式、可靠 的系统。它特别擅长将数据从各种数据源(如日志文件、消息队列等)传输到 HDFS、HBase、Kafka 等大数据存储系统。 特点: 可扩展࿱…...
一个简单的 **猜数字游戏** 的 C 语言例程
一个简单的 猜数字游戏 的 C 语言例程,代码包含详细注释,适合学习和练习基础语法: #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中的扩散模型时,输入prompt总是会被报错超过clip的最大长度限制。 解决方案:使用compel库 from diffusers import AutoPipelineForText2Image import torch import pdb from compel import Compeldevice torc…...
mysql-查看binlog日志
mysql目前binlog_format默认是row格式, 找到binlog日志文件,通过命令查看 >mysqlbinlog binlog日志路径内容大致如下: /*!*/; # at 1163 #250317 14:13:43 server id 1 end_log_pos 1194 CRC32 0x09c8bcfd Xid 14 COMMIT/*!*…...
【Linux系列】文件压缩
💝💝💝欢迎来到我的博客,很高兴能够在这里和您见面!希望您在这里可以感受到一份轻松愉快的氛围,不仅可以获得有趣的内容和知识,也可以畅所欲言、分享您的想法和见解。 推荐:kwan 的首页,持续学…...
微服务架构中10个常用的设计模式
在当今的微服务架构中,常见的十种设计模式,分别是服务发现模式、API网关模式、断路器模式、边车模式、负载均衡模式、Saga事务模式、CQRS模式、分片模式、分布式日志跟踪模式、熔断与降级模式 。其中,服务发现模式十分关键,通过…...
Vue3组件+leaflet,实现重叠marker的Popup切换显示
一、前言 GIS开发过程中,经常需要绘制marker,这些marker很大概率会有坐标相同导致的叠加问题,这种情况下会降低使用体验感。所以我们可以将叠加的marker的popup做一个分页效果,可以切换显示的marker。 二、技术要点 我们以leaf…...
将COCO格式的物体检测数据集划分训练集、验证集和测试集
目录 导入所需库 定义数据集路径 创建输出目录 读取JSON注释文件 随机打乱图像列表 计算划分大小 复制图像到相应文件夹 完整代码 导入所需库 我们需要以下Python库: os:处理文件路径。 json:读取和写入JSON文件。 numpyÿ…...
机器学习之距离度量方法
常见的距离度量方法及相关函数、图示如下: 1. 欧几里得距离(Euclidean Distance) 函数公式:对于两个 ( n ) 维向量 ( x = ( x 1 , x 2 , ⋯ ,...
3.1 在VisionPro脚本中添加CogGraphicLabel
本案例需要实现如下功能: 1.加载toolBlock 2.加载图片, 3.运行Block 4.VisionPro中添加脚本显示数值。 见下图:详细代码(C#以及visionPro)见下面链接: https://download.csdn.net/download/qq_340474…...
自动化APP测试APPium的元素等待
在使用Appium进行移动应用自动化测试时,有三种等待。 隐式等待driver.implicitly_wait() 显式等待(常用) time.sleep() 隐式等待(Implicit Wait) 应用场景: 当你希望对所有元素定位操作设置统一的超时…...
AI:Machine Learning Data Science
机器学习与数据科学 左侧 机器学习 Machine Learning 机器学习是一门多领域交叉学科,涉及概率论、统计学、逼近论、凸分析、算法复杂度理论等多门学科。专门研究计算机怎样模拟或实现人类的学习行为,以获取新的知识或技能,重新组织已有的知…...
软件需求分类、需求获取(高软46)
系列文章目录 软件需求分类,需求获取 文章目录 系列文章目录前言一、软件需求二、获取需求三、真题总结 前言 本节讲明软件需求分类、需求获取的相关知识。 一、软件需求 二、获取需求 三、真题 总结 就是高软笔记,大佬请略过!...
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 呢? 它是个引导程序,也就是硬件复位以后第一个要执行的程序,它主要工作就是初始化操作系统运行的环境,比如说内存、定时器、缓冲器等,当这个工作做完以后,再把操作系统的代码加载…...
【ARM中R0寄存器】
ARM中R0寄存器 1 RO介绍1.1 R0 的主要作用1 函数返回值2 函数参数3 通用寄存器4 与其他寄存器的区别 1.2 示例 1 RO介绍 在ARM架构中,R0寄存器是一个通用寄存器;是16 个通用寄存器(R0 到 R15)中的第一个,通常用于存储…...
