c++ 第五章 模板

模板

程序=算法+数据结构

数据结构:能够存储任意类型

算法:能够操作存储任意类型数据结构

泛型编程

泛型编程
面向过程编程用模板实现函数过程
面向对象编程用模板实现类

一、基本范例:

a) 模板定义是以template关键字开头。

b)类型模板参数T前面用typename来修饰,所以,遇到typename就应该知道其后面跟的是一个类型。

//typename可以被class取代,此处的class并没有类。

c)类型模板参数T(代表的是一个类型)以前面修饰符号typename/class都用<>括起来。

d)T这个类型名字可以使换成任意类型其他符号,没有影响,只是一种习惯而已。

二、实例化:

实例化:编译时,用具体的“类型”代替“类型模板参数”的过程叫做实例化(也有人叫做代码生成器)

//.obj / .o文件在编译完成后就会产生

//int __codcl _nmsp1::add<double>(double, double)

//例如,实例化之后的函数名分别为add<int>add<double>

通过实例化之后的函数名包含三个部分:

​ a)模板名;

​ b)后面跟着一对<>;

​ c)<>中间是一个具体类型。

编译期间;

在编译阶段,编译器会查看函数模板的函数体部分,来确定是都针对该类型string进行sub函数的实例化。

在编译阶段,编译器需要找到函数模板的函数体部分,所以要把整个模板放在.h中;

三、模板参数的推断

(3.1)常规的参数推断

通过<>可以只制定一部分模板参数的类型,另外一部分模板参数的类型可以通过调用时的实例来推断

auto代替函数模板的返回值类型

decltype,可以与auto结合使用构成返回类型的后置语法。这种后置语法也就是autodecltype结合来完成返回值类型的推导.

(3.2)各种推断的比较一级空模板参数类标的推断

a)自动推断

b)指定类型模板参数,优先级比

c)指定空模板参数列表<>:作用就是调用mydouble函数模板,而不是普通的mydouble函数

四、函数模板

5.特化

//泛化(泛化版本):大众化的,常规情况下,写的函数末班都是泛化的函数模板。

//特化(特化版本):往往代表着从泛化版本中抽出来的以组子集

### 模板

模板函数
template<typename T>
T add(T a, T b) {
    cout << "add function : " << sizeof(T) << endl;
    return a + b;
}
/*
int add(int a, int b) {
    cout << "add function : " << sizeof(T) << endl;
    return a + b; 
}
*/
int main() {
    cout << add(2, 3) << endl;//当传入2, 3时此时模板会生成上面注释的那些代码,替换成真正存在的类型,模板帮我们生成相关的代码,真正编译的时候,模板没有任何作用。类似宏定义,但是比宏强大的多,模板类型抽象,代码逻辑;
    cout << add(3.5, 6.7) << endl;
}

问题一、

image-20200803155855361

image-20200803155916241

怎么解决32行问题,我们可以将32行转为double类型就不会报错了:
image-20200803160102330

问题二、

同样类型的模板是不是调用的同一个模板产出的代码呢 ? 验证

template<typename T>
T add(T a, T b) {
    cout << "add function : " << sizeof(T) << endl;
    static int i = 0;
    cout <<"i : " << (++i) << endl;
    return a + b;
}
int main() {
    cout << add(2, 3) << endl;//隐式推导
    cout << add(2.3, 4.5) << endl;
    cout << add<double>(2.3, 3) << endl;//显式推导,与上一行调用同一个模板扩展出来的代码

    return 0;
}

add function : 4
i : 1
5
add function : 8
i : 1
6.8
add function : 8
i : 2
5.3

有结果可知,调用同一个模板扩展出来的代码,例子中两个double可以看出,模板只是制造作用

decltype推导类型
template<class T, class U>
decltype(T() + U()) add(T a, U b) {
    return a + b;
}
class A {
public:
    A(int x) :x(x) {}
    int x;
};

class B {
public:
    B(int y) : y(y) {}
    int y;
};

