第一章:数据类型(⭐)
1.1 概述
根据
变量
中存储
的值
的不同
,我们可以将变量
分为两类:普通变量
:变量所对应的内存中存储的是普通值
。指针变量
:变量所对应的内存中存储的是另一个变量的地址
。
如下图所示:
NOTE
普通变量和指针变量的相同点:
- 普通变量有内存空间,指针变量也有内存空间。
- 普通变量有内存地址,指针变量也有内存地址。
- 普通变量所对应的内存空间中有值,指针变量所对应的内存空间中也有值。
普通变量和指针变量的不同点:
- 普通变量所对应的内存空间存储的是普通的值,如:整数、小数、字符等;指针变量所对应的内存空间存储的是另外一个变量的地址。
- 普通变量有普通变量的运算方式,而指针变量有指针变量的运算方式(后续讲解)。
- 那么,在 C 语言中变量的数据类型就可以这么划分,如下所示:
NOTE
- 根据
普通变量
中存储
的值
的类型不同,可以将普通变量类型
划分为基本数据类型
(整型、字符类型、浮点类型、布尔类型)和复合数据类型
(数组类型、结构体类型、共用体类型、枚举类型)。 - 根据
指针变量
所指向空间
中存储
的值
的类型不同,可以将指针类型
分为基本数据类型指针
、复合数据类型指针
、函数指针
、数组指针
等,例如:如果指针所指向的空间保存的是 int 类型,那么该指针就是 int 类型的指针。
1.2 整数类型
1.2.1 概述
- 整数类型简称整型,用于存储整数值,如:12、20、50 等。
- 根据所占
内存空间
大小的不同,可以将整数类型划分为: - ① 短整型:
类型 | 存储空间(内存空间) | 取值范围 |
---|---|---|
unsigned short (无符号短整型) | 2 字节 | -32,768 (- 2^15) ~ 32,767 (2^15 -1) |
[signed] short(有符号短整型,默认) | 2 字节 | 0 ~ 65,535 (2^16 - 1) |
- ② 整型:
类型 | 存储空间(内存空间) | 取值范围 |
---|---|---|
unsigned int(无符号整型) | 4 字节(通常) | -2147483648(- 2^31) ~ 2147483647 (2^31-1) |
[signed] int(有符号整型,默认) | 4 字节(通常) | 0 ~ 4294967295 (0 ~2^32 -1) |
- ③ 长整型:
类型 | 存储空间(内存空间) | 取值范围 |
---|---|---|
unsigned long(无符号长整型) | 4 字节(通常) | - 2^31 ~ 2^31-1 |
[signed] long(有符号长整型,默认) | 4 字节(通常) | 0 ~2^31 -1 |
- ④ 长长整型:
类型 | 存储空间(内存空间) | 取值范围 |
---|---|---|
unsigned long long(无符号长整型) | 8 字节(通常) | - 2^63 ~ 2^63-1 |
[signed] long long(有符号长整型,默认) | 8 字节(通常) | 0 ~2^34 -1 |
NOTE
- ① C 语言默认没有规定各种数据类型所占存储单元的长度,但是通常需要遵守:
sizeof(short int) ≤ sizeof(int) ≤ sizeof(long int) ≤ sizeof(long long)
,具体的存储空间由编译系统自行决定;其中,sizeof 是测量类型或变量、常量长度的运算符。 - ② short 至少 2 个字节,long 至少 4 个字节。
- ③ 之所以这么规定,是为了可以让 C 语言长久使用,因为目前主流的 CPU 都是 64 位,但是在 C语言刚刚出现的时候,CPU 还是以 8 位和 16 位为主。如果那时候就将整型定死为 8 位或 16 位,那么现在我们肯定不会再学习 C 语言了。
- ④ 整型分为有符号 signed 和无符号 unsigned 两种,默认是 signed。
- ⑤ 在实际开发中,
最常用整型
就是int
类型了,如果取值范围不够,就使用 long 或 long long 。 - ⑥ C 语言中的
格式占位符
非常多,只需要大致了解即可;因为,我们在实际开发中,一般都会使用 C++ 或 Rust 以及其它的高级编程语言,如:Java 等,早已经解决了需要通过格式占位符
来输入和输出变量。
1.2.2 短整型(了解)
- 语法:
c
unsigned short x = 10 ; // 无符号短整型
c
short x = -10; // 有符号短整型
NOTE
- ① 有符号表示的是正数、负数和 0 ,即有正负号。无符号表示的是 0 和正数,即正整数,没有符号。
- ② 在
printf
中无符号短整型(unsigned short)
的格式占位符
是%hu
,有符号短整型(signed short)
的格式占位符
是%hd
。 - ③ 可以通过
sizeof
运算符获取无符号短整型(unsigned short)
和有符号短整型(signed short)
的存储空间(所占内存空间)
。 - ③ 可以通过
#include <limits.h>
来获取无符号短整型(unsigned short)
和有符号短整型(signed short)
的取值范围
。
- 示例:定义和打印短整型变量
c
#include <stdio.h>
int main() {
// 定义有符号 short 类型
signed short s1 = -100;
printf("s1 = %hd \n", s1); // s1 = -100
// 定义无符号 short 类型
unsigned short s2 = 100;
printf("s2 = %hu \n", s2); // s2 = 100
// 定义 short 类型,默认是有符号
short s3 = -200;
printf("s3 = %hd \n", s3); // s3 = -200
return 0;
}
- 示例:获取存储空间
c
#include <stdio.h>
int main() {
size_t s1 = sizeof(unsigned short);
printf("unsigned short 的存储空间是 %zu 字节 \n", s1); // 2
size_t s2 = sizeof(signed short);
printf("signed short 的存储空间是 %zu 字节 \n", s2); // 2
size_t s3 = sizeof(short);
printf("short 的存储空间是 %zu 字节 \n", s3); // 2
return 0;
}
- 示例:获取取值范围
c
#include <limits.h>
#include <stdio.h>
int main() {
printf("unsigned short 类型的范围是[0,%hu]\n", USHRT_MAX); // [0,65535]
printf("short 类型的范围是[%hd,%hd]\n", SHRT_MIN,SHRT_MAX); // [-32768,32767]
return 0;
}
1.2.3 整型
- 语法:
c
unsigned int x = 10 ; // 无符号整型
c
int x = -10; // 有符号整型
NOTE
- ① 有符号表示的是正数、负数和 0 ,即有正负号。无符号表示的是 0 和正数,即正整数,没有符号。
- ② 在
printf
中无符号整型(unsigned int)
的格式占位符
是%u
,有符号整型(signed int)
的格式占位符
是%d
。 - ③ 可以通过
sizeof
运算符获取无符号整型(unsigned int)
和有符号整型(signed int)
的存储空间(所占内存空间)
。 - ③ 可以通过
#include <limits.h>
来获取无符号整型(unsigned int)
和有符号整型(signed int)
的取值范围
。
- 示例:定义和打印整型变量
c
#include <stdio.h>
int main() {
// 定义有符号 int 类型
signed int i1 = -100;
printf("i1 = %d \n", i1); // i1 = -100
// 定义无符号 int 类型
unsigned int i2 = 100;
printf("i2 = %u \n", i2); // i2 = 100
// 定义 int 类型,默认是有符号
short i3 = -200;
printf("i3 = %d \n", i3); // i3 = -200
return 0;
}
- 示例:获取存储空间
c
#include <stdio.h>
int main() {
size_t i1 = sizeof(unsigned int);
printf("unsigned int 的存储空间是 %zu 字节 \n", i1); // 4
size_t i2 = sizeof(signed int);
printf("signed int 的存储空间是 %zu 字节 \n", i2); // 4
size_t i3 = sizeof(int);
printf("int 的存储空间是 %zu 字节 \n", i3); // 4
return 0;
}
- 示例:获取取值范围
c
#include <limits.h>
#include <stdio.h>
int main() {
printf("unsigned int 类型的范围是[0,%u]\n", UINT_MAX); // [0,4294967295]
printf("int 类型的范围是[%d,%d]\n", INT_MIN,INT_MAX); // [-2147483648,2147483647]
return 0;
}
1.2.4 长整型(了解)
- 语法:
c
unsigned long x = 10 ; // 无符号长整型
c
long x = -10; // 有符号长整型
NOTE
- ① 有符号表示的是正数、负数和 0 ,即有正负号。无符号表示的是 0 和正数,即正整数,没有符号。
- ② 在
printf
中无符号长整型(unsigned long)
的格式占位符
是%lu
,有符号长整型(signed long)
的格式占位符
是%ld
。 - ③ 可以通过
sizeof
运算符获取无符号长整型(unsigned long)
和有符号长整型(signed long)
的存储空间(所占内存空间)
。 - ③ 可以通过
#include <limits.h>
来获取无符号长整型(unsigned long)
和有符号长整型(signed long)
的取值范围
。
- 示例:定义和打印长整型变量
c
#include <stdio.h>
int main() {
// 定义有符号 long 类型
signed long l1 = -100;
printf("l1 = %ld \n", l1); // l1 = -100
// 定义无符号 long 类型
unsigned long l2 = 100;
printf("l2 = %lu \n", l2); // l2 = 100
// 定义 long 类型,默认是有符号
long l3 = -200;
printf("l3 = %ld \n", l3); // l3 = -200
return 0;
}
- 示例:获取存储空间
c
#include <stdio.h>
int main() {
size_t l1 = sizeof(unsigned long);
printf("unsigned long 的存储空间是 %zu 字节 \n", l1); // 4
size_t l2 = sizeof(signed long);
printf("signed long 的存储空间是 %zu 字节 \n", l2); // 4
size_t l3 = sizeof(long);
printf("long 的存储空间是 %zu 字节 \n", l3); // 4
return 0;
}
- 示例:获取取值范围
c
#include <limits.h>
#include <stdio.h>
int main() {
printf("unsigned long 类型的范围是[0,%lu]\n", ULONG_MAX); // [0,4294967295]
printf("long 类型的范围是[%ld,%ld]\n", LONG_MIN,LONG_MAX); // [-2147483648,2147483647]
return 0;
}
1.2.5 长长整型(了解)
- 语法:
c
unsigned long long x = 10 ; // 无符号长长整型
c
long long x = -10; // 有符号长长整型
NOTE
- ① 有符号表示的是正数、负数和 0 ,即有正负号。无符号表示的是 0 和正数,即正整数,没有符号。
- ② 在
printf
中无符号长长整型(unsigned long long)
的格式占位符
是%llu
,有符号长长整型(signed long long)
的格式占位符
是%lld
。 - ③ 可以通过
sizeof
运算符获取无符号长长整型(unsigned long long)
和有符号长长整型(signed long long)
的存储空间(所占内存空间)
。 - ③ 可以通过
#include <limits.h>
来获取无符号长长整型(unsigned long long)
和有符号长长整型(signed long long)
的取值范围
。
- 示例:定义和打印长长整型变量
c
#include <stdio.h>
int main() {
// 定义有符号 long long 类型
signed long long ll1 = -100;
printf("ll1 = %lld \n", ll1); // ll1 = -100
// 定义无符号 long long 类型
unsigned long long ll2 = 100;
printf("ll2 = %llu \n", ll2); // ll2 = 100
// 定义 long long 类型,默认是有符号
long long ll3 = -200;
printf("ll3 = %lld \n", ll3); // ll3 = -200
return 0;
}
- 示例:获取存储空间
c
#include <stdio.h>
int main() {
size_t ll1 = sizeof(unsigned long long);
printf("unsigned long long 的存储空间是 %zu 字节 \n", ll1); // 8
size_t ll2 = sizeof(signed long long);
printf("signed long long 的存储空间是 %zu 字节 \n", ll2); // 8
size_t ll3 = sizeof(long long);
printf("long long 的存储空间是 %zu 字节 \n", ll3); // 8
return 0;
}
- 示例:获取取值范围
c
#include <limits.h>
#include <stdio.h>
int main() {
printf("unsigned long long 类型的范围是[0,%llu]\n", ULLONG_MAX); // [0,18446744073709551615]
printf("long long 类型的范围是[%lld,%lld]\n", LLONG_MIN,LLONG_MAX); // [-9223372036854775808,9223372036854775807]
return 0;
}
1.2.6 字面量后缀
字面量
是源代码
中一个固定值
的表示方法
,用于直接表示数据,即:
c
int num1 = 100; // 100 就是字面量
c
long num2 = 100L; // 100L 就是字面量
c
long long num3 = 100LL; // 100LL 就是字面量
NOTE
- ① 默认情况下的,整数字面量的类型是 int 类型。
- ② 如果需要表示 long 类型的字面量,需要添加后缀 l 或 L ,建议 L。
- ③ 如果需要表示 long long类型的字面量,需要添加后缀 ll 或 LL,建议 LL 。
- ④ 如果需要表示无符号整数类型的字面量,需要添加 u 或 U,建议 U 。
- 示例:
c
#include <stdio.h>
int main() {
int num = 100;
printf("num = %d\n", num); // num = 100
long num2 = 100L;
printf("num2 = %ld\n", num2); // num2 = 100
long long num3 = 100LL;
printf("num3 = %lld\n", num3); // num3 = 100
unsigned int num4 = 100U;
printf("num4 = %u\n", num4); // num4 = 100
unsigned long num5 = 100LU;
printf("num5 = %lu\n", num5); // num5 = 100
unsigned long long num6 = 100ULL;
printf("num6 = %llu\n", num6); // num6 = 100
return 0;
}
1.2.7 精确宽度类型
- 在前文,我们了解到 C 语言的整数类型(short 、int、long、long long)在不同计算机上,占用的字节宽度可能不一样。但是,有的时候,我们希望整数类型的存储空间(字节宽度)是精确的,即:在任意平台(计算机)上都能一致,以提高程序的可移植性。
NOTE
- Java 语言中的数据类型的存储空间(字节宽度)是一致的,这也是 Java 语言能够跨平台的重要原因之一(最主要的原因还是 JVM)。
- 在嵌入式开发中,使用精确宽度类型可以确保代码在各个平台上的一致性。
- 在 C 语言的标准头文件
<stdint.h>
中定义了一些新的类型别名,如下所示:
类型名称 | 含义 |
---|---|
int8_t | 8 位有符号整数 |
int16_t | 16 位有符号整数 |
int32_t | 32 位有符号整数 |
int64_t | 64 位有符号整数 |
uint8_t | 8 位无符号整数 |
uint16_t | 16 位无符号整数 |
uint32_t | 32 位无符号整数 |
uint64_t64 | 64 位无符号整数 |
NOTE
上面的这些类型都是类型别名,编译器会指定它们指向的底层类型,如:在某个系统中,如果 int 类型是 32 位,那么 int32_t 就会指向 int ;如果 long 类型是 32 位,那么 int32_t 就会指向 long。
- 示例:
c
#include <stdio.h>
#include <stdint.h>
int main() {
// 变量 x32 声明为 int32_t 类型,可以保证是 32 位(4个字节)的宽度。
int32_t x32 = 45933945;
printf("x32 = %d \n", x32); // x32 = 45933945
return 0;
}
1.2.8 sizeof 运算符
- 语法:
c
sizeof(表达式)
NOTE
- ① 表达式可以是任何类型的数据类型、变量或常量。
- ② 返回某种数据类型或某个值占用的字节数量,并且
sizeof(...)
的返回值类型
是size_t
。 - ③ 在
printf
中使用占位符%zu
来处理size_t
类型的值。
- 示例:参数是数据类型
c
#include <stdio.h>
#include <stddef.h>
int main() {
size_t s = sizeof(int);
printf("%zu \n", s); // 4
return 0;
}
- 示例:参数是变量
c
#include <stdio.h>
#include <stddef.h>
int main() {
int num = 10;
size_t s = sizeof(num);
printf("%zu \n", s); // 4
return 0;
}
- 示例:参数是常量
c
#include <stdio.h>
#include <stddef.h>
int main() {
size_t s = sizeof(10);
printf("%zu \n", s); // 4
return 0;
}
1.2.9 数值溢出
所谓的数值溢出指的是:当超过一个数据类型能够存放的最大范围的时候,数值就会溢出。
- 如果达到了最⼤值,再进行加法计算,数据就会超过该类型能够表示的最大值,叫做上溢出。
- 如果这个数⽬前是最小值,再进行减法计算, 数据就会超过该类型的最小值, 叫做下溢出。
在 C 语言中,
整数
的数据类型
分为无符号
和有符号
的,其在底层表示和存储是不一样的,即:- 无符号整数不使用最高位作为符号位,所有的位都用于表示数值,如:对于一个 4 位无符号整数,二进制表示的范围是从 0000 到 1111 ,那么十进制表示的范围是从 0 到 15。
- 有符号整数使用最高位作为符号位,这意味着它们可以表示正数和负数,通常使用补码来表示有符号整数。在补码表示法中:最高位为 0 表示正数、最高位为 1 表示负数,如:对于一个4位有符号整数,二进制表示的范围是从 0000(0) 到 0111 (7),1000 (-8)到 1111(-1)。
NOTE
- 在 C 语言中,无符号整数,最高位不是符号位,它只是数值的一部分。
- 在 C 语言中,有符号整数,最高位是符号位,用于表示正负数。
对于无符号的数值溢出:
- 当数据到达最大值的时候,再加 1 就会回到无符号数的最小值。
- 当数据达到最小值的时候,再减 1 就会回到无符号数的最大值。
那么,无符号的上溢出,原理就是这样的:
- 那么,无符号的下溢,原理就是这样的(需要先借位,然后再减):
对于有符号的数值溢出:
- 当数据到达最大值的时候,再加 1 就会回到有符号数的最小值。
- 当数据达到最小值的时候,再减 1 就会回到有符号数的最大值。
那么,有符号的上溢出,原理就是这样的:
- 那么,有符号的下溢出,原理就是这样的:
NOTE
在实际开发中,选择合适的数据类型,以避免数值溢出问题!!!
- 示例:无符号的上溢出和下溢出
c
#include <limits.h>
#include <stdio.h>
int main() {
unsigned short s1 = USHRT_MAX + 1;
printf("无符号的上溢出 = %hu \n", s1); // 0
unsigned short s2 = 0 - 1;
printf("无符号的下溢出 = %hu \n", s2); // 65535
return 0;
}
- 示例:有符号的上溢出和下溢出
c
#include <limits.h>
#include <stdio.h>
int main() {
short s1 = SHRT_MAX + 1;
printf("有符号的上溢出 = %hd \n", s1); // -32768
short s2 = SHRT_MIN - 1;
printf("有符号的下溢出 = %hd \n", s2); // 32767
return 0;
}