当前位置:主页 > 软件编程 > C代码 >

程序员都不知道C语言中的这些小细节

时间:2021-10-24 10:33:52 | 栏目:C代码 | 点击:

既然题目都说了是小细节,一来就介绍细节多没意思啊,先坑坑大家再详细介绍吧,嘿嘿.直接上7个题吧,看看你能做对几个呢?


计算型细节

①:

#include <stdio.h>
int main()
{
	char a = 3;
	char b = 127;
	char c = a + b;
	printf("结果是:%d",c);
	return 0;
}

您想想这个题的答案是多少?先不要看后面的答案哦
答案是 -126, 嘿嘿,是不是答错了呢?先不要着急,继续看下面的题

②:

#include <stdio.h>
int main()
{
	char a = 0xb6;
	short b = 0xb600;
	int c = 0xb6000000;
	if(a==0xb6)
		printf("a");
	if(b==0xb600)
		printf("b");
	if(c==0xb6000000)
		printf("c");
	return 0;
}

您想想这个题的答案是什么呢? 先不要看后面的答案哦
答案是 c,嘿嘿,是不是又回答错误,先不要着急,再看看后面的题

③:

#include <stdio.h>
int main()
{
	 char c = 1;
	 printf("%u\n", sizeof(c));
	 printf("%u\n", sizeof(+c));
	 printf("%u\n", sizeof(-c));
	 return 0;
}

您想想这个题的答案是什么呢? 先不要看后面的答案哦
答案是1 4 4,嘿嘿,是不是又回答错误,先不要着急,再看看后面的题

表达式细节

①:

#include <stdio.h>
int main()
{
	 int c = 3;
	 int ret = c + --c;
	 printf("%d",ret);  
	 return 0;
}

您想想这个题的答案是什么呢? 先不要看后面的答案哦
答案是 5 或者 4,是不确定的,嘿嘿,是不是又回答错误,先不要着急,再看看后
面的题

②:

int main()
{
	 int i = 10;
	 i = i-- - --i * ( i = -3 ) * i++ + ++i;
	 printf("i = %d\n", i);
	 return 0;
}

您想想这个题的答案是什么呢? 先不要看后面的答案哦
答案有很多个,在不同的编译器结果不同,也就是说还是不能确定结果,嘿嘿,是不是仍然回答错误了,先不要着急,再看看后面的题

③:

#include <stdio.h>
int fun()
{
     static int count = 1;
     return ++count;
}

int main()
{
     int answer;
     answer = fun() - fun() * fun();
     printf( "%d\n", answer);//输出多少?
     return 0;
}

您想想这个题的答案是什么呢? 先不要看后面的答案哦
答案是 不同编译器不同结果 ,嘿嘿,是不是又回答错误,先不要着急,再看看后面的题

④:

#include <stdio.h>
int main()
{
	 int i = 1;
	 int ret = (++i) + (++i) + (++i);
	 printf("%d\n", ret);
	 printf("%d\n", i);
	 return 0;
}

这是最后一个题了,你目前答对了几道了呢??请在评论回答试试.嘿嘿
这个题的答案是 不同编译器不同结果

大家回答对了几道题?欢迎评论

 1.现在正式讲解上面所有的题设计到的内容--------表达式求值

表达式求值的顺序一部分是由操作符的优先级和结合性决定。
同样,有些表达式的操作数在求值的过程中可能需要转换为其他类型。


1.1隐式类型转换 (整型截断与提升)

什么是隐式类型转换,整型提升,整型截断?

C的整型算术运算总是至少以满足整型类型的精度来进行的。
为了获得这个精度,表达式中若有charshort类型必须在使用之前转换称为整型,这个过程叫做 整型提升
.
一个较大数据类型存储在较小数据类型中的过程叫做整型截断,比如整型a = 500,但是a把他的值放到了字符型b中,b不能完全存放a,就会发生整型截断
.
而这个转换行为叫做 隐式类型转换

1.1.1 第一题讲解

#include <stdio.h>
int main()
{
	char a = 3;
	char b = 127;
	char c = a + b;
	printf("结果是:%d",c);
	return 0;
}