int operator+(const A &a, const B &b) {
    return a.x + b.y;
}
//因为此处的add(T(), U())不能用,因为没有默认构造函数
template<typename T, typename U> //typename, class 作用上没有任何区别
auto add(T a, U b) -> decltype(a + b) { //decltype() 用来推导表达式返回值的类型,返回值后置
    return a + b;
}
模板类
template<typename T>
struct PrintAny{
    PrintAny(std::ostream &out) : out(out) {}
    void operator()(const T &a) {
        out << a;
    }
    std::ostream &out;
};
template<typename T>
class Array {
public:
    Array(int n) : n(n) {
        this->arr = new T[n];
    }
    T &operator[](int ind) {
        if(ind < 0 || ind >= n) return this->__end;
        return this->arr[ind];    
    }
    template<typename U>
    friend ostream &operator<< (ostream &, const Array<U> &);
private:
    T *arr;
    T __end;
    int n;
};

template<typename T>
ostream &operator<<(ostream &out, const Array<T> &a) {//重载输出
    out << "Class Array : ";
    for(int i = 0; i < a.n; i++) {
        out << a.arr[i] << " " << endl;
    }
    return out;
}

int main() {
    Array<int>a(10);//模板类
    Array<double> b(10);
    a[0] = 123;
    a[-123] = 456;
    for(int i = 0; i < 10; i++) {
        b[i] = (rand() % 100)/100.0;
    }
    cout << a << endl;
    cout << b << endl;
    return 0;
}

  1. 类内
class PrintAny {
public:
    template<typename T>
    void operator()(const T &a) {
        cout << a << endl;
    }
};
PrintAny print3;//不用<int>
PrintAny print4;
PrintAny print5;
  1. 类外
template<typename T>
class PrintAny {
public:
    void operator()(const T &a) {
        cout << a << endl;
    }
};
PrintAny<int> print1;//用<int>

image-20200806152525179

模板在编译的时候进行展开,实例化出来的是具体的方法具体的类,在编译阶段的事情在链接阶段对重复的合并。

template.h

#ifndef _TEMPLATE_H
#define _TEMPLATE_H
template<typename T>
T add(T a, T b) {
    return a + b;
}
int func();
#endif

test1.cpp

#include<iostream>
#include<cstdio>
#include<cmath>
#include<cstring>
#include<iomanip>
#include<algorithm>
#include<map>
#include<vector>
#include<set>
#include"template.h"
using namespace std;
int main() {
    cout << add(2, 3) << endl;
    return 0;
}

test2.cpp

#include<iostream>
#include<cstdio>
#include<cmath>
#include<cstring>
#include<iomanip>
#include<algorithm>
#include<map>
#include<vector>
#include<set>
#include"template.h"

int func() {
    return add(123, 456);
}

gcc -c test1.cpp//生成了test1.o

gcc -c test2.cpp//生成了test2.o

gcc test1.o test2.o//链接在一起

nm -C test1.o //看生成的目标程序

1.预处理 选项 gcc -E test.c -o test.c

预处理完成就停下来,产生结果放在test.i文件中。

2.编译 选项 gcc -S test.c

编译完成之后就停下来,结果保存在test.s中。

3.编译 gcc -C test.c

汇编完成之后就停下来,结果保存在test.o中。

模板类+模板函数
template<typename T>
struct Print{
    template<typename U>
    void operator()(const U &a) {
        cout << a << endl;
        cout << this->__temp << endl;
    }
    void set(const T &temp) {this->__temp = temp;}
    T __temp;
};
特化
template<>
int add(int a, int b) {
    cout << "specific template add function" << endl;
    return a + b;
}
template<>
class Array<double> {
public:
    Array(int n) : n(n) {
        cout << "double array template" << endl;
        this->arr = new double[n];
    }
    double &operator[](int ind) {
        if(ind < 0 || ind >= n) return this->__end;
        return this->arr[ind];    
    }
    template<typename T>
    friend ostream &operator<<(ostream &, const Array<T> &);
private:
    double *arr;
    double __end;
    int n;
};

对某些特定的类型进行特化版本的书写;

偏特化

任意类型的指针类型

template<typename T>
T add(T *a, T *b) {
    cout << "T * add function" << endl;
    return *a + *b;//有问题的
}
int main() {
int a_num = 123, b_num = 456;
cout << add(&a_num, &b_num) << endl;
}
template<class T, class U>//类偏特化
auto add(T *a, U *b) -> decltype(*a + *b) {
    return add(*a, *b);//未解决二维指针问题
}

