C语言笔记之数据类型(一)

《计算机眼里的数字》这篇文章中,我曾提到,字节是计算机最小的可寻址的单位,地址对应的是一个个字节,而不是字节的每个位。这样编址的原因很简单——单个的位所能表示的信息量太少了,只有两种状态0和1,只有把足够多的位组合起来才能表示足够丰富的信息。那么为什么一定要是8呢?因为大家都这么做。。。好吧,肯定一定的历史原因,我就不深究了。

但是,即使是8个位组成的字节,其所能表示的信息量仍然是有限的,因为最多只有256中状态组合,如果用每种状态对应0~255之间的数字的话,那么就无法表示256这个数。这时候只好用两个字节来表示大于255的数。同样的道理,当数字大到超过两个字节所能表示的数的极限后,就用四个字节。。。是的,你也发现了,字节数目总是翻倍增长,为什么不用3个字节呢?甚至,为啥不用1.5个字节呢?对于前者,是因为考虑到对齐的原因,这其中有太多东西要说,我就不展开了;而后者,前面其实说到了,因为没有位的编址,无法深入到字节内部把数据揪出来。。所以,存储肯定会有一定的浪费,在所难免。

上面罗嗦了那么多,其实只是为了引出本文的猪脚——C语言数据类型,为什么C语言要分那么多类型呢?因为对于不同大小的数,所需要的存储空间大小不同。如果都用4个字节存储,那么肯定不用分数据类型,但是好浪费哦~所以,本着节省内存的考虑,数据类型就诞生了。C的数据类型分为基本数据类型和复合数据类型,后者只是前者的某种组合。基本数据类型按照其在计算机中的存储方式又分为整数类型和浮点数类型。

一、整数类型

整数类型包括char、short、int、long、long long,它们没有小数部分。char虽然是字符类型,但是由于存储的是ASCII码,本质上也是按整数存储的,所以归为这一类。不同的整数类型具有不同的字节数,从而所占用的存储空间不同。然而,这一点是不确定的,准确的字节数依赖于具体的机器和编译器——机器不仅分品牌,还有32位和64位之分。下面的表格给出不同类型所占有的典型的字节数(来源《深入理解计算机系统》):

类型 32位机器 64位机器
char 1 1
short 2 2
int  4 4
long 4 8
long long 8 8
char * 4 8
float 4 4
double 8 8

注意,上表只是典型值,以32位为例:在有的机器上,short和int都是2个字节,long是4个字节;而有的机器上,short是2个字节,而int和long是4个字节。C语言仅仅规定:short <= int <= long,然后char是1个字节。不过,对于大部分机器,上表够用了。要查看自己机器上每种类型所占的字节数,请用sizeof(类型)来查看。

对于整数类型,C还用了signed和unsigned来修饰它们,以获得有符号数和无符号数,缺省时,认为是有符号数。正如在《计算机眼里的数字》中提到的那样,有符号数和无符号数的二进制表示可能是一样的,区别仅仅是解读方式。

有了各种类型之后(即字节数确定之后),就可以规定它们所表示的数的范围。下表是32位机器各种类型的典型取值范围:

  signed unsigned
char -128 ~ 127 0 ~ 255
short -32768 ~ 32767 0 ~ 65535
int  -2147483648 ~ 2147483647 0 ~ 4294967295
long -2147483648 ~ 2147483647 0 ~ 4294967295
long long -9223372036854775808 ~ 9223372036854775807 0 ~ 18446744073709551615

这些界限值可以通过包含limits.h头文件加以查看,比如对于int值的各种范围,可以通过打印INT_MAX  INT_MIN

UINT_MAX UINT_MIN 来查看。

我们用几个例子来看一下相同类型之间signed和unsigned的转换。

1、将signed强制转换成unsigned:

<span style="font-size:18px;">#include <stdio.h>

int main(void)
{
    int a = -1;
    unsigned int b = (unsigned int)a;

    printf("a = %d, b = %d\n", a, b);
    printf("a = %u, b = %d\n", a, b);
    printf("a = %d, b = %u\n", a, b);
    printf("a = %#x, b = %#x\n", a, b); 

    return 0;
}
</span>

