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

C++进阶-【高级语法】

模板编程概述

模板编程是 C++ 语言的重要特性之一,允许程序员编写通用、可重用的代码。通过使用模板,可以创建适用于不同数据类型的函数和类,从而提高代码的灵活性和可维护性。本文将详细介绍模板编程的各个方面,包括函数模板、类模板、模板特化和模板元编程。

1.1 模板编程

1.1.1 函数模板

定义与使用

函数模板是一个为不同数据类型提供通用实现的机制。通过定义一个模板,编译器可以根据传入的参数类型生成相应的函数。

示例:

cpp

#include <iostream>
using namespace std;

// 定义函数模板
template <typename T>
T add(T a, T b) {
    return a + b;
}

int main() {
    cout << add(3, 4) << endl;        // 整数相加
    cout << add(2.5, 3.7) << endl;    // 浮点数相加
    cout << add(string("Hello, "), string("World!")) << endl; // 字符串相加
    return 0;
}
过载与特化

函数模板可以被重载,也可以被特化。重载是指在同一作用域中定义多个同名函数模板,特化则是为特定数据类型提供不同实现。

示例:

cpp

// 过载示例
template <typename T>
void print(T value) {
    cout << value << endl;
}

template <>
void print(const char* value) { // 特化示例
    cout << "C-style string: " << value << endl;
}

int main() {
    print(10);                     // 调用模板
    print(3.14);                  // 调用模板
    print("Hello");               // 调用特化
    return 0;
}

1.1.2 类模板

定义与使用

类模板允许定义一个类的通用版本,以便于处理不同的数据类型。

示例:

cpp

#include <iostream>
using namespace std;

// 定义类模板
template <typename T>
class Box {
private:
    T value;
public:
    Box(T val) : value(val) {}
    T getValue() { return value; }
};

int main() {
    Box<int> intBox(123);               // 创建整型 Box
    Box<double> doubleBox(456.78);      // 创建浮点型 Box
    cout << intBox.getValue() << endl;  // 输出: 123
    cout << doubleBox.getValue() << endl; // 输出: 456.78
    return 0;
}
模板参数的非类型参数

类模板可以接受非类型参数,这允许在模板中使用常量值。

示例:

cpp

template <typename T, int size>
class Array {
private:
    T arr[size]; // 使用非类型模板参数
public:
    int getSize() const { return size; }
};

int main() {
    Array<int, 10> intArray; // 创建一个大小为 10 的整型数组
    cout << intArray.getSize() << endl; // 输出: 10
    return 0;
}

1.1.3 模板特化

全特化与部分特化

模板特化允许为特定类型或特定参数的组合提供不同的实现。全特化是针对特定类型的完全特化,而部分特化则是对模板参数的一部分进行特化。

全特化示例:

cpp

template <>
class Box<int> { // 针对 int 类型的全特化
private:
    int value;
public:
    Box(int val) : value(val) {}
    void display() { cout << "Integer: " << value << endl; }
};

部分特化示例:

cpp

template <typename T>
class Box<T*> { // 针对指针类型的部分特化
private:
    T* value;
public:
    Box(T* val) : value(val) {}
    void display() { cout << "Pointer to value: " << *value << endl; }
};
选择特化的条件

选择特化的条件依赖于模板参数的类型和数量。通常,在需要为特定类型提供优化或不同实现时会选择特化。

1.1.4 模板元编程

模板元编程是一种使用模板进行编译时计算的技术。它允许在编译时执行计算,从而产生高效的代码。

编写编译时计算

示例: 计算阶乘

cpp

template <int N>
struct Factorial {
    static const int value = N * Factorial<N - 1>::value;
};

// 基础情况
template <>
struct Factorial<0> {
    static const int value = 1;
};

int main() {
    cout << Factorial<5>::value << endl; // 输出: 120
    return 0;
}
常见应用示例
  • 类型选择:根据条件选择类型。

cpp

template <bool condition, typename T1, typename T2>
struct Conditional {
    using type = T1; // condition 为 true,选择 T1
};

template <typename T1, typename T2>
struct Conditional<false, T1, T2> {
    using type = T2; // condition 为 false,选择 T2
};

// 使用示例
using SelectedType = Conditional<true, int, double>::type; // 选择 int
  • 编写静态断言:在编译时检查条件。

cpp

template <typename T>
void assertIsIntegral() {
    static_assert(std::is_integral<T>::value, "Template parameter must be an integral type.");
}

int main() {
    assertIsIntegral<int>(); // 正确
    // assertIsIntegral<double>(); // 错误,编译时断言失败
}

模板编程结论

模板编程是 C++ 中一个强大的特性,能够提高代码的灵活性和重用性。

异常处理概述