template<>
int add(int a, int b) {//特化
    cout << "add int : " <<a <<" "<< b <<  endl;
    return a + b;
}
定义没有bug的add模板
  1. 定义一个参数的模板

    • template T add(T a, T b);
    • 当a、b变量类型不同的时候,模板就会报错
  2. 定义两个参数的模板

    • template<class T, class U> ??? add(T a, U b);
    • 没有办法确定返回值类型
  3. typeof的升级decltype类型的推导

    • decltype(T() + U())add(T a, U b)
    • 当T或者U类型没有默认构造函数的时候就会报错
  4. 返回值类型后置语法

    • auto add(T a,U b)->decltype(a +b);
    • 当传入指针变量的时候,出错
  5. 模板的片特化与特化

    • auto add(T *a, U *b) -> decltype(*a + *b)
    • 完美实现add模板函数
#include<iostream>
#include<cstdio>
#include<cmath>
#include<cstring>
#include<iomanip>
#include<algorithm>
#include<map>
#include<vector>
#include<set>
using namespace std;

class A{
    public:
    A(int x) : x(x) {}
    int x;
};
class B{
    public:
    B(int y) : y(y) {}
    int y;
};
int operator+(const A &a, const B &b) {
    return a.x + b.y;
}


template<class T, class U>
auto add(T a, U b) -> decltype(a + b) {//推导返回值类型,解决类对象相加问题
    return a + b;
}


int main() {
    A a(1000);
    B b(645);
    cout << a + b << endl;
    cout << add(2, 3) << endl;//隐式推导
    cout << add(3.5, 4.5) << endl;
    cout << add<double>(4, 5.5) << endl;//显式推导
    cout << add(a, b) << endl;
    
    cout << max(4, 3) << endl;
    cout << max<double>(4.3, 3) << endl;//标准库中的一个bug解决

    return 0;
}

模板参数推导

对于模板函数来说,编译器需要根据实际传入的参数来推导模板类型T。例如,假设我们有下面这个模板函数:

tempalte<typename T>
void f(T& param); // param si a reference

同时声明了这些变量:

int x = 27;				// x is an int
const int cx = x;		// cx is a const int
const int& rx = x;		// rx is a reference to x as a const int

那么调用模板函数时,编译器推导出的模板类型分别为:

f(x)		// T is int, param's type is int&
f(cx)		// T is const int, param's type is const int&
f(rx)		// T is const int, param's type is const int&

想要按值传递,将模板函数参数声明为T param;
想要按引用传递,但不考虑右值时,将模板函数参数声明为const T& param;
想要按引用传递,但要区分左值和右值时,将模板函数声明为T&& param;

正常类型的推导
  • 隐式转换:add(a, b)
  • 显示转换:add(a, b)
  • 模板函数作为参数,传参时的间接类型推导
//模板函数作为参数,传递给函数的间接类型推导
template<typename T, typename U>
T test_param_func(U a) {
    return a * 2;
}

void func2(int (*func)(double)) { //int 对应 T返回值类型   ; double 对应 U  参数类型
    cout << func(2.3) << endl;
}
int main() {
     func2(test_param_func);
}
引用类型的推导
  • 引用折叠

    也就是说,对于一个给定类型 x :
    x& &、x& && 、x&& &都折叠成类型x&
    类型x&& && 折叠成x&&
    //引用折叠只能应用于间接创建的引用的引用,如类型别名或模板参数
     如果一个函数参数是一个指向模板类型参数的右值引用,则它可以被绑定到一个左值。
    如果实参是一个左值,则推断出的模板实参将是一个左值引用,且函数参数将被实例化为一个左值引用参数。
     我们可以将任意类型的实参传递给T&&类型的函数参数。对于这种类型的参数,显示我们也可以传递给它右值,当然也可以传递左值。
    
  • T、T &、T &&的作用

    T &左值引用,T &&右值引用

  • 只有T &&是正确的声明模板中的引用

    • 左值时,T为int &
    • 右值时,T为int
  • 右值转换的含义是:
    形容funRef(T && arg)的这种参数为右值引用的模板,当实参为一个左值时,调用仍然成功,此时编译器推断模板参数(也就是T)为左值的引用。例如调用funRef(i),那么T是int&而非int,展开可知funRef(int & && arg)再采用上述的引用折叠规则,可知最后argint&

    funRef(i);//实参是左值,int,T是int&
    funRef(ci);//实参是左值,const int,T是const int &12
    

    显式模板实参
    在某些情况下,编译器无法推断出模板实参的类型。比如下面的例子:
    template <typename T1, typename T2, typename T3>
    T1 sum(T2, T3);

    上面的例子中,没有任何函数实参的类型可用来推断T1的类型。每次调用sum时,都必须为T1提供一个显式模板实参。sum的调用方法可以是:
    auto val = sum(i, lng);
    此调用显示指定T1的类型,T2和T3的类型则由编译器从i和lng的类型推断出来。

  • remove_reference的作用

