⚡【C语言趣味教程】(2) 整数类型:数据类型的概念 | 原码反码与补码 | 有符号型和无符类型 | 研究 signed char 与 unsigned char 的取值范围

   《C语言趣味教程》 猛戳订阅!!!

—— 热门专栏《维生素C语言》的重制版 ——

  • 写在前面:这是一套 C 语言趣味教学专栏,目前正在火热连载中,欢迎猛戳订阅!本专栏保证篇篇精品,继续保持本人一贯的幽默式写作风格,当然,在有趣的同时也同样会保证文章的质量,旨在能够产出 "有趣的干货" !本系列教程不管是零基础还是有基础的读者都可以阅读,可以先看看目录! 标题前带星号 (*) 的部分不建议初学者阅读,因为内容难免会超出当前章节的知识点,面向的是对 C 语言有一定基础或已经学过一遍的读者,初学者可自行选择跳过带星号的标题内容,等到后期再回过头来学习。值得一提的是,本专栏 强烈建议使用网页端阅读! 享受极度舒适的排版!你也可以展开目录,看看有没有你感兴趣的部分!希望需要学 C 语言的朋友可以耐下心来读一读。最后,可以订阅一下专栏防止找不到。

" 有趣的写作风格,还有特制的表情包,而且还干货满满!太下饭了!"

—— 沃兹基硕德

本章目录:

Ⅰ. 前置知识(Pre-Learning)

0x00 如何定义一个变量?

0x01 数据类型的概念

0x02 ASCII 码

* 0x03 原码、反码和补码

Ⅱ. 整数类型(Integer Types)

0x00 整型 int

0x01 短整型 short int

0x02 长整型 long int

0x03 超长整型 long long int

0x04 字符类型 char

* 0x05 整型和字符型可相互赋值

 0x06 总结:整型家族

Ⅲ. 有符号型和无符号型(signed & unsigned)

0x00 引入:数学中的正数和负数?

0x01 signed 和 unsigned 各类型的取值范围

0x02 unsigned 的特点

0x03 研究:有符号和无符号整型的取值范围

* 0x04 如何查看类型的取值范围?


Ⅰ. 前置知识(Pre-Learning)

  • 在讲解数据类型前,我们不得不先讲解一些必备的知识点,比如如何定义一个变量,数据类型的基本概念。并介绍 ASCII 码,为 char 类型的讲解做必要的铺垫。然后讲解原码反码和补码,讲解 IEEE754标准时需要这部分的知识作为基础,这一部分较难,属于星号内容。

0x00 如何定义一个变量?

​ C 语言的变量是有明确类型的,创建一个变量时需要明确该变量的数据类型。

⚡【C语言趣味教程】(2) 整数类型:数据类型的概念 | 原码反码与补码 | 有符号型和无符类型 | 研究 signed char 与 unsigned char 的取值范围_第1张图片​ 

定义方式:我们通过 类型 + 变量名 定义一个变量:

数据类型 变量名 = 值;

⚡【C语言趣味教程】(2) 整数类型:数据类型的概念 | 原码反码与补码 | 有符号型和无符类型 | 研究 signed char 与 unsigned char 的取值范围_第2张图片

❓ 还记得这个 int 是什么吗?

在第一章中,我们的 int main() 就是要求 main 函数返回一个整型,这里的 int 就是 整型 了。

#include 

int main()    函数返回值为整型
{
    printf("Hello,World!\n");

    return 0;
}

代码演示:创建一个整型变量

int a = 10; 

(我们将在下一章节详细地讲解变量,这里为了方便讲解数据类型,学会定义简单地变量即可)

0x01 数据类型的概念

* [注]  数据类型, [英] DataTypes,[繁] 資料類型

概念:在程序设计中,数据类型用来约束数据的解释。

​  数据类型是数据的一种属性,它告知编译器程序员打算如何使用数据。

数据类型定义了可以对数据执行的操作,数据的含义以及存储该类型的方式。