异常处理是 C++ 中用于管理运行时错误的重要机制。通过合理使用异常处理,可以提高程序的健壮性和可维护性。本文将详细介绍异常的捕获与抛出、自定义异常类,和 RAII(资源获取即初始化)原则。

1.2 异常处理

1.2.1 异常的捕获与抛出

try-catch 语句的使用

try-catch 语句用于捕获和处理异常。try 块中放置可能抛出异常的代码,而 catch 块中定义了如何处理这些异常。当 try 块中的代码抛出异常时,控制权会转移到对应的 catch 块。

示例:

cpp

#include <iostream>
#include <stdexcept> // std::runtime_error
using namespace std;

void mightGoWrong() {
    throw runtime_error("Something went wrong!"); // 抛出异常
}

int main() {
    try {
        mightGoWrong(); // 调用可能抛出异常的函数
    } catch (const runtime_error &e) {
        cout << "Caught an exception: " << e.what() << endl; // 捕获并处理异常
    }
    return 0;
}

说明:

  • runtime_error 是标准库提供的异常类,用于报告运行时错误。
  • what() 方法返回异常的描述信息。
throw 的语法与最佳实践

使用 throw 关键字抛出异常,通常与 try 块结合使用。最佳实践包括:

  • 只在错误情况下抛出:确保只在真实错误条件下抛出异常。
  • 提供清晰的错误信息:抛出异常时,提供有意义的错误消息,便于调试。
  • 使用标准异常类或自定义异常类:尽量使用标准库中的异常类或自定义类型,以便进行类型识别和处理。

示例:

cpp

void riskyFunction() {
    // 检查条件,抛出异常
    if (/* error condition */) {
        throw runtime_error("An error occurred in riskyFunction");
    }
}
异常安全性原则

异常安全性原则确保在抛出异常时,程序的状态保持一致。常见的异常安全性保证包括:

  • 基本保证:如果异常发生,程序的状态仍然有效,但可能没有完成预期的操作。
  • 强保证:如果异常发生,程序的状态将恢复到异常之前的状态,即所有操作都要么完全成功,要么完全不执行。
  • 不抛出保证:操作不会抛出异常,例如某些简单的 getter 函数。

示例:

cpp

class Resource {
public:
    Resource() { /* 资源初始化 */ }
    void doSomething() {
        if (/* error condition */) {
            throw runtime_error("Operation failed");
        }
    }
};

void manageResource() {
    Resource res;
    try {
        res.doSomething();
    } catch (const runtime_error &e) {
        // 处理异常
        cout << "Caught: " << e.what() << endl;
    }
}

1.2.2 自定义异常类

继承 std::exception

自定义异常类通常继承自 std::exception,并重写 what() 方法以提供错误信息。这种方式不仅可以提供特定的错误信息,还能保持与标准异常机制的兼容性。

示例:

cpp

#include <exception>
#include <string>

class MyException : public std::exception {
private:
    std::string message; // 存储错误信息
public:
    MyException(const std::string &msg) : message(msg) {}
    const char* what() const noexcept override { // 重写 what 方法
        return message.c_str(); // 返回错误信息
    }
};
添加上下文信息(如错误码、错误消息)

自定义异常类可以添加更多的上下文信息,例如错误码或详细的错误消息,帮助开发者更好地定位问题。

示例:

cpp

class FileReadException : public std::exception {
private:
    std::string message; // 错误消息
    int errorCode; // 错误码
public:
    FileReadException(const std::string &msg, int code) : message(msg), errorCode(code) {}
    const char* what() const noexcept override {
        return message.c_str(); // 返回错误消息
    }
    int getErrorCode() const { return errorCode; } // 获取错误码
};
示例:实现自定义文件读取异常

以下示例展示了如何实现一个自定义异常类,用于处理文件读取错误。

cpp

#include <iostream>
#include <fstream>
#include <exception>
#include <string>

class FileReadException : public std::exception {
private:
    std::string message; // 错误消息
public:
    FileReadException(const std::string &msg) : message(msg) {}
    const char* what() const noexcept override {
        return message.c_str(); // 返回错误消息
    }
};

void readFile(const std::string &filename) {
    std::ifstream file(filename);
    if (!file) {
        throw FileReadException("Failed to open file: " + filename); // 抛出自定义异常
    }

    std::string line;
    while (std::getline(file, line)) {
        std::cout << line << std::endl; // 读取并处理文件内容
    }
}

int main() {
    try {
        readFile("non_existent_file.txt"); // 尝试读取不存在的文件
    } catch (const FileReadException &e) {
        std::cout << "Caught an exception: " << e.what() << std::endl; // 捕获并处理自定义异常
    }
    return 0;
}

1.2.3 RAII(资源获取即初始化)

