第3章 数据和C

发布于:2025-05-11 ⋅ 阅读:(17) ⋅ 点赞:(0)

3.1 示例程序

3.2 变量与常量数据

    有些数据可以在程序使用之前预先设定并在整个运行过程中没有变化,这称为 常量;另外的数据在运行过程中可能变化或被赋值,这称为 变量。变量与常量的区别在于,变量的值可以在程序执行过程中变化与指定,而常量不可以。




3.3 数据:数据类型关键字

计算机需要一种方法来区分和使用这些不同的类型。如果是常量数据, 编译器 一般通过其书写来辨认其类型,比如:42是整数,而42.00是浮点数。变量则需要在声明语句中指明类型。
(1)将“编译器”标识出来是因为, 以上是书写源码时这么区分数据类型,编译器以同样方式识别数据的类型,然后编译成汇编码
(2)在汇编码中区分数据类型,主要区分整型和浮点型数据, 是通过寄存器。整型数据存到通用寄存器(%rax%eax),用常用指令处理;浮点型数据存到SSE 寄存器(类似%xmm0),通过SSE指令处理
(3) 在二进制目标文件中,整数和浮点数的区别,在于它们的存储方式的不同。浮点数表示法将一个数分为小数部分和指数部分分别存储。 因为任何区间比如(1.0和2.0)之间都有无穷多个实数,所以计算机浮点数不能表示区域内的所有值。浮点数往往只是实际值的近似

术语位、字节、字的简单概述
(1)位是计算机存储的基本单位。
(2) 字节是常用的计算机存储单位。也是内存最小寻址单位。几乎对于所有机器,1个字节均为8位。 C语言中对“字节”的定义有些区别。C语言把一个字节定义为char类型所用的位数。char类型所用的位数又由计算机采用的字符集决定。我们通常都是支持ASCII字符集,char只需8位就能表示该字符集,那么1字节=8位;有些字符集需要16位或32位才能表示,那么1字节=16位或32位。一般我们默认1字节=8位,C语言注意一下即可,不知道涉及其它国家字符编程时会不会因为没注意这个细节导致错误。
(3)对于一种给定的计算机设计中,字是自然的存储单位。 “字”(Word)的位数取决于具体的硬件架构和上下文,字的位数随着科技的发展,硬件的更新在变化



3.4 C数据类型

3.4.1 int类型

    ISO/ANSI C规定int类型值的最小范围是-32768到32767(16位表示范围)。C标准对各种数据类型的规定都是一个最小取值范围,一个数据类型具体的字节数量还要看硬件平台、操作系统、编译器的实现。硬件平台的编译器,编译时为数据类型分配该平台支持的类型大小

  • 一、声明int变量
  • 二、初始化变量
  • 三、int类型常量 C把不含小数点和指数的数当作整数,把大多整数常量看作int类型。如果整数特别大,则有不同的处理
  • 四、打印int值 必须确保格式说明符的数目同待打印值得数目相同。打印时以格式说明符得数目为准,格式说明符只有一个,那就打印一个,即使还有待打印值,也不打印;格式说明符不止一个,待打印值只有一个,那么会打印内存中得任意值。参考printf()函数。
  • 五、八进制和十六进制 书写时前缀0(零),表示用八进制,前缀0x或0X表示使用十六进制。
  • 六、显示八进制和十六进制 C允许用3种进制书写数字,也允许以这三种进制显示数字。要用八进制而不是十进制显示整数,用%o代替%d,要显示十六进制整数,请用%x。如果想显示C语言前缀,可以使用说明符%#o、%#x或%#X分别生成0、0x和0X前缀

3.4.2 其它整数类型