//remove——reference
template<typename T> struct remove_reference { typedef T type; }; //正常值版本

template<typename T> struct remove_reference<T &> { //左值引用偏特化版本
    typedef T type;
};

template<typename T> struct remove_reference<T &&> { //右值引用偏特化版本
    typedef T type;
};

//模板中的引用折叠
template<typename T>
typename remove_reference<T>::type add2(T &&a, T&&b) {//因为传入左值时返回值类型会被推导为int &,所以返回值要去引用
    typename remove_reference<T>::type c = a + b; //不使用remove会造成把右值赋值给左值引用
    return c;
}
int main() {
    int inta = 123, intb = 456;
    cout << add2(inta, intb) << endl;//传入左值时
    cout << add2(123, 456) << endl;//传入右值时
}
标准类型转换模板

remove_reference

  • 去掉引用

    //remove——reference
    template<typename T> struct remove_reference { typedef T type; }; //正常值版本
    
    template<typename T> struct remove_reference<T &> { //左值引用偏特化版本
        typedef T type;
    };
    
    template<typename T> struct remove_reference<T &&> { //右值引用偏特化版本
        typedef T type;
    };
    
    //模板中的引用折叠
    template<typename T>
    typename remove_reference<T>::type add2(T &&a, T&&b) {//因为传入左值时返回值类型会被推导为int &,所以返回值要去引用
        typename remove_reference<T>::type c = a + b; //不使用remove会造成把右值赋值给左值引用
        return c;
    }
    int main() {
        int inta = 123, intb = 456;
        cout << add2(inta, intb) << endl;//传入左值时
        /*当有一个左值绑定在右值引用上T, 就被推导为左值引用
    *T c = a + b;
    * 因为a + b 是右值,T 是int&,所以报错
    * 解决方法实现 remove_reference<T>
    */
        cout << add2(123, 456) << endl;//传入右值时
    }
    

add_const

  • 添加const限定

    //add_const
    template<typename T> struct add_const { typedef const T type; };
    template<typename T> struct add_const<const T> { typedef const T type; }; //T匹配到int
    

add_lvalue_reference

  • 转换成左值引用

    //add_lvalue_reference
    template<typename T> struct add_lvalue_reference { typedef T& type; };
    template<typename T> struct add_lvalue_reference <T &>{ typedef T& type; };
    template<typename T> struct add_lvalue_reference<T &&> { typedef T& type; };
    

add_rvalue_reference

  • 转换为右值引用

    //add_rvalue_reference
    template<typename T> struct add_rvalue_reference { typedef T&& type; };
    template<typename T> struct add_rvalue_reference<T &> { typedef T&& type; };
    template<typename T> struct add_rvalue_reference<T &&> { typedef T&& type; };
    

remove_pointer

  • 去掉指针类型

    //remove_pointer
    template<typename T> struct remove_pointer { typedef T type; };
    template<typename T> struct remove_pointer<T *> { typedef typename remove_pointer<T>::type type; };
    

add_pointer

  • 变成指针类型

make_signed

  • 变成有符号类型

make_unsigned

  • 变成无符号类型

remove_extent

  • 去掉一层数组

remove_all_extent

  • 去掉所有层数组
模板函数move的实现
//move
template<typename T>
typename add_rvalue_reference<T>::type move(T &&a) { //&& 接收引用,左值变量会被推导为左值引用类型,右值会被推导为普通类型
    return typename add_rvalue_reference<T>::type(a); //C++ 中的强转 类型(变量)
}

void f(int &x) {
    cout << "f function : left value" << endl;
    return ;
}
void f(int &&x) {
    cout << "f function : right value" << endl;
    return ;
}
int main() {
    haizei::f(n);
    haizei::f(haizei::move(m));
}
变参模板

(1)参数个数:利用参数个数逐一递减的特性,实现递归调用;

(2)参数类型:参数个数逐一递减导致参数类型也逐一递减;

两个注意点:

