我们知道变量的创建分为两步:① 指出变量的数据类型;② 为变量取一个名称。
那确定变量的数据类型是干什么的呢?——答:为了确定变量在内存中的存储方式、所占的字节数。
一、数据类型的分类
C语言提供的数据类型如下图所示:
在C语言中,基本数据类型由关键字直接给出,构造类型是较为复杂的数据类型,是由基本数据类型或其他构造类型构造而成。
注:关键字见——C语言学习笔记-初阶(4)C语言关键字详解-CSDN博客
二、基本类型
C语言提供了丰富的数据类型来描述生活中的各种数据:使用整型类型来描述整数,使用字符类型来描述字符,使用浮点型类型来描述小数……
所谓“类型”,就是相似的数据所拥有的共同特征,编译器只有知道了数据的类型,才知道怎么操作
数据。 上图盘点了C语言提供的各种数据类型,本章节主要探讨内置数据类型。
2.1 整型数据的常量与变量
2.1.1 整型数据类型
C语言中,基本整型的关键字为int。其分类如下:
① 根据可表示的数值范围的不同,可分为普通整型(int)、短整型(short int 或 short)和长整型(long int 或 long)。
②根据整型值是否带符号位来分类,可以分为无符号整型和有符号整型。无符号整型用关键字 unsigned 来修饰,有符号整型用关键字 signed 来修饰,signed可以缺省不写,因为缺省时,默认为有符号整型。
归纳起来,C有以下6种整数类型(不考虑long long和unsigned long long),如下表所示,其中[]表示该部分可省略。
实际上,C标准没有具体规定以上各类型数据所占内存字节数,只是要求保证short <= int <= long,具体实现由各C编译系统自行决定。通常情况是short类型分配2字节,int类型分配2或4字节,long类型分配4或8字节,这主要取决于机器字长。在C99标准中,甚至规定了long long类型,可以支持64位(8字节)整数的需求。
(注:long类型的使用会降低运算速度,除非不得已,不建议使用long型——不过现代计算机运行速度很快,这点已经无所谓了)
小芝士:signed 和 unsigned 的区别
C 语言使用 signed 和 unsigned 关键字修饰字符型和整型类型(浮点型默认有符号)。
signed 关键字:表示⼀个类型带有正负号,包含负值;
unsigned 关键字:表示该类型不带有正负号,只能表⽰零和正整数。
对于 int 类型,默认是带有正负号的,也就是说 int 等同于 signed int 。
由于这是默认情况,关键字 signed ⼀般都省略不写,但是写了也不算错。
字符类型 char 也可以设置 signed 和 unsigned。
2.1.2 整型常量
(1)整型常量的表示与书写
(2)整型数据在内存中的存放形式
整常数在源文件的代码文本(源文本)中可以有以上三种书写方式(注意没有二进制),但它们在内存中都是以二进制形式存储的。如,一个short型十进制正整数125的二进制形式为0000 0000 0111 1101,由于它是一个正数,且一个short型数据在内存中占用2个字节的内存单元。所以,+125在内存中的存放形式如下:
对于一个负整数,首先要得到该负数的补码,例如:-2的原码为1000 0000 0000 0010,反码为0111 1111 1111 1101,二进制补码形式为:1111 1111 1111 1110,那么-2在内存中的存放形式如下:
小芝士:原码、反码和补码
有符号整型、字符型:内存里存补码。
① 正数:补码与它的原码相同,内存里存补码就是存原码。
② 负数:内存里存补码,补码的说明如下:
原码:十/八/十六进制直接转成的二进制序列。
反码:原码按位取反(原码某位是1/0,则反码那一位是0/1)
补码:符号位不变,反码+1
无符号整型、字符型:内存里存原码。
(3)整型常量的表示与书写
小芝士:数值常量的后缀介绍
1.浮点数后缀
• `f`或`F`:表示单精度浮点数(`float`)。例如,`3.14f`或`3.14F`表示一个`float`类型的浮点数。
• `l`或`L`:表示长双精度浮点数(`long double`)。例如,`3.14l`或`3.14L`表示一个`long double`类型的浮点数。
如果没有后缀,默认情况下,浮点数常量是`double`类型。——所以没有d这个后缀
2.整数后缀
• `u`或`U`:表示无符号整数(`unsigned`)。例如,`123u`或`123U`表示一个无符号整数。
• `l`或`L`:表示长整数(`long`)。例如,`123l`或`123L`表示一个`long`类型的整数。
• `ll`或`LL`:表示长长整数(`long long`)。例如,`123ll`或`123LL`表示一个`long long`类型的整数。
• `ul`或`UL`:表示无符号长整数(`unsigned long`)。例如,`123ul`或`123UL`。
• `ull`或`ULL`:表示无符号长长整数(`unsigned long long`)。例如,`123ull`或`123ULL`。
注意事项
• 大小写敏感:虽然后缀的大小写(如`f`和`F`、`l`和`L`等)在功能上是相同的,但建议使用大写以提高代码的可读性。
• 避免歧义:在使用后缀时,要注意避免与变量名或其他标识符混淆。例如,`1l`(数字1后跟小写字母l)可能会被误认为是数字`11`,建议使用大写`L`,即`1L`。
• 默认类型:如果没有后缀,整数常量默认为`int`,浮点数常量默认为`double`。 这些后缀在编写代码时非常有用,尤其是在需要明确指定数据类型以避免隐式类型转换或确保精度的情况下。
2.1.3 整型变量
2.1.4 整型变量的初始化
C语言允许在定义变量的同时赋值,这称为变量的初始化,例如:
int i = 0; //指定i为整型变量,初值为0
long number = 12345 //指定number为长整型变量,初值为12345
也可以给部分变量赋初值,如:
int i, j, k = 0; //指定i, j, k为整型变量,且对k赋初值为0
这条语句只对变量k赋初值为0,变量i,j则只被内存分配了内存单位,其内容是随机的,而不是0。
如果对几个变量赋初值为0,应书写成:
int i = 0, j = 0, k = 0; //指定i, j, k为整型变量,且对i, j, k赋初值为0
要注意对上面的语句不能写成int i = j = k = 0;会报错“未定义的标识符j,k”,因为这个语句未对变量j,k进行定义,语句将会因为变量j,k未定义就使用而出错。
变量初始化是在程序运行时执行到本语句时赋初值的,并不是在编译阶段完成,它相对于一个赋值过程。
2.2 浮点型数据的常量与变量
2.2.1 浮点型数据类型
2.2.2 浮点型常量
(1)浮点型常量的表示与书写
很显然,一个浮点数的指数形式可以有很多种。例如589.74可以表示为589.74e0、58.974e1、5.8974e2、0.58974e3、0.058974e4……等等,其中,5.8974e2为“规范化的指数形式”,即在左边的小数部分中,只能有一位非零数字。一个浮点数在用指数形式输出时,会按照规范化的指数形式输出。
(2)浮点型数据在内存中的存放形式
在计算机中,浮点数(float)的取值范围是由其二进制表示方式决定的。浮点数的二进制表示遵循IEEE 754标准,该标准定义了浮点数的存储方式和运算规则。
1.IEEE 754标准
IEEE 754标准定义了两种基本的浮点数格式:
• 单精度浮点数(float):使用32位(4字节)存储。
• 双精度浮点数(double):使用64位(8字节)存储。
2.单精度浮点数(float)
单精度浮点数由三部分组成:
• 符号位(S):1位,用于表示正负号(0为正,1为负)。
• 指数位(E):8位,用于表示指数。
• 尾数位(M):23位,用于表示尾数(小数部分)。
指数位的偏移量
指数位使用偏移量表示,偏移量为127。因此,实际指数值为`E - 127`。
尾数位的表示
尾数位使用隐含的1表示整数部分,即实际尾数值为`1.M`(二进制表示)。
3.浮点数的取值范围
根据上述表示方式,我们可以计算出单精度浮点数的取值范围。
最大正数
• 指数位:`11111111`(二进制),实际指数值为`255 - 127 = 128`。
• 尾数位:`111...111`(23个1),实际尾数值为`1.111...111`(二进制),约等于`2 - 2^-23`。
因此,最大正数:
(2-2^-23) × 2^128 ≈ 3.4028235 × 10^38
最小正数
• 指数位:`00000001`(二进制),实际指数值为`1 - 127 = -126`。
• 尾数位:`000...000`(23个0),实际尾数值为`1.0`(二进制)。
因此,最小正数:
1.0 × 2^-126 ≈ 1.1754944 × 10^-38
最大负数
绝对值最大负数是最大正数的相反数,即:-3.4028235 × 10^38
最小负数
绝对值最小负数是最小正数的相反数,即:-1.1754944 × 10^-38
(3)浮点型常量的类型
C语言中编译器会将浮点常量当作double类型的。但有时候需要将常量指定为float类型,可以在常量后面添加上F或f后缀,如3.14f。当需要将常量指定为long double类型时,可以在常量后面加上L或l后缀,如9.42e6l。
2.2.3 浮点型变量
按照C语言中定义变量的格式,定义浮点型变量的例子如下:float a = 3.14;
2.3 字符型数据的常量与变量
2.3.1 字符型数据类型
2.3.2 字符型常量
(1)字符型常量的书写
③ 采用八进制或十六进制的形式书写,这种形式也适合ASCII码表中的任何字符的表示。其八进制形式为\ddd。其中,d是0~7中的任意一个八进制数,其方法是在单引号中间以"\"开头,然后后面跟上1~3位的八进制数,以表示字符的ASCII码。例如“换行”字符的ASCII码值是10,对应的八进制数为12。使用特殊的转义序列方法,“换行”字符表示为’\n',也可以表示为'\12'或'\012'。
同理,十六进制形式为\xdd,其中,d是字符0~9、a~f、A~ F中的任意一个,其方法是在单引号中间以\x或\X开头,然后跟上1~2位的十六进制数,例如“换行”字符可表示为'\xa'或'\XA'。
(2)字符型数据在内存中的存放形式
2.3.3 字符型变量
以后变量linefeed中的值既可以以字符形式输出,也可以以整数形式输出,甚至可以和整数进行算术运算——因为char属于整型家族(在内存中的存储方式都是直接存储数值对应的二进制序列)。
2.4 字符串常量
2.5 符号常量
在C语言语言中,不管是哪种基本数据类型的常量,都可以有三种形式。
1.在程序中直接使用数值——字面常量;
2.用const关键字把一个变量声明转换成常量声明——const常变量;
3.采用宏定义形式定义符号变量——#define宏定义的标识符常量;
(注:具体见:C语言学习笔记-初阶(6)标识符 & 变量 & 常量-CSDN博客)
在程序中使用常变量或符号常量,使得常量的含义比较明确,因此建议少用或不用无名常量。此外,因为常变量也是变量,编译器会对它进行类型检查(宏定义的符号常量没有类型检查),能更早发现一些错误,所以,使用常变量比使用符号常量更好。——在STM32的HAL库就大量使用define定义的宏常量,几乎见不到字面常量(无名常量)。