3.1 鬼话C言语变量和数据类型

在《数据在内存中的存储》一节中讲到:
●核算机要处理的数据(诸如数字、文字、符号、图形、音频、视频等)是以二进制的办法寄存在内存中的;
●咱们将8个比特(Bit)称为一个字节(Byte),并将字节作为最小的可操作单元。
咱们不妨先从最简略的整数说起,看看它是怎么放到内存中去的。
变量(Variable)
现实生活中咱们会找一个小箱子来寄存物品,一来显得不那么杂乱,二来便利今后找到。核算机也是这个道理,咱们需求先在内存中找一块区域,规则用它来寄存整数,并起一个好记的姓名,便利今后查找。这块区域便是“小箱子”,咱们可以把整数放进去了。 C言语中这样在内存中找一块区域:

int又是一个新单词,它是 Integer 的简写,意思是整数。a 是咱们给这块区域起的姓名;当然也可以叫其他姓名,例如 abc、mn123 等。 这个句子的意思是:在内存中找一块区域,命名为 a,用它来寄存整数。
留意 int 和 a 之间是有空格的,它们是两个词。
也留意终究的分号,int a表达了完好的意思,是一个句子,要用分号来完毕。
不过int a;仅仅是在内存中找了一块可以保存整数的区域,那么怎么将 123、100、999 这样的数字放进去呢? C言语中这样向内存中放整数:

=是一个新符号,它在数学中叫“等于号”,例如 1+2=3,但在C言语中,这个进程叫做赋值(Assign)。赋值是指把数据放到内存的进程。 把上面的两个句子连起来:

就把 123 放到了一块叫做 a 的内存区域。你也可以写成一个句子:

a 中的整数不是一成不变的,只需咱们需求,随时可以更改。更改的办法便是再次赋值,例如:

第2次赋值,会把第一次的数据覆盖(擦除)掉,也便是说,a 中终究的值是9999,123、1000 现已不存在了,再也找不回来了。 由于 a 的值可以改变,所以咱们给它起了一个形象的姓名,叫做变量(Variable)。 int a;创造了一个变量 a,咱们把这个进程叫做变量界说。
a=123;把 123 交给了变量 a,咱们把这个进程叫做给变量赋值;
又由于是第一次赋值,也称变量的初始化,或许赋初值。 你可以先界说变量,再初始化,例如:

也可以在界说的一起进行初始化,例如:

这两种办法是等价的。
数据类型(Data Type)
数据是放在内存中的,变量是给这块内存起的姓名,有了变量就可以找到并运用这份数据。但问题是,该怎么运用呢? 咱们知道,诸如数字、文字、符号、图形、音频、视频等数据都是以二进制办法存储在内存中的,它们并没有本质上的区别,那么,00010000 该了解为数字16呢,仍是图像中某个像素的颜色呢,仍是要发出某个声响呢?假如没有特别指明,咱们并不知道。 也便是说,内存中的数据有多种解说办法,运用之前有必要要确认;上面的int a;就标明,这份数据是整数,不能了解为像素、声响等。int 有一个专业的称呼,叫做数据类型(Data Type)。 望文生义,数据类型用来阐明数据的类型,确认了数据的解说办法,让核算机和程序员不会产生歧义。在C言语中,有多种数据类型,例如:

说 明 字符型 短整型 整型 长整型 单精度浮点型 双精度浮点型 无类型
数据类型 char short int long float double void


这些是最根本的数据类型,是C言语自带的,假如咱们需求,还可以经过它们组成愈加杂乱的数据类型,后边咱们会一一解说。 接连界说多个变量 为了让程序的书写愈加简洁,C言语支撑多个变量的接连界说,例如:

接连界说的多个变量以逗号,分隔,而且要具有相同的数据类型;变量可以初始化,也可以不初始化。
数据的长度(Length)
所谓数据长度(Length),是指数据占用多少个字节。占用的字节越多,能存储的数据就越多,关于数字来说,值就会更大,反之能存储的数据就有限。 多个数据在内存中是接连存储的,彼此之间没有显着的界限,假如不清晰指明数据的长度,核算机就不知道何时存取完毕。例如咱们保存了一个整数 1000,它占用4个字节的内存,而读取时却以为它占用3个字节或5个字节,这显然是不正确的。 所以,在界说变量时还要指明数据的长度。而这恰恰是数据类型的别的一个效果。数据类型除了指明数据的解说办法,还指明晰数据的长度。由于在C言语中,每一种数据类型所占用的字节数都是固定的,知道了数据类型,也就知道了数据的长度。 在32位环境中,各种数据类型的长度一般如下:

说 明 字符型 短整型 整型 长整型 单精度浮点型 双精度浮点型
数据类型 char short int long float double
长 度 1 2 4 4 4 8


C言语有多少种数据类型,每种数据类型长度是多少、该怎么运用,这是每一位C程序员都有必要要把握的,后续咱们会一一解说。

终究的总结

