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_cast vs dynamic_cast 对比

特性 static_cast dynamic_cast
是否需要虚函数 ❌ 不需要 ✅ 必须有(多态类型)
检查时机 编译时(无运行时检查) 运行时(RTTI 检查)
向下转型安全性 ❌ 不安全(程序员负责) ✅ 安全(自动验证)
失败行为 返回无效指针(未定义行为) 指针:返回 nullptr;引用:抛出 std::bad_cast
性能开销 无(和 C 风格转换一样快) 有(需查虚表、类型信息)
能否用于非多态类型 ✅ 可以 ❌ 编译错误
cpp 复制代码
#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_ptr shared_ptr weak_ptr
所有权 独占 共享 无(仅观察)
可复制
可移动
引用计数 ❌(依赖 shared_ptr)
性能开销 几乎无 有(原子操作) 有(同 shared_ptr)
解决循环引用 不适用
创建方式 make_unique make_shared shared_ptr 构造

C++中的深拷贝和浅拷贝

C++中的深拷贝和浅拷贝-CSDN博客

explicit 禁止编译器进行隐式类型转换

explicit 禁止编译器进行隐式类型转换-CSDN博客

explicit 是 C++ 中一个极其重要且常用的关键字 ,用于禁止编译器进行隐式类型转换(implicit conversion),从而避免意外的、难以调试的错误。

场景 行为
explicit 构造函数 禁止从参数类型到类类型的隐式转换
explicit 转换操作符 禁止从类类型到目标类型的隐式转换
共同目标 提高类型安全性,防止意外转换

auto类型和decltype类型

auto:根据初始化表达式推导变量类型

  • auto忽略顶层 const、引用、volatile,只保留底层类型。
  • 如果想保留引用或 const,需要显式加上:const auto&
cpp 复制代码
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:根据表达式本身推导其精确类型

cpp 复制代码
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 变量
cpp 复制代码
constexpr int N = 100;          // 编译期常量
constexpr double PI = 3.14159;  // 必须用常量表达式初始化

int arr[N];                     // ✅ 合法:N 是编译期常量
  1. constexpr 函数
cpp 复制代码
// 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);        // 运行期计算
  1. constexpr 构造函数(用于自定义类型)
cpp 复制代码
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 类)

cpp 复制代码
class NonCopyable {
public:
    NonCopyable() = default;

    // 显式删除拷贝构造和拷贝赋值
    NonCopyable(const NonCopyable&) = delete;
    NonCopyable& operator=(const NonCopyable&) = delete;
};

NonCopyable a;
// NonCopyable b = a;  // ❌ 编译错误!
// a = b;              // ❌ 编译错误!

💡 这比将拷贝函数设为 private 更清晰、更早报错(编译期 vs 链接期)。

示例 2:禁止特定类型调用(防隐式转换)

cpp 复制代码
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)

cpp 复制代码
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) → 泛型编程的核心

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

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

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

std::optional的使用

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

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

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

cpp 复制代码
#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";
    }
}
  1. 替代指针或引用表示"可为空"

避免裸指针带来的内存管理问题和歧义。

cpp 复制代码
std::optional<std::string> getConfigValue(const std::string& key);
// 比返回 const char* 或 string* 更安全、更语义清晰
  1. 作为类成员表示"尚未设置"的状态
cpp 复制代码
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...) 就地构造内部对象
cpp 复制代码
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>
表示"无值" nullptr nullopt
所有权 无(裸指针) 有(值语义,管理内部对象生命周期)
内存位置 堆/栈任意 内部存储(通常在栈上)
安全性 易悬空、误用 更安全(编译器帮助检查)

优先用 optional 表示"可选值",用智能指针表示"可选所有权"

可变参数函数

在 C++ 中,可变参数函数(variadic function) 指的是可以接受任意数量、任意类型参数的函数。C++ 提供了两种主要方式实现:

1、C 风格可变参数(不推荐,仅用于兼容 C)

使用 <cstdarg> 头文件中的宏:va_list, va_start, va_arg, va_end

