c语言输出0.000000或乱码,深究

0.引例

刚刚看到一篇帖子,发现我刚学编程时也遇到过,后来问同学要了代码(额)过了就没再管……现在看到了,接触的底层东西也多了,觉得有必要深究一下。

问题链接(已经有大佬解答了):https://fishc.com.cn/thread-147817-1-1.html
这是他的代码:

#include <stdio.h>
int main(void)
{
        int a,t,c;
        double cup,p;

        printf("请输入转换数值:____杯\b\b\b\b\b\b");
        scanf("%f",&cup);
        p = cup/2;
        a = cup*8; 
        t = a*2;
        c = t*3; 
        printf("%f杯可换算为%f品脱、%d盎司、%d汤勺、%d茶勺\n",cup,p,a,t,c);

        return 0;
} 

以下是他的截图和我的结果截图,他是GCC编译器,我用的vs应该是vc++的编译器,结果有不一样,但明显都是错的
在这里插入图片描述

其实他的代码里只需要把第5行

double cup,p;

改为

float cup,p;

就行了
——原因就是:输入控制符是【%f】,申请的数据内存类型是【double】,输出的又是【%f、%d】,乱用数据类型导致内存写入、读取方式的不匹配,从而导致0或者乱码。


1.错误汇总及解决

一、格式控制符、数据类型不匹配(本篇主要讲的)
double匹配%lf,
float匹配%f,
int匹配%d,
乱码都是因为不匹配搞的鬼。

格式控制符不匹配会导致输入和读取的规则不一致

提供一种解决办法:使用强制类型转化来告诉程序使用哪一种数据类型进行操作(在本篇结尾有详细)

二、其他乱七八糟的错误:
(1)csanf的输入控制符多了个“%”百分号会导致错误。(csdn的富文本编写模式,百分号没法加粗,只能加上汉字阻隔一下……)
在这里插入图片描述

(2)csanf的输入控制符出现了“%d,%d”,(难道每次输入必须输入个“,”吗?这会导致你输入的东西自己都不知道该对应哪个)

(3)指针的类型,加没加*等问题……

(4)没有赋值、没有初始化(0或者乱码)


2.试验

下面我就深究一下(用的是vc++编译器,gcc别找我……)
先对int型来个试验
(代码我详细写了注释,新手同学可以仔细看看)

#include <stdio.h>
#include <string.h>//memcpy函数头文件需要
#include <stdlib.h>//malloc函数头文件需要

void ToBin(int n);//声明一下转换二进制的函数

int main(void)
{
	printf("请输入数字:");
	int a;				//声明a是int型变量,按照int型分配一块内存
	scanf("%d", &a);	//按照%d整型格式,写入到a所在的地址(&是取地址符)

	printf("%%d: %d\n", a);
	printf("%%f: %f\n", a);
	printf("%%lf:%lf\n", a);

	printf("%%o:%o\n", a);//8进制
	printf("%%x:%x\n", a);//16进制

	void *p=malloc(4);		//申请4字节地址
	memcpy(p,&a,4);
	printf("二进制输出:");
	ToBin( *((int *)p) );	//“(int *)”强制类型转化成int *,由于之前是void型指针,所以可以不用担心转化会有错误
	delete p;				//用完及时清理自己分配的p所指向的内存

	return 0;
}

//10进制转二进制,代码来自https://blog.csdn.net/qq_41785863/article/details/84101711
void ToBin(int n)
{
	char a[1000];
	int y = 0, x=2;
	char z = 'A';
	while (n != 0)
	{
		y++;
		a[y] = n % x;
		n = n / x;
		a[y] = a[y] + '0';
	}
	for (int i = y; i > 0; i--)
	{
		if (i % 4 == 0) printf(" ");
		printf("%c", a[i]);
	}
}

先试验一个int型最大值2^31-1=2147483647
在这里插入图片描述
↑win10自带的计算器程序员模式还是挺好用的,可惜不支持小数。
在这里插入图片描述
↑很明显,用浮点输出的两个值都是0。

在这里插入图片描述
↑在改了a的内存类型(float)和输入格式控制符(%f)后,%d、%o、%x输出却都变成了0。

在这里插入图片描述
↑在改成了“int型变量a,用%f格式输入”后,%f和%lf都仍然是0,而%d、%o、%x都乱了。

由此可见,乱码的原因与:变量类型、输入控制符、输出控制符,都有关系(仔细一想,这不是废话吗……)。


先写代码看一下这3种数字格式在内存中是什么样子的

#include <stdio.h>
#include <string.h>//memcpy函数头文件需要
#include <stdlib.h>//malloc函数头文件需要

void ToBin(int n);//声明一下转换二进制的函数
void ToBin2(long long int n);//内存更宽了,用longlong搞8字节的double,没法用4字节的int了

