c++知识点2
c中四种显式类型转换操作符static_cast,dynamic_cast,reinterpret_cast和const_cast的作用和区别-CSDN博客转换操作符用途安全性典型场景static_cast编译时类型转换相关类型之间✅ 较安全基本类型转换、继承类指针/引用向上/向下转换无运行时检查dynamic_cast运行时安全向下转型多态类型✅ 安全带检查基类指针 → 派生类指针需虚函数const_cast添加或移除const/volatile⚠️ 危险慎用去掉const仅当对象本身非 const 时合法reinterpret_cast低级位模式重解释❌ 非常危险指针 ↔ 整数、不同类型指针互转static_castvsdynamic_cast对比特性static_castdynamic_cast是否需要虚函数❌ 不需要✅ 必须有多态类型检查时机编译时无运行时检查运行时RTTI 检查向下转型安全性❌ 不安全程序员负责✅ 安全自动验证失败行为返回无效指针未定义行为指针返回nullptr引用抛出std::bad_cast性能开销无和 C 风格转换一样快有需查虚表、类型信息能否用于非多态类型✅ 可以❌ 编译错误#include iostream #include cstring // 用于 memset可选 using namespace std; class Base { public: virtual ~Base() default; // 关键使类型成为多态 virtual void say() { std::cout Base\n; } }; class Derived : public Base { public: void say() override { std::cout Derived\n; } void special() { std::cout Special feature!\n; } }; int main() { Base* b new Derived(); // static_cast Derived* d1 static_castDerived*(b); d1-special(); // ✅ 正常工作但靠程序员保证正确 d1-say(); // dynamic_cast Derived* d2 dynamic_castDerived*(b); if (d2) d2-special(); // ✅ 安全且会检查 d2-say(); Base* b1 new Base(); // 实际是 Base 对象 // static_cast —— 危险 Derived* dd1 static_castDerived*(b1); dd1-special(); // ❌ 未定义行为可能崩溃、数据错乱 // dynamic_cast —— 安全 Derived* dd2 dynamic_castDerived*(b1); if (dd2) { //dd2 是否为 nullptr空指针 dd2-special(); } else { std::cout Not a Derived object!\n; // ✅ 正确处理 } }c的智能指针c的智能指针-CSDN博客特性unique_ptrshared_ptrweak_ptr所有权独占共享无仅观察可复制❌✅✅可移动✅✅✅引用计数❌✅❌依赖 shared_ptr性能开销几乎无有原子操作有同 shared_ptr解决循环引用不适用❌✅创建方式make_uniquemake_shared从shared_ptr构造C中的深拷贝和浅拷贝C中的深拷贝和浅拷贝-CSDN博客explicit禁止编译器进行隐式类型转换explicit 禁止编译器进行隐式类型转换-CSDN博客explicit是 C 中一个极其重要且常用的关键字用于禁止编译器进行隐式类型转换implicit conversion从而避免意外的、难以调试的错误。场景行为explicit构造函数禁止从参数类型到类类型的隐式转换explicit转换操作符禁止从类类型到目标类型的隐式转换共同目标提高类型安全性防止意外转换auto类型和decltype类型auto根据初始化表达式推导变量类型auto会忽略顶层 const、引用、volatile只保留底层类型。如果想保留引用或 const需要显式加上const auto。int x 10; const int cx 20; int rx x; auto a x; // a 是 int值拷贝 auto b cx; // b 是 int顶层 const 被丢弃 auto c rx; // c 是 int引用被退化为值 // 想保留引用/const const auto d cx; // d 是 const int auto e x; // e 是 intdecltype根据表达式本身推导其精确类型int x 10; const int cx 20; int rx x; decltype(x) a x; // a 是 int decltype(cx) b cx; // b 是 const int decltype(rx) c x; // c 是 int 因为 rx 声明为引用 decltype((x)) d x; // d 是 int 因为 (x) 是左值表达式 decltype(x 1) e 11; // e 是 int表达式结果是右值constexpr 主要用法1.constexpr变量constexpr int N 100; // 编译期常量 constexpr double PI 3.14159; // 必须用常量表达式初始化 int arr[N]; // ✅ 合法N 是编译期常量2.constexpr函数// C14 constexpr int factorial(int n) { if (n 1) return 1; int result 1; for (int i 2; i n; i) result * i; return result; } // 使用 constexpr int f5 factorial(5); // 编译期计算 → 120 int x 6; int runtime factorial(x); // 运行期计算3.constexpr构造函数用于自定义类型struct Point { constexpr Point(int x, int y) : x(x), y(y) {} constexpr int distance_sq() const { return x * x y * y; } int x, y; }; constexpr Point p(3, 4); constexpr int d p.distance_sq(); // 编译期计算 → 25 Point arr[d]; // ✅ 合法d 是编译期常量什么是右值引用什么是右值引用-CSDN博客C 构造函数 特殊函数C 构造函数相关知识-CSDN博客default 函数在 C11 及以后的标准中 default和 delete是两个非常重要的关键字用于显式控制特殊成员函数的生成行为。 default显式要求编译器生成默认实现告诉编译器“请为这个函数生成默认的实现”即使你已经定义了其他构造函数。支持 default的函数特殊成员函数默认构造函数析构函数拷贝构造函数拷贝赋值运算符移动构造函数C11移动赋值运算符C11⚠️ 注意 default只能用于特殊成员函数不能用于普通成员函数delete函数 delete显式禁止某个函数被使用作用告诉编译器“这个函数不允许被调用即使语法上看起来合法”。典型用途禁止拷贝如单例、资源管理类禁止某些参数类型的调用防止隐式转换禁用不安全的操作示例 1禁止拷贝常见于 RAII 类class NonCopyable { public: NonCopyable() default; // 显式删除拷贝构造和拷贝赋值 NonCopyable(const NonCopyable) delete; NonCopyable operator(const NonCopyable) delete; }; NonCopyable a; // NonCopyable b a; // ❌ 编译错误 // a b; // ❌ 编译错误 这比将拷贝函数设为private更清晰、更早报错编译期 vs 链接期。示例 2禁止特定类型调用防隐式转换class Number { public: Number(int n) : value(n) {} // 允许 int但禁止 double防止意外转换 Number(double) delete; private: int value; }; Number n1(42); // ✅ OK // Number n2(3.14); // ❌ 错误调用了 deleted 函数示例 3禁用动态分配禁止 newclass StackOnly { public: void* operator new(size_t) delete; void* operator new[](size_t) delete; }; StackOnly s; // ✅ OK栈上 // StackOnly* p new StackOnly(); // ❌ 错误 delete可用于任何函数 delete必须在第一次声明时就指定移动操作被 delete 后可能退化到拷贝如果移动被 delete但拷贝存在std::move(obj)会调用拷贝如果允许。所以要禁用所有复制/移动需同时 delete 拷贝和移动。c的多态序号名称实际归属说明1重载多态Overloading Polymorphism编译时函数/运算符重载2强制多态Coercion Polymorphism编译时隐式类型转换如int→double3参数多态Parametric Polymorphism编译时模板泛型4包含多态Inclusion Polymorphism运行时虚函数 继承子类型多态1.编译时多态静态多态在编译阶段就确定调用哪个函数。实现方式函数重载Function Overloading运算符重载Operator Overloading模板Templates→ 泛型编程的核心 特点无运行时开销效率高。2.运行时多态动态多态在程序运行时根据对象实际类型决定调用哪个函数。实现方式虚函数virtual functions 继承通过基类指针/引用调用派生类重写的函数 特点灵活但有虚表vtable和间接调用的开销。std::optional的使用std::optionalT封装了一个类型T的对象这个对象可能被初始化有值也可能未被初始化无值。1.函数可能无法返回有效结果例如查找操作可能失败。#include iostream #include optional #include vector #include string std::optionalint findIndex(const std::vectorint vec, int target) { for (size_t i 0; i vec.size(); i) { if (vec[i] target) { return static_castint(i); // 有值 } } return std::nullopt; // 无值类似 nullptr } int main() { auto idx findIndex({1, 3, 5, 7}, 5); if (idx.has_value()) { std::cout Found at index: *idx \n; } else { std::cout Not found!\n; } }2.替代指针或引用表示“可为空”避免裸指针带来的内存管理问题和歧义。std::optionalstd::string getConfigValue(const std::string key); // 比返回 const char* 或 string* 更安全、更语义清晰3.作为类成员表示“尚未设置”的状态class UserProfile { std::optionalstd::string nickname; // 用户可能还没设置昵称 public: void setNickname(const std::string name) { nickname name; } bool hasNickname() const { return nickname.has_value(); } std::string getNickname() const { return nickname.value_or(Anonymous); } };常用接口方法说明has_value()判断是否有值等价于bool(*this)operator*解引用获取值前提是有值value()获取值若无值则抛出std::bad_optional_accessvalue_or(default)有值则返回值否则返回默认值reset()清除值变为无值状态emplace(args...)就地构造内部对象std::optionalint x 42; if (x) { std::cout *x \n; // 42 std::cout x.value() \n; // 42 } std::cout x.value_or(0) \n; // 42 x.reset(); std::cout x.value_or(-1) \n; // -1std::optional与指针的区别特性T*std::optionalT表示“无值”nullptrnullopt所有权无裸指针有值语义管理内部对象生命周期内存位置堆/栈任意内部存储通常在栈上安全性易悬空、误用更安全编译器帮助检查✅优先用optional表示“可选值”用智能指针表示“可选所有权”可变参数函数在 C 中可变参数函数variadic function指的是可以接受任意数量、任意类型参数的函数。C 提供了两种主要方式实现1、C 风格可变参数不推荐仅用于兼容 C使用cstdarg头文件中的宏va_list,va_start,va_arg,va_end❌ 缺点无类型安全不能处理引用、类对象可能出错容易崩溃示例不推荐#include iostream #include cstdarg // 计算 int 类型参数的和必须提前知道参数个数 int sum(int count, ...) { va_list args; va_start(args, count); // 从 count 后开始读取 int total 0; for (int i 0; i count; i) { total va_arg(args, int); // 假设都是 int } va_end(args); return total; } int main() { std::cout sum(3, 10, 20, 30); // 输出: 60 }2、C11 起可变参数模板Variadic Templates✅推荐这是类型安全、高效、现代 C 的标准做法。typename... Args模板参数包表示 0 个或多个类型Args... args函数参数包表示 0 个或多个参数args...包展开pack expansion把参数一个一个“拆开”必须有终止条件通常是无参重载 核心语法templatetypename... Args void func(Args... args); // Args... 叫“参数包parameter pack”示例 1打印任意数量、任意类型的参数#include iostream // 递归终止无参数 void print() { std::cout \n; } // 递归展开取第一个参数其余继续递归 templatetypename T, typename... Args void print(T first, Args... rest) { std::cout first ; print(rest...); // 展开剩余参数 } int main() { print(1, 2.5, hello, A); // 输出: 1 2.5 hello A }示例 2使用折叠表达式C17 起更简洁#include iostream templatetypename... Args void print(Args... args) { ((std::cout args ), ...); // 折叠表达式 std::cout \n; } int main() { print(1, 2.5, world); // 输出: 1 2.5 world }完美转发 构造对象如make_unique#include memory #include string templatetypename T, typename... Args std::unique_ptrT my_make_unique(Args... args) { return std::unique_ptrT(new T(std::forwardArgs(args)...)); } // 测试类 class Person { std::string name; int age; public: Person(const std::string n, int a) : name(n), age(a) {} void show() { std::cout name , age \n; } }; int main() { auto p my_make_uniquePerson(Alice, 30); p-show(); // 输出: Alice, 30 }std::forward的作用完美转发Perfect Forwarding它的核心作用是在不改变原始值类别左值 / 右值的前提下将参数原样转发给另一个函数。templatetypename T void wrapper(T arg) { other_func(std::forwardT(arg)); // 保持 arg 原来的“身份” }如果调用wrapper(x)x是变量 →左值则other_func收到的是左值引用如果调用wrapper(42)42是字面量 →右值则other_func收到的是右值引用为什么需要std::forward问题普通引用会“丢失”右值信息void process(int x) { std::cout 左值\n; } void process(int x) { std::cout 右值\n; } templatetypename T void bad_wrapper(T arg) { process(arg); // ❌ 总是调用左值版本 } int main() { int a 10; bad_wrapper(a); // 输出: 左值 ✅ bad_wrapper(20); // 输出: 左值 ❌期望是右值 } 问题arg在函数体内是一个“命名变量” → 永远是左值即使传入的是右值如20arg本身也是左值所以process(arg)总是匹配左值重载。解决方案用std::forwardtemplatetypename T void good_wrapper(T arg) { process(std::forwardT(arg)); // ✅ 根据 T 的类型决定转发为左值 or 右值 } int main() { int a 10; good_wrapper(a); // 输出: 左值 good_wrapper(20); // 输出: 右值 ✅ }std::forwardT(arg)能还原arg最初的值类别什么是对象切片对象切片Object Slicing是 C 中一个常见且危险的陷阱发生在将派生类对象赋值给基类对象非指针/引用时派生类的额外成员被“切掉”只保留基类部分。C 中对象是值语义value semantics当你把一个Derived对象赋给Base类型的变量时编译器只拷贝Base部分#include iostream class Base { public: int x 1; virtual void say() { std::cout Base: x \n; } }; class Derived : public Base { public: int y 2; // 派生类特有成员 void say() override { std::cout Derived: x , y \n; } }; int main() { Derived d; d.say(); // 输出: Derived: 1, 2 Base b d; // ❌ 对象切片只拷贝 Base 部分x1y 被丢弃 b.say(); // 输出: Base: 1 注意不是 Derived }b是一个纯粹的Base对象没有虚表指向Derived所以调用的是Base::say()对象切片发生的场景如何避免对象切片场景是否切片说明Base b derived_obj;✅ 是值拷贝切片void func(Base obj); func(derived);✅ 是按值传参切片Base b derived;❌ 否引用多态有效Base* b derived;❌ 否指针多态有效std::vectorBase v; v.push_back(derived);✅ 是容器存储值切片如何避免对象切片方法 1使用指针或引用void process(const Base obj) { // 用 const 引用 obj.say(); // 多态生效 } int main() { Derived d; process(d); // ✅ 输出 Derived: 1, 2 }方法 2禁止按值传递多态对象将基类的拷贝构造函数设为deleteC11 起class Base { public: Base() default; Base(const Base) delete; // 禁止拷贝 Base operator(const Base) delete; // 禁止赋值 virtual Base() default; virtual void say() { /*...*/ } };这样一旦写Base b derived;编译直接报错方法 3容器中存储智能指针// 错误会切片 std::vectorBase shapes; // 正确用指针保持多态 std::vectorstd::unique_ptrBase shapes; shapes.push_back(std::make_uniqueDerived());对象切片 vs 多态行为对象切片正确多态存储方式Base obj derived;Base ref derived;或Base* ptr derived;调用虚函数调用Base版本调用实际对象的版本成员数据只有Base成员完整对象包括派生类成员对象切片只发生在“按值操作”多态对象时。只要使用引用、指针或智能指针就能安全享受 C 多态的好处static_assert的作用static_assert是 C11 引入的一个编译期断言机制用于在编译阶段检查某个常量表达式是否为true。如果条件不满足编译将失败并显示你提供的错误信息。它的核心作用是在编译时捕获逻辑错误、类型约束违规或平台假设不成立等问题而不是等到运行时才发现。static_assert(常量表达式, 错误提示字符串);常量表达式必须在编译期就能求值如sizeof(int) 4、模板参数、constexpr变量等。错误提示字符串必须是字符串字面量C17 起可省略但强烈建议保留。✅ 从 C17 开始允许只写一个参数static_assert(sizeof(int) 4); // 合法C171.验证类型属性常用于模板确保模板参数满足某些要求templatetypename T void process(T value) { static_assert(std::is_integral_vT, T must be an integral type!); // ... }如果用户调用process(3.14)编译器会报错error: static assertion failed: T must be an integral type!2.检查平台或编译器假设例如确保指针大小符合预期static_assert(sizeof(void*) 8, This code requires 64-bit pointers!);如果在 32 位系统上编译直接失败。3.强制接口契约设计约束比如确保结构体没有填充用于网络协议或硬件寄存器映射struct Packet { uint32_t id; uint16_t size; }; static_assert(sizeof(Packet) 6, Packet must be exactly 6 bytes (no padding)!);4.替代#error更灵活比预处理器指令更强大因为可以使用 C 类型系统和constexpr#if defined(_WIN32) static_assert(false, Windows is not supported!); // ❌ 错误见下方说明 #endif⚠️ 注意上面写法在 C17 前可能有问题因为false是常量即使代码路径不执行也会触发。正确做法依赖模板templatetypename T void unsupported_platform() { static_assert(sizeof(T) 0, Platform not supported!); }static_assert是 C 中实现“编译期防御性编程”的利器。它把错误暴露在最早阶段编译时提升代码健壮性、可维护性和文档性。namespace在c中的作用namespace命名空间在 C 中扮演着“容器”和“隔离区”的角色。它的核心作用是为了解决大型项目中不可避免的命名冲突问题并帮助开发者更好地组织代码。你可以把它想象成计算机里的“文件夹”不同文件夹里可以有同名的文件只要路径不同就不会混淆。以下是namespace的具体作用和使用方式1. 解决命名冲突核心作用在 C 语言时代所有的全局变量、函数和类都存在于同一个全局作用域中。如果两个库比如库 A 和库 B都定义了一个叫max()的函数或Buffer的类链接时就会报错重定义错误。namespace将代码封装在独立的作用域内使得不同命名空间下的同名标识符互不干扰。示例namespace Math { int add(int a, int b) { return a b; } } namespace StringHelper { int add(int a, int b) { return a b; } // 虽然同名但不会冲突 } int main() { Math::add(1, 2); // 调用 Math 里的 add StringHelper::add(1, 2); // 调用 StringHelper 里的 add return 0; }为了使用命名空间里的内容C 提供了三种主要方式它们各有优劣使用方式语法示例说明与风险显式限定std::cout最推荐。加上::前缀清晰明确完全无冲突风险。using 声明using std::cout;推荐。只引入特定的一个名字既方便又相对安全。using 指令using namespace std;慎用。将整个命名空间的内容全部导入当前作用域。在头文件中严禁使用否则会导致全局命名空间污染极易引发冲突。