前言

目前学习的内容,要和函数的实现,操作系统以及Linux函数库做交叉,对于任何一种编程语言来说都是一种进阶。学习的一种目的也在于开阔眼界,看到一种方法的多种实现。

程序异常描述

编译器玄学报错
image.png
从项目属性——高级中,将SDL检查改为否,然后%f转义字符改为%f*,这样才能输出结果

image.png

但是数值98.5被拆分成两次输出
image.png

OS—-缓冲分类


image.png


关于缓冲的C标准

非交互式设备使用全缓冲,标准错误不能使用全缓冲

缓存原理

scanf通过转义字符指定匹配并且在缓冲区删除该字符。如果缓冲区仍然有数据,比如上次scanf语句存储在缓冲区的’\n’,会接着被scanf语句获取到,这时候将其赋值给字符数据类型,再进行打印仍然有值的输出;
image.png


编译器细节处理

如下图,visual stido 会自动对数据初始化,内存地址对应的十六进制数据全为c;然而GCC却不会,gcc会保留程序上次执行时分配的数据,也就是脏数据;


image.png


visual studio 编译处理1

首先对内存地址以及十六进制编码的观察,需要在调试终止的时候,否则编译器会提示调试过程中内存不可用;

所以只有采用全部中断的方式,继续输入,这时候能够在监视区获取到i的内存地址和值,但是ret不能获取到
原因在于它的获取必须要在while循环中的赋值语句结束之后,正常的调试顺序就是后面的逐过程,但是编译器直接会报错,导致程序不能边调试边执行;
image.png


visual studio编译环境的报错处理

参考与引用:
https://blog.csdn.net/Angelina__/article/details/106320368

image.png


程序执行错误的排除技巧

增量编写代码,边写代码边执行,用printf语句查看输出

Ctrl + Z中断程序运行的操作原理

相当于ASCⅡ码表中的26,当scanf语句读取到26的时候,就知道程序出现问题

调试逻辑:

1,在程序执行过程中打断点,程序会停止并且等待输入;

2,先打断点,然后执行调试,输入值之后回车确认,然后点逐过程;在调试过程中,内存窗口不能搜索变量的内存区,但是可以读取变量的内存地址;

产生的问题:

本打算用excel查找功能,先把整个内存区的所有地址复制下来,直接搜索,没想到Ctrl + A没能选中全部,只是选了一页,那么就无法定位微软对c的初始化,cc cc cc cc;

ASCⅡ码表

image.png

字符型

逐步调试,实验字符类型的输出

如何调出内存一中c对应于内存中的值呢?
断点调试第七行——监视中输入c指针(&取地址符号)——将值复制到内存选区中的地址栏


说明了什么?
c是一个字符常量,在ASCⅡ中的值为六十五,但在内存中是以十六进制存储的,所以这里的值转换为十进制是:
4 * 16 + 1 = 65
image.png


可否直接写65呢?
可行的!效果如下图,原理是:编译器根据输出语句转义字符中的%c,在内存中寻找变量c的值,找到六十五之后在ASCⅡ码表中找六十五对应的字符,然后输出。
image.png


拓展,汉字如何编码?
使用GBK编码,两个字节,在内存中依然是0101代码
image.png


程序 = 数据 + 逻辑
程序 = 数据结构 + 算法
将变量定义直接写在最前面,增强可读性,理解代码的作用和实现
VS快捷键


如何快速实现大写字母转小写字母而不改变原有变量的值呢?
直接在输出语句的转义字符中,对原有变量,字符型对应的变量直接做加法即可。65 + 32 = 97对应的就是小写字母a
image.png


\r转义字符有什么作用?
回到行首,在第十三行语句中,但打印到\r的时候,光标移动到行首,再打印d并且换行,所以输出为dbc
image.png


如何实现删除效果?
在第试试行,从\b开始退格,将光标退到c字符之前,然后用空格替换c,最后再退格到b之前,换行输出结果
image.png


斜杠加数值的转义声明有何意义?

细节: 反斜杠打印出来之后,由于没有换行。这一行的反斜杠会和下一行的字符粘在一起,所以需要人为设置 换行
123转换为八进制为83,对应的ASCⅡ是S,所以输出就是S。
image.png


0d有什么作用?
在每一个字符串结尾都有这个代码,标识字符串的结束
image.png


