c++学习笔记(17.专题四经典问题解析)

本节知识点:

1.模板的历史痕迹(typename关键字的引入):

   a.在原始的c++编译器中是没有typename这个关键字的,模板最初的目的只是为了对类类型进行泛型操作而定义,因此是用class关键字声明的泛型类型,之后模板才扩展到所有的类型。但是问题也就随之出现了,下面具体分析。
示例代码:
#include <iostream>

using namespace std;

template <class T>
class test
{
public:
	T add(T a, T b)
	{
		return a + b;
	}
};

template <class T>
T Minus(T a, T b)
{
	return a - b;
}

int main()
{
	test<double> t1;
	cout << t1.add(3.5, 9.2) << endl;
	cout << Minus<int>(9, 7) << endl;
	return 0;
}
   b.在类类型中可以定义其它的新类型:如typedef、struct、enum、class等。这样类中就可以有,成员变量、成员函数、新类型、静态变量,对于这些新类型的使用不再需要通过具体的对象了,而是跟使用静态变量一样直接通过 类名加所属符:: 去调用!例如:test :: add  a1;
示例代码:
#include <iostream>

using namespace std;

class test
{
public:
	typedef int* intpoint;
	struct str
	{
		int a;
		int b;
	};
	class p
	{
		public:
			void fun()
			{
				cout << "hello world" << endl;
			}
	};
};
int main()
{
	int a = 19;
	test::intpoint p = &a; //对于类中新类型的使用要通过::所属符 
	cout << *p << endl;
	
	test::str st1;
	st1.a = 15;
	cout << st1.a << endl;
	
	test::p p1;
	p1.fun(); 
	return 0;
}
   c.正因为类中使用新类型和类中使用静态成员的形式一样,所以当模板调用类中静态成员和新类型的时候会发生二义性,不知道模板到底是调用谁!
错误示例代码:
#include <iostream>

using namespace std;

class test
{
public :
	typedef int* intpoint;
};

template <class T>
void fun()
{
	T::intpoint a;//这里会发生二义性 
	test::intpoint b; //这就没有二义性 
}

int main()
{
	return 0;
} 
注意:上面代码中模板调用intpoint就发生了二义性,而类名调用intpoint就没有发生二义性,这个跟模板的特殊二次编译有关,当模板进行第一次语法编译的时候,不知道T是哪个具体的类,根本就不能确定intpoint是新类型还是静态成员。所以会发生二义性!
            针对这个问题,前辈们提出了typename这个关键字,用来区分是新类型还是静态成员,typename T;: intpoint a;  这样表示intpoint是新类型,如果没有typename关键字,就表示是静态成员,所以默认情况是当做静态成员进行处理,所以T:: intpoint a会编译出错。
            以前使用class关键字表示模板,是因为想少引入关键字,但是都已经引入了typename关键字了,就用typename表示模板吧!!!这就是typename关键字引入的过程!
示例代码:
#include <iostream>

using namespace std;

class test
{
public :
	typedef int* intpoint;
};

template <typename T>
void fun()
{
	typename T::intpoint a;//这里没有发生二义性 
	test::intpoint b; //这就没有二义性 
}

int main()
{
	return 0;
} 

2.一道坑爹的面试题:

 利用c++写个函数判断一个变量是否为指针?
 a.背景知识:当普通函数,函数模板,可变参数函数,三种发生函数重载的时候,c++编译器的匹配调用优先级是,普通函数>函数模板>可变参数函数。
示例代码:
#include<iostream>

using namespace std;

template <typename T>
void fun(T b)
{
	cout << "b is " << b << endl;
}
void fun(int a)
{
	cout << "a is " << a << endl;
}
void fun(...)
{
	cout << "void fun" << endl; 
}

int main()
{
	int a = 12;
	fun(a); //重载时 普通函数最优先 
	double b = 0.01;
	fun(b); //函数模板其次 
	
	fun(a,b); //最后是可变参数函数 
}

 
 b.所以通过函数模板与可变参数函数进行重载,就可以区分出那个变量是指针类型的!
示例代码:
#include <iostream>

using namespace std;

template <typename T>
bool checkpoint (T*) //这个函数模板 只接受指针类型的变量 
{
	cout << "true" << endl;
	return true;
}

bool checkpoint (...) //这个可变参数函数 接受除了指针类型以外的变量 
{
	cout << "false" << endl;
	return false;
}

int main()
{
	int a;
	checkpoint(a);
	
	int* b;
	checkpoint(b); 
	return 0;
} 
 c.虽然上面的解决方案已经解决这个问题,但是效率还不够高,因为它仍然在函数调用的建栈与退栈中浪费了时间。高效的解决方案如下:
#include <iostream>

using namespace std;

template <typename T>
int checkpoint (T*)
/*{
	cout << "checkpoint (T*)" << endl;
}*/; //这里完全可以是一个空函数  

char checkpoint (...)
/*{
	cout << "checkpoint (...)" << endl; 
}*/; //这里完全可以是一个空函数 

#define ispoint(v) (sizeof(checkpoint(v)) == sizeof(int))

int main()
{
	int a;
	cout << ispoint(a) << endl;
	
	int *p;
	cout << ispoint(p) << endl;
	return 0;
} 
注意:因为没有打算调用这个两个函数,所以这两个函数可以为空函数,代码中注释掉的函数内容是为了证明sizeof是真的没有调用函数用的。因为sizeof是在编译期间,进行处理的,sizeof(checkpoint(v))在编译期间就确定了调用那个函数,就可以通过函数返回值来判断调用那个函数,从而判断函数参数是否为指针。因为没有调用函数,只是在编译期间进行了判断,所以没有建栈退栈的时间!



c++学习笔记(17.专题四经典问题解析)

上一篇:c++连接WMI实例


下一篇:C++回顾之拷贝构造函数