C语言简介

第一部分:C语言简介
1、编程语言发展简史
    1.机器语言时期(1842年~约1970年)
    奥古斯塔·阿达·金,勒芙蕾丝伯爵夫人(Augusta Ada King, Countess of Lovelace,1815年12月10日-1852年11月27日),原名奥古斯塔·阿达·拜伦(Augusta Ada Byron),通称阿达·洛芙莱斯(Ada Lovelace),是著名英国诗人拜伦之女,数学家,计算机程序创始人
    阿达·洛芙莱斯是有史记载的第一位采用程序化编程语言(机器语言)编程的人,被认为是“程序员的鼻祖”、“第一位给计算机编写程序的人”。
    阿达·洛芙莱斯最早使用的程序编程语言称为“机器语言”,形式为打孔纸带。机器语言是一种直接编写二进制的语言,非常复杂难懂,但是执行效率最高,因此仍然有许多程序员使用。直至高级编程语言发明后机器语言才被逐渐淘汰。
    2.汇编语言时期(1946~今)
    由于机器语言过于复杂、困难,且打孔纸带不便修改与保存,因此机器语言的升级版——汇编语言便应运而生。
    汇编语言(assembly language)可以视为机器语言的升级版,使用助记符代替机器语言的二进制码,使用地址符或者标签(label)作为地址。
    通常情况下来说,不同的机器指令集对应不同的汇编语言,因此不同版本的汇编语言不可直接移植。
    3.高级编程语言前期(1957~今)
    汇编语言对于新手程序员来说仍然过于晦涩难懂,且汇编语言不便移植的特点也一定程度限制了汇编语言的流行。因此,汇编语言的升级版——面向过程的编程语言出现了。
    1957年,IBM发明的FORTRAN语言可以视为第一类面向过程的编程语言,但是FORTRAN语言对程序书写格式的要求相当严格,使用起来十分困难。
    1972年发明的Pascal语言与C语言可以称之为面向过程的编程语言的代表。C语言的发明人Dennis Ritchie同时也是Unix系统的发明人之一,因此C语言搭借操作系统而全球风靡
    4.高级编程语言后期(1980~今)
    1980年,Alan Kay创造了Smalltalk并发明了“面向对象”这个词。不过对于当时的程序员来说,“面向对象”这个词过于抽象难以理解。
    1983年,Bjarne Stroustrup对C语言进行的大幅度的升级,将“面向对象”的概念嫁接到C语言上,创造出了C++语言。
    1986年,Brad Cox和Tom Love创造了Objective-C。
    1991年,荷兰程序员Guido van Rossum发明了Python语言。
    1990年,James Gosling发明了Oak编程语言,该语言在1995年改名为Java。James Gosling也被称为“Java之父”。
    5.自然语言编程(未来)
    
2、编程语言的发展流程:
机器语言 -----> 汇编语言 -----> 高级编程语言 -----> 自然语言
古老            较老,不太常见    常见                新(发展未成熟)
难-------------------------------------------------->简单
与电脑亲和度高-------------------------------------->与电脑亲和度低
底层------------------------------------------------>高层
3、高级语言分类
通常情况下,我们可以按照编程语言的运行方式,将编程语言分为:
    编译型编程语言(如C、C++等)
    解释型编程语言(如Python等脚本语言,以及Linux下的Shell编程)
还可以按照编程语言的发展先后顺序或者编程语言的属性,将编程语言分为:
    面向过程的编程语言(较少,如C语言、Pascal语言)
    面向对象的编程语言(较多,目前广泛应用)
