C++的一些问题

张开发
2026/4/10 23:17:03 15 分钟阅读

分享文章

C++的一些问题
类相关class和struct的异同相同点都可以包含成员变量属性和成员函数方法都支持访问控制修饰符public、private、protected都可以继承其他类 / 结构体也可以被继承都可以包含构造函数、析构函数都可以实现多态、模板等 C 特性都可以使用friend声明外部函数或类不同点特性structclass默认访问权限成员默认是public成员默认是private默认继承方式默认是public继承默认是private继承传统用途主要用于存储数据C 风格结构体的扩展主要用于实现面向对象的类封装数据和操作模板参数不能作为模板参数的关键字C11 前可以作为模板参数的关键字使用建议当需要一个主要用于存储数据的简单结构时使用struct当需要实现封装性强、包含复杂逻辑的面向对象类时使用class保持代码风格一致性避免混用造成混淆实际上struct和class在 C 中本质上是相似的只是默认行为不同开发者可以根据需求灵活选择。什么是C仿函数在 C 中仿函数Functor是一种重载了operator()的类或结构体实例。它的行为类似函数但可以像对象一样存储状态因此也被称为函数对象Function Object。仿函数的特点兼具函数和对象的特性可以像函数一样被调用同时可以拥有成员变量和成员函数来保存状态可定制性通过不同的成员变量值同一个仿函数类可以表现出不同的行为适配性好广泛用于 STL 算法中如sort、find_if等作为自定义操作的参数仿函数 vs 普通函数仿函数可以携带状态通过成员变量普通函数不行仿函数可以被模板特化普通函数不行仿函数的类型是明确的可用于模板参数推导在性能上仿函数调用通常可以被编译器内联优化效率可能高于函数指针C11 及以后lambda 表达式提供了更简洁的匿名函数对象实现但仿函数在需要复用或需要复杂状态管理的场景下仍然非常有用。一般性问题函数和函数指针的类型是什么由什么决定在 C 中函数和函数指针都有明确的类型其类型由函数的参数列表和返回值类型共同决定与函数名或指针变量名无关。1. 函数的类型函数的类型由两部分决定参数列表参数的类型、数量、顺序返回值类型例如// 函数1参数为int返回值为int int add(int a, int b) { return a b; } // 函数2无参数返回值为void void print() { cout hello endl; }函数add的类型是int(int, int)可理解为 “接受两个 int 参数返回 int 的函数”函数print的类型是void()可理解为 “无参数返回 void 的函数”2. 函数指针的类型函数指针是指向函数的指针变量其类型与它所指向的函数的类型完全一致同样由参数列表和返回值类型决定。定义函数指针时需要显式指定其指向的函数类型// 定义函数指针类型与add函数类型匹配 int (*func_ptr1)(int, int); // 定义函数指针类型与print函数类型匹配 void (*func_ptr2)();func_ptr1的类型是int(*)(int, int)指向 “接受两个 int 参数返回 int 的函数” 的指针func_ptr2的类型是void(*)()指向 “无参数返回 void 的函数” 的指针3. 类型匹配规则函数指针只能指向与其类型完全匹配的函数参数列表必须完全一致类型、数量、顺序返回值类型必须完全一致int add(int a, int b) { return a b; } int sub(int a, int b) { return a - b; } double mul(double a, double b) { return a * b; } int main() { int (*calc)(int, int); // 函数指针类型int(*)(int, int) calc add; // 合法类型匹配 calc sub; // 合法类型匹配 calc mul; // 非法参数和返回值类型不匹配double vs int return 0; }4. 简化函数指针类型的方法复杂的函数指针类型可以通过typedef或usingC11简化// 方法1typedef typedef int (*CalcFunc)(int, int); CalcFunc func1 add; // 等价于 int(*func1)(int, int) add; // 方法2using更直观 using PrintFunc void(*)(); PrintFunc func2 print; // 等价于 void(*func2)() print;总结函数的类型由参数列表和返回值类型共同决定。函数指针的类型必须与它所指向的函数类型完全一致。函数名本身不是类型但可以隐式转换为对应函数指针类型的右值。通俗理解锁多线程同时修改共享数据会产生数据竞争锁的作用是强制线程排队访问共享资源保证线程安全。C11 及以后标准库提供了完善的锁机制分为两类锁本体真正锁住资源的「物理锁头」RAII 锁管家自动上锁 / 解锁的工具避免手动操作忘记解锁导致程序卡死。通俗易懂理解一、锁本体std::mutex 普通互斥锁。单人厕所只有一把钥匙一个人线程进去锁门其他人必须排队死等同一个人不能反复锁门会直接卡死。特点最简单、最快90% 的基础场景首选。std::recursive_mutex 递归锁允许同一个人反复进出的单人厕所还是单人使用别人进不来但你已经在厕所里了可以反复锁门 / 开门最后一次性全部解锁即可。特点比普通锁慢专门用于函数递归调用、多层函数嵌套加锁。std::timed_mutex 超时互斥锁排队等厕所超时就走和普通锁一样单人使用但不用死等可以设定等待时间比如等 5 分钟没人开门就放弃等待去执行其他逻辑。特点避免线程无限阻塞灵活可控。std::recursive_timed_mutex 递归超时锁​​​​​​​全能厕所结合递归锁 超时锁的所有功能自己能反复进排队能超时走。特点功能最全性能最差仅特殊场景使用。std::shared_mutex 读写锁C17核心规则读和读兼容读和写、写和写互斥。特点专门用于读多写少的高并发场景比如缓存、配置文件。写必须独占所有人都不能进排他写。读多人可以同时进共享读二、锁管家RAII 工具自动开关锁必用手动调用lock()/unlock()极易忘记解锁C 提供自动管家创建时自动上锁离开代码块大括号时自动解锁。std::lock_guard 极简管家进门自动锁出门自动开中途不能手动解锁、不能暂停。特点零额外开销最简单日常开发首选。std::unique_lock 全能管家​​​​自动开关锁的基础上中途可以手动解锁、重新上锁、超时等待、把锁借给其他线程。特点功能最全有一丢丢性能开销用于需要灵活控制锁的场景。std::shared_lock 读模式管家​​​​​​​读者专用门禁专门配合std::shared_mutex使用获取读权限允许多个读者同时持有锁。C 标准定义核心术语定义互斥体Mutex实现线程独占访问共享资源的同步原语保证同一时间仅有一个线程持有锁RAII资源获取即初始化C 核心设计范式利用对象析构函数自动释放资源杜绝资源泄漏共享 / 独占模式读写锁的双状态共享锁读允许多线程并发持有独占锁写仅单线程持有递归锁同一线程可多次锁定内部维护引用计数解锁次数与锁定次数一致时释放锁超时锁支持指定时间内尝试获取锁超时未获取则返回失败非阻塞等待。一、互斥体类型锁本体头文件mutex1.std::mutex(C11)类型基础非递归、非超时、独占互斥体不可移动 / 复制核心接口lock()阻塞加锁、unlock()解锁、try_lock()非阻塞尝试加锁约束同一线程重复调用lock()会触发未定义行为适用场景无递归、无超时需求的通用线程同步性能最优。2.std::recursive_mutex(C11)类型递归、非超时、独占互斥体核心接口同std::mutex支持同一线程重复锁定约束解锁次数必须与锁定次数严格匹配否则锁无法释放适用场景递归函数、多层嵌套函数调用同一互斥体的场景。3.std::timed_mutex(C11)类型非递归、超时、独占互斥体核心接口新增try_lock_for(时间跨度)、try_lock_until(绝对时间点)特性支持阻塞 / 非阻塞 / 超时三种加锁模式适用场景需要避免线程无限阻塞等待锁的场景。4.std::recursive_timed_mutex(C11)类型递归、超时、独占互斥体特性融合递归锁与超时锁的全部能力适用场景同时需要递归锁定和超时等待的特殊场景。5.std::shared_mutex(C17)类型读者 - 写者共享互斥体支持双模式锁定核心接口独占模式写lock()/unlock()/try_lock()共享模式读lock_shared()/unlock_shared()/try_lock_shared()特性多读者并发单写者独占写优先级高于读适用场景读操作远多于写操作的高并发场景。二、RAII 锁包装器锁管家头文件mutex1.std::lock_guard(C11)类型轻量级独占锁 RAII 包装器无额外性能开销特性构造时自动调用mutex.lock()析构时自动调用mutex.unlock()不支持手动解锁、延迟加锁、移动语义约束仅适配独占互斥体生命周期内持续持有锁适用场景作用域内全程持有锁的通用场景工业界首选。2.std::unique_lock(C11)类型通用型独占锁 RAII 包装器灵活可控特性支持延迟加锁、手动lock/unlock、超时加锁、锁所有权转移内部维护锁状态标记开销略高于lock_guard适用场景条件变量、中途释放锁、超时等待、锁转移的场景。3.std::shared_lock(C14)类型专用于共享互斥体的 RAII 包装器特性构造调用lock_shared()析构调用unlock_shared()支持延迟加锁、超时、移动语义适用场景配合std::shared_mutex实现多线程并发读。三、未定义行为严禁违反未解锁直接销毁互斥体同一线程对std::mutex重复加锁未持有锁时调用unlock()互斥体生命周期短于使用它的线程。总结日常开发优先用std::mutexstd::lock_guard简单高效递归调用用std::recursive_mutex避免死等用std::timed_mutexstd::unique_lock读多写少用std::shared_mutexstd::shared_lock读/std::unique_lock写核心原则永远用 RAII 锁管家杜绝手动lock/unlock。STL相关在一个空的map中直接使用 map[key] value; 会有问题吗​在 C 中在一个空的std::map中直接使用map[key] value;语法是完全没有问题的。这是因为std::map的operator[]操作符设计具有一个特殊的行为当指定的键key在 map 中不存在时它会自动插入一个新的键值对。具体来说首先会在 map 中插入一个新元素键为key值为该类型的默认构造值然后返回对这个新插入值的引用最后通过赋值操作符将value赋给这个引用

更多文章