C++相关知识总结

内容分享1周前发布
0 0 0

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引入了
std::thread
作为标准线程库,替代了平台相关的线程API。每个
std::thread
对象代表一个执行线程,支持函数、Lambda表达式、成员函数等多种调用方式。

关键特性

线程在构造时立即开始执行必须调用
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 深度解析

知识点讲解

std::mutex
是C++11提供的基本互斥锁,用于保护共享数据免受数据竞争。C++提供了多种互斥锁变体以适应不同场景。

锁类型对比


std::mutex
:基本互斥锁,不可递归
std::recursive_mutex
:可递归互斥锁
std::timed_mutex
:带超时功能的互斥锁
std::shared_mutex
(C++17):读写锁


#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)模式管理锁资源,确保异常安全。

区别


std::lock_guard
:简单RAII包装器,构造时锁定,析构时解锁
std::unique_lock
:更灵活的RAII包装器,支持延迟锁定、手动锁定解锁、所有权转移


#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;
}

条件变量

生产者-消费者模式深度实现

知识点讲解
条件变量用于线程间的同步,允许线程等待特定条件成立。必须与互斥锁配合使用,且需要防止虚假唤醒。

关键点

总是使用
std::unique_lock
与条件变量配合使用while循环检查条件,防止虚假唤醒使用
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
:在新线程中异步执行
std::launch::deferred
:延迟执行,在调用get()时执行
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表达式参数使用
auto
类型推导,使得Lambda表达式更加通用。


#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;
}
© 版权声明

相关文章

暂无评论

您必须登录才能参与评论!
立即登录
none
暂无评论...