/********面向对象语言**********/
相对于面向过程的编程语言,面向对象的编程语言具有3个显著的特点:封装、继承、多态
⒈封装 (encapsulation):隐藏对象的属性和实现细节,仅对外公开接口,控制在程序中属性的读取和修改的访问级别。
⒉继承(inheritance):继承是面向对象语言的重要机制。借助继承,可以扩展原有的代码,应用到其他程序中,而不必重新编写这些代码。在java语言中,继承是通过扩展原有的类,声明新类来实现的。扩展声明的新类称为子类,原有的类称为超类(父类)。继承机制规定,子类可以拥有超类的所有属性和方法,也可以扩展定义自己特有的属性,增加新方法和重新定义超类的方法。
⒊多态(Polymorphism):多态性是允许你将父对象设置成为一个或更多的他的子对象相等的技术,赋值之后,父对象就可以根据当前赋值给它的子对象的特性以不同的方式运作。
/********面向对象语言end*******/
//JAVA属于半编译半解释型语言
4、C语言:
1)通过以上分类可以知道,C语言属于面向过程的编译型编程语言
2)C语言是美国Dennis Ritchie在1972年设计发明的,C语言主体诞生于1973年,正式发行于1977年
3)C语言经历过几个修订版本,其中影响比较大的是1989年修订的C89版与1999年修订的C99版。C89版也是当代C语言通行的基础版本
4)C语言主要用于编写操作系统内核、底层驱动程序、小型应用程序、数据库等,借助操作系统,C语言长期处于编程语言排行榜前几位的位置。

第二部分:编写简单的C程序:HelloWorld
1、编写简单的C程序HelloWorld
#include<stdio.h>
int main()
{
    printf("Hello World!\n");
    return 0;
}

程序讲解:
#include<stdio.h>:标准I/O头文件,下文中的printf需要使用这个文件
main():主函数,一个程序的入口。一个程序有且只有一个main()函数
int:代表主函数的返回值类型是int型
printf():格式化输出,将括号内的内容打印到显示器上,打印字符串需要用双引号""引上
return 0;:配合主函数类型使用,一般来说返回0的程序表示程序运行成功
2、编译器GCC:把程序代码变成可执行程序
1)GCC是GNU C Compiler的缩写,是开源的C语言编译器
2)把hello.c变成可执行程序步骤:
1.gcc hello.c -o hello
-o的作用:生成的产物的名字
2.如果不加-o和名字,则默认生成a.out文件
gcc hello.c
则生成产物为a.out
3.make hello
此时会调用系统默认的Makefile,编译hello.c生成hello。
等价于gcc hello.c -o hello

执行该程序:
./hello(./a.out)

练习:编写程序,输出“XXX欢迎来到动物园!”(XXX是自己的名字)
提示:
⒈不要忘记头文件#include<stdio.h>
⒉不要忘记main函数以及main后有()
⒊不要忘记printf()中输出字符串需要用引号引起
⒋不要忘记在这句话之后加\n
⒌不要忘记在main函数内的语句结尾加;
答案:
/**********简易版************/
#include<stdio.h>
int main()
{
    printf("你好Li,欢迎光临\n");
    return 0;
}
/**********高级版************/
#include<stdio.h>
int main()
{
    char name[64]={0};
    printf("请输入用户名");
    scanf("%s",name);
    printf("你好%s,欢迎光临\n",name);
    return 0;
}
3)注释:
注释是解释代码用的文本,是编写程序代码的人为了方便理解代码所写的文本。
常见注释:
行注释:使用//,注释一行
块注释:使用/**/,注释一段
被注释的文本不会被执行
#include<stdio.h>
int main()//这是一个行注释
{
    printf("Hello World!\n");//这是一个行注释
    /*
    这是一个块注释
    printf("Hello Student\n");//这行被注释了不会执行
    */
    //printf("Hello Boys and Girls\n");//这行被注释了不会执行,去掉行首注释即可执行
    return 0;
}
/**************使用条件编译快速注释代码***********************/
当需要注释的代码量较多时,如果使用块注释(/**/)则会出现无法配对等情况
此时可以使用以下方法来快速注释一段代码:
#if 0
待注释代码……
#endif
此时就注释了一段代码。
当需要取消这段注释时,只需将0改成1即可
#if 1
待注释代码……
#endif
/**************使用条件编译快速注释代码end********************/
4)C语言从代码变成可执行程序的步骤:
预处理 -----> 编译 -----> 汇编 -----> 链接
⒈预处理:去掉注释,加载头文件,代替宏定义,条件编译
需要文件:.c文件
生成产物:预处理文件(以.i结尾)
使用方法:gcc hello.c -E -o hello.i
可以使用vim打开预处理文件来查看生成产物
⒉编译:使用编译器进行C语言的语法检查,如果有语法错误,报错,并结束编译过程;如果没有语法错误,把C的源程序转变为汇编代码
需要文件:.i文件
生成产物:汇编文件(以.s结尾)
使用方法:gcc hello.i -S -o hello.s
可以使用vim打开汇编文件来查看生成产物
⒊汇编:把汇编源文件通过汇编器生成目标文件(二进制机器语言)
需要文件:.s文件
生成产物:机器码(或称为“目标代码”,以.o结尾)
使用方法:gcc hello.s -c -o hello.o
可以使用vim打开目标代码文件来查看生成产物(不过只会看到乱码)
⒋链接:把目标文件执行所依赖的所有二进制的其他目标文件及C的库文件都整合成一个可执行文件的过程
需要文件:.o文件及各种动态库或静态库
生成产物:可执行程序
使用方法:gcc hello.o -o hello

