面试C++易错点总结

写C++最扎心的不是不会写,而是写出来能运行,一上线就崩、一面试就被问住------尤其是新手,总在一些"不起眼"的地方栽跟头,浪费大量时间调试,还被领导/面试官吐槽代码不规范。

今天不聊虚的,不搞复杂理论,只讲 日常开发、面试高频的6个C++坑,每个坑都配"错误代码+正确写法+避坑技巧",新手看完直接避坑,老鸟也能查漏补缺,收藏起来,下次写代码直接对照看!

(文末附面试高频追问,帮你吃透每个知识点,避免踩坑还能拿offer)

坑1:内存泄漏------最常见,也最致命(面试必问)

新手最容易犯的错:new了对象不delete,尤其是在分支判断、异常场景下,直接导致内存泄漏,程序运行越久,占用内存越多,最后崩掉。

错误写法(90%新手会写):

cpp 复制代码
#include <iostream>
using namespace std;

int main() {
    // new了一个int对象,用完没delete
    int* p = new int(10);
    cout << *p << endl;
    // 直接return,p指向的内存未释放,内存泄漏
    return 0;
}

// 更隐蔽的坑:分支里new,忘记在所有分支delete
void test(int flag) {
    int* p = nullptr;
    if (flag == 1) {
        p = new int(20);
    }
    // 若flag != 1,p未new,但delete空指针无害;若flag==1,new后未delete,泄漏
    if (flag == 1) {
        delete p;
    }
}

正确写法(两种方式,优先第二种):

cpp 复制代码
#include <iostream>
#include <memory> // 智能指针头文件
using namespace std;

// 方式1:手动delete(注意:必须保证每个分支都delete,避免遗漏)
void test1(int flag) {
    int* p = nullptr;
    if (flag == 1) {
        p = new int(20);
        cout << *p << endl;
        delete p; // 用完立即释放
        p = nullptr; // 避免野指针
    }
}

// 方式2:用智能指针(推荐!自动释放,无需手动delete,彻底避免泄漏)
void test2(int flag) {
    // unique_ptr:独占所有权,自动释放
    unique_ptr<int> p(new int(30));
    // 或者用make_unique(C++14及以上,更安全)
    auto p2 = make_unique<int>(40);
    cout << *p << " " << *p2 << endl;
    // 无需delete,出作用域自动释放
}

避坑技巧: 新手尽量不用裸指针(new/delete),优先用智能指针(unique_ptr、shared_ptr);若必须用裸指针,记住"谁new谁delete",分支、异常场景一定要检查是否遗漏delete。

面试追问: 智能指针的原理?unique_ptr和shared_ptr的区别?shared_ptr为什么会有循环引用?(文末有简单答案)

坑2:野指针------藏得深,崩得突然

野指针就是"指向无效内存的指针",比如:未初始化的指针、delete后未置空的指针、指向栈内存的指针(栈内存释放后,指针仍指向该地址)。

野指针的可怕之处在于:偶尔能运行,偶尔崩掉,调试起来极其困难,面试中也是高频考点。

错误写法:

cpp 复制代码
#include <iostream>
using namespace std;

int* getNum() {
    int num = 100; // 栈内存,函数结束后自动释放
    return &num; // 返回栈内存地址,导致野指针
}

int main() {
    int* p; // 未初始化,随机指向一块内存,野指针
    cout << *p << endl; // 未定义行为,可能崩掉
    
    int* p2 = new int(50);
    delete p2; // 释放内存后,p2仍指向原地址,成为野指针
    cout << *p2 << endl; // 访问无效内存,可能崩掉
    
    int* p3 = getNum();
    cout << *p3 << endl; // 栈内存已释放,p3是野指针,结果随机
    return 0;
}

正确写法:

cpp 复制代码
#include <iostream>
#include <memory>
using namespace std;

// 避免返回栈内存:用堆内存(智能指针管理)或传参输出
unique_ptr<int> getNum() {
    auto num = make_unique<int>(100); // 堆内存,智能指针管理
    return num;
}

int main() {
    int* p = nullptr; // 指针初始化,避免野指针
    if (p != nullptr) { // 访问前先判断是否有效
        cout << *p << endl;
    }
    
    auto p2 = make_unique<int>(50);
    // 无需delete,出作用域自动释放,不会产生野指针
    
    auto p3 = getNum();
    cout << *p3 << endl; // 安全访问
    
    return 0;
}

**避坑技巧:**所有指针初始化时都设为nullptr;delete后立即置为nullptr;不返回栈内存的地址;访问指针前,先判断是否为nullptr。

坑3:数组越界------新手最容易忽略的"致命错误"

C++ 不检查数组越界,新手很容易因为"多循环一次""索引算错",导致数组越界,修改到非法内存,程序崩溃(甚至出现奇怪的逻辑错误)。