数据是放在内存中的,在内存中存取数据要清晰三件工作:数据存储在哪里、数据的长度以及数据的处理办法。 变量名不只仅是为数据起了一个好记的姓名,还告诉咱们数据存储在哪里,运用数据时,只需供给变量名即可;而数据类型则指明晰数据的长度和处理办法。所以诸如int n;、char c;、float money;这样的办法就确认了数据在内存中的一切要素。 C言语供给的多种数据类型让程序愈加灵活和高效,一起也增加了学习本钱。而有些编程言语,例如PHP、JavaScript等,在界说变量时不需求指明数据类型,编译器会依据赋值状况主动推演出数据类型,愈加智能。 除了C言语,Java、C++、C#,等在界说变量时也有必要指明数据类型,这样的编程言语称为强类型言语。
而PHP、JavaScript等在界说变量时不必指明数据类型,编译体系会主动推演,这样的编程言语称为弱类型言语。 强类型言语一旦确认了数据类型,就不能再赋给其他类型的数据,除非对数据类型进行转化。弱类型言语没有这种约束,一个变量,可以先赋给一个整数,然后再赋给一个字符串。 终究需求阐明的是:数据类型只在界说变量时指明,而且有必要指明;运用变量时无需再指明,由于此刻的数据类型现已确认了。

3.2 在屏幕上输出各种类型的数据
在《第一个C言语程序》一节中,咱们运用 puts 来输出字符串。puts 是 output string 的缩写,只能用来输出字符串,不能输出整数、小数、字符等,咱们需求用别的一个函数,那便是 printf。 printf 比 puts 愈加强大,不只可以输出字符串,还可以输出整数、小数、单个字符等,而且输出格局也可以自己界说,例如:
●以十进制、八进制、十六进制办法输出;
●要求输出的数字占 n 个字符的方位;
●操控小数的位数。

printf 是 print format 的缩写,意思是“格局化打印”。这儿所谓的“打印”便是在屏幕上显现内容,与“输出”的意义相同,所以咱们一般称 printf 是用来格局化输出的。 先来看一个简略的比如:

这个句子可以在屏幕上显现“C言语中文网”,与puts(“C言语中文网”);的效果相似。 输出变量 abc 的值:

这儿就比较有趣了。先来看%d,d 是 decimal 的缩写,意思是十进制数,%d 标明以十进制整数的办法输出。输出什么呢?输出变量 abc 的值。%d 与 abc 是对应的,也便是说,会用 abc 的值来替换 %d。 再来看个杂乱点的:

会在屏幕上显现: The value of abc is 999 ! 你看,字符串 “The value of abc is %d !” 中的 %d 被替换成了 abc 的值,其他字符没有改变。这阐明 %d 比较特别,不会原样输出,会被替换成对应的变量的值。 再来看:

会在屏幕上显现: a=100, b=200, c=300 再次证明晰 %d 与后边的变量是一一对应的,第一个 %d 对应第一个变量,第二个 %d 对应第二个变量…… %d称为格局操控符,它指明晰以何种办法输出数据。格局操控符均以%最初,后跟其他字符。%d 标明以十进制办法输出一个整数。除了 %d,printf 支撑更多的格局操控,例如:

●%c:输出一个字符。c 是 character 的简写。
●%s:输出一个字符串。s 是 string 的简写。
●%f:输出一个小数。f 是 float 的简写。
除了这些,printf 支撑愈加杂乱和美丽的输出格局,考虑到读者的根底暂时不行。 咱们把代码补充完好,体验一下:

输出成果: n=100, c=@, money=93.959999 要点提示: 1) \n是一个整体,组合在一起标明一个换行字符。换行符是 ASCII 编码中的一个操控字符,无法在键盘上直接输入,只能用这种特别的办法标明,被称为转义字符,请咱们暂时先记住\n的意义。
所谓换行,便是让文本从下一行的最初输出,相当于在编辑 Word 或许 TXT 文档时按下回车键。

puts 输出完结后会主动换行,而 printf 不会,要自己添加换行符,这是 puts 和 printf 在输出字符串时的一个区别。

  1. //后边的为注释。注释用来阐明代码是什么意思,起到提示的效果,可以协助咱们了解代码。注释尽管也是代码的一部分,可是它并不会给程序带来任何影响,编译器在编译阶段会忽略注释的内容,或许说删除注释的内容。

  2. money 的输出值并不是 93.96,而是一个十分接近的值,这与小数本身的存储机制有关,这种机制导致许多小数不能被准确地标明,即便像 93.96 这种简略的小数也不行。咱们将在《小数在内存中是怎么存储的,揭秘诺贝尔奖等级的规划(长篇神文)》一节具体介绍。 咱们也可以不必变量,将数据直接输出:

输出成果与上面相同。 在今后的编程中,咱们会经常运用 printf,说它是C言语中运用频率最高的一个函数一点也不为过,每个C言语程序员都应该把握 printf 的用法,这是最根本的技术。 不过 printf 的用法比较灵活,也比较杂乱,初学者知识储备不足,不能一会儿把握,现在咱们只需求把握最根本的用法,今后随着编程知识的学习,咱们会逐渐介绍愈加高档的用法,终究让咱们彻底把握 printf。
【脑筋急转弯】%ds输出什么
%d 输出整数,%s 输出字符串,那么 %ds 输出什么呢? 咱们不妨先来看一个比如:

运转成果: a=1234s

从输出成果可以发现,%d被替换成了变量 a 的值,而s没有变,原样输出了。这是由于, %d才是格局操控符,%ds在一起没有意义,s仅仅是跟在%d后边的一个一般字符,所以会原样输出。

【拓宽】怎么在字符串中书写长文本
假设现在咱们要输出一段比较长的文本,它的内容为:
C言语中文网,一个学习C言语和C++的网站,他们坚持用工匠的精神来打磨每一套教程。坚持做好一件工作,做到极致,让自己感动,让用户心动,这便是足以传世的作品!C言语中文网的网址是:c.biancheng.net
假如将这段文本放在一个字符串中,会显得比较臃肿,格局也不好看,就像下面这样:

C语言内功心法篇—变量与数据类型

超出编辑窗口宽度的文本换行

