计算机/网安 面试例题
1. 程序设计基本概念
例题1:下面C++代码的输出结果是什么?
C/C++ code int i =1; void main() { int i =i; }
A. main()里的i是一个未定义值
B. main()里的i值为1
C. 编译器不允许这种写法
D. main()里的i值为0
解析:当面试者看到int i=1,i变量从声明的那一刻开始就是可见的了,main()里的i不是1,因为它和main()外的i无关,而是一个未定义值。
答案:A
例题2:以下代码的输出结果是什么?
#include <stdio.h> main() { int b=3 int arr[]={6,7,8,9,10}; sint *ptr=arr; *(ptr++)+=123; printf("%d,%d\n",*ptr,*(++ptr)); }
A.8 8
B.130 8
C.7 7
D.7 8
解析:C中printf计算参数时是从右到左压栈的。几个输出结果分别如下。
1.printf( "%d\n ",*ptr); 此时ptr应指向第一个元素6。
2.*(ptr++)+=123应为 *ptr=*ptr+123;ptr++t, 此时 ptr应指向第二个元素7。
3.printf( "%d\n ",*(ptr-1)); 输出第一个元素129,注意此时是经过计算的。printf( "%d\n ",*ptr); 输出第二个元素7,此时ptr还是指向第二个元素 7。
4.printf( “%d,%d\n “,*ptr,*(+ +pt)); 从右到左运算,第一个是(++ptr),也就是ptr++, *ptr=8,此时ptr指向第三个元素8,所以全部为8。
答案:A
例题3:下面两段程序有两种写法,你青睐哪种?为什么?
A.
//a is a variable 写法1: if( 'A'==a) { a++; } 写法2: if(a=='A' ){ a++; }
B.
写法1: for(i=0;i<8;i++) { X= i+Y+J*7; printf ("%d",x) ; } 写法2: S=Y+J*7; for(i=0;i<8;i++){ printf("%d",i+s); }
答案:
A:第一种写法‘A’==a比较好些。这时如果把“==”误写成“=”的话,因为编译器不允许对常量赋值,就可以检查到错误。
B:第二种写法好一些,将部分加法运算放到了循环体外,提高了效率。缺点是程序不够简洁。
例题4:在C++程序中调用被C编译器编译后的函数,为什么要加extern"C"?
答案: C++语言支持函数重载,C语言不支持函数重载。函数被C++编译后在库中的名字与C语言的不同。
假设某个函数的原型为void foo(int x, int y),该函数被C编译器编译后在库中的名字为_foo, 而C++编译器则会产生像_foo_int_int之类的名字。
C++提供了C连接交换指定符号extern "C"解决名字匹配问题。
例题5:评价一下C与C++的各自特点。如果一个程序既需要大量运算,又要有一个好的用户界面,还需要与其他软件大量交流,应该怎样选择合适的语言?
答案: C是一种结构化语言,重点在于算法和数据结构。C程序的设计首先考虑的是如何通过一个过程,对输入(或环境条件)进行运算处理得到输出(或实现过程(事务)控制)。
而对于C++,首先考虑的是如何构造一个对象模型,让这个模型能够契合与之对应的问题域,这样就可以通过获取对象的状态信息得到输出或实现过程(事务)控制。
对于大规模数值运算,C/C++ 和 Java/.NET 之间没有明显的性能差异。不过,如果运算涉及向量计算、矩阵运算,可以使用FORTRAN或者MATLAB编写计算组件(如COM)。
大规模用户界面相关的软件可以考虑使用.NET进行开发(Windows 环境下),.NET同COM之间的互操作十分容易,同时.NET 对数据库访问的支持也相当好。
2. 指针与引用
例题6:下面的程序哪里有错?
#include <iostream>
using namespace std; int main() { int iv; //1 int iv2=1024; //2 int iv3=999; //3 int &reiv //4; int &reiv2 = iv; //5 int &reiv3 iv; //6 int *pi; //7 *pi=5; //8 pi=&iv3; //9 const dobule di ; //10 const double maxWage. =10.0; //11 const double minWage =0.5; const double *pc = &maxWage; //12 cout << pi; return O; }
答案:
1.正确,很正常地声明了一个整型变量。
2.正确,很正常地声明了一个整型变量,同时初始化这个变量。
3.正确,理由同上。
4. 错误,声明了一个引用,但引用不能为空,必须同时初始化。
5.正确,声明了一个引用reiv2,同时初始化了,也就是reiv2是iv的别名。
6. 正确,理由同上。
7.正确,声明了一个整数指针,但是并没有定义这个指针所指向的地址。 8.错误,整数指针pi并没有指向实际的地址。在这种情况下就给它赋值是错误的,因为赋的值不知道该放到哪里去,从而造成错误。
9. 正确,整数指针pi指向iv3的实际地址。
10.错误,const常量赋值时,必须同时初始化。
11.正确,const常量赋值并同时初始化。
12.正确,const 常量指针赋值并同时初始化。
例题7:运行“测试”后会发生什么?
#include <iostream.h> void GetMemory(char *p, int num) { p = (char *)malloc(sizeof(char) * num); } int main() { char *str = NULL; GetMemory(str, 100); strcpy(str, "hello"); return 0; }
解析:由于void GetMemory(char *p, int num)中的*p实际上是主函数中的一个str的一个副本,编译器总是要为函数的每个参数制作临时副本。在本例中,p申请了新的内存,只是把p的内存地址改变了,但str丝毫未变。因为GetMemory没有返回值,因此str并不指向p所申请的那段内存,所以函数GetMemory并不能输出任何东西。事实上,每执行一次GetMemory就会申请一块内存,但申请的内存却不能有效释放,结果是内存一直被独占,最终造成内存泄露。
答案:程序崩溃。因为GetMemory并不能传递动态内存,Test函数中的str一直都是NULL。
例题8:句柄和指针的区别和联系是什么?
解析:句柄是一个32位的整数,实际上是Windows在内存中维护的一个对象(窗口等)内存物理地址列表的整数索引。
因为Windows的内存管理经常会将当前空闲对象的内存释放掉,当需要访问时再重新提交到物理内存,所以对象的物理地址是变化的,不允许程序直接通过物理地址来访问对象。
程序将想访问的对象的句柄传递给系统,系统根据句柄检索自己维护的对象列表就能知道程序想访问的对象及其物理地址了。
句柄是一种指向指针的指针。我们知道,所谓指针是一种内存地址。应用程序启动后,组成这个程序的各对象是驻留在内存的。如果简单地理解,似乎我们只要获知这个内存的首地址,就可以随时用这个地址访问对象。
但是,如果真的这样认为,那么就大错特错了。我们知道,Windows 是个以虚拟内存为基础的操作系统。在这种系统环境下,Windows内存管理器经常在内存中来回移动对象,以此来满足各种应用程序的内存需要。
对象被移动意味着它的地址变化了。如果地址总是如此变化,我们该到哪里去找该对象呢?为了解决这个问题,Windows操作系统为各应用程序腾出一些内存地址,用来专门登记各应用对象在内存中的地址变化,而这个地址(存储单元的位置)本身是不变的。Windows内存管理器移动对象在内存中的位置后,把对象新的地址告知这个句柄地址来保存。这样我们只需记住这个句柄地址就可以间接地知道对象具体在内存中的哪个位置。这个地址是在对象装载(Load)时由系统分配的,当系统卸载时(Unload)又释放给系统。但是,必须注意的是,程序每次重新启动,系统不能保证分配给这个程序的句柄还是原来的那个句柄,而且绝大多数情况下的确不一样。假如我们把进入电影院看电影看成是一个应用程序的启动运行,那么系统给应用程序分配的句柄总是不一样,这和每次电影院售给我们的门票总是不同的座位是一样的道理。
HDC是设备描述表句柄。CDC是设备描述表类。用GetSafeHwnd和FromHandle可以互相转换。
答案:句柄和指针其实是两个截然不同的概念。Windows 系统用句柄标记系统资源,隐藏系统的信息。你只要知道有这个东西,然后去调用就行了,它是个32bit的uint。指针则标记某个物理内存地址。
例题9:指针和引用的差别?
答案:
(1)非空区别。在任何情况下都不能使用指向空值的引用。一个引用必须总是指向某个对象。因此如果你使用一个变量并让它指向一个对象,但是该变量在某些时候也可能不指向任何对象,这时你应该把变量声明为指针,因为这样你可以赋空值给该变量。相反,如果变量肯定指向一个对象,例如你的设计不允许变量为空,这时你就可以把变量声明为引用。不存在指向空值的引用这个事实意味着使用引用的代码效率比使用指针要高。
(2)合法性区别。在使用引用之前不需要测试它的合法性。相反,指针则应该总是被测试,防止其为空。
(3)可修改区别。指针与引用的另一个重要的区别是指针可以被重新赋值以指向另一个不同的对象。但是引用则总是指向在初始化时被指定的对象,以后不能改变,但是指定的对象其内容可以改变。
(4)应用区别。总的来说,在以下情况下应该使用指针:一是考虑到存在不指向任何对象的可能(在这种情况下,能够设置指针为空),二是需要能够在不同的时刻指向不同的对象(在这种情况下,你能改变指针的指向)。如果总是指向一个对象并且一旦指向一个对象后就不会改变指向,那么应该使用引用。
例题10:以下描述正确的是( )
A.函数的形参在函数未调用时预分配存储空间。
B.若函数的定义出现在主函数之前,则可以不必再说明。
C.若一个函数没有return 语句,则什么值都不返回。
D.一般来说,函数的形参和实参的类型应该一致。
解析:
A. 错误的,调用到实参才会分配空间。
B:函数需要在它被调用之前被声明,这个跟main()函数无关。
C:错误的,在主函数main中可以不写return语句,因为编译器会隐式返回0;但是在一般函数中没return语句是不行的。
D:正确
答案:D
例题11:C++中有了malloc/free,为什么还需要new/delete?
答案: malloc与free是C++/C语言的标准库函数,new/delete是C++的运算符。它们都可用于申请动态内存和释放内存。
对于非内部数据类型的对象而言,只用malloc/free无法满足动态对象的要求。对象在创建的同时要自动执行构造函数,对象在消亡之前要自动执行析构函数。
由于malloc/free 是库函数而不是运算符,不在编译器控制权限之内,不能够把执行构造函数和析构函数的任务强加于malloc/free。
因此C+ +语言需要一个能完成动态内存分配和初始化工作的运算符new,以及一个能完成清理与释放内存工作的运算符delete。new/delete不是库函数,而是运算符。
3. 面向对象
例题12:以下选项中哪个不是面向对象设计()
A:继承
B:里氏代换原则
C:开闭原则
D:多态
E:防御式编程
解析:面向对象设计的三原则:封装,继承,多态。
答案:E
例题13:C++中的空类默认产生哪些类成员函数?
class Empty
{
Public;
};
解析:类的概念问题。
答案:对于一个空类,编译器默认产生4个成员函数,即默认构造函数、析构函数、复制构造函数和赋值函数。
例题14:structure是否可以constructor、destructor 及成员函数?如果可以,那么structure和class还有区别吗?
答案:区别是class中的变量默认是 private,struct中的变量默认是public。struct可以有构造函数、析构函数,之间也可以继承。
C++中的struc其实和class意义一样,唯一不同的就是struct 里面默认的访问控制是public,class中默认的访问控制是private。C++中存在struct关键字的唯一意义就是为了让C程序员有个归属感,是为了让C++编译器兼容以前用C开发的项目。
例题15:析构函数可以为virtual型,构造函数则不能。那么为什么构造函数不能为虚呢?
答案:虚函数采用一种虚调用的办法。虚调用是种可以在只有部分信息的情况下工作的机制,特别允许我们调用一个只知道接口而不知道其准确对象类型的函数。但是如果要创建一个对象,你势必要知道对象的准确类型,因此构造函数不能为虚。
例题16:如果虚函数非常有效,我们是否可以把每个函数都声明为虚函数?
答案:不行,这是因为虚函数是有代价的:由于每个虚函数的对象都必须维护一个v表,因此在使用虚函数的时候都会产生一个系统开销。如果仅是一个很小的类,且不想派生其他类,那么根本没必要使用虚函数。
例题17:下面关于复制构造函数的说法哪一个是正确的?
A.给每一个对象复制一个构造函数。
B.有一个默认的复制构造函数。
C.不能复制队列。
D.以上结果都正确。
答案:B
例题18:下面所列举的类,哪个不需要复制构造函数?
一个矩阵类:动态分配,对象的建立是利用构造函数,删除是利用析构函数。
A.一个花名册类:每一个对象对照着唯一的ID。
B.一个word类:对象是字符串类和模板类。
C.一个图书馆类:由一系列书籍对象构成。
解析:
按照题意,寻找一个不需要复制构造函数的类。
A选项要定义复制构造函数。
B选项中,不自定义复制构造函数的话,势必造成两个对象的ID不唯一。至于说自定义了复制构造函数之后,如何保证新对象的ID。当然语义上有损失,不是完全意义上的复制,但在这儿只能在保持语义和实现目的之间来一个折中。
选C的原因是使用默认的复制构造,string子对象和vector子对象的类都是成熟的类,都有合适的赋值操作,复制构造函数以避免“浅复制”问题。 D选项显然是定义复制构造函数。
答案:C
例题19:什么是多态?
答案:开门,开窗户,开电视。在这里的“开”就是多态!
多态性可以简单地概括为“一个接口,多种方法”,在程序运行的过程中才决定调用的函数。多态性是面向对象编程领域的核心概念。
多态(Polymorphisn),按字面的意思就是“多种形状”。多态性是允许你将父对象设置成为和它的一个或更多的子对象相等的技术,赋值之后,父对象就可以根据当前赋值给它的子对象的特性以不同的方式运作。简单地说就是,允许将子类类型的指针赋值给父类类型的指针。
多态性在Object Pascal和C++中都是通过虚函数(Virtual Function)实现的。
4. 继承与接口
例题20:公有继承和私有继承的区别是什么?
A.没有区别。
B.私有继承使对象都可以继承,只是不能访问。
C.私有继承使父类中的函数转化成私有。
D.私有继承使对象不能被派生类的子类访问。
解析:A肯定错,因为子类只能继承父类的protected和public,所以B也是错误的。
C的叙述不全面,而且父类可能有自己的私有方法成员,所以也是错误的。
答案:D
例题21:一个类有5个虚方法,下列说法正确的是哪项?
A.类中的每个对象都有第一个虚方法的地址,每一个方法都有下一个虚方法的地址。
B.类中的每个对象都有一个链表用来保存虚方法地址。
C.类中的每个对象都保存5个虚方法的地址。
D.类中的每个对象有一个结构用来保存虚方法地址。
解析:每个对象里有虚表指针,指向虚表,虚表里存放了虚函数的地址。
虚函数表是顺序存放虚函数地址的,不需要用到链表(link list)。
答案:B
例题22:什么虚函数效率低?
答案:因为虚函数需要一次间接的寻址,而一般的函数可以在编译时定位到函数的地址。虚函数(动态类型调用)是要根据某个指针定位到函数的地址。多增加了一个过程,效率肯定会低一些,但带来了运行时的多态。
例题23:请评价多重继承的优点和缺陷。
答案:多重继承在语言上并没有什么很严重的问题,但是标准本身只对语义做了规定,而对编译器的细节没有做规定。所以在使用时(即使是继承),最好不要对内存布局等有什么假设。此类的问题还有虚析构函数等。为了避免由此带来的复杂性,通常推荐使用复合。
(1)多重继承本身并没有问题,如果运用得当可以收到事半功倍的效果。不过大多数系统的类层次往往有一个公共的基类,就像MFC中的Cobject,或Java 中的Object。而这样的结构如果使用多重继承,稍有不慎,将会出现一个严重现象——菱形继承,这样的继承方式会使得类的访问结构非常复杂。但并非不可处理,可以用virtual 继承(并非唯一的方法)及Loki库中的多继承框架来掩盖这些复杂性。
(2)从哲学上来说,C++多重继承必须要存在,这个世界本来就不是单根的。从实际用途上来说,多重继承不是必需的,但这个世界上有多少东西是必需的呢?对象不过是一组有意义的数据集合及其上的一组有意义的操作,虚函数(晚期绑定)也不过是一堆函数人口表,重载也不过是函数名扩展,这些东西都不是必需的,而且对它们的不当使用都会带来问题。但是没有这些东西行吗?很显然,不行。
(3)多重继承在面向对象理论中并非是必要的——因为它不提供新的语义,可以通过单继承与复合结构来取代。而Java则放弃了多重继承,使用简单的interface取代。多重继承是把双刃剑,应该正确地对待。况且,它不像got,不破坏面向对象语义。跟其他任何威力强大的东西一样,用好了会带来代码的极大精简,用坏了那就不用多说了。
C++是为实用而设计的,在语言里有很多东西存在着各种各样的“缺陷”。
所以,对于这种有“缺陷”的东西,它的优劣就要看使用它的人。C++不回避问题,它只是把问题留给使用者,从而给大家更多的自由。像Ada、Pascal 这类定义严格的语言,从语法上回避了问题,但并不是真正解决了问题,而使人做很多事时束手束脚(当然,习惯了就好了)。
(4)多重继承本身并不复杂,对象布局也不混乱,语言中都有明确的定义。真正复杂的是使用了运行时多态(virtual)的多重继承(因为语言对于多态的实现没有明确的定义)。为什么非要说多重继承不好呢?如果这样的话,指针不是更容易出错,运行时多态不是更不好理解吗?
因为C++中没有interface 这个关键字,所以不存在所谓的“接口”技术。但是C++可以很轻松地做到这样的模拟,因为C++中的不定义属性的抽象类就是接口。
(5)要了解C++,就要明白有很多概念是C++试图考虑但是最终放弃的设计。你会发现很多Java和C#中的东西都是C++考虑后放弃的。不是说这些东西不好,而是在C++中它将破坏C++作为一个整体的和谐性,或者C++并不需要这样的东西。举一个例子来说明,C#中有一个关键字base用来表示该类的父类,C++却没有对应的关键字。为什么没有?其实C++中曾经有人提议用一个类似的关键字inherited,来表示被继承的类,即父类。这样一个好的建议为什么没有被采纳呢?因为这样的关键字既不必需又不充分。不必需是因为C++有一个typedef * inherited,不充分是因为有多个基类,你不可能知道inherited指的是哪个基类。
很多其他语言中存在的时髦的东西在C++中都没有,这之中有的是待改进的地方,有的是不需要,我们不能一概而论, 需要具体问题具体分析。
例题24:什么是虚指针?
答案:虚指针或虚函数指针是一个虚函数的实现细节。带有虚函数的类中的每一个对象都有一个虚指针指向该类的虚函数表。
例题25:在分布式系统中,不适用RTTI的一个合理解释是?
A.RTTI太慢了。
B.RTTI不是一个标准行为。
C.RTTI行为不可预期及缺乏扩展性。
D.RTTI函数在运行时会失败。
解析:C++引入中的每个特性,都是从程序员平时生活中逐渐精化而来的。在不正确的场合使用它们必然会引起逻辑、行为和性能上的问题。对于上述特性,应该只在必要、合理的前提下才使用。
C++引入的额外开销体现在以下两方面。
(1)编译时开销
模板、类层次结构、强类型检查等新特性,以及大量使用了这些新特性的C++模板、算法库都明显地增加了C++编译器的负担。但是应当看到,这些新机能在不增加程序执行效率的前提下,明显降低了广大C++程序员的工作量。
(2)运行时开销
运行时开销恐怕是程序员最关心的问题之一了。 相对于传统C程序而言,C++中有可能引入额外运行时开销特性包括:虚基类、虚函数、RTTI(dynamic_cast和typeid)、异常、对象的构造和析构。
虚基类,从直接虚继承的子类中访问虚基类的数据成员或其虚函数时,将增加两次指针引用(大部分情况下可以优化为一次)和一次整型加法的时间开销。定义一个虚基类表,定义若干虚基类表指针的空间开销。
虚函数的运行开销有进行整型加法和指针引用的时间开销。定义一个虚表,定义若干个(大部分情况下是一个)虚表指针的空间开销。
RTTI的运行开销主要有进行整型比较和取址操作(可能还会有两次整形加法)所增加的时间开销。定义一个type_ info 对象(包括类型ID和类名称)的空间开销。
"dynamic cast"用于在类层次结构中漫游,对指针或引用进行自由的向上、向下或交叉转化。“typeid”则用于获取一个对象或引用的确切类型。
一般地讲,能用虚函数解决的问题就不要用"dynamic cast",能够用“dynamic_cast”解决的就不要用“typeid”。
关于异常,对于几乎所有编译器来说,在正常情况(未抛出异常)下,try块中的代码执行效率和普通代码一样高,而且由于不再需要使用传统上通过返回值或函数调用来判断错误的方式,代码的实际执行效率还会进一步提高。
抛出和捕捉异常的开销也只是在某些情况下会高于参数返回和函数调用的开销。
关于构造和析构,开销也不总是存在的。对于不需要初始化/ 销毁的类型,并没有构造和析构的开销,相反对于那些需要初始化/销毁的类型来说,即使用传统的C方式实现,也至少需要与之相当的开销。
实事求是地讲,RTTI是有用的。但因为一些理论上及方法论上的原因,它破坏了面向对象的纯洁性。
首先,它破坏了抽象,使本来不应该被使用的方法和属性被不正确地使用。其次,因为运行时类型的不确定性,它把程序变得更脆弱。第三点,也是最重要的一点,它使程序缺乏扩展性。当加入了一个新的类型时,你也许需要仔细阅读你的dynamic_cast或instanceof的代码,必要时改动它们,以保证这个新的类型的加入不会导致问题。而在这个过程中,编译器将不会给你任何帮助。
很多人一提到RTTI,总是侧重于它的运行时的开销。但是,相比于方法论上的缺点,这点运行时的开销真是无足轻重的。
总的来说,RTTI因为它的方法论上的一些缺点,它必须被非常谨慎地使用。今天面向对象语言的类型系统中的很多东西就是产生于避免RTTI的各种努力。
答案:C
例题26:一个参数可以既是const又是volatile吗?解释为什么。
答案:第一个问题:可以。一个例子就是只读的状态寄存器。它是volatile,因为它可能被意想不到地改变;它又是const,因为程序不应该试图去修改它。
第二个问题:可以。尽管这并不很正常。一个例子是当一个中断服务子程序修改一个指向一个buffer的指针时。
例题27:十进制数-10的三进制4位数补码形式是____。
A.0101
B.1010
C.2121
D.2122
解析:负数的补码:对于二进制而言,-10的补码为-(2^8-|-10|)=-(256-10)=-246=11110110
同理,4位三进制的补码:10的补码为3^4-|-10|=71=2212。
答案:D
5. 树、图、哈希表
例题28:在百度或淘宝搜索时,每输入字符都会出现搜索建议,例如输入“北京”,搜索框下面会以北京为前缀,展示“北京爱情故事”“北京公交”“北京医院”等搜索词。实现这类技术后台所采用的数据结构是什么?
答案:Trie 树,又称单词查找树、字典树,是一种树形结构,是一种哈希树的变种,是一种用于快速检索的多叉树结构。典型应用是用于统计和排序大量的字符串(但不仅限于字符串),所以经常被搜索引擎系统用于文本词频统计。
它的优点是:最大限度地减少无谓的字符串比较,查询效率比哈希表高。Trie 树的核心思想是空间换时间。利用字符串的公共前缀来降低查询时间的开销以达到提高效率的目的。
对于搜索引擎,一般会保留一个单词查找树的前N个字(全球或最近热门使用的);对于每个用户,保持Trie树最近前N个字为该用户使用的结果。
用户点击任何搜索结果后,Trie 树可以非常迅速并异步获取完整的部分/模糊查找,然后预取数据,再用一个Web应用程序发送一个较小的组结果到浏览器。
例题29:用二进制来编码字符串“abcdabaa”,需要能够根据编码,解码回原来的字符串,最少需要多长的二进制字符串?
A.12
B.14
C.18
D.24
解析:哈夫曼编码问题:字符串“abcdabaa”有4个a、2个b、1个C、1个d。构造哈夫曼树如下所示。
a编码0(1位),b编码10(2位),c编码110(3位),d编码111(3位)。这个字符串的总长度为: 1*4+2*2+3*1+3*1=14。
答案:B
6. 字符串
例题30:已知strcpy函数的原型是char *strcpy(char *strDest,const char *strSrc);其中strDest是目的字符串,strSrc是源字符串。不调用C++/C的字符串库函数, 请编写函数strcpy。 strcpy能把strSrc的内容复制到strDest,为什么还要char *类型的返回值?
解析:先来看第一个问题,实现一个字符串复制的函数,基本思路是使用两个指针指向新旧字符串,然后进行逐一复制。
复制过程中我们需要注意,字符串的截止条件,并且需要考虑传入参数的合法性判断,对意外情况作出处理。
这个题目算是比较简单的,只要仔细注意上边的两个问题即可。
答案:
char *strcpy(char *strDest, const char *strSrc) { if(strDest == NULL || strSrc== NULL) { return NULL; } char *p = strDest; //也可以用while循环 for(;*strSrc!='\0';strDest++,strSrc++){ *strDest= *strSrc; } *strDest= '\0';//复制完毕之后一定要加一个结束符号 return p; }
例题31:怎样将整数转换成字符串数,并且不用函数itoa?
答案:
#include <iostream> using namespace std; int main () { int num =12345,i=0,j=0; char temp[7],str[7]; while(num) { temp[i]=num%10+'0'; //将整数num从后往前的每一位数转换成char保存在temp中 i++; num=num/10; } temp[i]=0; cout<<"temp:"<<temp<<endl; i=i-1;//反转temp while (i>=0) { str[j++]=temp[i--]; } str[j]=0; cout<<"string:"<<str<<endl; return 0; } 如果可以使用 itoa函数的话,则十分简单,如下所示。 #include <iostream> #include <stdlib.h> using namespace std; //使用itoa函数 int main () { int num=12345; char str[7]; itoa(num,str,10); cout<<"integer:"<<num<<endl<<"string:"<<str<<endl; return 0; }
例题32:模拟实现strstr。(找子串)
答案:
int strSt r(stri ng haystack, string ne edle) { int m = haystack.size(), n = needle.size(); if (n == 0) return 0; for (int i = 0; i < m - n + 1;i++) { int j = 0; while(haystack[i + j] == needle[j]) { j++; if (j == n) return i; } j++; } return -1; }
例题33:模拟实现atoi。
答案:
int my_atoi(const char* str) { int num = 0; int sign = 1; const int n = strlen(str); int i = 0; while (str[i] == ' ' && i < n) i++; if (str[i] == '+') i++; else if (str[i] == '-') { sign = -1; i++; } for (; i < n; i++) { if (str[i] < '0' || str[i] > '9') break; if (num > INT_MAX / 10 || (num == INT_MAX / 10 && (str[i] - '0') > INT_MAX % 10)) return sign == -1 ? INT_MIN : INT_MAX; num = num * 10 + str[i] - '0'; } return num * sign; }
例题34:找出最长回文子串。
解析:本题的解决方法是从头到尾地遍历字符串S,以每个字符为中心,向两端不停地扩展,同时判断以当前字符为中心的两端字符是否相等,在判断的过程中找到最大的回文串长度,并记录下回文串开始的最左端,这样就找到了最大的回文串。但是这样做就遇到了一个问题:“abcab”这种个数为奇数的回文串可以计算出来,但是若是“abba”这样个数为偶数的情况,该如何计算?遇到这种情况,则可以判断相邻两字符是否相等来判断是否为回文串,针对s=“abba”,我们发现 i=1时,s[i]==s[i+1],然后同时向两端扩,发现s[0]=s[2],这样该串也为回文串,所以,在判断以某字符为中心的时候,要分两种情况,即回文串中字符的个数为奇数和为偶数的情况。
时间复杂度是O(n*n)。
答案:
//最长回文串 string longestPalindrome(string s) { string res = ""; int len = s.size(); if (len == 1) return s; int maxlen = 0, curlen = 0, sbegin; int left, right; for (int i = 0; i < len; ++i) { if (len % 2 == 0) { left = i - 1; right = i + 1; } else { left = i; right = i + 1; } while (left >= 0 && right < len && s[left] == s[right]) { curlen = right - left; if (curlen > maxlen) { maxlen = curlen; sbegin = left; } left--; right++; } } res = s.substr(sbegin, maxlen + 1); //substring()为前闭后开 return res; }
7. 软件测试
例题35:自动化测试为何重要?
解析: 自动化测试从根本上提高QA们的职业素质,让QA们彻底摆脱重复繁重的测试工作,而更着重在QA的流程上已经完成项目质量的重复保证上。此外某些人对QA有些偏见:对QA的普遍认识就是只是测试而已。因为他们能看到QA最直接的劳动就是在反反复复勤勤恳恳地测试。
答案:自动化测试可以让测试人员从枯燥无味的手工重复性测试中解放出来,并且提高工作效率,通过自动化测试结果来分析功能和性能上的缺陷。
例题36:描述一个测试结束的准则。
答案:一个测试结束的标准是已提交的bug是否已经全部解决并已验证关闭。一般来说,bug验证率在95%以上,并且没有大的影响功能的bug处于未解决状态,就可以测试通过。
例题37:在一个测试计划中能包含哪些内容,如可用的人力资源?
答案:在一个测试计划中可以包含需要测试的产品的特点和主要功能模块,列出需要测试的功能点,并标明侧重点;测试的策略和记录(测试工具的确认,测试用例等文档模板,测试方法的确定);测试资源配置(确定测试每一阶段的任务和所需资源)。
例题38:请描述功能测试和可用性测试之间的区别。
解析:本题涉及几个测试的重要概念:
Functional testing(功能测试),也称为behavioral testing(行为测试),根据产品特征、操作描述和用户方案,测试一个产品的特性和可操作行为以确定它们满足设计需求。
本地化软件的功能测试,用于验证应用程序或网站对目标用户能正确工作。使用适当的平台、浏览器和测试脚本,以保证目标用户的体验将足够好,就像应用程序是专门为该市场开发的一样。
功能测试也叫黑盒子测试或数据驱动测试,只需考虑各个功能,不需要考虑整个软件的内部结构及代码。一般从软件产品的界面、架构出发,按照需求编写出来的测试用例,输人数据在预期结果和实际结果之间进行评测,进而提出使产品更加符合用户使用的要求。
可用性测试是用户在和系统(网站、软件应用程序、移动技术或任何用户操作的设备)交互时对用户体验质量的度量。可用性(Usability)是交互式IT产品/系统的重要质量指标,指的是产品对用户来说有效、易学、高效、好记、少错和令人满意的程度,即用户能否用产品完成他的任务?效率如何?主观感受怎样?实际上是从用户角度所看到的产品质量,是产品竞争力的核心。
答案:功能测试主要是黑盒测试,由测试人员进行,主要验证产品是否符合需求设计的要求;可用性测试主要是由用户(或者测试人员模拟用户行为)来进行的测试,主要是对产品的易用性进行测试,包括有效性(effectiveness)、效率(efficiency)和用户主观满意度(satisfaction)。其中有效性指用户完成特定任务和达到特定目标时所具有的正确和完整程度;效率指用户完成任务的正确和完整程度与所使用资源(如时间)之间的比率,满意度指用户在使用产品过程中所感受到的主观满意和接受程度。
例题39:黑盒测试和白盒测试的区别。
答案:白盒测试是通过程序的源代码进行测试而不使用用户界面。这种类型的测试需要从代码句法发现内部代码在算法、溢出、路径、条件等中的缺点或者错误,进而加以修正。
黑盒测试是通过使用整个软件或某种软件功能来严格进行测试,而并不检查程序的源代码,也不很清楚地了解该软件的源代码程序具体是怎么样设计的。
8. 操作系统
例题40:试解释操作系统原理中的作业、进程、线程、管程各自的定义。
答案:作业:用户在一次解题或一个事务处理过程中要求计算机系统所做工作的集合。它包括用户程序、所需要的数据及控制命令等。作业是由一系列有序的步骤组成的。
进程:一个程序在一个数据集合上的一次运行过程。所以一个程序在不同数据集合上运行,乃至一个程序在同样数据集合上的多次运行都是不同的进程。
线程 : 线程是进程中的一个实体,它是被系统独立调度和执行的基本单位。
管程:管程实际上是定义了一个数据结构和在该数据结构上的能为并发进程所执行的一组操作,这组操作能同步进程和改变管程中的数据。
例题41:进程间的通信如何实现?
答案:现在最常用的进程间通信的方式有信号、信号量、消息队列、共享内存。
所谓进程通信,就是不同进程之间进行一些“接触”。这种接触有简单,也有复杂。机制不同,复杂度也不一样。通信是一个广义上的意义,不仅仅指传递一些message。它们的使用方法是基本相同的,所以只要掌握了一种使用方法,然后记住其他的使用方法就可以了。信号和信号量是不同的,它们虽然都可用来实现同步和互斥,但前者是使用信号处理器来进行的,后者是使用P、V操作来实现的。消息队列是比较高级的一种进程间通 信方法,因为它真的可以在进程间传送message,连传送一个“I seek you”都可以。
一个消息队列可以被多个进程所共享(IPC就是在这个基础上进行的);如果一个进程的消息太多,一个消息队列放不下,也可以用多于一个的消息队列(不过可能管理会比较复杂)。共享消息队列的进程所发送的消息中除了message 本身外还有一个标志,这个标志可以指明该消息将由哪个进程或者是哪类进程接受。每一个共享消息队列的进程针对这个队列也有自己的标志,可以用来声明自己的身份。
例题42:在Windows编程中互斥器(mutex)的作用和临界区(critical section)类似,请说一下二者间的主要区别。
解析:多线程编程问题。
答案:两者的区别是mutex可以用于进程之间互斥,critical section是线程之间的互斥。
例题43:请描述进程和线程的差别。
答案:进程是程序的一次执行。线程可以理解为进程中执行的一段程序片段。 在一个多任务环境中,下面的概念可以帮助我们理解两者间的差别。
进程间是独立的,这表现在内存空间、上下文环境上;线程运行在进程空间内。一般来讲(不使用特殊技术),进程无法突破进程边界存取其他进程内的存储空间;而线程由于处于进程空间内,所以同一进程所产生的线程共享同一内存空间。
同一进程中的两段代码不能够同时执行,除非引入线程。
线程是属于进程的,当进程退出时该进程所产生的线程都会被强制退出并清除。线程占用的资源要少于进程所占用的资源。进程和线程都可以有优先级。
进程间可以通过IPC通信,但线程不可以。
例题44:下面哪个选项不是PE文件?
A. EXE
B. DLL
C. COM
D. DOC
解析: PE文件被称为可移植的执行体,是Portable Execute的全称,常见 的 EXE、DLL、OCX、SYS、COM 都是 PE 文 件。PE 文件 是 微软Windows操作系统上的程序文件(可能是间接被执行,如DLL)
答案: D
例题45:Windows 将遵循下面的哪种搜索来定位DLL ?
(1)进程的当前工作目录
(2)包含EXE文件的目录
(3)列在Path环境变量中的一系列目录
(4)Windows系统目录
(5)Windows目录
A.12453 B.12543 C.21453 D.21345
解析: Windows平台的大多数程序都使用各种动态链接库(DLL)来避免重复实现功能。操作系统为每个程序加载若干个DLL,具体由程序的类型决定。当程序不指定DLL的绝对位置时,将使用默认的搜索顺序来找到它。
默认情况下,操作系统所使用的搜索顺序为:
(1)内存
(2)KnownDLLs
(3)清单与.local
(4)应用程序目录
(5)当前工作目录
(6)系统目录
(7)路径变量
答案: C
例题46:简述Windows内存管理的几种方式和优缺点。
答案:Windows 内存管理方式主要分为:页式管理、段式管理、段页式管理。
页式管理的基本原理是将各进程的虚拟空间划分成若干个长度相等的页(page);页式管理把内存空间按页的大小划分成片或者页面,然后把页式虚拟地址与内存地址建立一一对应的页表;并用相应的硬件地址变换机构来解决离散地址变换问题。
页式管理采用请求调页或预调页技术来实现内外存储器的统一管理。 其优点是没有外碎片,每个内碎片不超过页的大小。缺点是程序全部装入内存,要求有相应的硬件支持。例如地址变换机构缺页中断的产生和选择淘汰页面等都要求有相应的硬件支持。这增加了机器成本,也增加了系统开销。
段式管理的基本思想就是把程序按内容或过程函数关系分成段,每段有自己的名字。一个用户作业或进程所包含的段对应一个二维线形虚拟空间,也就是一个二维虚拟存储器。段式管理程序以段为单位分配内存,然后通过地址影射机构把段式虚拟地址转换为实际内存物理地址。
其优点是可以分别编写和编译,可以针对不同类型的段采取不同的保护,可以按段为单位来进行共享,包括通过动态链接进行代码共享。缺点是会产生碎片。
段页式管理:为了实现段页式管理,系统必须为每个作业或进程建立一张段表以管理内存分配与释放、缺段处理等。另外由于一个段又被划分成了若干页。每个段又必须建立一张页表以把段中的虚页变换成内存中的实际页面。
显然与页式管理时相同,页表中也要有相应的实现缺页中断处理和页面保护等功能的表项。段页式管理是段式管理与页式管理方案结合而成的,所以具有它们两者的优点。但反过来说,由于管理软件的增加,复杂性和开销也就随之增加了。另外需要的硬件以及占用的内存也有所增加,使得执行速度下降。
9. 数据库与SQL语言
例题47:存储过程和函数的区别是什么?
答案:存储过程是用户定义的一系列SQL语句的集合,涉及特定表或其他对象的任务,用户可以调用存储过程。而函数通常是数据库已定义的方法,它接收参数并返回某种类型的值并且不涉及特定用户表。
例题48:游标的作用是什么,如何知道游标已经到最后?
答案:游标用于定位结果集的行。通过判断全局变量 @@FETCH_ STATUS可以判断其是否到了最后。通常此变量不等于0表示出错或到了最后。
例题49:触发器分为事前触发和事后触发,这两种触发有何区别?语句级触发和行级触发有何区别?
答案:事前触发器运行于触发事件发生之前,而事后触发器运行于触发事件发生之后。语句级触发器可以在语句执行前或后执行,而行级触发在触发器所影响的每一行触发一次。
例题50:解释聚集索引和非聚集索引之间的区别。
答案:经典教科书对聚集索引的解释是,聚集索引的顺序就是数据的物理存储顺序。而对于非聚集索引的解释是索引顺序与数据物理排列顺序无关。
正是因为如此,一个表最多只能有一个聚集索引
在SQL Server中,索引是通过二叉树的数据结构来描述的,我们可以这么理解聚集索引:索引的叶节点就是数据节点。而非聚集索引的叶节点仍然是索引节点,只不过有一个指针指向对应的数据块。
聚集索引确定表中数据的物理顺序。聚集索引类似于电话簿(电话簿按照字母薄排序),后者按姓氏排列数据。由于聚集索引规定数据在表中的物理存储顺序,因此一个表只能包含一个聚集索引。但该索引可以包含多个列(组合索引),就像电话簿按姓氏和名字进行组织一样。
聚集索引对于那些经常要搜索范围值的列特别有效。使用聚集索引找到包含第一个值的行后,便可以确保包含后续索引值的行在物理相邻。例如,如果应用程序执行的一个查询经常检索某一日期范围内的记录,则使用聚集
索引可以迅速找到包含开始日期的行,然后检索表中所有相邻的行,直到到达结束日期。这样有助于提高此类查询的性能。同样,如果对从表中检索的数据进行排序时经常要用到某一列,则可以将该表在该列上聚集(物理排序),避免每次查询该列时都进行排序,从而节省成本。
使用非聚集索引,非聚集索引与课本中的索引类似。数据存储在一个地方,索引存储在另一个地方,索引带有指针指向数据的存储位置。索引中的项目按索引键值的顺序存储,而表中的信息按另一种顺序存储(这可以由聚集索引规定)。
如果在表中未创建聚集索引, 则无法保证这些行具有任何特定的顺序。
有索引就一定检索得快吗?答案是否定的。有些时候用索引还不如不用索引快。比如说我们要检索表中的所有8000条记录,如果不用索引,需要访问8000 条x1000字节/8K字节=1000个页面。
如果使用索引的话,首先检索索引,访问8000条x10字节/8K字节=10个页面得到索引检索结果,再根据索引检索结果去对应数据页面。由于是检索所有数据,所以需要再访问8000条x1000 字节/8K字节=1000个页面将全部数据读取出来,共访问了1010个页面。这显然不如不用索引快。
10. 计算机网络及分布式系统
例题51:在OSI参考模型中,物理层的作用是(1)。对等实体在一次交互作用中传送的信息单位称为(2),它包括(3)两部分。上下层实体之间的接口称为服务访问点(SAP),网络层的服务访问点也称为(4),通常分为(5)两部分。
(1)A.建立和释放连接 B.透明传输比特流
C.协议数据单元 D.发送和接收用户
(2)A.接口数据 B.服务数据单元
C.协议数据单元 D.交互数据单元
(3)A.控制信息和用户数据 B.接口信息和用户数据
C.接口信息和控制信息 D.控制信息和效验信息
(4)A.用户地址 B.网络地址
C.端口地址 D.网卡地址
(5)A.网络号和端口号 B.网络号和主机地址
C.超网号和子网号 D.超网号和端口地址
解析:网络问题。
OSI参考模型有7层。其分层原则如下。
(1)根据不同层次的抽象分层。
(2)每层应当有一个定义明确的功能。
(3)每层功能的选择应该有助于制定网络协议的国际标准。
(4)各层边界的选择应尽量节省跨过接口的通信量。
(5)层数应足够多,以避免不同的功能混杂在同一层中,但也不能太多,否则体系结构会过于庞大。
根据以上标准,OSI参考模型分为物理层、数据链路层、网络层、传输层、会话层、表示层、应用层。
● 物理层涉及在信道上传输的原始比特流。
● 数据链路层的主要任务是加强物理层传输原始比特流的功能,使之对应的网络层显现为无错线路。发送包把输入数据封装在数据帧,按顺序传送出去并处理接收方回送的确认帧。
● 网络层关系到子网的运行控制,其中一个关键问题是确认从源端到目的端如何选择路由。
● 传输层的基本功能是从会话层接收数据而且把其分成较小的单元传递给网络层。
● 会话层允许不同机器上的用户建立会话关系。
● 表示层用来完成某些特定的功能
● 应用层包含着大量人们普遍需要的协议。
答案:B,C,A,B,B。
例题52:TCP和UDP有什么区别?
解析:举例说明两者间的区别。
TCP连接就像打电话,两者之间必须有一条不间断的通路,数据不到达对方,对方就一直在等待,除非对方直接挂电话。先说的话先到,后说的话后到,有顺序。
UDP就像寄一封信,发信者只管发,不管到。但是你的信封上必须写明对方的地址。发信者和收信者之间没有通路,靠邮局联系。信发到时可能已经过了很久,也可能根本没有发到。先发的信未必先到,后发的也未必后到。
答案: TCP是传输控制协议,提供的是面向连接、可靠的字节流服务。客户和服务器彼此交换数据前,必须先在双方之间建立一个TCP连接,之后才能传输数据。TCP提供超时重发、丢弃重复数据、检验数据、流量控制等功能,保证数据能从一端传到另一端。
UDP是用户数据报协议,是一个简单的面向数据报的运输层协议。UDP不提供可靠性,它只是把应用程序传给IP层的数据报发送出去,但是并不保证它们能到达目的地。由于UDP在传输数据报前不用在客户和服务器之间建立一个连接,且没有超时重发等机制,故而传输速度很快。
11. 网络协议
例题53:如果蠕虫病毒攻击了一个家用PC的A类地址主机的话,这个地址最有可能接收很多______。
A.HTTP回应包
B.DNS回应包
C.ICMP目的无法抵达包
D.ARP回应
解析:大量发出IP请求,肯定很多不可达,返回不可达错误。
答案:C
例题54:如何编写Socket 套接字?
解析:Socket相当于进行网络通信两端的插座,只要对方的Socket和自己的Socket 有通信联接,双方就可以发送和接收数据了。其定义类似于文件句柄的定义。如果你要编写的是一个服务程序,那么先调用socket()创建一个套接字,调用bind()绑定IP地址和端口,然后启动一个死循环,循环中调用accept()接受连接。对于每个接受的连接,可以启动多线程方式进行处理,在线程中调用send()、recv()发送和接收数据。
如果你要编写的是一个客户端程序,那么就简单多了。先调用socket()创建一个套接字,然后调用connect()连接服务器,之后就是调用send()、recv()发送和接收数据了。
答案:服务器端程序编写如下。
(1)调用ServerSocket(int port)创建一个服务器端套接字,并绑定到指定端口上。
(2)调用accept(),监听连接请求,接收连接,返回通信套接字。
(3)调用Socket类的getOutStream()和getInputStream获取输出流和输入流,开始网络数据的发送和接收。
(4)关闭通信套接字.Socket.close()。
客户端程序编写:
调用Socket()创建一个流套接字,并连接到服务器端。
调用Socket类的getOutputStream()和fetInputStream获取输出流和输入流,开始网络数据的发送和接收。
关闭通信套接字.Socket.close()。
12. 网络安全
例题55:入侵检查与防火墙有何不同,各有什么优缺点?
答案:防火墙的优点:它能增强机构内部网络的安全性,用于加强网络间的访问控制,防止外部用户非法使用内部网的资源,保护内部网络的设备不被破坏,防止内部网络的敏感数据被窃取。防火墙系统决定了哪些内部服务可以被外界访问;外界的哪些人可以访问内部的哪些服务,以及哪些外部服务可以被内部人员访问。
防火墙的缺点:对于发生在内网的攻击无能为力;部分攻击可以绕过防火墙,防火墙发现不了;防火墙的策略是静态的,不能实施动态防御;等等。
入侵检测的优势:入侵监测系统扫描当前网络的活动,监视和记录网络的流量,根据定义好的规则来过滤从主机网卡到网线上的流量,提供实时报警。大多数的入侵检测系统可以提供关于网络流量非常详尽的分析。它们可以监视任何定义好的流量。很多系统对FTP、HTTP和Telnet流量都有默认的设置,还有其他的流量,如NetBus、本地和远程登录失败,等等。也可以自己定制策略。如果定义了策略和规则,便可以获得FTP、SMTP、Telnet和任何其他的流量。这种规则有助于追查该连接和确定网络上发生过什么,以及现在正在发生什么。这些程序在需要确定网络中策略实施的一致性情况时是非常有效的工具。
入侵检测的缺点:目前入侵检测技术的方法主要停留在异常检测统计方法和误用检测方法上,这两种方法都还存在这样或那样的问题。网络入侵技术在不断地发展,入侵的行为表现出不确定性、多样性等特点。网络应用的发展又带来新的安全问题。如高速网络技术出现流量大的特点,那么基于网络的入侵检测系统如何适应这种情况?基于主机审计数据怎样做到既减少数据量,又能有效地检测到入侵?入侵检测研究领域急需其他学科知识提供新的入侵检测解决方法。入侵检测只是试图发现计算机网络中的安全问题,要解决网络安全的问题还需要其他的网络安全技术。另外,入侵检测系统本身还存在安全问题。入侵检测系统也可能会受到攻击。
综上所述,其实防火墙和入侵检测各有优劣。打个比方,防火墙就相当于一栋大楼外的门卫系统,而入侵检测就相当于大楼内的监控系统,两者缺不可。 应该将入侵检测系统与防火墙联动起来,当入侵检测系统发现到有入侵行为时,应及时报告防火墙,以阻断入侵。
例题56:端口是做什么用的,有什么漏洞吗?
答案:25 端口为SMTP(Simple Mail Transfer Protocol,简单邮件传输协议)服务器所开放,主要用于发送邮件,如今绝大多数邮件服务器都使用该协议。例如在使用电子邮件客户端程序的时候,在创建账户时会要求输入SMTP服务器地址,该服务器地址默认情况下使用的就是25端口。
端口漏洞:利用25端口,黑客可以寻找SMTP服务器,用来转发垃圾邮件。25端口被很多木马程序开放,例如Ajan、Antigen、Email Password Sender、ProMail、Trojan、Tapiras、Terminator、WinPC、WinSpy等。拿WinSpy来说,通过开放25端口,可以监视计算机正在运行的所有窗口和模块。
标签:函数,int,C++,面试,对象,答案,网安,例题 From: https://www.cnblogs.com/3cH0-Nu1L/p/18081580