第二讲:C++ 基础回顾——变量、数据类型、控制流、函数、指针与引用
好,咱们正式开始第二讲。这一讲我打算带大家快速过一遍 C++ 的核心基础。你可能会想:「这些我早就会了,还用学?」嗯,我理解。但说实话,在 Unreal 项目里,很多坑恰恰就出在这些「基础」上。我见过太多人因为指针没搞明白,导致游戏运行时莫名其妙崩溃。所以,咱们还是踏踏实实过一遍。
2.1 变量与数据类型
变量,说白了就是一块内存的名字。你给它起个名,它就在那存着数据。C++ 是静态类型语言,这意味着每个变量在声明时就必须明确它的类型。
核心原则:类型决定了变量能存什么、占多大内存、能做什么操作。
常用的内置类型,我列个表给你看:
| 类型 | 大小(通常) | 示例 |
|---|---|---|
bool |
1 字节 | bool bIsReady = true; |
int |
4 字节 | int Score = 100; |
float |
4 字节 | float Speed = 1.5f; |
double |
8 字节 | double WorldScale = 0.001; |
char |
1 字节 | char Grade = 'A'; |
我个人习惯在 Unreal 里尽量用 int32、float、bool 这些明确大小的类型。为什么?因为跨平台时,int 在不同编译器下可能是 4 字节也可能是 8 字节,容易出问题。我在项目中就遇到过,一个 int 在 PC 上跑得好好的,打包到主机上就崩了——就是因为大小不一致。
小技巧:Unreal 提供了 int32、uint8、FString 等类型别名,建议优先使用它们。这样代码更清晰,也更容易维护。
2.2 控制流
控制流就是决定代码走哪条路。C++ 里最常用的就是 if、switch、for、while。没什么花哨的,但有几个细节我想强调一下。
2.2.1 if 语句
if (Health <= 0)
{
// 角色死亡
bIsAlive = false;
}
else if (Health < 30)
{
// 低血量警告
PlayLowHealthEffect();
}
else
{
// 正常状态
}
这里有个坑:if 后面的大括号,千万别省。哪怕只有一行代码,也给我加上。我曾经接手过一个项目,有人写了 if (bDead) return; 结果后来加了一行代码,忘了加括号,逻辑全乱了。嗯,这种 bug 最难查。
2.2.2 switch 语句
switch 适合处理枚举值。比如角色状态:
switch (CharacterState)
{
case ECharacterState::Idle:
// 待机动画
break;
case ECharacterState::Running:
// 跑步动画
break;
case ECharacterState::Jumping:
// 跳跃动画
break;
default:
break;
}
注意每个 case 后面都要有 break,否则会「穿透」到下一个 case。有时候穿透是有意为之,但大多数时候是 bug。我建议你养成习惯,每个 case 都写 break,除非你明确知道自己在做什么。
2.2.3 循环
循环嘛,for 和 while 最常用。在 Unreal 里,遍历容器时我更喜欢用范围 for:
TArray<AActor*> FoundActors;
// ... 填充数组
for (AActor* Actor : FoundActors)
{
if (Actor)
{
Actor->Destroy();
}
}
这种写法简洁,而且不容易越界。你想想看,用传统 for 循环时,一不小心就把索引写错了,对吧?
2.3 函数
函数是代码复用的基本单元。C++ 的函数声明和定义可以分开,这在大型项目中非常有用。
// 声明(通常在 .h 文件中)
int32 CalculateDamage(int32 BaseDamage, float Multiplier);
// 定义(通常在 .cpp 文件中)
int32 CalculateDamage(int32 BaseDamage, float Multiplier)
{
return static_cast<int32>(BaseDamage * Multiplier);
}
我个人习惯在函数参数里尽量用 const 引用。为什么?因为传值会拷贝,拷贝大对象(比如 FString、TArray)很费性能。用 const & 既安全又高效。
注意:如果函数参数是基本类型(如 int、float),传值反而更快。因为引用本质上是指针,有间接寻址的开销。别盲目用引用。
2.4 指针与引用
好,重点来了。指针和引用,是 C++ 里最容易出问题的地方,也是 Unreal 里最常用的东西。
2.4.1 指针
指针存的是地址。你可以通过指针间接访问变量。看个例子:
int32 Value = 42;
int32* Ptr = &Value; // Ptr 存的是 Value 的地址
*Ptr = 100; // 通过指针修改 Value
// 现在 Value 变成了 100
指针最怕什么?空指针。你想想看,如果 Ptr 是 nullptr,你直接 *Ptr = 100,程序立马崩溃。我在项目中遇到过无数次这种问题,尤其是网络同步时,对象可能已经被销毁了,但指针还在。
避坑指南:每次使用指针前,先检查是否为 nullptr。Unreal 里可以用 IsValid() 宏来检查 UObject 指针。
2.4.2 引用
引用可以理解为指针的语法糖。它一旦绑定到某个变量,就不能再指向别的变量。引用没有空引用,所以比指针安全一些。
int32 Value = 42;
int32& Ref = Value; // Ref 是 Value 的引用
Ref = 100; // 修改 Value
// Value 现在是 100
引用常用于函数参数和返回值。比如:
void AddScore(int32& Score, int32 Delta)
{
Score += Delta;
}
int32 PlayerScore = 0;
AddScore(PlayerScore, 10);
// PlayerScore 现在是 10
这里 Score 是引用,所以函数内部修改会直接影响外部的 PlayerScore。如果你用传值,那就只是拷贝一份,改不了原变量。
2.4.3 指针 vs 引用
什么时候用指针,什么时候用引用?我个人的经验是:
- 引用:参数必须存在,且不需要重新绑定。比如函数参数、操作符重载。
- 指针:参数可能为空,或者需要重新指向别的对象。比如动态分配的对象、数组遍历。
在 Unreal 里,AActor* 指针随处可见。因为 Actor 可能被销毁,指针可能变成 nullptr。而引用则常用于 const & 参数,避免拷贝。
一句话总结:能用引用就用引用,必须用指针时,记得检查空指针。
2.5 实战:在 Unreal 中应用这些基础
咱们来写一个简单的 Unreal 函数,把今天学的都用上:
// 计算角色受到的最终伤害
float UMyGameInstance::CalculateFinalDamage(float BaseDamage, const FDamageInfo& DamageInfo)
{
// 检查输入
if (BaseDamage <= 0.0f)
{
return 0.0f;
}
// 根据伤害类型计算修正
float DamageMultiplier = 1.0f;
switch (DamageInfo.DamageType)
{
case EDamageType::Fire:
DamageMultiplier = 1.5f; // 火焰伤害加成
break;
case EDamageType::Ice:
DamageMultiplier = 0.8f; // 冰霜伤害减免
break;
default:
break;
}
// 计算最终伤害
float FinalDamage = BaseDamage * DamageMultiplier;
// 应用护甲减伤(如果有)
if (DamageInfo.TargetArmor > 0)
{
FinalDamage *= (100.0f / (100.0f + DamageInfo.TargetArmor));
}
return FinalDamage;
}
你看,这里用到了 if、switch、const & 引用、基本类型。这些基础组合起来,就能写出健壮的游戏逻辑。
好了,这一讲就到这里。下一讲咱们会深入 Unreal 的反射系统和 UObject,那才是真正进入 Unreal 的核心。到时候你会发现,今天这些基础有多重要。