下图展示的是 C 语言的数据类型(本章我们将介绍下图中大部分的数据类型)

⚡【C语言趣味教程】(2) 整数类型:数据类型的概念 | 原码反码与补码 | 有符号型和无符类型 | 研究 signed char 与 unsigned char 的取值范围_第3张图片

[注] 图中打星号的类型为 C99 标准后颁布的类型(例如 bool 和 long long int)

类型的意义:

  • 内存决定开辟内存空间的大小,大小决定了使用的范围。
  • 类型决定了看待内存空间的视角。

⚡【C语言趣味教程】(2) 整数类型:数据类型的概念 | 原码反码与补码 | 有符号型和无符类型 | 研究 signed char 与 unsigned char 的取值范围_第4张图片 C 语言中只有 4 种基本的数据类型,整型浮点型指针 派生类型 

其它类型都是从以上这四种类型中派生出来的,本章我们会先介绍整型。

0x02 简单介绍一下 ASCII 码

ASCII 码,全称 American Standard Code for Information Interchange,美国信息交换标准代码。

ASCII 码介绍

ASCII 码是基于拉丁字母的一套电脑编码系统,主要用于显示现代英语和其他西欧语言。它是最通用的信息交换标准,并等同于国际标准 ISO/IEC 646。ASCII第一次以规范标准的类型发表是在1967年,最后一次更新则是在1986年,到目前为止共定义了128个字符。

⚡【C语言趣味教程】(2) 整数类型:数据类型的概念 | 原码反码与补码 | 有符号型和无符类型 | 研究 signed char 与 unsigned char 的取值范围_第5张图片

每个 ASCII 码都以 1 个字节储存,数字 0~127 分别映射不同的符号。

比如大写 A 的 ASCII 码是 65,小写 a 的 ASCII 码是 97。

ASCII 码的大小规则:数字 < 大写字母 < 小写字母

建议记住一些常见字母的 ASCII 码,"A" 为 65,"a" 为 97,"0" 为 48。

标准 ACSII 码使用 7 个 bit 位,而拓展的 ASCII 码使用 8 个 bit 位。

* 0x03 原码、反码和补码

⚡【C语言趣味教程】(2) 整数类型:数据类型的概念 | 原码反码与补码 | 有符号型和无符类型 | 研究 signed char 与 unsigned char 的取值范围_第6张图片​整数在内存中的存储方式是以二进制的形式存储的。

VS 中我们可以通过调试查看内存,可以发现 数据在内存中是以二进制的形式存储的。

⚡【C语言趣味教程】(2) 整数类型:数据类型的概念 | 原码反码与补码 | 有符号型和无符类型 | 研究 signed char 与 unsigned char 的取值范围_第7张图片

对于整数来说,内存中存储的二进制有三种表现形式,分别是 原码反码 补码

​ 正整数的原码反码和补码均相同,但是负整数的原码反码和补码就需要去计算了。

下面我们以变量 a = -10 为例,它就是一个负整数,我们用它来讲解计算规则。

原码:按照数据的数值直接写出的二进制序列。

符号位 (sign bit):最高位是 0 表示正数,最高位是 1 则表示负数。

⚡【C语言趣味教程】(2) 整数类型:数据类型的概念 | 原码反码与补码 | 有符号型和无符类型 | 研究 signed char 与 unsigned char 的取值范围_第8张图片

这里我们可以看到 -10 的最高位是 1,这个 1 就是符号位了,表示它是一个负数。

反码:原码的符号位不变,其它位一律按位取反,即为反码。

补码:反码 +1,得到的就是补码。

Ⅱ. 整数类型(Integer Types)

  • "我们刚才说过,C 有四种数据类型,分别是整型、浮点型、指针和派生类型,我们先来介绍整型。整型包括 int, short, long, char…… 我们都将逐个详细介绍。随后我们讲解有符号和无符号,探讨它们的数据范围。" 

0x00 整型 int

