C++ 面试知识点大全
一、C++基础语法
指针与引用
什么是指针?指针的大小及用法?
指针:指向另外一种类型的复合类型。
指针的大小:在64位计算机中,指针占8个字节空间。
#include<iostream>
using namespace std;
int main(){
int *p = nullptr;
cout << sizeof(p) << endl; // 8
char *p1 = nullptr;
cout << sizeof(p1) << endl; // 8
return 0;
}
指针的用法:
指向普通对象的指针:
#include <iostream>
using namespace std;
class A {};
int main(){
A *p = new A();
return 0;
}
指向常量对象的指针(常量指针):
#include <iostream>
using namespace std;
int main(void){
const int c_var = 10;
const int * p = &c_var;
cout << *p << endl;
return 0;
}
指向函数的指针(函数指针):
#include <iostream>
using namespace std;
int add(int a, int b){
return a + b;
}
int main(void){
int (*fun_p)(int, int);
fun_p = add;
cout << fun_p(1, 6) << endl;
return 0;
}
什么是野指针和悬空指针?
悬空指针:若指针指向一块内存空间,当这块内存空间被释放后,该指针依然指向这块内存空间。
void *p = malloc(size);
free(p);
// 此时p指向的内存空间已释放,p就是悬空指针
野指针:不确定其指向的指针,未初始化的指针为”野指针”。
void *p;
// 此时p是"野指针"
指针和引用的区别是什么?
是否可变:指针所指向的内存空间在程序运行过程中可以改变,而引用所绑定的对象一旦绑定就不能改变。是否占内存:指针本身在内存中占有内存空间,引用相当于变量的别名,在内存中不占内存空间。是否可为空:指针可以为空,但是引用必须绑定对象。是否能为多级:指针可以有多级,但是引用只能一级。
常量指针和指针常量的区别是什么?
常量指针:指向常量的指针
const int * p;
int const * p;
#include <iostream>
using namespace std;
int main(){
const int c_var = 8;
const int *p = &c_var;
*p = 6; // error: assignment of read-only location '* p'
return 0;
}
指针常量:指针本身是常量
const int var;
int * const c_p = &var;
#include <iostream>
using namespace std;
int main(){
int var, var1;
int * const c_p = &var;
c_p = &var1; // error: assignment of read-only variable 'c_p'
return 0;
}
变量与作用域
C++全局变量、局部变量、静态全局变量、静态局部变量的区别?
从作用域看:
全局变量:具有全局作用域静态全局变量:具有文件作用域局部变量:具有局部作用域静态局部变量:具有局部作用域,只初始化一次
从分配内存空间看:
静态存储区:全局变量,静态局部变量,静态全局变量栈:局部变量
extern C的作用是什么?
当C++程序需要调用C语言编写的函数时使用:
extern "C"{
int strcmp(const char*, const char*);
}
结构体与类
C和C++ struct的区别?
数据类型:C中struct是用户自定义数据类型;C++中struct是抽象数据类型成员函数:C中不能定义成员函数;C++中可以定义成员函数访问权限:C中没有访问权限设置;C++中有访问权限定义变量:C中需要加struct关键字;C++中不需要
C++中struct和class的区别是什么?
默认访问权限:struct默认public,class默认private默认继承方式:struct默认public继承,class默认private继承
#include<iostream>
using namespace std;
class A{
int x; // 默认private
};
struct B{
int x; // 默认public
};
class C : B { // 默认private继承
};
struct D : A { // 默认public继承
};
二、C++面向对象
面向对象基础
什么是面向对象?面向对象的三大特性
面向对象:对象是指具体的某一个事物,这些事物的抽象就是类。
三大特性:
封装:将实现过程和数据封装,只能通过接口访问继承:子类继承父类的特征和行为多态:不同对象对同一消息做出不同响应
重载、重写、隐藏的区别是什么?
重载:同一作用域,同名函数参数列表不同
class A {
public:
void fun(int tmp);
void fun(float tmp); // 重载
void fun(int tmp, float tmp1); // 重载
};
隐藏:派生类函数屏蔽同名基类函数
class Base {
public:
void fun(int tmp, float tmp1) { }
};
class Derive : public Base {
public:
void fun(int tmp) { } // 隐藏基类同名函数
};
重写(覆盖):派生类重新定义虚函数
class Base {
public:
virtual void fun(int tmp) { }
};
class Derived : public Base {
public:
virtual void fun(int tmp) { } // 重写
};
虚函数与多态
什么是虚函数?什么是纯虚函数?
虚函数:
class A{
public:
virtual void v_fun() { cout << "A::v_fun()" << endl; }
};
纯虚函数:
class A{
public:
virtual void fun() = 0; // 纯虚函数
};
虚函数和纯虚函数的区别?
虚函数必须实现,纯虚函数不需要实现含有纯虚函数的类是抽象类,不能实例化纯虚函数在派生类中必须被实现
虚函数的实现机制是什么?
通过虚函数表(vtable)实现:
每个有虚函数的类都有一个虚函数表每个对象有一个虚表指针(vptr)指向虚函数表虚函数表中存放虚函数的地址
为什么析构函数一般写为虚函数?
防止内存泄漏:
class Base {
public:
~Base() { } // 非虚析构函数
};
class Derived : public Base {
~Derived() { } // 不会被调用
};
Base *p = new Derived();
delete p; // 只调用Base的析构函数,内存泄漏
为什么构造函数不写为虚函数?
对象未实例化,无法找到vtable构造函数不能通过父类指针调用
三、C++内存管理
内存分区
C++中的内存分区有哪些?
栈区:局部变量,函数参数堆区:动态分配的内存全局区:全局变量和静态变量常量区:字符串常量等代码区:函数体的二进制代码
C++空类的大小是多少?
1字节,为了保证每个实例有唯一地址。
只含虚函数的类的大小是多大?
32位系统4字节,64位系统8字节(虚表指针的大小)。
动态内存管理
malloc和new的区别是什么?
位置:malloc从堆分配;new从自由存储区分配返回类型:malloc返回void*;new返回对象类型指针失败处理:malloc返回NULL;new抛出异常构造函数:new会调用构造函数;malloc不会
delete和free的区别
delete会调用析构函数,free不会delete是操作符,free是函数delete带类型信息
delete实现原理?delete和delete[]的区别?
delete调用析构函数并释放单个对象delete[]对数组中每个元素调用析构函数
四、C++新特性
C++11特性
auto类型推导
auto x = 5; // int
auto y = 3.14; // double
范围for循环
std::vector<int> vec = {1, 2, 3};
for(auto &num : vec) {
cout << num << endl;
}
智能指针
// unique_ptr
std::unique_ptr<int> ptr1 = std::make_unique<int>(10);
// shared_ptr
std::shared_ptr<int> ptr2 = std::make_shared<int>(20);
// weak_ptr
std::weak_ptr<int> w_ptr = ptr2;
Lambda表达式
auto lambda = [](int a, int b) { return a + b; };
移动语义
std::vector<int> v1 = {1, 2, 3};
std::vector<int> v2 = std::move(v1); // 移动构造
nullptr
int *p = nullptr; // 代替NULL
右值引用
左值和右值的区别?
左值:有持久状态的表达式右值:临时对象,即将被销毁
std::move()函数的实现原理
template <typename T>
typename remove_reference<T>::type&& move(T&& t) {
return static_cast<typename remove_reference<T>::type &&>(t);
}
五、C++ STL
容器分类
序列式容器:
vector:动态数组deque:双端队列list:双向链表array:固定大小数组
关联式容器:
set/multiset:集合map/multimap:映射unordered_set/unordered_multiset:哈希集合unordered_map/unordered_multimap:哈希映射
容器适配器:
stack:栈queue:队列priority_queue:优先队列
常用容器特性
vector中push_back和emplace_back的区别?
push_back:创建对象再拷贝/移动emplace_back:直接在容器尾部构造
使用迭代器怎么删除一个元素?
序列式容器:
std::vector<int> vec = {1, 2, 3};
for(auto it = vec.begin(); it != vec.end(); ) {
if(*it == 2) {
it = vec.erase(it);
} else {
++it;
}
}
关联式容器:
std::map<int, int> m;
for(auto it = m.begin(); it != m.end(); ) {
if(it->first == 2) {
m.erase(it++);
} else {
++it;
}
}
六、C++问题排查
程序崩溃定位
linux程序崩溃怎么定位问题?
使用core dump文件:
gdb myapp core
(gdb) bt
直接调试:
gdb myapp
(gdb) run
(gdb) bt
内存泄漏检测
内存泄露怎么检测?
使用Valgrind:
valgrind --tool=memcheck --leak-check=full ./myapp
内存泄露怎么避免?
使用智能指针遵循RAII原则使用内存检测工具
七、现代C++多线程编程
线程基础
std::thread 基本用法与原理
知识点讲解:
C++11引入了作为标准线程库,替代了平台相关的线程API。每个
std::thread对象代表一个执行线程,支持函数、Lambda表达式、成员函数等多种调用方式。
std::thread
关键特性:
线程在构造时立即开始执行必须调用等待线程结束或
join()分离线程线程不可复制但可移动
detach()
#include <iostream>
#include <thread>
#include <chrono>
void hello_function() {
std::cout << "Hello from function thread! Thread ID: "
<< std::this_thread::get_id() << std::endl;
}
class Worker {
public:
void operator()() {
std::cout << "Hello from functor thread! Thread ID: "
<< std::this_thread::get_id() << std::endl;
}
};
int main() {
// 方式1:函数指针
std::thread t1(hello_function);
// 方式2:函数对象
std::thread t2(Worker());
// 方式3:Lambda表达式
std::thread t3([]() {
std::cout << "Hello from lambda thread! Thread ID: "
<< std::this_thread::get_id() << std::endl;
});
t1.join();
t2.join();
t3.join();
return 0;
}
互斥锁与同步机制
std::mutex 深度解析
知识点讲解:
是C++11提供的基本互斥锁,用于保护共享数据免受数据竞争。C++提供了多种互斥锁变体以适应不同场景。
std::mutex
锁类型对比:
:基本互斥锁,不可递归
std::mutex:可递归互斥锁
std::recursive_mutex:带超时功能的互斥锁
std::timed_mutex(C++17):读写锁
std::shared_mutex
#include <iostream>
#include <thread>
#include <mutex>
#include <vector>
#include <chrono>
std::mutex cout_mutex;
void safe_print(const std::string& msg) {
std::lock_guard<std::mutex> lock(cout_mutex);
std::cout << "[" << std::this_thread::get_id() << "] " << msg << std::endl;
}
int main() {
std::vector<std::thread> threads;
for (int i = 0; i < 5; ++i) {
threads.emplace_back([i]() {
safe_print("Thread " + std::to_string(i) + " is running");
});
}
for (auto& t : threads) {
t.join();
}
return 0;
}
RAII锁管理:lock_guard vs unique_lock
知识点讲解:
C++使用RAII(Resource Acquisition Is Initialization)模式管理锁资源,确保异常安全。
区别:
:简单RAII包装器,构造时锁定,析构时解锁
std::lock_guard:更灵活的RAII包装器,支持延迟锁定、手动锁定解锁、所有权转移
std::unique_lock
#include <iostream>
#include <thread>
#include <mutex>
std::mutex mtx;
int shared_data = 0;
void use_lock_guard() {
std::lock_guard<std::mutex> lock(mtx);
shared_data++;
std::cout << "lock_guard: " << shared_data << std::endl;
}
void use_unique_lock() {
std::unique_lock<std::mutex> lock(mtx, std::defer_lock);
// 执行一些不需要锁的操作
std::cout << "Doing some work without lock..." << std::endl;
// 手动锁定
lock.lock();
shared_data++;
std::cout << "unique_lock: " << shared_data << std::endl;
}
条件变量
生产者-消费者模式深度实现
知识点讲解:
条件变量用于线程间的同步,允许线程等待特定条件成立。必须与互斥锁配合使用,且需要防止虚假唤醒。
关键点:
总是使用与条件变量配合使用while循环检查条件,防止虚假唤醒使用
std::unique_lock或
notify_one()通知等待线程
notify_all()
#include <iostream>
#include <queue>
#include <thread>
#include <mutex>
#include <condition_variable>
#include <chrono>
template<typename T>
class ThreadSafeQueue {
private:
mutable std::mutex mtx;
std::queue<T> data_queue;
std::condition_variable data_cond;
bool shutdown = false;
public:
// 生产者:添加数据
void push(T value) {
std::unique_lock<std::mutex> lock(mtx);
data_queue.push(std::move(value));
data_cond.notify_one();
}
// 消费者:获取数据
bool pop(T& value) {
std::unique_lock<std::mutex> lock(mtx);
data_cond.wait(lock, [this]() {
return !data_queue.empty() || shutdown;
});
if (shutdown && data_queue.empty()) return false;
value = std::move(data_queue.front());
data_queue.pop();
return true;
}
};
原子操作与内存模型
std::atomic 深度解析
知识点讲解:
原子操作提供无锁编程能力,确保操作的原子性。C++11提供了多种原子类型和内存顺序选项。
内存顺序语义:
:只保证原子性,不保证顺序
memory_order_relaxed:保证该操作后的读写不会被重排到前面
memory_order_acquire:保证该操作前的读写不会被重排到后面
memory_order_release:最严格的顺序一致性(默认)
memory_order_seq_cst
#include <atomic>
#include <thread>
#include <vector>
#include <iostream>
class AtomicCounter {
private:
std::atomic<int> count{0};
public:
void increment() {
count.fetch_add(1, std::memory_order_relaxed);
}
int get() const {
return count.load(std::memory_order_acquire);
}
};
int main() {
AtomicCounter counter;
std::vector<std::thread> threads;
for (int i = 0; i < 10; ++i) {
threads.emplace_back([&counter]() {
for (int j = 0; j < 1000; ++j) {
counter.increment();
}
});
}
for (auto& t : threads) {
t.join();
}
std::cout << "Final counter value: " << counter.get() << std::endl;
return 0;
}
异步编程
std::async 与 std::future
知识点讲解:
提供了一种简单的异步执行机制,
std::async用于获取异步操作的结果。
std::future
启动策略:
:在新线程中异步执行
std::launch::async:延迟执行,在调用get()时执行
std::launch::deferred:由实现决定(默认)
std::launch::async | std::launch::deferred
#include <iostream>
#include <future>
#include <chrono>
#include <vector>
#include <numeric>
std::future<long long> async_sum(const std::vector<int>& data) {
return std::async(std::launch::async, [data]() {
std::cout << "Async calculation started" << std::endl;
std::this_thread::sleep_for(std::chrono::seconds(1));
return std::accumulate(data.begin(), data.end(), 0LL);
});
}
int main() {
std::vector<int> data = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
auto future = async_sum(data);
std::cout << "Main thread doing other work..." << std::endl;
long long sum = future.get();
std::cout << "Sum: " << sum << std::endl;
return 0;
}
线程池模式
知识点讲解:
线程池管理一组工作线程,避免频繁创建销毁线程的开销。现代C++特性使得线程池实现更加简洁安全。
#include <iostream>
#include <vector>
#include <queue>
#include <thread>
#include <mutex>
#include <condition_variable>
#include <future>
#include <functional>
class ThreadPool {
public:
explicit ThreadPool(size_t threads) : stop(false) {
for (size_t i = 0; i < threads; ++i) {
workers.emplace_back([this] {
for (;;) {
std::function<void()> task;
{
std::unique_lock<std::mutex> lock(this->queue_mutex);
this->condition.wait(lock, [this] {
return this->stop || !this->tasks.empty();
});
if (this->stop && this->tasks.empty()) return;
task = std::move(this->tasks.front());
this->tasks.pop();
}
task();
}
});
}
}
template<class F, class... Args>
auto enqueue(F&& f, Args&&... args) -> std::future<typename std::result_of<F(Args...)>::type> {
using return_type = typename std::result_of<F(Args...)>::type;
auto task = std::make_shared<std::packaged_task<return_type()>>(
std::bind(std::forward<F>(f), std::forward<Args>(args)...)
);
std::future<return_type> res = task->get_future();
{
std::unique_lock<std::mutex> lock(queue_mutex);
if (stop) throw std::runtime_error("enqueue on stopped ThreadPool");
tasks.emplace([task](){ (*task)(); });
}
condition.notify_one();
return res;
}
~ThreadPool() {
{
std::unique_lock<std::mutex> lock(queue_mutex);
stop = true;
}
condition.notify_all();
for (std::thread &worker : workers)
worker.join();
}
private:
std::vector<std::thread> workers;
std::queue<std::function<void()>> tasks;
std::mutex queue_mutex;
std::condition_variable condition;
bool stop;
};
八、C++14/17/20新特性
C++14核心特性
泛型Lambda表达式
知识点讲解:
C++14允许Lambda表达式参数使用类型推导,使得Lambda表达式更加通用。
auto
#include <iostream>
#include <vector>
int main() {
std::vector<int> numbers = {1, 2, 3, 4, 5};
std::vector<std::string> strings = {"hello", "world", "cpp"};
// 泛型Lambda
auto printer = [](const auto& container) {
for (const auto& item : container) {
std::cout << item << " ";
}
std::cout << std::endl;
};
printer(numbers);
printer(strings);
return 0;
}
返回类型推导
知识点讲解:
C++14扩展了函数返回类型推导,使得函数编写更加简洁。
#include <iostream>
#include <vector>
// 返回类型自动推导
auto create_vector() {
return std::vector<int>{1, 2, 3, 4, 5};
}
auto add(int a, int b) {
return a + b;
}
int main() {
auto vec = create_vector();
auto result = add(10, 20);
std::cout << "Vector size: " << vec.size() << std::endl;
std::cout << "Add result: " << result << std::endl;
return 0;
}
C++17核心特性
结构化绑定(Structured Bindings)
知识点讲解:
C++17引入了结构化绑定,可以方便地将pair、tuple或结构体的成员绑定到变量上,简化代码。
#include <iostream>
#include <map>
#include <tuple>
int main() {
// map遍历
std::map<int, std::string> my_map = {{1, "one"}, {2, "two"}};
for (const auto& [key, value] : my_map) {
std::cout << key << ": " << value << std::endl;
}
// tuple分解
auto [x, y, z] = std::make_tuple(1, 2.0, "three");
std::cout << x << ", " << y << ", " << z << std::endl;
return 0;
}
std::optional
知识点讲解:
表示一个可能包含值也可能不包含值的容器,替代了使用特殊值或指针表示”无值”的情况。
std::optional
#include <iostream>
#include <optional>
#include <vector>
std::optional<int> find_number(const std::vector<int>& vec, int target) {
for (int num : vec) {
if (num == target) {
return num;
}
}
return std::nullopt;
}
int main() {
std::vector<int> numbers = {1, 3, 5, 7, 9};
auto result = find_number(numbers, 5);
if (result.has_value()) {
std::cout << "Found: " << result.value() << std::endl;
} else {
std::cout << "Not found" << std::endl;
}
return 0;
}
C++20核心特性
概念(Concepts)
知识点讲解:
Concepts是C++20的重大特性,用于对模板参数进行约束,提供更好的编译时错误信息和代码可读性。
#include <iostream>
#include <concepts>
#include <vector>
template<typename T>
concept Arithmetic = std::is_arithmetic_v<T>;
template<Arithmetic T>
T square(T x) {
return x * x;
}
void print_arithmetic(Arithmetic auto value) {
std::cout << "Arithmetic value: " << value << std::endl;
}
int main() {
std::cout << "Square of 5: " << square(5) << std::endl;
std::cout << "Square of 3.14: " << square(3.14) << std::endl;
print_arithmetic(42);
print_arithmetic(2.718);
return 0;
}
范围(Ranges)
知识点讲解:
C++20 Ranges提供了操作序列的现代方法,支持惰性求值和管道操作,使代码更加简洁。
#include <iostream>
#include <vector>
#include <ranges>
#include <algorithm>
namespace rv = std::ranges::views;
int main() {
std::vector<int> numbers = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
// 使用Ranges管道操作
auto result = numbers
| rv::filter([](int n) { return n % 2 == 0; })
| rv::transform([](int n) { return n * n; });
std::cout << "Even squares: ";
for (int n : result) {
std::cout << n << " ";
}
std::cout << std::endl;
return 0;
}
九、现代C++设计模式与最佳实践
RAII模式
知识点讲解:
RAII(Resource Acquisition Is Initialization)是C++的核心编程理念,通过对象的生命周期管理资源。
#include <iostream>
#include <memory>
#include <fstream>
class FileHandler {
private:
std::unique_ptr<std::fstream> file;
public:
explicit FileHandler(const std::string& filename) {
file = std::make_unique<std::fstream>(filename);
if (!file->is_open()) {
throw std::runtime_error("Cannot open file");
}
std::cout << "File opened" << std::endl;
}
~FileHandler() {
if (file && file->is_open()) {
file->close();
std::cout << "File closed" << std::endl;
}
}
// 禁用拷贝
FileHandler(const FileHandler&) = delete;
FileHandler& operator=(const FileHandler&) = delete;
void write(const std::string& data) {
if (file && file->is_open()) {
*file << data << std::endl;
}
}
};
单例模式
Meyer’s Singleton (推荐)
#include <iostream>
class MeyerSingleton {
private:
MeyerSingleton() = default;
~MeyerSingleton() = default;
public:
static MeyerSingleton& get_instance() {
static MeyerSingleton instance;
return instance;
}
// 禁用拷贝和移动
MeyerSingleton(const MeyerSingleton&) = delete;
MeyerSingleton& operator=(const MeyerSingleton&) = delete;
MeyerSingleton(MeyerSingleton&&) = delete;
MeyerSingleton& operator=(MeyerSingleton&&) = delete;
void do_something() {
std::cout << "MeyerSingleton doing something" << std::endl;
}
};
int main() {
MeyerSingleton::get_instance().do_something();
return 0;
}