程序员都不知道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个字节.而整型提升有两种方式

  • 算术提升:在最前面补符号位数字,直到32位
  • 逻辑提升:无论什么都只补0,直到32位

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

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

  • 在vs编译器下答案是 4,他是在--c之后准备好了c
  • 在gcc编译器下答案是 5,他是在--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;
}

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

  • vs编译器是从左往右依次调用的,结果为-10.
  • 但是其他编译器呢??大家去试试gcc,codeblocks,Devc++,你一定会发现完全不一样.

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
不同的编译器不同的值,结果不一样,这样的代码不配叫代码.!!!

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

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

以上就是程序员都不知道C语言中的这个小细节的详细内容,更多关于C语言小细节的资料请关注我们其它相关文章!

(0)

相关推荐

  • C语言中#define与typedef的互换细节详解

    复制代码 代码如下: #include <stdio.h>/*<---------           #define    string    char *            ---->*/typedef   char *   string; int main(void){   string   a[] = {"I", "like", "to", "fight,"},   b[] = {"

  • C语言中隐藏结构体的细节

    我们都知道,在C语言中,结构体中的字段都是可以访问的.或者说,在C++ 中,类和结构体的主要区别就是类中成员变量默认为private,而结构体中默认为public.结构体的这一个特性,导致结构体中封装的数据,实际上并没有封装,外界都可以访问结构体重的字段. C++中我们尚可用类来替代结构体,但是,C语言中是没有类的,只能用结构体,但很多时候,我们需要隐藏结构体的字段,不让外界直接访问,而是通过我们写的函数进行间接访问,这样就提高了程序的封装性. 实现方法,简单来说,就是,结构体定义时,要定义在.

  • C语言结构体(struct)常见使用方法(细节问题)

    基本定义:结构体,通俗讲就像是打包封装,把一些有共同特征(比如同属于某一类事物的属性,往往是某种业务相关属性的聚合)的变量封装在内部,通过一定方法访问修改内部变量. 结构体定义: 第一种:只有结构体定义 struct stuff{ char job[20]; int age; float height; }; 第二种:附加该结构体类型的"结构体变量"的初始化的结构体定义 //直接带变量名Huqinwei struct stuff{ char job[20]; int age; floa

  • C语言中宏定义使用的小细节

    #pragma#pragma 预处理指令详解 在所有的预处理指令中,#Pragma 指令可能是最复杂的了,它的作用是设定编译器的状态或者是指示编译器完成一些特定的动作.#pragma指令对每个编译器给出了一个方法,在保持与C和 C++语言完全兼容的情况下,给出主机或操作系统专有的特征.依据定义,编译指示是机器或操作系统专有的,且对于每个编译器都是不同的. 其格式一般为: #Pragma Para.............etc.. baike.baidu.com/view/1451188.htm

  • 对C语言中sizeof细节的三点分析介绍

    1.sizeof是运算符,跟加减乘除的性质其实是一样的,在编译的时候进行执行,而不是在运行时才执行.那么如果编程中验证这一点呢?ps:这是前两天朋友淘宝面试的一道题,小编理解: 复制代码 代码如下: #include<iostream> using namespace std; int main() {     int i=1;     cout<<i<<endl;     sizeof(++i);     cout<<i<<endl;    

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

    既然题目都说了是小细节,一来就介绍细节多没意思啊,先坑坑大家再详细介绍吧,嘿嘿.直接上7个题吧,看看你能做对几个呢? 计算型细节 ①: #include <stdio.h> int main() { char a = 3; char b = 127; char c = a + b; printf("结果是:%d",c); return 0; } 您想想这个题的答案是多少?先不要看后面的答案哦 答案是 -126, 嘿嘿,是不是答错了呢?先不要着急,继续看下面的题 ②: #in

  • 每个程序员都需要学习 JavaScript 的7个理由小结

    最近在和招聘经理交流现在找一个好的程序员有多难的时候,我渐渐意识到了现在编程语言越来越倾重于JavaScript.Web开发人员尤其如此.所以,如果你是一个程序员,那么你应该去学习JavaScript. 需求 我之所以这样说的主要原因是,随着JavaScript的日渐成熟,以及Node.js方案变得越来越可行,我们对JavaScript程序员的需求正在持续增长. JavaScript在需求比例上已经超过了C#,仅屈居于Java之下.如果你看看GitHub上可行的项目,你会发现JavaScript

  • 为什么Java是程序员受欢迎的语言这几个原因你该清楚

    Java一直稳居程序员很受欢迎的编程语言的榜首,是企业中使用最广泛的编程语言.同样也是广大有志青年加入程序员行列中,想要学习的一门语言,java语言为什么有如此大的魔力呢? 任何一个从事开发的人员,你在问他:想学习编程,学习哪种语言会比较好呢?他的回答肯定是"java语言".那么为什么会产生这样的结果呢?总结大概有这么几方面的原因: 1. Java有着25年的独立开发史 Java是源自一个"Oak"语言,从1995年正式改名为Java,一直运行到今天,已经有25年的

  • 经验丰富程序员才知道的15种高级Python小技巧(收藏)

    目录 1.通过多个键值将对象进行排序 2.数据类别 3.列表推导 4.检查对象的内存使用情况 5.查找最频繁出现的值 6.属性包 7.合并字典(Python3.5+) 8.返回多个值 9.列表元素的过滤 filter()的使用 10.修改列表 11.利用zip()来组合列表 12.颠倒列表 13.检查列表中元素的存在情况 14.展平嵌套列表 15.检查唯一性 1.通过多个键值将对象进行排序 假设要对以下字典列表进行排序: people = [ { 'name': 'John', "age&quo

  • 浅析Go语言中数组的这些细节

    目录 Go语言基础二 len&cap 二维数组的遍历 数组的拷贝与传参 求数组所有元素之和 例题:数组元素匹配问题 今日总结 Go语言基础二 len&cap 书接上文,我们提到二维数组中的第二个维度的数组不能用...来表示,接下来我们要认识两个新的函数,它们分别是len和cap package main ​ func main() { a := [2]int{} println(len(a), cap(a)) } 由上方代码可知,我们在main函数里面定义了一个a数组,长度为2,未进行初始

  • 每个程序员都应该学习使用Python或Ruby

    如果你是个学生,你应该会C,C++和Java.还会一些VB,或C#/.NET.多少你还可能开发过一些Web网页,你知道一些HTML,CSS和JavaScript知识.总体上说,我们很难发现会有学生显露出掌握超出这几种语言范围外的语言的才能.这真让人遗憾,因为还有很多种编程语言,它们能让你成为一个更好的程序员. 在这篇文章里,我将会告诉你,为什么你一定要学习Python或Ruby语言. 跟C/C++/Java相比 - Python/Ruby能让你用少的多的多的代码写出相同的程序.有人计算过,Pyt

  • 让程序员都费解的10大编程语言特性

    每种语言都有自己的独到之处,或奇特的语法,或不常见的函数,或非标准的执行方式.因此,不论新丁还是老手,看着某个特性会突然醉了.文中总结了10个经常被提及的"奇异"特性. 1. Javascript: + 是一个连接符 问题描述: 在JS中,+ 号用在数字间,可以用作常规加法:但如果遇上字符,又可作为字符连接符.例如:'1'+ 1的结果是11. 成因分析: 根本性原因是JS属于弱类型语言.比方说Python,同样地使用+ 号作为字符连接符,但由于它是强类型语言,一旦发现一个字符与一个整数

  • 每个程序员需掌握的20个代码命名小贴士

    代码中到处都需要命名.作为程序员,我们得给类命名,给变量命名,给函数命名,给参数命名,给命名空间命名,等等等等.下面有20条小贴士能帮助你提高你的命名能力. 1.使用能够表达意图的名字 名字得能告诉我们它要做什么,为什么存在,以及是如何工作的.选择能够表达意图的名字,将更有利于我们理解代码. int d; // elapsed time in days int elapsedTimeInDays; int daysSinceCreation; int daysSinceModification;

  • ASP.NET 程序员都非常有用的85个工具

    介绍 这篇文章列出了针对ASP.NET开发人员的有用工具. 工具 1.Visual Studio Visual Studio Productivity Power tool:Visual Studio专业版(及以上)的扩展,具有丰富的功能,如快速查找,导航解决方案,可搜索的附加参考对话框等 ReSharper:提高.NET开发人员生产力的工具,提高代码质量,通过提供快速修复消除错误,等等 MZ-Tools:它可以在方法.文件.项目.解决方案或项目组.选定的文本,文件组合或项目组合中找到字符串.结

  • Java程序员编程性能优化必备的34个小技巧(总结)

    1.尽量在合适的场合使用单例 使用单例可以减轻加载的负担,缩短加载的时间,提高加载的效率,但并不是所有地方都适用于单例,简单来说,单例主要适用于以下三个方面: 控制资源的使用,通过线程同步来控制资源的并发访问: 控制实例的产生,以达到节约资源的目的: 控制数据共享,在不建立直接关联的条件下,让多个不相关的进程或线程之间实现通信. 2.尽量避免随意使用静态变量 要知道,当某个对象被定义为static变量所引用,那么GC通常是不会回收这个对象所占有的内存,如: 此时静态变量b的生命周期与A类同步,如

随机推荐