C提供3个复数关键字修饰基本的整数类型:short、long和unsigned。以下几点简单说明:
(1) C标准对这些整型数据只规定了最小取值范围,具体的字节数由实现决定
(2)在C90标准中,还允许 unsigned long int(简写为unsigned long)和unsigned short int(简写为unsigned short)类型。C99又增加了unsigned long long int(简写为unsigned long long)类型
(3)关键字signed可以和任何有符号类型一起使用,它使数据的类型更加明确。
  • 一、声明其他整数类型

  • 二、使用多种整数类型的原因

    • 在long类型大于int类型的系统中,使用long类型会减慢计算,所以没有必要时不要使用long类型。如果在long类型等于int类型的系统中编写代码,当确实需要32位整数时,应使用long类型(而不是int类型),以便使程序被移植到16位机器上后仍然可以正常工作
    • 使用short类型可以节省存储空间。只有当程序使用了使系统可用内存很紧张的较大整数数组时,节省存储空间才是重要的。
    • 使用short类型的另一个原因是计算机中的一些硬件寄存器是16位的
  • 三、long常量和long long常量

    • 常量通常被视为int类型。如果值过大,编译器会适用unsigned int,如果不够大,编译器会依次试用long、unsigned long、long long和unsigned long long类型
    • 如果希望把一个较小的常量作为long类型对待,可以使用l(小写L)或L后缀 。l和L后缀对八进制和十六进制数同样适用
    • 支持long long类型的系统中,可以使用ll或LL后缀标识long long类型值。u或U后缀用于标识unsigned long long类型值,比如5ull、10LLU。
  • 四、打印short、long、long long和unsigned类型数

    • 尽管C中常量后缀可以使用大写和小写,但格式说明符只能使用小写字母
    • short类型变量作为参数传递时,会自动转换为int类型传递。因为int类型被认为是计算机处理起来最方便有效的整数类型,所以在short类型和int类型长度不同的系统中,使用int类型值进行参数传递的速度最快
    • 在C语言中,八进制和十六进制本身只是整型数据的表示方式,它们可以用于表示有符号(signed)和无符号(unsigned)整型数据,但具体解释方式取决于变量的类型和上下文使用
      • 字面值默认是有符号的(除非加 U 后缀)。
      • 格式说明符(如 %o、%x)在输出时按无符号解释数据(负数的补码会被直接显示)。
      • 在赋值或计算时,符号性由变量类型决定。

常用十进制整型数据打印格式说明符:

有符号整型 格式说明符 无符号整型 格式说明符
int %d unsigned %u
short %hd unsigned short %hu
long %ld unsigned long %lu
long long %lld unsigned long long %llu

3.4.3 使用字符:char类型

    C把一个字节定义为char类型所用的位数。char类型所用的位数,由该系统支持的字符集决定。如果支持的是ASCII码字符集,8位就可以表示,则1字节=8位。如果支持的字符集需要16位或32位表示,则认为1字节=16位或32位。注意以上只是C语言标准对C的规定,我们通常都认为1字节=8位。大部分都是支持ASCII码的,不知道在其他国家的C开发中,有没有因为C标准对字节的规定,需要对char、字节特殊处理的。

  • 一、声明char变量
  • 二、字符常量及其初始化 C将字符常量视为int类型而非char类型。例如 char grade='B’中,'B’作为数值66存储在一个32位单元中,而赋值后的grade则把66存储在一个8位单元中
  • 三、非打印字符
    • C提供的表示字符的三种方法:
      • 第一种:使用ASCII码。 char beep= 7;
      • 第二种:使用特殊的符号序列,即转义序列。char nerf=‘\n’; “\0oo和\xhh”是ASCII码的专用表示方法,对于转义序列的八进制表示法\0oo中的0可以省略
      • 第三种:从C90开始,C提供了 第三种选择,即使用十六进制形式表示字符常量,反斜线后面跟一个x或X,再加上1到3位十六进制数。char name=‘\x41’;
  • 四、打印字符 打印时的格式说明符%c
  • 五、有符号还是无符号 根据C90标准,C允许在关键字char前使用signed和unsigned。无论默认的char类型是什么,signed char是有符号类型,而unsigned char则是无符号类型。

