4. C语言 -- 数据类型和取值范围

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/dugudaibo/article/details/82787894

本博客主要内容为 “小甲鱼” 视频课程《带你学C带你飞》【第一季】 学习笔记,文章的主题内容均来自该课程,在这里仅作学习交流。在文章中可能出现一些错误或者不准确的地方,如发现请积极指出,十分感谢。
也欢迎大家一起讨论交流,如果你觉得这篇文章对你有所帮助,记得评论、点赞哦 ~(。・∀・)ノ゙

1. 数据类型

  在 C 语言里,数据类型即说明了它是什么类型的数据,更重要的是存储这类数据所需的内存的大小,C 语言允许使用的类型如下:
在这里插入图片描述
在基本类型中的整数类型、浮点数类型和字符类型都已经在之前的文章中使用过了,这里面的_Bool是布尔型,只能取 0 和 1 两个值;另一个是枚举类型(enum),这个类型将在后面的部分进行介绍。

  其余的数据类型,如指针类型、构造类型和空类型也将在后面的部分进行介绍。

1.1 数据类型的限定符

  • short , long, long long

  我们可以为这些基本数据类型加上一些限定符,比如表示长度的 shortlong。比如 int 经过限定符修饰之后,可以是 short intlong int,还可以是 long long int。其中 short int表示所占内存比int小的数据类型,而long int表示所占内存比int大的数据类型。

  在 C 语言并没有限制 int 的大小,更没有限制short int等带限定符的数据类型的大小,只是规定了

`short int` <= `int` <= `long int` <= `long long int`
  • signed 和 unsigned

  还有一对类型限定符是 signedunsigned,它们用于限定 char 类型和任何 int 类型变量的取值范围。signed 表示该变量是带符号位的,而 unsigned 表示该变量是不带符号位的。带符号位的变量可以表示负数,而不带符号位的变量只能表示正数,它的存储空间也就相应扩大一倍。默认所有的整型变量都是 signed 的,也就是带符号位的。

  对于 int 类型的变量来说,有四种表示长度的限定符(除int本身外,还有 shortlonglong long),在加上符号位的限定signedunsigned,所以一共存在着 8 种int 类型的变量。

1.2 sizeof 运算符

  sizeof 用于获得数据类型或表达式的长度,它有三种使用方式:

  • sizeof(type_name); //sizeof(类型),即某一种类型的变量所占内存大小;
  • sizeof(object); //sizeof(对象),即某一个对象所占内存大小;
  • sizeof object; //sizeof 对象,查看对象占用内存大小的另一种表达方式;

1.3 举例说明

  下面的程序将使用sizeof输出每一种数据类型或者每一个变量的在内存中所占的大小,具体地是使用8 种int 类型的变量进行说明。

#include <stdio.h>

int main()
{
        unsigned int a;
        signed int b;

        a = 123;

        printf("size of i is %d\n", sizeof(a));
        printf("size of int is %d\n", sizeof(unsigned int)); 

        printf("size of short int is %d\n",sizeof(short int));
        printf("size of int is %d\n",sizeof(int));
        printf("size of long int is %d\n",sizeof(long int));
        printf("size of long long int is %d\n",sizeof(long long int));

        printf("size of signed int  is %d\n", sizeof(signed int));
        printf("size of unsigned int  is %d\n", sizeof(unsigned int));      
        
        return 0;
}

  在 64 位的 Ubuntu 使用 gcc 编译执行上面的代码可以看到如下的结果
在这里插入图片描述

如上图所示,我们可以看到许多的 Warning,根据提示我们可以知道,这是由于sizeof返回的是一个long unsigned int的变量,所以使用 %d作为占位符就有可能出现溢出的现象,这里修改的方法是将上面的%d改为%ld

  分析输出的结果,通过第 1 行和第 2 行输出可以看出对于某一种数据类的变量,变量和数据类型的大小是相同的,这是很显然的;其次通过第 3 行到第 6 行可以看到,数据类型的长度满足上面的不等式short int <= int <= long int <= long long int的要求;通过最后两行可以看出,对于同一种数据类型,signedunsigned只是最高位bit的意义,数据长度不会被改变的。

  在上面给我们将有符号数赋值为负数,将无符号数赋值为整数,但是我们如果强制将无符号数赋值为负数

#include <stdio.h>

