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;

}

 

原文地址:https://www.cnblogs.com/free-1122/p/11336299.html

时间: 2024-07-30 13:31:56

C++--第24课 - 专题四经典问题解析的相关文章

C++--第14课 - 专题二经典问题解析

第14课 - 专题二经典问题解析 1. malloc与free和new与delete有什么区别? malloc和free是函数,new和delete是关键字. #include <cstdlib> #include <iostream> using namespace std; class Test { private: int i; public: Test() { cout<<"Test()"<<endl; i = 0; } Test

专题一经典问题解析-7

一.const和引用的疑惑 #include <stdio.h> int main() { const int x = 1; const int& rx = x; int& nrx = const_cast<int&>(rx); nrx = 5; printf("x = %d\n", x); printf("rx = %d\n", rx); printf("nrx = %d\n", nrx); pr

[C# 网络编程系列]专题四:自定义Web浏览器

转自:http://www.cnblogs.com/zhili/archive/2012/08/24/WebBrowser.html 前言: 前一个专题介绍了自定义的Web服务器,然而向Web服务器发出请求的正是本专题要介绍的Web浏览器,本专题通过简单自定义一个Web浏览器来简单介绍浏览器的工作原理,以及帮助一些初学者揭开浏览器这层神秘的面纱(以前总感觉这些应用感觉很深奥的,没想到自己也可以自定义一个浏览器出来),下面不啰嗦了,进入正题. 一.Web浏览器的介绍 Web浏览器是指可以显示Web

kuangbin专题专题四 Til the Cows Come Home POJ - 2387

题目链接:https://vjudge.net/problem/POJ-2387 题意:从编号为n的城市到编号为1的城市的最短路. 思路:dijkstra模板题,直接套板子,代码中我会带点注释给初学者看. 1 #include <iostream> 2 #include <cstring> 3 #include <algorithm> 4 #include <cstdio> 5 #include <string> 6 using namespac

UI标签库专题四:JEECG智能开发平台 Upload(上传标签)

?? 1. Upload(上传标签) 1.1.  参数 属性名 类型 描述 是否必须 默认值 id string 上传控件唯一标示 是 null name string 控件name 是 null formData string 上传文件提交后台的其他表单参数取ID 否 null uploader string 上传提交路径 是 null extend string 上传文件扩展名(可选类型组1,pic[*.jpg;*,jpeg;*.png;*.gif;*.bmp;*.ico;*.tif],2,

kuangbin专题四 : 最短路 I 题 Arbitrage

kuangbin专题四 : 最短路 I 题  Arbitrage POJ 2240 Arbitrage is the use of discrepancies in currency exchange rates to transform one unit of a currency into more than one unit of the same currency. For example, suppose that 1 US Dollar buys 0.5 British pound,

第三课 第四讲03_04_Linux用户及权限详解

第三课 第四讲03_04_Linux用户及权限详解1.库和进程是同级的.进程可以获取CPU时间,内存地址,调用各种文件2.权限:定义了计算机资源和服务的访问能力叫权限3.逻辑容器,用户 关联权限就是用户组.用于指派权限,不能独立登陆 4.文件属主,文件属组,其他,5.用户和组在计算机里面就是个标识符6.进程也有属主和属组进程的安全上下文(secure context),进程属主和资源属主7.文件r,w,xr:read可读,可用使用文件查看命令cat等命令查看文件内容w:write可写.可用使用文

IOS开发之——四种方法解析Jason数据(转)

本文将介绍TouchJson. SBJson .JSONKit 和 iOS5所支持的原生的json方法,解析国家气象局API,TouchJson和SBJson需要下载他们的库 TouchJson包下载: http://download.csdn.net/detail/enuola/4523169 SBJson 包下载: http://download.csdn.net/detail/enuola/4523177 JSONKit包下载:http://download.csdn.net/detail

Visual C NET数据库开发经典案例解析 附书光盘免费下载

为了让更多的朋友学得更快我共享出这代码(由于文件太大,我只上传了第二章人事管理的代码): 下载地址:http://www.cnblogs.com/Files/meta/Chap2.rar 配套光盘使用说明 (1)  本光盘是<Visual C#.NET数据库开发经典案例解析>的配套光盘,与图书一起发行,不得单独出售. (2)  本光盘的主要内容包括各章示例程序的代码及数据库文件.光盘的说明文件为Readme.txt,就是本文件. (3)  示例程序是按照它们出现的章顺序编排的. (4)  光盘