3.4.4 _Bool类型

    _Bool类型由C99引入,用于表示布尔值,即逻辑值true与false。因为C用值1表示true,用值0表示false,所以_Bool类型实际上也是一种整数类型。


3.4.5 可移植的类型:inttypes.h

  • C语言中对整数类型规定了一个最小值的范围,比如,知道一个变量是int类型并不能告诉我们它有多少位。C标准对int类型规定了最小取值范围-32768到32767(16位表示范围)。根据硬件平台和系统的不同,对整数类型的实现位数也是不同的。如果你想了解一个硬件平台相关类型的具体位数,可以查看系统文档
  • C99标准引入了inttypes.h头文件。该头文件通多关键字typedef创建一些类型:确切长度类型(形如:uint32_t)、最小长度类型(形如:int_least8_t)、最快最小长度类型(形如:int_fast8_t)。这些类型提供了更直观的位数,增强了代码可移植性。实现这些类型会根据硬件平台和系统的不同(比如,不同的平台基本类型int有些是16,有些是32位),而不同。
  • 编译器厂商会依据开发的编译器适用平台的不同、实现C标准的不同,进而对自己包含的inttypes.h头文件中实现类型的方法微调,但是对外的接口是一致的(不同编译器厂商的inttypes.h中创建的类型的名字是一致的,可能实现这些类型采用的基本类型会不同,比如int32_t有些用int实现,有些用long实现)。

3.4.6 float、double和long double类型

C标准对float、double和long double类型的规定:
(1) C标准规定,float类型必须至少能表示6位有效数字,取值范围至少位10-37到10+37。通常,系统使用32位存储一个浮点数。其中8位用于表示指数及其符号,24位用于表示非指数部分(称为位数或有效数字)及其符号。
(2) C标准规定,double类型和float类型具有相同的最小取值范围要求,但它必须至少能表示10位有效数字。一般,double使用64位而不是32位长度。一些系统将多出的32位全部用于尾数部分,增加了数值的精度并减小舍入误差。其他一些系统将其中的一些位分配给指数部分,以容纳更大的指数,增加可以表示的数的范围。每种分配方法都使数值至少具有13位有效数字,超出了C的最小标准规定。
(3)C提供了第三种浮点类型long double类型。 C只保证long double类型至少同double类型一样精确
  • 一、声明浮点变量
  • 二、浮点常量
    • 书写浮点常量有多种选择。一个浮点常量最基本的形式是:包含小数点的一个带符号的数字序列,接着是字母e或E,然后是代表10的指数的一个有符号值。可以省略正号。可以没有小数点(2E5)或指数部分(19.28),但是不能同时没有二者(整型常量才同时没二者)。可以省略小数部分(3.E16)或整数部分(.45E-6),但是二者不能同时省略(那样做会什么也不会剩下)。
    • 浮点常量中不要使用空格。形如:1.56 E+12 是错位的
    • 默认情况下,编译器将浮点常量当作double类型。可以通过f或F后缀使编译器把浮点常量当作float类型,l或L后缀使一个浮点常量成为long double类型。建议使用L,l和1容易混淆。
    • C99为表示浮点常量新添加了一种十六进制格式。这种格式使用前缀0x或0X,接着是十六进制数字,然后是p或P(而不是e或E),最后是2的指数(而不是10的指数),如下表示:0xa.1fp10
  • 三、打印浮点值
    • printf()函数使用%f格式说明符打印十进制计数法的float和double数字,用%e打印指数计数法的数字。如果系统支持C99的十六进制格式浮点数,用%a或%A打印。打印long double类型的浮点数需要%Lf、%Le和%La说明符。
    • 注意float和double类型的输出都使用%f、%e或%a说明符。这时由于当它们向那些未在原型中显示说明参数类型的函数(如printf())传递参数时,C自动将float类型的参数转换为double类型
  • 四、浮点值的上溢和下溢
    • 当计算结果是一个大得不能表达的数时,会发生上溢。对这种情况得反应原来没规定,但是现在得C语言要求为toobig赋予一个代表无穷大得特殊值,printf()函数显示此值为inf或infinity(或这个含义的其他名称)
    • 对浮点数可表示精度范围内最小的值,除以2,这个操作将使指数部分减小,但是指数已经达到最小值,所以计算机只能将尾数部分的位进行右移,空出首位二进制位,并丢弃最后一位二进制值,此过程称为下溢。C将损失了类型精度的浮点值称为低于正常的(subnormal),所以把最小的正浮点数除以2将得到一个低于正常的值。如果除以一个足够大的值,将使所有的位都位0。现在C库提供了用于检查计算是否会产生低于正常的值的函数