⚡【C语言趣味教程】(2) 整数类型:数据类型的概念 | 原码反码与补码 | 有符号型和无符类型 | 研究 signed char 与 unsigned char 的取值范围_第9张图片​ 整数类型中,最具代表性的就是 基本整型 int 了。

在第一章中,我们的 int main() 就是要求 main 函数返回一个整型,这里的 int 就是 整型 了。

整型占 4 个字节,我们可以用它来定义一个整型变量:

int 变量名 = 值; 

这样我们就定义了一个 int 型变量 age,它的值为 10

int 类型的取值范围为 ,即 -2,147,483,648 ~ 2,147,483,647

int 类型可以使用格式说明中的 %d 来打印:

#include 

int main(void)
{
    int a = 100;
    printf("%d", a);

    return 0;
}

运行结果:100

0x01 短整型 short int

⚡【C语言趣味教程】(2) 整数类型:数据类型的概念 | 原码反码与补码 | 有符号型和无符类型 | 研究 signed char 与 unsigned char 的取值范围_第10张图片​ 有一种比 int 要小的整型,叫 短整型,用 short int 表示,可简写为 short

short int 变量名 = 值;
short 变量名 = 值;      // 可简写为 short

短整型占 2 个字节,如果我们需要表示的数比较小,我们就可以使用 short,节省系统资源。

short 在 32 位系统中以 2 个字节表示,每个字节为 8 个二进制比特,一共 16 个比特。

​16 个比特能表示的个数为 0 ~ 65535,共 65536 个,若正负整数各一半,那么: 

short 的取值范围为 ,即 -32767 ~ 32767,共 65536 个。

short 类型的格式化字符为 %hd,可以按 short 形式打印:

#include 

int main(void)
{
    short a = 100;
    printf("%hd", a);

    return 0;
}

运行结果:100

0x02 长整型 long int

⚡【C语言趣味教程】(2) 整数类型:数据类型的概念 | 原码反码与补码 | 有符号型和无符类型 | 研究 signed char 与 unsigned char 的取值范围_第11张图片​ 对应的,还有一种比 int 要大的整型,叫 长整型,用 long int 表示,可简写为 long

long int 变量名 = 值;
long 变量名 = 值;       // 可简写为 long

也可以用来表示一个整数,long 能表示的数不一定是大于 int 的,但一定不会小于 int 类型范围。

因此,long 的取值范围大于等于 int 的取值范围。

长整型占 4/8 个字节:长整型在 32 位系统下占 4 个字节,但在 64 位系统下占 8 个字节。

取值范围为 ,即 -2,147,483,648 ~ 2,147,483,647

long 类型可以使用格式说明中的 %ld 来打印,表示打印长整数:

#include 

int main(void)
{
    long int a = 100;
    printf("%ld", a);

    return 0;
}

运行结果:100

注意:long int 是 C99 的。

0x03 超长整型 long long int

" 十年 IO 一场空,不开 long long 见祖宗。"

⚡【C语言趣味教程】(2) 整数类型:数据类型的概念 | 原码反码与补码 | 有符号型和无符类型 | 研究 signed char 与 unsigned char 的取值范围_第12张图片​还有比 long 还要大的整型!long long int超长整数,可简写为 long long

long long int 变量名 = 值;
long long 变量名 = 值;     // 可简写为 long long

说实话,long long 让我想到了 "Let me do it for you" 的长鼻狗:

⚡【C语言趣味教程】(2) 整数类型:数据类型的概念 | 原码反码与补码 | 有符号型和无符类型 | 研究 signed char 与 unsigned char 的取值范围_第13张图片

表示的范围比 long int 还要大,占 8 个字节,大到令人瞠目结舌的 [-2^{63},\, 2^{63}-1] ,即:

-9223372036854775807 ~ 9223372036854775807

珍妮马大啊…… 值得注意的是,long long 不是所有编译器都使用的。

long long 类型可以使用格式说明中的 %lld 来打印,表示打印超长整数:

#include 

