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;

}

 

猜你喜欢

转载自www.cnblogs.com/free-1122/p/11336299.html