2、变量与基本数据类型:从内存到代码的第一次握手

各位同学,今天我们来聊聊C++里最基础、也最核心的东西——变量和数据类型。说实话,这部分内容看起来简单,但我在面试过不少自称“精通C++”的候选人时,发现很多人其实栽在了这里。嗯,咱们从头捋一遍。

2.1 变量定义与初始化:给数据一个“家”

变量是什么?说白了,就是内存里一块有名字的空间。你给它起个名字,它帮你存数据。

int age;           // 定义了一个整型变量,叫age
age = 25;          // 赋值,把25放进去
int score = 90;    // 定义+初始化,一步到位

我个人习惯是:定义变量时尽量初始化。为什么?因为未初始化的变量里存的是“垃圾值”——就是那块内存之前留下的随机数据。我曾经在项目里排查过一个bug,查了整整两天,最后发现是一个局部变量没初始化,在某些条件下读到了脏数据。从那以后,我写代码必初始化。

核心原则:定义变量时立即初始化,不要留到后面再赋值。

C++11之后,还多了一种初始化方式——花括号初始化(也叫列表初始化):

int x{10};        // 安全,不会发生窄化转换
int y = {20};     // 也可以
int z{};          // 初始化为0

花括号初始化的好处是:类型不匹配时会报错,而不是悄悄截断。比如 int a{3.14} 会编译失败,而 int a = 3.14 只会把小数部分丢掉。你想想看,哪个更安全?

2.2 整型家族:int、short、long、long long

整型就是存整数的。但C++给了你好几个选择,每个占的内存大小不一样:

类型 常见大小 取值范围(以32位系统为例)
short 2字节 -32768 ~ 32767
int 4字节 -21亿 ~ 21亿
long 4或8字节(看平台) 至少和int一样大
long long 8字节 非常大,9e18级别

我的建议:日常用 int 就够了。如果确定数值不会超过32767,可以用 short 省内存(比如嵌入式开发)。如果数值可能很大,直接用 long long,别纠结。

我记得有一次做金融交易系统,需要存储毫秒级时间戳,数值轻松超过21亿。有人用了 int,结果上线后数据溢出,交易记录全乱了。嗯,这种坑踩一次就够了。

2.3 浮点型:float 与 double

浮点型用来存小数。两个选择:

  • float:4字节,精度约7位有效数字
  • double:8字节,精度约15位有效数字
float pi = 3.14159f;      // 注意后面的f,表示float字面量
double e = 2.718281828;   // 默认就是double

避坑指南:千万不要用 float 做精确计算,比如金额、坐标。我曾经在图形学项目里用 float 累加坐标,跑了10万帧之后,位置漂移了半个屏幕。换成 double 就好了。

另外,浮点数比较时别用 ==,因为精度问题。应该用差值小于某个极小值来判断:

if (fabs(a - b) < 1e-6) {
    // 认为a和b相等
}

2.4 字符型:char

char 存单个字符,占1字节。本质上它是个小整数:

char ch = 'A';        // 实际存的是65(ASCII码)
char digit = 48;      // 存的是字符'0'
char newline = '\n';  // 转义字符,换行

这里有个小细节:char 是有符号还是无符号,取决于编译器。如果你需要存0~255的值,最好明确写成 unsigned char。我在做网络协议解析时,经常用 unsigned char 来操作二进制数据,避免符号扩展带来的麻烦。

2.5 布尔型:bool

bool 只有两个值:truefalse。占1字节。

bool isReady = true;
bool isDone = false;

有意思的是,C++里 bool 可以跟整数互相转换:0是false,非0是true。但我不建议你这么用——代码可读性会变差。你想想看,if (flag)if (flag != 0) 清爽多了。

2.6 sizeof 运算符:问编译器“这个类型占多大?”

sizeof 是个运算符,不是函数。它返回类型或变量占用的字节数:

cout << sizeof(int) << endl;        // 4
cout << sizeof(double) << endl;     // 8
int arr[10];
cout << sizeof(arr) << endl;        // 40(10个int,每个4字节)

实用技巧:sizeof(arr) / sizeof(arr[0]) 可以算出数组元素个数。但注意,这只对真正的数组有效,对指针无效。我见过有人把数组传给函数后,在函数里用这个公式算长度,结果永远得到1——因为参数退化成指针了。

2.7 类型转换:隐式与显式

类型转换分两种:编译器偷偷帮你做的(隐式),和你明确告诉编译器要做的(显式)。

隐式转换

发生在你“不小心”混用了不同类型时:

int a = 3.14;        // double转int,小数被截断,a=3
double b = 10;       // int转double,b=10.0
bool c = 42;         // int转bool,c=true

隐式转换虽然方便,但容易出问题。比如 int a = 3.14 不会报错,但你可能没意识到小数丢了。这就是为什么我前面推荐花括号初始化——它会阻止这种不安全的转换。

显式转换(强制类型转换)

当你明确知道要转换时,用C++风格的强制转换:

double pi = 3.14159;
int approx = static_cast<int>(pi);   // 3,丢掉小数

// 其他场景
const int* p = &someInt;
int* q = const_cast<int*>(p);        // 去掉const属性(慎用!)

// 不相关类型之间的转换
void* ptr = &someDouble;
double* dptr = reinterpret_cast<double*>(ptr);

我的忠告:尽量少用 reinterpret_castconst_cast。它们绕过了类型系统的保护,容易写出难以调试的bug。我见过一个同事用 reinterpret_cast 把int指针转成float指针来读数据,结果因为字节序问题,读出来的值完全不对——嗯,调试了整整一个下午。

2.8 知识体系总览

下面这张图把本章的核心内容串起来了:

变量与基本数据类型 变量定义与初始化 整型家族 浮点型 字符型 char 布尔型 bool sizeof 运算符 类型转换(隐式 vs 显式) 核心:选对类型 + 及时初始化 + 小心转换 用 sizeof 确认大小,用 static_cast 做转换

2.9 本章小结

好了,这一章的内容就这些。总结几个关键点:

  • 变量定义时一定要初始化,花括号初始化更安全
  • 整型选 int 或 long long,别在大小上纠结
  • 浮点用 double,除非你明确知道 float 够用
  • char 本质是整数,处理二进制数据用 unsigned char
  • sizeof 是运算符,不是函数,注意数组和指针的区别
  • 类型转换用 static_cast,少用 reinterpret_cast 和 const_cast

这些基础东西,写多了自然就熟了。刚开始可能会觉得啰嗦,但相信我——打好基础,后面学指针、引用、类的时候会轻松很多。


专注资料整理