int main(void)
{
    long long int a = 1300300100;
    printf("%lld", a);

    return 0;
}

运行结果:1300300100

0x04 字符类型 char

⚡【C语言趣味教程】(2) 整数类型:数据类型的概念 | 原码反码与补码 | 有符号型和无符类型 | 研究 signed char 与 unsigned char 的取值范围_第14张图片​在 C 语言中,单个字符的表示使用 字符型 来表示。

我们可以使用 char 来定义一个字符类型:

char 变量名 = 值;

char 也可以看作是整型,因为 char 的本质就是一定范围的 int

(char 实际上是英文单词 character 的缩写)

其存储大小为 1 字节,char 的取值范围为 [-2^7,\, 2^7-1],即 -128 ~ 127

我们可以使用 char 类型来表示一个字符:

char a = 'a';
char b = '1';
char c = 'C';

" 什么?char 居然算整型?!"

❓ 思考:为什么 char 算整型?因为 char 底层存储的是 ASCII 码值。

ASCII 码值是整数,因此在归类时会把 char 类型归结到整型类型中。

注意:char 类型只能用 单引号 ' ' 来表示,而不能用双引号表示:

char ch = 'a';   ✅
char ch = "a";   ❌

并且,char 内也是支持空格和转义字符的:

char d = '\63';   // ASCII 码中对应字符为 ?
char e = ' ';     // 空格

char 类型可以使用格式化符 %c 来打印:

#include 

int main(void)
{
    int ch = 'a';
    printf("%c", ch);

    return 0;
}

运行结果如下:c

* 0x05 整型和字符型可相互赋值

⚡【C语言趣味教程】(2) 整数类型:数据类型的概念 | 原码反码与补码 | 有符号型和无符类型 | 研究 signed char 与 unsigned char 的取值范围_第15张图片

int 型和 char 型变量可以相互赋值,它们是互通的,int 和 char 在内存中存储的本质是相同的。

只是存储的范围不同而已,整型可占 2/4/8 个字节(对应 short, int, long),但 char 只占 1 字节。

" 你可以认为 char 是大蛋糕 int 里切下来的一块小蛋糕"

​因此我们可以使用 %d 打印字符类型变量:

char ch = 'a'        // 字符常量的值在单引号内
printf("%c", ch);    // a
printf("%d", ch);    // 97

如果用 %d 整数形式输出,则结果为 97,对应了 ASCII 码中的 a。

 0x06 总结:整型家族

" 画的可谓是非常的形象……"

⚡【C语言趣味教程】(2) 整数类型:数据类型的概念 | 原码反码与补码 | 有符号型和无符类型 | 研究 signed char 与 unsigned char 的取值范围_第16张图片

整型家族有 整型 int,字符型 char,短整型 short,长整型 long,超长整型 long long

❓ 思考:长整型的值一定要比短整型表示的值要大吗?

不一定,因为标准并没有规定长整型必须比短整型长,只是规定它不能比短整型短。

标准只是规定了 int 至少要和 short 一样长,long 至少要和 int 一样长。

short\leq int\leq long

Ⅲ. 有符号型和无符号型(signed & unsigned)

0x00 引入:数学中的正数和负数?

" 整型都分为有符号和无符号两个版本。"

—— 《C和指针》

这里的概念类似于数学中正数和负数的概念,数学中正数用加号 (+) 表示,负数用减号 (-) 表示。 

而 C 语言和这个类似,其整型是有两种类型的,即 有符号型 signed 和 无符号型 unsigned 

⚡【C语言趣味教程】(2) 整数类型:数据类型的概念 | 原码反码与补码 | 有符号型和无符类型 | 研究 signed char 与 unsigned char 的取值范围_第17张图片

定义有符号变量时,修饰符 signed 可以省略,默认就是有符号:

signed 类型 变量名 = 值;
类型 变量名 = 值;          // 默认就是 signed

定义无符号变量时,修饰符 unsigned 不可省略:

unsigned 类型 变量名 = 值;