(1)递归调用

(2)递归终止:使用重载的办法终止递归调用

可变参数模板和普通模板的语义是一样的,只是写法上稍有区别,声明可变参数模板时需要在typenameclass后面带上省略号“...”。比如我们常常这样声明一个可变模版参数:
template<typename...>
template<class...>
一个典型的可变模版参数的定义是这样的:
template <typename...  Args> // Args: 模板参数包
void f(Args... args);// args: 函数参数包
/*在模板形参 Args 的左边出现三个英文点号"...",表示 Args 是零个或多个类型的列表,是一个模板参数包(template parameter pack)。正如其名称一样,编译器会将 Args 所表示的类型列表打成一个包,将其当做一个特殊类型处理。相应的函数参数列表中也有一个函数参数包。与普通模板函数一样,编译器从函数的实参推断模板参数类型,与此同时还会推断包中参数的数量。
*/
//省略号的作用有两个:
1.声明一个参数包Args... args,这个参数包中可以包含0到任意个模板参数;
2.在模板定义的右边,可以将参数包展开成一个一个独立的参数。//  print(rest...);
 //可变模版参数和普通的模版参数语义是一致的,所以可以应用于函数和类,即可变模版参数函数和可变模版参数类,然而,模版函数不支持偏特化,所以可变模版参数函数和可变模版参数类展开可变模版参数的方法还不尽相同,
//递归终止函数
void print(){
   cout << "empty" << endl;
}
//展开函数
template <class T, class ...Args>
void print(T head, Args... rest){
   cout << "parameter " << head << endl;
   print(rest...);
}
int main(void){
   print(1,2,3,4);
   return 0;
}
/*上例会输出每一个参数,直到为空时输出empty。展开参数包的函数有两个,一个是递归函数,另外一个是递归终止函数,参数包Args...在展开的过程中递归调用自己,每调用一次参数包中的参数就会少一个,直到所有的参数都展开为止,当没有参数时,则调用非模板函数print终止递归过程。
*/

代码讲解: ARGS代表模板中剩余部分的类型数量是可变的,但是最少为1个,此代码会递归展开模板函数Print

#include<iostream>
#include<cstdio>:wq
#include<cmath>
#include<cstring>
#include<iomanip>
#include<algorithm>
#include<map>
#include<vector>
#include<set>
using namespace std;

template<typename T>
void print(const T &a) {
    cout << a << endl;
}

template<typename T, typename ...ARGS>
void print(const T &a, ARGS ...args) {
    cout << a << " ";
    print(args...);
    return ;
}

template<int n, typename T, typename ...ARGS> 
struct ARG {
    typedef typename ARG<n - 1, ARGS...> ::getT getT;
    typedef ARG<n - 1, ARGS...> N;
};

template<typename T, typename ...ARGS>
struct ARG<0, T, ARGS...>{
    typedef T getT;
};

template<typename T>
struct ARG<0, T> {
    typedef T getT;
    typedef T finalT;
};

template<typename T, typename ...ARGS> class Test;
template<typename T, typename ...ARGS>
class Test<T(ARGS...)>{
public:
    T operator()(typename ARG<0, ARGS...>::getT a, typename ARG<1, ARGS...>::N::finalT b) {
        return a + b;
    }
};

int main() {
    print(123, "hello world", 78, 1.05);
    Test<int(double, float)> e;
    cout << e(2.3, 4.5) << endl;
    return 0;
}

变参模板的知识

  • 变参模板语法

  • 演示print模板函数递归展开变参列表

    template<typename  T> //使用特化模板,解决递归一直展开的问题
    void printAny(const T &a) {
        cout << a << endl;
        return ;
    }
    
    template<typename T, typename ...ARGS> //变参模板
    void printAny(const T &a, ARGS... args) {
        cout << a << " ";
        printAny(args...);
        return ;
    }
    

设计处理变参列表的模板类

  • 正向展开的ARG模板类
  • 反向展开的ARG模板类(传入整形模板参数n)
//帮助模板类展开的模板类
template<int n, typename T, typename ...ARGS> //为什么传入 int n编译期常量? 因为模板在编译后就不存在了
struct ARG {
    typedef typename ARG<n - 1, ARGS...>::__type __type;
    //typedef ARG<n - 1, ARGS...> __rest;
};

template<typename T, typename ...ARGS>
struct ARG<0, T, ARGS...> {
   typedef T __type; 
};

