C++泛型相关知识
参考南软2024秋C++高级程序设计课件generic1, generic2
C++程序的组织
头文件.h
头文件中一般放编译时常量const
,各种声明,别名定义typedef
,宏,内联函数,预处理等代码,并对需要向外扩展作用域的成员加上extern
关键字
对应的,源文件中就要负责实现只在对应头文件中声明而没有定义的内容 ,比如函数定义、类成员定义等
作用域
分为:
程序级:比如#include
的内容
文件级:比如全局变量、函数等
函数级:比如函数的最外层局部变量
块级:比如代码块的局部变量(for
, while
等)
namespace
两种使用方式:
声明式declaration
直接使用directive
设有名空间L
:
namespace L { int k; void f (int ) ; }
声明式
using L::k;using L::f;k = 1 ; f (k);
直接使用
using namespace L;k = 1 ; f (k);
在约束作用域方面,用namespace
代替static
细节
namespace可以用来给一个名空间起别名
namespace alias_L = L;using namespace alias_L;
namespace的作用域是影响全局的
namespace是开放的,即可以多次定义同一个命名空间,内容将进行合并
namespace L { void f2 () ; int m; } using L::k;using L::m;using L::f2;
可以嵌套
namespace L1 { int a; namespace L2 { int b; void f () ; } }
支持对其中的函数重载
namespace A { void f (int ) ; void f (char ) ; void f (double ) ; } void f () ;int main () { using A::f; f ('1' ); }
最常用的还是using namespace std;
编译预处理
编译预处理既便捷又危险,它与C++的作用域、类型、接口等概念格格不入,潜伏于环境中,穿透作用域
但其应用方式的确丰富,很难为其找到具有更好的结构且高效的替代品
#include
#define
宏定义是非常有用的
#define CONCAT(x, y) x##y #define TO_STRING(x) #x cout << CONCAT (abc, def); cout << TO_STRING (1 ); cout << TO_STRING (xxx);
#ifdef
#pragma
#pragma Optimize ("Ofast" )
宏
更优雅的宏定义示例,可以简化重复但不好抽象的代码块:
#define ARRAY_SIZE(arr) (sizeof(arr) / sizeof(arr[0])) #define FOR_EACH(arr, fp) for (int i = 0; i < ARRAY_SIZE(arr); i++) {\ (fp)(arr[i]); \ } int main () { int arr[12 ] = {1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 , 9 }; FOR_EACH (arr, [](const auto a) { cout << a << endl; }); }
泛型编程
C如何实现泛型编程
#include <stdio.h> #include <stdlib.h> #define DEFINE_STACK_TYPE(TYPE) \ typedef struct { \ TYPE *data; \ size_t capacity; \ size_t size; \ } Stack_##TYPE; \ \ void Stack_##TYPE##_init(Stack_##TYPE *stack) { \ stack->capacity = 4; \ stack->size = 0; \ stack->data = (TYPE *)malloc(stack->capacity * sizeof(TYPE)); \ } \ \ void Stack_##TYPE##_push(Stack_##TYPE *stack, TYPE value) { \ if (stack->size == stack->capacity) { \ stack->capacity *= 2; \ stack->data = (TYPE *)realloc(stack->data, stack->capacity * sizeof(TYPE)); \ } \ stack->data[stack->size++] = value; \ } \ \ TYPE Stack_##TYPE##_pop(Stack_##TYPE *stack) { \ if (stack->size == 0) { \ fprintf(stderr, "Stack underflow!\n" ); \ exit(EXIT_FAILURE); \ } \ return stack->data[--stack->size]; \ } \ \ TYPE Stack_##TYPE##_top(Stack_##TYPE *stack) { \ if (stack->size == 0) { \ fprintf(stderr, "Stack is empty!\n" ); \ exit(EXIT_FAILURE); \ } \ return stack->data[stack->size - 1]; \ } \ \ void Stack_##TYPE##_destroy(Stack_##TYPE *stack) { \ free(stack->data); \ stack->data = NULL; \ stack->capacity = 0; \ stack->size = 0; \ } DEFINE_STACK_TYPE(int ) DEFINE_STACK_TYPE(double ) int main () { Stack_int int_stack; Stack_int_init(&int_stack); Stack_int_push(&int_stack, 10 ); Stack_int_push(&int_stack, 20 ); Stack_int_push(&int_stack, 30 ); printf ("Top of int stack: %d\n" , Stack_int_top(&int_stack)); while (int_stack.size > 0 ) { printf ("Popped from int stack: %d\n" , Stack_int_pop(&int_stack)); } Stack_int_destroy(&int_stack); Stack_double double_stack; Stack_double_init(&double_stack); Stack_double_push(&double_stack, 1.1 ); Stack_double_push(&double_stack, 2.2 ); Stack_double_push(&double_stack, 3.3 ); printf ("Top of double stack: %.1f\n" , Stack_double_top(&double_stack)); while (double_stack.size > 0 ) { printf ("Popped from double stack: %.1f\n" , Stack_double_pop(&double_stack)); } Stack_double_destroy(&double_stack); return 0 ; }
输出
Top of int stack: 30 Popped from int stack: 30 Popped from int stack: 20 Popped from int stack: 10
缺点:
代码几乎没有可读性
难以调试
需显式写出类型参数
需要手动实例化
C++解决方案
template
模板 template
用法:在需要使用模板的地方最前面加上template <typename T>
#include <iostream> #include <vector> #include <stdexcept> template <typename T>class Stack {private : std::vector<T> data; public : bool empty () const { return data.empty (); } size_t size () const { return data.size (); } void push (const T& value) { data.push_back (value); } void pop () { if (empty ()) { throw std::underflow_error ("Stack underflow: cannot pop from an empty stack" ); } data.pop_back (); } T top () const { if (empty ()) { throw std::underflow_error ("Stack underflow: stack is empty" ); } return data.back (); } }; int main () { try { Stack<int > intStack; intStack.push (10 ); intStack.push (20 ); intStack.push (30 ); std::cout << "Top of int stack: " << intStack.top () << "\n" ; while (!intStack.empty ()) { std::cout << "Popped from int stack: " << intStack.top () << "\n" ; intStack.pop (); } Stack<double > doubleStack; doubleStack.push (1.1 ); doubleStack.push (2.2 ); doubleStack.push (3.3 ); std::cout << "Top of double stack: " << doubleStack.top () << "\n" ; while (!doubleStack.empty ()) { std::cout << "Popped from double stack: " << doubleStack.top () << "\n" ; doubleStack.pop (); } Stack<std::string> stringStack; stringStack.push ("Hello" ); stringStack.push ("World" ); std::cout << "Top of string stack: " << stringStack.top () << "\n" ; while (!stringStack.empty ()) { std::cout << "Popped from string stack: " << stringStack.top () << "\n" ; stringStack.pop (); } } catch (const std::exception& e) { std::cerr << "Error: " << e.what () << "\n" ; } return 0 ; }
输出
Top of int stack: 30 Popped from int stack: 30 Popped from int stack: 20 Popped from int stack: 10 Top of double stack: 3.3 Popped from double stack: 3.3 Popped from double stack: 2.2 Popped from double stack: 1.1 Top of string stack: World Popped from string stack: World Popped from string stack: Hello
编译器是如何处理模板的
示例
template <typename T, typename U>void foo (T, U) ;int main () { foo (1 , 2 ); } template <typename T, typename U>void foo (T t, U u) {}
template<typename T, typename U> foo(T, U);
=> template<typename int, typename U> foo(int, U);
=> template<typename int, typename int> foo(int, int);
=> finish!
concept
C++20引入了一个新的关键字concept
,用于定义模板的 约束条件 ,可以明确限定模板必须满足的参数要求,从而确保类型安全,提高程序的可靠性与稳定性。
示例
template <typename T>concept Addable = requires (T a, T b) { { a + b } -> std::same_as<T>; }; template <Addable T>T add (T a, T b) { return a + b; } int main () { std::cout << add (1 , 2 ) << std::endl; }
*注:*C++20的concept特性是默认禁用的,必须在编译时显式加上-fconcepts
参数才能编译成功
示例
$ g++ -std=c++20 -fconcepts main.cpp -o main
元编程
meta programing
指的是编写能够操作、生成或修改其他程序(或自身)代码的程序。简而言之,元编程是编写能够处理代码的代码。其核心概念是,程序在更高的抽象层次上工作,关注代码的结构和行为,而不仅仅是直接的逻辑
关键点
代码生成 :元程序可以根据某些输入或条件生成新的代码。
反射 :反射是程序在运行时检查和修改自身结构或行为的能力。例如,程序可以在运行时检查自身的函数、方法或变量。
宏 :宏是一组在编译前生成代码的指令,用于自动化重复的任务。比如 C/C++ 中的宏允许以在编译前评估的方式编写代码。
C++中可以利用的特性:宏(定义宏来替代重复的代码,每次根据不同的传入参数动态生成类似的代码),模板,constexpr
,面向对象编程等
Lambda表达式
语法
[captureList](paramList) specifiers exception -> returnType {body}
其中除了[]
, ()
, {}
都是可省的
captureList用于指定Lambda表达式内部如何访问其外部作用域中的变量,捕获方式有值(=
)捕获、引用(&
)捕获和混合捕获
paramList定义Lambda表达式的参数
specifiers用于指定Lambda表达式的属性,如mutable
(可以修改捕获的变量)
exception指定是否抛出异常,若抛出抛什么
returnType说明返回值类型
body定义函数逻辑
示例
vector<int > arr = {2 , 5 , 1 , 7 , 9 , 4 }; sort (arr.begin (), arr.end (), [](const auto a, const auto b) { return a < b; }); int k = 1 ;auto it = upper_bound (arr.begin (), arr.end (), k); sort (arr.begin (), arr.end (), [](const auto a, const auto b) { return a > b; }); it = upper_bound (arr.begin (). arr.end (), k, [&](const auto a, const auto b) { return a > b; });
<functional>
<functional>
库详见 C++面向对象编程进阶II
十个问题,为面向对象编程抛砖引玉
当类中未自定义构造函数,编译器是否会提供默认构造函数,为什么?
什么时候将构造函数、析构函数定义为private
?什么时候使用友元、static
成员?
为什么引入成员初始化表?为什么初始化表的执行次序只与类数据成员的定义次序相关
为什么引入拷贝构造函数、移动构造函数、=
操作符重载?
为什么需要后期(动态)绑定?C++
如何实现virtual
?
什么时候使用virtual
?
public继承和非public继承分别意味着什么?
为什么=
, ()
, []
, ->
不能全局重载
什么时候成员函数能返回&
?
大多数情况下都可以返回&
,除去只读的情况(事实上只读可以返回一个常量引用),但以下情况需要注意:
不要返回局部变量的引用 ,因为它们的生命周期在函数返回后结束。
不要返回临时对象的非 const
引用 ,因为它们的生命周期非常短。
返回动态分配的对象的引用 需要非常小心,确保管理好内存的释放。
返回静态成员变量的引用 是安全的,但返回非静态成员变量的引用时要确保该对象仍然存在。
什么时候重载new
和delete
?怎么重载?