深入浅出 C/C++ 数据类型:从入门到踩坑

深入浅出 C/C++ 数据类型:从入门到踩坑

原创不易,先赞后看,养成习惯。这篇文章我尽量把 C/C++ 的数据类型讲透,顺便带上一些我平时写代码时踩过的坑。

前言

刚学 C/C++ 的时候,总觉得数据类型就是 intcharfloat 那么简单。后来项目做大了,才发现类型系统是 C/C++ 的基石,也是很多诡异 bug 的源头。今天我就从头捋一遍,把常用的、不常用的,以及那些容易混淆的点都过一下。


一、基本内置类型(Primary Types)

1. 整型家族

类型 典型大小(32位平台) 范围(有符号)
char 1 字节 -128 ~ 127
short 2 字节 -32768 ~ 32767
int 4 字节 -2^31 ~ 2^31-1
long 4 字节(Windows)/ 8 字节(Linux 64位) 视平台
long long 8 字节 -2^63 ~ 2^63-1

注意char 比较特殊,它虽然本质是整型,但通常用来表示字符。而且 char 是否有符号是实现定义 的,有的编译器默认 charsigned char,有的默认 unsigned char。所以如果你需要明确符号,请显式写 signed charunsigned char

C++ 11 起 ,多了固定宽度的类型,在 <cstdint> 里:

cpp 复制代码
int8_t, uint8_t, int16_t, uint16_t, int32_t, uint32_t, int64_t, uint64_t

我强烈建议你在写跨平台代码时用这些,别再迷信 int 一定占 4 字节了。

2. 浮点类型

类型 精度 通常字节数
float 约 7 位十进制有效数字 4
double 约 15~16 位 8
long double 更高精度(实现定义) 常见 12/16 字节

坑点 :浮点数比较大小千万别用 ==,要用 fabs(a - b) < eps

  1. 布尔类型(C++)

C 语言没有真正的布尔类型(C99 有 _Bool),C++ 有 bool,取值 true / false,本质是 1 字节。


二、修饰符(Modifiers)

可以搭配整型使用的修饰符有:

  • signed / unsigned ------ 有无符号
  • short / long ------ 长度

组合示例:

cpp 复制代码
unsigned int ui = 42u;      // 后缀 u 表示 unsigned
long long ll = 123LL;       // LL 后缀

重要规则unsignedsigned 混合运算时,会发生隐式类型提升 ,通常 signed 会被转为 unsigned。这是很多死循环的元凶。

举个例子:

cpp 复制代码
for (int i = 10; i >= 0; --i) { ... }  // 正常
for (unsigned i = 10; i >= 0; --i) { ... } // 死循环!因为 i>=0 永远为真

三、构造类型(Derived/Compound Types)

1. 数组(Array)

cpp 复制代码
int arr[10];                // 10 个 int
int arr2[] = {1,2,3};       // 自动推导大小为 3
char str[] = "hello";       // 大小 6(含 '\0')

数组名在大多数情况下会退化为指向首元素的指针,除了 sizeof& 操作。

:数组作为函数参数时,会退化为指针,所以 sizeof 拿不到数组长度。通常你需要额外传一个长度参数。

2. 结构体(struct)/ 联合体(union)/ 枚举(enum)

  • struct:字段按声明顺序排列,但存在内存对齐,字节数不一定等于各字段大小之和。
  • union:所有成员共用一块内存,大小等于最大成员的大小。
  • enum:枚举常量底层是 int,C++11 支持枚举类 enum class,更安全。

四、指针与引用(C++)

指针

cpp 复制代码
int a = 10;
int* p = &a;   // p 保存 a 的地址
*p = 20;       // 解引用

指针的大小在 32 位平台是 4 字节,64 位是 8 字节。void* 可以指向任意类型,但不能直接解引用。

引用(C++ only)

cpp 复制代码
int a = 10;
int& ref = a;   // ref 是 a 的别名,不占额外内存(逻辑上)
ref = 20;       // a 变为 20

引用必须在声明时初始化,且不能改变绑定。引用比指针更安全,但底层还是指针实现的。


五、类型别名

  • typedef int my_int;
  • using my_int = int; (C++11)

建议用 using,语法更直观,尤其在模板别名时。


六、类型转换(重中之重)

C 风格强制转换

cpp 复制代码
int a = 10;
double b = (double)a;

C++ 四种 cast

  • static_cast:最常用,编译时检查,用于相关类型转换(如 int->double,基类子类指针)。
  • dynamic_cast:运行时检查,用于多态类型向下转型(需要 RTTI)。
  • const_cast:去掉 const 属性(谨慎使用,只有在你确定原始对象非 const 时才能改)。
  • reinterpret_cast:最危险,重新解释二进制位,基本属于"我明确知道我在做什么"时才用。

建议 :日常开发尽量少用强制转换,设计良好的接口应该避免转换。实在需要,优先 static_cast


七、字面量后缀

后缀 含义
u / U unsigned
l / L long
ll / LL long long
f / F float
lu / UL unsigned long
z (C++23) size_t

例:

cpp 复制代码
auto x = 3.14f;   // float
auto y = 42ULL;   // unsigned long long

八、类型推导(C++11 起)

  • auto:自动推导类型(会忽略引用和 const,除非显式写 const auto&)。
  • decltype:推导表达式的类型,保留引用和 const。
cpp 复制代码
int a = 10;
const int& ra = a;
auto b = ra;      // b 是 int,不是 const int&
decltype(ra) c = a; // c 是 const int&

九、完整示例:一个综合演练

下面我写一个小程序,把上面大部分知识点串起来,并附带一些容易犯错的点。

