说明:由于所有内容放在一个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函数
-
查看汇编代码:
-
查看寄存器信息