计算机基础理论
|——数据结构与算法:链表、栈、队列、树与图、查找与排序等。
|——程序设计基础:结构化、面向对象方法。
|——软件工程基础:软件生命周期、测试与调试。
|——数据库设计基础:数据模型、关系代数、SQL。
|——C语言:重点考察结构化编程能力,核心是指针、函数、数组和结构体。
|——C++:重点是掌握面向对象编程(OOP)思想,核心是类与对象、继承与派生。
目录
计算机基础理论
引言
一、C 语言中如何定义和使用 bool 类型
1.1 传统 C 语言模拟方式
1.2 现代 C 语言推荐方式(C99 及以上)
二、C++ 中创建静态数组与对象数组
2.1 基本类型静态数组
2.2 类对象数组
三、C 语言 UTF-8 转 GBK 编码(Windows)
四、深拷贝 vs 浅拷贝:何时需要深拷贝?
4.1 概念对比
4.2 示例代码
4.3 最佳实践
五、委托构造函数:消除重复初始化代码
5.1 问题场景
5.2 解决方案:委托构造函数(C++11)
5.3 注意事项
六、角度值归一化:将角度限制在 [0, 360) 范围内
七、类型判断:使用 typeid 与更好的替代方案
7.1 使用 `typeid` 运算符
7.2 局限性
7.3 更好的替代方案
八、构造函数 ≠ 成员函数:正确初始化方式
8.1 错误做法
8.2 正确做法
8.3 正确的构造函数定义与使用
九、总结与建议
引言
> >无论是初学者还是有一定经验的 C++ 开发者,总会遇到一些容易混淆或遗忘的细节:如何在 C 语言中定义 bool?深拷贝和浅拷贝有什么区别?委托构造函数如何避免代码重复?角度值怎样归一化到 [0, 360)?本文整理了 C++/C 开发中多个高频“坑点”,给出清晰的解释、代码示例和最佳实践,帮助你写出更健壮、更易维护的代码。适用于 C++11 及以上标准(部分内容兼容 C99)。
一、C 语言中如何定义和使用 bool 类型
C89/C90 标准中没有内置的 `bool` 类型,通常使用 `int` 配合宏定义来模拟。C99 标准引入了 `stdbool.h`,提供了 `bool`、`true`、`false`。
1.1 传统 C 语言模拟方式
```c
typedef int bool;
#define true 1
#define false 0
bool flag = true;
if (flag == false) {
// ...
}
```
1.2 现代 C 语言推荐方式(C99 及以上)
```c
#include <stdbool.h>
bool flag = true;
if (!flag) {
// ...
}
```
> **注意**:`stdbool.h` 中的 `bool` 实际是 `_Bool` 的宏,`true`/`false` 分别展开为 `1`/`0`。建议在支持 C99 的环境中使用标准头文件,提高可移植性。
二、C++ 中创建静态数组与对象数组
2.1 基本类型静态数组
```cpp
#define MAXSIZE_STATICARRAY 100
int arr[MAXSIZE_STATICARRAY]; // 栈上分配,大小必须为编译期常量
```
2.2 类对象数组
```cpp
#include <string>
using namespace std;
class Student {
public:
string name;
int age;
Student() = default; // 默认构造函数
Student(const string& n, int a) : name(n), age(a) {}
};
// 创建对象数组的几种方式
Student students[3]; // 调用默认构造函数 3 次
Student classArr[2] = {Student("A", 20), Student("B", 21)}; // 初始化列表
```
**注意事项**:
- 如果类没有默认构造函数,则必须为每个元素提供初始化器。
- 静态数组大小在编译时确定,不可变。如果需要动态大小,使用 `std::vector<Student>`。
三、C 语言 UTF-8 转 GBK 编码(Windows)
在 Windows 环境下,控制台默认使用 GBK 编码,而源代码文件通常是 UTF-8。直接 `printf` 中文会乱码。下面提供一个通用的转换函数。
```c
#include <stdio.h>
#include <stdlib.h>
#include <windows.h>
// 将 UTF-8 字符串转换为 GBK 编码,返回的指针需要 free()
char* utf8_to_gbk(const char* utf8) {
// 1. UTF-8 → 宽字符 Unicode
int wlen = MultiByteToWideChar(CP_UTF8, 0, utf8, -1, NULL, 0);
wchar_t* wstr = (wchar_t*)malloc(wlen * sizeof(wchar_t));
if (!wstr) return NULL;
MultiByteToWideChar(CP_UTF8, 0, utf8, -1, wstr, wlen);
// 2. Unicode → GBK (代码页 936)
int len = WideCharToMultiByte(936, 0, wstr, -1, NULL, 0, NULL, NULL);
char* gbk = (char*)malloc(len);
if (gbk) {
WideCharToMultiByte(936, 0, wstr, -1, gbk, len, NULL, NULL);
}
free(wstr);
return gbk;
}
int main() {
const char* utf8_str = "正常退出循环逻辑,返回0退出程序";
char* gbk_str = utf8_to_gbk(utf8_str);
if (gbk_str) {
printf("%s\n", gbk_str);
free(gbk_str);
}
return 0;
}
```
> **注意**:此代码仅适用于 Windows 平台。Linux 下通常使用 `iconv` 库进行编码转换。
四、深拷贝 vs 浅拷贝:何时需要深拷贝?
4.1 概念对比
# 深拷贝 vs 浅拷贝 对比表
[[shallow_vs_deep]]
characteristic = "资源处理"
shallow = "仅复制指针,多个对象指向同一块堆内存"
deep = "复制指针指向的内容,分配独立内存"
[[shallow_vs_deep]]
characteristic = "修改影响"
shallow = "修改一个对象会影响另一个"
deep = "完全独立,互不影响"
[[shallow_vs_deep]]
characteristic = "资源释放"
shallow = "可能重复释放(double free)导致崩溃"
deep = "各自释放自己的资源,安全"
[[shallow_vs_deep]]
characteristic = "性能"
shallow = "快,只复制指针"
deep = "慢,需要分配内存和复制数据"
[[shallow_vs_deep]]
characteristic = "内存占用"
shallow = "小,多对象共享同一资源"
deep = "大,每个对象有独立副本"
4.2 示例代码
```cpp
class Shallow {
public:
int* data;
Shallow(int val) { data = new int(val); }
// 默认拷贝构造是浅拷贝
};
class Deep {
public:
int* data;
Deep(int val) { data = new int(val); }
Deep(const Deep& other) { // 深拷贝构造函数
data = new int(*other.data);
}
~Deep() { delete data; }
// 赋值运算符也需要深拷贝,此处省略
};
```
4.3 最佳实践
- **遵循“三/五法则”**:如果类需要自定义析构函数、拷贝构造函数或拷贝赋值运算符,通常三者都需要定义(或全部禁用)。
- **优先使用 RAII 容器**:如 `std::vector`、`std::string` 等,它们已经实现深拷贝语义,避免手动管理内存。
- **使用移动语义**(C++11 起)可减少不必要的深拷贝开销。
五、委托构造函数:消除重复初始化代码
5.1 问题场景
当类有多个构造函数时,经常出现重复的初始化逻辑。例如:
```cpp
class MyClass {
public:
MyClass(int a) { /* 公共初始化 A */; /* 特定逻辑 */ }
MyClass(int a, int b) { /* 公共初始化 A */; /* 另一特定逻辑 */ }
};
```
5.2 解决方案:委托构造函数(C++11)
一个构造函数可以调用同一个类的另一个构造函数,避免代码重复。
```cpp
#include <QDebug> // Qt 示例,也可替换为 iostream
class MyClass {
public:
// 主构造函数:包含公共初始化
explicit MyClass(QObject* parent = nullptr) : QObject(parent) {
qDebug() << "公共初始化代码";
}
// 委托构造函数
MyClass(const std::string& str) : MyClass(nullptr) {
qDebug() << "字符串参数特有逻辑";
m_name = QString::fromStdString(str);
}
private:
QString m_name;
};
```
5.3 注意事项
- 委托构造函数不能同时使用成员初始化列表(除目标构造函数外)。
- 避免循环委托(A 委托 B,B 又委托 A),会导致未定义行为。
- 委托链最终必须落在一个非委托构造函数上。
六、角度值归一化:将角度限制在 [0, 360) 范围内
在图形、机器人或物理引擎中,经常需要将角度规范化到标准区间,防止数值溢出或无效值。
```cpp
#include <cmath> // 用于 fmod
// 将任意角度归一化到 [0, 360)
double normalizeAngle(double angle) {
// 取模运算,结果在 [0, 360) 范围
angle = std::fmod(angle, 360.0);
if (angle < 0) {
angle += 360.0;
}
// 处理浮点精度导致的 -0.0
if (angle < 0.0) angle = 0.0;
return angle;
}
// 角度增加或减少 delta 度
double adjustAngle(double originalAngle, double delta) {
return normalizeAngle(originalAngle + delta);
}
```
> **注意**:`fmod` 返回的符号与被除数相同,所以需要额外的负数调整。对于整数角度,也可用 `%` 运算符,但浮点数场景必须用 `fmod`。
七、类型判断:使用 typeid 与更好的替代方案
7.1 使用 `typeid` 运算符
```cpp
#include <iostream>
#include <typeinfo>
int a = 10;
if (typeid(a) == typeid(int)) {
std::cout << "a 是 int 类型\n";
}
```
7.2 局限性
- `typeid` 返回的 `std::type_info` 对象的比较依赖于实现,可能不稳定。
- 对于多态类型,`typeid` 会返回最派生类型(需要启用 RTTI)。
- 过多使用 `typeid` 通常意味着设计问题(如滥用运行时类型分支),应优先考虑虚函数或 `std::variant`。
7.3 更好的替代方案
- **编译期类型比较**:使用 `std::is_same<T, U>::value`(C++11)。
- **运行时分发**:使用虚函数或 `std::visit`(针对 `std::variant`)。
- **常量表达式判断**:`if constexpr` 和 `std::is_same_v`。
```cpp
template<typename T>
void process(T&& value) {
if constexpr (std::is_same_v<std::decay_t<T>, int>) {
std::cout << "处理 int\n";
} else {
std::cout << "其他类型\n";
}
}
```
八、构造函数 ≠ 成员函数:正确初始化方式
很多初学者会错误地尝试直接调用构造函数来重新初始化对象,这是不可能的。构造函数仅在对象创建时执行一次。
8.1 错误做法
```cpp
Person p("张三", 20, 1.75f, true);
p.Person("李四", 22, 1.80f, false); // 错误!不能直接调用构造函数
```
8.2 正确做法
- **重新赋值**:通过成员函数修改,或使用拷贝赋值运算符。
- **placement new**(高级用法):在已有内存上重新构造对象,但需要手动析构旧对象。
- **使用智能指针**:重新创建新对象并替换指针。
8.3 正确的构造函数定义与使用
```cpp
#include <iostream>
#include <string>
using namespace std;
class Person {
private:
string name;
int age;
float height;
bool isStudent;
public:
// 构造函数使用初始化列表
Person(string n, int a, float h, bool s)
: name(n), age(a), height(h), isStudent(s) {}
void printInfo() const {
cout << "姓名:" << name << ", 年龄:" << age
<< ", 身高:" << height << "m, 学生:" << (isStudent ? "是" : "否") << endl;
}
};
int main() {
Person p("张三", 20, 1.75f, true);
p.printInfo();
// 如需重新初始化,可以创建新对象并赋值(需要实现赋值运算符)
return 0;
}
```
> **提示**:若需要修改对象的所有成员,建议提供 `reset()` 成员函数或直接使用赋值操作。
九、总结
# 总结与建议表
[[summary]]
common_issue = "C 语言 bool 类型"
solution = "优先 `#include <stdbool.h>`(C99 以上)"
[[summary]]
common_issue = "静态对象数组"
solution = "确保类有默认构造函数,或使用初始化列表"
[[summary]]
common_issue = "编码转换(Windows)"
solution = "使用 `MultiByteToWideChar` + `WideCharToMultiByte`"
[[summary]]
common_issue = "深拷贝 vs 浅拷贝"
solution = "遵循三/五法则,优先使用 RAII 容器"
[[summary]]
common_issue = "重复初始化代码"
solution = "使用 C++11 委托构造函数"
[[summary]]
common_issue = "角度归一化"
solution = "`fmod` + 负数修正,注意浮点精度"
[[summary]]
common_issue = "运行时类型判断"
solution = "优先虚函数,谨慎使用 `typeid`"
[[summary]]
common_issue = "构造函数调用"
solution = "不能显式调用,通过创建新对象或赋值修改"
**核心原则**:
- **理解 C++ 的资源管理语义**:拷贝、赋值、析构应保持一致。
- **利用现代 C++ 特性**:委托构造函数、移动语义、`constexpr` 等简化代码并提高安全性。
- **避免 C 风格的强制转换和类型判断**,尽量使用模板和编译期多态。
希望这份指南能帮助你避开常见陷阱,写出更高质量的 C++ 代码。如果你有其他避坑经验,欢迎在评论区分享交流。