天天看点

C++ Primer 中文版 第十六章 模板与泛型编程

作者:明政面朝大海春暖花开

《C++ Primer 中文版(第 5 版)》是一本权威的C++编程教材,涵盖了广泛的主题,包括模板与泛型编程。下面是对一些与模板相关的内容的解释和代码示例:

  1. 函数模板:函数模板是一种通用的函数定义,可以用于不同的数据类型。通过使用模板参数,可以在编译时生成多个函数实例。例如:
template<typename T>
T max(T a, T b) {
    return (a > b) ? a : b;
}

int result1 = max(5, 10);  // 使用int类型的模板实例
double result2 = max(3.14, 2.71);  // 使用double类型的模板实例
           
  1. 类模板:类模板是用于创建通用类的模板定义。类模板可以包含成员函数、成员变量和类型成员。例如:
template<typename T>
class MyStack {
public:
    void push(T element);
    T pop();
private:
    std::vector<T> data;
};

MyStack<int> stack;  // 使用int类型的类模板实例
stack.push(10);
int element = stack.pop();
           
  1. 模板参数:模板参数是在模板定义中使用的占位符,用于指定在实例化时将被替换的类型或值。模板参数可以是类型参数、非类型参数或模板参数包。例如:
template<typename T, int N>
class Array {
    T data[N];
};

Array<int, 5> arr;  // 使用int类型和5作为模板参数实例化Array类模板
           
  1. 成员模板:成员模板是定义在类或结构体内部的模板函数或模板类。成员模板可以访问类的成员和模板参数。例如:
template<typename T>
class MyContainer {
public:
    template<typename U>
    void add(U element);
};

MyContainer<int> container;
container.add(10);  // 实例化add函数模板为void add<int>(int element);
           
  1. 控制实例化:C++编译器在使用模板时会根据需要实例化模板。可以使用显式实例化、显式特例化和内联等方式来控制模板的实例化行为。例如:
template<typename T>
void foo(T value);

template void foo<int>(int value);  // 显式实例化为int类型的foo函数模板
template<> void foo<double>(double value);  // 显式特例化为double类型的foo函数模板
           
  1. 效率与灵活性:模板编程可以提供高效的代码,因为编译器可以在编译时生成特定类型的代码。然而,模板也可能导致编译时间增加和代码膨胀。可以通过使用模板特例化和模板元编程等技术来提高效率和灵活性。

以上是《C++ Primer 中文版(第 5 版)》中与模板相关的一些内容的解释和代码示例。这本书还包含更多关于模板和泛型编程的详细讲解和示例代码,建议阅读该书以深入了解这些概念。

