C++ 面试问题
基础概念
1. 什么是指针和引用?它们的区别是什么?
指针:
- 存储另一个变量的内存地址
- 可以被重新赋值指向不同的对象
- 可以为空(nullptr)
- 需要解引用操作符(*)访问值
cpp
int x = 10;
int* ptr = &x; // 指针
*ptr = 20; // 通过指针修改值
ptr = nullptr; // 可以重新赋值引用:
- 现有变量的别名
- 一旦初始化就不能改变指向的对象
- 不能为空
- 直接使用,无需解引用
cpp
int x = 10;
int& ref = x; // 引用
ref = 20; // 直接修改
// ref = y; // 错误:不能重新赋值主要区别:
- 指针占用内存,引用只是别名
- 指针可以为空,引用不能
- 指针可以重新赋值,引用不能
- 指针需要解引用,引用直接使用
2. 什么是虚函数?如何实现多态?
cpp
class Shape {
public:
virtual double area() = 0; // 纯虚函数
virtual ~Shape() = default;
};
class Circle : public Shape {
private:
double radius;
public:
Circle(double r) : radius(r) {}
double area() override {
return 3.14159 * radius * radius;
}
};
class Rectangle : public Shape {
private:
double width, height;
public:
Rectangle(double w, double h) : width(w), height(h) {}
double area() override {
return width * height;
}
};
// 使用多态
void printArea(Shape* shape) {
std::cout << "Area: " << shape->area() << std::endl;
}虚函数表(vtable):
- 每个含有虚函数的类都有一个虚函数表
- 对象包含指向虚函数表的指针
- 调用虚函数时通过虚函数表查找
3. const 关键字的用途
cpp
// 1. const 变量
const int MAX_SIZE = 100;
// 2. const 指针
const int* ptr1 = &x; // 指向常量的指针,值不能修改
int* const ptr2 = &x; // 常量指针,地址不能修改
const int* const ptr3 = &x; // 指向常量的常量指针
// 3. const 引用
void printValue(const int& value) {
// value = 10; // 错误:不能修改
std::cout << value << std::endl;
}
// 4. const 成员函数
class MyClass {
public:
int getValue() const { // 不修改对象状态
return value;
}
private:
int value;
};
// 5. const 返回值
const std::string& getName() {
return name;
}4. static 关键字的用途
cpp
// 1. 静态局部变量
void counter() {
static int count = 0; // 只初始化一次
count++;
std::cout << count << std::endl;
}
// 2. 静态成员变量
class Counter {
public:
static int count; // 所有对象共享
Counter() {
count++;
}
};
int Counter::count = 0; // 必须在类外初始化
// 3. 静态成员函数
class Math {
public:
static int add(int a, int b) { // 不需要对象调用
return a + b;
}
};
// Math::add(2, 3);
// 4. 静态全局变量/函数(文件作用域)
static int globalVar = 10; // 只在本文件可见面向对象
5. 什么是封装?如何实现?
cpp
class BankAccount {
private: // 私有成员,外部无法直接访问
double balance;
public:
BankAccount(double initial) : balance(initial) {}
// 公共接口
void deposit(double amount) {
if (amount > 0) {
balance += amount;
}
}
bool withdraw(double amount) {
if (amount > 0 && balance >= amount) {
balance -= amount;
return true;
}
return false;
}
double getBalance() const {
return balance;
}
};6. 继承类型及其区别
cpp
// 1. 公有继承
class Base {};
class Derived : public Base {}; // 基类的 public 变为 public,protected 变为 protected
// 2. 保护继承
class Derived : protected Base {}; // 基类的 public 和 protected 都变为 protected
// 3. 私有继承
class Derived : private Base {}; // 基类的 public 和 protected 都变为 private用途:
- 公有继承:is-a 关系
- 保护继承:在派生类中实现细节
- 私有继承:has-a 关系
7. 抽象类和接口的区别
cpp
// 抽象类
class Animal {
protected:
std::string name;
public:
Animal(const std::string& n) : name(n) {}
virtual void makeSound() = 0; // 纯虚函数
virtual void eat() { // 可以有实现
std::cout << name << " is eating" << std::endl;
}
virtual ~Animal() = default;
};
// 接口(C++ 中通过纯抽象类实现)
class IShape {
public:
virtual double area() const = 0;
virtual double perimeter() const = 0;
virtual ~IShape() = default;
};区别:
- 抽象类可以有成员变量和非纯虚函数
- 接口通常只包含纯虚函数
- 一个类可以继承多个接口(通过多重继承)
内存管理
8. 栈和堆的区别
| 特性 | 栈 | 堆 |
|---|---|---|
| 分配方式 | 自动 | 手动 |
| 分配速度 | 快 | 慢 |
| 大小限制 | 小 | 大 |
| 内存释放 | 自动 | 手动 |
| 用途 | 局部变量 | 动态分配 |
cpp
// 栈分配
int x = 10; // 自动释放
// 堆分配
int* ptr = new int(10); // 需要手动释放
delete ptr;
// 使用智能指针(推荐)
#include <memory>
std::unique_ptr<int> ptr2 = std::make_unique<int>(10);
// 自动释放9. 内存泄漏及其预防
cpp
// 内存泄漏示例
void memoryLeak() {
int* ptr = new int[100]; // 分配内存
// 忘记释放
// delete[] ptr;
}
// 预防方法
// 1. 使用智能指针
void noLeak() {
std::unique_ptr<int[]> ptr = std::make_unique<int[]>(100);
// 自动释放
}
// 2. RAII 模式
class Resource {
private:
int* data;
public:
Resource(size_t size) : data(new int[size]) {}
~Resource() { delete[] data; }
};
// 3. 遵循 new/delete 配对
void correctUsage() {
int* ptr = new int;
*ptr = 10;
delete ptr; // 必须释放
}10. 深拷贝与浅拷贝
cpp
class ShallowCopy {
public:
int* data;
ShallowCopy(int value) {
data = new int(value);
}
// 默认拷贝构造函数 - 浅拷贝
~ShallowCopy() {
delete data; // 两次释放!
}
};
class DeepCopy {
public:
int* data;
DeepCopy(int value) {
data = new int(value);
}
// 深拷贝构造函数
DeepCopy(const DeepCopy& other) {
data = new int(*other.data); // 创建新副本
}
// 拷贝赋值运算符
DeepCopy& operator=(const DeepCopy& other) {
if (this != &other) {
delete data;
data = new int(*other.data);
}
return *this;
}
~DeepCopy() {
delete data;
}
};模板和泛型
11. 什么是模板?如何使用?
cpp
// 函数模板
template<typename T>
T maximum(T a, T b) {
return (a > b) ? a : b;
}
// 使用
int maxInt = maximum(5, 10);
double maxDouble = maximum(3.14, 2.71);
// 类模板
template<typename T>
class Stack {
private:
std::vector<T> elements;
public:
void push(const T& element) {
elements.push_back(element);
}
T pop() {
T top = elements.back();
elements.pop_back();
return top;
}
bool empty() const {
return elements.empty();
}
};
// 使用
Stack<int> intStack;
Stack<std::string> stringStack;12. 模板特化
cpp
// 通用模板
template<typename T>
class Container {
public:
void print(T value) {
std::cout << "Generic: " << value << std::endl;
}
};
// 显式特化(针对特定类型)
template<>
class Container<bool> {
public:
void print(bool value) {
std::cout << "Boolean: " << (value ? "true" : "false") << std::endl;
}
};
// 使用
Container<int> intContainer;
intContainer.print(42); // Generic: 42
Container<bool> boolContainer;
boolContainer.print(true); // Boolean: trueSTL 容器
13. vector 与 list 的选择
cpp
// vector - 连续内存,随机访问快
std::vector<int> vec = {1, 2, 3, 4, 5};
vec.push_back(6);
int third = vec[2]; // O(1)
// list - 双向链表,插入删除快
std::list<int> lst = {1, 2, 3, 4, 5};
lst.push_back(6);
lst.insert(lst.begin(), 0); // O(1)选择标准:
- vector:需要随机访问,插入/删除主要在末尾
- list:频繁在中间插入/删除
14. map 和 unordered_map 的区别
| 特性 | map | unordered_map |
|---|---|---|
| 实现 | 红黑树 | 哈希表 |
| 查找 | O(log n) | 平均 O(1) |
| 有序 | 是 | 否 |
| 内存 | 较少 | 较多 |
cpp
// map - 有序
std::map<std::string, int> orderedMap;
orderedMap["banana"] = 2;
orderedMap["apple"] = 1;
// 输出:apple, banana(按字母顺序)
// unordered_map - 无序
std::unordered_map<std::string, int> hashMap;
hashMap["banana"] = 2;
hashMap["apple"] = 1;
// 输出顺序不确定并发和线程
15. 如何创建和管理线程?
cpp
#include <thread>
#include <mutex>
#include <atomic>
std::mutex mtx;
int sharedCounter = 0;
void increment() {
std::lock_guard<std::mutex> lock(mtx);
sharedCounter++;
}
std::atomic<int> atomicCounter(0);
void atomicIncrement() {
atomicCounter++;
}
int main() {
// 创建线程
std::thread t1(increment);
std::thread t2(increment);
// 等待线程完成
t1.join();
t2.join();
std::cout << "Counter: " << sharedCounter << std::endl;
// 使用原子操作
std::thread t3(atomicIncrement);
std::thread t4(atomicIncrement);
t3.join();
t4.join();
std::cout << "Atomic Counter: " << atomicCounter << std::endl;
return 0;
}16. 竞态条件和死锁
cpp
// 竞态条件
int counter = 0;
void raceCondition() {
counter++; // 非原子操作,可能导致错误结果
}
// 解决方法
std::mutex mtx;
int safeCounter = 0;
void safeIncrement() {
std::lock_guard<std::mutex> lock(mtx);
safeCounter++;
}
// 死锁示例
std::mutex m1, m2;
void thread1() {
std::lock(m1, m2); // 锁定多个互斥量
std::lock_guard<std::mutex> lock1(m1, std::adopt_lock);
std::lock_guard<std::mutex> lock2(m2, std::adopt_lock);
// 执行操作
}
void thread2() {
std::lock(m1, m2); // 相同的锁定顺序
std::lock_guard<std::mutex> lock1(m1, std::adopt_lock);
std::lock_guard<std::mutex> lock2(m2, std::adopt_lock);
// 执行操作
}高级主题
17. 移动语义和右值引用
cpp
#include <utility>
class Resource {
private:
int* data;
size_t size;
public:
// 构造函数
Resource(size_t s) : size(s), data(new int[s]) {}
// 拷贝构造函数(深拷贝)
Resource(const Resource& other) : size(other.size), data(new int[other.size]) {
std::copy(other.data, other.data + size, data);
}
// 移动构造函数
Resource(Resource&& other) noexcept : size(other.size), data(other.data) {
other.data = nullptr; // 清空原对象
other.size = 0;
}
~Resource() {
delete[] data;
}
};
// 使用移动语义
Resource createResource() {
return Resource(100); // 返回值优化 + 移动语义
}
Resource r1 = createResource(); // 移动构造
Resource r2 = std::move(r1); // 显式移动18. lambda 表达式
cpp
#include <algorithm>
#include <vector>
void lambdaExamples() {
std::vector<int> numbers = {1, 2, 3, 4, 5};
// 基本 lambda
auto square = [](int x) { return x * x; };
int result = square(5); // 25
// 带捕获的 lambda
int multiplier = 2;
auto multiply = [multiplier](int x) { return x * multiplier; };
result = multiply(3); // 6
// 可变捕获
int counter = 0;
auto increment = [counter]() mutable {
counter++;
return counter;
};
// 在算法中使用
std::vector<int> doubled(5);
std::transform(numbers.begin(), numbers.end(), doubled.begin(),
[](int x) { return x * 2; });
// 带谓词的查找
auto it = std::find_if(numbers.begin(), numbers.end(),
[](int x) { return x > 3; });
}19. 异常处理
cpp
#include <stdexcept>
class CustomException : public std::exception {
public:
const char* what() const noexcept override {
return "Custom exception occurred";
}
};
void riskyOperation(bool shouldFail) {
if (shouldFail) {
throw std::runtime_error("Operation failed!");
}
}
void exceptionExample() {
try {
riskyOperation(true);
} catch (const std::runtime_error& e) {
std::cerr << "Runtime error: " << e.what() << std::endl;
} catch (const std::exception& e) {
std::cerr << "Exception: " << e.what() << std::endl;
} catch (...) {
std::cerr << "Unknown exception" << std::endl;
}
// noexcept 指定
void safeFunction() noexcept {
// 保证不会抛出异常
}
}设计模式
20. 单例模式
cpp
class Singleton {
private:
static Singleton* instance;
Singleton() {} // 私有构造函数
public:
// 禁止拷贝和赋值
Singleton(const Singleton&) = delete;
Singleton& operator=(const Singleton&) = delete;
static Singleton& getInstance() {
static Singleton instance; // 线程安全(C++11)
return instance;
}
void doSomething() {
std::cout << "Singleton method called" << std::endl;
}
};
// 使用
Singleton& s = Singleton::getInstance();
s.doSomething();21. 工厂模式
cpp
#include <memory>
enum class ShapeType { CIRCLE, RECTANGLE };
class Shape {
public:
virtual ~Shape() = default;
virtual void draw() const = 0;
};
class Circle : public Shape {
public:
void draw() const override {
std::cout << "Drawing Circle" << std::endl;
}
};
class Rectangle : public Shape {
public:
void draw() const override {
std::cout << "Drawing Rectangle" << std::endl;
}
};
class ShapeFactory {
public:
static std::unique_ptr<Shape> createShape(ShapeType type) {
switch (type) {
case ShapeType::CIRCLE:
return std::make_unique<Circle>();
case ShapeType::RECTANGLE:
return std::make_unique<Rectangle>();
default:
throw std::invalid_argument("Invalid shape type");
}
}
};
// 使用
auto shape = ShapeFactory::createShape(ShapeType::CIRCLE);
shape->draw();性能优化
22. 优化技巧
cpp
// 1. 避免不必要的拷贝
void processString(const std::string& str) { // 使用引用
// 处理字符串
}
// 2. 使用移动语义
std::string createLargeString() {
std::string result = "large string...";
return result; // 移动而非拷贝
}
// 3. 预分配容器大小
std::vector<int> vec;
vec.reserve(1000); // 预分配
// 4. 使用 emplace_back 代替 push_back
std::vector<std::pair<int, int>> pairs;
pairs.emplace_back(1, 2); // 就地构造
// pairs.push_back(std::make_pair(1, 2)); // 先构造再移动
// 5. 使用 constexpr
constexpr int factorial(int n) {
return (n <= 1) ? 1 : n * factorial(n - 1);
}
constexpr int result = factorial(5); // 编译时计算
// 6. 避免虚函数调用(如果性能关键)
class FastClass {
public:
void fastMethod() { // 非虚函数
// 更快
}
};23. 内存对齐
cpp
#include <iostream>
// 结构体填充
struct Padded {
char a; // 1 byte + 3 padding
int b; // 4 bytes
double c; // 8 bytes
}; // 总共 16 bytes
struct Packed {
char a; // 1 byte
int b; // 4 bytes
double c; // 8 bytes
} __attribute__((packed)); // 总共 13 bytes
// 对齐指定
alignas(16) struct AlignedStruct {
int data[4];
};
// 检查对齐
void checkAlignment() {
std::cout << "Alignment of int: " << alignof(int) << std::endl;
std::cout << "Size of Padded: " << sizeof(Padded) << std::endl;
std::cout << "Size of Packed: " << sizeof(Packed) << std::endl;
}实际问题
24. 实现 strcpy 函数
cpp
char* myStrcpy(char* dest, const char* src) {
if (dest == nullptr || src == nullptr) {
return nullptr;
}
char* original = dest;
while ((*dest++ = *src++) != '\0') {
// 空循环
}
return original;
}25. 检测链表中的环
cpp
struct ListNode {
int val;
ListNode* next;
ListNode(int x) : val(x), next(nullptr) {}
};
bool hasCycle(ListNode* head) {
if (head == nullptr || head->next == nullptr) {
return false;
}
ListNode* slow = head;
ListNode* fast = head->next;
while (slow != fast) {
if (fast == nullptr || fast->next == nullptr) {
return false;
}
slow = slow->next;
fast = fast->next->next;
}
return true;
}准备建议
理论准备
- 深入理解基础概念:指针、引用、内存管理
- 掌握面向对象原则:封装、继承、多态
- 熟悉 STL:容器、算法、迭代器
- 了解现代 C++ 特性:C++11/14/17/20
实践准备
- 刷题:LeetCode、HackerRank
- 项目经验:准备 2-3 个代表性项目
- 代码规范:遵循良好的编码风格
- 性能分析:了解性能优化技术
常见问题类型
- 代码分析题:理解并解释代码
- 编程题:实现特定功能
- 设计题:设计类或系统架构
- 调试题:找出代码中的错误
- 性能题:优化代码性能