C语言内功心法篇—变量与数据类型

超出编辑窗口宽度的文本躲藏
当文本超出编辑窗口的宽度时,可以挑选将文本换行,也可以挑选将文本躲藏(可以在编辑器里面自行设置),可是不论哪种办法,在一个字符串里书写长文本总是不太漂亮。 当然,你可以多写几个 puts 函数,就像下面这样:

C语言内功心法篇—变量与数据类型

我不否认这种写法也比较漂亮,可是这儿我要讲的是别的一种写法:

在 puts 函数中,可以将一个较长的字符串切割成几个较短的字符串,这样会使得长文本的格局愈加整齐。 留意,这仅仅办法上的切割,编译器在编译阶段会将它们合并为一个字符串,它们放在一块接连的内存中。 多个字符串并不一定非得换行,也可以将它们写在一行中,例如:

本节讲到的 puts、printf,以及后边要讲到的 fprintf、fputs 等与字符串输出有关的函数,都支撑这种写法。
3.3 C言语中的整数(short,int,long)
整数是编程中常用的一种数据,C言语通常运用int来界说整数(int 是 integer 的简写),这在《鬼话C言语变量和数据类型》中现已进行了具体解说。 在现代操作体系中,int 一般占用 4 个字节(Byte)的内存,共计 32 位(Bit)。假如不考虑正负数,当一切的位都为 1 时它的值最大,为 232-1 = 4,294,967,295 ≈ 43亿,这是一个很大的数,实践开发中很少用到,而诸如 1、99、12098 等较小的数运用频率反而较高。 运用 4 个字节保存较小的整数捉襟见肘,会空闲出两三个字节来,这些字节就白白浪费掉了,不能再被其他数据运用。现在个人电脑的内存都比较大了,装备低的也有 4G,浪费一些内存不会带来显着的丢失;而在C言语被发明的早期,或许在单片机和嵌入式体系中,内存都是十分稀缺的资源,一切的程序都在尽力节约内存。 反过来说,43 亿尽管现已很大,但要标明全球人口数量仍是不行,有必要要让整数占用更多的内存,才干标明更大的值,比如占用 6 个字节或许 8 个字节。 让整数占用更少的内存可以在 int 前边加 short,让整数占用更多的内存可以在 int 前边加 long,例如:

这样 a、b、c 只占用 2 个字节的内存,而 m、n、p 或许会占用 8 个字节的内存。 也可以将 int 省略,只写 short 和 long,如下所示:

这样的写法愈加简洁,实践开发中常用。 int 是根本的整数类型,short 和 long 是在 int 的根底上进行的扩展,short 可以节约内存,long 可以包容更大的值。 short、int、long 是C言语中常见的整数类型,其间 int 称为整型,short 称为短整型,long 称为长整型。
整型的长度

仔细的读者或许会发现,上面咱们在描绘 short、int、long 类型的长度时,只对 short 运用肯定的说法,而对 int、long 运用了“一般”或许“或许”等不确认的说法。这种描绘的弦外之音是,只要 short 的长度是确认的,是两个字节,而 int 和 long 的长度无法确认,在不同的环境下有不同的表现。

一种数据类型占用的字节数,称为该数据类型的长度。例如,short 占用 2 个字节的内存,那么它的长度便是 2。

实践状况也确实如此,C言语并没有严厉规则 short、int、long 的长度,只做了广泛的约束:
●short 至少占用 2 个字节。

●int 主张为一个机器字长。32 位环境下机器字长为 4 字节,64 位环境下机器字长为 8 字节。
●short 的长度不能大于 int,long 的长度不能小于 int。
总结起来,它们的长度(所占字节数)联系为:

这就意味着,short 并不一定真的”短“,long 也并不一定真的”长“,它们有或许和 int 占用相同的字节数。 在 16 位环境下,short 的长度为 2 个字节,int 也为 2 个字节,long 为 4 个字节。16 位环境多用于单片机和初级嵌入式体系,在PC和服务器上现已见不到了。 关于 32 位的 Windows、Linux 和 Mac OS,short 的长度为 2 个字节,int 为 4 个字节,long 也为 4 个字节。PC和服务器上的 32 位体系占有率也在渐渐下降,嵌入式体系运用 32 位越来越多。 在 64 位环境下,不同的操作体系会有不同的成果,如下所示:

操作体系 short int long
Win64(64位 Windows) 2 4 4
类Unix体系(包含 Unix、Linux、Mac OS、BSD、Solaris 等) 2 4 8

现在咱们运用较多的PC体系为 Win XP、Win 7、Win 8、Win 10、Mac OS、Linux,在这些体系中,short 和 int 的长度都是固定的,别离为 2 和 4,咱们可以放心运用,只要 long 的长度在 Win64 和类 Unix 体系下会有所不同,运用时要留意移植性。
sizeof 操作符
获取某个数据类型的长度可以运用 sizeof 操作符,如下所示:

在 32 位环境以及 Win64 环境下的运转成果为:

short=2, int=4, long=4, char=1

在 64 位 Linux 和 Mac OS 下的运转成果为:

short=2, int=4, long=8, char=1

sizeof 用来获取某个数据类型或变量所占用的字节数,假如后边跟的是变量称号,那么可以省略( ),假如跟的是数据类型,就有必要带上( )。 需求留意的是,sizeof 是C言语中的操作符,不是函数,所以可以不带( ),后边会具体解说。
不同整型的输出

