当前位置: 首页 > news >正文

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_cast<Derived*>(b); d1->special(); // ✅ 正常工作(但靠程序员保证正确) d1->say(); // dynamic_cast Derived* d2 = dynamic_cast<Derived*>(b); if (d2) d2->special(); // ✅ 安全,且会检查 d2->say(); Base* b1 = new Base(); // 实际是 Base 对象! // static_cast —— 危险! Derived* dd1 = static_cast<Derived*>(b1); dd1->special(); // ❌ 未定义行为(可能崩溃、数据错乱) // dynamic_cast —— 安全! Derived* dd2 = dynamic_cast<Derived*>(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_sharedshared_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 是 int&

decltype:根据表达式本身推导其精确类型

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函数

// C++14+ 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 函数

在 C++11 及以后的标准中,= default= delete是两个非常重要的关键字,用于显式控制特殊成员函数的生成行为

= default:显式要求编译器生成默认实现

告诉编译器:“请为这个函数生成默认的实现”,即使你已经定义了其他构造函数。

支持= default的函数(特殊成员函数)

  • 默认构造函数
  • 析构函数
  • 拷贝构造函数
  • 拷贝赋值运算符
  • 移动构造函数(C++11)
  • 移动赋值运算符(C++11)

⚠️ 注意:= default只能用于特殊成员函数,不能用于普通成员函数

delete函数

= delete:显式禁止某个函数被使用

作用:告诉编译器:“这个函数不允许被调用,即使语法上看起来合法”。

典型用途

  1. 禁止拷贝(如单例、资源管理类)
  2. 禁止某些参数类型的调用(防止隐式转换)
  3. 禁用不安全的操作

示例 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:禁用动态分配(禁止 new)

