C++基础:Stanford CS106L学习笔记 14 类型安全 & `std::optional`

目录

14.1 类型安全(Type Safety)

类型安全性:

指一种编程语言防止发生类型错误(typing errors)的能力范围或程度。

指一种编程语言对程序行为进行保障的程度。

例1:容器为空
cpp 复制代码
void removeOddsFromEnd(vector<int>& vec){
    while(vec.back() % 2 == 1){
        vec.pop_back();
    }
}

类型安全吗?

如果vec是空的怎么办?

未定义行为(Undefined Behavior):函数可能会崩溃,可能会返回无用的垃圾数据,也可能意外返回某个实际有效的值。

解决方案1:

cpp 复制代码
void removeOddsFromEnd(vector<int>& vec){
    while(!vec.empty() && vec.back() % 2 == 1){
        vec.pop_back();
    }
}

核心思想:确保向量(vec)非空这一前置条件,是程序员的职责;否则,程序将出现未定义行为!

例2:指针指向空
cpp 复制代码
valueType& vector<valueType>::back(){
    return *(begin() + size() - 1);
}

不验证指针是否指向实际内存就对其进行解引用,这种行为属于未定义行为!

cpp 复制代码
valueType& vector<valueType>::back(){
    if(empty()) throw std::out_of_range;
    return *(begin() + size() - 1);
}
std::pair解决

类型安全指函数签名对函数行为进行保障的程度。

cpp 复制代码
valueType& vector<valueType>::back(){
    return *(begin() + size() - 1);
}

vector 容器的 back() 函数存在 "签名承诺与实际情况不匹配" 的问题。从函数签名来看,back() 的返回类型被定义为 valueType&(即 valueType 类型的引用),这相当于向开发者 "承诺":调用该函数时,一定会返回一个有效的 valueType 类型数据(对应容器的最后一个元素)。但实际场景中,若容器为空(vec.empty() == true),"最后一个元素" 根本不存在。

cpp 复制代码
std::pair<bool, valueType&> vector<valueType>::back(){
    if(empty()){
        return {false, valueType()};        // valueType()为默认构造函数
    }
    return {true, *(begin() + size() - 1)};
}

现在,back () 函数会明确告知(开发者):容器中可能存在也可能不存在最后一个元素。

但调用构造函数是资源昂贵的。

14.2 std::optional(C++17)

std::optional 是一个模板类,它要么包含一个 T 类型的值,要么不包含任何值(用 nullopt 表示)。

nullptr可转换为任意指针类型值的对象

nullopt可转换为任意 optional 类型值的对象

std::optional 类型包含以下方法:

  • .value() 方法:

返回其包含的值;若 std::optional 未包含值,则抛出 bad_optional_access 异常。

  • .value_or(valueType val) 方法:

返回其包含的值;若 std::optional 未包含值,则返回默认值(即参数 val)。

  • .has_value() 方法:

std::optional 包含值,则返回 true;若未包含值,则返回 false

补充说明(技术术语适配):

  • bad_optional_access:是 C++ 标准库中专门为 std::optional 设计的异常类型,中文通常直接保留原名(或译为"可选值访问异常"),用于明确提示"访问了无值的 std::optional 对象"这一错误场景。
  • valueType val:此处 valueTypestd::optional 模板类的泛型参数类型(如 std::optional<int> 中的 int),val 是开发者传入的"默认值",其类型需与 valueType 一致。
cpp 复制代码
std::optional<valueType> vector<valueType>::back(){
    if(empty()){
        return {};
    }
    return *(begin() + size() - 1);
}

实例:

cpp 复制代码
#include <iostream>
#include <optional>

std::optional<int> divide(int numerator, int denominator) {
  if (denominator != 0) {
    return numerator / denominator;
  } else {
    return std::nullopt;
  }
}

int main() {
  int a = 10;
  int b = 2;

  std::optional<int> result = divide(a, b);

  if (result) {
    std::cout << "Result: " << result.value() << std::endl;
  } else {
    std::cout << "Division by zero occurred." << std::endl;
  }

  result = divide(10, 0);

  if (result) {
    std::cout << "Result: " << result.value() << std::endl;
  } else {
    std::cout << "Division by zero occurred." << std::endl;
  }

  return 0;
}

这段代码展示了 C++ 中std::optional的用法,主要用于处理可能失败的操作(此处为除法运算)。