❌ 缺点:

  • 无类型安全
  • 不能处理引用、类对象(可能出错)
  • 容易崩溃

示例(不推荐):

cpp 复制代码
#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),把参数一个一个"拆开"
  • 必须有终止条件(通常是无参重载)

🔑 核心语法:

cpp 复制代码
template<typename... Args>
void func(Args... args);  // Args... 叫"参数包(parameter pack)"

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

cpp 复制代码
#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 起,更简洁)

cpp 复制代码
#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

cpp 复制代码
#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)

它的核心作用是:

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

cpp 复制代码
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

问题:普通引用会"丢失"右值信息

cpp 复制代码
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

cpp 复制代码
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 部分
cpp 复制代码
#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:使用指针或引用

cpp 复制代码
void process(const Base& obj) {  // 用 const 引用
    obj.say(); // 多态生效!
}

int main() {
    Derived d;
    process(d); // ✅ 输出 "Derived: 1, 2"
}

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

  • 将基类的拷贝构造函数设为 delete(C++11 起)
cpp 复制代码
class Base {
public:
    Base() = default;
    Base(const Base&) = delete;            // 禁止拷贝
    Base& operator=(const Base&) = delete; // 禁止赋值
    virtual ~Base() = default;
    virtual void say() { /*...*/ }
};

这样一旦写 Base b = derived;编译直接报错

方法 3:容器中存储智能指针

cpp 复制代码
// 错误:会切片
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 开始,允许只写一个参数:

cpp 复制代码
static_assert(sizeof(int) >= 4); // 合法(C++17+)
  1. 验证类型属性(常用于模板)

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

cpp 复制代码
template<typename T>
void process(T value) {
    static_assert(std::is_integral_v<T>, "T must be an integral type!");
    // ...
}

如果用户调用 process(3.14),编译器会报错:

cpp 复制代码
error: static assertion failed: T must be an integral type!
  1. 检查平台或编译器假设

例如,确保指针大小符合预期:

cpp 复制代码
static_assert(sizeof(void*) == 8, "This code requires 64-bit pointers!");

如果在 32 位系统上编译,直接失败。

  1. 强制接口契约(设计约束)

比如,确保结构体没有填充(用于网络协议或硬件寄存器映射):

cpp 复制代码
struct Packet {
    uint32_t id;
    uint16_t size;
};
static_assert(sizeof(Packet) == 6, "Packet must be exactly 6 bytes (no padding)!");
  1. 替代 #error(更灵活)

比预处理器指令更强大,因为可以使用 C++ 类型系统和 constexpr

cpp 复制代码
#if defined(_WIN32)
    static_assert(false, "Windows is not supported!"); // ❌ 错误!见下方说明
#endif

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

正确做法(依赖模板):

cpp 复制代码
template<typename T>
void unsupported_platform() {
    static_assert(sizeof(T) == 0, "Platform not supported!");
}

static_assert 是 C++ 中实现"编译期防御性编程"的利器。它把错误暴露在最早阶段(编译时),提升代码健壮性、可维护性和文档性。

相关推荐
fengfuyao9852 小时前
海浪PM谱及波形的Matlab仿真实现
开发语言·matlab
xiaoye-duck2 小时前
C++ string 底层原理深度解析 + 模拟实现(下)——面试 / 开发都适用
开发语言·c++·stl
Azure_withyou2 小时前
Visual Studio中try catch()还未执行,throw后便报错
c++·visual studio
琉染云月2 小时前
【C++入门练习软件推荐】Visual Studio下载与安装(以Visual Studio2026为例)
c++·visual studio
Hx_Ma163 小时前
SpringMVC框架提供的转发和重定向
java·开发语言·servlet
期待のcode3 小时前
原子操作类LongAdder
java·开发语言
L_09074 小时前
【C++】高阶数据结构 -- 红黑树
数据结构·c++
lly2024064 小时前
C 语言中的结构体
开发语言
JAVA+C语言5 小时前
如何优化 Java 多主机通信的性能?
java·开发语言·php