class 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)编译时隐式类型转换(如intdouble
3参数多态(Parametric Polymorphism)编译时模板(泛型)
4包含多态(Inclusion Polymorphism)运行时虚函数 + 继承(子类型多态)

1.编译时多态(静态多态)

  • 编译阶段就确定调用哪个函数。
  • 实现方式:
    • 函数重载(Function Overloading)
    • 运算符重载(Operator Overloading)
    • 模板(Templates)→ 泛型编程的核心

📌 特点:无运行时开销,效率高。

2.运行时多态(动态多态)

  • 程序运行时根据对象实际类型决定调用哪个函数。
  • 实现方式:
    • 虚函数(virtual functions) + 继承
    • 通过基类指针/引用调用派生类重写的函数

📌 特点:灵活,但有虚表(vtable)和间接调用的开销。

std::optional的使用

std::optional<T>封装了一个类型T的对象,这个对象可能被初始化(有值),也可能未被初始化(无值)。

1.函数可能无法返回有效结果

例如:查找操作可能失败。

#include <iostream> #include <optional> #include <vector> #include <string> std::optional<int> findIndex(const std::vector<int>& vec, int target) { for (size_t i = 0; i < vec.size(); ++i) { if (vec[i] == target) { return static_cast<int>(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::optional<std::string> getConfigValue(const std::string& key); // 比返回 const char* 或 string* 更安全、更语义清晰

3.作为类成员表示“尚未设置”的状态

class UserProfile { std::optional<std::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_access
value_or(default)有值则返回值,否则返回默认值
reset()清除值(变为无值状态)
emplace(args...)就地构造内部对象
std::optional<int> 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"; // -1

std::optional与指针的区别

特性T*std::optional<T>
表示“无值”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、C++11 起:可变参数模板(Variadic Templates)✅(推荐!)

这是类型安全、高效、现代 C++ 的标准做法

  • typename... Args模板参数包(表示 0 个或多个类型)
  • Args... args函数参数包(表示 0 个或多个参数)
  • args...包展开(pack expansion),把参数一个一个“拆开”
  • 必须有终止条件(通常是无参重载)

🔑 核心语法:

template<typename... Args> void func(Args... args); // Args... 叫“参数包(parameter pack)”

示例 1:打印任意数量、任意类型的参数

#include <iostream> // 递归终止:无参数 void print() { std::cout << "\n"; } // 递归展开:取第一个参数,其余继续递归 template<typename 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:使用折叠表达式(C++17 起,更简洁)

#include <iostream> template<typename... 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> template<typename T, typename... Args> std::unique_ptr<T> my_make_unique(Args&&... args) { return std::unique_ptr<T>(new T(std::forward<Args>(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_unique<Person>("Alice", 30); p->show(); // 输出: Alice, 30 }

std::forward的作用?完美转发(Perfect Forwarding)

它的核心作用是:

在不改变原始值类别(左值 / 右值)的前提下,将参数原样转发给另一个函数。

template<typename T> void wrapper(T&& arg) { other_func(std::forward<T>(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"; } template<typename T> void bad_wrapper(T&& arg) { process(arg); // ❌ 总是调用左值版本! } int main() { int a = 10; bad_wrapper(a); // 输出: 左值 ✅ bad_wrapper(20); // 输出: 左值 ❌(期望是右值!) }

💥 问题:arg在函数体内是一个“命名变量” → 永远是左值!
即使传入的是右值(如20),arg本身也是左值,所以process(arg)总是匹配左值重载。

解决方案:用std::forward

template<typename T> void good_wrapper(T&& arg) { process(std::forward<T>(arg)); // ✅ 根据 T 的类型决定转发为左值 or 右值 } int main() { int a = 10; good_wrapper(a); // 输出: 左值 good_wrapper(20); // 输出: 右值 ✅ }

std::forward<T>(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 部分(x=1),y 被丢弃 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::vector<Base> v; v.push_back(derived);✅ 是容器存储值,切片

如何避免对象切片?

方法 1:使用指针或引用

void process(const Base& obj) { // 用 const 引用 obj.say(); // 多态生效! } int main() { Derived d; process(d); // ✅ 输出 "Derived: 1, 2" }

方法 2:禁止按值传递多态对象

  • 将基类的拷贝构造函数设为delete(C++11 起)
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::vector<Base> shapes; // 正确:用指针保持多态 std::vector<std::unique_ptr<Base>> shapes; shapes.push_back(std::make_unique<Derived>());

对象切片 vs 多态

行为对象切片正确多态
存储方式Base obj = derived;Base& ref = derived;Base* ptr = &derived;
调用虚函数调用Base版本调用实际对象的版本
成员数据只有Base成员完整对象(包括派生类成员)

对象切片只发生在“按值操作”多态对象时。
只要使用引用、指针或智能指针,就能安全享受 C++ 多态的好处!

static_assert的作用??

static_assert是 C++11 引入的一个编译期断言机制,用于在编译阶段检查某个常量表达式是否为true。如果条件不满足,编译将失败,并显示你提供的错误信息。

它的核心作用是:在编译时捕获逻辑错误、类型约束违规或平台假设不成立等问题,而不是等到运行时才发现。

static_assert(常量表达式, "错误提示字符串");
  • 常量表达式:必须在编译期就能求值(如sizeof(int) == 4、模板参数、constexpr变量等)。
  • 错误提示字符串:必须是字符串字面量(C++17 起可省略,但强烈建议保留)。

✅ 从 C++17 开始,允许只写一个参数:

static_assert(sizeof(int) >= 4); // 合法(C++17+)

1.验证类型属性(常用于模板)

确保模板参数满足某些要求:

template<typename T> void process(T value) { static_assert(std::is_integral_v<T>, "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

⚠️ 注意:上面写法在 C++17 前可能有问题(因为false是常量,即使代码路径不执行也会触发)。
正确做法(依赖模板):

template<typename 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;慎用。将整个命名空间的内容全部导入当前作用域。在头文件中严禁使用,否则会导致全局命名空间污染,极易引发冲突。
http://www.jsqmd.com/news/674618/

相关文章:

  • 如何快速构建黑苹果EFI:OpCore-Simplify终极指南
  • 在统信UOS上,用达梦8数据库替换MySQL的完整迁移与配置指南(含性能对比)
  • 避坑指南:Livox_ros_driver的点云数据,为什么你的标定/算法代码读不了?
  • HTML头部元信息必知避坑指南
  • 测试功能指南 富文本
  • 如何使用go-torch在5分钟内创建你的第一个Go性能火焰图
  • EaseProbe SSH远程探测:支持堡垒机和密钥认证的终极服务器监控方案
  • EcomGPT-7B多语言模型实战:用同一模型服务中国工厂(中文)与海外买家(英文)
  • 谷歌不收录怎么办? 改掉这4个排版坏习惯,收录率直接
  • 如何快速掌握Vue.js技术:从原理到实践的终极指南
  • ECharts饼图内外双标签显示实战:一个‘笨’方法解决产品经理的‘奇葩’需求
  • Java抽象类深度解析(面试必备)
  • 注意力机制模块:2026大厂主流套路:借鉴 EfficientViT 的级联群体注意力(CGA)替换传统自注意力模块
  • DeepSeek-R1-Distill-Qwen-1.5B入门指南:如何用官方tokenizer.apply_chat_template拼接多轮对话
  • Overleaf平台gbt7714参考文献排版完全指南:从问题排查到完美解决
  • Pixel Dream Workshop惊艳效果展示:动态像素粒子系统与GIF导出能力
  • 第5章,[标签 Win32] :设备环境
  • R 4.5回测精度跃迁至毫秒级:基于xts 0.13+和nanotime的Tick级重采样方案(附NASA级测试数据集)
  • ESP32 BLE通信提速秘籍:手把手教你设置MTU,让数据传输快人一步
  • 谷歌地图排名怎么做?本地商户搜索进店率翻倍的18个细节
  • 为什么企业做了多年数字化,还是停留在表面?——从“工具堆砌”到“Agent原生”的深度解构与实战破局
  • 如何高效实现InstantSearch路由管理:构建复杂搜索导航的完整指南
  • HarmonyOS 6.0 开发实战:ArkTS 新特性与 AI 智能体开发指南(2026 最新版)
  • Face3D.ai Pro实际作品集:不同肤色/年龄/光照下重建稳定性验证
  • 【人像识别】face_recognition库windows快速安装教程
  • 前端独立开发的救星:5分钟上手Apifox Mock,让你的Vue/React项目不再等后端接口
  • Java面试必备:final修饰类深度解析(附示例)
  • C语言(1)----C语言是什么?基本概念介绍
  • AI编程革命:Codex如何终结重复脚本开发
  • Symfony Doctrine集成:实体映射、关联关系和数据库操作完全指南