程序员面试精选.docx
- 文档编号:4696391
- 上传时间:2022-12-07
- 格式:DOCX
- 页数:23
- 大小:23.58KB
程序员面试精选.docx
《程序员面试精选.docx》由会员分享,可在线阅读,更多相关《程序员面试精选.docx(23页珍藏版)》请在冰豆网上搜索。
程序员面试精选
程序员经典
1双向链表的查找节点。
考点:
双向链表的操作
出现频率:
★★★★
解析:
使用right指针遍历,直至找到数据为data的节点,如果找到节点,返回节点,否则返回NULL。
1//查找节点,成功则返回满足条件的节点指针,否则返回NULL
2DbNode*FindNode(DbNode*head,intdata)//参数1是链表的表头节点
3{//参数2是要查找的节点,其数据为data
4DbNode*pnode=head;
5
6if(head==NULL)//链表为空时返回NULL
7{
8returnNULL;
9}
10
11/*找到数据或者到达链表末尾退出while循环*/
12while(pnode->right!
=NULL&&pnode->data!
=data)
13{
14pnode=pnode->right;//使用right指针遍历
15}
16
17//没有找到数据为data的节点,返回NULL
18if(pnode->right==NULL)
19{
20returnNULL;
21}
22
23returnpnode;
24}
2考点:
模板的特化的理解
出现频率:
★★★
解析:
模板的特化(templatespecialization)分为两类:
函数模板的特化和类模板的特化。
(1)函数模板的特化:
当函数模板需要对某些类型进行特别处理,称为函数模板的特化。
例如:
1boolIsEqual(Tt1,Tt2)
2{
3returnt1==t2;
4}
5
6intmain()
7{
8charstr1[]="Hello";
9charstr2[]="Hello";
10cout< 11cout< 12return0; 13} 代码11行比较字符串是否相等。 由于对于传入的参数是char*类型的,max函数模板只是简单的比较了传入参数的值,即两个指针是否相等,因此这里打印0。 显然,这与我们的初衷不符。 因此,max函数模板需要对char*类型进行特别处理,即特化: 1template<> 2boolIsEqual(char*t1,char*t2)//函数模板特化 3{ 4returnstrcmp(t1,t2)==0; 5} 这样,当IsEqual函数的参数类型为char*时,就会调用IsEqual特化的版本,而不会再由函数模板实例化。 (2)类模板的特化: 与函数模板类似,当类模板内需要对某些类型进行特别处理时,使用类模板的特化。 例如: 1template 2classcompare 3{ 4public: 5boolIsEqual(Tt1,Tt2) 6{ 7returnt1==t2; 8} 9}; 10 11intmain() 12{ 13charstr1[]="Hello"; 14charstr2[]="Hello"; 15comparec1; 16comparec2; 17cout< 18cout< 19return0; 20} 这里代码18行也是调用模板类compare的IsEqual进行两个字符串比较,显然这里存在的问题和上面函数模板中的一样,我们需要比较两个字符串的内容,而不是仅仅比较两个字符指针。 因此,需要使用类模板的特化: 1template<> 2classcompare//特化(char*) 3{ 4public: 5boolIsEqual(char*t1,char*t2) 6{ 7returnstrcmp(t1,t2)==0;//使用strcmp比较字符串 8} 9}; 注意: 进行类模板的特化时,需要特化所有的成员变量及成员函数。 3考点: 双向链表的操作 出现频率: ★★★★ 解析: 与测长的方法一样,使用right指针进行遍历。 1//打印整个链表 2voidPrintList(DbNode*head)//参数为链表的表头节点 3{ 4DbNode*pnode=NULL; 5 6if(head==NULL)//head为NULL表示链表空 7{ 8return; 9} 10pnode=head; 11while(pnode! =NULL) 12{ 13printf("%d",pnode->data); 14pnode=pnode->right;//使用right指针遍历 15} 16printf(""); 17} 4考点: 类模板的实例化的理解 出现频率: ★★★★ 1template 2classArray{ 3… 4}; 5voidfoo() 6{ 7Arrayarr1; 8Arrayarr4,arr5; 9Arrayarr2,arr3; 10Arrayarr6; 11… 12} HowmanyinstancesofthetemplateclassArraywillgetinstantiatedinsidethefunctionfoo() A3B6C4D1 解析: 模板类(templateclass)的实例个数是由类型参数的种类决定的。 代码7行和9行实例化的模板类都是Array,代码8行实例化的模板类是Array,代码10行实例化的模板类是Array。 一共是三个实例。 答案: A 5考点: 双向链表的操作 出现频率: ★★★★ 解析: 为了得到双向链表的长度,需要使用right指针进行遍历,直到得到NULL为止。 1//获取链表的长度 2intGetLength(DbNode*head)//参数为链表的表头节点 3{ 4intcount=1; 5DbNode*pnode=NULL; 6 7if(head==NULL)//head为NULL表示链表空 8{ 9return0; 10} 11pnode=head->right; 12while(pnode! =NULL) 13{ 14pnode=pnode->right;//使用right指针遍历 15count++; 16} 17 18returncount; 19} 更多精彩内容,请到“融智技术学苑rzchina” 使用模板有什么缺点? 如何避免? 6考点: 理解模板编程的缺陷 出现频率: ★★★ 解析: templates(模板)是节省时间和避免代码重复的极好方法,我们可以只输入一个类模板,就能让编译器实例化所需要的很多个特定类及函数。 类模板的成员函数只有被使用时才会被实例化,所以只有在每一个函数都在实际中被使用时,我们才会得到这些函数。 确实这是一个很重要的技术,但是如果不小心,使用模板可能会导致代码膨胀。 什么是代码膨胀? 请看下面的例子: 1template 2classA 3{ 4public: 5voidwork() 6{ 7cout<<"work()"< 8cout< 9} 10}; 11 12intmain() 13{ 14Av1; 15Av2; 16Av3; 17Av4; 18v1.work(); 19v2.work(); 20v3.work(); 21v4.work(); 22return0; 23} 类模板A取得一个类型参数T,并且它还有一个类型为int的参数,一个非类型参数(non-typeparameter),与类型参数相比,虽然非类型参数不是很通用,但他们是完全合法的。 在本例中,由于num的不同,代码14到17行的调用将会生成了三个A的实例,然后在18~21行又生成了不同的函数调用。 虽然这些函数做了相同的事情(打印一个“work()”和num),但他们却有不同的二进制代码。 这就是所说的由于模板导致的代码膨胀。 也就是说,虽然源代码看上去紧凑而整洁,但是目标代码却臃肿而松散,会严重影响程序的运行效率。 如何避免由于这种代码膨胀呢? 有一个原则,就是把C++模板中与参数无关的代码分离出来。 也就是让与参数无关的代码只有一份拷贝。 对类模板A可以进行如下地修改: 1template 2classBase 3{ 4public: 5voidwork(intnum) 6{ 7cout<<"work"; 8cout< 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_idx 35{//初始化链表的每个节点,从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(;i 67{ 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;
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- 程序员 面试 精选