template<typename T>
struct ARG<0, T> {
    typedef T __type;
};


template<typename T, typename ...ARGS> class Test; //工具模板类,去解析变参列表
template<typename T, typename ...ARGS>
class Test<T(ARGS...)> { //使用偏特化形式,让这个模板类使用更像一个函数
public:
    T operator()(ARGS... args) { //函数的入口写起
        return add<T>(args...);   
    }
        //typename ARG<0, ARGS...>::__type a, //前面的typename主要是用来说明 后的的内容是一个类型(模板中的内置类型),避免语义歧义。
private:
    template<typename T1, typename U, typename ...US>
        T1 add(U a, US ...args) {
            return a + add<T1>(args...);
        }
    template<typename T1, typename U>
        T1 add(U a) {
            return a;
        }
};
int main() {
    haizei::Test<int(int, int, int, int)> f4;
    cout << f4(1, 2, 3, 4) << endl;
    haizei::Test<int(int, int, int, int, int)> f5;
    cout << f5(1, 2, 3, 4, 5) << endl;
}

拓展知识

统计函数执行的次数
面向对象的程序设计使用function模板
自己写的function对象赋值给系统的function中bug
template<typename RT, typename ...ARGS>class FunctionCnt;
template<typename RT, typename ...ARGS>
class FunctionCnt<RT(ARGS...)>{
public:
    FunctionCnt(haizei::function<RT(ARGS...)> g) : g(g), __cnt(0){}
    RT operator()(ARGS ...args) {
        __cnt ++;
        return g(args...);
    }
    int cnt(){return __cnt;}
private:
    function<RT(ARGS...)>g;
    int __cnt;
};

image-20200813095845375

add构造为haizei::function g->通过g(g),这一步中发生段错误

g(A)->function(T f)时因为浅拷贝时指向同一个ptr,析构g因为外面的ptr被释放,f指向null,所以报错.

解决问题:写一个深拷贝

template<typename RT, typename ...PARAMS>
class base{
public :
    virtual RT operator()(PARAMS...) = 0;
    virtual base *getCopy() = 0;
    virtual ~base() {}
};

template<typename RT, typename ...PARAMS>
class normal_func :public base<RT, PARAMS...>{//继承base
public:
    typedef RT (*(func_type))(PARAMS...);
    normal_func(func_type func) : ptr(func){}
    virtual RT operator()(PARAMS...args) override{
        return this->ptr(args...);
    }
    virtual base<RT, PARAMS...> *getCopy() override{
        return new normal_func<RT, PARAMS...>(ptr);
    }
private:
    func_type ptr;
};

template<typename C,typename RT, typename ...PARAMS>
class functor :public base<RT, PARAMS...>{
    //继承base
public:
    typedef RT (*(func_type))(PARAMS...);
    functor(C &func) : ptr(func){}
    virtual RT operator()(PARAMS...args) override{
        return this->ptr(args...);
    }
    virtual base<RT, PARAMS...> *getCopy() override{
        return new functor<C, RT, PARAMS...>(ptr);
    }
private:
    C &ptr;
};

template<typename RT, typename ...PARAMS> class function;
template<typename RT, typename ...PARAMS>
class function<RT(PARAMS...)> {
public :
    function(RT (*func)(PARAMS...))//传入普通函数的构造函数
    :ptr(new normal_func<RT, PARAMS...>(func)){//普通函数构造
        cout << "normal function constructor" << endl;
    }
    template<typename T>//传入函数对象的构造函数
    function(T a)
    : ptr(new functor<typename remove_reference<T>::type, RT, PARAMS...>(a)){//对象构造函数
        cout << "function object constructor" << endl;
    }
    /*问题所在实现深拷贝就可以*/
    function(const function &f) {//深拷贝
        this->ptr = f.ptr->getCopy();//拷贝直接获取
    }
    function(function &&f) {//移动构造
        this->ptr = f.ptr;
        f.ptr = nullptr;
    }
    
    RT operator()(PARAMS... args) {
        return this->ptr->operator()(args...);
    }
    ~function() {
        if(ptr != nullptr)delete ptr;
    }

private :
    base<RT, PARAMS...> *ptr;
//步骤2,把函数或函数对象打包成一个新的函数对象(即为base类,为了记录数据),然后把这个新的函数对象地址存下来。
};
函数式编程使用bind与引用
  • ref的使用

  • std::placeholder::_1._2介绍