运用不同的格局操控符可以输出不同类型的整数,它们别离是:
●%hd用来输出 short int 类型,hd 是 short decimal 的简写;
●%d用来输出 int 类型,d 是 decimal 的简写;
●%ld用来输出 long int 类型,ld 是 long decimal 的简写。
下面的比如演示了不同整型的输出:

运转成果: a=10, b=100, c=9437

在编写代码的进程中,我主张将格局操控符和数据类型严厉对应起来,养成良好的编程习气。当然,假如你不严厉对应,一般也不会导致过错,例如,许多初学者都运用%d输出一切的整数类型,请看下面的比如:

运转成果仍然是: a=10, b=100, c=9437

当运用%d输出 short,或许运用%ld输出 short、int 时,不论值有多大,都不会产生过错,由于格局操控符满足包容这些值。 当运用%hd输出 int、long,或许运用%d输出 long 时,假如要输出的值比较小(就像上面的状况),一般也不会产生过错,假如要输出的值比较大,就很有或许产生过错,例如:

在 64 位 Linux 和 Mac OS 下(long 的长度为 8)的运转成果为:

m=-21093, n=4556 n=-1898311220

输出成果彻底是过错的,这是由于%hd包容不下 m 和 n 的值,%d也包容不下 n 的值。 读者需求留意,当格局操控符和数据类型不匹配时,编译器会给出正告,提示程序员或许会存在危险。

编译器的正告是分等级的,不同程度的危险被区别成了不同的正告等级,而运用%d输出 short 和 long 类型的危险较低,假如你的编译器设置只对较高危险的操作发出正告,那么此处你就看不到正告信息。

3.4 C言语中的二进制数、八进制数和十六进制数

C言语中的整数除了可以运用十进制,还可以运用二进制、八进制和十六进制。
二进制数、八进制数和十六进制数的标明

一个数字默许便是十进制的,标明一个十进制数字不需求任何特别的格局。可是,标明一个二进制、八进制或许十六进制数字就不相同了,为了和十进制数字区别开来,有必要选用某种特别的写法,具体来说,便是在数字前面加上特定的字符,也便是加前缀。

  1. 二进制
    二进制由 0 和 1 两个数字组成,运用时有必要以0b或0B(不区别大小写)最初,例如:

读者请留意,规范的C言语并不支撑上面的二进制写法,仅仅有些编译器自己进行了扩展,才支撑二进制数字。换句话说,并不是一切的编译器都支撑二进制数字,只要一部分编译器支撑,而且跟编译器的版别有联系。

下面是实践测验的成果:
●Visual C++ 6.0 不支撑。
●Visual Studio 2015 支撑,可是 Visual Studio 2010 不支撑;可以以为,高版别的 Visual Studio 支撑二进制数字,低版别的 Visual Studio 不支撑。
●GCC 4.8.2 支撑,可是 GCC 3.4.5 不支撑;可以以为,高版别的 GCC 支撑二进制数字,低版别的 GCC 不支撑。
●LLVM/Clang 支撑(内嵌于 Mac OS 下的 Xcode 中)。

  1. 八进制
    八进制由 0~7 八个数字组成,运用时有必要以0最初(留意是数字 0,不是字母 o),例如:

  2. 十六进制
    十六进制由数字 09、字母 AF 或 a~f(不区别大小写)组成,运用时有必要以0x或0X(不区别大小写)最初,例如:

  3. 十进制
    十进制由 0~9 十个数字组成,没有任何前缀,和咱们平常的书写格局相同,不再赘述。
    二进制数、八进制数和十六进制数的输出
    C言语中常用的整数有 short、int 和 long 三种类型,经过 printf 函数,可以将它们以八进制、十进制和十六进制的办法输出。上节咱们解说了怎么以十进制的办法输出,这节咱们重点解说怎么以八进制和十六进制的办法输出,下表列出了不同类型的整数、以不同进制的办法输出时对应的格局操控符:

short int long
八进制 %ho %o %lo
十进制 %hd %d %ld
十六进制 %hx 或许 %hX %x 或许 %X %lx 或许 %lX

十六进制数字的标明用到了英文字母,有大小写之分,要在格局操控符中体现出来:

●%hx、%x 和 %lx 中的x小写,标明以小写字母的办法输出十六进制数;

●%hX、%X 和 %lX 中的X大写,标明以大写字母的办法输出十六进制数。
八进制数字和十进制数字不区别大小写,所以格局操控符都用小写办法。假如你比较背叛,想运用大写办法,那么行为是未界说的,请你稳重:

●有些编译器支撑大写办法,只不过行为和小写办法相同;

●有些编译器不支撑大写办法,或许会报错,也或许会导致古怪的输出。
留意,尽管部分编译器支撑二进制数字的标明,可是却不能运用 printf 函数输出二进制,这一点比较惋惜。当然,经过转化函数可以将其它进制数字转化成二进制数字,并以字符串的办法存储,然后在 printf 函数中运用%s输出即可。考虑到读者的根底还不行,这儿就先不讲这种办法了。 【实例】以不同进制的办法输出整数:

运转成果:

a=126, b=2713, c=7325603
a=86, b=1483, c=1944451
a=56, b=5cb, c=1dab83
a=56, b=5CB, c=1DAB83

