• C++--第24课


    第24课 - 专题四经典问题解析

    1. 历史的痕迹

    #include <cstdlib>

    #include <iostream>

    using namespace std;

    template<class T>  //以前是用typename定义,现在是用class定义

    T Minus(T a, T b)

    {

        return a - b;

    }

    template<class T>  //类模板

    class Add

    {

    public:

        T add(T a, T b)

        {

            return a + b;

        }

    };

    int main(int argc, char *argv[])

    {

        cout<<Minus(3, 4)<<endl;

        cout<<Minus<float>(0.3, 0.4)<<endl;

        Add<double> ap;

        cout<<ap.add(9, 8)<<endl;

        cout<<ap.add(0.001, 0.1)<<endl;

        cout << "Press the enter key to continue ...";

        cin.get();

        return EXIT_SUCCESS;

    }

    运行结果:

    -1

    -0.1

    17

    0.101

    对于上面的程序,class可以用来定义模板参数,为什么还有引进typename呢?

    在有C++的时候,泛型还没有广泛的使用。最早的时候typename还没有使用,知道泛型广泛应用的时候,才引进泛型。我们看下面的分析。

    在类中可以定义其它的新类型

    #include <cstdlib>

    #include <iostream>

    using namespace std;

    class Test

    {

    public:

        typedef int* PINT;  //指针类

        struct Point   //结构体

        {

            int x;

            int y;

        };

        class Sub   //内部类

        {

            public:

                Sub()

                {

                    cout<<"Sub()"<<endl;

                }

                void print()

                {

                    cout<<"Hello World"<<endl;

                }

        };

    };

    int main(int argc, char *argv[])

    {

        Test::PINT pi = new int(5);

        Test::Point po = {2, 3};

        Test::Sub sub; //可以像使用普通的类类型一样使用内部类

        cout<<*pi<<endl;  //打印pi这个指针指向的空间的值

        cout<<po.x<<" "<<po.y<<endl;

        sub.print();

        delete pi;  //与new对应,必须释放

        cout << "Press the enter key to continue ...";

        cin.get();

        return EXIT_SUCCESS;

    }

    运行结果:

    Sub()

    5

    2 3

    Hello World

    我们看到普通的类中可以定义新的类,那么类模板中肯定也能,我们看下面。

    在类模板中定义新的类型  

    template<class T, int N>

    class Test

    {

    public:

    typedef T ElemType;  //将ElemType定义为T

    enum { LEN = N };

    T array[LEN];

    };

    在函数模板中使用类模板的内部类型

    #include <cstdlib>

    #include <iostream>

    using namespace std;

    template<typename T, int N>

    class Test

    {

    public:

        typedef T ElemType;

        enum { LEN = N };

        ElemType array[LEN];

    };

    template<typename T>

    void test_copy(T& test, typename T::ElemType a[], int len)  /*模板函数进行复制,此时的ElemType就是是内部的类型还是成员函数,有二义性,编译器无法确定T是什么,编译器就会默认这是一个静态成员变量,变量名后跟数组,就是不合理的,于是我们就出现了typename,这样就合理了。所以那么我们在应用的开始,直接就把class变成typename就好了,这样就使得程序更好理解。就像我们的class和struct都可以定义类,但是我们在C++中会使用class。这样就会解决我们编译器之间的兼容性。*/

    {

        int l = (len < T::LEN) ? len : T::LEN;

        for(int i=0; i<l; i++)

        {

            test.array[i] = a[i];

        }

    }

    int main(int argc, char *argv[])

    {

        Test<int, 5> t1;

        Test<float, 3> t2;

        int ai[] = {5, 4, 3, 2, 1, 0};

        float af[] = {0.1, 0.2, 0.3};

        test_copy(t1, ai, 6);

        test_copy(t2, af, 3);

        for(int i=0; i<5; i++)

        {

            cout<<t1.array[i]<<endl;

        }

        for(int i=0; i<Test<float, 3>::LEN; i++)

        {

            cout<<t2.array[i]<<endl;

        }

        cout << "Press the enter key to continue ...";

        cin.get();

        return EXIT_SUCCESS;

    }

    运行结果:

    5

    4

    3

    2

    1

    0.1

    0.2

    0.3

    模板最初的目标只是为了对类类型进行泛型操作的定义,因此用class关键字声明泛型类型。

    在之后的进化过程中发现了模板相互调用时产生的::操作符的二义性。

    因此引入typename关键字是用于告诉编译器将::符号后的标识符看作类型。

    2. 坑爹的面试题

    面试官:你的简历上写着你熟悉C++,那么你写函数判断一个变量是否为指针吗?

    C++中仍然支持C语言中的可变参数函数,C++编译器的匹配调用优先级:

    (1)重载函数

    (2)函数模板

    (3)可变参数函数

    C++编译器匹配实例

    #include <cstdlib>

    #include <iostream>

    using namespace std;

    int test(int i, int j)  //普通函数

    {

        cout<<"int test(int i, int j)"<<endl;

    }

    template<typename T>  //函数模板

    T test(T i, T j)

    {

        cout<<"T test(T i, T j)"<<endl;

    }

    int test(...)    //可变参数

    {

        cout<<"int test(...)"<<endl;

    }

    int main(int argc, char *argv[])

    {

        int i = 0;

        int j = 0;

        test(i, j);

        cout << "Press the enter key to continue ...";

        cin.get();

        return EXIT_SUCCESS;

    }

    运行结果:

    int test(int i, int j)

    函数模板与可变参数函数的化学变化

    #include <cstdlib>

    #include <iostream>

    using namespace std;

    template<typename T>

    void isPtr(T*)

    {

        cout<<"void isPtr(T*)"<<endl;

    }

    void isPtr(...)

    {

        cout<<"void isPtr(...)"<<endl;

    }

    int main(int argc, char *argv[])

    {

        int* pi = NULL;

        float* pf = NULL;

        int i = 0;

        int j = 0;

        isPtr(pi);

        isPtr(pf);

        isPtr(i);

        isPtr(j);

        cout << "Press the enter key to continue ...";

        cin.get();

        return EXIT_SUCCESS;

    }

    运行结果:

    void isPtr(T*)

    void isPtr(T*)

    void isPtr(...)

    void isPtr(...)

    我们写一个函数模板,只能匹配指针参数,当这个函数被选中就说明我们定义的是指针,不被引用,说明我们用的是常量。

     

    解决方案1

    template<typename T>

    bool isPtr(T*)

    {

        return true;

    }

    bool isPtr(...)

    {

        return false;

    }

    面试官:你的方法实现了指针的判断,但是我觉得不够高效,你有更好的办法吗?

    分析

    解决方案1已经很好的解决面试官的问题,那么为什么还不够高效呢?哪里不够高效呢?

    解决方案1中的唯一耗时的地方在于函数调用的建栈与退栈过程,因此需要考虑如何避免这个过程以提高程序效率。函数的进出需要函数调用栈,很耗时。

    解决方案2     

    template<typename T>

    char isPtr(T*);   //没有写函数体,就不用调用了

    int isPtr(...);     //没有写函数体,就不用调用了

    #define ISPTR(v) (sizeof(isPtr(v)) == sizeof(char))

    /*sizeof在定义的时候就知道大小,即使不会被调用,在编译的时候就会被调用。当v是指针的时候,选择函数模板,返回值是char类型,大小与sizeof(char)一致,显示为一。当v不是指针的时候,调用可变参数函数,返回值为int,与后面的值不等。*/

    int main(int argc, char *argv[])

    {

        int* pi = NULL;

        float* pf = NULL;

        int i = 0;

        int j = 0;

        cout<<ISPTR(pi)<<endl;

        cout<<ISPTR(pf)<<endl;

        cout<<ISPTR(i)<<endl;

        cout<<ISPTR(j)<<endl;

        cout << "Press the enter key to continue ...";

        cin.get();

        return EXIT_SUCCESS;

    }

     

  • 相关阅读:
    Razor中@:和text
    Razor返回HTML字符串
    EPPlus使用
    ASP.NET MVC使用AllowAnonymous特性跳过授权验证
    下拉框获取json文件的数据
    SpringBoot 监听机制
    MybatisPlus Warpper实现复杂查询
    整合MybatisPlus心得
    MybatisPlus性能分析插件
    MybatisPlus物理删除、逻辑删除
  • 原文地址:https://www.cnblogs.com/free-1122/p/11336299.html
Copyright © 2020-2023  润新知