RAII 是一种资源管理模式,确保资源在对象的生命周期内被正确管理。当对象被销毁时,资源会自动释放。这种策略极大地减少了内存泄漏和资源未释放的问题。

资源管理原则及其重要性

RAII 的核心原则是将资源的生命周期绑定到对象的生命周期。这意味着当对象超出作用域时,它所管理的资源也会被自动释放,从而避免内存泄漏和资源泄露的问题。

示例:

cpp

#include <iostream>

class Resource {
public:
    Resource() { std::cout << "Resource acquired" << std::endl; }
    ~Resource() { std::cout << "Resource released" << std::endl; }
};

void useResource() {
    Resource res; // 资源在此处被获取
    // 使用资源...
} // 当 res 超出作用域时,资源会自动释放

int main() {
    useResource(); // 调用使用资源的函数
    return 0;
}
实现智能指针的基本原理

智能指针是 RAII 的一种实现,用于管理动态分配的内存。常见的智能指针包括 std::unique_ptrstd::shared_ptr。它们自动管理内存的分配和释放,防止内存泄漏。

示例:

cpp

#include <iostream>
#include <memory> // std::unique_ptr

class Resource {
public:
    Resource() { std::cout << "Resource acquired" << std::endl; }
    ~Resource() { std::cout << "Resource released" << std::endl; }
};

void manageResource() {
    std::unique_ptr<Resource> res(new Resource()); // RAII 管理资源
    // 使用资源...
} // 当 res 超出作用域时,资源会自动释放

int main() {
    manageResource(); // 调用管理资源的函数
    return 0;
}
结合 RAII 管理文件句柄与内存

RAII 可以用于管理文件句柄和动态内存,确保它们在不再需要时被正确释放。可以使用 std::ifstream 来管理文件句柄,并结合智能指针来管理动态分配的内存。

示例:

cpp

#include <iostream>
#include <fstream>
#include <memory>

void readFile(const std::string &filename) {
    std::ifstream file(filename); // RAII 管理文件句柄
    if (!file.is_open()) {
        throw std::runtime_error("Failed to open file"); // 抛出异常
    }

    std::string line;
    while (std::getline(file, line)) {
        std::cout << line << std::endl; // 读取并处理文件内容
    }
}

int main() {
    try {
        readFile("example.txt"); // 尝试读取文件
    } catch (const std::exception &e) {
        std::cerr << "Error: " << e.what() << std::endl; // 捕获并处理异常
    }
    return 0;
}

异常处理结论

异常处理是 C++ 中重要的编程概念。通过理解异常的捕获与抛出、自定义异常类和 RAII 原则,开发者能够编写出更健壮和可维护的代码。

Lambda 表达式概述

Lambda 表达式是 C++11 引入的一项强大特性,允许程序员定义匿名函数,并在需要时即时使用。通过 Lambda 表达式,可以以更简洁的方式编写代码,提高代码的可读性和可维护性。本文将详细介绍 Lambda 表达式的基本语法、在标准库中的应用以及状态与持久性。

1.3 Lambda 表达式

1.3.1 基本语法

Lambda 表达式的基本语法如下:

cpp

[capture](parameters) -> return_type {
    // 函数体
}
捕获列表与参数列表
  • 捕获列表:用于指定在 Lambda 表达式中使用的外部变量。可以按值捕获或按引用捕获。
  • 参数列表:与普通函数的参数列表相同,用于定义 Lambda 表达式的输入参数。

示例:

cpp

#include <iostream>
#include <vector>
using namespace std;

int main() {
    int x = 10;
    vector<int> vec = {1, 2, 3, 4, 5};

    // 按值捕获 x
    auto lambda_by_value = [x](int y) {
        return x + y; // 使用捕获的 x
    };

    // 按引用捕获 x
    auto lambda_by_reference = [&x](int y) {
        return x + y; // 使用捕获的 x
    };

    cout << "By Value: " << lambda_by_value(5) << endl;       // 输出: 15
    cout << "By Reference: " << lambda_by_reference(5) << endl; // 输出: 15

    return 0;
}
返回类型推断

C++11 引入了自动推断 Lambda 表达式的返回类型。如果未显式指定返回类型,编译器会根据函数体自动推断返回类型。

示例:

cpp

auto lambda = [](int a, int b) {
    return a + b; // 返回类型为 int
};

cout << "Sum: " << lambda(3, 4) << endl; // 输出: Sum: 7

如果需要更复杂的返回类型或使用 decltype 进行推断,可以显式指定返回类型:

示例:

cpp

auto lambda_with_type = [](int a, int b) -> double {
    return static_cast<double>(a) / b; // 显式指定返回类型为 double
};

cout << "Division: " << lambda_with_type(5, 2) << endl; // 输出: Division: 2.5

