第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;
}