第一章:数据类型(🎯)
1.1 概述
1.1.1 数据类型是什么?
数据类型决定了数据的身份、大小、解释方式和可进行的操作,如下所示:
提醒
- ①
数据类型用来告诉编译器:一块数据是什么、占用多少内存、应该怎么解释以及允许对它进行什么操作。 - ② 可以将
数据类型理解为给内存中的二进制数据贴上说明书。

- 示例:
c
#include <stdio.h>
/*
* 数据类型类似于给内存中的二进制贴上说明书
*/
int main(void) {
// int 是数据类型,表示整数
int a = 18;
// double 是数据类型,表示浮点数
double price = 19.22;
// char 是数据类型,表示字符
char grade = 'A';
return 0;
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
1.1.2 为什么需要数据类型?
- 计算机内存中本质只有二进制;那么,对于同一段二进制,不同的数据类型有不同的含义。
提醒
二进制:0100 0001 可以被解释为 整数:65 ,也可以被解释为 字符:'A' 。
- 示例:
c
#include <stdio.h>
/*
* 同一段二进制,不同的数据类型可能有不同的含义。
*/
int main(void) {
int c = 0b01000001;
// 变量 c 在内存中数据是没有变化的,只是解释方式不同
// A
printf("%c\n", c);
// 65
printf("%d\n", c);
return 0;
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
1.1.3 数据类型的本质
- 数据类型主要决定了四件事情,如下所示:
点我查看 具体细节
- ① 数据的含义:数据类型告诉编译器,这段二进制数据应该被当成整数、小数、字符等什么内容来解释。
- ② 占用的内存大小:数据类型决定一个变量通常需要占用多少字节的内存空间。
- ③ 能表示的数值范围:数据类型决定这个变量能够存储的最小值和最大值。
- ④ 可以进行哪些操作:数据类型决定这个数据可以参与哪些运算,如:加减乘除、取模、比较或位运算等。

- 示例:
c
#include <stdio.h>
int main(void) {
int a = 10;
int b = 20;
/* 因为 a 和 b 都是整数,
* 所以编译器知道 a + b 的值也是整数,
* 所以 c 的类型也应该是整数
*/
int c = a + b;
printf("c = %d\n", c);
return 0;
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
2
3
4
5
6
7
8
9
10
11
12
13
14
15
1.2 C 语言中的数据类型
1.2.1 概述
- C 语言提供了各种数据类型,如下所示:
提醒
下文主要探讨基本数据类型!!!
1.2.2 字符类型
字符的英文单词是character,使用char来表示字符,以下是所有的字符类型:
类型写法([] 内的可省略) | 说明 | 备注 |
|---|---|---|
| char | 基本字符类型 | 其默认是有符号还是无符号,取决于具体的编译器和平台实现。 |
| [signed] char | 有符号字符型 | |
| unsigned char | 无符号字符型 |
- 示例:
c
#include <stdio.h>
int main(void) {
char c = 'A';
printf("c = %c\n", c);
return 0;
}1
2
3
4
5
6
7
8
9
2
3
4
5
6
7
8
9

1.2.3 整数类型
整数的英文单词是integer,使用int来表示整数,以下是所有的整数类型:
| 分类 | 类型写法([] 内的可省略) | 说明 | 备注 |
|---|---|---|---|
| 短整型 | short [int] | 默认为有符号类型。int 和 signed 关键字均可省略。 | --- |
| [signed] short [int] | |||
| unsigned short [int] | |||
| 整型 | int | 默认为有符号类型。signed 关键字可省略。 | 默认类型 |
| [signed] int | |||
| unsigned int | |||
| 长整型 | long [int] | 默认为有符号类型。int 和 signed 关键字均可省略。 | --- |
| [signed] long [int] | |||
| unsigned long [int] | |||
| 长长整型 | long long [int] | C99 标准中引入。默认为有符号类型。int 和 signed 关键字均可省略。 | --- |
| [signed] long long [int] | |||
| unsigned long long [int] |
- 示例:
c
#include <stdio.h>
int main(void) {
int age = 18;
printf("age = %d\n", age);
return 0;
}1
2
3
4
5
6
7
8
9
2
3
4
5
6
7
8
9

1.2.4 浮点类型
- 以下是所有的浮点类型:
| 分类 | 类型 | 说明 | 典型大小与精度(参考) | 备注 |
|---|---|---|---|---|
| 单精度浮点型 | float | 基本的浮点数类型,适用于对精度要求不高的场景。 | 通常 4 字节,约 7 位 十进制有效数字。 | 字面量需要加 f 或 F。 |
| 双精度浮点型 | double | 精度比 float 更高。 | 通常 8 字节,约 15 位 十进制有效数字。 | 默认 |
| 长双精度浮点型 | long double | 提供比 double 更高的精度,常用于科学计算。 | 通常 12 或 16 字节(x86 架构下常见为 80 位扩展精度),约 19 位或更多 有效数字。 | 其具体大小和精度 由编译器和硬件平台决定。 |
- 示例:
c
#include <stdio.h>
int main(void) {
float f = 3.14f;
printf("f = %.10f\n", f);
double d = 3.14;
printf("d = %.10f\n", d);
return 0;
}1
2
3
4
5
6
7
8
9
10
11
2
3
4
5
6
7
8
9
10
11

1.2.5 布尔类型
- C 语言原先并没有单独为布尔值设置一个类型,而是使用
0表示假,非0表示真。
提醒
- ① 在 C99 中引入了
_Bool作为布尔类型,true表示真以及false表示假(需要引入头文件<stdbool.h>)。 - ② 在 C23 中,使用
bool作为布尔类型的关键字,以代替_Bool。
- 示例:
c
#include <stdio.h>
int main(void) {
bool is_flag = true;
if (is_flag) {
printf("哈哈\n");
} else {
printf("呵呵\n");
}
return 0;
}1
2
3
4
5
6
7
8
9
10
11
12
13
2
3
4
5
6
7
8
9
10
11
12
13

1.2.6 数据类型的长度
1.2.6.1 概述
- 每种数据类型都有自己的长度,使用不同的数据类型创建的变量,其长度也会有所差异。
1.2.6.2 sizeof 操作符
- 格式:
c
sizeof(类型)
sizeof 表达式1
2
3
2
3
提醒
- ① sizeof 是一个关键字,也是操作符,专门是用来计算 sizeof 的操作数的类型长度的,单位是字节。
- ② sizeof 操作符的操作数可以是类型,也可是变量或者表达式。
- ③ sizeof 的操作数如果不是类型,是表达式的时候,可以省略掉后边的括号的。
- ④ sizeof 后边的表达式是不真实参与运算的,根据表达式的类型来得出大小。
- ⑤ sizeof 的计算结果是
size_t类型的,如果要打印在屏幕上,可以使用%zu的占位符。 - ⑥ 数据类型长度和取值范围的 官方文档 地址。
- 示例:
c
#include <stdio.h>
int main(void) {
int a = 10;
printf("%zu\n", sizeof(a)); // 4
// sizeof 的操作数如果不是类型,是表达式的时候,可以省略掉后边的括号的。
printf("%zu\n", sizeof a); // 4
printf("%zu\n", sizeof(int)); // 4
printf("%zu\v", sizeof(3 + 3.5)); // 8
return 0;
}1
2
3
4
5
6
7
8
9
10
11
12
13
2
3
4
5
6
7
8
9
10
11
12
13

- 示例:
c
#include <stdio.h>
int main(void) {
printf("%zu\n", sizeof(char)); // 1
printf("%zu\n", sizeof(bool)); // 1
printf("%zu\n", sizeof(short)); // 2
printf("%zu\n", sizeof(int)); // 4
printf("%zu\n", sizeof(long)); // 4
printf("%zu\n", sizeof(long long)); // 8
printf("%zu\n", sizeof(float)); // 4
printf("%zu\n", sizeof(double)); // 8
printf("%zu\n", sizeof(long double)); // 16
return 0;
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

- 示例:
c
#include <stdio.h>
int main(void) {
/*
* sizeof 中表达式不计算
*
* sizeof 在代码进行编译的时候,就根据表达式的结果的类型,确定了类型的长度,
* 而表达式真要被执行,却要在程序运行期间才会发生,
* 如果在编译期间已经将 sizeof 处理掉了,在运行期间就不会执行表达式了。
*/
short s = 12;
int b = 10;
printf("%zu\n", sizeof(s = b + 1)); // 2
printf("s = %d\n", s); // s = 12
return 0;
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

1.3 signed 和 unsigned
1.3.1 概述
- C 语言中使用
signed或unsigned关键字修饰整型家族(字符型和整型)。
提醒
- ① signed(有符号)是关键字,如果修饰一个整型家族的类型,则表示该类型修饰的变量可以是:正数、负数和 0。
- ② unsigned(无符号)是关键字,如果修饰一个整型家族的类型,则表示该类型修饰的变量只能是:正数和 0(自然数) 。
1.3.2 整数类型
- 对于 int 类型而言,默认是 signed 修饰的;换言之,int 等同于 signed int。
c
signed int a ; // 等同于 int a ;1
- 对于 int 类型而言,也可以不带正负号,只表示非负整数,则必须使用关键字 unsigned 来修饰。
c
unsigned int a ;1
提醒
整数变量声明为 unsigned 的好处是,同样长度的内存能够表示的最大整数值,增大了一倍。
整型的取值范围可以在
limits.h中进行查看。示例:
c
#include <stdio.h>
#include <limits.h>
int main(void) {
/*
* 输出各种整数类型的取值范围
*/
printf("signed short 的取值范围:%d 到 %d\n", SHRT_MIN, SHRT_MAX);
printf("unsigned short 的取值范围:0 到 %u\n", USHRT_MAX);
printf("signed int 的取值范围:%d 到 %d\n", INT_MIN, INT_MAX);
printf("unsigned int 的取值范围:0 到 %u\n", UINT_MAX);
printf("signed long 的取值范围:%ld 到 %ld\n", LONG_MIN, LONG_MAX);
printf("unsigned long 的取值范围:0 到 %lu\n", ULONG_MAX);
printf("signed long long 的取值范围:%lld 到 %lld\n", LLONG_MIN, LLONG_MAX);
printf("unsigned long long 的取值范围:0 到 %llu\n", ULLONG_MAX);
return 0;
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

1.3.3 字符类型
- 字符类型的 char 也可以设置 signed 或 unsigned。
c
signed char c; // 范围为 -128 到 1271
c
signed char c; // 范围为 -128 到 1271
提醒
C 语言并没有规定 char 类型默认是否带正负号,由编译器自行决定。
- 示例:
c
#include <stdio.h>
#include <limits.h>
int main(void) {
/*
* 输出各种字符类型的取值范围
*/
printf("char 的取值范围:%d 到 %d\n", CHAR_MIN, CHAR_MAX);
printf("signed char 的取值范围:%d 到 %d\n", SCHAR_MIN, SCHAR_MAX);
printf("unsigned char 的取值范围:0 到 %u\n", UCHAR_MAX);
return 0;
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
2
3
4
5
6
7
8
9
10
11
12
13
14

1.4 数据类型的取值范围
- 数值类型中的每种数据类型都有自己的取值范围,我们需要在合适的场景下选择对应的类型。
提醒
- ① 数值类型 = 整数类型 + 浮点数类型 + 字符类型。
- ②
limits.h头文件中说明了整型类型的取值范围。 - ③
float.h头文件中说明浮点型类型的取值范围。
- 示例:整数类型中各个数据类型的取值范围
c
#include <stdio.h>
#include <limits.h>
int main(void) {
/*
* 输出各种整数类型的取值范围
*/
printf("signed short 的取值范围:%d 到 %d\n", SHRT_MIN, SHRT_MAX);
printf("unsigned short 的取值范围:0 到 %u\n", USHRT_MAX);
printf("signed int 的取值范围:%d 到 %d\n", INT_MIN, INT_MAX);
printf("unsigned int 的取值范围:0 到 %u\n", UINT_MAX);
printf("signed long 的取值范围:%ld 到 %ld\n", LONG_MIN, LONG_MAX);
printf("unsigned long 的取值范围:0 到 %lu\n", ULONG_MAX);
printf("signed long long 的取值范围:%lld 到 %lld\n", LLONG_MIN, LLONG_MAX);
printf("unsigned long long 的取值范围:0 到 %llu\n", ULLONG_MAX);
return 0;
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

- 示例:字符类型中各个数据类型的取值范围
c
#include <stdio.h>
#include <limits.h>
int main(void) {
/*
* 输出各种字符类型的取值范围
*/
printf("char 的取值范围:%d 到 %d\n", CHAR_MIN, CHAR_MAX);
printf("signed char 的取值范围:%d 到 %d\n", SCHAR_MIN, SCHAR_MAX);
printf("unsigned char 的取值范围:0 到 %u\n", UCHAR_MAX);
return 0;
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
2
3
4
5
6
7
8
9
10
11
12
13
14

- 示例:浮点类型中各个数据类型的取值范围
c
#include <stdio.h>
#include <float.h>
int main(void) {
/*
* 输出各种浮点类型的取值范围
*/
printf("float 的取值范围:%e 到 %e\n", -FLT_MAX, FLT_MAX);
printf("double 的取值范围:%e 到 %e\n", -DBL_MAX, DBL_MAX);
printf("long double 的取值范围:%Le 到 %Le\n", -LDBL_MAX, LDBL_MAX);
return 0;
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
2
3
4
5
6
7
8
9
10
11
12
13
14

1.5 数据类型转换
1.5.1 概述
数据类型转换就是把一种数据类型的值,转换成另一种数据类型。
提醒
类型转换改变的是“如何解释和表示这个值”,有时会导致精度丢失、溢出或数据含义发生变化。
- C 语言中的数据类型转换分为两类,如下所示:
提醒
① 不同类型参与运算时,C 经常会先自动转换成共同类型。
②
char、short参与很多运算时会先提升为int。③ 大范围转小范围、浮点转整数,可能丢失数据。
④
signed和unsigned混合运算,是最需要警惕的类型转换之一。
1.5.2 隐式类型转换(自动类型转换)
- 所谓的隐式类型转换是编译器自动帮我们完成的。
提醒
- ① 从小范围类型转换为大范围类型,通常比较安全,不会丢失精度。
- ② 从大范围类型转换为小范围类型,本质上是向 0 截取,会丢失精度,不是很安全(慎用)。
- ③ 对于从大范围类型转换为小范围类型,在 Java 中的编译器会直接报错(除非程序员主动强制转换);而 C 语言不会,这也是许多人为什么吐槽 C 语言不安全的原因之一(也可以让 CLANG 开启告警,使其编译不通过)。
- ④ 在发生算术运算之前,会进行整型提升,如:char --> int 以及 short --> int。
- 示例:小范围 --> 大范围
c
#include <stdio.h>
int main(void) {
/*
* 小范围 --> 大范围
*/
int a = 10;
double d = a;
printf("a = %d\n", a);
printf("d = %f\n", d);
return 0;
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
2
3
4
5
6
7
8
9
10
11
12
13
14
15

- 示例:大范围 --> 小范围
c
#include <stdio.h>
int main(void) {
/*
* 小范围 --> 大范围
*/
int a = 10;
double d = a;
printf("a = %d\n", a);
printf("d = %f\n", d);
return 0;
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
2
3
4
5
6
7
8
9
10
11
12
13
14
15

1.5.3 显示类型转换(强制类型转换)
- 格式:
c
目标数据类型 变量 = (目标数据类型)表达式;1
提醒
- ① 显式类型转换 = 强制类型转换。
- ② 当两个不同类型的数据进行运算时,C 通常会先把它们转换成某个共同类型。
- ⑤ 整数 --> 浮点数,通常比较安全;浮点数 --> 整数,会丢失精度。
- ⑥ 大整数 --> 小整数,可能造成数据丢失。
- ⑦
signed和unsigned转换最容易出问题,不要随意混用有符号整数和无符号整数。
- 示例:大范围 --> 小范围(可能损失精度)
c
#include <stdio.h>
int main(void) {
/*
* 大范围 --> 小范围
*/
double d = 3.14;
// 强制类型转换
int a = (int)d;
printf("d = %f\n", d);
printf("a = %d\n", a);
return 0;
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

- 示例:大整数 --> 小整数(可能造成数据丢失)
c
#include <stdio.h>
int main(void) {
/*
* 大整数 --> 小整数(能造成数据丢失。)
*/
int a = 300;
// 强制类型转换
unsigned char b = (unsigned char)a;
printf("a = %d\n", a);
printf("b = %d\n", b);
return 0;
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

1.5.4 指针的类型转换
- 对于
普通对象指针是可以隐式转换为void *类型的,如下所示:
c
int num = 10;
int *p = #
void* ptr = p ;1
2
3
4
2
3
4
- 对于
void *指针类型也可以自动转换为普通对象指针(不推荐),如下所示:
c
int *p = malloc(sizeof(int)); // 合法,但不推荐1
c
int *p = (int*)malloc(sizeof(int)); // 推荐1
- 示例:
c
#include <stdio.h>
#include <string.h>
/**
* 核心:万能交换函数。不管传入的是 int、float 还是指针,只要告诉它大小就能交换。
* @param a 指针地址
* @param b 指针地址
* @param size
*/
static void swap(void *a, void *b, const size_t size) {
char temp[size]; // 开辟一块临时内存
memcpy(temp, a, size); // 把 a 的内存拷贝到 temp
memcpy(a, b, size); // 把 b 的内存拷贝到 a
memcpy(b, temp, size); // 把 temp 的内存拷贝到 b
}
int main() {
// 1. 交换整数
int x = 10, y = 20;
// 类似于 void* ptr = &x
// 类似于 void* ptr = &y
swap(&x, &y, sizeof(int));
printf("整数交换: x=%d, y=%d\n", x, y);
// 2. 交换浮点数
float f1 = 3.14f, f2 = 2.71f;
swap(&f1, &f2, sizeof(float));
printf("浮点交换: f1=%.2f, f2=%.2f\n", f1, f2);
// 3. 交换字符串指针
char *s1 = "Hello", *s2 = "World";
swap(&s1, &s2, sizeof(char*));
printf("指针交换: s1=%s, s2=%s\n", s1, s2);
return 0;
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36

- 示例:
c
#include <stdio.h>
#include <stdlib.h>
int main(void) {
const int SIZE = 10;
int *p = nullptr;
p = (int *)malloc(sizeof(int) * SIZE);
if (p == nullptr) {
perror("malloc error");
return 1;
}
// 初始化数组
for (int i = 0; i < SIZE; i++) {
p[i] = i + 1;
}
// 遍历数组
for (int i = 0; i < SIZE; i++) {
printf("%d ", p[i]);
}
// 释放内存
free(p);
p = nullptr;
return 0;
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32

1.6 生活中的数据类型
- 在生活中,在电商平台购物的时候会遇到各种类型的数据,如下所示:

- 购物车中可能包含的信息,如下所示:
| 货号 | 商品名称 | 单位 | 单价(元) | 数量 | 小计 |
|---|---|---|---|---|---|
| A001 | 无线蓝牙耳机 | 件 | 199.00 | 2 | 398.00 |
| B052 | Type-C 数据线 | 条 | 25.50 | 5 | 127.50 |
- 在购物车中,包含了很多信息,如:货号、商品名称等;分析之后,我们发现:
| 商品的 SKU | 类型 | 数据类型 |
|---|---|---|
| 货号 | 整数/字符串 | 整型/字符串 |
| 商品名称 | 字符串 | 字符串 |
| 单位 | 字符串 | 字符串 |
| 单价 | 小数 | 浮点型 |
| 数量 | 整数 | 整型/ |
| 小计 | 小数 | 浮点型 |