从放弃到重启 C++ 泛型编程篇(6)—类模板(中)

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第26天,点击查看活动详情

在我们开始内容之前,让我们想一想为什么需要模板,模板给我们带来什么,我们学习面向对象编程时,都学到面向对象 3 个,其中一个就是多态,可以通过继承于一个基类不同子类来实现多态

模板中静态成员变量

在 C++ 类中,通过类上的静态成员。有这么一个情景就是,就是我们需要对象的计数器,这个成员变量需要由类来维护,在构造函数中对其增加,来记录创建了多少个对象,在析构函数中将其减少来。

template<typename T>
class MyClass
{
public:
    MyClass(T val){
        val_ = val;
    }
    
    void print(){
        cout << "Value: " << val_ << endl;
    }
    
private:
    T val_;
};

int main()
{
    MyClass<int> i1(1);
    i1.print();
    cout<<"Hello World";

    return 0;
}
复制代码

上面我们创建了一个不用类模板,接下来就需要在个类模板上添加一个静态变量

template <typename T>
class MyClass {
public:
    MyClass(T val) {
        val_ = val;
        object_count++;
    }
    
    ~MyClass() {
        object_count--;
    }
    
    void print() {
        std::cout << "Value: " << val_ << std::endl;
    }
    
static unsigned int object_count;
static void printObjectCount() {
    std::cout << "Object count: " << object_count << std::endl;
}
    
private:
    T val_;
};

template <typename T>
unsigned int MyClass<T>::object_count = 0;

int main()
{
    MyClass<int> i1(4);
    MyClass<int> i2(5);
    MyClass<int> i3(7);
    
    MyClass<int>::printObjectCount();
    cout<<"Hello World";

    return 0;
}
复制代码

我们需要值得注意的是在类模板中,只允许声明一个静态成员,而不能对其进行定义。如果想要定义可以static const unsigned int object_count = 0;

int main()
{
    MyClass<int> i1(4);
    MyClass<int> i2(5);
    MyClass<float> f1(0.5);
    MyClass<std::string> s1("string1");
    MyClass<std::string> s2("string2");
    MyClass<std::string> s3("string3");
    
    MyClass<int>::printObjectCount();
    MyClass<float>::printObjectCount();
    MyClass<std::string>::printObjectCount();


    return 0;
}
复制代码
#include <iostream>
#include <cstdlib>
#include <string>

class MyClassBase {
    public:
    MyClassBase() {
        object_count++;
    }
    
    ~MyClassBase() {
        object_count--;
    }
    
    static unsigned int object_count;
    static void printObjectCount() {
        std::cout << "Object count: " << object_count << std::endl;
    }
};

unsigned int MyClassBase::object_count = 0;

template <typename T>
class MyClass : public MyClassBase {
public:
    MyClass(T val) {
        val_ = val;
    }
    
    ~MyClass() {
    }
    
    void print() {
        std::cout << "Value: " << val_ << std::endl;
    }
        
private:
    T val_;
};

int main()
{
    MyClass<int> i1(4);
    MyClass<int> i2(5);
    MyClass<float> f1(0.5);
    MyClass<std::string> s1("string1");
    MyClass<std::string> s2("string2");
    MyClass<std::string> s3("string3");
    
    MyClassBase::printObjectCount();
    
    return 0;
}

复制代码

对于成员函数定义为了避免语法复杂性给我们带来麻烦,可以将成员函数定义都在类模板中进行定义,而不是在类模板以外进行定义。

在 c++17 版本中类模板中,无法定义一个静态成员变量,而在 C++17 之后,允许使用 inline关键字来定义静态成员变量。

模板参数推断

当我们在调用函数模板时,有时候可以和调用一个函数类似,编译器会根据传入参数类型进行推断 T 类型,然后将占位符 T 替换成指定的类型。

template<typename T>
T MyAbs(T t)
{
    if (t < 0)
        return -t;
    return t;
}
复制代码

可以调用函数模板就和调用重载函数类似,如下MyAbs(-) 无需指定MyAbs<int>,其实编译器是足够聪明将调用补全MyAbs<int>(-1) 这才是函数模板正确的调用姿势。

int main()
{
   int res = MyAbs(-1);
   std::cout << res << std::endl;
    
    return 0;
}
复制代码

编译器足够聪明可以推断出 T 的参数为引用类型

template<typename T>
void swap(T &a, T &b)
{
    T temp(a);
    a = b;
    b = temp;
}
复制代码
template<typename T>
void swap2(T &a, T &b)
{
    T temp(a);
    a = b;
    b = temp;
}

int main()
{
    int a = 1;
    int b = 2;
    
    int* p = &a;
    int* q = &b;
    
    
    swap2(a,b);
    swap2(p,q);
    return 0;
}
复制代码

而且编译器也可以推断出一些位于复合类型中的类型例如

template<typename T>
void f(T const *a[]){
    //do something
}

int main(){
    
    char const *names[] = {"RNN","CNN","LSTM",nullptr};
    
    f(names);
 
    return 0;
}
复制代码

这里提出的是char const *name[] 和参数类型结构是匹配的 T const *a[]

T const *a
char const *names[]

主要两个结构是一致,编译器就可以推断出 T 所对应类型。

那些场景编译器不会进行推断

对于函数模板的参数类型是用于给出返回值的类型,如下

template<typename T>
T f()
{
    T res;
    return res;
}
复制代码

这时即使返回值类型已经确定给出了 int res ,模板可以根据模板参数来推断出返回值时,需要在调用模板函数时给出类型,所以像下面这样调用模板函数是会

int res = f();
复制代码

对于类模板调用时需要指定类型参数

需要在调用函数模板时指定类型参数的值,这样调用才有效。

int res = f<int>();
复制代码
template<typename T>
class Rational
{
public:
    Rational(T n ):num_(n),den_(0){}

private:
    T num_;
    T den_;
};

int main()
{
    Rational rationalInt(1);
    
    return 0;
}
复制代码
template<typename T>
class Rational
{
public:
    Rational():num_(0),den_(1){};
    Rational(T n ):num_(n),den_(1){};

private:
    T num_;
    T den_;
};

int main()
{
    Rational r1,r2;
    
    return 0;
}
复制代码

对于类模板我们需要声明时需要给类型参数的值,即便Rational r2(1);像这样给出了参数,可以根据给出参数来推测类型,也需要给出类型参数来声明类 Rational<int> r2(1);

Rational<int> r1(1);
Rational r2(r1);
复制代码

也需要像这样Rational<int> r2(r1); 给出类型参数来声明一个 Rational 的对象,不过在 c++17之后,Rational r2(r1) 这样来声明也是合法的。

template<typename T>
T foo(T val){
    //do something
}
复制代码

猜你喜欢

转载自juejin.im/post/7110832509222387742