Skip to content

第一章:数据类型(⭐)

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_t8 位有符号整数
int16_t16 位有符号整数
int32_t32 位有符号整数
int64_t64 位有符号整数
uint8_t8 位无符号整数
uint16_t16 位无符号整数
uint32_t32 位无符号整数
uint64_t6464 位无符号整数

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;
}

1.3 浮点类型

1.4 字符类型

1.5 布尔类型

1.6 数据类型转换

第二章:运算符

2.1 概述

2.2 算术运算符

2.3 关系运算符(比较运算符)

2.4 逻辑运算符

2.5 位运算符

2.6 三元运算符

2.7 运算符优先级

Released under the MIT License.