适合具备 C 语言基础的 C++ 教程(十一)

2021-03-04 12:11:39 浏览数 (1)

前言

在上一则教程中,着重叙述了抽象类界面以及函数模板的相关内容,在本节教程中,笔者将详细阐述函数模板重载的概念,将从具体的实例中阐述函数模板重载要注意的内容。

函数模板重载

函数重载是在教程最开始就阐述过的概念,那么函数模板重载又该如何理解呢,我们以上一节教程中比大小的函数模板为背景,具体阐述函数模板重载的概念,代码如下所示:

代码语言:javascript复制
template<typename T>
const T& mymax(const T& a, const T& b)
{
    cout<<"1: "<<__PRETTY_FUNCTION__<<endl;
    return (a < b)? b : a;
}

template<typename T>
const T& mymax(T& a, T& b)
{
    cout<<"2: "<<__PRETTY_FUNCTION__<<endl;
    return (a < b)? b : a;
}

const int& mymax(int& a, int& b)
{
    cout<<"3: "<<__PRETTY_FUNCTION__<<endl;
    return (a < b)? b : a;
}

上述代码中展示了两个函数模板和一个普通函数,两个函数模板的区别就在第一个函数模板形参中具有 const,但是第二个函数模板不具有const,剩余的就是一个普通函数,基于此,我们来编写主函数的代码:

代码语言:javascript复制
int main(int argc, char **argv)
{
    int ia = 1;
    int ib = 2;

    cout<<mymax(ia, ib)<<endl;

    return 0;
}

代码执行的结果如下所示:

image-20210224171715532

通过第一行的打印信息可以看到,当前调用mymax()函数是调用的普通函数,并不是模板函数,那么这是为什么呢,在这里通过传入的参数可知两个模板函数也是调用的,但是为什么就是调用的普通函数而非模板函数呢?这个原则是什么,下面列出了一个详细地分析步骤来分析这个调用过程。

  • 1、第一步首先是列出可能被调用的候选函数,就包括普通函数和参数推导成功的模板函数
  • 针对于这个例子来说,列出的候选函数如下所示:
    • 第一个模板函数:mymax(const int &, const int &);
    • 第二个模板函数:mymax(int&, int&);
    • 第三个普通函数:mymax(int&, int&);
  • 2、根据参数转换,进行排序:
  • 第一个模板函数:int ->const int
  • 第二个模板函数:int ->int
  • 第三个普通函数:int ->int
  • 所以,第二个模板函数和第三个普通函数并列第一,第一个模板函数排第二
  • 3、选择更加匹配的候选函数
  • 如果匹配度相同
    • 优先选择普通函数
    • 对于多个模板函数,选择一个更加特化的(特化后续解释)
    • 否则,代码出现二义性
  • 所以,根据第二得到的结果是第二个模板函数和第三个普通函数并列第一,根据第三条原则,优先选择普通函数,因此,在这里调用的是普通函数。

二义性例子

接下来,我们来看一个二义性的例子,首先给出模板函数的代码,代码如下所示:

代码语言:javascript复制
template<typename T>
const T& mymax(const T& a, const T& b)
{
    cout<<"1: "<<__PRETTY_FUNCTION__<<endl;
    return (a < b)? b : a;
}

template<typename T>
const T& mymax(T& a, T& b)
{
    cout<<"2: "<<__PRETTY_FUNCTION__<<endl;
    return (a < b)? b : a;
}

template<typename T>
const T mymax(T a, T b)
{
    cout<<"4: "<<__PRETTY_FUNCTION__<<endl;
    return (a < b)? b : a;
}

有了模板函数,我们再来编写主函数,主函数代码如下所示:

代码语言:javascript复制
int main(int argc, char **argv)
{
    int ia = 1;
    int ib = 2;

    cout<<mymax(ia, ib)<<endl;

    return 0;
}

