C++ 基础常见面试题总结(上)

✍️ Demo User·📅 2026年4月24日·👁 7 次阅读
c++开发
📚 系列:现代 C++ 学习之路

本文采用面试问答形式,系统梳理 C++ 基础知识中最高频的考点。⭐ 标记为高频重点题。


一、基础概念与常识

C++ 语言有哪些特点?

  1. 面向对象:支持封装、继承、多态三大特性。
  2. 高性能:编译为机器码,无虚拟机开销,适合系统级开发。
  3. 兼容 C:几乎完全兼容 C 语言,可直接调用 C 的库。
  4. 多范式:同时支持面向过程、面向对象、泛型编程、函数式编程。
  5. 零成本抽象:模板、内联等机制在编译期展开,运行时没有额外开销。
  6. 手动内存管理:开发者可以精确控制内存分配和释放(也可借助智能指针自动管理)。
  7. 标准库丰富:STL 提供了容器、算法、迭代器等大量工具。

⭐ 编译型语言和解释型语言的区别?C++ 属于哪种?

特性编译型语言解释型语言
执行方式先编译为机器码,再执行逐行解释执行
执行速度相对慢
可移植性需针对不同平台编译通常跨平台
代表语言C++、C、Rust、GoPython、JavaScript、Ruby

C++ 是编译型语言。源代码经过预处理 → 编译 → 汇编 → 链接,最终生成可执行的二进制文件。

⭐ C++ 程序的编译过程是怎样的?

源代码(.cpp/.h) → 预处理(.i) → 编译(.s) → 汇编(.o) → 链接 → 可执行文件
阶段说明产物
预处理展开 #include#define、条件编译.i 文件
编译语法分析、语义分析、优化,生成汇编代码.s 文件
汇编将汇编代码转为机器码(目标文件).o / .obj 文件
链接合并目标文件,解析外部符号,链接库可执行文件
bash
# 可以用 g++ 分步查看:
g++ -E main.cpp -o main.i    # 预处理
g++ -S main.i -o main.s      # 编译
g++ -c main.s -o main.o      # 汇编
g++ main.o -o main            # 链接

静态链接和动态链接的区别?

特性静态链接动态链接
时机编译时运行时
库文件.a(Linux)/ .lib(Windows).so(Linux)/ .dll(Windows)
可执行文件大小较大(包含库代码)较小(引用共享库)
内存占用每个程序各自一份多个程序共享一份
更新维护需重新编译替换库文件即可

⭐ C++ 和 C 的区别?

特性CC++
编程范式面向过程多范式(面向对象+泛型+函数式)
封装struct(无访问控制)class / struct + public/private/protected
继承/多态不支持支持(虚函数、纯虚函数)
内存管理malloc / freenew / delete + 智能指针
函数重载不支持支持
引用不支持支持 &
模板不支持支持(函数模板、类模板)
异常处理不支持try / catch / throw
标准库stdio.hSTL(容器、算法、迭代器)
命名空间不支持namespace
布尔类型C99 引入 _Bool内置 bool

⭐ C++ 和 Java 的区别?

特性C++Java
编译/运行编译为机器码编译为字节码,JVM 解释执行
内存管理手动(+ 智能指针)垃圾回收(GC)
指针支持原始指针无指针(只有引用)
多继承支持不支持(用接口替代)
运算符重载支持不支持
模板/泛型模板(编译期展开,零开销)泛型(类型擦除)
头文件需要 .h 头文件不需要
预处理器#define#include
性能通常更高略低(JIT 可优化)

二、基本语法

注释有哪几种形式?

cpp
// 1. 单行注释
int a = 10; // 行尾注释

/* 2. 多行注释(块注释) */
/*
 * 这是
 * 多行注释
 */

/**
 * 3. Doxygen 文档注释(用于生成 API 文档)
 * @brief 计算两数之和
 * @param a 第一个数
 * @param b 第二个数
 * @return 两数之和
 */
int Add(int a, int b) { return a + b; }

⭐ C++ 中的关键字有哪些?

C++ 共有约 90+ 个关键字,以下按类别列出最常用的:

类别关键字
数据类型int char float double bool void long short unsigned signed auto
类与对象class struct union enum this friend operator virtual override final
访问控制public private protected
流程控制if else switch case default for while do break continue return goto
类型修饰const constexpr volatile mutable static extern register inline
内存管理new delete sizeof alignof alignas
异常处理try catch throw noexcept
模板template typename concept requires
命名空间namespace using
类型转换static_cast dynamic_cast const_cast reinterpret_cast
其他nullptr decltype typedef typeid explicit export co_await co_yield co_return

