🔗 《C语言趣味教程》👈 猛戳订阅!!!
【C语言趣味教程】(2) 整数类型 | 数据类型的概念 | 原码反码与补码 | 有符号型和无符类型 | 研究 signed char 与 unsigned char 的取值范围
—— 热门专栏《维生素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 语言的变量是有明确类型的,创建一个变量时需要明确该变量的数据类型。
📚 定义方式:我们通过 类型 + 变量名 定义一个变量:
数据类型 变量名 = 值;
❓ 还记得这个 int 是什么吗?
在第一章中,我们的 int main() 就是要求 main 函数返回一个整型,这里的 int 就是 整型 了。
#include <stdio.h>
int main() 👈 函数返回值为整型
{
printf("Hello,World!\n");
return 0;
}
💬 代码演示:创建一个整型变量
int a = 10;
(我们将在下一章节详细地讲解变量,这里为了方便讲解数据类型,学会定义简单地变量即可)
0x01 数据类型的概念
* [注] 数据类型, [英] DataTypes,[繁] 資料類型
📚 概念:在程序设计中,数据类型用来约束数据的解释。
数据类型是数据的一种属性,它告知编译器程序员打算如何使用数据。
数据类型定义了可以对数据执行的操作,数据的含义以及存储该类型的方式。
下图展示的是 C 语言的数据类型(本章我们将介绍下图中大部分的数据类型):
[注] 图中打星号的类型为 C99 标准后颁布的类型(例如 bool 和 long long int)。
📚 类型的意义:
- 内存决定开辟内存空间的大小,大小决定了使用的范围。
- 类型决定了看待内存空间的视角。
C 语言中只有 4 种基本的数据类型,整型、浮点型、指针 和 派生类型 。
其它类型都是从以上这四种类型中派生出来的,本章我们会先介绍整型。
0x02 简单介绍一下 ASCII 码
ASCII 码,全称 American Standard Code for Information Interchange,美国信息交换标准代码。
ASCII 码介绍
ASCII 码是基于拉丁字母的一套电脑编码系统,主要用于显示现代英语和其他西欧语言。它是最通用的信息交换标准,并等同于国际标准 ISO/IEC 646。ASCII第一次以规范标准的类型发表是在1967年,最后一次更新则是在1986年,到目前为止共定义了128个字符。
每个 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 原码、反码和补码
整数在内存中的存储方式是以二进制的形式存储的。
在 VS 中我们可以通过调试查看内存,可以发现 数据在内存中是以二进制的形式存储的。
对于整数来说,内存中存储的二进制有三种表现形式,分别是 原码、反码 和 补码。
正整数的原码反码和补码均相同,但是负整数的原码反码和补码就需要去计算了。
下面我们以变量 a = -10 为例,它就是一个负整数,我们用它来讲解计算规则。
① 原码:按照数据的数值直接写出的二进制序列。
符号位 (sign bit):最高位是 0 表示正数,最高位是 1 则表示负数。
这里我们可以看到 -10 的最高位是 1,这个 1 就是符号位了,表示它是一个负数。
② 反码:原码的符号位不变,其它位一律按位取反,即为反码。
③ 补码:反码 +1,得到的就是补码。
Ⅱ. 整数类型(Integer Types)
- "我们刚才说过,C 有四种数据类型,分别是整型、浮点型、指针和派生类型,我们先来介绍整型。整型包括 int, short, long, char…… 我们都将逐个详细介绍。随后我们讲解有符号和无符号,探讨它们的数据范围。"
0x00 整型 int
整数类型中,最具代表性的就是 基本整型 int 了。
在第一章中,我们的 int main() 就是要求 main 函数返回一个整型,这里的 int 就是 整型 了。
整型占 4 个字节,我们可以用它来定义一个整型变量:
int 变量名 = 值;
这样我们就定义了一个 int 型变量 age,它的值为 10。
int 类型的取值范围为 ,即 -2,147,483,648 ~ 2,147,483,647。
int 类型可以使用格式说明中的 %d 来打印:
#include <stdio.h>
int main(void)
{
int a = 100;
printf("%d", a);
return 0;
}
🚩 运行结果:100
0x01 短整型 short int
有一种比 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 <stdio.h>
int main(void)
{
short a = 100;
printf("%hd", a);
return 0;
}
🚩 运行结果:100
0x02 长整型 long int
对应的,还有一种比 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 <stdio.h>
int main(void)
{
long int a = 100;
printf("%ld", a);
return 0;
}
🚩 运行结果:100
注意:long int 是 C99 的。
0x03 超长整型 long long int
" 十年 IO 一场空,不开 long long 见祖宗。"
还有比 long 还要大的整型!long long int,超长整数,可简写为 long long:
long long int 变量名 = 值;
long long 变量名 = 值; // 可简写为 long long
说实话,long long 让我想到了 "Let me do it for you" 的长鼻狗:
表示的范围比 long int 还要大,占 8 个字节,大到令人瞠目结舌的 ,即:
-9223372036854775807 ~ 9223372036854775807
珍妮马大啊…… 值得注意的是,long long 不是所有编译器都使用的。
long long 类型可以使用格式说明中的 %lld 来打印,表示打印超长整数:
#include <stdio.h>
int main(void)
{
long long int a = 1300300100;
printf("%lld", a);
return 0;
}
🚩 运行结果:1300300100
0x04 字符类型 char
在 C 语言中,单个字符的表示使用 字符型 来表示。
我们可以使用 char 来定义一个字符类型:
char 变量名 = 值;
char 也可以看作是整型,因为 char 的本质就是一定范围的 int。
(char 实际上是英文单词 character 的缩写)
其存储大小为 1 字节,char 的取值范围为 ,即 -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 <stdio.h>
int main(void)
{
int ch = 'a';
printf("%c", ch);
return 0;
}
🚩 运行结果如下:c
* 0x05 整型和字符型可相互赋值
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 总结:整型家族
" 画的可谓是非常的形象……"
整型家族有 整型 int,字符型 char,短整型 short,长整型 long,超长整型 long long。
❓ 思考:长整型的值一定要比短整型表示的值要大吗?
不一定,因为标准并没有规定长整型必须比短整型长,只是规定它不能比短整型短。
标准只是规定了 int 至少要和 short 一样长,long 至少要和 int 一样长。
Ⅲ. 有符号型和无符号型(signed & unsigned)
0x00 引入:数学中的正数和负数?
" 整型都分为有符号和无符号两个版本。"
—— 《C和指针》
这里的概念类似于数学中正数和负数的概念,数学中正数用加号 (+) 表示,负数用减号 (-) 表示。
而 C 语言和这个类似,其整型是有两种类型的,即 有符号型 signed 和 无符号型 unsigned 。
① 定义有符号变量时,修饰符 signed 可以省略,默认就是有符号:
signed 类型 变量名 = 值;
类型 变量名 = 值; // 默认就是 signed
② 定义无符号变量时,修饰符 unsigned 不可省略:
unsigned 类型 变量名 = 值;
没有显式声明是 signed 还是 unsigned 则默认为 signed,没有则声明默认为有符号型。
所以我们可以这么理解:数学中加号是可以省略的,比如:
这里声明 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 <stdio.h>
int main(void)
{
unsigned int a = 10;
printf("%u", a);
return 0;
}
🚩 运行结果:10
0x01 signed 和 unsigned 各类型的取值范围
有符号型 (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 <stdio.h>
int main(void)
{
unsigned int a = -10; // 无符号类型无法存储负数
printf("%u", a); // 打印的值并非我们期望的 -10
return 0;
}
🚩 运行结果:4294967286
运行结果并不是我们期望的 -10,而是 4294967286,因为 unsigned 类型只能存储无符号类型。
所以,我们这里赋值为负数,打印变量 a 时不是我们赋值的 -10。
0x03 研究:有符号和无符号整型的取值范围
数学中,任意基数的负数都是在最前面加上 - 符号来表示。
而在计算机硬件中,数字以无符号的二进制形式表示。
因此,signed 的存储需要 1 个比特位要专门用来作为 符号位,0 表示正数,1 表示负数。
对于 signed,二进制最高位为 符号位,而对于 unsigned,二进制最高位将视为 数值位。
💭 举个例子:我们分别用 signed 和 unsigned 定义变量 ch 的值
ch 的值为 1,二进制为 ,最高位 0,我们视作符号位(0 表示正数 1 表示负数):
signed char a = 1; 👉 0|0000001
但是,如果 ch 是 unsigned,无符号的话自然就没有符号位的说法了,因此:
其二进制 的最高位 0 不再视作符号位,全部当成数值看待。
unsigned char a = 1; 👉 00000001
📚 取值范围:
- 有符号字符型 signed char 的取值范围是:-128 ~ 127
- 无符号字符型 unsigned char 的取值范围是:0 ~ 255
它们的取值范围实际上是一个 "循环往复" 的过程,比如对于 signed char:
当我们对 127 + 1 时会变成 -128,其循环过程我们可以画一张图来表示:
"汝可识得此阵?此乃 pvz 月夜无尽阵 —— 轮回之瞳!"
上图描述的是当二进制 持续加 1 ,一直加到 时,
对应的十进制 0 加到 127,此时再继续加变成 -128,然后继续 + 1 变成 -127,
继续 +1 变成 -126 ... 最后变成 -1 的一个循环过程。当我们对 127 加 1 时,就会得到 -128:
* 0x04 如何查看类型的取值范围?
在头文件 limit.h 中,说明了不同的整数类型的特点,定义了各个变量的取值范围。
#include <limit.h>
① 整型 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 最大值
如果想打印这些变量的最大值或最小值,我们首先需要引入 limits.h 头文件。
#include <limit.h>
💬 代码演示:获取 int 的取值范围
#include <stdio.h>
#include <limits.h>
int main(void)
{
printf("INT_MAX: %d\n", INT_MAX);
printf("INT_MIN: %d\n", INT_MIN);
return 0;
}
🚩 运行结果如下:
我们可以看到,int 的最大值和最小值就被打印出来了。
📌 [ 笔者 ] 王亦优 | 雷向明
📃 [ 更新 ] 2023.7.5(初稿)
❌ [ 勘误 ] /* 暂无 */
📜 [ 声明 ] 由于作者水平有限,本文有错误和不准确之处在所难免,
本人也很想知道这些错误,恳望读者批评指正!
📜 参考文献:
|