代码解析如下:

  1. 包含了必要的头文件:iostream用于输入输出,optional提供std::optional类型支持。
  2. 定义了divide函数,返回类型为std::optional<int>
  • 当分母不为 0 时,返回除法结果(包装在std::optional中)
  • 当分母为 0 时,返回std::nullopt表示操作失败

main函数中:

  • 首先计算 10 除以 2,结果有效,输出计算结果
  • 然后尝试计算 10 除以 0,操作失败,输出错误信息

std::optional的优势在于:

  • 明确表示一个操作可能成功或失败
  • 避免使用特殊值(如 - 1)来表示错误,使代码更清晰
  • 通过if (result)可以便捷地检查操作是否成功
  • 使用result.value()安全地获取结果(前提是已检查操作成功)

在 C++ 的 std::optional 语境中,Monadic Operations(单子操作) 是一组用于安全、链式处理"可能存在或不存在的值"(即 std::optional 包裹的值)的成员函数,核心目的是避免显式条件判断,如 if,同时确保代码简洁且无空指针风险。其本质是通过"链式调用"自动处理"值存在"和"值不存在"两种分支,仅在值有效时执行后续逻辑,无效时直接跳过并传递"无值"状态。

Monadic操作(C++23)

文档
std::optional 提供的核心 Monadic 操作(C++23)有 3 种,各自分工明确:

操作函数 核心作用 输入函数要求 行为逻辑
transform 转换值 ​:若有值,对值执行"无副作用的转换",结果仍包裹在optional 输入:T(原始值类型);输出:U(转换后类型) 有值:调用函数转换值,返回optional;无值:直接返回nullopt
and_then 链式依赖 ​:若有值,执行"返回新optional的逻辑"(如后续查询) 输入:T;输出:optional 有值:调用函数获取新optional,返回该结果;无值:直接返回nullopt
or_else 兜底处理 ​:若无值,执行"生成兜底optional"的逻辑 输入:无;输出:optional 有值:直接返回原始optional;无值:调用函数获取兜底optional
关键特点(为何用 Monadic 操作?)
  1. 消除显式条件 :无需写 if (opt.has_value()),链式调用自动处理分支,代码更简洁。例:原本需要判断 course 是否存在再拼接字符串,用 transform+or_else 可直接链式完成。
  2. 安全无空访问:仅当值存在时才执行转换/依赖逻辑,从根源避免"空指针解引用"风险。
  3. 类型严格匹配:每个操作的输入输出类型由模板强制约束,编译期即可发现类型错误。
实际场景示例(对应题目需求)

题目要求:用 2 个 Monadic 操作实现"有课程则输出详情,无则输出提示",核心逻辑如下:

cpp 复制代码
std::optional<Course> course = db.find_course(argv[1]);
// 1. transform:有课程则拼接详情字符串(值转换),无则留空
// 2. or_else:无课程则返回兜底提示(兜底处理)
std::string output = course
    .transform([](const Course& c) -> std::string {
        return "Found course: " + c.title + "," + std::to_string(c.number_of_units) + "," + c.quarter;
    })
    .or_else([]() -> std::optional<std::string> {
        return "Course not found."; // 无值时返回兜底字符串的 optional
    })
    .value(); // 此时必存在值,直接获取

std::cout << output << std::endl;
  • course 有值:transform 生成"课程详情字符串"的 optionalor_else 直接返回该 optional,最终 value() 获取字符串。
  • course 无值:transform 返回 nulloptor_else 触发兜底逻辑,返回"提示字符串"的 optional,最终 value() 获取提示。
相关推荐
小智RE0-走在路上2 小时前
Python学习笔记(9) --文件操作
笔记·python·学习
L_09072 小时前
【C++】高阶数据结构 -- 二叉搜索树(BST)
数据结构·c++
WongLeer2 小时前
Redis 学习笔记
redis·笔记·学习·redis缓存·redis发布订阅
大筒木老辈子2 小时前
C++笔记---并发支持库(future)
java·c++·笔记
PyGata2 小时前
CMake学习笔记(二):CMake拷贝文件夹
c++·笔记·学习
Lucky小小吴2 小时前
JAVA漫谈反序列化篇——笔记
java·开发语言·笔记
摇滚侠2 小时前
Redis 零基础到进阶,Redis 事务,Redis 管道,Redis 发布订阅,笔记47-54
数据库·redis·笔记
仰泳的熊猫2 小时前
1150 Travelling Salesman Problem
数据结构·c++·算法·pat考试
小蜗笔记2 小时前
ABM模型库的笔记
笔记