cpp 复制代码
#include <iostream>
#include <cstdint>
#include <iomanip>
#include <cmath>

// 自定义类型别名
using byte_t = uint8_t;   // 无符号 8 位

// 结构体,注意内存对齐
struct Student {
    char name[20];   // 20 字节
    uint32_t id;     // 4 字节,但可能偏移到 20 的倍数(对齐)
    float score;     // 4 字节
    // 理论总大小 28,但实际可能 32(取决于对齐)
};

// 枚举类,更安全
enum class Color : uint8_t {
    Red,
    Green,
    Blue
};

int main() {
    // 1. 固定宽度整型
    int32_t x = 100;
    uint64_t y = 0xFFFFFFFFFFFFFFFFULL;  // 最大无符号64位
    std::cout << "x = " << x << ", y = " << y << std::endl;

    // 2. 浮点数精度问题
    double a = 0.1 + 0.2;
    double b = 0.3;
    const double eps = 1e-9;
    if (std::fabs(a - b) < eps) {
        std::cout << "0.1+0.2 == 0.3 (within tolerance)" << std::endl;
    } else {
        std::cout << "Not equal!" << std::endl;
    }

    // 3. unsigned 陷阱演示
    unsigned int count = 5;
    // 注意:下面这个循环不会执行?错!因为 i 是 int,但 count 是 unsigned,
    // 比较时 count 被转为 unsigned,i 也被转为 unsigned,-1 变成很大的数,条件为假
    // 但更常见的坑是 for (int i = count - 1; i >= 0; --i) 当 count=0 时死循环
    // 这里我们演示一个安全的写法
    std::cout << "逆序输出 0~4: ";
    for (int i = static_cast<int>(count) - 1; i >= 0; --i) {
        std::cout << i << " ";
    }
    std::cout << std::endl;

    // 4. 数组与指针
    int arr[] = {1, 2, 3, 4, 5};
    int* p = arr;  // 退化为指针
    std::cout << "数组大小: " << sizeof(arr) / sizeof(arr[0]) << std::endl;
    std::cout << "指针大小: " << sizeof(p) << std::endl;  // 64位下 8

    // 5. 结构体大小(对齐)
    std::cout << "Student 大小: " << sizeof(Student) << " 字节" << std::endl;

    // 6. 引用与 auto
    int value = 42;
    int& ref = value;
    auto copy = ref;     // copy 是 int
    decltype(ref) ref2 = value; // ref2 是 int&
    ref2 = 100;
    std::cout << "value = " << value << std::endl;

    // 7. 枚举类
    Color c = Color::Red;
    // 不能隐式转为 int
    // int colorValue = c; // 编译错误
    int colorValue = static_cast<int>(c);
    std::cout << "Color Red = " << colorValue << std::endl;

    // 8. const_cast 示例(谨慎)
    const int constVal = 30;
    // 注意:下面这种修改实际上会导致未定义行为,因为 constVal 可能存于只读区
    // 这里只是演示语法,实际项目中不要这么写
    // int* pConst = const_cast<int*>(&constVal);
    // *pConst = 50; // 危险!

    // 安全用法:去除函数形参中的 const(但实参本身非常量)
    auto func = [](const int* p) {
        // 已知 p 指向的是非常量内存
        int* q = const_cast<int*>(p);
        *q = 999;
    };
    int normal = 123;
    func(&normal);
    std::cout << "normal after const_cast: " << normal << std::endl;

    return 0;
}

输出示例(64位 Linux)

复制代码
x = 100, y = 18446744073709551615
0.1+0.2 == 0.3 (within tolerance)
逆序输出 0~4: 4 3 2 1 0 
数组大小: 5
指针大小: 8
Student 大小: 32 字节
value = 100
Color Red = 0
normal after const_cast: 999

十、总结与建议

  1. 跨平台优先用固定宽度类型int32_tuint64_t 等)。
  2. 小心 unsigned 和 signed 混用,循环变量尤其注意。
  3. 浮点数永远不要直接 == ,用 eps 比较。
  4. 结构体对齐 可以用 #pragma pack__attribute__((packed)),但会影响性能,非必要不碰。
  5. 类型转换优先使用 C++ cast,让意图明确。
  6. auto 很好用 ,但要记得引用和 const 会被剥离,需要时可以显式写 const auto&

写这篇的时候,我尽可能把日常开发中容易翻车的地方都点到了。如果还有我没说清楚的,欢迎评论区交流,我看到都会回。

如果觉得有用,点个赞再走呗~

相关推荐
无垠的广袤1 小时前
【工业树莓派 CM0 Dev Board】扩展板设计
linux·python·嵌入式硬件·pcb设计·模块化·传感器
白色冰激凌2 小时前
[SECS/GEM研究] (三)SECS-I 串口上消息怎么分块和重传
c++·secs/gem
会编程的土豆2 小时前
MySQL 入门:库、表、行、主键是什么
linux·数据库·网络协议·http
光头闪亮亮2 小时前
Fyne ( go跨平台GUI )项目实战-项目开发必备基础知识(中)
android·c++·go
光头闪亮亮2 小时前
Fyne ( go跨平台GUI )项目实战-项目开发必备基础知识(下)
android·c++·go
阿扬ABCD2 小时前
ipmitool的Windows版本编译
windows·驱动开发
_wyt0012 小时前
洛谷 P7912 [CSP-J 2021] 小熊的果篮 题解
c++·队列
△曉風殘月〆2 小时前
如何在Linux中安装Qt开发环境
linux·运维·qt
choumin3 小时前
创建型模式——原型模式
c++·设计模式·原型模式·创建型模式