#include <xxx>#include "xxx" 的区别?

形式搜索路径用途
#include <iostream>系统/标准库头文件目录引用标准库和第三方库
#include "myheader.h"先搜索当前目录,再搜索系统目录引用自定义头文件

⭐ 声明(declaration)和定义(definition)的区别?

声明定义
作用告诉编译器某个名字的存在和类型为名字分配存储空间或提供实现
内存不分配内存分配内存
次数可以多次声明只能定义一次(ODR 原则)
cpp
// 声明(不分配内存)
extern int globalVar;           // 变量声明
void Foo(int x);                // 函数声明
class MyClass;                  // 前向声明

// 定义(分配内存 / 提供实现)
int globalVar = 42;             // 变量定义
void Foo(int x) { /*...*/ }    // 函数定义
class MyClass { /*...*/ };      // 类定义

structclass 的区别?

在 C++ 中,structclass 几乎完全相同,唯一的区别是 默认访问权限

structclass
默认成员访问权限publicprivate
默认继承方式public 继承private 继承
cpp
struct A {
    int x;   // 默认 public
};

class B {
    int x;   // 默认 private
};

struct C : A {};  // 默认 public 继承
class D : A {};   // 默认 private 继承

⚠️ 习惯约定struct 通常用于简单的数据聚合(POD 类型),class 用于有复杂行为的类。

⭐ 自增自减运算符 i++++i 的区别?

cpp
int i = 5;
int a = i++;   // a = 5, i = 6(先取值,后自增)
int b = ++i;   // b = 7, i = 7(先自增,后取值)
i++(后置)++i(前置)
返回值返回自增前的旧值(临时对象)返回自增后的新值(引用)
效率需要创建临时对象,稍慢无临时对象,更高效
建议需要旧值时使用优先使用(特别是迭代器)
cpp
// 对于自定义类型(如迭代器),前置更高效
for (auto it = vec.begin(); it != vec.end(); ++it) {  // 推荐 ++it
    // ...
}

sizeof 运算符的常见结果?

cpp
// 基本类型(64 位系统常见值)
sizeof(char)        // 1
sizeof(short)       // 2
sizeof(int)         // 4
sizeof(long)        // 4 或 8(平台相关)
sizeof(long long)   // 8
sizeof(float)       // 4
sizeof(double)      // 8
sizeof(bool)        // 1
sizeof(void*)       // 8(64 位)/ 4(32 位)

// 数组
int arr[10];
sizeof(arr)         // 40(10 * 4)
sizeof(arr) / sizeof(arr[0])  // 10(元素个数)

// ⚠️ 数组退化为指针后
void Foo(int arr[]) {
    sizeof(arr);    // 8(指针大小,不是数组大小!)
}

typedefusing 的区别?

两者都可以定义类型别名,但 using(C++11)更强大:

cpp
// typedef(传统方式)
typedef int Int32;
typedef void (*FuncPtr)(int, int);   // 函数指针别名(语法复杂)
typedef std::vector<int> IntVec;

// using(C++11,推荐)
using Int32 = int;
using FuncPtr = void (*)(int, int);  // 更直观
using IntVec = std::vector<int>;

// using 可以用于模板别名,typedef 不行!
template <typename T>
using Vec = std::vector<T>;          // ✅ using 支持

Vec<int> v;                          // std::vector<int>

🌈 建议:优先使用 using,语法更清晰,且支持模板别名。

#define 宏和 const / constexpr 的区别?

特性#defineconstconstexpr(C++11)
处理阶段预处理期(文本替换)编译期编译期
类型检查❌ 无✅ 有✅ 有
作用域全局(无作用域)遵守作用域规则遵守作用域规则
调试❌ 无法调试✅ 可调试✅ 可调试
内存不占内存可能占内存编译期求值,通常不占
cpp
#define PI 3.14159          // 宏:无类型,全局替换
const double PI = 3.14159;  // 编译期常量,有类型
constexpr double PI = 3.14159;  // 编译期确定值,有类型

// 宏的陷阱
#define SQUARE(x) x * x
int a = SQUARE(3 + 1);     // 展开为 3 + 1 * 3 + 1 = 7(不是 16!)

// 正确做法
#define SQUARE(x) ((x) * (x))  // 加括号
// 更好的做法:使用 constexpr 函数
constexpr int Square(int x) { return x * x; }