同样的,按照上述的分析方法,一步一步进行分析:

  • 1、第一步,列出候选函数:
  • 第一个模板函数:mymax(const int &, const int &);
  • 第二个模板函数:mymax(int&, int&);
  • 第三个模板函数:mymax(int, int)
  • 2、根据参数转换,进行排序
  • 第一个模板函数:int ->const int
  • 第二个模板函数:int ->int
  • 第三个模板函数:int ->int
  • 3、选择更加匹配的候选函数
  • 2、可知,第二个和第三个匹配度一样,所以当前这个程序也就造成了二义性的错误

下面是代码编译的结果:

image-20210224195510811

参数为指针

接下来,叙述一个实参为指针的一个例子,首先先看模板函数和普通函数,代码如下所示:

代码语言:javascript复制
template<typename T>
const T& mymax(const T& a, const T& b)
{
    cout<<"1: "<<__PRETTY_FUNCTION__<<endl;
    return (a < b)? b : a;
}

template<typename T>
const T& mymax(T& a, T& b)
{
    cout<<"2: "<<__PRETTY_FUNCTION__<<endl;
    return (a < b)? b : a;
}

const int& mymax(int& a, int& b)
{
    cout<<"3: "<<__PRETTY_FUNCTION__<<endl;
    return (a < b)? b : a;
}

template<typename T>
const T mymax(T * a, T* b)
{
    cout<<"4: "<<__PRETTY_FUNCTION__<<endl;
    return (*a < *b)? *b : *a;
}

基于这样一个模板函数,我们现在来编写主函数,主函数代码如下所示:

代码语言:javascript复制
int main(int argc, char** argv)
{
    int *p1=&ia;
    int *p2=&ib;

    cout<<mymax(p1, p2)<<endl;

    return 0
}

代码执行结果如下图所示:

image-20210224201939143

由上述打印出来的信息可以知道,上述是调用了标号为4的模板函数,我们按照之前的方法来分析一下,首先调用的函数是mymax(int *, int *):

  • 1、列出候选函数:
  • 第一个模板函数:mymax(const int*&, const int*&);
  • 第二个模板函数:mymax(int *&, int *&);
  • 第三个普通函数:不满足
  • 第四个模板函数:mymax(int *,int *);
  • 2、根据参数,进行排序:
  • 第一个:int* -> const int*
  • 第二个:int* -> int*
  • 第四个:int* -> int*
  • 3、根据参数,进行排序:
  • 最匹配的是:第二个和第四个
  • 4、它们都是模板函数,选出“更特化”的,更特化的意思也就是说参数匹配更加特殊更加具体更加细化

我们这个时候,回过头来看第二个模板函数,mymax(T& a, T& b),对于这个模板函数来说,它存在两种情况:

  • T = int的时候,那么也就是mymax(int &,int &);
  • T= int *的时候,那么也就是mymax(int *&, int *&)

我们再来看第四个模板函数,mymax(T*, T*),参数只能是指针,也就是说当T = int的时候,也就是 mymax(int*, int*),通过这里的分析,我们可以看出对于第二个模板函数和第四个模板函数来讲,第四个模板函数更加具体,也就是更加特化,所以上述是调用的第四个模板函数。

const int *

接下来,我们看一个由int* -> const int*的例子,首先,依旧是先看模板函数,代码如下所示:

代码语言:javascript复制
template<typename T>
const T& mymax(const T& a, const T& b)
{
    cout<<"1: "<<__PRETTY_FUNCTION__<<endl;
    return (a < b)? b : a;
}

template<typename T>
const T mymax(const T * a, const T* b)
{
    cout<<"2: "<<__PRETTY_FUNCTION__<<endl;
    return (*a < *b)? *b : *a;
}

我们在基于上述两个模板函数的基础上,来编写我们的主函数,主函数代码如下所示:

代码语言:javascript复制
int main(int argc, char **argv)
{
    int ia = 1;
    int ib = 2;

    int *p1=&ia;
    int *p2=&ib;

    cout<<mymax(p1, p2)<<endl;

    return 0;
}