-o:指定生成的产物的名字
-Wall:让编译器报告全部错误
我们要养成良好习惯,在编译过程中添加-o指定生成产物名称,添加-Wall报告所有的error和warning方便我们调试程序。完整的编译指令如下:
gcc hello.c -o hello -Wall

编译完成后(无error,无warning),会生成-o之后的文件(如没有加-o则会生成a.out文件)
执行文件:
./hello(./a.out)
练习:编程实现打印用*组成的字母C。要求先使用分步编译并观察每步的产物,再使用完整编译。
生成图案如下:
  ***
 *   *
*
*
*
 *   *
  ***
答案:
#include<stdio.h>
int main()
{
    printf("  ***\n");
    printf(" *   *\n");
    printf("*\n");
    printf("*\n");
    printf("*\n");
    printf(" *   *\n");
    printf("  ***\n");
    return 0;
}
3、C语言的编程规范:
1)命名规范:
⒈在所有命名中,都应使用标准的英文单词或缩写。
⒉所有命名都应遵循达意原则,即名称应含义清晰、明确。
⒊所有命名都不易过长,应控制在规定的最大长度以内,一般不超过32个字节。
2)编码规范:
⒈在一行代码内只写一条语句;
⒉在嵌套的函数块中使用一个TAB缩进;
⒊每行长度不应超过80个字符;
(如超过,在行尾加\表示换行)
⒋一个函数建议不要超过200行代码;
⒌程序中结束符号;(分号)前面不要加空格;
⒍为程序添加适当的注释;
3)代码要求:
⒈代码格式清楚、层次分明;
⒉代码易于理解、可读性强;
⒊代码具有健壮性,且没有错误;

有关于Linux C的编码规范,我们强烈推荐阅读linux kernel coding style文档(读1~8章和12章)
百度linux kernel coding(中文版:百度linux kernel coding cn)

第三部分:标识符,变量与常量
1、标识符
用来标识变量名、符号常量名、函数名、类型名、文件名等的有效字符序列;
1)标识符的命名规则:
⒈标识符只能由字母、数字和下划线三种字符组成,且第一个字符必须为字母或下划线;
⒉C语言中的标识符大小写敏感;
⒊用户自定义的标识符不能与关键字同名;

练习:下列哪些标识符是合法的?哪些是不合法的?不合法的标识符错误在哪?
Arena、 2ndtest、 arena  s_count、marks40、 oh!god、 class_one、start... end、int