尤其是在for循环中,经常出现"i <= n" instead of "i < n"的错误。

错误写法:

cpp 复制代码
#include <iostream>
using namespace std;

int main() {
    int arr[5] = {1,2,3,4,5};
    // 数组下标0-4,循环到i=5,越界
    for (int i = 0; i <= 5; i++) {
        cout << arr[i] << " ";
    }
    // 越界修改:修改了数组后面的非法内存
    arr[5] = 10;
    return 0;
}

正确写法:

cpp 复制代码
#include <iostream>
#include <vector> // 优先用vector,更安全
using namespace std;

int main() {
    // 方式1:用普通数组,严格控制下标范围
    int arr[5] = {1,2,3,4,5};
    int n = sizeof(arr) / sizeof(arr[0]); // 计算数组长度,避免硬编码
    for (int i = 0; i < n; i++) { // i < n,不越界
        cout << arr[i] << " ";
    }
    
    // 方式2:用vector(推荐!支持边界检查)
    vector<int> vec = {1,2,3,4,5};
    // 方式2.1:范围for循环(最安全,不会越界)
    for (int num : vec) {
        cout << num << " ";
    }
    // 方式2.2:用at()访问,越界会抛异常(便于调试)
    for (int i = 0; i < vec.size(); i++) {
        cout << vec.at(i) << " ";
    }
    
    return 0;
}

避坑技巧: 尽量不用普通数组,优先用vector;若用普通数组,用sizeof计算长度,避免硬编码;循环时严格控制下标范围,避免"<="误写;vector用at()访问,便于调试越界问题。

坑4:混淆值传递、引用传递、指针传递

新手最容易搞混这三种传递方式,导致"传参后,原变量没变化""不小心修改了原变量"等问题,尤其是在函数传参、返回值时,踩坑率极高。

一句话区分:值传递传副本,引用/指针传递传本身

错误写法(以为能修改原变量):

cpp 复制代码
#include <iostream>
using namespace std;

// 值传递:形参是实参的副本,修改形参不影响实参
void changeNum(int num) {
    num = 100;
}

int main() {
    int a = 10;
    changeNum(a);
    cout << a << endl; // 输出10,没被修改,新手以为会输出100
    return 0;
}

正确写法(根据需求选择传递方式):

cpp 复制代码
#include <iostream>
using namespace std;

// 1. 引用传递:修改原变量(推荐,比指针更安全)
void changeNum1(int& num) {
    num = 100;
}

// 2. 指针传递:修改原变量(适合需要传递nullptr的场景)
void changeNum2(int* num) {
    if (num != nullptr) {
        *num = 200;
    }
}

// 3. 常量引用传递:不修改原变量,避免拷贝(适合传递大对象)
void printNum(const int& num) {
    cout << num << endl;
    // num = 300; // 报错,无法修改常量引用
}

int main() {
    int a = 10;
    changeNum1(a);
    cout << a << endl; // 输出100,原变量被修改
    
    changeNum2(&a);
    cout << a << endl; // 输出200,原变量被修改
    
    printNum(a); // 输出200,不修改原变量
    return 0;
}

避坑技巧: 想修改原变量,用引用传递(优先)或指针传递;不想修改原变量,且传递大对象(如string、结构体),用常量引用(const &),避免拷贝浪费性能;简单类型(int、char),直接用值传递即可。

坑5:string 拼接/赋值的坑------内存浪费+逻辑错误

新手用string时,经常犯两个错:一是频繁拼接字符串用"+",导致内存频繁分配释放;二是用"=="比较字符串时,混淆string和char*。

错误写法:

cpp 复制代码
#include <iostream>
#include <string>
using namespace std;

int main() {
    // 错误1:频繁用"+"拼接字符串,效率极低
    string str;
    for (int i = 0; i < 1000; i++) {
        str += "abc"; // 每次拼接都要重新分配内存
    }
    
    // 错误2:用"=="比较string和char*,看似没问题,实则有隐患
    string s1 = "hello";
    char* s2 = "hello";
    if (s1 == s2) { // 碰巧相等,但逻辑不严谨(比较的是内容,但char*可能指向不同地址)
        cout << "相等" << endl;
    }
    
    // 错误3:string未初始化,直接赋值/拼接
    string s3;
    s3 += "test"; // 虽然能运行,但不规范,容易出问题
    return 0;
}

正确写法:

cpp 复制代码
#include <iostream>
#include <string>
#include <sstream> // 字符串流头文件
using namespace std;