从一种传参变成另一种传参

function<int(int)>f = bind(add, 5, placeholders::_1);

image-20200813112937442

使用bind统计函数的执行次数的方法:

int add_cnt(function<int(int, int)>func, int &n, int a, int b) {
    n++;
    return func(a, b);
}
int main() {
    //绑定一个可以统计函数执行次数的方法
    int n = 0;
    function<int(int, int)> f5 = bind(add_cnt, add, ref(n), placeholders::_1, placeholders::_2);//n其实是拷贝了一份,传过去,n并没有变,当加上ref是是加&//bind有拷贝功能
    f5(99, 88);
    f5(99, 88);    
    f5(99, 88);
    cout << n << endl;
    //为什么n输出0;
    return 0;
}
//解决bind中的引用问题是用ref
C++线程类thread介绍
  • thread基本使用

    #include <iostream>
    #include <thread>
    using namespace std;
    
    void func() {
        cout << "hello world" << endl;
    }
    
    void func2(int x) {
        cout << x << " hello world" << endl;
        return ;
    }
    
    void func3(int &x) {
        x += 1;
        cout << x << " hello world" << endl;
        return ;
    }
    
    int main() {
        thread t1(func); //内部实现借用到bind,将函数进行绑定,生成新的无参函数
        thread t2(func);
        thread t3(func);
        t1.join();
        t2.join();
        t3.join();
    
        thread t4(func2, 4);
        thread t5(func2, 5);
        thread t6(func2, 6);
        t4.join();
        t5.join();
        t6.join();
    
        int n = 0;
        thread t7(func3, ref(n));//类似bind加一个ref
        thread t8(func3,ref(n));
        thread t9(func3, ref(n));
        t7.join();
        t8.join();
        t9.join();
        cout << n << endl;
        return 0;
    }
    
  • thread构造函数与bind之间的关系

    class Task{
    public:
        template<typename T, typename ...ARGS>
        Task(T func, ARGS... args) {
            this->func = std::bind(func, args...);//函数与参数进行绑定
        }
        void operator()() {
            this->func();
            return;
        }
    private:
        function<void()>func;
    };
    
    void thread_func1(int a, int b) {
        cout << a << " + " << b << " = "<< a + b << endl;
        return ;
    }
    int main() {
        Task t1(thread_func1, 3, 4);
        Task t2(thread_func1, 5, 4);
        Task t3(thread_func1, 9, 4);
        t1();
        t2();
        t3();
        return 0;
    }
    
模板的图灵完备性演示

#include<iostream>
#include<cstdio>
#include<cmath>
#include<cstring>
#include<iomanip>
#include<algorithm>
#include<map>
#include<vector>
#include<set>
using namespace std;


template<int x>
struct IsOdd{
    constexpr static int r = x % 2;
};

//is_prime:

template<int i, int x>
struct getNextK{
    constexpr static int  r = (i * i > x ? 0 : (x % i == 0 ? 1 : 2));
};



template<int i, int x, int k>
struct Test {
    constexpr static int r = Test<i + 1,x, getNextK<i, x>::r >::r;
};

template<int i, int x>
struct Test<i, x, 0> {
    constexpr static int r = 1;
};

template<int i, int x>
struct Test<i, x, 1> {
    constexpr static int r = 0;
};

template<int x> 
struct is_prime{
    constexpr static int r = Test<2, x, 2>::r;
};

//is_prime_1
bool __is_prime(int i, int x) {
    if(i * i > x) return true;//k = 0;
    if(x % i == 0) return false;//k = 1
    return __is_prime(i + 1, x);//k = 2
}
bool is_prime_1(int x) {
    return __is_prime(2, x);
}


//count_prime
template<int i, int x>
struct getNextK2{
    constexpr static int r = (i > x ? 0 : 1);
};

template<int i, int x, int k>
struct __calc_count_prime{
    constexpr static int r = i * is_prime<i>::r + __calc_count_prime<i + 1, x, getNextK2<i + 1, x>::r >::r;
};


template<int i, int x>
struct __calc_count_prime<i, x, 0> {
    constexpr static int r = 0;
};