2)关键字
关键字:对编译器具有特定含义的标识符,是标识符的一个特殊的集合。所有的关键字都是小写。
/*******************************C语言关键字*****************************************/
1、基本数据类型
void:声明函数无返回值或无参数,声明无类型指针,显示丢弃运算结果。(C89标准新增)
char:字符型类型数据,属于整型数据的一种。(K&R时期引入)
int:整型数据,表示范围通常为编译器指定的内存字节长。(K&R时期引入)
float:单精度浮点型数据,属于浮点数据的一种。(K&R时期引入)
double:双精度浮点型数据,属于浮点数据的一种。(K&R时期引入)
//_Bool:布尔型(C99标准新增)
//_Complex:复数的基本类型(C99标准新增)
//_Imaginary:虚数,与复数基本类型相似,没有实部的纯虚数(C99标准新增)
//_Generic:提供重载的接口入口(C11标准新增)
2、类型修饰关键字
short:修饰int,短整型数据,可省略被修饰的int。(K&R时期引入)
long:修饰int,长整型数据,可省略被修饰的int。(K&R时期引入)
//long long:修饰int,超长整型数据,可省略被修饰的int。(C99标准新增)
signed:修饰整型数据,有符号数据类型。(C89标准新增)
unsigned:修饰整型数据,无符号数据类型。(K&R时期引入)
//restrict:用于限定和约束指针,并表明指针是访问一个数据对象的唯一且初始的方式。(C99标准新增)
3、复杂类型关键字
struct:结构体声明。(K&R时期引入)
union:联合体声明。(K&R时期引入)
enum:枚举声明。(C89标准新增)
typedef:声明类型别名。(K&R时期引入)
sizeof:得到特定类型或特定类型变量的大小。(K&R时期引入)
//inline:内联函数用于取代宏定义,会在任何调用它的地方展开。(C99标准新增)
4、存储级别关键字
auto:指定为自动变量,由编译器自动分配及释放。通常在栈上分配。与static相反。当变量未指定时默认为auto。(K&R时期引入)
static:指定为静态变量,分配在静态变量区,修饰函数时,指定函数作用域为文件内部。(K&R时期引入)
register:指定为寄存器变量,建议编译器将变量存储到寄存器中使用,也可以修饰函数形参,建议编译器通过寄存器而不是堆栈传递参数。(K&R时期引入)
extern:指定对应变量为外部变量,即标示变量或者函数的定义在别的文件中,提示编译器遇到此变量和函数时在其他模块中寻找其定义。(K&R时期引入)
const:指定变量不可被当前线程改变(但有可能被系统或其他线程改变)。(C89标准新增)
volatile:指定变量的值有可能会被系统或其他线程改变,强制编译器每次从内存中取得该变量的值,阻止编译器把该变量优化成寄存器变量。(C89标准新增)
5、流程控制关键字
1)跳转结构
return:用在函数体中,返回特定值(如果是void类型,则不返回函数值)。(K&R时期引入)
continue:结束当前循环,开始下一轮循环。(K&R时期引入)
break:跳出当前循环或switch结构。(K&R时期引入)
goto:无条件跳转语句。(K&R时期引入)
2)分支结构
if:条件语句,后面不需要放分号。(K&R时期引入)
else:条件语句否定分支(必须与if连用)。(K&R时期引入)
switch:开关语句(多重分支语句)。(K&R时期引入)
case:开关语句中的分支标记,与switch连用。(K&R时期引入)
default:开关语句中的“其他”分支,可选。(K&R时期引入)
for:循环(K&R时期引入)
while:循环(K&R时期引入)
/*******************************C语言关键字end**************************************/
2、变量
1)变量的类型:
常见的变量类型有:
int:整型,保存整数
char:字符型,保存一个字符(字母、数字、其他符号等)
float:浮点型,保存小数
double:双精度浮点型,保存小数范围更大,小数点后位数更多(更精确)
2)变量的定义:
变量必须先定义,再使用
定义一个int型变量:
int a;//定义一个int型变量
定义一个char型变量:
char c;//定义一个char型变量
注意:定义变量不要重名,而且在选择变量名和其它标识符时,应注意做到“见名知意”,即选有含意的英文单词(或其缩写)作标识符。

扫描二维码关注公众号,回复: 2363357 查看本文章