从这个比如可以发现,一个数字不论以何种进制来标明,都可以以任意进制的办法输出。数字在内存中一直以二进制的办法存储,其它进制的数字在存储前都有必要转化为二进制办法;同理,一个数字在输出时要进行逆向的转化,也便是从二进制转化为其他进制。
输出时加上前缀
请读者留意调查上面的比如,会发现有一点不完美,假如只看输出成果:
●关于八进制数字,它无法和十进制、十六进制区别,由于八进制、十进制和十六进制都包含 07 这几个数字。
●关于十进制数字,它无法和十六进制区别,由于十六进制也包含 0
9 这几个数字。假如十进制数字中还不包含 8 和 9,那么也不能和八进制区别了。
●关于十六进制数字,假如没有包含 af 或许 AF,那么就无法和十进制区别,假如还不包含 8 和 9,那么也不能和八进制区别了。
区别不同进制数字的一个简略办法便是,在输出时带上特定的前缀。在格局操控符中加上#即可输出前缀,例如 %#x、%#o、%#lX、%#ho 等,请看下面的代码:

运转成果:

a=0126, b=02713, c=07325603
a=86, b=1483, c=1944451
a=0x56, b=0x5cb, c=0x1dab83
a=0X56, b=0X5CB, c=0X1DAB83

十进制数字没有前缀,所以不必加#。假如你加上了,那么它的行为是未界说的,有的编译器支撑十进制加#,只不过输出成果和没有加#相同,有的编译器不支撑加#,或许会报错,也或许会导致古怪的输出;可是,大部分编译器都能正常输出,不至于当成一种过错。
3.5 C言语中的正负数及其输出
在数学中,数字有正负之分。在C言语中也是相同,short、int、long 都可以带上正负号,例如:

假如不带正负号,默许便是正数。 符号也是数字的一部分,也要在内存中体现出来。符号只要正负两种状况,用1位(Bit)就足以标明;C言语规则,把内存的最高位作为符号位。以 int 为例,它占用 32 位的内存,0~30 位标明数值,31 位标明正负号。如下图所示:

C语言内功心法篇—变量与数据类型

在编程言语中,计数往往是从0开端,例如字符串 “abc123″,咱们称第 0 个字符是 a,第 1 个字符是 b,第 5 个字符是 3。这和咱们平常从 1 开端计数的习气不相同,咱们要渐渐适应,培育编程思维。
C言语规则,在符号位中,用 0 标明正数,用 1 标明负数。例如 int 类型的 -10 和 +16 在内存中的标明如下:

C语言内功心法篇—变量与数据类型

short、int 和 long 类型默许都是带符号位的,符号位以外的内存才是数值位。假如只考虑正数,那么各种类型能标明的数值规模(取值规模)就比原本小了一半。 可是在许多状况下,咱们十分确认某个数字只能是正数,比如班级学生的人数、字符串的长度、内存地址等,这个时分符号位便是多余的了,就不如删掉符号位,把一切的位都用来存储数值,这样能标明的数值规模更大(大一倍)。 C言语答应咱们这样做,假如不期望设置符号位,可以在数据类型前面加上 unsigned 关键字,例如:

这样,short、int、long 中就没有符号位了,一切的位都用来标明数值,正数的取值规模更大了。这也意味着,运用了 unsigned 后只能标明正数,不能再标明负数了。 假如将一个数字分为符号和数值两部分,那么不加 unsigned 的数字称为有符号数,能标明正数和负数,加了 unsigned 的数字称为无符号数,只能标明正数。 请读者留意一个小细节,假如是unsigned int类型,那么可以省略 int ,只写 unsigned,例如:

无符号数的输出
无符号数可以以八进制、十进制和十六进制的办法输出,它们对应的格局操控符别离为:

unsigned short unsigned int unsigned long
八进制 %ho %o %lo
十进制 %hu %u %lu
十六进制 %hx 或许 %hX %x 或许 %X %lx 或许 %lX

上节咱们也讲到了不同进制办法的输出,可是上节咱们还没有讲到正负数,所以也没有关怀这一点,仅仅“抽象”地介绍了一遍。现在本节现已讲到了正负数,那咱们就再深入地说一下。 严厉来说,格局操控符和整数的符号是紧密相关的,具体便是:

●%d 以十进制办法输出有符号数;
●%u 以十进制办法输出无符号数;
●%o 以八进制办法输出无符号数;
●%x 以十六进制办法输出无符号数。

那么,怎么以八进制和十六进制办法输出有符号数呢?很惋惜,printf 并不支撑,也没有对应的格局操控符。在实践开发中,也根本没有“输出负的八进制数或许十六进制数”这样的需求,我想或许正是由于这一点,printf 才没有供给对应的格局操控符。 下表全面地总结了不同类型的整数,以不同进制的办法输出时对应的格局操控符(–标明没有对应的格局操控符)。

short int long unsigned short unsigned int unsigned long
八进制 %ho %o %lo
十进制 %hd %d %ld %hu %u %lu
十六进制 %hx 或许 %hX %x 或许 %X %lx 或许 %lX

有读者或许会问,上节咱们也运用 %o 和 %x 来输出有符号数了,为什么没有产生过错呢?这是由于:

●当以有符号数的办法输出时,printf 会读取数字所占用的内存,并把最高位作为符号位,把剩余的内存作为数值位;
●当以无符号数的办法输出时,printf 也会读取数字所占用的内存,并把一切的内存都作为数值位对待。