没有显式声明是 signed 还是 unsigned 则默认为 signed没有则声明默认为有符号型。

signed\, \, int \equiv int

⚡【C语言趣味教程】(2) 整数类型:数据类型的概念 | 原码反码与补码 | 有符号型和无符类型 | 研究 signed char 与 unsigned char 的取值范围_第18张图片​  所以我们可以这么理解:数学中加号是可以省略的,比如:+10\rightarrow 10

这里声明 a 为有符号数 (signed) 整型,也自然是可以省略的:

signed int a = 10;
int a = 10;          // 等价

代码演示:定义各种类型的有符号和无符号类型

signed int a = 0;      // 有符号整型
unsigned int b = 0;    // 无符号整型

signed short c = 0;    // 有符号短整型
unsigned short d = 0;  // 无符号短整型

signed long e = 0;     // 有符号长整型
unsigned long f = 0;   // 无符号短整型

...

unsigned 类型可以使用格式化符 %u 来打印:

#include 

int main(void)
{
    unsigned int a = 10;
    printf("%u", a);

    return 0;
}

运行结果:10

0x01 signed 和 unsigned 各类型的取值范围

⚡【C语言趣味教程】(2) 整数类型:数据类型的概念 | 原码反码与补码 | 有符号型和无符类型 | 研究 signed char 与 unsigned char 的取值范围_第19张图片​有符号型 (signed) 和无符号型 (unsigned) 的取值范围如下:

short 的有符号类型为 -32768 ~ 32767,无符号类型为 -32768 ~ 0xFFFF

int 的有符号类型为 -2147483648 ~ 2147483647,无符号类型为 -2147483648 ~ 0xFFFFFFFF

long 的有符号类型为 -2147483647 - 1 ~ 2147483647,无符号类型为 -2147483647L - 1 ~ 0xFFFFFFFF

long long 的有符号类型为 -9223372036854775807 - 1 ~ 9223372036854775807,无符号类型为 -9223372036854775807 - 1 ~ 0xFFFFFFFFFFFFFFFF

0x02 unsigned 的特点

由于 unsigned 只能存储无符号型,所以如果赋值一个负数,则打印出的结果必然不是该数。

代码演示:unisigned 存负数,我们期望的值是 -10

#include 

int main(void)
{
    unsigned int a = -10;  // 无符号类型无法存储负数
    printf("%u", a);       // 打印的值并非我们期望的 -10

    return 0;
}

运行结果:4294967286

运行结果并不是我们期望的 -10,而是 4294967286,因为 unsigned 类型只能存储无符号类型。

所以,我们这里赋值为负数,打印变量 a 时不是我们赋值的 -10

0x03 研究:有符号和无符号整型的取值范围

 ⚡【C语言趣味教程】(2) 整数类型:数据类型的概念 | 原码反码与补码 | 有符号型和无符类型 | 研究 signed char 与 unsigned char 的取值范围_第20张图片​ 数学中,任意基数的负数都是在最前面加上 - 符号来表示。

而在计算机硬件中,数字以无符号的二进制形式表示。

因此,signed 的存储需要 1 个比特位要专门用来作为 符号位,0 表示正数,1 表示负数。

对于 signed,二进制最高位为 符号位,而对于 unsigned,二进制最高位将视为 数值位

举个例子:我们分别用 signed 和 unsigned 定义变量 ch 的值

ch 的值为 1,二进制为 00000001_{(2)},最高位 0,我们视作符号位(0 表示正数 1 表示负数):

signed char a = 1;      0|0000001

但是,如果 ch 是 unsigned,无符号的话自然就没有符号位的说法了,因此:

其二进制 00000001_{(2)} 的最高位 0 不再视作符号位,全部当成数值看待。

unsigned char a = 1;    00000001

取值范围:

  • 有符号字符型 signed char 的取值范围是:-128 ~ 127
  • 无符号字符型 unsigned char 的取值范围是:0 ~ 255

