C 语言的基本数据类型
基本数据类型概述
按计算机的存储方式, 数据类型可以分为两大基本类型: 整数类型和浮点数类型.
常量有四种: 字面常量, const 修饰的常变量, #define 定义的标识符常量, 枚举常量.
字面常量包括整型常量,浮点型常量,字符常量,字符串常量。
常量根据书写的形式来识别具体的类型, 如 43 是整数, 43.1 是浮点数.
变量需要在定义时指定具体的类型.
C 语言的数据类型关键字如下:
int 表示基本的整数类型, 即基本整型, long, short, unsigned, signed 提供基本整数类型的变式.
long long 不是关键字.
signed 和 unsigned 只能修饰整型 (包括基本整型的扩展的整型) 和 char 类型.
char 用于指定字符, 也可以表示较小的整数.
float, double, long double 表示浮点数.
_Bool 表示布尔值 (true 或者 false)
_Complex 和 _Imaginary 分别表示复数和虚数.
整数类型
整数是以二进制的方式存储的.
8 种整数类型:
-
[signed] short [int] : 占用的存储空间可能比 int 少. C 语言规定 short 至少占用 16 位。
-
[signed] int : 如果占 16 位则取值范围为 [-32768, 32767], 规定取值范围最小为 [-32768,
32767]. C 语言规定 int 至少占用 16 位。 -
[signed] long [int] : 占用的存储空间可能比 int 多. C 语言规定 long 至少占用 32 位。
-
[signed] long long [int] : C99标准加入, 占用的存储空间可能比 long 多, C 语言规定 long long 至少占 64 位, 因为 long long 类型就是为了支持 64 位机器才引入的.
-
unsigned short [int]
-
unsigned [int] : 如果占 16 位则取值范围为 [0, 65535].
-
unsigned long [int]
-
unsigned long long [int] : C99标准加入.
C 语言只规定了 short 占用的存储空间不能多于 int, long 占用的存储空间不能少于 int. 一般而言,long 一定比 short 长,int 要么和 long 一样长,要么和 short 一样长。最常见的情况是:short 占 16 位,int 可能占 16 或 32 位,long 占 32 位,long long 占 64 位。
一般而言, 存储一个 int 类型要占用一个机器字长. ISO C 规定, int 类型的取值范围最小为 [-32768, 32767] (即 int 占 16 位时的取值范围).
不同整数类型所占用的内存空间大小以及各个类型表示的数值范围不同.
一般而言, 系统用一个特殊位的值表示有符号整数的正负号.
整型常量一般都默认为 int 类型 (只要该常量在 int 类型的取值范围内). 可以用整型常量后缀修改这一默认的数据类型.
一般, 十进制的整型常量按照自身数值的大小, 被认为的类型依次为: int, long, unsigned long, long long, unsigned long long.
整型常量一般都默认为十进制. 可以用前缀修改默认的进制. 0x 或 0X 表示是十六进制整数. 0 前缀表示八进制整数.
使用不同的进制数是为了方便, 不会影响数被储存的方式.
一般, 八和十六进制的整型常量按照自身数值的大小, 被认为的类型依次为: int, unsigned int, long, unsigned long, long long, unsigned long long.
long 类型的后缀: 对于十进制, 八进制和十六进制的整数而言, 如果需要将一个较小的整数视为 long 类型, 可以加后缀 l 或者 L. 在 int 占 16 位, long 占 32 位的系统中, 会将 7, 07 和 0X7 均作为 16 位存储, 将 7L, 07L 和 0X7L 均作为 32 位存储.
注意: 表示八进制和十六进制的前缀和常量的后缀可以一起使用.
unsigned long 类型的后缀: UL
long long 类型的后缀: ll 或 LL.
unsigned long long 类型的后缀: ull 或 ULL.
溢出
溢出行为是未定义的, C 标准并未规定溢出规则.
常见的情况是从起始位置重新开始, 比如取值范围为 [-32768, 32767] 时, 32767+1 = -32768, 32768-1 = 32767.
整数类型在内存中的储存方式
浮点型概述
C 语言有三种浮点数类型:
-
float 称为单精度浮点型。
-
double 称为双精度浮点型。
-
long double 称为长双精度浮点型。
C 标准规定,float 必须至少能表示 6 位有效数字,且取值范围至少是 10-37 ~ 1037。
float 类型必须能够表示 33.333333 的前 6 位数字,而不是精确到小数点后 6 位数字。
储存一个 float 需要 32 位,8 位用于表示指数的值和符号,剩下的 24 位用于表示非指数部分(也叫尾数或有效数)及其符号。
double 类型和 float 类型的最小取值范围相同。
C 标准规定,double 必须至少能表示 10 位有效数字,但是通常在任何实现上都至少能表示 13 位有效数字,超过了标准的最低位数规定。
一般情况下,double 占用 64 位。
C 规定,long double 类型至少与 double 类型的精度相同。
小数的长度是固定的,float 始终占用 4 个字节,double 始终占用 8 个字节。
给未在函数原型中显式说明参数类型的函数(如 printf())传递参数时,C 编译器会把 float 类型的值自动转换为 double 类型。
浮点型常量
在代码中有两种方式表示浮点型常量:一般计数法和指数计数法(C99 标准新增十六进制表示浮点型常量,即 p 记数法)。
e 计数法:也称为指数计数法,是科学计数法在计算机中的写法。
e 计数法表示的浮点型常量:有符号的数字(包括小数点),后面紧跟 e 或 E,然后是一个带符号的数表示 10 的指数,e 后面的数字代表 10 的指数。
e 计数法可以分成四个部分,即:整数部分,小数点,小数部分,指数部分。如图所示:
形式为: aEn 或 aen
a 为尾数部分, 是一个十进制数; n 为指数部分, 是一个十进制整数; E 或 e 是固定的字符, 用于分割尾数部分和指数部分. 整个表达式等价于 a×10n.
e 计数法表示的浮点数示例:
+1.2E+12 -5.6e-3
正号可以省略,如:
1.2E12
可以没有小数点,仍然是一个浮点数,如:
4e6
可以省略尾数部分或指数部分,但不可以同时省略两者。如:
12.98 // 可以没有指数部分 e6 // 可以没有指数部分
尾数部分可以省略小数部分或整数部分,但不能同时省略二者。如:
3.e12 // 省略小数部分 .45e-6 // 省略整数部分
有效的浮点数的示例:
.12 100. .8e-4
浮点型常量中间不能加空格,如下面的写法是错误的:
13.8 e-4
通常,浮点型常量会被认为是 double 类型,会被当成 double 类型储存。
浮点数默认是十进制,除非用十六进制的 p 记数法。
C99 标准新增十六进制表示浮点型常量,即 p 记数法,在十六进制数的前面加上前缀 0x 或 0X,用 p 或 P 取代 e 或 E,用 2 的幂次取代 10 的幂次。如:
0xa.1fp10
十六进制 a 等于十进制 10,.1f 是 1/16 加上 15/256,p10 是 210 即 1024。0xa.1fp10 表示的值为
浮点型常量的后缀
使用后缀 f 或者 F 可以将浮点数当做 float 类型,例如:
12.4f 34.5F
使用后缀 l 或者 L 可以将浮点数当做 long double 类型,例如:
12.4L 34.5L
没有后缀的浮点型常量则为 double 类型。
溢出
上溢
C 语言规定,某个浮点型变量发生上溢时,将会赋给其一个表示无穷大的特定值,用 printf() 显示该值则为 inf 或 infinity,或者具有无穷含义的其他内容。
下溢
一个特殊的浮点值:NaN
这是 not a number 的缩写。
printf() 函数将其显示为 nan, NaN 或其他类似的内容。
比如,给 asin() 函数传递一个大于 1 的实参,则该函数返回 nan。
代码示例:
#include <stdio.h> #include <math.h> int main(void) { printf("%f\n", asin(1)); printf("%f\n", asin(1.1)); return 0; }
执行结果:
1.570796 -nan(ind)
初始化
把一个类型的数值初始化给不同类型的变量时,编译器会把值转换成与变量类型匹配的类型。
用浮点型初始化整型会发生截断,直接丢弃小数点后面的数据。
用 double 初始化 float 会发生四舍五入。
代码示例:
#include<stdio.h> int main(void) { int a = 10.99; float b = 10.1111115; printf("%d\n", a); printf("%f\n", b); return 0; }
结果;
10 10.111112
浮点数在内存中的储存方式
计算机把浮点数分成整数部分和小数部分分开存储, 使用二进制和 2 的幂进行存储, 而不是 10 的幂.
因为在任何区间内, 都存在无穷多个实数, 所以计算机的浮点数不能表示区间内所有的值. 浮点数通常只是实际值的近似值.
精度越高, 计算速度往往越慢.
char 类型
char 是整数类型。
char 类型实际上存储的是整数而不是字符。一个字符变量实际上被存储为 1 字节的整数值。
计算机使用数字编码来处理字符,即使用特定的整数来表示特定的字符。
标准 ASCII 码的范围是 [0, 127],只需要 7 位二进制即可。
通常, char 类型被定义为 8 位的存储单元,因此容纳标准 ASCII 码绰绰有余。
C 语言把 1 字节定义为 char 类型占用的位数,即 bit 数。
用单引号括起来的单个字符被称为字符常量。
代码示例:
#include <stdio.h> int main(void) { char c1 = 'a'; char c2 = 97; // 相当于把字符a赋给c2 printf("%d\t%d\n", c1, c2); // 97 97 printf("%c\t%c\n", c1, c2); // a a return 0; }
执行结果:
97 97 a a
C 语言将字符常量视为 int 类型而非 char 类型。
#include <stdio.h> int main(void) { char c = 'abcd'; printf("%c\t%d\n", c, c); // d 100,即只有最后的8位有效 printf("%d\n", sizeof('abcd')); // 4 printf("%d\n", sizeof(c)); // 1 return 0; }
结果:
d 100 4 1
有的编译器将 char 实现为有符号类型,则 char 的取值范围是 [-128, 127]。有的编译器将 char 实现为无符号类型,则 char 的取值范围为 [0, 255]。根据 C90 标准,C 语言允许在关键字 char 前面使用 signed 或 unsigned。这样,无论编辑器默认的 char 类型为有符号还是无符号的,signed char 都将表示有符号类型,unsigned char 都将表示无符号类型。如果只用 char 处理字符,则 char 前面无需使用 signed 或 unsigned 进行修饰。
_Bool 类型
表示真或假的变量称为布尔变量 (Boolean variable)
C99 标准新增了 _Bool 类型,用于表示布尔值,即 true 和 false。
仅占用 1 位存储空间。
实际上也是一种整数类型,且是无符号的 int 类型。
C 语言将非 0 的数当为 true,0 当为 false。
_Bool 是 C 语言中布尔变量的类型名.
_Bool 类型的变量只能储存 1 或 0.
如果把其他非零数值赋值给布尔变量, 则该变量会被设置为 1. 这体现了 C 把所有非零值都视为真.
程序示例:
#include<stdio.h> int main(void) { _Bool a = 10; printf("%d\n", a); return 0; }
结果:
1
代码示例:
#include<stdio.h> int main(void) { int beep = 11; if (beep) printf("1\n"); else printf("2\n"); return 0; }
结果:
1
代码示例:
#include<stdio.h> int main(void) { int beep = 0; if (beep) printf("1\n"); else printf("2\n"); return 0; }
结果:
2
复数和虚数类型
C99 标准支持复数和虚数类型,但是有所保留。
C 语言有 3 种复数类型:float _Complex, double _Complex, long double _Complex.
C 语言有 3 种虚数类型:float _Imaginary, double _Imaginary, long double _Imaginary.
如果包含了 complex.h 头文件,便可用 complex 代替 _Complex,用 imaginary 代替 _Imaginary,用 I 代替 -1 的平方根。
float _Complex 类型的变量要有两个 float 类型的值,分别表示复数的实部和虚部。其他 5 个类型类似。
数据类型转换
自动类型转换
一些基本的自动类型转换规则:
-
当类型转换出现在表达式时,无论是 unsigned 还是 signed 的 char 和 short,都会被自动转换为 int。如有必要,还会被转换为 unsigned int(当 short 与 int 大小相同,unsigned short 就会比 int 大,此时,unsigned short 会被转换为 unsigned int)。
-
类型的级别从高至低依次为:long double, double, float, unsigned long long, long long, unsigned long, long, unsigned int, int。例外的情况是,当 long 和 int 大小相同时,unsigned int 比 long 级别高。之所以没有列出 short 和 char,是因为它们已经被升级为 int 或 unsigned int。
-
对于没有用函数原型指明形参类型的函数,如 printf 函数,作为参数传递时,char 和 short 被转换为 int,float 被转换为 double。
代码示例:
#include<stdio.h> int main(void) { char ch; int i; float fl; ch = i = fl = 'C'; printf("ch = %c, i = %d, fl = %2.2f\n", ch, i, fl); return 0; }
结果:
ch = C, i = 67, fl = 67.00
分析: 这里, 字符 C 作为一字节的 ASCII 值, 储存在 ch 中. 整数变量 i 接受由字符 C 转换而来的整数, 即 67, 然后按照 4 字节储存 67. 浮点型变量 fl 接受由字符 C 转换而来的整数, 即 67, 再将 67 转换为浮点数, 67.00.
程序示例:
#include<stdio.h> int main(void) { char ch; ch = 'C' + 1; printf("ch = %c\n", ch); return 0; }
结果:
ch = D
分析: 字符 C 转换成整数, 即 67, 然后加 1. 计算结果是四字节的整数 68, 然后被截断成 1 字节的整数 68, 储存在字符型变量 ch 中, 根据转换说明 %c 进行打印时, 68 被解释成 D 的 ASCII 码值. 于是打印字符 D.
程序示例:
#include<stdio.h> int main(void) { char ch = 'A'; int i; float fl = 2.0f; printf("字母 A 的 ASCII 码是:%d\n", ch); i = fl + ch * 2; printf("i = %d\n", i); return 0; }
结果:
字母 A 的 ASCII 码是:65 i = 132
分析: 语句 i = fl + ch * 2;
中, ch 为了和 2 相乘, 需要先转换为 4 位的 int 型的 65, 得到计算结果为 4 位的 int 型的 130. 4 位的 int 型的 130 为了和 float 类型的 fl 相加, 需要先转换为 float 类型, 即 130f. 变成了 130.0f + 2.0f
, 得到结果为 132.0f. 要把 132.0f 赋值给 int 类型的 i, 需要把 132.0f 转变为 4 位的 int 类型的 132, 储存在 int 类型的 i 中.
程序示例:
#include<stdio.h> int main(void) { char ch = 1107; printf("%c\n", ch); printf("%d\n", 1107 % 256); printf("%c\n", 1107 % 256); return 0; }
结果:
S 83 S
分析: 这里显示了降级的过程. 把 ch 设置为超出其范围的一个值, 忽略额外的位, 按照上述的规则, 这里的目标类型是 8 位的,因此相当于将这个值对 256 求模.
程序示例:
#include<stdio.h> int main(void) { char ch = 87.56; printf("%d\n", ch); printf("%c\n", ch); printf("%c\n", 87); return 0; }
结果:
87 W W
分析: 演示了另一个截断的例子. 这里将一个 float 值赋给了 char 类型变量, 丢弃了小数点后面的数字.
C 允许编写混合数值类型的表达式,但是在求表达式的值之前,会进行自动类型转换,将类型全部转换为同一种类型后,再进行计算。
强制类型转换
应该尽量避免自动类型转换, 尤其是降级. 有时需要进行精确的类型转换, 或者明确指明类型转换的意图.
可使用强制类型转换, 即在某个变量的前面放置一个用括号括起来的类型名, 该类型名是希望转换成的目标类型.
圆括号和它括起来的类型名构成了强制类型转换运算符
通用形式为:
(type) variable
代码示例:
int a = 4.4 + 4.5; int b = (int)4.4 + (int)4.5;
第一行是自动类型转换. 首先 4.4 和 4.5 相加, 得到 8.9, 然后为了将 8.9 赋值给 int 类型的变量 a, 将 8.9 截断为 8.
第二行是强制类型转换. 相加之前 4.4 和 4.5 分别被转换为 4 和 5. 把 4+5 的结果赋给 b.
强制类型转换只对紧跟在它后面的那个变量起作用。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术