3)变量的赋值
a = 10;//给int型变量赋值需要整数
c = 'A';//给char型变量赋值需要字符
有时为了书写简便,可以直接在定义变量时赋值。
int a = 10;
char c = 'A';
这种在定义时就直接赋值的使用方法称为“初始化”。
变量之间也可赋值,如:
int a = 10;
int b;
b = a;//将a中存放的值(10)赋值给b
4)打印变量的值
使用printf()来打印变量的值。
要打印a中的值10:printf("%d\n",a);
%d代表十进制输出一个整数。程序运行到这里,printf函数会扫描逗号后面的内容,遇到第一个变量后将这个变量的值放进%d的位置然后输出。
练习:交换杯子里的液体
有两个杯子(用int型代替),其中一个装酒,其中一个装水。如何让两个杯子中的液体交换?
提示:需要第三个杯子
答案:
#include<stdio.h>
int main()
{
    int a,b,tmp;//3个杯子,tmp用于中间交换用的空杯子
    a = 1;//用1代表酒
    b = 2;//用2代表水
    tmp = a;
    a = b;
    b = tmp;
    printf("a is %d\n",a);
    printf("b is %d\n",b);
    return 0;
}

3、常量
1)常量:在程序执行过程中,其值不被改变的量
直接常量:直接引用的数字等;
符号常量:使用标识符来代替一个数字(常见的:宏定义常量 和 常变量)
2)宏定义:又称为宏代换,是定义一个标识符来代表一个值或一个表达式
使用方法:在程序开头使用,#define
#define MAX 10
定义一个宏定义,使用MAX来代替10
练习1:
使用宏定义PI来定义3.1415926,计算圆的面积
答案:
#include<stdio.h>
#define PI 3.1415926
int main()
{
    float r,area;
    r = 5.0;
    area = PI*r*r;
    printf("%f\n",area);
    return 0;
}
练习2:租船问题。
写程序计算游客需要付的租船的费用。租船每小时30元,押金100元。游客输入租船时间,计算出租船费用。租船费用=时间*每小时钱数+押金。
要求押金与每小时钱数使用宏定义。
答案:
#include<stdio.h>
#define YAJIN 100
#define PERHOUR 30
int main()
{
    int hour;
    printf("请您输入需要租船的时间:");
    scanf("%d",&hour);
    printf("需要支付:%d元\n",hour*PERHOUR+YAJIN);
    return 0;
}
宏定义的使用阶段:在预处理阶段,将所有的宏换成宏值
宏定义也可定义表达式:
#define ADD(a,b) (a)+(b)
#define MUL(a,b) (a)*(b)
注意:如果要使用宏定义来代替表达式,需要在每个表达式的变量都加上括号以防止出现计算错误
练习:输入程序,对比以下两个宏定义的不同,思考产生不同结果的原因。
#include<stdio.h>
#define FUN1(a,b) a * b
#define FUN2(a,b) (a)*(b)
int main()
{
    int a=2;
    int b=3;
    printf("%d\n",FUN1(a+b,b+a));
    printf("%d\n",FUN2(a+b,b+a));
    return 0;
}
执行程序,输出:
13
25
答案:
FUN1的宏替换会变成:a+b*b+a
FUN2的宏替换会变成:(a+b)*(b+a)
因此两个宏替换会得到不同的结果。
3)常变量
常变量:变量值不可改变的量,使用const修饰
const int a = 10;//定义一个常变量
注意:
const修饰后的变量会变成只读,因此无法再次赋值。因此初始化常变量需要在定义时直接赋值。
常变量与宏定义常量的区别:
宏定义常量是在预处理阶段,使用宏名来代替宏值。而常变量是在程序运行时,程序在内存分配的一个变量,只不过变量不允许再次赋值。
/***********常量与后缀*************/
有时候我们需要显式地表示出常量的类型,这时候我们可以在常量后加后缀
u或U:unsigned类型,如123u
l或L:long类型,如123l
ll或LL:long long类型,如123456ll
f或F:float类型,如0.123f
/***********常量与后缀end**********/

第四部分:数据类型与类型转换
1、C语言的数据类型分类
C语言的数据类型分类:
基本数据类型:
    整型int
    字符型char
    浮点型:
        单精度浮点型float
        双精度浮点型double
构造数据类型:
    数组(如int a[])
    结构体struct
    联合体(或叫共用体)union
    枚举类型enum