⚠️ 建议:尽量用 const / constexpr 替代 #define,避免宏的副作用。


三、基本数据类型

⭐ C++ 中的基本数据类型有哪些?

类别类型大小(常见)范围
整型char1 字节-128 ~ 127
short2 字节-32768 ~ 32767
int4 字节约 ±21 亿
long4/8 字节平台相关
long long8 字节约 ±9.2×10¹⁸
浮点float4 字节精度约 6-7 位
double8 字节精度约 15-16 位
long double8/12/16 字节平台相关
布尔bool1 字节true / false
void无值
宽字符wchar_t2/4 字节平台相关

⚠️ C++ 标准只规定了类型的最小大小,具体大小取决于编译器和平台。可以用 sizeof 确认。

⭐ 隐式类型转换的规则是什么?有什么风险?

C++ 会自动进行隐式类型转换(也叫"类型提升"),基本规则:

char/short → int → unsigned int → long → unsigned long → long long → float → double → long double
cpp
int a = 3;
double b = 2.5;
auto c = a + b;   // a 隐式转为 double,c 是 double 类型

// ⚠️ 风险1:有符号和无符号混用
int x = -1;
unsigned int y = 1;
if (x < y) {
    // 这个分支不会执行!x 被转为 unsigned int,变成一个很大的正数
}

// ⚠️ 风险2:精度丢失
int big = 2000000000;
float f = big;   // float 精度不够,可能丢失数据

// ⚠️ 风险3:缩窄转换
double d = 3.14;
int i = d;       // 小数部分被截断,i = 3

🌈 建议:使用 {} 初始化来检测缩窄转换:

cpp
int i{3.14};  // ❌ 编译错误:缩窄转换

浮点数精度丢失的原因?如何解决?

原因:浮点数在计算机中以 IEEE 754 标准存储,使用二进制表示,很多十进制小数(如 0.1)在二进制中是无限循环小数,无法精确表示。

cpp
double a = 0.1 + 0.2;
std::cout << (a == 0.3) << "\n";  // 输出 0(false!)
std::cout << std::setprecision(20) << a << "\n";  // 0.30000000000000004

解决方案

cpp
// 方法1:使用 epsilon 比较
#include <cmath>
bool AlmostEqual(double a, double b, double epsilon = 1e-9) {
    return std::fabs(a - b) < epsilon;
}

// 方法2:整数运算(如金额用"分"而非"元")
int price_cents = 199;  // 1.99 元

// 方法3:使用第三方高精度库(如 Boost.Multiprecision)

auto 关键字的用法和注意事项?

auto(C++11)让编译器自动推导变量类型:

cpp
auto i = 42;              // int
auto d = 3.14;            // double
auto s = std::string("hello");  // std::string
auto p = std::make_unique<int>(10);  // std::unique_ptr<int>

// 与范围 for 循环配合
std::vector<int> vec = {1, 2, 3};
for (auto& v : vec) { v *= 2; }         // 引用,可修改
for (const auto& v : vec) { /*...*/ }   // 常量引用,不可修改(推荐)

// 函数返回类型推导(C++14)
auto Add(int a, int b) { return a + b; }

注意事项