运行结果如下:

<span style="font-size:18px;">a = -1, b = -1
a = 4294967295, b = -1
a = -1, b = 4294967295
a = 0xffffffff, b = 0xffffffff</span>

可以明显的看出,将一个负数强制转换为无符号数,并没有改变其位模式(二进制表示),它仍然按照原来的模样存储,第四行的结果证明了这一点;而前三行的结果表明,即使不做signed到unsigned的强制类型转换,只需要在打印时改变一下输出格式,就能达到同样的效果。(这里我开始怀疑把一个变量声明为unsigned有啥意义?)

而将一个有符号的正数转换为同类型的无符号数又如何呢?

2、将unsigned强制转换成signed:

<span style="font-size:18px;">#include <stdio.h>

int main(void)
{
    unsigned short a = 32767;
    short b = (short)a;

    printf("a = %u, b = %u\n", a, b);
    printf("a = %u, b = %d\n", a, b);
    printf("a = %d, b = %u\n", a, b);
    printf("a = %#x, b = %#x\n", a, b); 

    return 0;
}
</span>

结果如下:

<span style="font-size:18px;">a = 32767, b = 32767
a = 32767, b = 32767
a = 32767, b = 32767
a = 0x7fff, b = 0x7fff
</span>

可以看出,当一个非负数处于0~TMax(TMax代表某一类型有符号数的最大值)的范围时,无论把它的二进制表示解读成signed还是unsigned,结果都是一样的。这是因为,处于这部分范围内的数,转换成二进制时,最高为都为0,如果按signed解读,它是一个正数,数字部分就是7fff,;而按unsigned解读,07fff == 7fff,结果总是一样的。

那如果正数的范围超过了TMax呢?

<span style="font-size:18px;">#include <stdio.h>

int main(void)
{
    unsigned int a = 2147483648u;
    int b = (int)a;

    printf("a = %u, b = %d\n", a, b);
    printf("a = %d, b = %u\n", a, b);
    printf("a = %#x, b = %#x\n", a, b); 

    return 0;
}
</span>

结果:

<span style="font-size:18px;">a = 2147483648, b = -2147483648
a = -2147483648, b = 2147483648
a = 0x80000000, b = 0x80000000</span>

可以看到,底层的二进制表示仍然是一致的,只是解读方式发生了变化:由于最高位是1,所以解读成signed时,是一个负数;解读成unsigned仍然是正数,结果不同。

注意:因为变量分为signed和unsigned,对应的常量也要分为signed和unsigned;类型前没有修饰时,默认为signed,对应的,一个常量数字默认为signed,即有符号数。如果希望一个常量数字被当成无符号数,就要在其末尾添加字母‘u‘或‘U’。

时间: 2024-12-15 16:10:18

C语言笔记之数据类型(一)的相关文章

C语言笔记之数据类型(三)

浮点型数据 一.非整数的表示 除了整数,平时的计算也离不开非整数,即带有小数部分的那些数.在数字系统中,整数和非整数合称为有理数,有理数和无理数合称为实数(好吧,这和本文主旨没关系,但为了显示一下我曾经是数学系的学生...) 非整数由一个"."号来凸显,十进制表示法中,"."号左边的数字的权为10的正整数幂,幂值按离小数点的距离远近依次为0.1.2...而右边为10的负整数幂,幂值由近及远依次为-1.-2.-3... 例如:12.25 = 1 * 10 ^ 1 +

C语言笔记之数据类型(二)

接下来是不同类型之间的强制转换.当把一个高容量的类型强制转换为低容量的类型时,会发生截断:丢弃二进制的高位,只保留低位(二进制的左边为高位,右边为低位):而把低容量类型强制转换成高容量类型时,会发生扩展:在二进制的高位左边继续填充数字.扩展分为两类:零扩展和符号扩展.下面看代码示例. 1.截断 #include <stdio.h> int main(void) { short a = 257; char b = (char)a; printf("a = %u, b = %d\n&qu