1.3.2 在标准库中的应用

Lambda 表达式在 C++ 标准库中得到了广泛应用,特别是在 STL 算法中。以下是一些常见的应用示例。

std::for_each

std::for_each 可以与 Lambda 表达式结合使用,以遍历容器中的元素并对每个元素执行操作。

示例:

cpp

#include <iostream>
#include <vector>
#include <algorithm> // std::for_each
using namespace std;

int main() {
    vector<int> vec = {1, 2, 3, 4, 5};

    // 使用 Lambda 表达式打印每个元素
    std::for_each(vec.begin(), vec.end(), [](int value) {
        cout << value << " "; // 输出: 1 2 3 4 5
    });
    cout << endl;

    return 0;
}
std::sort

std::sort 可以使用 Lambda 表达式定义自定义排序规则。

示例:

cpp

#include <iostream>
#include <vector>
#include <algorithm> // std::sort
using namespace std;

int main() {
    vector<int> vec = {5, 3, 1, 4, 2};

    // 使用 Lambda 表达式按升序排序
    std::sort(vec.begin(), vec.end(), [](int a, int b) {
        return a < b; // 升序排序
    });

    cout << "Sorted: ";
    for (int value : vec) {
        cout << value << " "; // 输出: 1 2 3 4 5
    }
    cout << endl;

    return 0;
}

1.3.3 状态与持久性

Lambda 表达式可以捕获外部状态,使其在调用时保持该状态。捕获的变量的生命周期必须在 Lambda 表达式的使用期间内有效。

捕获外部状态的示例

cpp

#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;

int main() {
    int threshold = 3; // 外部状态

    vector<int> vec = {1, 2, 3, 4, 5};

    // 使用 Lambda 表达式捕获外部状态
    auto count_greater = [threshold](const vector<int>& numbers) {
        return std::count_if(numbers.begin(), numbers.end(), [threshold](int num) {
            return num > threshold; // 使用捕获的 threshold
        });
    };

    cout << "Count greater than " << threshold << ": " << count_greater(vec) << endl; // 输出: 2

    return 0;
}
状态的持久性

如果 Lambda 表达式捕获了状态(例如,通过按引用捕获),需要确保该状态在 Lambda 的生命周期内有效。如果状态被销毁,Lambda 将访问无效内存。

示例:

cpp

#include <iostream>
#include <functional> // std::bind
using namespace std;

int main() {
    int x = 10;

    auto lambda = [&x]() {
        return x * 2; // 捕获 x
    };

    cout << "Before changing x: " << lambda() << endl; // 输出: 20

    x = 20; // 修改 x
    cout << "After changing x: " << lambda() << endl; // 输出: 40

    return 0;
}

Lambda 表达式结论

Lambda 表达式是 C++ 中一个强大的特性,能够以简洁的方式定义匿名函数。


http://www.kler.cn/a/457333.html

相关文章:

  • HTML——28.音频的引入
  • docker使用国内镜像
  • 《计算机网络A》单选题-复习题库
  • 网关的主要作用
  • 56.在 Vue 3 中使用 OpenLayers 通过 moveend 事件获取地图左上和右下的坐标信息
  • C++面向对象编程:纯虚函数、抽象类、虚析构、纯虚析构
  • 使用GitHub Pages部署静态网站:简易指南
  • 《Vue进阶教程》第二十四课:优化
  • c++ 里 常量转换 const_cast < T > ,要给模板参数 T 传递什么类型呢?
  • iClient3D for Cesium 加载shp数据并拉伸为白模
  • Node.js 工具:在 Windows 11 中配置 Node.js 的详细步骤
  • 影刀进阶应用 | 知乎发布想法
  • EMQX5.X版本性能配置调优参数
  • NSSCTF-web刷题
  • 爬虫入门二 beautifulsoup
  • 一个通用的居于 OAuth2的API集成方案
  • 解密MQTT协议:从QOS到消息传递的全方位解析
  • Element分阶段逐步升级
  • (计算机毕设)基于SpringBoot+Vue的在线音乐平台
  • K8s Flannel vs Calico:基于 L2 与 L3 的 CNI 之战(一)
  • DINO: 基于双向知识蒸馏的视觉智能自学习方法
  • 设计模式之状态模式:自动售货机的喜怒哀乐
  • 通过 python 获取金融数据-akshare
  • ESP32_H2(IDF)学习系列-ADC模数转换(单次转换)
  • 将 ASP.NET Core 应用程序的日志保存到 D 盘的文件中 (如 Serilog)
  • 【2025最新计算机毕业设计】基于SpringBoot+Vue体育资讯系统(可定制,项目包括源码、文档、远程调试、免费答疑至毕业】