指针类型(如int *p)
空类型void
2、int,unsigned int,short,long
1)整型int:
大小:16位2字节 或 32位4字节(取决于编译器)
存储格式:0~30位是数据位,第31位是符号位,用0代表正数,1代表负数。负数用补码存储。
存储范围:-2^31 ~ 2^31-1
打印格式:%d(十进制)、%o(八进制)、%x或%X(十六进制)
如需要打印八进制和十六进制的特殊格式,加#
%#o(打印八进制数,数前有0表示八进制数),%#x(打印十六进制数,数前有0x表示十六进制数)
说明:
int类型用于存放整数,占16或32位(取决于编译器),2或4字节。其中第31位为符号位(符号位0代表正数,1代表负数),后面几位为数据位
负数在内存中是以补码的形式存储的。
补码的计算方法:按位取反,末位加1
常见的有十进制,八进制,十六进制三种数字进制。
八进制输入输出格式化控制符使用%o或%#o。八进制数以0开头,如0666
十六进制输入输出格式化控制符使用%x或%X或%#x或%#X。十六进制数以0x开头,如0x12345678
/*********************原码、反码与补码**************************************/
计算机只能存储二进制数,因此十进制数、八进制数、十六进制数等在内存中都以二进制的形式存储的。内存中存储的二进制数分原码、反码和补码3种。
原码:将一个十进制数直接转换成二进制数存储
例如:9----->1001(原)
反码:在原码的基础上,按位取反
例如:9----->0110(反)
补码:在反码的基础上,末位+1。注意进位问题。
例如:9----->0111(补)
负数在内存中是以补码的形式存储的。即:负数的二进制存储就是其正数的补码
例如:9----->0111(补)-----> -9
练习:求以下数字的原码、反码、补码。(可以使用计算器)
1、15(10进制)
2、0xE3A4(16进制)
思考:为什么计算机要使用补码来存储负数?
答案:因为数字0。负数是正数的相反数,因此逻辑上来说使用反码存储负数合理。但是,如果使用反码存储负数,数字0会出现问题:
+0--->00000000
-0--->11111111
会发现+0与-0是两个不同的二进制数。但数字0不能够被刻意划分成+0和-0(0只能有一个,即+0与-0必须相等)。因此使用反码存储负数会出现问题。
而补码就不会出现+0与-0不同的问题:
+0--->00000000
-0--->11111111+1--->00000000(最高位的1被舍弃)
因此计算机使用补码来存储负数。

思考:对一个数取2次补码会产生什么结果?
答案:取2次补码等于自身
例如:9----->0111(补)
0111----->1001(补)----->9
/*********************原码、反码与补码end***********************************/