混合运算

如果发生精度丢失应该怎么办?
不同数据类型的变量之间传递数据,以赋值的形式,会发生精度丢失,所以需要在赋值的时候强制转换数据类型,大的数据类型和小的数据类型之间的转换由情况决定。即使前端已经限定了数值的大小,传递给后端的时候,后端同样需要处理数据大小的合法性,否则由黑客抓包,获取数据存储格式后会对程序发起攻击。

在这段代码中,将j的值传递给i的时候,由于j的数据类型是long类型,比i的类型大,所以需要向下转换,便于数据的存储和表达。
image.png


如何解决相乘造成的数值的溢出呢?

在声明数据类型的时候,直接声明变量为64位八个字节的数据类型,在输出语句的转义声明中直接使用%ll
相乘造成的数值的溢出 (2).png


字符变量的赋值

但我们给变量赋值的时候,一定要区分字符和字符串类型,仔细审查变量和数据类型是否完全一致。

如果将字符串赋值给字符数据类型,那么打印的值会随机抽取内存地址的第一位,因此打印的结果会随着运行时内存分配的不同而不同。

字符串赋值乱用.png


混合运算

在混合运算中,不同数据类型之间以赋值的方式传递值,可能会发生数据丢失,为避免此情况,可以强制转换数据类型;但数据传入的时候加判断,数据合法则强制转换数据类型;

注意:数据丢失的警告,需要使用打印语句才能够调出,赋值的过程并不需要直接使用变量;

应用:前端传过来的数据,后端仍然需要做数据检查,否则黑客可以利用抓包工具获取数据存取方式进而发起攻击;

编译原理:在编译的时候不会将值拿过去,而是对变量做数据类型检查;
image.png


image.png


浮点运算的默认大小

第一个打印的值只有7位精度,单精度浮点数f只有4字节的存储空间,能够表示的精度是6~7位,所以只保证1~7位是正确的,后面的都是近似值;

注意第五行和第七行,先赋值后计算和先计算后赋值不一样。前者会发生数据丢失,后者不会;

第二个打印的值是正确的浮点型常量,它是按8字节即double型进行运算的,同时%f会访问寄存器8字节的空间进行浮点运算,因此可以正常输出。

补充:大多数运算给四个字节存储或者表示即可,位数的增加会直接增加硬件成本;Python没有编译步骤,执行时动态扩容;
image.png


image.png


scanf语句

实现原理:对系统调用的封装,这个系统调用包含三个部分,标准输入,标准输出,标准错误。使用系统调用缓冲区读取数据

语句特性:1,按照转义字符指定类型从缓冲区读取数据;2,会自动忽略whitespace,先执行删除,然后阻塞;


image.png


scanf循环获取输入

程序执行分析

循环体结束的条件:但scanf的逻辑值为-1的时候,但是scanf只存在针对字符的0和针对非零以外的所有数字均视作1,所以这是一个无线循环语句;

ret的值也只有0/1俩种情况,但输入为数字的时候,程序会立即执行printf语句打印值,但是当输入为字符的时候,由于scanf语句无法自动删除缓冲区内的\n,也就是回车符,而且此时scanf语句的逻辑判断符合循环条件,那么该循环会一直打印上一次的输入;

解决办法

vs2017 & 2019使用rewind(stdin)清空缓存区, 其他版本使用fflush(stdin);

使用for循环控制循环输入的字符个数【未教学】

image.png


image.png


混合输入

语句控制技巧

第十八行,用一个变量接收scanf_s语句获取输入后的逻辑值,但这里必须加括号,限定运算优先级,否则这条控制语句将彻底变为赋值语句,ret的值只有1、0两种情况

image.png

执行原理:
scanf语句获取到的数据, 实际上是 数值100和空格键,所以缓冲区还会存留上次没有完成的a,由于是字符,会不断的循环输入

解决策略

在获取输入的语句中,转义字符中对应的两个值之间留一个空格,那么输入时的空格就会被填进去,也就不会影响之后的输入;

比较第二幅图与第三幅图,修改之处在于空格和对f转义字符的类型限定,由于变量d的数据类型是double,所以转义字符也是%lf。