关于一个有符号的正数,它的符号位是 0,当依照无符号数的办法读取时,符号位就变成了数值位,可是该位恰好是 0 而不是 1,所以对数值不会产生影响,这就好比在一个数字前面加 0,有多少个 0 都不会影响数字的值。 假如对一个有符号的负数运用 %o 或许 %x 输出,那么成果就会截然不同,读者可以亲试。 可以说,“有符号正数的最高位是 0”这个偶然才使得 %o 和 %x 输出有符号数时不会犯错。 再次强调,不论是以 %o、%u、%x 输出有符号数,仍是以 %d 输出无符号数,编译器都不会报错,仅仅对内存的解说不同了。%o、%d、%u、%x 这些格局操控符不会关怀数字在界说时到底是有符号的仍是无符号的:
●你让我输出无符号数,那我在读取内存时就不区别符号位和数值位了,我会把一切的内存都看做数值位;
●你让我输出有符号数,那我在读取内存时会把最高位作为符号位,把剩余的内存作为数值位。
说得再直接一些,我管你在界说时是有符号数仍是无符号数呢,我只关怀内存,有符号数也可以依照无符号数输出,无符号数也可以依照有符号数输出,至于输出成果对不对,那我就不论了,你自己承当危险。 下面的代码进行了全面的演示:

运转成果:

a=0100, b=0xffffffff, c=720
m=-1, n=-2147483648, p=100

关于绝大多数初学者来说,b、m、n 的输出成果看起来十分古怪,甚至不能了解。依照一般的推理,b、m、n 这三个整数在内存中的存储办法别离是:

C语言内功心法篇—变量与数据类型

当以 %x 输出 b 时,成果应该是 0x80000001;当以 %hd、%d 输出 m、n 时,成果应该别离是 -7fff、-0。可是实践的输出成果和咱们推理的成果却截然不同,这是为什么呢?
留意,-7fff 是十六进制办法。%d 原本应该输出十进制,这儿仅仅为了看起来便利,才改为十六进制。
其实这跟整数在内存中的存储办法以及读取办法有关。b 是一个有符号的负数,它在内存中并不是像上图演示的那样存储,而是要经过一定的转化才干写入内存;m、n 的内存尽管没有过错,可是当以 %d 输出时,并不是原样输出,而是有一个逆向的转化进程(和存储时的转化进程恰好相反)。 也便是说,整数在写入内存之前或许会产生转化,在读取时也或许会产生转化,而咱们没有考虑这种转化,所以才会导致推理过错。那么,整数在写入内存前,以及在读取时究竟产生了怎样的转化呢?为什么会产生这种转化呢?咱们将在《整数在内存中是怎么存储的,为什么它堪称天才般的规划》一节中揭开谜底。

3.6 整数在内存中是怎么存储的,为什么它堪称天才般的规划
加法和减法是核算机中最根本的运算,核算机时时刻刻都离不开它们,所以它们得由硬件直接支撑。为了提高加减法的运算效率,硬件电路需求规划地满足精简。
关于有符号数,内存要区别符号位和数值位,关于人脑来说,很简略区别,可是关于核算机而言,就要规划专门的电路,这无疑增加了硬件的杂乱性,增加了核算的时间。
要是能把符号位和数值位等同起来,让它们一起参加运算,不再加以区别,这样硬件电路就变得简略了起来。
别的,加法和减法也可以合并为一种运算,便是加法运算,由于减去一个数相当于加上这个数的相反数,例如:5 – 3等价于 5 + (-3),10 – (-9)等价于10+9.

相反数是指数值相同,符号不同的两个数,例如,10和-10便是一堆相反数,-98和98也是一对相反数。
假如可以完成上面的两个方针,那么只需规划一种简略的、不必区别符号位和数值位的加法电路,就能一起完成加法和减法运算,而且十分高效。
实践上,这两个方针都现已完成了,实在的核算机硬件电路便是如此简略。
可是,简化硬件电路是有价值的,这个价值便是有符号数在存储和读取时都要进行转化。那么,这个转化进程究竟是怎样的呢?接下来咱们就具体地解说一下。

首先,请记住如下几个概念。
(1)原码
将一个整数转化成二进制办法,便是其原码。
例如short a = 6;
a的原码便是0000 0000 0000 0110;
更改a的值a = -18,此刻a的原码便是1000 0000 0001 0010。
浅显的了解,原码便是一个整数原本的二进制办法

(2)反码
谈到反码,需求将正数和负数区别对待,由于它们的反码不相同。
关于正数,它的反码便是其原码(原码和反码相同);
负数的反码便是将原码中除符号位以外的一切位(数值位)取反,也便是0变成1,1变成0.。
例如short a = 6,a的原码和反码都是0000 0000 0000 0110;
更改a的值a = -18,此刻a的反码是 1111 1111 1110 1101.

(3)补码
正数和负数的补码也不相同,也要区别对待。
关于正数,它的补码便是其原码(原码、反码、补码都相同);
负数的补码是其反码加 1。
例如short a = 6;,a 的原码、反码、补码都是0000 0000 0000 0110;
更改 a 的值a = -18;,此刻 a 的补码是1111 1111 1110 1110。

可以以为,补码是在反码的根底上打了一个补丁,进行了一下修正,所以叫“补码”。
原码、反码、补码的概念只对负数有实践意义,关于正数,它们都相同。

终究咱们总结一下 6 和 -18 从原码到补码的转化进程:

C语言内功心法篇—变量与数据类型

在核算机内存中,整数一律选用补码的办法来存储。这意味着,当读取整数时还要选用逆向的转化,也便是将补码转化为原码。将补码转化为原码也很简略:先减去 1,再将数值位取反即可。
补码到底是怎么简化电路的

假设 6 和 18 都是 short 类型的,现在咱们要核算 6 – 18 的成果,依据运算规则,它等价于 6 + (-18)。