我们知道计算机中一切都是操作的补码.所以这里也是一样,(不明白原码补码反码的自己去百度下哦).
其中 正数的 原码 反码 补码 一模一样
… …负数的 补码是反码加一,反码是原码除了符号位所有位都按位取反

a只有1个字节,他的补码是00000011(二进制)
b只有1个字节,他的补码是01111111(二进制)
a加b,进行了算术操作,需要提升到4个字节.而整型提升有两种方式

大多数计算器都是进行的算术提升,这里便讲解算术提升.

a算术提升后就是00000000000000000000000000000011
b算术提升后就是00000000000000000000000001111111
a+b的结果是------00000000000000000000000010000010
存进c里面,c又只有1个字节,所以发生整型截断,只存取最后8位
10000010
此时c存取的是补码,且c是有符号型,记住了!!,打印显示时候会还原位原码的,该补码对应的数字就是 -126

下面有张字符类型原码补码反码的对应理解图
在这里插入图片描述


所以有符号的范围是 [-128,127]
无符号的范围是[0,255]

变成圆圈更好理解,自己按照有无符号对应去找就行.



在这里插入图片描述



1.1.2 第二题讲解

#include <stdio.h>
int main()
{
	char a = 0xb6;
	short b = 0xb600;
	int c = 0xb6000000;
	if(a==0xb6)
		printf("a");
	if(b==0xb600)
		printf("b");
	if(c==0xb6000000)
		printf("c");
	return 0;
}

0xb6的二进制是 00000000000000000000000010110110
存进去a中,发生截断,得到1011 0110
0xb600的二进制是00000000 00000000 10110110 00000000
存进去b中发生截断,得到10110110 00000000
0xb600000010110110 00000000 00000000 00000000
存进去c中刚刚好.

a此时存的是补码,a又与0xb6比较,参与了运算,所以又整型提升为
11111111 11111111 11111111 10110110,

整型提升后的数字与0xb6的二进制不一样,所以不等.

b此时存的是补码,b又与0xb600比较,参与了运算,所以又整型提升为
11111111 11111111 10110110 00000000

整型提升后的数字与0xb600的二进制不一样,所以不等

c此时存的是10110110 00000000 00000000 00000000,注意哦~,有人可能会问0xb6000000是正数啊,不!!!!!,字面常量只要不带符号就是默认int类型.所以此时0xb6000000的整数值是超过int的,就会认为它此时的二进制是负数的补码.所以c与0xb6000000是相等的.



1.1.3 第三题讲解

#include <stdio.h>
int main()
{
	 char c = 1;
	 printf("%u\n", sizeof(c));
	 printf("%u\n", sizeof(+c));
	 printf("%u\n", sizeof(-c));
	 return 0;
}

sizeof 测量的是类型属性的值.
c的类型是 char,只有一个字节,所以答案是1
+c,+是单目操作符,与c在一起发生了整型提升,变成了int,所以是4个字节,答案是 4
-c,同样的道理,仍然是4

1.2算术转换

上面我们说到,如果操作数大小 小于int,会发生整型提升,但是如果都是大于等于int大小的类型参与算术运算呢?? 这里就会涉及到 算术转换.
什么是算术转换呢? 比如下面的例子:

#include <stdio.h>
int main()
{
	int a = -127;
	unsigned int b =  129;
	if(a > b)
	{
		printf("a会大于b");
	}
	return 0;
}

结果会打印 a会大于b
因为这就是算术转换,即还是需要满足同类型运算,unsigned int 比int 高,所以a的值会默认是 无符号的,a就会比b大.

算术提升方向:
在这里插入图片描述


1.3 操作符属性

复杂表达式的求值有三个影响的因素。

操作符的优先级操作符的结合性是否控制求值顺序。


1.3.1 什么是优先级?

就是决定先计算什么.比如d = a + b*c. 因为*的优先级高于+,所以,先算b*c,再算+

1.3.2 什么是结合性?