int main(void)
{
	int a;	float b;	double c;
	a = 64;	b = 64;	c = 64;
	printf("所占字节长度:%d,%d,%d,%d\n", sizeof(int), sizeof(float), sizeof(double), sizeof(long long int));
	printf("%d\n",a);

	void *p = malloc(4);		//申请4字节地址(int)
	memcpy(p, &a, 4);//拷贝a的数据
	printf("int二进制输出:\t\t");
	ToBin(*((int *)p));	//“(int *)”强制类型转化成int *,由于之前是void型指针,所以可以不用担心转化会有错误
	delete p;				//用完及时清理自己分配的p所指向的内存

	p = malloc(4);		//申请4字节地址(float)
	memcpy(p, &b, 4);//拷贝b的数据
	printf("float二进制输出:\t");
	ToBin(*((int *)p));	
	delete p;

	p = malloc(8);		//申请8字节地址(double)
	memcpy(p, &c, 8);//拷贝c的数据
	printf("double二进制输出:\t");
	ToBin2(*((long long int*)p));	
	delete p;

	return 0;
}


void ToBin(int n)//10进制转二进制,代码来自https://blog.csdn.net/qq_41785863/article/details/84101711
{
	char a[1000];
	int y = 0, x=2;
	char z = 'A';
	while (n != 0)
	{
		y++;
		a[y] = n % x;
		n = n / x;
		a[y] = a[y] + '0';
	}
	for (int i = y; i > 0; i--)
	{
		if (i % 4 == 0) printf(" ");
		printf("%c", a[i]);
	}
	printf("\n");
}


void ToBin2(long long int n)
{
	char a[1000];
	int y = 0, x = 2;
	char z = 'A';
	while (n != 0)
	{
		y++;
		a[y] = n % x;
		n = n / x;
		a[y] = a[y] + '0';
	}
	for (int i = y; i > 0; i--)
	{
		if (i % 4 == 0) printf(" ");
		printf("%c", a[i]);
	}
	printf("\n");
}

↑为了保全内存内的东西不受影响,我用void型指针申请相应大小的内存,再用memcpy函数拷贝进来,最后统一用int或longlongint进行二进制转化。
在这里插入图片描述
↑64得出的东西

在这里插入图片描述
↑12.5的结果
=1100.1
=1.1001*2的3次方
=0 10000010 1001 0000000000000000000 (浮点数float)
现在大致明了,为什么整型和浮点型不能互相转化(包括:格式读取、格式输出、还有一部分赋值截断可能带来的错误)——由于浮点的表示方式和整型有很大不同。
(现在明白全是1的数据用浮点表示来读取为什么是0了吧~)


3.深究

之前能“看得懂”的int、long int、long long int型存储方式是定点数存储方式,而float、double等的存储方式为浮点数存储方式。

至于 IEEE754浮点数存储标准,就是《计算机组成原理》中讲了一堆我到现在还没记清楚的东西……

IEEE 浮点标准表示: V = (-1)s * M * 2E 。
  ①、s 是符号位,为0时表示正,为1时表示负。
  ②、M为尾数,是一个二进制小数,它的范围是0至1-ε,或者1至2-ε(ε的值一般是2-k次方,其中设k > 0)
  ③、E为阶码,可正可负,作用是给尾数加权。

【12.5的IEEE 浮点标准表示】
(1)首先,十进制转二进制:
整数部分 除二余数倒写:
12: 12/2=6 余0 ;6/2=3 余0 ;3/2=1 余1 ;1/2=0 余1
倒写 也就是:1100
小数部分 乘二取整顺写:
0.5: 0.5×2=1.0
取整 也就是:1
12.5的二进制:1100.1
(2)然后将二进制转化为浮点数:
由于12.5为正数,所以符号位为0;
1100.1=1.1001×2^3 指数为3 ,
则 阶码=3+127=130 ,即:10000010
0 10000010 1001 0000000000000000000
  摘自:https://www.cnblogs.com/rosesmall/p/9473126.html
符号位:0
阶码:10000010
尾数:1001 0000000000000000000
再说明一下,尾数为什么不带“1”,因为标准就是将“有效数字”化为整数第一位是1后跟着小数的形式(只能是1因为是二进制,十进制我们可以1到9),故而省去了,只留下小鼠的部分1.1001->1001 0000000000000000000

float的存储格式:
在这里插入图片描述
↓现在再看127得出的东西,可以分清float的【符号位、阶码、尾数】了吧。
在这里插入图片描述


使用强制类型转换运算符

平时在编译器waring下我们会偷懒地用 隐式类型转换
这里介绍一下强制类型转换运算符

#include <stdio.h>
int main(void)
{
	float b;
	b = 12.5;

	printf("%d\n", b);
	printf("%d\n", (int)b);

	return 0;
}

↓见证奇迹的时刻到了!!!
在这里插入图片描述
不输出“0”了!!!!
这里可以理解为命令(告诉)程序用int类型取读取b变量!

我读《深入理解计算机系统》得到的知识(有点个人理解,不晓得是否正确恰当)——指针的类型(数据类型)实际上是由位数的多少读取方式区分的,所以数据类型的不同会导致我们不希望出现的bug。

补充:
1.强制类型转化也有c++式的(类似于实例化对象的风格)

	printf("%d\n", (int)b);	//c		式的强制类型转化
	printf("%d\n", int(b));	//c++	式的强制类型转化

2.void*型的指针由于其未指定具体的数据类型(void型),可以用强制类型转化变成任何你需要的类型,很好用的。(在本篇博客里的“二进制输出”代码就有用到)

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

超自然祈祷

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值