作为初学者,C++的复杂性常常让人头疼。下面这些错误几乎每个C++新手都会遇到,来看看你中招了几个?
1. 内存管理:野指针和内存泄漏
常见错误代码:
cpp
// 错误示例1:野指针
int* ptr = new int(42);
delete ptr;
cout << *ptr; // 危险!ptr已经成为野指针
// 错误示例2:内存泄漏
void createMemoryLeak() {
int* ptr = new int[100];
// 忘记 delete[] ptr;
return; // 内存泄漏!
}
正确做法:
cpp
// 方案1:使用智能指针(C++11+)
#include <memory>
auto ptr = std::make_unique<int>(42); // 自动管理内存
// 方案2:RAII原则
class ResourceHolder {
private:
std::unique_ptr<int[]> data;
public:
ResourceHolder(size_t size) : data(std::make_unique<int[]>(size)) {}
// 析构函数自动调用,内存自动释放
};
2. 字符串操作陷阱
常见错误:
cpp
// 错误:缓冲区溢出
char str[10];
strcpy(str, "This string is too long!"); // 缓冲区溢出!
// 错误:字符串比较
char str1[] = "hello";
char str2[] = "hello";
if (str1 == str2) { // 比较的是地址,不是内容!
// 永远不会执行
}
正确做法:
cpp
// 使用std::string代替C风格字符串
#include <string>
std::string str = "hello";
std::string str2 = "hello";
if (str == str2) { // 正确比较内容
// 会执行
}
// 或者安全的C字符串操作
char str[20];
strncpy(str, "This string is too long!", sizeof(str) - 1);
str[sizeof(str) - 1] = '\0'; // 确保以null结尾
3. 对象切片(Object Slicing)
常见错误:
cpp
class Animal {
public:
virtual void speak() { cout << "Animal sound" << endl; }
};
class Dog : public Animal {
public:
void speak() override { cout << "Woof!" << endl; }
};
void makeSound(Animal animal) { // 按值传递
animal.speak(); // 总是输出 "Animal sound"
}
Dog dog;
makeSound(dog); // 对象被切片,多态失效
正确做法:
cpp
// 使用引用或指针传递
void makeSound(Animal& animal) { // 按引用传递
animal.speak(); // 正确调用派生类的方法
}
// 或者使用智能指针
void makeSound(std::unique_ptr<Animal> animal) {
animal->speak();
}
4. 浅拷贝问题
常见错误:
cpp
class BadString {
private:
char* data;
public:
BadString(const char* str) {
data = new char[strlen(str) + 1];
strcpy(data, str);
}
// 缺少拷贝构造函数和赋值运算符
// 默认的浅拷贝会导致双重释放!
};
BadString str1("hello");
BadString str2 = str1; // 灾难!两个对象共享同一块内存
正确做法:
cpp
class GoodString {
private:
char* data;
public:
// 构造函数
GoodString(const char* str) {
data = new char[strlen(str) + 1];
strcpy(data, str);
}
// 拷贝构造函数
GoodString(const GoodString& other) {
data = new char[strlen(other.data) + 1];
strcpy(data, other.data);
}
// 赋值运算符
GoodString& operator=(const GoodString& other) {
if (this != &other) {
delete[] data;
data = new char[strlen(other.data) + 1];
strcpy(data, other.data);
}
return *this;
}
// 析构函数
~GoodString() {
delete[] data;
}
};
5. 数组边界溢出
常见错误:
cpp
int arr[5] = {1, 2, 3, 4, 5};
// 错误:访问越界
for (int i = 0; i <= 5; i++) { // i=5 时越界
cout << arr[i] << endl;
}
// 错误:sizeof误用
void printArray(int arr[]) {
// sizeof(arr) 返回的是指针大小,不是数组大小!
for (int i = 0; i < sizeof(arr) / sizeof(arr[0]); i++) {
cout << arr[i] << endl; // 可能越界
}
}
正确做法:
cpp
// 使用std::array或std::vector
#include <array>
#include <vector>
std::array<int, 5> arr = {1, 2, 3, 4, 5};
for (int i = 0; i < arr.size(); i++) { // 安全的边界检查
cout << arr[i] << endl;
}
// 或者使用范围for循环
for (const auto& element : arr) {
cout << element << endl;
}
6. 未初始化变量
常见错误:
cpp
int value; // 未初始化,值是不确定的
cout << value; // 可能输出任意值
class MyClass {
int x; // 未初始化
public:
void print() { cout << x; } // 危险!
};
正确做法:
cpp
// 总是初始化变量
int value = 0;
int value{}; // C++11统一初始化
class MyClass {
int x = 0; // 成员初始化
public:
MyClass() = default;
MyClass(int val) : x(val) {} // 成员初始化列表
};
7. 函数返回局部变量的引用/指针
常见错误:
cpp
// 错误:返回局部变量的引用
int& badFunction() {
int localVar = 42;
return localVar; // 局部变量会被销毁!
}
// 错误:返回局部变量的指针
char* badString() {
char localStr[] = "hello";
return localStr; // 局部数组会被销毁!
}
正确做法:
cpp
// 返回值而不是引用
int goodFunction() {
int localVar = 42;
return localVar; // 返回副本,安全
}
// 或者返回动态分配的内存(但要记得释放)
std::unique_ptr<int> createNumber() {
return std::make_unique<int>(42);
}
// 或者让调用者提供存储空间
void fillString(char* buffer, size_t size) {
strncpy(buffer, "hello", size - 1);
buffer[size - 1] = '\0';
}
8. 混淆=和==
常见错误:
cpp
int x = 5;
// 这个bug很难发现!
if (x = 10) { // 应该是 x == 10
cout << "x is 10" << endl; // 总是会执行,而且x被改成了10!
}
正确做法:
cpp
// 习惯把常量放在左边
if (10 == x) { // 如果写成 10 = x,编译器会报错
cout << "x is 10" << endl;
}
// 或者启用编译器警告
// g++ -Wall -Wextra 会警告这种赋值操作
9. 头文件包含问题
常见错误:
cpp
// 头文件 guard 忘记写
// myclass.h
class MyClass {
// ...
}; // 如果没有头文件guard,可能重复定义
// 循环包含
// a.h
#include "b.h"
// b.h
#include "a.h" // 循环包含!
正确做法:
cpp
// myclass.h
#ifndef MYCLASS_H // 头文件guard
#define MYCLASS_H
class MyClass {
// ...
};
#endif // MYCLASS_H
// 或者使用 #pragma once(大多数编译器支持)
#pragma once
class MyClass {
// ...
};
10. 异常安全忽略
常见错误:
cpp
void unsafeFunction() {
int* ptr = new int[100];
someFunctionThatMightThrow(); // 如果这里抛出异常...
delete[] ptr; // 这行不会执行,内存泄漏!
}
正确做法:
cpp
// 使用RAII确保异常安全
void safeFunction() {
std::vector<int> data(100); // 自动管理内存
someFunctionThatMightThrow(); // 即使抛出异常,data也会自动清理
}
// 或者使用智能指针
void safeFunction2() {
auto ptr = std::make_unique<int[]>(100);
someFunctionThatMightThrow(); // 异常安全
}
给C++小白的生存指南
-
启用所有编译器警告:
bashg++ -Wall -Wextra -Wpedantic -std=c++17 program.cpp -
优先使用标准库 :
std::vector、std::string、std::unique_ptr -
学习RAII原则:资源获取即初始化
-
避免裸指针:使用智能指针管理内存
-
多写测试:特别是边界情况的测试
-
使用现代C++:C++11/14/17/20的特性让很多传统问题变得简单
记住,每个C++高手都曾经是小白,都踩过这些坑。关键是要从错误中学习,理解背后的原理,这样你就能写出更安全、更高效的C++代码!