int main() {
    // 正确1:频繁拼接用stringstream,效率高,避免内存浪费
    stringstream ss;
    for (int i = 0; i < 1000; i++) {
        ss << "abc"; // 先缓存,最后一次性生成字符串
    }
    string str = ss.str();
    
    // 正确2:比较string和char*,用c_str()转换,逻辑严谨
    string s1 = "hello";
    char* s2 = "hello";
    if (s1 == string(s2)) { // 转换为string再比较
        cout << "相等" << endl;
    }
    // 或者用strcmp(注意:需包含cstring头文件)
    #include <cstring>
    if (strcmp(s1.c_str(), s2) == 0) {
        cout << "相等" << endl;
    }
    
    // 正确3:string初始化后再使用
    string s3 = ""; // 初始化
    s3 += "test";
    
    return 0;
}

避坑技巧: 频繁拼接字符串,优先用stringstream;比较string和char*时,用c_str()转换后再比较;string一定要初始化(至少置为空字符串)。

坑6:忘记初始化变量------结果随机,调试崩溃

C++ 中,局部变量(栈上的变量)不会自动初始化,默认是随机值;全局变量、静态变量会自动初始化为0,但新手经常忽略局部变量的初始化,导致程序逻辑错误、崩溃。

错误写法:

cpp 复制代码
#include <iostream>
using namespace std;

int main() {
    int a; // 局部变量,未初始化,值随机
    int b = a + 10; // 基于随机值计算,结果随机
    cout << b << endl; // 每次运行结果都不一样,可能崩溃
    
    bool flag; // 未初始化,可能是true也可能是false
    if (flag) { // 逻辑随机,bug隐蔽
        cout << "true" << endl;
    } else {
        cout << "false" << endl;
    }
    return 0;
}

正确写法:

cpp 复制代码
#include <iostream>
using namespace std;

int main() {
    // 局部变量必须初始化
    int a = 0; // 初始化为0
    int b = a + 10; // 结果确定,10
    cout << b << endl;
    
    bool flag = false; // 初始化,逻辑明确
    if (flag) {
        cout << "true" << endl;
    } else {
        cout << "false" << endl;
    }
    
    // 结构体、类对象也需初始化
    struct Student {
        int id;
        string name;
    };
    Student s = {1, "张三"}; // 初始化
    return 0;
}

避坑技巧: 所有局部变量(int、bool、指针、结构体等),声明时立即初始化;养成"初始化"的习惯,哪怕初始化为0、nullptr、空字符串,也能避免大部分随机bug。

文末福利:面试高频追问(直接背)

结合上面的坑,面试官常问这3个问题,记住答案,面试不慌:

  1. 智能指针的原理? 答:智能指针是RAII(资源获取即初始化)思想的实现,通过类封装裸指针,在析构函数中自动释放内存,避免内存泄漏;核心有unique_ptr(独占所有权)、shared_ptr(共享所有权,引用计数)、weak_ptr(解决shared_ptr循环引用)。

  2. shared_ptr为什么会有循环引用?怎么解决? 答:两个shared_ptr互相指向对方,导致引用计数永远不为0,内存无法释放;解决方法:将其中一个改为weak_ptr(不增加引用计数)。

  3. 值传递、引用传递、指针传递的区别? 答:值传递传副本,修改形参不影响实参,有拷贝开销;引用传递传实参本身,无拷贝,修改形参影响实参,不能传nullptr;指针传递传实参地址,无拷贝,修改指针指向的内容影响实参,可传nullptr。

结尾(提高互动、收藏、转发)

以上6个坑,你踩过几个?评论区告诉我你最常犯的C++错误~

收藏这篇文章,下次写代码、面试前,拿出来快速过一遍,帮你少踩坑、少挨骂、多拿offer!

关注我,后续更新更多C++实战避坑、面试干货,新手也能快速上手!

相关推荐
lly2024062 小时前
C语言中的循环结构:深入理解与高效应用
开发语言
异步的告白2 小时前
链接脚本SECTIONS逐行深度解析
linux·开发语言
yashuk2 小时前
C语言条件编译三种方式及第一种方式的格式、作用与示例
c语言·程序设计·条件编译·代码示例·预处理程序
Aurorar0rua2 小时前
CS50 x 2024 Notes C - 04
java·开发语言
iCxhust2 小时前
C#程序,窗体1向窗体2的textbox控件写入字符串“hello”
开发语言·c#
低客的黑调2 小时前
Redis-不止是缓存
java·开发语言·数据库
花间相见2 小时前
【大模型微调与部署02】—— ms-swift 自定义数据集完全教程:格式、dataset_info 配置、多格式兼容实战
开发语言·ssh·swift
Hello--_--World3 小时前
JS:闭包、函数柯里化、工厂函数、偏函数、立即执行函数 相关知识点与面试题
开发语言·javascript·ecmascript
一只幸运猫.3 小时前
字节跳动Java大厂面试版
java·开发语言·面试