2)无符号整型unsigned int:
大小:同int型
存储格式:0~31位都是数据位,无符号位
存储范围:0 ~ 2^32-1
打印格式:%u
说明:
与int型基本相同,区别是int型的符号位不再是符号位而也作为数据位,因此无符号整型数据比整型数据存储范围大2倍,不过无法存储负数
适用于只有正数的情况
unsigned关键字不仅仅可以修饰int类型,还可修饰short long char等类型。
注意:unsigned关键字修饰的变量无法接收负数。
无符号数的输入输出格式控制是%u
3)短整型short:
大小:16位2字节
存储格式:0~14位为数据位,第15位为符号位,用0代表正数,1代表负数
存储范围:-32768 ~ 32767
打印格式:%hd
说明:
short类型与int类型类似,只不过是16位,2字节。第15位为符号位,后面几位为数据位
short类型适用于存储不太大的数据,节省内存空间。
short类型的输入输出格式控制是%hd
4)长整型long:
大小:32位4字节
存储格式:同32位int类型
存储范围:同32位int类型
打印格式:%ld
说明:
在过去的16位编译器中,int型是16位,所以long类型是32位。
不过在现代的32位编译器中,int型与long类型已无本质区别。
如果需要考虑程序跨平台移植(如16位编译器<--->32位编译器)需要谨慎选择使用int还是long
3、char类型:
大小:8位1字节
存储格式:0~6位都为数据位(128) 或 0~7位都为数据位(256,扩展后的ASCII表)
存储范围:0 ~ 255
打印格式:%c
说明:
char类型也属于整型数据类型(int、short、long)的一份子,只不过char类型运算时使用ASCII表来确定值。与其他三个整型数据类型不同的是,char类型默认就是unsigned类型,而其他三个则默认是signed类型。
/*********************ASCII表与转义字符*******************************/
在计算机的编码中,字符是使用ASCII表进行编码的。每一个字符都有一个对应的数字,例如:
'A'--->65
'a'--->97
'0'--->48 等
除了常见的数字、字母、符号(如+-*/%等)外,ASCII表还存储了一些看不见的控制字符,如:
空或'\0'(字符串结束标志)--->0
空格--->32
任意的ASCII表内字符都可以用'\'+数字(八进制)的方式来表示,有些还可以用'\'+字符来表示,称之为转义字符。转义字符即在'\'后的字符不代表了它本来的含义。
常见的转义字符:
\a:蜂鸣器
\b:backspace退格键
\n:换行,光标移动至下行行首
\r:光标移动至本行行首
\f:换页
\t:tab水平制表符
\\:输出\
\':输出'
\":输出"
\?:输出?
\0:NULL字符串结束标志
/*********************ASCII表与转义字符end****************************/
4、float与double
/*********************浮点数的存储***************************/
1)浮点数存储位划分
浮点数分为float类型(32位)和double类型(64位)
其中最高位是符号位,代表整个数的正负
指数部分实际存储的是移码E,E=e+127(double型中E=e+1023)。若e为正代表小数点向左移动了e位,反之e为负代表小数点向右移动了e位。
尾数部分使用科学计数法,转化成1.XXXXXXX的形式(二进制,浮点数的有效数字),但不存1和.
由此我们可以看出,尾数代表了浮点数的精度,指数代表了浮点数的范围。
2)浮点数存储方法:
示例1:2.5的浮点数存储
⒈先将该数字转化成二进制形式
2.5(十进制)----->10.1(二进制)
⒉移位。移动小数点,使之变成1.XXXXX*2^e的二进制科学计数法的形式
10.1----->1.01*2
⒊舍弃1.,将尾数部分(XXXXX)存数在浮点数的尾数位部分。注意是由高位起存储。其余位补0
01000000000000000000000(尾数)
⒋在第二步的移位过程中,小数点向左移动,因此e的值为+1,获得移码E
E=e+127=128
⒌将E转换成二进制,存储在指数位部分
128(十进制)----->1000000
⒍最后确定这个数是正数,最高位为0。
通过以上几步我们就得到了2.5的浮点数存储
2.5(十进制)----->0 10000000 01000000000000000000000(float类型)
示例2:125.5的浮点数存储
⒈先将该数字转化成二进制形式
125.5(10进制)----->1111101.1(二进制)
⒉移位。移动小数点,使之变成1.XXXXX*2^e的二进制科学计数法的形式
1111101.1----->1.1111011*2^6
⒊舍弃1.,将尾数部分(XXXXX)存数在浮点数的尾数位部分。
11110110000000000000000
⒋在第二步的移位过程中,小数点向左移动,因此e的值为+6,获得移码E
E=e+127=133
⒌将E转换成二进制,存储在指数位部分
133(十进制)----->10000101
⒍最后确定这个数是正数,最高位为0。
通过以上几步我们就得到了125.5的浮点数存储
125.5(十进制)----->0 10000101 11110110000000000000000
示例3:0.5的浮点数存储
⒈先将该数字转化成二进制形式
0.5(十进制)----->0.1(二进制)
⒉移位。移动小数点,使之变成1.XXXXX*2^e的二进制科学计数法的形式
0.1----->1.0*2^-1
⒊舍弃1.,将尾数部分(XXXXX)存数在浮点数的尾数位部分。
00000000000000000000000
⒋在第二步的移位过程中,小数点向右移动,因此e的值为-1,获得移码E
E=e+127=126
⒌将E转换成二进制,存储在指数位部分
126----->01111110
⒍最后确定这个数是正数,最高位为0。
通过以上几步我们就得到了0.5的浮点数存储
0.5(十进制)----->0 01111110 00000000000000000000000
练习1:将以下十进制数转换成浮点数存储
①17.625
②-0.75
答案:
①0 10000011 00011010000000000000000
②1 01111110 10000000000000000000000
练习2:已知以下的十六进制数均是以单精度浮点型(float)形式存储的数,求它们的实际值
例如:0x40200000----->0100000000100000000000000000000----->2.5(十进制)
①0xC1480000
②0x42F6E800
答案:
①-12.5
②123.453125