int main()
{
        short a;
        unsigned short b;

        a = -1;
        b = -1;

        printf("a is %d\n", a);
        printf("b is %d\n", b); 
        
        return 0;
}

输出的结果如下图所示

  我们可以看到无符号数 b果然没有输出对应的 -1 ,但是为什么输出 65535 呢?这就与数据类型的取值范围有关了。下面的一小节将介绍数据的取值范围。

2. 取值范围

2.1 比特与字节

  CPU能读懂的最小单位 —— 比特位,bit,b,即 0 1 两个数字;内存机构的最小寻址单位 —— 字节,Byte,B。如下图所示,为字节和比特之间的关系

因此一个字节所能存储的最大数字是二进制的11111111。那这个二进制的数字对应十进制的数字是多少呢?是不是 255 呢?这还要取决于改二进制数字的符号位。

2.2 符号位

  对于上面的 11111111,如果他对应一个无符号变量,那么与他对应的是十进制的数字255(即 2 8 1 = 255 2^8-1 = 255 )。但是对于存放signed类型的存储单元中的数据,左边第一位表示符号位。如果该位为0,表示该整数是一个正数;如果该位为1,表示该整数是一个负数。一个32位的整型变量,除去左边第一位符号位,剩下表示值的只有31个比特位。

  事实上计算机是用补码的形式来存放整数的值,其中正数的补码是该数的二进制形式,而负数的补码需要通过以下几步获得:

  (1) 先取得该数的绝对值的二进制形式,符号位置为1;
  (2) 符号位不变,将第1步的值按位取反(即将 0 都变为 1,1 都变为 0);
  (3) 符号位不变,最后将第2步的值加1。

如下图为正数 7 和负数 -7 的补码
在这里插入图片描述

  一个字节的有符号数的取值范围如下图所示
在这里插入图片描述
其中我们可以看到负数最高可以到 -128,而正数最高只能到127,这是为什么呢?主要因为 0 也占据了整数中的一部分,所以导致正数最高只能到127。

  现在 1.3 中的问题就很好解释了,-1 的补码是 11111111,这个数据正好对应着无符号数中的 65535,所以将 -1(也就是 11111111)赋值给无符号数 b 之后输出的是 65535。

2.3 基本数据类型的取值范围

  基本数据类型的取值范围如下面的两张图所示,一张图主要是字符型和整数型,另一张图主要是小数型。
在这里插入图片描述
在这里插入图片描述

2.4 举例说明

  下面是一个通过 “计算指数值” 的程序来说明取值范围这一概念,如下所示

#include <stdio.h>
#include <math.h>

int main()
{
        int result = pow(2,32) - 1;
        printf("result is %d\n", result); 
        
        return 0;
}

在Ubuntu16.04下面使用 gcc 编译执行可以使用下面这条命令

gcc -lm tmp.c && ./a.out

其中的 lm 表示表式我们使用了<math.h> 这个头文件,&&省略了原本的 -o 的操作,此时生成的可执行文件名为 a.out,通过上面的语句进行编译执行得到如下的结果
在这里插入图片描述
  可以看到 gcc 给出了 Warning 中指出了常量转换溢出(overflow),然后我们可以验证一下上面给出的结果是否正确。通过计算器可以知道 2 32 1 2^{32} -1 的正确结果是 4294967295,与上面给出的结果不符。

  出现这个的问题在于,在默认情况下 int 为有符号型,所以第一位是符号位,不能用来存放数字,所以如果我们将 32 位都拿来存放数字很容易溢出的现象。所以这个时候可以将 result 声明为 unsigned int,如下所示

#include <stdio.h>
#include <math.h>

int main()
{
        unsigned int result = pow(2,32) - 1;
        printf("result is %u\n", result); 
        
        return 0;
}

这时的结果为 4294967295 ,即正确答案。但是细心的同学也可以发现我们不仅仅将 int 修改为 unsigned int,在 printf 语句中也将占位符 %d 改变为 %u,因为此时的 result 是一个无符号数。

参考

[1] “小甲鱼” 视频课程《带你学C带你飞》【第一季】P6
[2] “小甲鱼” 视频课程《带你学C带你飞》【第一季】P7

欢迎大家关注我的知乎号(左侧)和经常投稿的微信公众号(右侧)
在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/dugudaibo/article/details/82787894