3.4.7 复数和虚数类型

    简单的讲有三种复数类型,分别是float_Complex、double_Complex和long double_Complex。有3种虚数类型,分别是float_Imaginary、double_Imaginary和long double_Imaginary。

3.4.8 其它类型

    C没有字符串类型,但是它可以很好的处理字符串。C从基本类型中衍生出其他类型,包括数组、指针、结构和联合。

3.4.9 类型大小

    C的内置运算符sizeof以字节为单位给出类型的大小。




3.5 使用数据类型

    当为某个数值类型的变量进行初始化时,如果使用了其他类型的值,C会自动对该值进行类型转换以便和变量类型相匹配。一般都是对默认的常量类型进行隐式转换。




3.6 参数和易犯的错误

  • C用逗号隔开函数调用中的多个参数。printf()和scanf()函数比较特殊,其参数数目可以不受限制,这两个函数通过第一个参数,确定后续参数的个数,方法是第一个参数字符串中的每个说明符对应后面的一个参数
  • 现在C通过一种函数原型机制检查函数调用是否使用了正确数目及类型的参数,但是这对printf()和scanf()函数不起作用,因为它们的参数数目是变化的。
  • 使用%d显示float值,不会把该float值转换为近似的int值,而是显示垃圾值。与之类似,使用%f显示int值也不会把该int值转换为浮点值。这里与3.5节介绍的初始化时将一个double类型的浮点值常量截尾赋值给一个int类型变量的情况不同。3.5节初始化截尾的操作是编译器进行的,截尾后以整型数据保存到内存,自然也就可以以整型的形式打印,只是损失了精度,不会产生垃圾值;而这里是以浮点型数据保存进内存,再以整型数据打印,整型和浮点型数据在内存中结构完全不同,所以这里的打印会产生毫无作用的垃圾值



3.7 另一个例子:转义序列

  • C的一些专用转义字符。退格(\b)制表符(\t)回车符(\r)。通常,退格字符不删除退回时所经过的字符,但有些实现是删除的
  • printf()函数什么时候真正把输出传送给屏幕?首先,printf()语句将输出传递给一个被称为缓冲区的中介存储区域。缓冲区中的内容再不断地被传递给屏幕。标准C规定在以下几种情况下将缓冲区内容传给屏幕:缓冲区满的时候、遇到换行符的时候以及需要输入的时候。将缓冲区内容传递给屏幕或文件称为刷新缓冲区



3.11 编程练习

    前面的章节都是介绍基础知识的,所以编程练习很简单,个人觉得有价值的只有这章的第一个练习,其他的有手就行。

#include <stdio.h>
#include <float.h>
//#include <stdint.h>