假如选用原码核算,那么运算进程为:
6 – 18 = 6 + (-18) = [0000 0000 0000 0110]原 + [1000 0000 0001 0010]原 = [1000 0000 0001 1000]原 = -24
直接用原码标明整数,让符号位也参加运算,关于相似上面的减法来说,成果显然是不正确的。
于是人们开端继续探究,不断试错,后来规划出了反码。下面就演示了反码运算的进程:
6 – 18 = 6 + (-18) = [0000 0000 0000 0110]反 + [1111 1111 1110 1101]反 = [1111 1111 1111 0011]反 = [1000 0000 0000 1100]原 = -12
这样一来,核算成果就正确了。

可是,这样还不算万事大吉,咱们不妨将减数和被减数交流一下方位,也便是核算 18 – 6 的成果:
18 – 6 = 18 + (-6) = [0000 0000 0001 0010]反 + [1111 1111 1111 1001]反 = [1 0000 0000 0000 1011]反 = [0000 0000 0000 1011]反 = [0000 0000 0000 1011]原 = 11
依照反码核算的成果是 11,而实在的成果应该是 12 才对,它们相差了 1。

加粗的 1 是加法运算进程中的进位,它溢出了,内存包容不了了,所以直接截掉。
6 – 18 的成果正确,18 – 6 的成果就不正确,相差 1。依照反码来核算,是不是小数减去大数正确,大数减去小数就不对了,一直相差 1 呢?咱们不妨再看两个比如,别离是 5 – 13 和 13 – 5。

5 – 13 的运算进程为:
5 – 13 = 5 + (-13) = [0000 0000 0000 0101]原 + [1000 0000 0000 1101]原 = [0000 0000 0000 0101]反 + [1111 1111 1111 0010]反 = [1111 1111 1111 0111]反 = [1000 0000 0000 1000]原 = -8
13 – 5 的运算进程为:

13 – 5 = 13 + (-5) = [0000 0000 0000 1101]原 + [1000 0000 0000 0101]原 = [0000 0000 0000 1101]反 + [1111 1111 1111 1010]反 = [1 0000 0000 0000 0111]反 = [0000 0000 0000 0111]反 = [0000 0000 0000 0111]原 = 7
这足以证明,方才的猜想是正确的:小数减去大数不会有问题,而大数减去小数的就不对了,成果一直相差 1。
相差的这个 1 要进行纠正,可是又不能影响小数减去大数,怎么办呢?于是人们又绞尽脑汁规划出了补码,给反码打了一个“补丁”,总算把相差的 1 给纠正过来了。
下面演示了依照补码核算的进程:

6 – 18 = 6 + (-18) = [0000 0000 0000 0110]补 + [1111 1111 1110 1110]补 = [1111 1111 1111 0100]补 = [1111 1111 1111 0011]反 = [1000 0000 0000 1100]原 = -12
18 – 6 = 18 + (-6) = [0000 0000 0001 0010]补 + [1111 1111 1111 1010]补 = [1 0000 0000 0000 1100]补 = [0000 0000 0000 1100]补 = [0000 0000 0000 1100]反 = [0000 0000 0000 1100]原 = 12
5 – 13 = 5 + (-13) = [0000 0000 0000 0101]补 + [1111 1111 1111 0011]补 = [1111 1111 1111 1000]补 = [1000 1111 1111 0111]反 = [1000 0000 0000 1000]原 = -8
13 – 5 = 13 + (-5) = [0000 0000 0000 1101]补 + [1111 1111 1111 1011]补 = [1 0000 0000 0000 1000]补 = [0000 0000 0000 1000]补 = [0000 0000 0000 1000]反 = [0000 0000 0000 1000]原 = 8

你看,选用补码的办法正好把相差的 1 纠正过来,也没有影响到小数减去大数,这个“补丁”真是巧妙。

小数减去大数,成果为负数,之前(负数从反码转化为补码要加 1)加上的 1,后来(负数从补码转化为反码要减 1)还要减去,正好抵消掉,所以不会受影响。
而大数减去小数,成果为正数,之前(负数从反码转化为补码要加 1)加上的 1,后来(正数的补码和反码相同,从补码转化为反码不必减 1)就没有再减去,不能抵消掉,这就相当于给核算成果多加了一个 1。

补码这种天才般的规划,一举达成了本文最初提到的两个方针,简化了硬件电路。
C言语中的小数(float,double)
小数分为整数部分和小数部分,它们由点号.分隔,例如 0.0、75.0、4.023、0.27、-937.198 -0.27 等都是合法的小数,这是最常见的小数办法,咱们将它称为十进制办法。 此外,小数也可以选用指数办法,例如 7.25102、0.0368105、100.2210-2、-27.3610-3 等。任何小数都可以用指数办法来标明。 C言语一起支撑以上两种办法的小数。可是在书写时,C言语中的指数办法和数学中的指数办法有所差异。 C言语中小数的指数办法为:aEn 或 aen
a 为尾数部分,是一个十进制数;n 为指数部分,是一个十进制整数;E或e是固定的字符,用于切割尾数部分和指数部分。整个表达式等价于 a10n。 指数办法的小数举例:

●2.1E5 = 2.1105,其间 2.1 是尾数,5 是指数。
●3.7E-2 = 3.710-2,其间 3.7 是尾数,-2 是指数。
●0.5E7 = 0.5107,其间 0.5 是尾数,7 是指数。