C++中,列表初始化是一种初始化对象的方式,可以在创建对象时使用花括号{}来传递参数。以下是对列表初始化相关概念的解释及举例:

  1. pair的列表初始化:

    pair是C++标准库中的一个模板类,用于存储一对值。可以使用列表初始化来创建pair对象,将两个值作为参数传递给pair的构造函数,然后将其打包成一个pair对象。

    例如:

  2. std::pair<int, std::string> myPair{ 42, "hello" };
  3. 无序容器:

    无序容器是C++标准库中提供的一种数据结构,它使用哈希函数来存储和访问元素,不保证元素的顺序。无序容器也支持列表初始化,可以通过花括号{}来初始化容器中的元素。

    例如:

  4. std::unordered_set<int> mySet{ 1, 2, 3, 4, 5 };
  5. 智能指针:

    智能指针是C++中一种用于管理动态分配对象的指针的类模板。智能指针可以自动处理资源的释放,避免内存泄漏等问题。智能指针也支持列表初始化,可以在创建智能指针时使用花括号{}来传递参数。

    例如:

  6. std::shared_ptr<int> mySharedPtr{ new int(42) };
  7. 动态分配对象的列表初始化:

    动态分配对象时,也可以使用列表初始化来初始化对象。

    例如:

  8. int* myDynamicInt = new int{ 42 };
  9. auto和动态分配:

    auto关键字可以用于自动推导变量的类型。当使用auto关键字和动态分配结合时,可以使用列表初始化来初始化动态分配的对象。

    例如:

  10. auto* myDynamicInt = new int{ 42 };
  11. unique_ptr类:

    unique_ptr是C++中的一种智能指针,它独占所指向的对象,保证了资源的唯一拥有权。unique_ptr也支持列表初始化。

    例如:

  12. std::unique_ptr<int> myUniquePtr{ new int(42) };
  13. weak_ptr类:

    weak_ptr是C++中的一种智能指针,它指向一个由shared_ptr管理的对象,但不会增加对象的引用计数。weak_ptr也支持列表初始化。

    例如:

  14. std::weak_ptr<int> myWeakPtr{ mySharedPtr };
  15. 范围for语句不能应用于动态分配数组:

    范围for语句用于遍历容器中的元素,但不支持动态分配的数组。因为动态分配的数组不是一个容器,无法提供begin和end函数供范围for语句使用。

    例如:

  16. int* myArray = new int[5]{ 1, 2, 3, 4, 5 }; for (auto element : myArray) { // 无法使用范围for语句遍历动态分配的数组 }
  17. 动态分配数组的列表初始化:

    动态分配数组时,也可以使用列表初始化来初始化数组中的元素。

    例如:

  18. int* myArray = new int[5]{ 1, 2, 3, 4, 5 };

下面是对你提到的C++相关内容的详细解释和示例:

  1. 容器的cbegin和cend函数:
  2. cbegin函数返回一个指向容器中第一个元素的常量迭代器。
  3. cend函数返回一个指向容器中最后一个元素之后位置的常量迭代器。
  4. 示例代码:
  5. #include <iostream> #include <vector> int main() { std::vector<int> numbers = {1, 2, 3, 4, 5}; // 使用cbegin和cend输出容器中的元素 for (auto it = numbers.cbegin(); it != numbers.cend(); ++it) { std::cout << *it << " "; } return 0; }
  6. 容器的列表初始化:

    C++11引入了列表初始化语法,可以用于初始化容器。

  7. 示例代码:
  8. #include <iostream> #include <vector> #include <list> int main() { // 列表初始化vector std::vector<int> numbers = {1, 2, 3, 4, 5}; // 列表初始化list std::list<std::string> names = {"Alice", "Bob", "Charlie"}; // 输出容器中的元素 for (const auto& num : numbers) { std::cout << num << " "; } std::cout << std::endl; for (const auto& name : names) { std::cout << name << " "; } std::cout << std::endl; return 0; }
  9. 容器的非成员函数swap:

    swap函数用于交换两个容器的内容。

  10. 示例代码:
  11. #include <iostream> #include <vector> int main() { std::vector<int> numbers1 = {1, 2, 3}; std::vector<int> numbers2 = {4, 5, 6}; // 交换两个容器的内容 numbers1.swap(numbers2); // 输出交换后的容器内容 for (const auto& num : numbers1) { std::cout << num << " "; } std::cout << std::endl; for (const auto& num : numbers2) { std::cout << num << " "; } std::cout << std::endl; return 0; }
  12. 容器insert成员的返回类型:

    insert函数用于在容器中插入元素,并返回一个指向插入的元素的迭代器。

  13. 示例代码:
  14. #include <iostream> #include <vector> int main() { std::vector<int> numbers = {1, 2, 3, 4, 5}; auto it = numbers.insert(numbers.begin() + 2, 6); // 输出插入后的容器内容 for (const auto& num : numbers) { std::cout << num << " "; } std::cout << std::endl; // 输出插入的元素值 std::cout << "Inserted element: " << *it << std::endl; return 0; }
  15. 容器的emplace成员的返回类型:

    emplace函数用于在容器中就地构造元素,并返回一个指向新构造的元素的迭代器。

  16. 示例代码:
  17. #include <iostream> #include <map> int main() { std::map<int, std::string> myMap; auto it = myMap.emplace(1, "Alice"); // 输出插入后的容器内容 for (const auto& pair : myMap) { std::cout << pair.first << ": " << pair.second << std::endl; } // 输出插入的元素值 std::cout << "Inserted element: " << it->first << ": " << it->second << std::endl; return 0; }
  18. shrink_to_fit:

    shrink_to_fit函数用于要求容器减小其内存占用,将容器的容量减小到与其大小相匹配。

  19. 示例代码:
  20. #include <iostream> #include <vector> int main() { std::vector<int> numbers = {1, 2, 3, 4, 5}; numbers.pop_back(); // 删除最后一个元素 numbers.shrink_to_fit(); // 减小容器的容量 std::cout << "Size: " << numbers.size() << std::endl; std::cout << "Capacity: " << numbers.capacity() << std::endl; return 0; }
  21. string的数值转换函数:

    std::stoi、std::stol、std::stoll等函数可以将字符串转换为数值类型。

  22. 示例代码:
  23. #include <iostream> #include <string> int main() { std::string str = "12345"; int num = std::stoi(str); std::cout << "Number: " << num << std::endl; return 0; }
  24. Lambda表达式:

    Lambda表达式是C++11引入的一种匿名函数的语法,可以用于创建临时的函数对象。

  25. 示例代码:
  26. #include <iostream> #include <vector> #include <algorithm> int main() { std::vector<int> numbers = {1, 2, 3, 4, 5}; // 使用Lambda表达式打印容器中的元素 std::for_each(numbers.begin(), numbers.end(), [](int num) { std::cout << num << " "; }); std::cout << std::endl; return 0; }
  27. Lambda表达式中的尾置返回类型:

    Lambda表达式可以使用尾置返回类型来指定返回值的类型。

  28. 示例代码:
  29. #include <iostream> int main() { auto lambda = [](int a, int b) -> int { return a + b; }; int result = lambda(2, 3); std::cout << "Result: " << result << std::endl; return 0; }
  30. 标准库bind函数:

    bind函数用于创建一个函数对象,可以将参数绑定到特定的值或者函数。

  31. 示例代码:
  32. #include <iostream> #include <functional> int add(int a, int b) { return a + b; } int main() { auto addFive = std::bind(add, std::placeholders::_1, 5); int result = addFive(10); std::cout << "Result: " << result << std::endl; return 0; }
  33. 关联容器的列表初始化:

    关联容器(如std::map和std::set)也可以使用列表初始化语法进行初始化。

  34. 示例代码:
  35. #include <iostream> #include <map> #include <set> int main() { // 列表初始化map std::map<int, std::string> myMap = {{1, "Alice"}, {2, "Bob"}, {3, "Charlie"}}; // 列表初始化set std::set<int> mySet = {1, 2, 3, 4, 5}; // 输出map中的键值对 for (const auto& pair : myMap) { std::cout << pair.first << ": " << pair.second << std::endl; } std::cout << std::endl; // 输出set中的元素 for (const auto& num : mySet) { std::cout << num << " "; } std::cout << std::endl; return 0; }

希望以上解释和示例能够对你有所帮助。如有任何进一步的问题,请随时提问。

继续阅读