场景说明
auto 会忽略顶层 const 和引用const int& r = x; auto a = r; → a 是 int(不是 const int&
需要引用必须显式写auto& ref = x;
需要 const 必须显式写const auto& cref = x;
不能用于函数参数(C++20 前)void foo(auto x) 仅 C++20 起支持
不能用于类成员变量auto member = 10;

decltype 的用法?和 auto 有什么区别?

decltype(C++11)获取表达式的类型,不会求值

cpp
int x = 10;
decltype(x) y = 20;      // y 的类型是 int
decltype(x + 0.5) z;     // z 的类型是 double

// 与 auto 的区别
const int& rx = x;
auto a = rx;              // a 是 int(丢掉了 const 和 &)
decltype(rx) b = x;       // b 是 const int&(完整保留类型)

// 常用场景:获取返回类型
template <typename T, typename U>
auto Add(T t, U u) -> decltype(t + u) {
    return t + u;
}

四、变量

⭐ 局部变量、全局变量和静态变量的区别?

特性局部变量全局变量静态局部变量静态全局变量
作用域函数/块内整个程序函数/块内当前文件
生命周期函数执行期间程序运行期间程序运行期间程序运行期间
存储位置全局数据区全局数据区全局数据区
默认初始值未初始化(随机值)000
cpp
int g = 100;              // 全局变量

static int sg = 200;      // 静态全局变量(仅当前文件可见)

void Foo() {
    int local = 10;        // 局部变量,函数结束后销毁
    static int count = 0;  // 静态局部变量,只初始化一次
    ++count;
    std::cout << count << "\n";  // 每次调用递增
}

⭐ C++ 的内存分区(内存布局)是怎样的?

┌──────────────────┐ 高地址
│     内核空间       │
├──────────────────┤
│       栈 (Stack)  │ ↓ 向低地址增长
│                  │ 局部变量、函数参数、返回地址
├──────────────────┤
│       堆 (Heap)   │ ↑ 向高地址增长
│                  │ new/malloc 动态分配
├──────────────────┤
│   全局/静态数据区   │ 全局变量、静态变量
│  (.data / .bss)  │ .data: 已初始化  .bss: 未初始化
├──────────────────┤
│   常量区 (.rodata)│ 字符串字面量、const 全局变量
├──────────────────┤
│   代码区 (.text)  │ 编译后的机器指令
└──────────────────┘ 低地址
区域管理方式存储内容
编译器自动分配释放局部变量、函数参数
手动 new / delete动态分配的对象
全局/静态区程序启动时分配,结束时释放全局变量、static 变量
常量区只读字符串字面量、const 全局常量
代码区只读程序指令

⭐ 栈和堆的区别?

特性栈(Stack)堆(Heap)
管理方式编译器自动管理手动管理(new/delete
分配速度非常快(移动栈指针)较慢(需要查找空闲内存)
大小限制较小(通常 1-8 MB)较大(受物理内存限制)
碎片无碎片(连续分配)可能产生碎片
增长方向高地址 → 低地址低地址 → 高地址
生命周期随作用域自动销毁手动释放或程序结束
cpp
void Example() {
    int stackVar = 10;                   // 栈上分配
    int* heapVar = new int(20);          // 堆上分配
    auto smartPtr = std::make_unique<int>(30); // 堆上分配(智能指针管理)

    delete heapVar;                      // 必须手动释放
    // smartPtr 离开作用域自动释放
}

extern 关键字的作用?

extern 用于声明一个在其他文件中定义的全局变量或函数:

cpp
// file1.cpp
int globalVar = 42;           // 定义
void Foo() { /*...*/ }        // 定义

// file2.cpp
extern int globalVar;          // 声明(不分配内存)
extern void Foo();             // 声明(函数可省略 extern)

void Bar() {
    std::cout << globalVar;    // 使用 file1 中定义的变量
    Foo();
}

constconstexpr 的区别?

特性constconstexpr(C++11)
含义运行时常量(承诺不修改)编译时常量(必须编译期可求值)
初始化时机可以在运行时初始化必须在编译时初始化
用于函数❌ 不能修饰普通函数✅ 修饰函数,要求编译期可执行
cpp
const int a = 10;           // ✅ 编译期确定
const int b = GetValue();   // ✅ 运行时确定也可以

constexpr int c = 10;       // ✅ 编译期确定
// constexpr int d = GetValue();  // ❌ 除非 GetValue 也是 constexpr

constexpr int Square(int x) { return x * x; }
constexpr int val = Square(5);   // 编译期计算,val = 25
int arr[Square(3)];              // ✅ 可以用于数组大小

五、函数

⭐ 值传递、引用传递和指针传递的区别?

方式语法是否拷贝能否修改原值能否为空
值传递void f(int x)✅ 拷贝❌ 不能
引用传递void f(int& x)❌ 不拷贝✅ 能❌ 不能为空
常量引用void f(const int& x)❌ 不拷贝❌ 不能❌ 不能为空
指针传递void f(int* x)拷贝指针✅ 能✅ 可以为空
cpp
void ByValue(int x)         { x = 100; }         // 不影响原值
void ByRef(int& x)          { x = 100; }         // 修改原值
void ByConstRef(const int& x) { /* 只读 */ }     // 不拷贝,不修改
void ByPtr(int* x)          { if (x) *x = 100; } // 需要判空

int a = 1;
ByValue(a);    // a 仍为 1
ByRef(a);      // a 变为 100
ByPtr(&a);     // a 变为 100

🌈 最佳实践

  • 小类型(intdouble)→ 值传递
  • 大对象只读 → const 引用
  • 需要修改 → 非 const 引用
  • 可能为空 → 指针

⭐ 函数重载(Overload)是什么?重载的规则?

函数重载是指同一作用域内,多个同名函数的参数列表不同

cpp
int Add(int a, int b)          { return a + b; }
double Add(double a, double b) { return a + b; }
int Add(int a, int b, int c)   { return a + b + c; }

Add(1, 2);       // 调用第一个
Add(1.0, 2.0);   // 调用第二个
Add(1, 2, 3);    // 调用第三个

重载规则

可以用于区分重载不能用于区分重载
✅ 参数个数不同❌ 仅返回类型不同
✅ 参数类型不同❌ 仅参数名不同
✅ 参数顺序不同
✅ const 修饰(成员函数)
cpp
// ❌ 错误:仅返回类型不同,不构成重载
int Foo(int x);
double Foo(int x);  // 编译错误

// ✅ const 重载(成员函数)
class MyClass {
    void Print() { std::cout << "non-const\n"; }
    void Print() const { std::cout << "const\n"; }  // ✅ 可以重载
};

默认参数的规则?

cpp
// 默认参数从右往左
void Foo(int a, int b = 10, int c = 20);  // ✅
// void Foo(int a = 1, int b, int c);     // ❌ 错误

// 声明和定义分离时,默认参数只能在声明中写
// header.h
void Foo(int a, int b = 10);

// source.cpp
void Foo(int a, int b) {  // 这里不能再写默认值
    // ...
}

inline 内联函数的作用?

内联函数在编译时将函数体展开到调用处,避免函数调用的开销:

cpp
inline int Max(int a, int b) {
    return (a > b) ? a : b;
}

// 调用 Max(3, 5) 会被编译器替换为 (3 > 5) ? 3 : 5
特性说明
优点减少函数调用开销(压栈/跳转/返回)
缺点代码膨胀(函数体在每个调用处展开)
适用短小频繁调用的函数(几行以内)
注意inline 只是建议,编译器可能忽略
类内定义类内定义的成员函数默认是 inline

⚠️ 现代编译器已经很智能,通常会自动决定是否内联,不需要手动指定。

⭐ 什么是函数指针?什么是 std::function

cpp
// 函数指针
int Add(int a, int b) { return a + b; }
int (*funcPtr)(int, int) = Add;     // 传统语法
auto funcPtr2 = Add;                // 或用 auto
int result = funcPtr(3, 5);         // 调用

// std::function(C++11,更通用)
#include <functional>
std::function<int(int, int)> func;

func = Add;                                         // 普通函数
func = [](int a, int b) { return a + b; };          // Lambda
func = std::bind(Add, std::placeholders::_1, 10);   // 绑定参数

int r = func(3, 5);
特性函数指针std::function
可存储普通函数、静态成员函数任何可调用对象
Lambda❌ 无捕获的 Lambda 才行✅ 支持
成员函数语法复杂✅ 配合 std::bind
开销零开销有少量开销(类型擦除)

六、运算符

⭐ 逻辑运算符的短路求值是什么?

cpp
// && 短路:左边为 false,不执行右边
if (ptr != nullptr && ptr->IsValid()) {
    // 如果 ptr 为空,不会执行 ptr->IsValid(),避免崩溃
}

// || 短路:左边为 true,不执行右边
if (IsDefault() || LoadConfig()) {
    // 如果 IsDefault() 为 true,不会执行 LoadConfig()
}

⚠️ 利用短路特性可以做安全判断,但不要在短路表达式中放有副作用的操作(难以维护)。

⭐ 位运算符有哪些?常见应用?

运算符含义示例
&按位与5 & 31
|按位或5 | 37
^按位异或5 ^ 36
~按位取反~5-6
<<左移1 << 38
>>右移16 >> 24
cpp
// 常见应用

// 1. 判断奇偶
bool isOdd = (n & 1);

// 2. 交换两个数
a ^= b; b ^= a; a ^= b;

// 3. 乘除 2 的幂
int x = n << 1;   // n * 2
int y = n >> 1;   // n / 2

// 4. 标志位操作
const int READ  = 1 << 0;  // 0001
const int WRITE = 1 << 1;  // 0010
const int EXEC  = 1 << 2;  // 0100

int perm = READ | WRITE;       // 设置权限
bool canRead = perm & READ;    // 检查权限
perm &= ~WRITE;                // 清除权限
perm ^= EXEC;                  // 切换权限

💬 评论

加载评论中...