int main(void)
{
	float f_max=FLT_MAX;
	float f_min=FLT_MIN;

	printf("f_max  %f  %e\n",f_max,f_max);		//float型浮点数的最大值
	/*乘10之后上溢,小数形式输出#INF00,指数形式输出#INF00e+000  C语言用INF表示上溢,无穷大*/
	printf("f_max*10  %f  %e\n",f_max*10,f_max*10);	
	// float型浮点数的最小值   小数形式输出0.000000  指数形式输出1.175494e-38
	printf("f_min %f  %e\n",f_min,f_min);	
	//除10后,开始下溢,C语言中下溢的过程是丢失精度的过程 小数形式输出0.000000  指数形式输出1.175494e-38
	printf("f_min/10  %f  %e\n",f_min/10,f_min/10);	
	//除以100,继续下溢  小数形式输出0.000000  指数形式输出1.175494e-40   下溢到一定的程度从规约数编程非规约数,最后内存中全0
	printf("f_min/100 %f  %e\n",f_min/100,f_min/100);
	//除以100000,继续下溢  小数形式输出0.000000  指数形式输出1.175494e-40
	printf("f_min/100000  %f  %e\n",f_min/100000,f_min/100000);
	//除以10000000,继续下溢  小数形式输出0.000000  指数形式输出1.401298e-45
	printf("f_min/10000000 %f  %e\n",f_min/10000000,f_min/10000000);
	/*除以100000000,继续下溢  小数形式输出0.000000  指数形式输出0.000000e+000   此时下溢丢失精度到全零,
	下溢过程中前面几次,只有小数形式对float舍入输出0.000000,从指数形式来看内存中float占用的4字节中还有位是1
	下溢到此刻,精度全丢失,从指数形式的输出来看,我本来猜测应该内存中全为0了,但我通过指针打印出来的不是全为0,
	查deepseek得知:可以通过一直除,一直下溢到内存中全为0,通过循环实现,我这里就不写了*/
	printf("f_min/100000000 %f  %e\n",f_min/100000000,f_min/100000000); 
	//除以1000000000,继续下溢  小数形式输出0.000000  指数形式输出0.000000e+000
	printf("f_min/1000000000 %f  %e\n",f_min/1000000000,f_min/1000000000);


	return 0;
}     

    我在写上面这段代码时,查阅了些资料,需要对浮点数的IEEE 754标准简单了解。浮点数在内存中的组成是:符号位、指数位和尾数位。尾数位决定了浮点数的精度,指数位决定了浮点数的范围所谓上溢下溢就是超出了浮点数的表示范围,那就主要针对指数位来讨论这个问题。而且浮点数的上溢下溢讨论的都是浮点数绝对值的上溢下溢。负浮点数和正浮点数的上溢下溢是一样的
    我在测试时很艰难的找到了浮点数边界值,参照整型的测试方式,让浮点值的最大值加0.1,但完全没变化。查了deepseek才参透了要针对指数位操作才能有明线变化,然后才有了乘10的操作,如果用加法就得加个很大的浮点数才行。有兴趣自己测吧。
    浮点数出现上溢后指数位全1,这就是IEEE 754标准中的特殊值,C中用INF表示代表上溢无穷大。
    下溢的本质:数值过小,超出规约数表示范围,被迫牺牲精度或归零。处理下溢的方式:非规约数(渐进下溢)或直接归零(FTZ),取决于硬件和配置。我们这里的处理方式就是采用非规约数,渐进式下溢,知道最后全为0。简单的或,下溢就是非规约数和规约数的界限。

IEEE 754标准对浮点数的规范的一点理解:
(1)浮点数可分为规约数(大部分浮点数都是)、非规约数(下溢,无线接近0)、特殊值(上溢INF无穷大)。
(2)根据浮点数的指数位来区分属于哪种,指数位不全为0也不全为1是规约数,指数位全为0是非规约数,指数位全为1是特殊值。
(3)浮点数的指数位用移码表示,float类型的浮点数,移码的偏移值为127, float类型的变量的最小指数是-126,得出的-126的方法是移码的最小值是1,1减去偏移值127就得到真实的float类型变量最小的指数-126。我原来认为是-128,因为补码比较常用,看到float类型的指数位共8位,补码方式能表示的最小值就是-128,习惯性认为就是-128了,要注意

网站公告

今日签到

点亮在社区的每一天
去签到