它们的取值范围实际上是一个 "循环往复" 的过程,比如对于 signed char

当我们对 127 + 1 时会变成 -128,其循环过程我们可以画一张图来表示:

⚡【C语言趣味教程】(2) 整数类型:数据类型的概念 | 原码反码与补码 | 有符号型和无符类型 | 研究 signed char 与 unsigned char 的取值范围_第21张图片

 "汝可识得此阵?此乃 pvz 月夜无尽阵 —— 轮回之瞳!"

上图描述的是当二进制 00000000_{(2)} 持续加 1 ,一直加到 11111111_{(2)} 时,

对应的十进制 0 加到 127,此时再继续加变成 -128,然后继续 + 1 变成 -127,

继续 +1 变成 -126 ... 最后变成 -1 的一个循环过程。当我们对 127 加 1 时,就会得到 -128:

127+1=-128

01111111_{(2)}+1=1000000_{(2)}

* 0x04 如何查看类型的取值范围?

 在头文件 limit.h 中,说明了不同的整数类型的特点,定义了各个变量的取值范围。

#include 

整型 int

INT_MIN    // signed int 最小值
INT_MAX    // signed int 最大值

UINT_MAX   // unsigned int 最大值

短整型 short

SHORT_MIN    // signed short 最小值
SHORT_MAX    // signed short 最大值

USHORT_MAX   // unsigned short 最大值

长整型 long

LONG_MIN    // signed long 最小值
LONG_MAX    // signed long 最大值

ULONG_MAX   // unsigned long 最大值

 字符型 char

SCHAR_MIN    // signed char 最小值
SCHAR_MAX    // signed char 最大值

UCHAR_MAX    // unsigned char 最大值

⚡【C语言趣味教程】(2) 整数类型:数据类型的概念 | 原码反码与补码 | 有符号型和无符类型 | 研究 signed char 与 unsigned char 的取值范围_第22张图片 如果想打印这些变量的最大值或最小值,我们首先需要引入 limits.h 头文件。

#include 

代码演示:获取 int 的取值范围

#include 
#include 

int main(void)
{
    printf("INT_MAX: %d\n", INT_MAX);
    printf("INT_MIN: %d\n", INT_MIN);

    return 0;
}

运行结果如下:

⚡【C语言趣味教程】(2) 整数类型:数据类型的概念 | 原码反码与补码 | 有符号型和无符类型 | 研究 signed char 与 unsigned char 的取值范围_第23张图片

我们可以看到,int 的最大值和最小值就被打印出来了。

​​

 [ 笔者 ]   王亦优 | 雷向明
 [ 更新 ]   2023.7.5(初稿)
❌ [ 勘误 ]   /* 暂无 */
 [ 声明 ]   由于作者水平有限,本文有错误和不准确之处在所难免,
              本人也很想知道这些错误,恳望读者批评指正!

参考文献:

  • - C++reference[EB/OL]. []. http://www.cplusplus.com/reference/.
  • - Microsoft. MSDN(Microsoft Developer Network)[EB/OL]. []. .
  • - 百度百科[EB/OL]. []. https://baike.baidu.com/.
  • - 维基百科[EB/OL]. []. https://zh.wikipedia.org/wiki/Wikipedia
  • - R. Neapolitan, Foundations of Algorithms (5th ed.), Jones & Bartlett, 2015.
  • - 林锐博士. 《高质量C/C++编程指南》[M]. 1.0. 电子工业, 2001.7.24.
  • - 陈正冲. 《C语言深度解剖》[M]. 第三版. 北京航空航天大学出版社, 2019.
  • - 侯捷. 《STL源码剖析》[M]. 华中科技大学出版社, 2002.
  • - T. Cormen《算法导论》(第三版),麻省理工学院出版社,2009年。
  • - T. Roughgarden, Algorithms Illuminated, Part 1~3, Soundlikeyourself Publishing, 2018.

你可能感兴趣的:(C语言趣味教程,c语言,开发语言,数据类型)