同样的方法,为了分析出它会调用的是哪一个模板函数,我们按照之前所述的步骤一步一步地进行分解,首先,明确调用的函数是:mymax(int*, int*)

  • 1、列出候选函数:
  • 第一个模板函数:mymax(const int*&, const int*&)
  • 第二个模板函数:mymax(const int*, const int*)
  • 2、根据参数,进行比较:
  • 第一个模板函数:int* -> const int *;
  • 第二个模板函数:int* ->const int*
  • 3、根据参数,进行匹配:
  • 2、的结果可知,两个都是匹配的

既然两个都是匹配的,那要如何进行选取呢?首先额外补充一个知识点:

const int *p,遇到指针的时候,都是从右往左读,遇见p就读成p is a,遇见*就读成pointer to,那么这条语句也就翻译成这样:p is a pointer to const int,也就是说 p指向的对象是不可修改的

我们这个时候,来看第二个模板函数,对照其推导出来的模板函数,mymax(const int*, const int*),也就是说传进去的实参所指向的内容是不可变的。

此时,我们再来看第一个模板函数,其模板函数是这样的:const T& mymax(const T& a, const T& b),而推导的模板函数实际上也就是说T = int *,所以它的形参实际上应该是这样的:const (int*)&,那这么说来,p是常量引用,p无法修改,但是p指向的对象是可修改的。

回到我们的代码上去,我们传到函数里面的两个实参是可以修改的,所以这里应该选择第一个模板函数进行调用,下面是代码执行的结果:

image-20210224210604846

虽然调用的是第一个模板函数,编译没有出错,但是实际上这里函数运行结果并非我们想要,它是比较的两个传进去的实参的地址的大小,返回的也是地址的大小,并非值的大小。

类模板

在首次介绍模板的时候,我们也指出,除了有函数模板以外还具有类模板,接下来,笔者将对类模板的相关概念进行阐述。当我们碰到相似的函数的时候,会想到使用函数模板来解决问题;自然,如果我们碰到有相似的类的时候,也可以使用类模板来解决问题,下面的代码就是一个类模板:

代码语言:javascript复制
template<typename T>
class AAA
{
private:
    T t;
public:
    void test_func(const T &t);
    void print(void);
};

上述就是一个类模板的写法,上述只是类的一个声明,如果要实现模板类里面的成员函数,则需要使用如下所示的代码:

代码语言:javascript复制
template<typename T>
void AAA<T>::test_func(const T &t)
{
    this->t = t;
}

template<typename T>
void AAA<T>::print(void)
{
    cout << t << endl;
}

基于上述模板类,我们就可以编写主函数了,代码如下所示:

代码语言:javascript复制
int main(int argc, char **argv)
{
    AAA<int> a;

    a.test_func(1);
    a.print();

    AAA<double> b;

    b.test_func(1.23);
    b.print();

    return 0;
}

代码运行结果如下所示:

image-20210224224823979

类重载

我们知道函数是可以重载的,那么其实类也是可以进行重载的,类重载也可以称之为是定做,在上述代码的基础上,我们来定做类,代码如下所示:

代码语言:javascript复制
template<>
class AAA<int>
{
public:
    void test_func_int(const int &t)
    {
        cout << t << endl;
    }
    void print_int(void);
};

void AAA<int>::print_int(void)
{
    cout << "for test" << endl;
}

在定做了类之后,我们来看主函数的代码:

代码语言:javascript复制
int main(int argc, char** argv)
{
    A<int> a;

    a.test_func_int(1);
    a.print_int();

    return 0;
}

代码运行结果如下所示:

image-20210224230451701

注:在上述介绍的函数模板和类模板,虽然在介绍的时候,都是在 .cpp中实现的,但是在实际的项目当中,其实基本都是写在.h文件中的,因为对于模板来说,它只是一些编译指令,一般都是将其放在头文件中供其他代码引用。

小结

上述便是本期分享的内容,本期涉及到的代码可以通过百度云链接的方式获取到:

链接:https://pan.baidu.com/s/1p-GU0_5aa46lvaB2AdHemA 提取码:1sxd

0 人点赞