就是同样优先级,就决定从哪个方向计算.比如d = a * b * c ,因为连续的*,优先级已经没有用了,所以此时就是结合性,*的结合性是从左到右.也就是说先计算a*b 然后计算*c.

1.3.3 什么是求值顺序?

就是只计算哪边.c语言的操作符具有求值顺序的只有寥寥几个,比如||, && , !
求值顺序到底什么意思呢?
比如a等于0,b等于2,c等于3,d = a && b && c,d的值最后是0,但是在运算时候只到a就完结了,因为&&是只要碰到假就是假,后面的真假已经无关,a为0,是假,所以后面不用再管.这就是求值顺序.

下面有两个关于求值顺序的小练习:

#include <stdio.h>
int main()
{
    int i = 0,a=0,b=2,c =3,d=4;
    i = a++ && ++b && d++;
    printf("a = %d\n b = %d\n c = %d\nd = %d\n", a, b, c, d);
    return 0;
}

答案是 1 2 3 4.理由: a是后置++,先使用a=0的值,一开始就遇到假了,后面不再执行.但是a还是增加了的,因为后置加加是 先使用再加加

#include <stdio.h>
int main()
{
    int i = 0,a=0,b=2,c =3,d=4;
    i = ++a || ++b || d++;
    printf("a = %d\n b = %d\n c = %d\nd = %d\n", a, b, c, d);
    return 0;
}

答案:1 2 3 4,前置++,即先加加,a成了1,因为||一遇到真就结束,不再管后面真假.所以只有a变化了.


这里有张操作符属性的表:

在这里插入图片描述
其中优先级从上往下逐渐降低

1.3.4 第四题讲解

#include <stdio.h>
int main()
{
	 int c = 3;
	 int ret = c + --c;
	 printf("%d",ret);  
	 return 0;
}

ret = c + --c中有两个操作符号,先看优先级,--的优先级高于+,所以决定了先算?Cc,但是+号左边的c是什么时候准备的呢? 我们知道,c语言是编译性语言,在代码写好以后是需要先进行编译为机器语言,然后执行的.那么在编译时候,+号左边的值是在--c之前就已经编译好了呢,还是--c之后编译好了呢?这是不确定的.

所以: 这是问题代码,我们以后不要写这样的垃圾代码.

1.3.5 第五题讲解

int main()
{
	 int i = 10;
	 i = i-- - --i * ( i = -3 ) * i++ + ++i;
	 printf("i = %d\n", i);
	 return 0;
}

这个是同样的道理,虽然知道优先级,但是结合性中的操作数什么时候准备不确定,你看看在不同的编译器操作的结果:
在这里插入图片描述
同样是个垃圾代码

1.3.6 第六题讲解

#include <stdio.h>
int fun()
{
     static int count = 1;
     return ++count;
}

int main()
{
     int answer;
     answer = fun() - fun() * fun();
     printf( "%d\n", answer);//输出多少?
     return 0;
}

()函数调用符号优先级最高,但是这里有三个,到底先调用哪个呢??这又不确定了.

1.3.7 第七题讲解

再说这个题之前,博主一定要批判某些高校,为什么呢??学校教大家++符号时候是不是最喜欢用这种类型来考大家?在这明确告诉大家,这完全是在浪费时间!!!因为这种代码根本没有意义!!!.就是垃圾代码

#include <stdio.h>
int main()
{
	 int i = 1;
	 int ret = (++i) + (++i) + (++i);
	 printf("%d\n", ret);
	 printf("%d\n", i);
	 return 0;
}

首先,()括号的优先级最高,但是有三个,到底先计算哪个?
其次,一个i的变化,会不会影响另外的?这里也不确定.和第四题我们说的那个编译之前c到底什么时候准备一样.不确定.!!!

vs编译器的值是 12 4gcc编译器的值是 10 4
不同的编译器不同的值,结果不一样,这样的代码不配叫代码.!!!

所以,以后看到这种题直接跳过,没有意义.

总结: 知道了什么是整型提升与截断知道了什么是算术转换知道了什么操作符的属性,以后不能写出这种类似的垃圾代码.

您可能感兴趣的文章:

相关文章