C/C++笔试面试题目集合
C/C++笔试面试题目集合
一位同学整理的常见笔试/面试题目,答案仅供参考,不代表本人观点。
|
这个东西有些参考价值,和同学讨论一下发现还是有些错误,
1.已知strcpy函数的原型是: char * strcpy(char * strDest,const char * strSrc); 1.不调用库函数,实现strcpy函数。 2.解释为什么要返回char *。 解说: 1.strcpy的实现代码 char * strcpy(char * strDest,const char * strSrc) { if ((strDest==NULL)||(strSrc==NULL)) //[1] throw "Invalid argument(s)"; //[2] char * strDestCopy=strDest;//[3] while ((*strDest++=*strSrc++)!='/0'); //[4] return strDestCopy; } 错误的做法: [1] (A)不检查指针的有效性,说明答题者不注重代码的健壮性。 (B)检查指针的有效性时使用((!strDest)||(!strSrc))或(!(strDest&&strSrc)),说明答题者对C语言中类型的隐式转换没有深刻认识。在本例中char *转换为bool即是类型隐式转换,这种功能虽然灵活,但更多的是导致出错概率增大和维护成本升高。所以C++专门增加了bool、true、false三个关键字以提供更安全的条件表达式。 (C)检查指针的有效性时使用((strDest==0)||(strSrc==0)),说明答题者不知道使用常量的好处。直接使用字面常量(如本例中的0)会减少程序的可维护性。0虽然简单,但程序中可能出现很多处对指针的检查,万一出现笔误,编译器不能发现,生成的程序内含逻辑错误,很难排除。而使用NULL代替0,如果出现拼写错误,编译器就会检查出来。 [2] (A)return new string("Invalid argument(s)");,说明答题者根本不知道返回值的用途,并且他对内存泄漏也没有警惕心。从函数中返回函数体内分配的内存是十分危险的做法,他把释放内存的义务抛给不知情的调用者,绝大多数情况下,调用者不会释放内存,这导致内存泄漏。 (B)return 0;,说明答题者没有掌握异常机制。调用者有可能忘记检查返回值,调用者还可能无法检查返回值(见后面的链式表达式)。妄想让返回值肩负返回正确值和异常值的双重功能,其结果往往是两种功能都失效。应该以抛出异常来代替返回值,这样可以减轻调用者的负担、使错误不会被忽略、增强程序的可维护性。 [3] (A)忘记保存原始的strDest值,说明答题者逻辑思维不严密。 [4] (A)循环写成while (*strDest++=*strSrc++);,同[1](B)。 (B)循环写成while (*strSrc!='/0') *strDest++=*strSrc++;,说明答题者对边界条件的检查不力。循环体结束后,strDest字符串的末尾没有正确地加上'/0'。
2.返回strDest的原始值使函数能够支持链式表达式,增加了函数的“附加值”。同样功能的函数,如果能合理地提高的可用性,自然就更加理想。 参考文献:林锐,《高质量程序设计指南--C/C++语言》,北京,电子工业出版社,2002.6。 2.已知类String 的原型为: class String { public: String(const char *str = NULL); // 普通构造函数 String(const String &other); // 拷贝构造函数 ~ String(void); // 析构函数 String & operate =(const String &other); // 赋值函数 private: char *m_data; // 用于保存字符串 }; 请编写String 的上述4 个函数。 答案: String::String(const char *str) { if ( str == NULL ) //strlen在参数为NULL时会抛异常才会有这步判断 { m_data = new char[1] ; m_data[0] = '{C}' ; } else { m_data = new char[strlen(str) + 1]; strcpy(m_data,str); } } String::String(const String &other) { m_data = new char[strlen(other.m_data) + 1]; strcpy(m_data,other.m_data); } String & String::operator =(const String &other) { if ( this == &other) return *this ; delete []m_data; m_data = new char[strlen(other.m_data) + 1]; strcpy(m_data,other.m_data); return *this ; } String::~ String(void) { delete []m_data ; } 3.简答 3.1 头文件中的ifndef/define/endif 干什么用? 答:防止该头文件被重复引用。 3.2#i nclude <filename.h> 和#i nclude “filename.h” 有什么区别? 答:对于#i nclude <filename.h> ,编译器从标准库路径开始搜索filename.h 对于#i nclude “filename.h”,编译器从用户的工作路径开始搜索filename.h 3.3 在C++ 程序中调用被C 编译器编译后的函数,为什么要加extern “C”? 答:C++语言支持函数重载,C 语言不支持函数重载。函数被C++编译后在库中的名字与C 语言的不同。假设某个函数的原型为: void foo(int x, int y); 该函数被C 编译器编译后在库中的名字为_foo , 而C++ 编译器则会产生像_foo_int_int 之类的名字。 C++提供了C 连接交换指定符号extern“C”来解决名字匹配问题。 3.4 一个类有基类、内部有一个其他类的成员对象,构造函数的执行顺序是怎样的。(Autodesk) 答:先执行基类的(如果基类当中有虚基类,要先执行虚基类的,其他基类则按照声明派生类时的顺序依次执行),再执行成员对象的,最后执行自己的。 3.5 请描述一个你熟悉的设计模式(Autodesk) 3.6 在UML 中,聚合(aggregation)和组合(composition)有什么区别 Autodesk) 答案:聚合关系更强,类似于pages 和book 的关系;组合关系要弱,类似于books和bookshelf 的关系。 3.7C#和C++除了语法上的差别以外,有什么不同的地方?(Autodesk,Microsoft) 答案:(C#我只是了解,不是很精通) (1) c#有垃圾自动回收机制,程序员不用担心对象的回收。(2)c#严禁使用指针,只能处理对象。如果希望使用指针,则仅可在unsafe 程序块中能使用指针。(3)c#只能单继承。(4)必须通过类名访问静态成员。不能像C++中那样,通过对象访问静态成员。(5)在子类中覆盖父类的虚函数时必须用关键字override,覆盖父类的方法要用关键字new 3.8ADO.net 和ADO 的区别? 答案:实际上除了“能够让应用程序处理存储于DBMS 中的数据“这一基本相似点外,两者没有太多共同之处。但是ADO 使用OLE DB 接口并基于微软的COM 技术,而ADO.NET 拥有自己的ADO.NET 接口并且基于微软的.NET 体系架构。众所周知.NET 体系不同于COM 体系,ADO.NET 接口也就完全不同于ADO和OLE DB 接口,这也就是说ADO.NET 和ADO是两种数据访问方式。ADO.net 提供对XML 的支持。 3.9 New delete 与malloc free 的区别 ( Autodesk) 答案:用malloc 函数不能初始化对象,new 会调用对象的构造函数。Delete 会调用对象的destructor,而free 不会调用对象的destructor. 3.10 #define DOUBLE(x) x+x (Autodesk) i = 5*DOUBLE(10); i 是多少?正确的声明是什么? 答案:i 为60。正确的声明是#define DOUBLE(x) (x+x) 3.11 有哪几种情况只能用intialization list 而不能用assignment? (Autodesk) 答案:当类中含有const、reference 成员变量;基类的构造函数都需要参数;类中含有其他类的成员对象,而该类的构造函数都需要参数。 3.11 C++是不是类型安全的? (Autodesk) 答案:不是。两个不同类型的指针之间可以强制转换。C#是类型安全的。 3.12 main 函数执行以前,还会执行什么代码? (Autodesk) 答案:全局对象的构造函数会在main 函数之前执行。 3.13 描述内存分配方式以及它们的区别。 (Autodesk , Microsoft) 答案:1) 从静态存储区域分配。内存在程序编译的时候就已经分配好,这块内存在程序的整个运行期间都存在。例如全局变量,static 变量。 (2) 在栈上创建。在执行函数时,函数内局部变量的存储单元都可以在栈上创建,函数执行结束时这些存储单元自动被释放。栈内存分配运算内置于处理器的指令集。 (3) 从堆上分配,亦称动态内存分配。程序在运行的时候用malloc 或new 申请任意多少的内存,程序员自己负责在何时用free 或delete 释放内存。动态内存的生存期由我们决定,使用非常灵活,但问题也最多。 3.14 什么是虚拟存储器?virtual memory 怎样映射到physical memory?页面替换算法有哪些? (Microsoft) 见操作系统 p238 页。掌握的页面替换算法NRU,FIFO,第二次机会页面替换算法,LRU 3.15 有四个同样的容器,里面装满了粒数相同的药丸,正常药丸的质量为m,变质药丸的质量为m+1,现在已知这四个容器中,有一个装的全是变质药丸,用电子秤只称一次,找出哪个容器装的是变质药丸 (Microsoft) 答案:把四个容器依次编号为1、2、3、4,然后从中分别取出1、2、3、4 粒药丸,称这10 粒药丸的质量,如果质量为10m+1,则说明第一个容器装的是变质药丸,如果为10m+2 则说明第二个装的变质药丸,依次类推。 3.16 比较一下C++中static_cast 和 dynamic_cast 的区别。 (Autodesk) dynamic_casts在帮助你浏览继承层次上是有限制的。它不能被用于缺乏虚函数的类型上,它被用于安全地沿着类的继承关系向下进行类型转换。如你想在没有继承关系的类型中进行转换,你可能想到static_cast 3.17 Struct 和class 的区别 (Autodesk) 答案:struct 中成员变量和成员函数默认访问权限是public,class 是private 3.18 当一个类A 中没有生命任何成员变量与成员函数,这时sizeof(A)的值是多少,如果不是零,请解释一下编译器为什么没有让它为零。(Autodesk) 答案:肯定不是零。我举个反例,如果是零的话,声明一个class A[10]对象数组,而每一个对象占用的空间是零,这时就没办法区分A[0],A[1]…了 3.19 在8086 汇编下,逻辑地址和物理地址是怎样转换的?(Intel) 答案:通用寄存器给出的地址,是段内偏移地址,相应段寄存器地址*10H+通用寄存器内地址,就得到了真正要访问的地址。 3.20 描述一下C++的多态 (microsoft) 答案:C++的多态表现在两个部分,一个是静态连编下的函数重载,运算符重载;动态连编下的虚函数、纯虚函数(抽象类) 4.写出BOOL,int,float,指针类型的变量a 与零的比较语句。 答案: BOOL : if ( !a ) int : if ( a == 0) float : const EXPRESSION EXP = 0.000001 if ( a < EXP && a >-EXP) pointer : if ( a != NULL) 5.请说出const 与#define 相比优点 答案: (1) const 常量有数据类型,而宏常量没有数据类型。编译器可以对前者进行类型安全检查。而对后者只进行字符替换,没有类型安全检查,并且在字符替换可能会产生意料不到的错误。 (2) 有些集成化的调试工具可以对const 常量进行调试,但是不能对宏常量进行调试。 6.简述数组与指针的区别 数组要么在静态存储区被创建(如全局数组),要么在栈上被创建。指针可以随时指向任意类型的内存块。 (1)修改内容上的差别 char a[] = “hello”; a[0] = ‘X’; char *p = “world”; // 注意p 指向常量字符串 p[0] = ‘X’; // 编译器不能发现该错误,运行时错误 (2) 用运算符sizeof 可以计算出数组的容量(字节数)。sizeof(p),p 为指针得到的是一个指针变量的字节数,而不是p 所指的内存容量。C++/C 语言没有办法知道指针所指的内存容量,除非在申请内存时记住它。注意当数组作为函数的参数进行传递时,该数组自动退化为同类型的指针。 char a[] = "hello world"; char *p = a; cout<< sizeof(a) << endl; // 12 字节 cout<< sizeof(p) << endl; // 4 字节 计算数组和指针的内存容量 void Func(char a[100]) { cout<< sizeof(a) << endl; // 4 字节而不是100 字节 } 7.类成员函数的重载、覆盖和隐藏区别 答案: 成员函数被重载的特征: (1)相同的范围(在同一个类中); (2)函数名字相同; (3)参数不同; (4)virtual 关键字可有可无。 覆盖是指派生类函数覆盖基类函数,特征是: (1)不同的范围(分别位于派生类与基类); (2)函数名字相同; (3)参数相同; (4)基类函数必须有virtual 关键字。 “隐藏”是指派生类的函数屏蔽了与其同名的基类函数,规则如下: (1)如果派生类的函数与基类的函数同名,但是参数不同。此时,不论有无virtual关键字,基类的函数将被隐藏(注意别与重载混淆)。 (2)如果派生类的函数与基类的函数同名,并且参数也相同,但是基类函数没有virtual 关键字。此时,基类的函数被隐藏(注意别与覆盖混淆) 8.There are two int variables: a and b, don’t use “if”, “? :”, “switch” or other judgement statements, find out the biggest one of the two numbers. 答案:( ( a + b ) + abs( a – b ) ) / 2 9.如何打印出当前源文件的文件名以及源文件的当前行号? 答案: cout << __FILE__ ; cout<<__LINE__ ; __FILE__和__LINE__是系统预定义宏,这种宏并不是在某个文件中定义的,而是由编译器定义的。 10.main 主函数执行完毕后,是否可能会再执行一段代码,给出说明? 答案:可以,可以用_onexit 注册一个函数,它会在main 之后执行int fn1(void), fn2(void), fn3(void), fn4 (void); void main( void ) { String str("zhanglin"); _onexit( fn1 ); _onexit( fn2 ); _onexit( fn3 ); _onexit( fn4 ); printf( "This is executed first./n" ); } int fn1() { printf( "next./n" ); return 0; } int fn2() { printf( "executed " ); return 0; } int fn3() { printf( "is " ); return 0; } int fn4() { printf( "This " ); return 0; } The _onexit function is passed the address of a function (func) to be called when the program terminates normally. Successive calls to _onexit create a register of functions that are executed in LIFO (last-in-first-out) order. The functions passed to _onexit cannot take parameters. 11.如何判断一段程序是由C 编译程序还是由C++编译程序编译的? 答案: #ifdef __cplusplus cout<<"c++"; #else cout<<"c"; #endif 12.文件中有一组整数,要求排序后输出到另一个文件中 答案: void Order(vector<int> &data) //起泡排序 { int count = data.size() ; int tag = false ; for ( int i = 0 ; i < count ; i++) { for ( int j = 0 ; j < count - i - 1 ; j++) { if ( data[j] > data[j+1]) { tag = true ; int temp = data[j] ; data[j] = data[j+1] ; data[j+1] = temp ; } } if ( !tag ) break ; } } void main( void ) { vector<int>data; ifstream in("c://data.txt"); if ( !in) { cout<<"file error!"; exit(1); } int temp; while (!in.eof()) { in>>temp; data.push_back(temp); } in.close(); Order(data); ofstream out("c://result.txt"); if ( !out) { cout<<"file error!"; exit(1); } for ( i = 0 ; i < data.size() ; i++) out<<data[i]<<" "; out.close(); } 13.排序方法比较 (intel) 排序方法 平均时间 最坏时间 辅助存储 直接插入排序 起泡排序 快速排序 简单选择排序 堆排序 归并排序 基数排序 14.一个链表的结点结构 struct Node { int data ; Node *next ; }; typedef struct Node Node ; (1)已知链表的头结点head,写一个函数把这个链表逆序 ( Intel) Node * ReverseList(Node *head) //链表逆序 { if ( head == NULL || head->next == NULL ) return head; Node *p1 = head ; Node *p2 = p1->next ; Node *p3 = p2->next ; p1->next = NULL ; while ( p3 != NULL ) { p2->next = p1 ; p1 = p2 ; p2 = p3 ; p3 = p3->next ; } p2->next = p1 ; head = p2 ; return head ; } (2)已知两个链表head1 和head2 各自有序,请把它们合并成一个链表依然有序。 Node * Merge(Node *head1 , Node *head2) { if ( head1 == NULL) return head2 ; if ( head2 == NULL) return head1 ; Node *head = NULL ; Node *p1 = NULL; Node *p2 = NULL; if ( head1->data < head2->data ) { head = head1 ; p1 = head1->next; p2 = head2 ; } else { head = head2 ; p2 = head2->next ; p1 = head1 ; } Node *pcurrent = head ; while ( p1 != NULL && p2 != NULL) { if ( p1->data <= p2->data ) { pcurrent->next = p1 ; pcurrent = p1 ; p1 = p1->next ; } else { pcurrent->next = p2 ; pcurrent = p2 ; p2 = p2->next ; } } if ( p1 != NULL ) pcurrent->next = p1 ; if ( p2 != NULL ) pcurrent->next = p2 ; return head ; } (2)已知两个链表head1 和head2 各自有序,请把它们合并成一个链表依然有序,这次要求用递归方法进行。 ( Autodesk) 答案: Node * MergeRecursive(Node *head1 , Node *head2) { if ( head1 == NULL ) return head2 ; if ( head2 == NULL) return head1 ; Node *head = NULL ; if ( head1->data < head2->data ) { head = head1 ; head->next = MergeRecursive(head1->next,head2); } else { head = head2 ; head->next = MergeRecursive(head1,head2->next); } return head ; } 15.分析一下这段程序的输出 (Autodesk) class B { public: B() { cout<<"default constructor"<<endl; } ~B() { cout<<"destructed"<<endl; } B(int i):data(i) { cout<<"constructed by parameter" << data <<endl; } private: int data; }; B Play( B b) { return b ; } int main(int argc, char* argv[]) { B temp = Play(5); return 0; } 请自己执行一下看看。 16.写一个函数找出一个整数数组中,第二大的数 (microsoft) 答案: const int MINNUMBER = -32767 ; int find_sec_max( int data[] , int count) //类似于1 4 4 4这样的序列将认为1是第二大数 { int maxnumber = data[0] ; int sec_max = MINNUMBER ; for ( int i = 1 ; i < count ; i++) { if ( data[i] > maxnumber ) { sec_max = maxnumber ; maxnumber = data[i] ; } else { if ( data[i] > sec_max ) sec_max = data[i] ; } } return sec_max ; } 17 写一个在一个字符串中寻找一个子串第一个位置的函数 这个题目的一般算法比较简单我就不给出了,如果要求高效率的话请参见数据结构中的KMP 算法,不过在笔试时间有限情况下,写出那个算法还是挺难的。 英文题目 1. Introduce yourself in English 2. What is your great advantage you think of yourself? 3. What is your drawback you think of yourself? Maybe I will feel very tense if I make a speech in front of a lot of people. 4. How do you feel shanghai? <script type="text/javascript"><!-- google_ad_client = "pub-3555979289815451"; google_ad_slot = "0437120238"; google_ad_width = 468; google_ad_height = 60; //--> </script> <script src="http://pagead2.googlesyndication.com/pagead/show_ads.js" type="text/javascript"> </script> |