C言语中常用的小数有两种类型,别离是 float 或 double;float 称为单精度浮点型,double 称为双精度浮点型。 不像整数,小数没有那么多幺蛾子,小数的长度是固定的,float 一直占用4个字节,double 一直占用8个字节。
小数的输出
小数也可以运用 printf 函数输出,包含十进制办法和指数办法,它们对应的格局操控符别离是:
●%f 以十进制办法输出 float 类型;
●%lf 以十进制办法输出 double 类型;
●%e 以指数办法输出 float 类型,输出成果中的 e 小写;
●%E 以指数办法输出 float 类型,输出成果中的 E 大写;
●%le 以指数办法输出 double 类型,输出成果中的 e 小写;
●%lE 以指数办法输出 double 类型,输出成果中的 E 大写。
下面的代码演示了小数的标明以及输出:

运转成果:

a=3.020000e-01
b=128.100998
c=123.000000
d=1.126400E+05
e=0.007623
f=1.230024

对代码的阐明: 1) %f 和 %lf 默许保存六位小数,不足六位以 0 补齐,超过六位按四舍五入切断。 2) 将整数赋值给 float 变量时会变成小数。 3) 以指数办法输出小数时,输出成果为科学计数法;也便是说,尾数部分的取值为:0 ≤ 尾数 < 10。 4) b 的输出成果让人费解,才三位小数,为什么不能准确输出,而是输出一个近似值呢?这和小数在内存中的存储办法有关,许多简略的小数压根不能准确存储,所以也就不能准确输出,咱们将在下节《小数在内存中是怎么存储的,揭秘诺贝尔奖等级的规划(长篇神文)》中具体解说。 别的,小数还有一种愈加智能的输出办法,便是运用%g。%g 会比照小数的十进制办法和指数办法,以最短的办法来输出小数,让输出成果愈加简练。所谓最短,便是输出成果占用最少的字符。 %g 运用示例:

运转成果:

a=1e-05
b=3e+07
c=12.84
d=1.22934

对各个小数的剖析:
●a 的十进制办法是 0.00001,占用七个字符的方位,a 的指数办法是 1e-05,占用五个字符的方位,指数办法较短,所以以指数的办法输出。
●b 的十进制办法是 30000000,占用八个字符的方位,b 的指数办法是 3e+07,占用五个字符的方位,指数办法较短,所以以指数的办法输出。
●c 的十进制办法是 12.84,占用五个字符的方位,c 的指数办法是 1.284e+01,占用九个字符的方位,十进制办法较短,所以以十进制的办法输出。
●d 的十进制办法是 1.22934,占用七个字符的方位,d 的指数办法是 1.22934e+00,占用十一个字符的方位,十进制办法较短,所以以十进制的办法输出。
读者需求留意的两点是:
●%g 默许最多保存六位有效数字,包含整数部分和小数部分;%f 和 %e 默许保存六位小数,只包含小数部分。
●%g 不会在终究强加 0 来凑够有效数字的位数,而 %f 和 %e 会在终究强加 0 来凑够小数部分的位数。
总之,%g 要以最短的办法来输出小数,而且小数部分表现很自然,不会强加零,比 %f 和 %e 更有弹性,这在大部分状况下是契合用户习气的。 除了 %g,还有 %lg、%G、%lG:
●%g 和 %lg 别离用来输出 float 类型和 double 类型,而且当以指数办法输出时,e小写。
●%G 和 %lG 也别离用来输出 float 类型和 double 类型,仅仅当以指数办法输出时,E大写。
数字的后缀
一个数字,是有默许类型的:关于整数,默许是 int 类型;关于小数,默许是 double 类型。 请看下面的比如:
long a = 100;int b = 294;float x = 52.55;double y = 18.6;
100 和 294 这两个数字默许都是 int 类型的,将 100 赋值给 a,有必要先从 int 类型转化为 long 类型,而将 294 赋值给 b 就不必转化了。 52.55 和 18.6 这两个数字默许都是 double 类型的,将 52.55 赋值给 x,有必要先从 double 类型转化为 float 类型,而将 18.6 赋值给 y 就不必转化了。 假如不想让数字运用默许的类型,那么可以给数字加上后缀,手动指明类型:
●在整数后边紧跟 l 或许 L(不区别大小写)标明该数字是 long 类型;
●在小数后边紧跟 f 或许 F(不区别大小写)标明该数字是 float 类型。
请看下面的代码:
long a = 100l;int b = 294;short c = 32L; float x = 52.55f;double y = 18.6F;float z = 0.02;
加上后缀,尽管数字的类型变了,但这并不意味着该数字只能赋值给指定的类型,它仍然可以赋值给其他的类型,只需进行了一下类型转化就可以了。 关于初学者,很少会用到数字的后缀,加不加往往没有什么区别,也不影响实践编程,可是既然学了C言语,仍是要知道这个知识点的,如果看到他人的代码这么用了,而你却不明白怎么回事,那就为难了。
关于数据类型的转化,咱们将在《C言语数据类型转化》一节中深入探讨。
小数和整数彼此赋值
在C言语中,整数和小数之间可以彼此赋值:
●将一个整数赋值给小数类型,在小数点后边加 0 就可以,加几个都无所谓。
●将一个小数赋值给整数类型,就得把小数部分丢掉,只能取整数部分,这会改变数字原本的值。留意是直接丢掉小数部分,而不是依照四舍五入取近似值。
请看下面的代码:
#include <stdio.h>int main(){ float f = 251; int w = 19.427; int x = 92.78; int y = 0.52; int z = -87.27; printf(“f = %f, w = %d, x = %d, y = %d, z = %d\n”, f, w, x, y, z); return 0;}

运转成果: f = 251.000000, w = 19, x = 92, y = 0, z = -87

由于将小数赋值给整数类型时会“失真”,所以编译器一般会给出正告,让咱们引起留意。