内存地址原理解析
下面我们来了解一下内存地址原理。众所周知我们电脑有32位和64位(现在普遍64位,因为一次可以传输8个字节效率飞快)
原理图如下:
我们可以把cpu和内存看成买家和卖家,在交易中,买家需要的商品告诉给卖家,卖家才能找出商品给买家。
在电脑中就是cpu通过控制32根总线高低电频的传输某个地址给内存,内存通过地址找到数据然后通过控制数据总线的高低电频把数据给到CPU的寄存器中。
注意:
1.我们内存中的地址是用16进制表示,因此计算机读取时会转化为二进制,这样才能控制电频的高低
2.其中内存地址不会存到内存上,只有数据才会。
我们可以去软件看看内存地址和内存内容,更加形象化
我用的是visual studio2019版本,在断点调试的过程中我们可以看到内存的如下窗口。
其中左边是内存地址 右边是数据
可以看到上图中地址那一列下一个减去上一个的差值都是为4,这跟右边宽度中显示的数量(右边的一行显示的是4个)有关,这个可以拖动进行改变。
比如:现在我让右边的问号显示8对,现在内存地址下一个减去上一个的差值都是为就为8了。
scanf循环读取
复习一遍scanf的原理
scanf(读取匹配,format格式)字符串的输入,当读现到一个控制字符,它把值的置到下一个变量.
tabs、空格等等会跳过(字符不可以%c会读取空格),非空白字符和输入匹配。然后丢弃、如果一个在%号和控制符间的数量。
如果scanf (遇到一个字符集(用%[]控制字符表示),那么在括号中的任意字符都会读取到变量中。
scanf 的返回值是成功赋值的变量的数量,发生错误时返回EOF。
#include<stdio.h>
int main() {
EOF
}
我们ctrl+右键点击可以看到,EOF是一个系统定义的常量,值为-1.
下面来编写一个scanf循环输入的例子
#include<stdio.h>
int main() {
int a;
while (scanf("%d",&a) != EOF) {
printf("a=%d", a);
}
return 0;
}
可以看到,我们使用了一个while循环,然后里面的条件是scanf("%d",&a) != EOF(即输入的数字正确时就运行循环)
另外退出循环需要按ctrl+z+回车三次(必须新的一行连续按)
例如这样是错误的
但是!!!!!!!!!!!!!问题它就来了
如果我输入一个不符合条件的数字会发生什么呢?
我先输入了个123 然后又输入一个a
结果
很明显以上的程序进入死循环
那为有人就会问了:“哎呀!什么会发生这种情况呢?不符合条件的话不是返回EOF,然后不进行循环吗?”
接下来就是解密时刻:
首先,进入了死循环就证明while中scnaf返回的值不是EOF。
其次,我们上一篇博客了解了scanf的运行原理,其中阻塞的条件是标准输入缓存区为空,那么没进入阻塞状态也能说明缓存区不为空。
下面我们作图分析一下过程
因为上面说过scanf 的返回值是成功赋值的变量的数量,发生错误时返回EOF。所以我们改一下程序让加多一个ref的整型值,让其显示出返回值
#include<stdio.h>
int main() {
int a;
int ref;
while ((ref=scanf("%d",&a) )!= EOF) {
printf("a=%d\n", a);
printf("ref=%d\n", ref);
}
return 0;
}
我们第一次输入1,第二次输入a结果如下:
可以看到但输入字符时,返回的值是0不是EOF
综合scanf的运行分析和输出结果,答案就显而易见了。
因为我们下一次输入为字符,系统不会把字符值赋给整形变量a,但系统又不会把a移除缓冲区,因此不会进入阻塞状态等待用户输入,而是系统自动从缓冲区获取剩下的内容输入。
其次返回值是0不是-1(EOF常量的值),说明(ref=scanf("%d",&a) )!= EOF)这个条件成立,因此还是会进入到while循环打印结果。
因为我们一开始给a赋的值就是1,所以会不断打印a=1。从而进入死循环
总的来说进入死循环就是两个原因1.没进入阻塞状态 2.while判断返回值为真。
那么怎么解决这个问题呢?
既然刚刚的字符老是阴魂不散,那么我们每一次都清空标准输入缓存区不就解决了吗?
恰好windows里有个函数rewind可以做到完成清空的功能。上一篇博客说过标准输入时stdin,因此只需要在while上加上rewind(stdin)即可。
代码如下:
#include<stdio.h>
int main() {
int a;
int ref;
while (rewind(stdin),(ref=scanf("%d",&a) )!= EOF) {
printf("a=%d\n", a);
printf("ref=%d\n", ref);
}
return 0;
}
结果如下:
scanf循环读取字符
虽然说rewind十分好用,但是也不是处处都适用的。比如下面的例子就不适用了
需求:
需要在控制台上输入一个单词然后输出结果是其大写,比如输入hello输出结果为HELLO。
思路:
根据ACSII码的规律26个字母小写对应的数字减去大写的对应的数字的差值都是32.因此只需要让字符c-32即可获得其大写。配合if判断其是否是输入回车来判断是否单词输入完成。
程序如下:
#include<stdio.h>
int main() {
char c;
while (scanf("%c", &c) != EOF) {
if (c != '\n')
printf("%c", c - 32);
else
printf("\n");
}
return 0;
}
如果我们加入rewind(stdin)就会出现情况。
因为每次读取缓存区是rewind都会情况缓存区,因此输入hello实际上系统只读取到了h,其他都被清空了。
scanf多种数据类型混合输入
其实scanf输入中就%c即字符最令人头疼,因为当有%c时它会识别空格和\n,所以混合输入起来尤其有主意它,否则就会酿成大错。
下面举个例子,我们混合输入a(整形) b(浮点型) c(字符型)三个不同类型的变量,输入时分别用空格隔开,然后进行打印输出。
#include<stdio.h>
int main() {
int a;
float b;
char c;
int ret;
ret=scanf("%d%c%f", &a, &c, &b);
printf("a=%d b=%f c=%c \nret = % d\n", a, b, c,ret);
return 0;
}
可以看到我们输入的时 123 m 96.5,但是输出的结果中变量c和变量b的值却和我们预想的不一样,那是为什么呢?
首先我们先看一下ret即scanf的返回值为2,证明有两个变量a和c的值是读取成功的。
c的值从输出窗口中什么都没有,当监视显示它的值为32。
根据ASCII表中我们可以看出本来变量c的值应该是109(m),为什么会对应32呢?
原来ASCII中32对应的字符就是空格,那么答案c读取到了空格而b读取到了m导致读取成功的数量为2
下面我们在scanf中%c前加一个空格
#include<stdio.h>
int main() {
int a;
float b;
char c;
int ret;
ret=scanf("%d %c%f", &a, &c, &b);
printf("a=%d b=%f c=%c \nret = % d\n", a, b, c,ret);
return 0;
}
这次我们就看到输出结果完全符合我们预期了。
所以说在混合输入使用%c输入时一定要注意空格和回车问题
printf输出格式
#include<stdio.h>
int main() {
int i = 5;
float l = 96.5;
char c = 'a';
printf("i=%d,c=%c,l=%5.2f,%s\n", i, c, l,"hello world!");
return 0;
}
其中需要注意的时%f的输出这里的5.2表示一共5位(包括小数点)其中保留小数点后两位。
*%d中也有格式表示,%(-)(*)d中 表示有多少位 , -是右对齐