C语言笔记1--类型、运算符与表达式

 C语言笔记1--类型.运算符与表达式 总论: 变量和常量是程序处理的两种基本的数据对象.声明语句说明变量的名字和类型,也可以指定变量的初值.运算符指定将要进行的操作.表达式则把变量与常量组合起来生成新的值.对象的类型决定该对象可取值的集合以及可以对该对象执行的操作. 一.变量与常量的名字 1.名字是由字母.下划线和数字组成的序列,其第一个字符必须为字母:下划线"_"被看做是字母,但一般不要以下划线"_"开头. 2.名字区分字母大小写,变量名使用小写字母.符号常量

C语言笔记

C语言笔记 基础知识 数据类型 序号 类型与描述 1 基本类型: 它们是算术类型,包括:整数类型.浮点类型 2 枚举类型: 也是算术类型,被用来定义只能使用某些整型值的变量,使用时需要程序员先使用eumn关键字来声明定义 3 Void类型: 用于函数,指明函数的返回值或参数.作用于变量会发生编译错误 4 派生类型: 包括:指针类型.数组类型.结构类型.联合体类型.函数类型 补充:1.函数类型是指函数返回值的类型,数组类型与结构类型统称为聚会类型. 2.除了基本类型,其他的类型都是程序员使用相关关

C++学习笔记之数据类型

一.变量名 几条简单的C++命名规则: 在名称中只能使用字母,数字和下划线 名称的第一个字符不能是数字 区分大小写 不能将C++关键字用作名称 以两个下划线和大写字母打头的名称被保留给实现(编译器及其使用资源)使用.以一个下划线开头的名称被保留给实现,用作全局标识符. C++对名称长度没有限制 二.数据类型 计算机内存的最基本单元是位(bit).字节(byte)通常指的是8位内存单元,可以表示的范围0-255或者-128到127. (1)整型 short至少16位(大多数系统16位,-32768

C++语言笔记系列之十八——虚函数(1)

1.C++中的多态 (1)多态性:同一个函数的调用可以进行不同的操作,函数重载是实现多态的一种手段. (2)联编:在编译阶段进行联接,即是在编译阶段将一个函数的调用点和函数的定义点联接起来. A.静态联编:在编译阶段就完成的函数联编--函数重载. B.动态联编:在程序的运行阶段由系统自动选择具体的函数--虚函数. 注:C++的多态主要指的就是动态联编. 2.虚函数 (1)虚函数是在函数的定义时将其声明为虚函数即可. (2)说明:virtual 数据类型 函数名(参数表) {函数体} A.目的:当

C++语言笔记系列之十——静态成员

1.静态成员 (1)由关键字static修饰 静态变量定义语句在编译阶段就执行,运行过程中不再执行. (2)分类:静态数据成员.静态成员函数. (3)静态成员时类的所有对象共享的成员,而不是某一个对象的成员. 2.静态成员的使用 (1)在定义说明前加上static关键字.例如: static int x: (2)静态数据成员必须进行初始化,并且初始化必须在类外完成. (3)静态数据成员的初始化 数据类型 类名::静态数据成员名 = 值://注意这里没有static出现 (4)绝对不能使用对象名来

Oracle之PL/SQL学习笔记之数据类型(三)

Oracle之PL/SQL学习笔记之数据类型(三) 所有的编程语言中变量是使用最频繁的.PL/SQL作为一个面向过程的数据库编程语言同样少不了变量,利用变量可以把PL/SQL块需要的参数传递进来,做到动态执行程序,同时也可以利用变量在PL/SQL内部进行值得传递,甚至可以把值传递出去,最终返回给用户,由此可见,变量是PL/SQL不可或缺的一部分. 1. Oracle预定义的普通数据类型(常见的数据类型) 类型 子类 说明 Oracle中的范围 char Character,String Rowi

C++语言笔记系列之十二——C++的继承

C++的继承 1.继承方式 public(公有继承) 派生类中的成员可以访问基类的public成员和protected成员,但不能访问基类的private成员. 派生类的对象只能访问基类的public成员. protected(保护继承),private(私有继承) 派生类中的成员可以访问基类的public成员和protected成员,但不能访问基类的private成员. 派生类的对象不能访问基类的任何成员. 2.例子 example 1: #include <iostream.h> clas