第二幅图是老师的输出结果,由图中观察可以得知,即使输入的值,数值上是相同的,我们可以看到内存中的十六进制的数值,内存地址是一样的,但是最后打印在屏幕上的字符却不一样,就是因为数据类型:一个是float数据类型,一个是double数据类型;

image.png


%lf的理论效果.jpg


image.png


getchar()函数的使用

设计初衷

功能:从标准输入中读取并且返回下一个字符,如果达到文件尾,会返回ROF,所以这里会使用强制类型转换,所以getchar()的数据类型是整数,在ASC中没有-1这个数值;

image.png

printf()转义字符控制对齐

对比图中第一行和第三行数据,可以发现,hello语句前面存在空格,这里有五个,可以转义字符规则来控制对齐
image.png

scanf()循环获取输入—-多种数据类型

代码

  1. #include <stdio.h>
  2. int main()
  3. {
  4. int i, ret;
  5. char j;
  6. float k;
  7. while ((ret = scanf("%d %c%f", &i, &j, &k)) != EOF)
  8. {
  9. printf("i = %d, j = %c, k = %f", i, j, k);
  10. }
  11. printf("Hello World!\n");
  12. getchar();
  13. return 0;
  14. }

知识点

对于转义字符%d %f来说,它能够自动忽略空格和换行符,但是对于%c来说,一切符号都应该从缓冲区读取出来

所以在这份代码中,为了不让%c错误读取到空格,所以在获取输入语句中前置一个空格;

在代码设计中,满足ret等于EOF,只有读取到文件末尾,或者程序异常,所以这是一个不会被打破的循环语句;

double数据类型

错误代码

image.png

正确代码

image.png

知识点

double数据类型是以八个字节存储的,但是float只使用四个字节存储,当把double类型的变量,使用float型变量打印出来的时候,就会因为类型匹配的问题导致值不够存储,从而变为负数。

这告诉我们,编程一定要严谨规范,转义字符和数据类型,以及参数个数严格匹配,否则就会出现各种问题;

编程习惯

在使用主函数运行多个子函数的程序中,为每一个子函数编写注释,以便快速了解子函数的功能;

getchar()

函数定义说明

虽然getchar()设计上,函数的返回值是字符,但是由于ASCⅡ中没有负数,所以返回值为int,但是int兼容char数据类型
image.png

对齐方式

image.png

算术运算符-关系运算符-位运算符

运算符分类

(1)算术运算符(+-*/ %)。
(2)关系运算符(><==>=<=!=)。
(3)逻辑运算符(! && ||)。
(4)位运算符(<<>>~|^ &)。
(5)赋值运算符(=及其扩展赋值运算符)。
(6)条件运算符(?:)。
(7)逗号运算符()。
(8)指针运算符(*和&)。
(9)求字节数运算符(sizeof)。
(10)强制类型转换运算符((类型))。(11)分量运算符( ->)。
(12)下标运算符(O)。
(13)其他(如函数调用运算符0))。

取余运算符

取余运算符的使用

image.png

  1. #include <stdio.h>
  2. #include <stdlib.h>
  3. // 取余运算符
  4. int main(void)
  5. {
  6. int i = 12321;
  7. while (i)
  8. {
  9. printf("%d\n", i % 10); // 不保存运算结果就直接输出,必须要有换行符,否则不会xiang4对联一样显示
  10. i = i / 10; // 辗转相除
  11. }
  12. return 0;
  13. }
  14. // 20210419 20:55

将数字转换为字符

留意参照ASCⅡ码表,码值为48刚好对应数字零,也就是说这时候运算值是几,加上48后就是几;

而且由于是辗转相除,数值始终在0~9之间,不会出现大于9的数字。
image.png

  1. #include <stdio.h>
  2. #include <stdlib.h>
  3. // 取余运算符 演示将数值转化为字符
  4. int main(void)
  5. {
  6. int i = 12321;
  7. while (i)
  8. {
  9. printf("%c\n", i % 10 + 48); // 或者写成 i % 10 + '0', 因为ASCⅡ第第十八位就是0
  10. i = i / 10;
  11. }
  12. return 0;
  13. }
  14. // 20210419 20:58

关系运算符

使用&&运算符连接中间值判断,在形式上正确的东西,在计算机的逻辑中未必正确;

不要使用过多的括号,否则阅读极其困难;