template<int x>
struct count_prime{
    constexpr static int r = __calc_count_prime<2, x, 1>::r;
};
//count_odd
template<int i, int x, int k>
struct __calc_count_odd {
    constexpr static int r = i * IsOdd<i>::r + __calc_count_odd<i + 1, x, getNextK2<i + 1, x>::r >::r;
};

template<int i, int x>
struct __calc_count_odd<i, x, 0> {
    constexpr static int r = 0;
};

template<int x>
struct count_odd {
    constexpr static int r = __calc_count_odd<1, x, 1>::r;
};

//print_prime
template<int x>
struct print_prime{
    template<int i, int k>
    struct __output{
        static void run() {
            if(is_prime<i>::r){
                cout << i << endl;
            }
            print_prime::__output<i + 1, getNextK2<i + 1, x>::r>::run();
        }
    };
    template<int i>
    struct __output<i, 0> {
        static void run() {
            return;
        }
    };
    static void output() {
        print_prime<x>::__output<2, 1>::run();
        return ;
    }
};

//get_next_prime

template<int x, int k>
struct __get_next_prime{
    constexpr static int r = __get_next_prime<x + 1, is_prime<x + 1>::r> ::r;
};


template<int x> 
struct __get_next_prime<x, 1>{
    constexpr static int r = x;
};

template<int x>
struct get_next_prime{
    constexpr static int r = __get_next_prime<x + 1, is_prime<x + 1>::r >::r;
};

//print_trangle
template<int n>
struct print_trangle {
    template<int i, int m, int k>
    struct __output_line{
        static void run() {
            cout << "-";
            __output_line<i + 1, m, getNextK2<i + 1, m>::r >::run();
        }
    };
    template<int i, int m>
    struct __output_line<i, m, 0>{
        static void run() {
            cout << endl;
        }
    };

    template<int i, int k>
    struct __output {
        static void run(){
            __output_line<1, i, 1>::run();
            __output<i + 1, getNextK2<i + 1 , n>::r >::run();
        }
    };
    template<int i>
    struct __output<i, 0> {
        static void run() {
            return;
        }
    };

    static void output() {
        __output<1, 1>::run();
    }
};

int main() {
    cout << IsOdd<5>::r << endl;
    cout << IsOdd<6>::r << endl;
    cout << is_prime<10000>::r << endl;
    cout << count_prime<7>::r << endl;
    cout << count_odd<10>::r << endl;
    print_prime<100>::output();
    print_trangle<6>::output();
    return 0;
}

其他

C++ 的函数模板本质上函数的重载,泛型只是简化了程序员的工作,让这些重载通过编译器来完成。我们可以用 c++flit 来观察这一现象。

img

通过 objdump 可以把 template1.out 中代码段的与 swap 相关的符号提出来,有两个, 分别是_Z4swapIdEvRT_S1_和_Z4swapIiEvRT_S1_

再用 f++filt 将 Name Mangling 后的名字翻转过来,就对应了两个函数原型:

void swap(double&, double&)
void swap(int&, int&)

函数模板重载

函数模板之间、普通函数和模板函数之间可以重载。编译器会根据调用时提供的函数参数,调用能够处理这一类型的最佳匹配版本。在匹配度上,一般按照如下顺序考虑:

img

可以通过空模板实参列表来限定编译器只匹配函数模板,比如 main 函数中的最后一条语句。

//template6.cpp
\#include
\#include
template
const T &max(const T &a, const T &b)
{ std::cout << "max(&, &) = ";
return a > b ? a : b;
}
// 函数模板重载
template
const T *max(T *a, T *b) {
std::cout << "max(*, *) = ";
return *a > *b ? a : b;
}
// 函数模板重载
template
const T &max(const T &a, const T &b, const T &c)
{ std::cout << "max(&, &, &) = ";
const T &t = (a > b ? a : b);
return t > c ? t : c;
}
// 普通函数
const char *max(const char *a, const char *b)
{ std::cout << "max(const char *, const char *) = ";
return strcmp(a, b) > 0 ? a : b;
}
int main() {
int a = 1, b = 2;
std::cout << max(a, b) << std::endl;
std::cout << *max(&a, &b) << std::endl;
std::cout << max(a, b, 3) << std::endl;
std::cout << max("en", "ch") << std::endl;
// 可以通过空模板实参列表来限定编译器只匹配函数模板
std::cout << max<>("en", "ch") << std::endl;
std::cout << max(100, 200) << std::endl;
return 0;
}

img

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值