由上文我们可以看出,如果遇到无法完全转化成浮点数的小数(例如0.3这种没法完美转化成二进制数),则浮点数只能存储这个数的近似值而非实际值。因此,浮点数存储的小数不是实际值,只能是近似值。
若判断一个浮点数是否等于某数,我们通常不使用==(判等)符号,因为float类型的小数点第7位为估算数据,因此编译器认为在某个极小区间内的数字都可以视为相等。例如0.0000001,虽然最后一位为1但是由于1在小数点第7位,不属于有效位,因此编译器认定该数字仍然是0。所以,判定float类型是否是0,应使用判断这个数是否在某个极小区间内的方法。
例如:判断某浮点数f是否等于0
if(f==0)//错误
if(f<0.000001 && f>-0.000001) 或 if(fabs(f)<(1e-6))//正确
/*********************浮点数的存储end************************/
1)float类型:
单精度浮点型
大小:32位4字节
存储格式:符号位(1位)+尾数(23位)+指数(8位)
精度:小数部分最多有效7位,实际有效6位(2^23=8388608,所以是7位)
存储范围:-1.17E+38 ~ +3.40E+38(即-2^128 ~ 2^128)
打印格式:%f(十进制计数法),%e(指数计数法)
2)double类型:
双精度浮点型
大小:64位8字节
存储格式:符号位(1位)+尾数52(位)+指数(11位)
精度:小数部分最多有效16位,实际有效15位(2^52=4503599627370496,所以是16位)
存储范围:-2.22E+308 ~ +1.79E+308(即-2^1023 ~ 2^1023)
打印格式:同float类型
注意:double类型是8字节而float类型是4字节,因此double型比float型的数据更加精确,但计算速度会更慢。

思考:若有一个浮点数(float类型或double类型)以%d的方式打印,会出现什么结果?
示例:
#include<stdio.h>
int main()
{
    float a = 2.5;
    printf("%d\n",a);
    return 0;
}
我们会发现打印的是0。
而将a=2.5改成a=2.3
#include<stdio.h>
int main()
{
    float a = 2.3;
    printf("%d\n",a);
    return 0;
}
我们会发现打印的是一个很大的整数。
这是为什么?
答案:由上文我们可以发现,无论float类型还是double类型,打印格式都是%f(或%e),二者的打印格式是一样的。由此我们可以得出结论,无论float类型还是double类型,printf()函数是一视同仁的。
而float类型是4字节,double类型是8字节。因此为了方便,编译器将printf()函数需要处理的数据都转换成double类型(即float类型数据会转换成double类型,double类型不变)。
而在实际输出过程中,printf()函数是根据数据所占内存大小来读取数据的,即打印int类型的时候取4字节。
2.5从float类型转换成double类型后,低32位全部是0。而在打印的时候,printf()函数只取32位(由最低位向前)打印,会取得32个0。因此会打印出整数0。
而2.3因为无法完全转换成二进制数,因此在从float类型转换成double类型后,低32位仍有数据。在打印的时候,printf()取32位后会取得不全是0的一个数,因此会打印出一个很大的数。

3)long double类型:
大小:96位12字节(取决于编译器,有8字节、10字节、12字节、16字节几种情况)
存储格式:未知
精度:不少于double类型
存储范围:不少于double类型
打印格式:%lf,%le

猜你喜欢

转载自blog.csdn.net/qq_34427165/article/details/81175887
今日推荐