错误代码

如果写成下面的形式,那么代码会出错,原因在于前一个不等于语句的运算结果为真,那么值为1,1这个值恒定小于右边的数字;

image.png

正确代码应为:

image.png

  1. /*judge_leap-year.c -尝试几种不同的运算符优先级-*/
  2. #include <stdio.h>
  3. #include <stdlib.h>
  4. int main(void)
  5. {
  6. int year;
  7. while (scanf("%d", &year)!=EOF)
  8. {
  9. if (year % 4 == 0 && year % 100 != 0 || year % 400 == 0) // && > || 逻辑与的优先级大于逻辑或
  10. {
  11. printf("This year is a leap year\n");
  12. }
  13. else
  14. {
  15. printf("This is not a leap year\n");
  16. }
  17. }
  18. return 0;
  19. }
  20. // 20210420 21:40

逻辑运算符(短路运算)

第一种

在内核和中间件中非常喜欢使用短路运算;

由于 与运算 的条件是全真为真,一假为假,所以只要输入值不正确,则会提示系统错误;

老师说设置这样的语句可以用来记录系统日志,静待观之;
image.png

  1. /*-calculation_short-circuit.c 短路运算(逻辑运算)-*/
  2. #include <stdio.h>
  3. #include <stdlib.h>
  4. // 在内核和中间价中非常喜欢使用短路运算
  5. int main(void)
  6. {
  7. int j = 0;
  8. j == 0 && printf("System is error.\n");
  9. return 0;
  10. }
  11. // 20210420 21:55

第二种

注意:一旦与运算符前面的条件已经为假之后,后面的条件判断不会执行;

这样的操作是为了不写if语句却能实现该语句的功能,减少代码的数量并且提高书写速度;
image.png

  1. /*-calculation_short-circuit.c 短路运算(逻辑运算)-*/
  2. #include <stdio.h>
  3. #include <stdlib.h>
  4. // 在内核和中间价中非常喜欢使用短路运算
  5. // 这样写代码是为了避免写if语句
  6. int main(void)
  7. {
  8. int j = 1, i = 5;
  9. j == 0 && (i = 3); // 直接不执行后面的赋值语句
  10. printf("i = %d\n", i);
  11. return 0;
  12. }
  13. // 20210420 21:55

第三种 — 逻辑非运算符

逻辑非运算符则和逻辑与运算符刚好相反,一假为真,全假为假,所以即使逻辑非运算符前一个条件语句为假,后面的语句依然会判断执行;
image.png

  1. /*-calculation_short-circuit.c 短路运算(逻辑运算)-*/
  2. #include <stdio.h>
  3. #include <stdlib.h>
  4. // 在内核和中间价中非常喜欢使用短路运算
  5. // 这样写代码是为了避免写if语句
  6. int main(void)
  7. {
  8. int j = 1, i = 5;
  9. j == 0 && printf("System is error.\n"); // 直接不执行后面的赋值语句
  10. j == 0 ||printf("System is error two.\n");
  11. return 0;
  12. }
  13. // 20210420 22:15

位运算符

位图—数据去重

文件有三种权限,可读可写可执行;权限存储用位,一个字节可以存储多种权限;

存储框架,MySQL也用位;

检验逻辑运算能力;

第一个版本 — 演示左移位运算

// 左移:高位补零,低位丢弃
// 能产生值变化的运算方式就两种,赋值运算和自增减
// 左移乘二,但不能循环移位,最后会将正数数值转换为负数数值
image.png

  1. /*-calculation_bit.c 详细解答位运算符的使用逻辑-*/
  2. #include <stdio.h>
  3. #include <stdlib.h>
  4. // 左移:高位补零,低位丢弃
  5. // 能产生值变化的运算方式就两种,赋值运算和自增减
  6. // 左移乘二,但不能循环移位,最后会将正数数值转换为负数数值
  7. int main(void)
  8. {
  9. int i = 5; // malloc (i << 30/20/10) 分别对应 1GB 1MB 1KB
  10. printf("%d\n", i << 1); // 左移一位,相当于原值乘以2,运算效率比乘号高
  11. printf("%d\n", i); // 左移之后i的值有没有变化呢?也是思考究竟是引用还是赋值?
  12. return 0;
  13. }
  14. // 20210420 22:27