9}
10};
11
12template
13classDerived:
publicBase
14{
15public:
16voidwork()
17{
18Base:
:
work(num);
19}
20};
21
22intmain()
23{
24Derivedd1;
25Derivedd2;
26Derivedd3;
27
28d1.work();
29d2.work();
30d3.work();
31return0;
32}
程序中work的参数版本是在一个Base类(基类)中的。
与Derived类一样,Base类也是一个类模板,但是与Derived类不一样的是,它参数化的仅仅是类型T,而没有num。
因此,所有持有一个给定类型的Derived将共享一个单一的Base类。
比如代码24~26行实例化的模板类都共享Base模板类,从而他们的成员函数都共享Base模板类中的work这个单一的拷贝。
答案:
模板的缺点:
不当地使用模板会导致代码膨胀,即二进制代码臃肿而松散,会严重影响程序的运行效率。
解决方法:
把C++模板中与参数无关的代码分离出来。
7如何建立一个双向链表?
考点:
双向链表的操作
出现频率:
★★★★
解析:
双向链表的定义如下:
1typedefstructDbNode
2{
3intdata;//节点数据
4DbNode*left;//前驱节点指针
5DbNode*right;//后继节点指针
6}DbNode;
(1)建立双向链表:
为方便,这里定义了三个函数:
qCreateNode()根据数据来创建一个节点,返回新创建的节点。
qCreateList()函数根据一个节点数据创建链表的表头,返回表头节点。
qAppendNode()函数总在表尾插入新节点(其内部调用CreateNode()生成节点),返回表头节点。
1//根据数据创建创建节点
2DbNode*CreateNode(intdata)
3{
4DbNode*pnode=(DbNode*)malloc(sizeof(DbNode));
5pnode->data=data;
6pnode->left=pnode->right=pnode;//创建新节点时
7//让其前驱和后继指针都指向自身
8returnpnode;
9}
10
11//创建链表
12DbNode*CreateList(inthead)//参数给出表头节点数据
13{//表头节点不作为存放有意义数据的节点
14DbNode*pnode=(DbNode*)malloc(sizeof(DbNode));
15pnode->data=head;
16pnode->left=pnode->right=pnode;
17
18returnpnode;
19}
20
21//插入新节点,总是在表尾插入;返回表头节点
22DbNode*AppendNode(DbNode*head,intdata)//参数1是链表的表头节点,
23{//参数2是要插入的节点,其数据为data
24DbNode*node=CreateNode(data);//创建数据为data的新节点
25DbNode*p=head,*q;
26
27while(p!
=NULL)
28{
29q=p;
30p=p->right;
31}
32q->right=node;
33node->left=q;
34
35returnhead;
36}
我们可以使用其中的CreateList()和AppendNode()来生成一个链表,下面是一个数据生成从0到9含有10个节点的循环链表。
1DbNode*head=CreateList(0);//生成表头,表头数据为0
2
3for(inti=1;i<10;i++)
4{
5head=AppendNode(head,i);//添加9个节点,数据为从1到9
6}
8考点:
函数模板与类模板的基本概念和区别
出现频率:
★★★
解析:
(1)什么是函数模板和类模板。
函数模板是一种抽象函数定义,它代表一类同构函数。
通过用户提供的具体参数,C++编译器在编译时刻能够将函数模板实例化,根据同一个模板创建出不同的具体函数,这些函数之间的不同之处主要在于函数内部一些数据类型的不同,而由模板创建的函数的使用方法与一般函数的使用方法相同。
函数模板的定义格式如下:
1templateFunction_Definition
其中,Function_Definition为函数定义;TYPE_LIST被称为类型参数表,是由—系列代表类型的标识符组成的,其间用逗号分隔,这些标识符的通常风格是由大写字母组成,ARG_LIST称为变量表,其中含有由逗号分隔开的多个变量说明,相当于一般函数定义中的形式参数。
前面例题中的max就是函数模板的一个例子,因此这里不再另外举例。
C++提供的类模板是一种更高层次的抽象的类定义,用于使用相同代码创建不同类模板的定义与函数模板的定义类似,只是把函数摸板中的函数定义部分换作类说明,并对类的成员函数进行定义即可。
在类说明中可以使用出现在TYPE_LIST中的各个类型标识以及出现在ARG_LIST中的各变量。
1template<<棋板参数表>>
2class<类模板名>
3{<类模板定义体>},
例如我们需要定义一个表示平面的点(Point)类,这个类有两个成员变量分别表示横坐标和纵坐标,并且这两个坐标的类型可以是int、float、double等等类型。
因此可能写出类似Point_int_int、Point_float_int、Point_float_float等这样的类。
通过类模板,我们只需要写一个类。
1#include
2usingnamespacestd;
3
4template
5classPoint_T
6{
7public:
8T1a;//成员a为T1类型
9T2b;//成员b为T2类型
10Point_T():
a(0),b(0){}//默认构造函数
11Point_T(T1ta,T2tb):
a(ta),b(tb){}//带参数的构造函数
12Point_T&operator=(Point_T&pt);//赋值函数
13friendPoint_Toperator+(Point_T&pt1,Point_T&pt2);//重载+
14};
15
16template
17Point_T&Point_T:
:
operator=(Point_T&pt)//赋值函数
18{
19this->a=pt.a;
20this->b=pt.b;
21return*this;
22}
23
24template
25Point_Toperator+(Point_T&pt1,Point_T&pt2)//重载+
26{
27Point_Ttemp;
28temp.a=pt1.a+pt2.a;//结果对象中的a和b分别为两个参数对象的各自a和b之和
29temp.b=pt1.b+pt2.b;
30returntemp;
31}
32
33template
34ostream&operator<<(ostream&out,Point_T&pt)//重载输出流操作符
35{
36out<<"("<37out<38returnout;
39}
40
41intmain()
42{
43Point_TintPt1(1,2);//T1和T2都是int
44Point_TintPt2(3,4);//T1和T2都是int
45Point_TfloatPt1(1.1f,2.2f);//T1和T2都是float
46Point_TfloatPt2(3.3f,4.4f);//T1和T2都是float
47
48Point_TintTotalPt;
49Point_TfloatTotalPt;
50
51intTotalPt=intPt1+intPt2;//类型为Point_T的对象相加
52floatTotalPt=floatPt1+floatPt2;//类型为Point_T的对象相加
53
54cout<55cout<56
57return0;
58}
Point_T类就是一个类模板,它的成员a和b分别为T1和T2类型,这里我们还实现了它的构造函数、赋值函数、“+”运算符的重载以及输出流操作符“<<”的重载。
使用Point_T类非常方便,我们可以进行各种类型的组合。
代码43、44行,定义了两个Point_T类的对象intPt1和intPt2,表明这两个对象的成员a和b都是int类型。
代码45、46行,定义了两个Point_T类的对象floatPt1和floatPt2,表明这两个对象的成员a和b都是float类型。
代码51行,对intPt1和intPt2进行对象加法,结果保存到intTotalPt中,此过程先调用“+”函数,再调用了“=”函数。
代码52行,与51行类似,只是相加的对象和结果对象都是Point_T类的对象。
代码54、55行,输出对象intTotalPt和floatTotalPt的内容。
可以看出,通过使用类模板Point_T我们可以创建不同的类,大大提高了代码的可维护性以及可重用性。
有一些概念需要区别:
函数模板与模板函数,类模板和模板类是不同的意思。
函数模板的重点是模板,它表示的是一个模板,用来生产函数。
例如前面例题的max是一个函数模板。
而模板函数的重点是函数,它表示的是由一个模板生成而来的函数。
例如max,max等都是模板函数。
类模板和模板类的区别与上面的类似,类模板用于生产类,例如Point_T就是一个类模板。
而模板类是由一个模板生成而来的类,例如Point_T和Point_T都是模板类。
(2)函数模板和类模板有什么区别。
在面试例题1的程序代码中,我们在使用函数模板max时不一定要必须指明T的类型,函数模板max的实例化是由编译程序在处理函数调用时自动完成的,当调用max(1,2)时自动生成实例max,而调用max(1.1f,2.2f)时自动生成实例max。
当然也可以显示指定T的类型。
对于本例题的类模板Point_T来说,其实例化必须被显示地指定,比如Point_T、Point_T。
答案:
函数模板是一种抽象函数定义,它代表一类同构函数。
类模板是一种更高层次的抽象的类定义。
函数模板的实例化是由编译程序在处理函数调用时自动完成的,而类模板的实例化必须由程序员在程序中显式地指定。
9约瑟夫问题的解答
考点:
循环链表的操作
出现频率:
★★★★
编号为1,2,....,N的N个人按顺时针方向围坐一圈,每人持有一个密码(正整数),一开始任选一个正整数作为报数上限值M,从第一个人开始按顺时针方向自1开始顺序报数,报到M时停止报数。
报M的人出列,将他的密码作为新的M值,从他在顺时针方向上的下一个人开始重新从1报数,如此下去,直至所有人全部出列为止。
试设计一个程序求出出列顺序。
解析:
显然当有人退出圆圈后,报数的工作要从下一个人开始继续,而剩下的人仍然是围成一个圆圈的,因此可以使用循环单链表,由于退出圆圈的工作对应着表中结点的删除操作,对于这种删除操作频繁的情况,选用效率较高的链表结构,为了程序指针每一次都指向一个具体的代表一个人的结点而不需要判断,链表不带头结点。
所以,对于所有人围成的圆圈所对应的数据结构采用一个不带头结点的循环链表来描述。
设头指针为p,并根据具体情况移动。
为了记录退出的人的先后顺序,采用一个顺序表进行存储。
程序结束后再输出依次退出的人的编号顺序。
由于只记录各个结点的data值就可以,所以定义一个整型一维数组。
如:
intquit[n];n为一个根据实际问题定义的一个足够大的整数。
程序代码如下:
1#include
2usingnamespacestd;
3
4/*结构体和函数声明*/
5typedefstructnode
6{
7intdata;
8node*next;
9}node;
10
11node*node_create(intn);
12
13//构造节点数量为n的单向循环链表
14node*node_create(intn)
15{
16node*pRet=NULL;
17
18if(0!
=n)
19{
20intn_idx=1;
21node*p_node=NULL;
22
23/*构造n个node*/
24p_node=newnode[n];
25if(NULL==p_node)//申请内存失败,返回NULL
26{
27returnNULL;
28}
29else
30{
31memset(p_node,0,n*sizeof(node));//初始化内存
32}
33pRet=p_node;
34while(n_idx35{//初始化链表的每个节点,从1到n
36p_node->data=n_idx;
37p_node->next=p_node+1;
38p_node=p_node->next;
39n_idx++;
40}
41p_node->data=n;
42p_node->next=pRet;
43}
44
45returnpRet;
46}
47
48intmain()
49{
50node*pList=NULL;
51node*pIter=NULL;;
52intn=20;
53intm=6;
54
55/*构造单向循环链表*/
56pList=node_create(n);
57
58/*Josephus循环取数*/
59pIter=pList;
60m%=n;
61while(pIter!
=pIter->next)
62{
63inti=1;
64
65/*取到第m-1个节点*/
66for(;i67{
68pIter=pIter->next;
69}
70
71/*输出第m个节点的值*/
72printf("%d",pIter->next->data);
73
74/*从链表中删除第m个节点*/
75pIter->next=pIter->next->next;
76pIter=pIter->next;
77}
78printf("%d",pIter->data);
79
80/*释放申请的空间*/
81delete[]pList;
82return0;