C/C++面试题 济南达内 CSD1202 班 整理日期:2012-3-9 整理人: 彭根燕 基本概念的理解(必须掌握) 1. 面向对象的程序设计思想是什么? 答:把数据结构和对数据结构进行操作的方法封装形成一个个的对象。 2. 什么是类? 答:把一些具有共性的对象归类后形成一个集合,也就是所谓的类。 3. 对象都具有的二方面特征是什么?分别是什么含义? 答:对象都具有的特征是:静态特征和动态特征。 静态特征是指能描述对象的一些属性,动态特征是指对象表现出来的行为 4. 在头文件中进行类的声明,在对应的实现文件中进行类的定义有什么意义? 答:这样可以提高编译效率,因为分开的话只需要编译一次生成对应的.obj 文件后,再 次应用该类的地方,这个类就不会被再次编译,从而大大提高了效率。 5. 在类的内部定义成员函数的函数体,这种函数会具备那种属性? 答:这种函数会自动为内联函数,这种函数在函数调用的地方在编译阶段都会进行代码 替换。 6. 成员函数通过什么来区分不同对象的成员数据?为什么它能够区分? 答:通过 this 指针来区分的, 因为它指向的是对象的首地址。 7. C++编译器自动为类产生的四个缺省函数是什么? 答:默认构造函数,拷贝构造函数,析构函数,赋值函数。 8. 拷贝构造函数在哪几种情况下会被调用? 答:1.当类的一个对象去初始化该类的另一个对象时; 2.如果函数的形参是类的对象,调用函数进行形参和实参结合时; 3.如果函数的返回值是类对象,函数调用完成返回时。 9. 构造函数与普通函数相比在形式上有什么不同?(构造函数的作用,它的声明形式 来分析) 答:构造函数是类的一种特殊成员函数,一般情况下,它是专门用来初始化对象成员变 量的。 构造函数的名字必须与类名相同,它不具有任何类型,不返回任何值。 10. 什么时候必须重写拷贝构造函数? 答:当构造函数涉及到动态存储分配空间时,要自己写拷贝构造函数,并且要深拷贝。 11. 构造函数的调用顺序是什么? 答:1.先调用基类构造函数 2.按声明顺序初始化数据成员 3.最后调用自己的构造函数。 12. 哪几种情况必须用到初始化成员列表? 答:类的成员是常量成员初始化; 类的成员是对象成员初始化,而该对象没有无参构造函数 类的成员为引用时。 13. 什么是常对象? 答:常对象是指在任何场合都不能对其成员的值进行修改的对象。 14. 静态函数存在的意义? 答:静态私有成员在类外不能被访问,可通过类的静态成员函数来访问; 当类的构造函数是私有的时,不像普通类那样实例化自己,只能通过静态成员函数来调 用构造函数。 15. 在类外有什么办法可以访问类的非公有成员? 答:友元,继承,公有成员函数。 16. 什么叫抽象类? 答:不用来定义对象而只作为一种基本类型用作继承的类。 17. 运算符重载的意义? 答:为了对用户自定义数据类型的数据的操作与内定义的数据类型的数据的操作形式一 致。 18. 不允许重载的 5 个运算符是哪些? 答: 1. .*(成员指针访问运算符号) 2. ::域运算符 3. Sizeof 长度运算符号 4. ?:条件运算符号 5. .(成员访问符) 19. 运算符重载的三种方式? 答:普通函数,友元函数,类成员函数。 20. 流运算符为什么不能通过类的成员函数重载?一般怎么解决? 答:因为通过类的成员函数重载必须是运算符的第一个是自己,而对流运算的重载要求 第一个参数是流对象。一般通过友元来解决。 21. 赋值运算符和拷贝构造函数的区别与联系? 答:相同点:都是将一个对象 copy 到另一个中去。 不同点:拷贝构造函数涉及到要新建立一个对象。 22. 在哪种情况下要调用该类的析构函数? 答:对象生命周期结束时。 23. 对象间是怎样实现数据的共享的? 答:通过类的静态成员变量来实现的。静态成员变量占有自己独立的空间不为某个对象所 私有。 24. 友元关系有什么特性? 答:单向的,非传递的,不能继承的。 25. 对对象成员进行初始化的次序是什么? 答:它的次序完全不受它们在初始化表中次序的影响,只有成员对象在类中声明的次序 来决定的。 26. 类和对象之间的关系是什么? 答:类是对象的抽象,对象是类的实例。 27. 对类的成员的访问属性有什么? 答:public,protected,private。 28. const char *p,char * const p;的区别 如果 const 位于星号的左侧,则 const 就是用来修饰指针所指向的变量,即指针指向为 常量; 如果 const 位于星号的右侧,const 就是修饰指针本身,即指针本身是常量。 29. 是不是一个父类写了一个 virtual 函数,如果子类覆盖它的函数不加 virtual ,也能实 现多态? virtual 修饰符会被隐形继承的。 virtual 可加可不加,子类覆盖它的函数不加 virtual ,也能实现多态。 30. 函数重载是什么意思?它与虚函数的概念有什么区别? 函数重载是一个同名函数完成不同的功能,编译系统在编译阶段通过函数参数个数、参数 类型不同,函数的返回值来区分该调用哪一个函数,即实现的是静态的多态性。但是记住: 不能仅仅通过函数返回值不同来实现函数重载。而虚函数实现的是在基类中通过使用关键 字 virtual 来申明一个函数为虚函数,含义就是该函数的功能可能在将来的派生类中定义 或者在基类的基础之上进行扩展,系统只能在运行阶段才能动态决定该调用哪一个函数, 所以实现的是动态的多态性。它体现的是一个纵向的概念,也即在基类和派生类间实现。 31. 构造函数和析构函数是否可以被重载,为什么? 答:构造函数可以被重载,析构函数不可以被重载。因为构造函数可以有多个且可以带参 数,而析构函数只能有一个,且不能带参数。 32. 如何定义和实现一个类的成员函数为回调函数? 答: 所谓的回调函数,就是预先在系统的对函数进行注册,让系统知道这个函数的存在,以 后,当某个事件发生时,再调用这个函数对事件进行响应。 定义一个类的成员函数时在该函数前加 CALLBACK 即将其定义为回调函数,函数的实现 和普通成员函数没有区别 33. 虚函数是怎么实现的? 答:简单说来使用了虚函数表. 34. 抽象类不会产生实例,所以不需要有构造函数。 错 35. 从一个模板类可以派生新的模板类,也可以派生非模板类。 对 36. main 函数执行以前,还会执行什么代码? 答案:全局对象的构造函数会在 main 函数之前执行。 37. 当一个类 A 中没有生命任何成员变量与成员函数,这时 sizeof(A)的值是多少,如果 不是零,请解释一下编译器为什么没有让它为零。(Autodesk) 答案:肯定不是零。举个反例,如果是零的话,声明一个 class A[10]对象数组,而每一 个对象占用的空间是零,这时就没办法区分 A[0],A[1]…了。 38. delete 与 delete []区别: delete 只会调用一次析构函数,而 delete[] 会调用每一个成员的析构函数。 39. 子类析构时要调用父类的析构函数吗? 会调用, 析构函数调用的次序是先派生类的析构后基类的析构,也就是说在基类的的析构调用的 时候,派生类的信息已经全部销毁了 40. .继承优缺点。 1、类继承是在编译时刻静态定义的,且可直接使用, 2、类继承可以较方便地改变父类的实现。 缺点: 1、因为继承在编译时刻就定义了,所以无法在运行时刻改变从父类继承的实现 2、父类通常至少定义了子类的部分行为,父类的任何改变都可能影响子类的行为 3、如果继承下来的实现不适合解决新的问题,则父类必须重写或被其他更适合的类替换。 这种依赖关系限制了灵活性并最终限制了复用性。 41. 解释堆和栈的区别。 栈区(stack — ) 由编译器自动分配释放 ,存放函数的参数值,局部变量的值等。 堆: 一般由程序员分配释放, 若程序员不释放,程序结束时可能由 OS 回收 。 42. 一个类的构造函数和析构函数什么时候被调用,是否需要手工调用? 答:构造函数在创建类对象的时候被自动调用,析构函数在类对象生命期结束时,由系 统自动调用。 43. 何时需要预编译: 总是使用不经常改动的大型代码体。 程序由多个模块组成,所有模块都使用一组标准的包含文件和相同的编译选项。在这种情 况下,可以将所有包含文件预编译为一个预编译头。 44. 多态的作用? 主要是两个: 1. 隐藏实现细节,使得代码能够模块化;扩展代码模块,实现代码重用; 2. 接口重用:为了类在继承和派生的时候,保证使用家族中任一类的实例的某一属性时 的正确调用 45. 虚拟函数与普通成员函数的区别?内联函数和构造函数能否为虚拟函数? 答案:区别:虚拟函数有 virtual 关键字,有虚拟指针和虚函数表,虚拟指针就是虚拟函 数的接口,而普通成员函数没有。内联函数和构造函数不能为虚拟函数。 46. 构造函数和析构函数的调用顺序? 析构函数为什么要虚拟? ——答案:构造函数的调用顺序:基类构造函数 对象成员构造函数 派生类构造函数;析 构函数的调用顺序与构造函数相反。析构函数虚拟是为了防止析构不彻底,造成内存的泄 漏。 47. .C++中类型为 private 的成员变量可以由哪些函数访问? 只可以由本类中的成员函数和友员函数访问 48. 请说出类中 private,protect,public 三种访问限制类型的区别 private 是私有类型,只有本类中的成员函数访问;protect 是保护型的,本类和继承类 可以访问;public 是公有类型,任何类都可以访问. 49. 类中成员变量怎么进行初始化? 可以通过构造函数的初始化列表或构造函数的函数体实现。 50. “”在什么时候需要使用 常引用 ?  如果既要利用引用提高程序的效率,又要保护传递给函数的数据不在函数中被改变,就 应使用常引用。 51. 引用与指针有什么区别? 答 、1) 引用必须被初始化,指针不必。 2) 引用初始化以后不能被改变,指针可以改变所指的对象。 3) 不存在指向空值的引用,但是存在指向空值的指针。 52. 描述实时系统的基本特性 53. 答 、在特定时间内完成特定的任务,实时性与可靠性。 54. 全局变量和局部变量在内存中是否有区别?如果有,是什么区别? 答 、全局变量储存在静态数据区,局部变量在堆栈中。 55. 堆栈溢出一般是由什么原因导致的? 答 、没有回收垃圾资源 56. 什么函数不能声明为虚函数? 答 构造函数(constructor) 57. .IP 地址的编码分为哪俩部分? 答 IP 地址由两部分组成,网络号和主机号。 58. .不能做 switch()的参数类型是: 答 、switch 的参数不能为实型。 59. 如何引用一个已经定义过的全局变量? 答 、可以用引用头文件的方式,也可以用 extern 关键字,如果用引用头文件方式来引用 某个在头文件中声明的全局变理,假定你将那个变写错了,那么在编译期间会报错,如 果你用 extern 方式引用时,假定你犯了同样的错误,那么在编译期间不会报错,而在连 接期间报错 60. 对于一个频繁使用的短小函数,在 C 语言中应用什么实现,在 C++中应用什么实现? 答 、c 用宏定义,c++用 inline 61. C++是不是类型安全的? 答案:不是。两个不同类型的指针之间可以强制转换(用 reinterpret cast) 62. 当一个类 A 中没有生命任何成员变量与成员函数,这时 sizeof(A)的值是多少,请解 释一下编译器为什么没有让它为零。 答案:为 1。举个反例,如果是零的话,声明一个 class A[10]对象数组,而每一个对象 占用的空间是零,这时就没办法区分 A[0],A[1]…了。 63. 简述数组与指针的区别? 数组要么在静态存储区被创建(如全局数组),要么在栈上被创建。指针可以随时指向任 意类型的内存块。 (1)修改内容上的区别 char a[] = “hello”; a[0] = ‘X’; char *p = “world”; // 注意 p 指向常量字符串 p[0] = ‘X’; // 编译器不能发现该错误,运行时错误 (2) 用运算符 sizeof 可以计算出数组的容量(字节数)。sizeof(p),p 为指针得到的是一 个指针变量的字节数,而不是 p 所指的内存容量。 64. C++函数中值的传递方式 有三种方式:值传递、指针传递、引用传递 65. 内存的分配方式 分配方式有三种, 1 、 静态存储区,是在程序编译时就已经分配好的,在整个运行期间都存在,如全局变 量、常量。 2 、 栈上分配,函数内的局部变量就是从这分配的,但分配的内存容易有限。 3 、 堆上分配,也称动态分配,如我们用 new,malloc 分配内存,用 delete,free 来释放 的内存。 66. extern“C”有什么作用? Extern “C”是由C++提供的一个连接交换指定符号,用于告诉C++这段代码是C函 数。这是因为 C++编译后库中函数名会变得很长,与 C 生成的不一致,造成C++不能 直接调用 C 函数,加上 extren “c”后,C++就能直接调用 C 函数了。 Extern “C”主要使用正规 DLL 函数的引用和导出 和 在 C++包含 C 函数或 C 头文件时 使用。使用时在前面加上 extern “c” 关键字即可。 67. 用什么函数开启新进程、线程。 答案: 线程:CreateThread/AfxBeginThread 等 进程:CreateProcess 等 68. SendMessage 和 PostMessage 有什么区别 答案:SendMessage 是阻塞的,等消息被处理后,代码才能走到 SendMessage 的下 一行。PostMessage 是非阻塞的,不管消息是否已被处理,代码马上走到 PostMessage 的下一行。 69. CMemoryState 主要功能是什么 答案:查看内存使用情况,解决内存泄露问题。 70. 26、#include 和 #include “filename.h” 有什么区别? 答:对于#include ,编译器从标准库路径开始搜索 filename.h 对于#include “filename.h” ,编译器从用户的工作路径开始搜索 filename.h 71. 处理器标识#error 的目的是什么? 答:编译时输出一条错误信息,并中止继续编译。 72. #if!defined(AFX_…_HADE_H) #define(AFX_…_HADE_H) …… #endif 作用? 防止该头文件被重复引用。 73. 在定义一个宏的时候要注意什么? 定义部分的每个形参和整个表达式都必须用括号括起来,以避免不可预料的错误发 生 74. 数组在做函数实参的时候会转变为什么类型? 数组在做实参时会变成指针类型。 75. 系统会自动打开和关闭的 3 个标准的文件是? (1) 标准输入----键盘---stdin (2) 标准输出----显示器---stdout (3) 标准出错输出----显示器---stderr 76. .在 Win32 下 char, int, float, double 各占多少位? (1) Char 占用 8 位 (2) Int 占用 32 位 (3) Float 占用 32 位 (4) Double 占用 64 位 77. strcpy()和 memcpy()的区别? strcpy() 和 memcpy() 都 可 以 用 来 拷 贝 字 符 串, strcpy() ’拷 贝 以 \0’ 结束,但 memcpy()必须指定拷贝的长度。 78. 说明 define 和 const 在语法和含义上有什么不同? (1) #define 是 C 语法中定义符号变量的方法,符号常量只是用来表达一个值,在编译 阶段符号就被值替换了,它没有类型; (2) Const 是 C++语法中定义常变量的方法,常变量具有变量特性,它具有类型,内存 中存在以它命名的存储单元,可以用 sizeof 测出长度。 79. 说出字符常量和字符串常量的区别,并使用运算符 sizeof 计算有什么不用? ‘字符常量是指单个字符,字符串常量以 \0’结束,使用运算符 sizeof 计算多占一字节的 存储空间。 80. 简述全局变量的优缺点? 全局变量也称为外部变量,它是在函数外部定义的变量,它属于一个源程序文件,它保 存上一次被修改后的值,便于数据共享,但不方便管理,易引起意想不到的错误。 81. 总结 static 的应用和作用? (1)函数体内 static 变量的作用范围为该函数体,不同于 auto 变量,该变量的内存只 被分配一次,因此其值在下次调用时仍维持上次的值; (2)在模块内的 static 全局变量可以被模块内所用函数访问,但不能被模块外其它函 数访问; (3)在模块内的 static 函数只可被这一模块内的其它函数调用,这个函数的使用范围 被限制在声明它的模块内; (4)在类中的 static 成员变量属于整个类所拥有,对类的所有对象只有一份拷贝; (5)在类中的 static 成员函数属于整个类所拥有,这个函数不接收 this 指针,因而只 能访问类的 static 成员变量。 82. .总结 const 的应用和作用? (1)欲阻止一个变量被改变,可以使用 const 关键字。在定义该 const 变量时,通常需 要对它进行初始化,因为以后就没有机会再去改变它了; (2)对指针来说,可以指定指针本身为 const,也可以指定指针所指的数据为 const, 或二者同时指定为 const; (3)在一个函数声明中,const 可以修饰形参,表明它是一个输入参数,在函数内部不 能改变其值; (4)对于类的成员函数,若指定其为 const 类型,则表明其是一个常函数,不能修改 类的成员变量; (5)对于类的成员函数,有时候必须指定其返回值为 const 类型,以使得其返回值不 “ ” 为 左值 。 83. 什么是指针?谈谈你对指针的理解? 指针是一个变量,该变量专门存放内存地址; 指针变量的类型取决于其指向的数据类型,在所指数据类型前加* 指针变量的特点是它可以访问所指向的内存。 84. 什么是常指针,什么是指向常变量的指针? 常指针的含义是该指针所指向的地址不能变,但该地址所指向的内容可以变化,使用常 指针可以保证我们的指针不能指向其它的变量, 指向常变量的指针是指该指针的变量本身的地址可以变化,可以指向其它的变量,但是 它所指的内容不可以被修改。指向长变量的指针定义, 85. 函数指针和指针函数的区别? 函数指针是指指向一个函数入口的指针; 指针函数是指函数的返回值是一个指针类型。 87. 简述 Debug 版本和 Release 版本的区别? Debug 版本是调试版本,Release 版本是发布给用户的最终非调试的版本, 88. 指针的几种典型应用情况? int *p[n];-----指针数组,每个元素均为指向整型数据的指针。 int (*)p[n];------p 为指向一维数组的指针,这个一维数组有 n 个整型数据。 int *p();----------函数带回指针,指针指向返回的值。 int (*)p();------p 为指向函数的指针。 89. static 函数与普通函数有什么区别? static 函数在内存中只有一份,普通函数在每个被调用中维持一份拷贝 90. struct(结构) 和 union(联合)的区别? 1. 结构和联合都是由多个不同的数据类型成员组成, 但在任何同一时刻, 联合中只存放了 一个被选中的成员(所有成员共用一块地址空间), 而结构的所有成员都存在(不同成员 的存放地址不同)。 2. 对于联合的不同成员赋值, 将会对其它成员重写, 原来成员的值就不存在了, 而对于结 构的不同成员赋值是互不影响的。 91. class 和 struct 的区别? struct 的成员默认是公有的,而类的成员默认是私有的。 92. 简述枚举类型? 枚举方便一次定义一组常量,使用起来很方便; 93. assert()的作用? ASSERT()是一个调试程序时经常使用的宏,在程序运行时它计算括号内的表达式,如果 表达式为 FALSE (0), 程序将报告错误,并终止执行。如果表达式不为 0,则继续执行后 面的语句。这个宏通常原来判断程序中是否出现了明显非法的数据,如果出现了终止程序 以免导致严重后果,同时也便于查找错误。 94. 局部变量和全局变量是否可以同名? 能,局部会屏蔽全局。要用全局变量,需要使用"::"(域运算符)。 95. 程序的局部变量存在于(堆栈)中,全局变量存在于(静态区 )中,动态申请数据 存在于( 堆)中。 96. 在什么时候使用常引用? 如果既要利用引用提高程序的效率,又要保护传递给函数的数据不在函数中被改变,就 应使用常引用。 97. 类的声明和实现的分开的好处? 1. 起保护作用; 2. 提高编译的效率。 98. windows 消息系统由哪几部分构成? 由一下 3 部分组成: 1. 消息队列:操作系统负责为进程维护一个消息队列,程序运行时不断从该消息队列 中获取消息、处理消息; 2. 消息循环:应用程序通过消息循环不断获取消息、处理消息。 3. 消息处理:消息循环负责将消息派发到相关的窗口上使用关联的窗口过程函数进行 处理。 99. 什么是消息映射? 消息映射就是让程序员指定 MFC 类(有消息处理能力的类)处理某个消息。然后由程序 员完成对该处理函数的编写,以实现消息处理功能。 100. 什么是 UDP 和 TCP 的区别是什么? TCP 的全称为传输控制协议。这种协议可以提供面向连接的、可靠的、点到点的通信。 UDP 全称为用户报文协议,它可以提供非连接的不可靠的点到多点的通信。 用 TCP 还是 UDP,那要看你的程序注重哪一个方面?可靠还是快速? 101. winsock 建立连接的主要实现步骤? 答: 服务器端:socket()建立套接字,绑定(bind)并监听(listen),用 accept()等待 客户端连接, accept()发现有客户端连接,建立一个新的套接字,自身重新开始等待 连 接。 该新产生 的套接字使用 send()和 recv( )写读数 据,直至数据交换 完毕 closesocket()关闭套接字。 客户端:socket()建立套接字,连接(connect)服务器,连接上后使用 send()和 recv(),在套接字上写读数据,直至数据交换完毕,closesocket()关闭套接字。 102. 进程间主要的通讯方式? 信号量,管道,消息,共享内存 103. 构成 Win32 API 函数的三个动态链接库是什么? 答:内核库,用户界面管理库,图形设备界面库。 104. 创建一个窗口的步骤是? 答:填充一个窗口类结构->注册这个窗口类->然后再创建窗口->显示窗口->更新窗口。 105. 模态对话框和非模态对话框有什么区别? 答:1.调用规则不同:前者是用 DoModal()调用,后者通过属性和 ShowWindow()来显 示。 2.模态对话框在没有关闭前用户不能进行其他操作,而非模态对话框可以。 3.非模态对话框创建时必须编写自己的共有构造函数,还要调用 Create()函数。 106. 从 EDIT 框中取出数据给关联的变量,已经把关联的变量的数据显示在 EDIT 框 上的函数是什么? 答: UpdateData(TRUE), Updatedata(FALSE). 107. 简单介绍 GDI? 答;GDI 是 Graphics Device Interface 的缩写,译为:图形设备接口;是一个在 Windows 应用程序中执行与设备无关的函数库,这些函数在不同的输出设备上产生图形 以及文字输出。 108. windows 消息分为几类?并对各类做简单描述。 1.窗口消息:与窗口相关的消息,除 WM_COMMAND 之外的所有以 WM_开头的消息; 2.命令消息;用于处理用户请求,以 WM_COMMAND 表示的消息; 3.控件通知消息:统一由 WM_NOTIFY 表示, 4.用户自定义消息。 109. 如何自定义消息? 使用 WM_USER 和 WM_APP 两个宏来自定义消息, 110. 简述 Visual C++ 、Win32 API 和 MFC 之间的关系? (1) Visual C+是一个以 C++程序设计语言为基础的、集成的、可视化的编程环境; (2) Win32 API 是 32 位 Windows 操作系以 C/C++形式提供的一组应用程序接口; (3) MFC 是对 Win32 API 的封装,简化了开发过程。 111.怎样消除多重继承中的二义性? 1.成员限定符 2.虚基类 112 什么叫静态关联,什么叫动态关联 在多态中,如果程序在编译阶段就能确定实际执行动作,则称静态关联, 如果等到程序运行才能确定叫动态关联。 113 多态的两个必要条件 1.一个基类的指针或引用指向一个派生类对象, 2.虚函数 114.什么叫智能指针? 当一个类中,存在一个指向另一个类对象的指针时,对指针运算符进行重载,那么当前 类对象可以通过指针像调用自身成员一样调用另一个类的成员。 115.什么时候需要用虚析构函数? 当基类指针指向用 new 运算符生成的派生类对象时,delete 基类指针时,派生类部分 没有释放掉而造成释放不彻底现象,需要虚析构函数。 116. MFC 中,大部分类是从哪个类继承而来? CObject 117.什么是平衡二叉树? 答:左右子树都是平衡二叉树,而且左右子树的深度差值的绝对值不大于 1 118.语句 for( ;1 ;)有什么问题?它是什么意思? 答:无限循环,和 while(1)相同。 119.派生新类的过程要经历三个步骤 1 吸收基类成员 2.改造基类成员 3.添加新成员 121. TCP/IP 建立连接的过程 在 TCP/IP 协议中,TCP 协议提供可靠的连接服务,采用三次握手建立一个连接。 第一次握手:建立连接时,客户端发送连接请求到服务器,并进入 SYN_SEND 状态,等 待服务器确认; 第二次握手:服务器收到客户端连接请求,向客户端发送允许连接应答,此时服务器进 入 SYN_RECV 状态; 第三次握手:客户端收到服务器的允许连接应答,向服务器发送确认,客户端和服务器 进入通信状态,完成三次握手 122. .memset ,memcpy 的区别 memset 用来对一段内存空间全部设置为某个字符,一般用在对定义的字符串进行初始 化为'\0'。 memcpy 用来做内存拷贝,你可以拿它拷贝任何数据类型的对象,可以指定拷贝的数据 长度; 123. 在 C++ 程序中调用被 C 编译器编译后的函数,为什么要加 extern “C”? 答:C++语言支持函数重载,C 语言不支持函数重载。函数被 C++编译后在库中的 名字 与 C 语言的不同。假设某个函数的原型为:void foo(int x, int y);该函数被 C 编译器编 译后在库中的名字为_foo , 而 C++编译器则会产生像_foo_int_int 之类的名字。C++ 提供了 C 连接交换指定符号 extern“C”来解决名字匹配问题。 124 怎样定义一个纯虚函数?含有纯虚函数的类称为什么? 在虚函数的后面加=0,含有虚函数的类称为抽象类。 125.已知 strcpy 函数的原型是: char * strcpy(char * strDest,const char * strSrc);不调用库函数,实现 strcpy 函 数。 答案: char *strcpy(char *strDest, const char *strSrc) { if ( strDest == NULL || strSrc == NULL) return NULL ; if ( strDest == strSrc) return strDest ; char *tempptr = strDest ; while( (*strDest++ = *strSrc++) != ‘\\0’) ; return tempptr ; } 126.已知类 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] = '' ; } 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 ; } 127.类成员函数的重载、覆盖和隐藏区别 答案: 成员函数被重载的特征: (1)相同的范围(在同一个类中); (2)函数名字相同; (3)参数不同; (4)virtual 关键字可有可无。 覆盖是指派生类函数覆盖基类函数,特征是: (1)不同的范围(分别位于派生类与基类); (2)函数名字相同; (3)参数相同; (4)基类函数必须有 virtual 关键字。 “”隐藏 是指派生类的函数屏蔽了与其同名的基类函数,规则如下: (1)如果派生类的函数与基类的函数同名,但是参数不同。此时,不论有无 virtual 关 键字,基类的函数将被隐藏(注意别与重载混淆)。 (2)如果派生类的函数与基类的函数同名,并且参数也相同,但是基类函数没有 virtual 关键字。此时,基类的函数被隐藏(注意别与覆盖混淆) 128.如何打印出当前源文件的文件名以及源文件的当前行号? 答案: cout << __FILE__ ; cout<<__LINE__ ; __FILE__和__LINE__是系统预定义宏,这种宏并不是在某个文件中定义的,而是由编译 器定义的。 129.文件中有一组整数,要求排序后输出到另一个文件中 答案: void Order(vector &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 ) { vectordata; 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<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 ; } 131. 一个链表的结点结构 struct Node { int data ; Node *next ; }; typedef struct Node Node ; 已知两个链表 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 ; } 132.已知两个链表 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 ; } 133 .分析一下这段程序的输出 (Autodesk) class B { public: B() { cout<<"default constructor"<>)和赋值操作符(=)的返回值、拷贝构造函数的参数、赋值操作符的 参数、其它情况都推荐使用引用。 135.面向对象的三个基本特征,并简单叙述之? 1. 封装:将客观事物抽象成类,每个类对自身的数据和方法实行 protection(private, protected,public) 2. 继承:广义的继承有三种实现形式:实现继承(指使用基类的属性和方法而无需额外 编码的能力)、可视继承(子窗体使用父窗体的外观和实现代码)、接口继承(仅使用属 性和方法,实现滞后到子类实现)。前两种(类继承)和后一种(对象组合=>接口继承 以及纯虚函数)构成了功能复用的两种方式。 3. 多态:是将父对象设置成为和一个或更多的他的子对象相等的技术,赋值之后,父对 象就可以根据当前赋值给它的子对象的特性以不同的方式运作。简单的说,就是一句话: 允许将子类类型的指针赋值给父类类型的指针。 136.求下面函数的返回值(微软) int func(x) { int countx = 0; while(x) { countx ++; x = x&(x-1); } return countx; } 假定 x = 9999 。 答案:8 思路:将 x 转化为 2 进制,看含有的 1 的个数。   137、写出下列代码的输出内容 #i nclude int inc(int a) { return(++a); } int multi(int*a,int*b,int*c) { return(*c=*a**b); } typedef int(FUNC1)(int in); typedef int(FUNC2) (int*,int*,int*); void show(FUNC2 fun,int arg1, int*arg2) { INCp=&inc; int temp =p(arg1); fun(&temp,&arg1, arg2); printf("%d\n",*arg2); } main() { int a; show(multi,10,&a); return 0; } 答:110 138 。编写一个 C 函数,该函数在一个字符串中找到可能的最长的子字符串,且该字符 串是由同一字符组成的。 char * search(char *cpSource, char ch) { char *cpTemp=NULL, *cpDest=NULL; int iTemp, iCount=0; while(*cpSource) { if(*cpSource == ch) { iTemp = 0; cpTemp = cpSource; while(*cpSource == ch) ++iTemp, ++cpSource; if(iTemp > iCount) iCount = iTemp, cpDest = cpTemp; if(!*cpSource) break; } ++cpSource; } return cpDest; } 139 。请编写一个 C 函数,该函数在给定的内存区域搜索给定的字符,并返回该字符所 在位置索引值。 int search(char *cpSource, int n, char ch) { int i; for(i=0; inext,并随后删除 原 next 指向的节点。 141 “、用指针的方法,将字符串 ABCD1234efgh”前后对调显示 #i nclude #i nclude #i nclude int main() { char str[] = "ABCD1234efgh"; int length = strlen(str); char * p1 = str; char * p2 = str + length - 1; while(p1 < p2) { char c = *p1; *p1 = *p2; *p2 = c; ++p1; --p2; } printf("str now is %s\n",str); system("pause"); return 0; } 142、有一分数序列:1/2,1/4,1/6,1/8……,用函数调用的方法,求此数列前 20 项的和 #i nclude double getValue() { double result = 0; int i = 2; while(i < 42) { result += 1.0 / i;//一定要使用 1.0 做除数,不能用 1,否则结果将自动转化成整 数,即 0.000000 i += 2; } return result; } int main() { printf("result is %f\n", getValue()); system("pause"); return 0; } 143、有一个数组 a[1000]存放 0--1000;要求每隔二个数删掉一个数,到末尾时循环至 开头继续进行,求最后一个被删掉的数的原始下标位置。 以 7 个数为例: {0,1,2,3,4,5,6,7} 0-->1-->2(删除)-->3-->4-->5(删除)-->6-->7-->0(删除), 如此循环直到最后一个数被删除。 方法 1:数组 #i nclude using namespace std; #define null 1000 int main() { int arr[1000]; for (int i=0;i<1000;++i) arr[i]=i; int j=0; int count=0; while(count<999) { while(arr[j%1000]==null) j=(++j)%1000; j=(++j)%1000; while(arr[j%1000]==null) j=(++j)%1000; j=(++j)%1000; while(arr[j%1000]==null) j=(++j)%1000; arr[j]=null; ++count; } while(arr[j]==null) j=(++j)%1000; cout< using namespace std; #define null 0 struct node { int data; node* next; }; int main() { node* head=new node; head->data=0; head->next=null; node* p=head; for(int i=1;i<1000;i++) { node* tmp=new node; tmp->data=i; tmp->next=null; head->next=tmp; head=head->next; } head->next=p; while(p!=p->next) { p->next->next=p->next->next->next; p=p->next->next; } cout<data; return 0; } 方法 3:通用算法 #i nclude #define MAXLINE 1000 //元素个数 /* MAXLINE 元素个数 a[] 元素数组 R[] 指针场 suffix 下标 index 返回最后的下标序号 values 返回最后的下标对应的值 start 从第几个开始 K 间隔 */ int find_n(int a[],int R[],int K,int& index,int& values,int s=0) { int suffix; int front_node,current_node; suffix=0; if(s==0) { current_node=0; front_node=MAXLINE-1; } else { current_node=s; front_node=s-1; } while(R[front_node]!=front_node) { printf("%d\n",a[current_node]); R[front_node]=R[current_node]; if(K==1) { current_node=R[front_node]; continue; } for(int i=0;i #i nclude #i nclude char *commanstring(char shortstring[], char longstring[]) { int i, j; char *substring=malloc(256); if(strstr(longstring, shortstring)!=NULL) // ……如 果 , 那 么 返 回 shortstring return shortstring; for(i=strlen(shortstring)-1;i>0; i--) //否则,开始循环计算 { for(j=0; j<=strlen(shortstring)-i; j++){ memcpy(substring, &shortstring[j], i); substring[i]='\0'; if(strstr(longstring, substring)!=NULL) return substring; } } return NULL; } main() { char *str1=malloc(256); char *str2=malloc(256); char *comman=NULL; gets(str1); gets(str2); if(strlen(str1)>strlen(str2)) //将短的字符串放前面 comman=commanstring(str2, str1); else comman=commanstring(str1, str2); printf("the longest comman string is: %s\n", comman); } 147、写一个函数比较两个字符串 str1 和 str2 的大小,若相等返回 0,若 str1 大于 str2 返回 1,若 str1 小于 str2 返回-1 int strcmp ( const char * src,const char * dst) { int ret = 0 ; while( ! (ret = *(unsigned char *)src - *(unsigned char *)dst) && *dst) { ++src; ++dst; } if ( ret < 0 ) ret = -1 ; else if ( ret > 0 ) ret = 1 ; return( ret ); } 148、判断一个字符串是不是回文 int IsReverseStr(char *aStr) { int i,j; int found=1; if(aStr==NULL) return -1; j=strlen(aStr); for(i=0;i> temp; unsigned int const size2 = temp; char str2[ size2 ]; 答:str2 定义出错,size2 非编译器期间常量,而数组定义要求长度必须为编译期常量。 171. 以下代码中的输出语句输出 0 吗,为什么? struct CLS { int m_i; CLS( int i ) : m_i(i) {} CLS() { CLS(0); } }; CLS obj; cout << obj.m_i << endl; 答:不能。在默认构造函数内部再调用带参的构造函数属用户行为而非编译器行为,亦即 仅执行函数调用,而不会执行其后的初始化表达式。只有在生成对象时,初始化表达式才 会随相应的构造函数一起调用。 172 C++中的空类,默认产生哪些类成员函数? 答: class Empty { public: Empty(); // 缺省构造函数 Empty( const Empty& ); // 拷贝构造函数 ~Empty(); // 析构函数 Empty& operator=( const Empty& ); // 赋值运算符 Empty* operator&(); // 取址运算符 const Empty* operator&() const; // 取址运算符 const }; 173 以下两条输出语句分别输出什么? float a = 1.0f; cout << (int)a << endl; cout << (int&)a << endl; cout << boolalpha << ( (int)a == (int&)a ) << endl; // 输出什么? float b = 0.0f; cout << (int)b << endl; cout << (int&)b << endl; cout << boolalpha << ( (int)b == (int&)b ) << endl; // 输出什么? 答:分别输出 false 和 true。注意转换的应用。(int)a 实际上是以浮点数 a 为参数构造了 一个整型数,该整数的值是 1,(int&)a 则是告诉编译器将 a 当作整数看(并没有做任何 实质上的转换)。因为 1 以整数形式存放和以浮点形式存放其内存数据是不一样的,因此 两者不等。对 b 的两种转换意义同上,但是 0 的整数形式和浮点形式其内存数据是一样的, 因此在这种特殊情形下,两者相等(仅仅在数值意义上)。 注意,程序的输出会显示(int&)a=1065353216,这个值是怎么来的呢?前面已经说了, 1 以浮点数形式存放在内存中,按 ieee754 规定,其内容为 0x0000803F(已考虑字节 反序)。这也就是 a 这个变量所占据的内存单元的值。当(int&)a 出现时,它相当于告诉它 “”的 上 下 文 : 把 这 块 地 址 当 做 整 数 看 待 ! 不 要 管 它 原 来 是 什 么 。 这 样 , 内 容 0x0000803F 按整数解释,其值正好就是 1065353216(十进制数)。 “通过查看汇编代码可以证实 (int)a 相当于重新构造了一个值等于 a ”的整型数 之说,而 (int&)的作用则仅仅是表达了一个类型信息,意义在于为 cout<<及==选择正确的重载 版 174、请简述以下两个 for 循环的优缺点(5 分) for (i=0; i void list::delnode(int p) { int k=1; listnode *ptr,*t; ptr=first; while(ptr->next!=NULL&&k!=p) { ptr=ptr->next; k++; } t=ptr->next; cout<<" 你已经将数据项 "<data<<"删除"<next=ptr->next->next; length--; delete t; } 在节点 P 后插入一个节点: template bool list::insert(type t,int p) { listnode *ptr; ptr=first; int k=1; while(ptr!=NULL&&knext; k++; } if(ptr==NULL&&k!=p) return false; else { listnode *tp; tp=new listnode; tp->data=t; tp->next=ptr->next; ptr->next=tp; length++; return true; } } 181 .完成下列程序 * *.*. *..*..*.. *...*...*...*... *....*....*....*....*.... *.....*.....*.....*.....*.....*..... *......*......*......*......*......*......*...... *.......*.......*.......*.......*.......*.......*.......*....... #i nclude using namespace std; const int n = 8; main() { int i; int j; int k; for(i = n; i >= 1; i--) { for(j = 0; j < n-i+1; j++) { cout<<"*"; for(k=1; k < n-i+1; k++) { cout<<"."; } } cout< using namespace std; void sort(int* arr, int n); int main() { int array[]={45,56,76,234,1,34,23,2,3}; sort(array, 9); for(int i = 0; i <= 8; i++)//曾经在这儿出界 cout<=0; --i ) // 反向遍历 array 数组 { cout << array[i] << endl; } 184 写一个函数,完成内存之间的拷贝。[考虑问题是否全面] 答: void* mymemcpy( void *dest, const void *src, size_t count ) { char* pdest = static_cast( dest ); const char* psrc = static_cast( src ); if( pdest>psrc && pdest class A { public: virtual void print(void) { cout<<"A::print()"<print(); pb->print(); pc->print(); print(a); print(b); print(c); } A::print() A::print() B::print() C::print() A::print() B::print() C::print() A::print() A::print() A::print() -------------------------------------------------------------------------- 194.程序改错 class mml { private: static unsigned int x; public: mml(){ x++; } mml(static unsigned int &) {x++;} ~mml{x--;} pulic: virtual mon() {} = 0; static unsigned int mmc(){return x;} ...... }; class nnl:public mml { private: static unsigned int y; public: nnl(){ x++; } nnl(static unsigned int &) {x++;} ~nnl{x--;} public: virtual mon() {}; static unsigned int nnc(){return y;} ...... }; 代码片断: mml* pp = new nnl; .......... delete pp; A: 基类的析构函数应该为虚函数 virtual ~mml{x--;} -------------------------------------------------------------------------- 195.101 个硬币 100 真、1 假,真假区别在于重量。请用无砝码天平称两次给出真币重还 是假币重的结论。 答: 101 个先取出 2 堆, 33,33 第一次称,如果不相等,说明有一堆重或轻 那么把重的那堆拿下来,再放另外 35 个中的 33 如果相等,说明假的重,如果不相等,新放上去的还是重的话,说明假的轻(不可能新放上去的 轻) 第一次称,如果相等的话,这 66 个肯定都是真的,从这 66 个中取出 35 个来,与剩下的没 称过的 35 个比 下面就不用说了 方法二: 第 3 题也可以拿 A(50),B(50)比一下,一样的话拿剩下的一个和真的比一下。 如果不一样,就拿其中的一堆。比如 A(50)再分成两堆 25 比一下,一样的话就在 B(50)中,不一样就在 A(50) 中,结合第一次的结果就知道了。 196.写出程序结果: void Func(char str[100]) { printf("%d\n", sizeof(str)); } 答: 4 分析: 指针长度 197.int id[sizeof(unsigned long)]; 这个对吗?为什么?? 答: 对 这个 sizeof 是编译时运算符,编译时就确定了 可以看成和机器有关的常量。 198 、 sizeof 应用在结构上的情况 请看下面的结构: struct MyStruct { double dda1; char dda; int type }; 对结构 MyStruct 采用 sizeof 会出现什么结果呢?sizeof(MyStruct)为多少呢?也许你 会这样求: sizeof(MyStruct)=sizeof(double)+sizeof(char)+sizeof(int)=13 但是当在 VC 中测试上面结构的大小时,你会发现 sizeof(MyStruct)为 16。你知道为什 么在 VC 中会得出这样一个结果吗? 其实,这是 VC 对变量存储的一个特殊处理。为了提高 CPU 的存储速度,VC 对一些变量 的起始地址做了"对齐"处理。在默认情况下,VC 规定各成员变量存放的起始地址相对于 结构的起始地址的偏移量必须为该变量的类型所占用的字节数的倍数。下面列出常用类型 的对齐方式(vc6.0,32 位系统) 。 类型 对齐方式(变量存放的起始地址相对于结构的起始地址的偏移量) Char 偏移量必须为 sizeof(char)即 1 的倍数 int 偏移量必须为 sizeof(int)即 4 的倍数 float 偏移量必须为 sizeof(float)即 4 的倍数 double 偏移量必须为 sizeof(double)即 8 的倍数 Short 偏移量必须为 sizeof(short)即 2 的倍数 各成员变量在存放的时候根据在结构中出现的顺序依次申请空间,同时按照上面的对齐 方式调整位置,空缺的字节 VC 会自动填充。同时 VC 为了确保结构的大小为结构的字节 边界数(即该结构中占用最大空间的类型所占用的字节数)的倍? 199 #include "stdafx.h" Y n P }2{&k O v H `,o0 #define SQR(X) X*X int main(int argc, char* argv[]) { int a = 10; int k = 2; int m = 1; a /= SQR(k+m)/SQR(k+m); printf("%d\n",a); return 0; } 这道题目的结果是什么啊? define 只是定义而已,在编择时只是简单代换 X*X 而已,并不经过算术法则的 a /= k+m*k+m/k+m*k+m; =>a /= (k+m)*1*(k+m); =>a = a/9; =>a = 1; 200.下面的代码有什么问题? void DoSomeThing(...) { char* p; p = malloc(1024); // 分配 1K 的空间 2y x if (NULL == p) return; p = realloc(p, 2048); // 空间不够,重新分配到 2K if (NULL == p) return; } A: p = malloc(1024); 应该写成: p = (char *) malloc(1024); 没有释放 p 的空间,造成内存泄漏。 201 下面的代码有什么问题?并请给出正确的写法。 void DoSomeThing(char* p) { char str[16]; int n; assert(NULL != p); sscanf(p, "%s%d", str, n); if (0 == strcmp(str, "something")) { } } A: sscanf(p, "%s%d", str, n); 这 句 该 写 成 : sscanf(p, "%s%d", str, &n); -------------------------------------------------------------------------- 202.下面代码有什么错误? Void test1() { char string[10]; char *str1="0123456789"; strcpy(string, str1); } 数组越界 203.下面代码有什么问题? Void test2() { char string[10], str1[10]; for(i=0; i<10;i++) { str1[i] ='a'; } strcpy(string, str1); } } 数组越界 204 下面代码有什么问题?LUPA 开源社区+j H2B F,c U Void test3(char* str1) { char string[10]; if(strlen(str1)<=10) { strcpy(string, str1); } } ==数组越界 ==strcpy 拷贝的结束标志是查找字符串中的\0 因此如果字符串中没有遇到\0 的话 会一 直复制,直到遇到\0,上面的 123 都因此产生越界的情况 建议使用 strncpy 和 memcpy 205.写出运行结果: { char str[] = "world"; cout << sizeof(str) << ": "; char *p = str; cout << sizeof(p) << ": "; char i = 10; cout << sizeof(i) << ": "; void *pp = malloc(10); cout << sizeof(p) << endl; } 6:4:1:4 -------------------------------------------------------------------------- 206.C 和 C++有什么不同? 从机制上:c 是面向过程的(但 c 也可以编写面向对象的程序);c++是面向对象的, 提供了类。但是,c++编写面向对象的程序比 c 容易 从适用的方向:c 适合要求代码体积小的,效率高的场合,如嵌入式;c++适合更上层 的,复杂的; llinux 核心大部分是 c 写的,因为它是系统软件,效率要求极高。 从名称上也可以看出,c++比 c 多了+,说明 c++是 c 的超集;那为什么不叫 c+而叫 c++呢,是因为 c++比 c 来说扩充的东西太多了,所以就在 c 后面放上两个+;于是就成了 c++ C 语言是结构化编程语言,C++是面向对象编程语言。LUPA 开源社区 } n*r2C/M8f C++侧重于对象而不是过程,侧重于类的设计而不是逻辑的设计。 207 在不用第三方参数的情况下,交换两个参数的值 #include void main() { int i=60; int j=50; i=i+j; j=i-j; ) i=i-j; printf("i=%d\n",i); printf("j=%d\n",j); } 方法二: i^=j; j^=i; i^=j; 方法三: // 用加减实现,而且不会溢出 a = a+b-(b=a) 208. 下面的函数实现在一个固定的数上加上一个数,有什么错误,改正 int add_n(int n) { static int i=100; i+=n; return i; } 答: 因为 static 使得 i 的值会保留上次的值。 去掉 static 就可了 209.union a { int a_int1; double a_double; int a_int2; }; typedef struct { a a1; char y; } b; class c { double c_double; b b1; a a2; }; 输出 cout< class A { public: virtual void print(void) { cout<<"A::print()"<print(); pb->print(); pc->print(); print(a); print(b); print(c); } A: A::print() B::print() C::print() A::print() B::print() C::print() A::print() A::print() A::print() =============================================== ================================ 基础知识: 213 C++语言是在___ C ______语言的基础上发展起来的。 214 C++语言的编译单位是扩展名为____ .cpp______的____程序______文件。 215. 行尾使用注释的开始标记符为____ //_____。 216 多行注释的开始标记符和结束标记符分别为_____ /*_____和___ */_______。 217. 用于输出表达式值的标准输出流对象是____ cout_____。 218 用于从键盘上为变量输入值的标准输入流对象是__ cin______。 219. 一个完整程序中必须有一个名为____ main____的函数。 220 一个函数的函数体就是一条____复合_____语句。 221. 当执行 cin 语句时,从键盘上输入每个数据后必须接着输入一个___空白_____符, 然后才能继续输入下一个数据。 222 在 C++程序中包含一个头文件或程序文件的预编译命令为____#include ______。 223. 程序中的预处理命令是指以___#___字符开头的命令。 224. 一条表达式语句必须以___分号___作为结束符。 225. 在#include 命令中所包含的头文件,可以是系统定义的头文件,也可以是___用户 (或编程者_____定义的头文件。 226. 使用#include 命令可以包含一个头文件,也可以包含一个__程序____文件。 227.一个函数定义由__函数头______和__函数体_____两部分组成。 228.若一个函数的定义处于调用它的函数之前,则在程序开始可以省去该函数的__原 型(或声明)____语句。 229.C++头文件和源程序文件的扩展名分别为__.h ___和___.cpp ___。 230.程序文件的编译错误分为____警告(warning)____和____致命(error) ____两类。 231.当使用___ void ____保留字作为函数类型时,该函数不返回任何值。 232.当函数参数表用___ void __保留字表示时,则表示该参数表为空。 233 “.从一条函数原型语句 int fun1(void);”可知,该函数的返回类型为______,该函 数带有______个参数。 234. 当执行 cout 语句输出 endl 数据项时,将使 C++显示输出屏幕上的光标从当前位 置移动到___下一行_____的开始位置。 235. 假定 x=5,y=6,则表达式 x++*++y 的值为___35_______。 236. 假定 x=5,y=6,则表达式 x--*--y 的值为___25_______。 237. 假定 x=5,y=6,则执行表达式 y*=x++计算后,x 和 y 的值分别为___6___和__ _30 _____。 238. 假定 x=5,y=6,则执行表达式 y+=x--计算后,x 和 y 的值分别为____4__和 ___11___。 239. C++常数 0x145 对应的十进制值为___325 ___。 240. C++常数 0345 对应的十进制值为____ 229__。 241. 十进制常数 245 对应的十六进制的 C++表示为____0xF5___。 242. 十进制常数 245 对应的八进制的 C++表示为___0365 ___ 。 243. signed char 类型的值域范围是__-128__至___+127 __之间的整数。 244. int 和 float 类型的数据分别占用___ 4___和____ 4___个字节。 245. float 和 double 类型的数据分别占用____ 4___和_____8___个字节。 246. bool 和 char 类型的数据分别占用_____1____和____1___个字节。 247. unsigned short int 和 int 类型的长度分别为____ 2___和____4___。 248. ”字符串 This\’s a book.\n”的长度为_____ 15____。 249. ”字符串 \nThis\’s a pen\n\n”的长度为_____ 15_____。 250. 在 C++ ”中存储字符串 abcdef”至少需要___7 _____个字节。 251. 在 C++ ”中存储字符串 a+b=c”至少需要_____6 ___个字节。 252. 假定 x 和 y 为整型,其值分别为 16 和 5,则 x%y 和 x/y 的值分别为___1_______ 和____3____。 253. 假定 x 和 y 为整型,其值分别为 16 和 5,则 x/y 和 double(x)/y 的值分别为 ____3____和___3.2____。 254. 假定 x 是一个逻辑量,则 x && true 的值为___ x ____。 255. 假定 x 是一个逻辑量,则 x || true 的值为_____ true(或 1)_____。 256. 假定 x 是一个逻辑量,则 x && false 的值为____ false(或 0) ___。 257. 假定 x 是一个逻辑量,则 x || false 的值为 x。 258. 假定 x 是一个逻辑量,则!x || false 的值为____!x ____。 259. 假定 x 是一个逻辑量,则 x && !x 的值为____ false(或 0)____。 260. 假定 x 是一个逻辑量,则 x || !x 的值为____ true(或 1)___。 261. 设 enum Printstatus{ready,busy,error}; 则 cout<a /= (k+m)*1*(k+m); =>a = a/9; =>a = 1; 286. 以面向对象方法构造的系统,其基本单位是_____对象___。 287. 每个对象都是所属类的一个__实例__。 288. C++支持两种多态性:___编译____时的多态性和____运行__时的多态性。 289. 在 C++中,编译时的多态性是通过___重载___实现的,而运行时的多态性则是通 过___虚函数____实现的。 290. 对于类中定义的任何成员,其隐含访问权限为___ private(或私有)__。 291. 对于结构中定义的任何成员,其隐含访问权限为__ public(或公有)_。 292. 若在类的定义体中给出了一个成员函数的完整定义,则该函数属于__内联__函数。 293. 为了避免在调用成员函数时修改对象中的任何数据成员,则应在定义该成员函数时, 在函数头的后面加上__ const __ 关键字。 294. 若只需要通过一个成员函数读取数据成员的值,而不需要修改它,则应在函数头的 后面加上__ const __ 关键字。 295.判断一个字符串是不是回文 int IsReverseStr(char *aStr) { int i,j; int found=1; if(aStr==NULL) return -1; j=strlen(aStr); for(i=0;i> temp; unsigned int const size2 = temp; char str2[ size2 ]; 答:str2 定义出错,size2 非编译器期间常量,而数组定义要求长度必须为编译期常量。 303.以下反向遍历 array 数组的方法有什么错误? vector array; array.push_back( 1 ); array.push_back( 2 ); array.push_back( 3 ); for( vector::size_type i=array.size()-1; i>=0; --i ) // 反向遍历 array 数组 { cout << array[i] << endl; } 答 : 首 先 数 组 定 义 有 误 , 应 加 上 类 型 参 数 : vector array 。 其 次 vector::size_type 被定义为 unsigned int,即无符号数,这样做为循环变量的 i 为 0 时 再减 1 就会变成最大的整数,导致循环失去控制。 304. 以下代码中的输出语句输出 0 吗,为什么? struct CLS { int m_i; CLS( int i ) : m_i(i) {} CLS() { CLS(0); } }; CLS obj; cout << obj.m_i << endl; 答:不能。在默认构造函数内部再调用带参的构造函数属用户行为而非编译器行为,亦即 仅执行函数调用,而不会执行其后的初始化表达式。只有在生成对象时,初始化表达式才 会随相应的构造函数一起调用。 =============================================== =============================================== =============================================== ================= 1.new、delete、malloc、free 关系 delete 会调用对象的析构函数,和 new 对应 free 只会释放内存,new 调用构造函数 。 malloc 与 free 是 C++/C 语言的标准库函数,new/delete 是 C++的运算符。它们都可 用于申请动态内存和释放内存。对于非内部数据类型的对象而言,光用 maloc/free 无法 满足动态对象的要求。对象在创建的同时要自动执行构造函数,对象在消亡之前要自动执 行析构函数。由于 malloc/free 是库函数而不是运算符,不在编译器控制权限之内,不能 够把执行构造函数和析构函数的任务强加于 malloc/free。因此 C++语言需要一个能完 成动态内存分配和初始化工作的运算符 new,以及一个能完成清理与释放内存工作的运 算符 delete。注意 new/delete 不是库函数。 2.delete 与 delete []区别 delete 只会调用一次析构函数,而 delete[]会调用每一个成员的析构函数。在 More Effective C++ “中有更为详细的解释: 当 delete 操作符用于数组时,它为每个数组元 素调用析构函数,然后调用 operatordelete ”来释放内存。delete 与 New 配套,delete []与 new []配套 MemTest*mTest1=newMemTest[10]; MemTest*mTest2=newMemTest; int*pInt1=newint[10]; int*pInt2=newint; delete[]pInt1; //-1- delete[]pInt2; //-2- delete[]mTest1;//-3- delete[]mTest2;//-4- 在-4-处报错。 这就说明:对于内建简单数据类型,delete 和 delete[]功能是相同的。对于自定义的复 杂数据类型,delete 和 delete[]不能互用。delete[]删除一个数组,delete 删除一个指 针简单来说,用 new 分配的内存用 delete 删除用 new[]分配的内存用 delete[]删除 delete[]会调用数组元素的析构函数。内部数据类型没有析构函数,所以问题不大。如果 你在用 delete 时没用括号,delete 就会认为指向的是单个对象,否则,它就会认为指 向的是一个数组。 3.C C++ JAVA 共同点,不同之处? 面向对象,指针,多重继承,内存管理。 4.继承优缺点。 类继承是在编译时刻静态定义的,且可直接使用,类继承可以较方便地改变父类的实现。 但是类继承也有一些不足之处。首先,因为继承在编译时刻就定义了,所以无法在运行时 刻改变从父类继承的实现。更糟的是,父类通常至少定义了子类的部分行为,父类的任何 改变都可能影响子类的行为。如果继承下来的实现不适合解决新的问题,则父类必须重写 或被其他更适合的类替换。这种依赖关系限制了灵活性并最终限制了复用性。 (待补充) 5.C++有哪些性质(面向对象特点) 封装,继承和多态。 在面向对象程序设计语言中,封装是利用可重用成分构造软件系统的特性,它不仅支持 系统的可重用性,而且还有利于提高系统的可扩充性;消息传递可以实现发送一个通用 的消息而调用不同的方法;封装是实现信息隐蔽的一种技术,其目的是使类的定义和实 现分离。 6.子类析构时要调用父类的析构函数吗? 析构函数调用的次序是先派生类的析构后基类的析构,也就是说在基类的的析构调用的 时候,派生类的信息已经全部销毁了定义一个对象时先调用基类的构造函数、然后调用派 生类的构造函数;析构的时候恰好相反:先调用派生类的析构函数、然后调用基类的析构 函数 JAVA 无析构函数深拷贝和浅拷贝 7.多态,虚函数,纯虚函数 8.求下面函数的返回值(微软) int func(x) { int countx = 0; while(x) { countx ++; x = x&(x-1); } return countx; } 假定 x = 9999 。 答案:8 思路:将 x 转化为 2 进制,看含有的 1 的个数。 9. “”“”什么是 引用 ?申明和使用 引用 要注意哪些问题? “”答:引用就是某个目标变量的 别名 (alias),对应用的操作与对变量直接操作效果完全 相同。申明一个引用的时候,切记要对其进行初始化。引用声明完毕后,相当于目标变量 名有两个名称,即该目标原名称和引用名,不能再把该引用名作为其他变量名的别名。声 明一个引用,不是新定义了一个变量,它只表示该引用名是目标变量名的一个别名,它 本身不是一种数据类型,因此引用本身不占存储单元,系统也不给引用分配存储单元。不 能建立数组的引用。 10. “”将 引用 作为函数参数有哪些特点? (1)传递引用给函数与传递指针的效果是一样的。这时,被调函数的形参就成为原来主 调函数中的实参变量或对象的一个别名来使用,所以在被调函数中对形参变量的操作就 是对其相应的目标对象(在主调函数中)的操作。 (2)使用引用传递函数的参数,在内存中并没有产生实参的副本,它是直接对实参操作; 而使用一般变量传递函数的参数,当发生函数调用时,需要给形参分配存储单元,形参 变量是实参变量的副本;如果传递的是对象,还将调用拷贝构造函数。因此,当参数传递 的数据较大时,用引用比用一般变量传递参数的效率和所占空间都好。 (3)使用指针作为函数的参数虽然也能达到与使用引用的效果,但是,在被调函数中同 ”样要给形参分配存储单元,且需要重复使用 * ”指针变量名 的形式进行运算,这很容易产 生错误且程序的阅读性较差;另一方面,在主调函数的调用点处,必须用变量的地址作 为实参。而引用更容易使用,更清晰。 11. “”在什么时候需要使用 常引用 ?  如果既要利用引用提高程序的效率,又要保护传递给函数的数据不在函数中被改变,就 应使用常引用。常引用声明方式:const 类型标识符 &引用名=目标变量名; 例 1 int a ; const int &ra=a; ra=1; //错误 a=1; //正确 例 2 string foo( ); void bar(string & s); 那么下面的表达式将是非法的: bar(foo( )); bar(“hello world”); 原因在于 foo( ) ”和 hello world”串都会产生一个临时对象,而在 C++中,这些临时对 象都是 const 类型的。因此上面的表达式就是试图将一个 const 类型的对象转换为非 const 类型,这是非法的。引用型参数应该在能被定义为 const 的情况下,尽量定义为 const 。 12. “”将 引用 作为函数返回值类型的格式、好处和需要遵守的规则? 格式:类型标识符 &函数名(形参列表及类型说明){ // 函数体 } 好处:在内存中不产生被返回值的副本;(注意:正是因为这点原因,所以返回一个局 部变量的引用是不可取的。因为随着该局部变量生存期的结束,相应的引用也会失效,产 生 runtime error! 注意事项: (1)不能返回局部变量的引用。这条可以参照 Effective C++[1]的 Item 31。主要原因 ””是局部变量会在函数返回后被销毁,因此被返回的引用就成为了 无所指 的引用,程序 会进入未知状态。 (2)不能返回函数内部 new 分配的内存的引用。这条可以参照 Effective C++[1]的 Item 31。虽然不存在局部变量的被动销毁问题,可对于这种情况(返回函数内部 new 分配内存的引用),又面临其它尴尬局面。例如,被函数返回的引用只是作为一个临时变 量出现,而没有被赋予一个实际的变量,那么这个引用所指向的空间(由 new 分配)就 无法释放,造成 memory leak。 (3)可以返回类成员的引用,但最好是 const。这条原则可以参照 Effective C++[1]的 Item 30。主要原因是当对象的属性是与某种业务规则(business rule)相关联的时候, 其赋值常常与某些其它属性或者对象的状态有关,因此有必要将赋值操作封装在一个业 务规则当中。如果其它对象可以获得该属性的非常量引用(或指针),那么对该属性的单 纯赋值就会破坏业务规则的完整性。 (4 “”)流操作符重载返回值申明为 引用 的作用: 流操作符<<和>>,这两个操作符常常希望被连续使用,例如:cout << "hello" << endl; 因此这两个操作符的返回值应该是一个仍然支持这两个操作符的流引用。可选的 其它方案包括:返回一个流对象和返回一个流对象指针。但是对于返回一个流对象,程序 必须重新(拷贝)构造一个新的流对象,也就是说,连续的两个<<操作符实际上是针 对不同对象的!这无法让人接受。对于返回一个流指针则不能连续使用<<操作符。因此 返回一个流对象引用是惟一选择。这个唯一选择很关键,它说明了引用的重要性以及无可 替代性,也许这就是 C++ 语言中引入引用这个概念的原因吧。 赋值操作符=。这个操作 符象流操作符一样,是可以连续使用的,例如:x = j = 10;或者(x=10)=100;赋值操 作符的返回值必须是一个左值,以便可以被继续赋值。因此引用成了这个操作符的惟一返 回值选择。 例 3 #i nclude int &put(int n); int vals[10]; int error=-1; void main() { put(0)=10; //以 put(0)函数值作为左值,等价于 vals[0]=10; put(9)=20; //以 put(9)函数值作为左值,等价于 vals[9]=20; cout<=0 && n<=9 ) return vals[n]; else { cout<<"subscript error"; return error; } } (5)在另外的一些操作符中,却千万不能返回引用:+-*/ 四则运算符。它们不能返回引 用,Effective C++[1]的 Item23 详细的讨论了这个问题。主要原因是这四个操作符没 有 side effect,因此,它们必须构造一个对象作为返回值,可选的方案包括:返回一个 对象、返回一个局部变量的引用,返回一个 new 分配的对象的引用、返回一个静态对象引 用。根据前面提到的引用作为返回值的三个规则,第 2、3 两个方案都被否决了。静态对象 的引用又因为((a+b) == (c+d))会永远为 true 而导致错误。所以可选的只剩下返回一个 对象了。 13.“ ”引用 与多态的关系? 引用是除指针外另一个可以产生多态效果的手段。这意味着,一个基类的引用可以指向它 的派生类实例。例 4 Class A; Class B : Class A{...}; B b; A& ref = b; 14.“ ”引用 与指针的区别是什么? 指针通过某个指针变量指向一个对象后,对它所指向的变量间接操作。程序中使用指针, 程序的可读性差;而引用本身就是目标变量的别名,对引用的操作就是对目标变量的操 作。此外,就是上面提到的对函数传 ref 和 pointer 的区别。 15. “”什么时候需要 引用 ? 流操作符<<和>>、赋值操作符=的返回值、拷贝构造函数的参数、赋值操作符=的参数、 其它情况都推荐使用引用。以上 2-8 参考:http://develop.csai.cn/c/NO0000021.htm 16.结构与联合有和区别? (1). 结构和联合都是由多个不同的数据类型成员组成, 但在任何同一时刻, 联合中只存放 了一个被选中的成员(所有成员共用一块地址空间), 而结构的所有成员都存在(不同成 员的存放地址不同)。 (2). 对于联合的不同成员赋值, 将会对其它成员重写, 原来成员的值就不存在了, 而对于 结构的不同成员赋值是互不影响的。 17. “”面关于 联合 的题目的输出? a) #i nclude union { int i; char x[2]; }a; void main() { a.x[0] = 10; a.x[1] = 1; printf(“%d”,a.i); } 答案:266 (低位低地址,高位高地址,内存占用情况是 Ox010A) b) main() { union{ /*定义一个联合*/ int i; struct{ /*在联合中定义一个结构*/ char first; char second; }half; }number; number.i=0×4241; /*联合成员赋值*/ printf(“%c%cn”, number.half.first, mumber.half.second); number.half.first=’a'; /*联合中结构成员赋值*/ number.half.second=’b'; printf(“%xn”, number.i); getch(); } 答案: AB (0×41 ’对应 A',是低位;Ox42 ’对应 B',是高位) 6261 (number.i 和 number.half 共用一块地址空间) 18.关联、聚合(Aggregation)以及组合(Composition)的区别? 涉及到 UML“”“”中的一些概念:关联是表示两个类的一般性联系,比如 学生 和 老师 就是 一种关联关系;聚合表示 has-a 的关系,是一种相对松散的关系,聚合类不需要对被聚 合类负责,如下图所示,用空的菱形表示聚合关系:从实现的角度讲,聚合可以表示为: class A {…} class B { A* a; …..} 而组合表示 contains-a 的关系,关联性强于聚合:组合类与被组合类有相同的生命周期, 组合类要对被组合类负责,采用实心的菱形表示组合关系:实现的形式是: class A{…} class B{ A a; …} 参考文章:http://www.cnitblog.com/Lily/archive/2006/02/23/6860.html http://www.vckbase.com/document/viewdoc/?id=422 19.面向对象的三个基本特征,并简单叙述之? 1. 封装:将客观事物抽象成类,每个类对自身的数据和方法实行 protection(private, protected,public) 2. 继承:广义的继承有三种实现形式:实现继承(指使用基类的属性和方法而无需额外 编码的能力)、可视继承(子窗体使用父窗体的外观和实现代码)、接口继承(仅使用属 性和方法,实现滞后到子类实现)。前两种(类继承)和后一种(对象组合=>接口继承 以及纯虚函数)构成了功能复用的两种方式。 3. 多态:是将父对象设置成为和一个或更多的他的子对象相等的技术,赋值之后,父对 象就可以根据当前赋值给它的子对象的特性以不同的方式运作。简单的说,就是一句话: 允许将子类类型的指针赋值给父类类型的指针。 20.重载(overload)和重写(overried “”,有的书也叫做 覆盖 )的区别? 常考的题目。从定义上来说: 重载:是指允许存在多个同名函数,而这些函数的参数表不同(或许参数个数不同,或 许参数类型不同,或许两者都不同)。 重写:是指子类重新定义父类虚函数的方法。 从实现原理上来说: 重载:编译器根据函数不同的参数表,对同名函数的名称做修饰,然后这些同名函数就 成了不同的函数(至少对于编译器来说是这样的)。如,有两个同名函数:function func(p:integer):integer;和 function func(p:string):integer;。那么编译器做过修饰后 的函数名称可能是这样的:int_func、str_func。对于这两个函数的调用,在编译器间就 已经确定了,是静态的。也就是说,它们的地址在编译期就绑定了(早绑定),因此,重 载和多态无关! 重写:和多态真正相关。当子类重新定义了父类的虚函数后,父类指针根据赋给它的不同 的子类指针,动态的调用属于子类的该函数,这样的函数调用在编译期间是无法确定的 (调用的子类的虚函数的地址无法给出)。因此,这样的函数地址是在运行期绑定的(晚 绑定)。 21.多态的作用? 主要是两个: 1. 隐藏实现细节,使得代码能够模块化;扩展代码模块,实现代码重用; 2. 接口重用:为了类在继承和派生的时候,保证使用家族中任一类的实例的某一属性时 的正确调用。 22.Ado 与 Ado.net 的相同与不同? “除了 能够让应用程序处理存储于 DBMS “中的数据 这一基本相似点外,两者没有太多共 同之处。但是 Ado 使用 OLE DB 接口并基于微软的 COM 技术,而 ADO.NET 拥有自己 的 ADO.NET 接口并且基于微软的.NET 体系架构。众所周知.NET 体系不同于 COM 体系, ADO.NET 接口也就完全不同于 ADO 和 OLE DB 接口,这也就是说 ADO.NET 和 ADO 是两种数据访问方式。ADO.net 提供对 XML 的支持。 23.New delete 与 malloc free 的联系与区别? 答案:都是在堆(heap)上进行动态的内存操作。用 malloc 函数需要指定内存分配的字节 数并且不能初始化对象,new 会自动调用对象的构造函数。delete 会调用对象的 destructor,而 free 不会调用对象的 destructor. 24.#define DOUBLE(x) x+x ,i = 5*DOUBLE(5) ; i 是多少? 答案:i 为 30。 25.有哪几种情况只能用 intialization list 而不能用 assignment? 答案:当类中含有 const、reference 成员变量;基类的构造函数都需要初始化表。 26. C++是不是类型安全的? 答案:不是。两个不同类型的指针之间可以强制转换(用 reinterpret cast)。C#是类型 安全的。 27. main 函数执行以前,还会执行什么代码? 答案:全局对象的构造函数会在 main 函数之前执行。 28. 描述内存分配方式以及它们的区别? 1 ) 从静态存储区域分配。内存在程序编译的时候就已经分配好,这块内存在程序的整个 运行期间都存在。例如全局变量,static 变量。 2 ) 在栈上创建。在执行函数时,函数内局部变量的存储单元都可以在栈上创建,函数执 行结束时这些存储单元自动被释放。栈内存分配运算内置于处理器的指令集。 3 ) 从堆上分配,亦称动态内存分配。程序在运行的时候用 malloc 或 new 申请任意多 少的内存,程序员自己负责在何时用 free 或 delete 释放内存。动态内存的生存期由程序 员决定,使用非常灵活,但问题也最多。 29.struct 和 class 的区别 答案:struct 的成员默认是公有的,而类的成员默认是私有的。struct 和 class 在其他 方面是功能相当的。从感情上讲,大多数的开发者感到类和结构有很大的差别。感觉上结 构仅仅象一堆缺乏封装和功能的开放的内存位,而类就象活的并且可靠的社会成员,它 有智能服务,有牢固的封装屏障和一个良好定义的接口。既然大多数人都这么认为,那么 只有在你的类有很少的方法并且有公有数据(这种事情在良好设计的系统中是存在的!) 时,你也许应该使用 struct 关键字,否则,你应该使用 class 关键字。 30.当一个类 A 中没有任何成员变量与成员函数,这时 sizeof(A)的值是多少? 答案:如果不是零,请解释一下编译器为什么没有让它为零。(Autodesk)肯定不是零。 举个反例,如果是零的话,声明一个 class A[10]对象数组,而每一个对象占用的空间是 零,这时就没办法区分 A[0],A[1]…了。 31. 在 8086 汇编下,逻辑地址和物理地址是怎样转换的?(Intel) 答案:通用寄存器给出的地址,是段内偏移地址,相应段寄存器地址*10H+通用寄存器 内地址,就得到了真正要访问的地址。 32. 比较 C++中的 4 种类型转换方式? 请参考:http://blog.csdn.net/wfwd/archive/2006/05/30/763785.aspx,重点是 static_cast, dynamic_cast 和 reinterpret_cast 的区别和应用。 dynamic_casts 在帮助你浏览继承层次上是有限制的。它不能被用于缺乏虚函数的类型 上,它被用于安全地沿着类的继承关系向下进行类型转换。如你想在没有继承关系的类型 中进行转换,你可能想到 static_cast 33.分别写出 BOOL,int,float,指针类型的变量 a “”与 零 的比较语句。 答案: BOOL : if ( !a ) or if(a) int : if ( a == 0) float : const EXPRESSION EXP = 0.000001 if ( a < EXP && a >-EXP) pointer : if ( a != NULL) or if(a == NULL) 34.请说出 const 与#define 相比,有何优点? 答案: Const 作用:定义常量、修饰函数参数、修饰函数返回值三个作用。被 Const 修饰的东西 都受到强制保护,可以预防意外的变动,能提高程序的健壮性。 1 ) const 常量有数据类型,而宏常量没有数据类型。编译器可以对前者进行类型安全检 查。而对后者只进行字符替换,没有类型安全检查,并且在字符替换可能会产生意料不到 的错误。 2 ) 有些集成化的调试工具可以对 const 常量进行调试,但是不能对宏常量进行调试。 35.简述数组与指针的区别? 数组要么在静态存储区被创建(如全局数组),要么在栈上被创建。指针可以随时指向任 意类型的内存块。 (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 字节 } 36.类成员函数的重载、覆盖和隐藏区别? 答案:a.成员函数被重载的特征: (1)相同的范围(在同一个类中); (2)函数名字相同; (3)参数不同; (4)virtual 关键字可有可无。 b.覆盖是指派生类函数覆盖基类函数,特征是: (1)不同的范围(分别位于派生类与基类); (2)函数名字相同; (3)参数相同; (4)基类函数必须有 virtual 关键字。 c.“ ”隐藏 是指派生类的函数屏蔽了与其同名的基类函数,规则如下: (1)如果派生类的函数与基类的函数同名,但是参数不同。此时,不论有无 virtual 关 键字,基类的函数将被隐藏(注意别与重载混淆)。 (2)如果派生类的函数与基类的函数同名,并且参数也相同,但是基类函数没有 virtual 关键字。此时,基类的函数被隐藏(注意别与覆盖混淆) 37.求出两个数中的较大这 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 38.如何打印出当前源文件的文件名以及源文件的当前行号? 答案: cout << __FILE__ ; cout<<__LINE__ ; __FILE__和__LINE__是系统预定义宏,这种宏并不是在某个文件中定义的,而是由编译 器定义的。 39. 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. 40.如何判断一段程序是由 C 编译程序还是由 C++编译程序编译的? 答案: #ifdef __cplusplus cout<<“c++”; #else cout<<“c”; #endif 41.文件中有一组整数,要求排序后输出到另一个文件中 答案: #i nclude #i nclude using namespace std; void Order(vector& data) //bubble sort { 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 ) { vectordata; 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< instance of B) { cout<<"constructed by parameter " << data < maxnumber ) { sec_max = maxnumber ; maxnumber = data[i] ; } else { if ( data[i] > sec_max ) sec_max = data[i] ; } } return sec_max ; } 44.写一个在一个字符串(n)中寻找一个子串(m)第一个位置的函数。 KMP 算法效率最好,时间复杂度是O(n+m),详见: http://www.zhanglihai.com/blog/c_335_kmp.html 46.多重继承的内存分配问题: 比如有 class A : public class B, public class C {} 那么 A 的内存结构大致是怎么样的? 这个是 compiler-dependent 的, 不同的实现其细节可能不同。如果不考虑有虚函数、虚 继承的话就相当简单;否则的话,相当复杂。可以参考《深入探索 C++对象模型》,或者: http://blog.csdn.net/rainlight/archive/2006/03/03/614792.aspx http://msdn.microsoft.com/archive/default.asp?url=/archive/en- us/dnarvc/html/jangrayhood.asp 47.如何判断一个单链表是有环的?(注意不能用标志位,最多只能用两个额外指针) struct node { char val; node* next;} bool check(const node* head) {} //return false : 无环;true: 有环一种 O(n)的 办法就是(搞两个指针,一个每次递增一步,一个每次递增两步,如果有环的话两者必 然重合,反之亦然): bool check(const node* head) { if(head==NULL) return false; node *low=head, *fast=head->next; while(fast!=NULL && fast->next!=NULL) { low=low->next; fast=fast->next->next; if(low==fast) return true; } return false; } 48.指针找错题 分析这些面试题,本身包含很强的趣味性;而作为一名研发人员,通过对这些面试题的深 入剖析则可进一步增强自身的内功。   2. 找错题 试题 1: 以下是引用片段: void test1() //数组越界   {   char string[10];   char* str1 = “0123456789″;   strcpy( string, str1 );   }   试题 2:  以下是引用片段:  void test2()   {   char string[10], str1[10];   int i;   for(i=0; i<10; i++)   {   str1= 'a';   }   strcpy( string, str1 );   }   试题 3:   以下是引用片段: void test3(char* str1)   {   char string[10];   if( strlen( str1 ) <= 10 )   {   strcpy( string, str1 );   }   }   解答:   试题 1 字符串 str1 需要 11 个字节才能存放下( ’包括末尾的 \0’),而 string 只有 10 个字节的空间,strcpy 会导致数组越界;对试题 2,如果面试者指出字符数组 str1 不能 在数组内结束可以给 3 分;如果面试者指出 strcpy(string,str1) 调用使得从 str1 内存起 复制到 string 内存起所复制的字节数具有不确定性可以给 7 分,在此基础上指出库函数 strcpy 工作方式的给 10 分; 对试题 3,if(strlen(str1) <= 10)应改为 if(strlen(str1) <10),因为 strlen 的结果未 ’统计 \0’所占用的 1 个字节。剖析:考查对基本功的掌握   (1) ’字符串以 \0’结尾;   (2)对数组越界把握的敏感度;   (3)库函数 strcpy 的工作方式, 49.如果编写一个标准 strcpy 函数 总分值为 10,下面给出几个不同得分的答案:2 分 以下是引用片段: void strcpy( char *strDest, char *strSrc )   {   while( (*strDest++ = * strSrc++) != ‘\0’ );   }   4 分 以下是引用片段:  void strcpy( char *strDest, const char *strSrc )   //将源字符串加 const,表明其为输入参数,加 2 分   {   while( (*strDest++ = * strSrc++) != ‘\0’ );   }   7 分 以下是引用片段: void strcpy(char *strDest, const char *strSrc)   {   //对源地址和目的地址加非 0 断言,加 3 分   assert( (strDest != NULL) &&(strSrc != NULL) );   while( (*strDest++ = * strSrc++) != ‘\0’ );   }   10 分 以下是引用片段: //为了实现链式操作,将目的地址返回,加 3 分!   char * strcpy( char *strDest, const char *strSrc )   {   assert( (strDest != NULL) &&(strSrc != NULL) );   char *address = strDest;   while( (*strDest++ = * strSrc++) != ‘\0’ );   return address;   }   从 2 分到 10 分的几个答案我们可以清楚的看到,小小的 strcpy 竟然暗藏着这么多 玄机,真不是盖的!需要多么扎实的基本功才能写一个完美的 strcpy 啊!   (4)对 strlen 的掌握,它没有包括字符串末尾的'\0'。   读者看了不同分值的 strcpy 版本,应该也可以写出一个 10 分的 strlen 函数了,完 美的版本为: int strlen( const char *str ) //输入参数 const 以下是引用片段:  {   assert( strt != NULL ); //断言字符串地址非 0   int len=0; //注,一定要初始化。   while( (*str++) != '\0' )   {   len++;   }   return len;   }   试题 4:以下是引用片段: void GetMemory( char *p )   {   p = (char *) malloc( 100 );   }   void Test( void )   {   char *str = NULL;   GetMemory( str );   strcpy( str, "hello world" );   printf( str );   }   试题 5:  以下是引用片段: char *GetMemory( void )   {   char p[] = "hello world";   return p;   }   void Test( void )   {   char *str = NULL;   str = GetMemory();   printf( str );   }   试题 6:以下是引用片段: void GetMemory( char **p, int num )   {   *p = (char *) malloc( num );   }   void Test( void )   {   char *str = NULL;   GetMemory( &str, 100 );   strcpy( str, "hello" );   printf( str );   }   试题 7:以下是引用片段:  void Test( void )   {   char *str = (char *) malloc( 100 );   strcpy( str, "hello" );   free( str );   ... //省略的其它语句   }   解答:试题 4 传入中 GetMemory( char *p )函数的形参为字符串指针,在函数内 部修改形参并不能真正的改变传入形参的值,执行完   char *str = NULL;   GetMemory( str );   后的 str 仍然为 NULL;试题 5 中   char p[] = "hello world";   return p;   的 p[]数组为函数内的局部自动变量,在函数返回后,内存已经被释放。这是许多程 序员常犯的错误,其根源在于不理解变量的生存期。   试题 6 的 GetMemory 避免了试题 4 的问题,传入 GetMemory 的参数为字符串指 针的指针,但是在 GetMemory 中执行申请内存及赋值语句 tiffanybracelets   *p = (char *) malloc( num );   后未判断内存是否申请成功,应加上:   if ( *p == NULL )   {   ...//进行申请内存失败处理   }   试题 7 存在与试题 6 同样的问题,在执行   char *str = (char *) malloc(100);   后未进行内存是否申请成功的判断;另外,在 free(str)后未置 str 为空,导致可能变 “”成一个 野 指针,应加上:   str = NULL;   试题 6 的 Test 函数中也未对 malloc 的内存进行释放。   剖析:   试题 4~7 考查面试者对内存操作的理解程度,基本功扎实的面试者一般都能正确 的回答其中 50~60 的错误。但是要完全解答正确,却也绝非易事。 软件开发网 www.mscto.com   对内存操作的考查主要集中在:   (1)指针的理解;   (2)变量的生存期及作用范围;   (3)良好的动态内存申请和释放习惯。   再看看下面的一段程序有什么错误:   以下是引用片段: swap( int* p1,int* p2 )   {   int *p;   *p = *p1;   *p1 = *p2;   *p2 = *p;   }   在 swap 函数中,p “”是一个 野 指针,有可能指向系统区,导致程序运行的崩溃。在 VC++中 DEBUG “运行时提示错误 Access Violation”。该程序应该改为 以下是引用片段: swap( int* p1,int* p2 )   {   int p;   p = *p1;   *p1 = *p2;   *p2 = p;   } 50.String 的具体实现 已知 String 类定义如下: class String { public: String(const char *str = NULL); // 通用构造函数 String(const String &another); // 拷贝构造函数 ~ String(); // 析构函数 String & operater =(const String &rhs); // 赋值函数 private: char *m_data; // 用于保存字符串 }; 尝试写出类的成员函数实现。 答案: String::String(const char *str) { if ( str == NULL ) //strlen 在参数为 NULL 时会抛异常才会有这步判断 { m_data = new char[1] ; m_data[0] = '\0' ; } else { m_data = new char[strlen(str) + 1]; strcpy(m_data,str); } } String::String(const String &another) { m_data = new char[strlen(another.m_data) + 1]; strcpy(m_data,other.m_data); } String& String::operator =(const String &rhs) { if ( this == &rhs) return *this ; delete []m_data; //删除原来的数据,新开一块内存 m_data = new char[strlen(rhs.m_data) + 1]; strcpy(m_data,rhs.m_data); return *this ; } String::~String() { delete []m_data ; } 51.h 头文件中的 ifndef/define/endif 的作用? 答:防止该头文件被重复引用。 52.#i nclude 与 #i nclude “file.h”的区别? 答:前者是从 Standard Library 的路径寻找和引用 file.h,而后者是从当前工作路径搜 寻并引用 file.h。 53.在 C++ 程序中调用被 C 编译器编译后的函数,为什么要加 extern “C”? C++语言支持函数重载,C 语言不支持函数重载。C++提供了 C 连接交换指定符号 extern “C” 解决名字匹配问题。 首先,作为 extern 是 C/C++语言中表明函数和全局变量作用范围(可见性)的关键字, 该关键字告诉编译器,其声明的函数和变量可以在本模块或其它模块中使用。 通常,在模块的头文件中对本模块提供给其它模块引用的函数和全局变量以关键字 extern 声明。例如,如果模块 B 欲引用该模块 A 中定义的全局变量和函数时只需包含模 块 A 的头文件即可。这样,模块 B 中调用模块 A 中的函数时,在编译阶段,模块 B 虽然 找不到该函数,但是并不会报错;它会在连接阶段中从模块 A 编译生成的目标代码中找 到此函数 extern “C”是连接申明(linkage declaration),被 extern “C”修饰的变量和函数是按照 C 语言方式编译和连接的,来看看 C++中对类似 C 的函数是怎样编译的: 作为一种面向对象的语言,C++支持函数重载,而过程式语言 C 则不支持。函数被 C++ 编译后在符号库中的名字与 C 语言的不同。例如,假设某个函数的原型为: void foo( int x, int y );    该函数被 C 编译器编译后在符号库中的名字为_foo,而 C++编译器则会产生像 _foo_int_int 之类的名字(不同的编译器可能生成的名字不同,但是都采用了相同的机 “制,生成的新名字称为 mangled name”)。 _foo_int_int 这样的名字包含了函数名、函数参数数量及类型信息,C++就是靠这种机 制来实现函数重载的。例如,在 C++中,函数 void foo( int x, int y )与 void foo( int x, float y )编译生成的符号是不相同的,后者为_foo_int_float。 同样地,C++中的变量除支持局部变量外,还支持类成员变量和全局变量。用户所编写 ”程序的类成员变量可能与全局变量同名,我们以 .”来区分。而本质上,编译器在进行编 译时,与函数的处理相似,也为类中的变量取了一个独一无二的名字,这个名字与用户 程序中同名的全局变量名字不同。 未加 extern “C”声明时的连接方式 假设在 C++中,模块 A 的头文件如下: // 模块 A 头文件 moduleA.h #ifndef MODULE_A_H #define MODULE_A_H int foo( int x, int y ); #endif   在模块 B 中引用该函数: // 模块 B 实现文件 moduleB.cpp #i nclude “moduleA.h” foo(2,3); 加 extern “C”声明后的编译和连接方式 加 extern “C”声明后,模块 A 的头文件变为: // 模块 A 头文件 moduleA.h #ifndef MODULE_A_H #define MODULE_A_H extern “C” int foo( int x, int y ); #endif   在模块 B 的实现文件中仍然调用 foo( 2,3 ),其结果是: (1)模块 A 编译生成 foo 的目标代码时,没有对其名字进行特殊处理,采用了 C 语言 的方式; (2)连接器在为模块 B 的目标代码寻找 foo(2,3)调用时,寻找的是未经修改的符号名 _foo。 如果在模块 A 中函数声明了 foo 为 extern “C”类型,而模块 B 中包含的是 extern int foo( int x, int y ) ,则模块 B 找不到模块 A 中的函数;反之亦然。 所以,可以用一句话概括 extern “C”这个声明的真实目的(任何语言中的任何语法特性 的诞生都不是随意而为的,来源于真实世界的需求驱动。我们在思考问题时,不能只停留 在这个语言是怎么做的,还要问一问它为什么要这么做,动机是什么,这样我们可以更 深入地理解许多问题):实现 C++与 C 及其它语言的混合编程。   明白了 C++中 extern “C”的设立动机,我们下面来具体分析 extern “C”通常的使用技 巧: extern “C”的惯用法 (1)在 C++中引用 C 语言中的函数和变量,在包含 C 语言头文件(假设为 cExample.h)时,需进行下列处理: extern “C” { #i nclude “cExample.h” } 而在 C 语言的头文件中,对其外部函数只能指定为 extern 类型,C 语言中不支持 extern “C”声明,在.c 文件中包含了 extern “C”时会出现编译语法错误。 C++引用 C 函数例子工程中包含的三个文件的源代码如下: /* c 语言头文件:cExample.h */ #ifndef C_EXAMPLE_H #define C_EXAMPLE_H extern int add(int x,int y); #endif /* c 语言实现文件:cExample.c */ #i nclude “cExample.h” int add( int x, int y ) { return x + y; } // c++实现文件,调用 add:cppFile.cpp extern “C” { #i nclude “cExample.h” } int main(int argc, char* argv[]) { add(2,3); return 0; } 如果 C++调用一个 C 语言编写的.DLL 时,当包括.DLL 的头文件或声明接口函数时,应 加 extern “C” { }。 (2)在 C 中引用 C++语言中的函数和变量时,C++的头文件需添加 extern “C”,但 是在 C 语言中不能直接引用声明了 extern “C”的该头文件,应该仅将 C 文件中将 C++ 中定义的 extern “C”函数声明为 extern 类型。 C 引用 C++函数例子工程中包含的三个文件的源代码如下: //C++ 头文件 cppExample.h #ifndef CPP_EXAMPLE_H #define CPP_EXAMPLE_H extern “C” int add( int x, int y ); #endif //C++ 实现文件 cppExample.cpp #i nclude “cppExample.h” int add( int x, int y ) { return x + y; } /* C 实现文件 cFile.c /* 这样会编译出错:#i nclude “cExample.h” */ int main( int argc, char* argv[] ) { add( 2, 3 ); return 0; } 15 题目的解答请参考《C++中 extern “C”含义深层探索》注解: 几道 c 笔试题(含参考答案) 1. What is displayed when f() is called given the code: class Number { public: string type; Number(): type(“void”) { } explicit Number(short) : type(“short”) { } Number(int) : type(“int”) { } }; void Show(const Number& n) { cout << n.type; } void f() { short s = 42; Show(s); } a) void b) short c) int d) None of the above 2. Which is the correct output for the following code double dArray[2] = {4, 8}, *p, *q; p = &dArray[0]; q = p + 1; cout << q – p << endl; cout << (int)q - (int)p << endl; a) 1 and 8 b) 8 and 4 c) 4 and 8 d) 8 and 1 第一个选 C; 虽然传入的是 short 类型,但是 short 类型的构造函数被生命被 explicit,也就是只能显 示类型转换,不能使用隐式类型转换。 第二个选 A; 第一个是指针加减,按照的是指向地址类型的加减,只跟类型位置有关,q 和 p 指向的 数据类型以实际数据类型来算差一个位置,因此是 1。而第二个加减是实际指针值得加减, 在内存中一个 double 类型占据 8 个字节,因此是 8 54.Sony 笔试题   1.完成下列程序   *   *.*.   *..*..*..   *...*...*...*...   *....*....*....*....*....   *.....*.....*.....*.....*.....*.....   *......*......*......*......*......*......*......   *.......*.......*.......*.......*.......*.......*.......*.......   #include   #define N 8   int main()   {    int i;    int j;    int k;    ---------------------------------------------------------    | |    | |    | |    ---------------------------------------------------------    return 0;   }   2.完成程序,实现对数组的降序排序   #include   void sort( );   int main()   {    int array[]={45,56,76,234,1,34,23,2,3} ; //数字任//意给出    sort( );    return 0;   }   void sort( )   {    ____________________________________    | |    | |    |-----------------------------------------------------|   }   3.费波那其数列,1,1,2,3,5……编写程序求第十项。可以用递归,也可以用 其 他方法,但要说明你选择的理由。   #include   int Pheponatch(int);   int main()   {    printf("The 10th is %d",Pheponatch(10));    return 0;   }   int Pheponatch(int N)   {   --------------------------------   | |   | |   --------------------------------   }   4.下列程序运行时会崩溃,请找出错误并改正,并且说明原因。   #include   #include   typedef struct{    TNode* left;    TNode* right;    int value;   } TNode;   TNode* root=NULL;   void append(int N);   int main()   {    append(63);    append(45);    append(32);    append(77);    append(96);    append(21);    append(17); // Again, 数字任意给出   }   void append(int N)   {    TNode* NewNode=(TNode *)malloc(sizeof(TNode));    NewNode->value=N;    if(root==NULL)    {    root=NewNode;    return;    }    else    {    TNode* temp;    temp=root;    while((N>=temp.value && temp.left!=NULL) || (N !=NULL   ))    {    while(N>=temp.value && temp.left!=NULL)    temp=temp.left;    while(N    temp=temp.right;    }    if(N>=temp.value)    temp.left=NewNode;    else    temp.right=NewNode;    return;    }   } ※ 来源:• 哈工大紫丁香 http://bbs.hit.edu.cn•[FROM:219.217.233.47] ─────────────────────────────────────── ─ mengfd (Icebreaker) 于 (Sun Oct 23 14:59:59 2005) 说道: 55 请你分别画出 OSI 的七层网络结构图和 TCP/IP 的五层结构图。 应用层:为应用程序提供服务 表示层:处理在两个通信系统中交换信息的表示方式 会话层:负责维护两个结点间会话连接的建立、管理和终止,以及数据交换 传输层:向用户提供可靠的端到端服务。UDP TCP 协议。 网络层:通过路由选择算法为分组通过通信子网选择最适当的路径,以及实现拥塞控制、 网络互联等功能。数据传输单元是分组。IP 地址,路由器,IP 协议。 数据链路层:在物理层提供的服务基础上,数据链路层在通信的实体间建立数据链路连 接,传输一帧为单位的数据包(,并采用差错控制与流量控制方法,使有差错的物理线 路变成无差错的数据链路。) 物理层:传输比特流。传输单元是比特。调制解调器。 56 请你详细地解释一下 IP 协议的定义,在哪个层上面?主要有什么作用?TCP 与 UDP 呢 ? 网络层。 57.请问交换机和路由器各自的实现原理是什么?分别在哪个层次上面实现的? 交换机:数据链路层。路由器:网络层。 58. 全局变量和局部变量有什么区别?是怎么实现的?操作系统和编译器是怎么知道的 ? 59.8086 是多少位的系统?在数据总线上是怎么实现的? 8086 微处理器共有 4 个 16 位的段寄存器,在寻址内存单元时,用它们直接或间接地存 放段地址。   代码段寄存器 CS:存放当前执行的程序的段地址。   数据段寄存器 DS:存放当前执行的程序所用操作数的段地址。   堆栈段寄存器 SS:存放当前执行的程序所用堆栈的段地址。   附加段寄存器 ES:存放当前执行程序中一个辅助数据段的段地址。 由 cs:ip 构成指令地址,ss:sp 构成堆栈的栈顶地址指针。DS 和 ES 用作数据段和附加段 的段地址(段起始地址或段值) 8086/8088 微处理器的存储器管理 1.地址线(码)与寻址范围:N 条地址线 寻址范围=2N 2.8086 有 20 地址线 寻址范围为 1MB 由 00000H~FFFFFH 3. 8086 微处理器是一个 16 位结构,用户可用的寄存器均为 16 位:寻址 64KB 4. 8086/8088 采用分段的方法对存储器进行管理。具体做法是:把 1MB 的存储器空间 分成若干段,每段容量为 64KB,每段存储器的起始地址必须是一个能被 16 整除的地址 码,即在 20 位的二进制地址码中最低 4 “位必须是 0”。每个段首地址的高 16 位二进制代 码就是该段的段号(称段基地址)或简称段地址,段号保存在段寄存器中。我们可对段寄存 器设置不同的值来使微处理器的存储器访问指向不同的段。 5.段内的某个存储单元相对于该段段首地址的差值,称为段内偏移地址(也叫偏移量)用 16 位二进制代码表示。 6.物理地址是由 8086/8088 芯片地址引线送出的 20 位地址码,它用来参加存储器的 地址译码,最终读/写所访问的一个特定的存储单元。 7.逻辑地址由某段的段地址和段内偏移地址(也叫偏移量)两部分所组成。写成: 段地址:偏移地址(例如,1234H:0088H)。 8.在硬件上起作用的是物理地址,物理地址=段基地址×10H 十偏移地址 联想笔试题   1 .设计函数 int atoi(char *s)。   2.int i=(j=4,k=8,l=16,m=32); printf(“%d”, i); 输出是多少? 60. 解释局部变量、全局变量和静态变量的含义。   4.解释堆和栈的区别。 61. 论述含参数的宏与函数的优缺点。 普天 C++笔试题   1.实现双向链表删除一个节点 P,在节点 P 后插入一个节点,写出这两个函数。   2.写一个函数,将其中的\t 都转换成 4 个空格。 61.Windows 程序的入口是哪里?写出 Windows 消息机制的流程。 4.如何定义和实现一个类的成员函数为回调函数? 62.C++里面是不是所有的动作都是 main()引起的?如果不是,请举例。    6.C++里面如何声明 const void f(void)函数为 C 程序中的库函数?   7.下列哪两个是等同的   int b;   A const int* a = &b;   B const* int a = &b;   C const int* const a = &b;   D int const* const a = &b;   8.内联函数在编译时是否做参数类型检查?   void g(base & b){    b.play;   }   void main(){    son s;    g(s);    return;   } 大唐电信   DTT 笔试题   考试时间一小时,第一部分是填空和选择:   1.数列 6,10,18,32 “”“”, ? ,问 ? 是几?   2.某人出 70 买进一个 x,80 卖出,90 买回,100 卖出,这桩买卖怎么样?   3.月球绕地球一圈,至少要多少时间?   4.7 个人用 7 小时挖了 7 米的沟,以同样的速度在 50 小时挖 50 米的沟要多少人?   5.鱼头长 9,鱼尾等于鱼头加半个鱼身,鱼身等于鱼头加鱼尾,问鱼全长多少?   6.一个小姐买了一块手表,回家发现手表比她家的表慢了两分钟,晚上看新闻的时 候 又发现她家的表比新闻里的时间慢了两分钟,则 。   A 手表和新闻里的时间一样   B 手表比新闻里的时间慢   C 手表比新闻里的时间快   7.王先生看到一则招聘启事,发现两个公司除了以下条件不同外,其他条件都相同   A 半年年薪 50 万,每半年涨 5 万   B 一年年薪 100 万,每一年涨 20 万   王先生想去一家待遇比较优厚的公司,他会去哪家?   10.问哪个袋子里有金子?   A 袋子上的标签是这样写的:B 袋子上的话是对的,金子在 A 袋子。   B 袋子上的标签是这样写的:A 袋子上的话是错的,金子在 A 袋子里。   11.3 个人住酒店 30 块钱,经理找回 5 块钱,服务生从中藏了 2 块钱,找给每人 1 块钱, 1)+2=29,问这是怎么回事?3×(10   12.三篇写作,均为书信形式。   (1)一片中文的祝贺信,祝贺某男当了某公司 xx   (2)两篇英文的,一是说有事不能应邀,派别人去;另一篇是讨债的,7 天不给钱 就 走人(主要考 business letter 格式)。   大唐面试试题   1.什么是中断?中断发生时 CPU 做什么工作?   2.CPU 在上电后,进入操作系统的 main()之前必须做什么工作?   3.简述 ISO OSI 的物理层 Layer1,链路层 Layer2,网络层 Layer3 的任务。   4.有线电话和无线电话有何区别?无线电话特别需要注意的是什么?    63.软件开发五个主要 step 是什么?   6.你在开发软件的时候,这 5 个 step 分别占用的时间百分比是多少?   7.makefile 文件的作用是什么?   8.UNIX 显示文件夹中,文件名的命令是什么?能使文件内容显示在屏幕的命令是 什么 ?   9.(选做)手机用户在从一个基站漫游到另一个基站的过程中,都会发生什么? 网通笔试题   选择题(每题 5 分,只有一个正确答案)   1.中国 1 号信令协议属于 的协议。   A ccs B cas C ip D atm   2.isdnpri 协议全称是 。   A 综合业务模拟网基速协议   B 综合业务模拟网模拟协议   C 综合业务数字网基率协议   D 综合业务数字网基次协议   3 .路由协议中, 协议是用距离作为向量的。   A ospf B bgp C is-is D rip   4.中国智能网中,ssp 与 scp 间最上层的 ss7 协议是 。   A incs B is41b C is41c D inap   5.dtmf 全称是 。   A 双音多频 B 多音双频 C 多音三频 D 三音多频   6 .计算机的基本组成部分中,不包含下面设备的是 。   A cpu B 输入设备 C 存储器 D 接口   7 .脉冲编码调制的简称是 。   A pcm B pam C (delta)M D atm   8 .普通电话线接口专业称呼是 。   A rj11 B rj45 C rs232 D bnc   9 .现有的公共数据网都采用 。   A 电路交换技术 B 报文交换技术   C 语音插空 D 分组交换   10.ss7 协议中的制止市忙消息简写为 。   A stb B slb C sub D spb   简答题(每题 10 分)   1.简述普通电话与 IP 电话的区别。   2.简述随路信令与公路信令的根本区别。   3.说明掩码的主要作用。   4.ss7 协议中,有三大要素决定其具体定位,哪三大要素?   5.描述 ss7 的基本通话过程。   6.简述通信网的组成结构。   7.面向连接与面向非连接各有何利弊?   8.写出爱尔兰的基本计算公式。   9.数据网主要有哪些设备?   10 .中国一号协议是如何在被叫号码中插入主叫号码的? 东信笔试题目   笔试:30 分钟。   1.压控振荡器的英文缩写。   2.动态随机存储器的英文缩写。   3.选择电阻时要考虑什么?   4.单片机上电后没有运转,首先要检查什么?   5.计算机的基本组成部分及其各自的作用。   6.怎样用 D 触发器、与或非门组成二分频电路? 64.static 有什么用途?(请至少说明两种) 答 、1.限制变量的作用域(文件级的)。 2.设置变量的存储域(全局数据区)。 65.引用与指针有什么区别? 答 、1) 引用必须被初始化,指针不必。 2) 引用初始化以后不能被改变,指针可以改变所指的对象。 3) 不存在指向空值的引用,但是存在指向空值的指针。 66.描述实时系统的基本特性 答 、在特定时间内完成特定的任务,实时性与可靠性。 67.全局变量和局部变量在内存中是否有区别?如果有,是什么区别? 答 、全局变量储存在静态数据区,局部变量在堆栈中。 68.什么是平衡二叉树? 答 、左右子树都是平衡二叉树 且左右子树的深度差值的绝对值不大于 1。 69.堆栈溢出一般是由什么原因导致的? 答 、1.没有回收垃圾资源 2. 层次太深的递归调用 70.什么函数不能声明为虚函数? 答 、constructor Deconstructor 可以声明为虚函数。 系统为一个空类创建的成员函数有那些。 71.冒泡排序算法的时间复杂度是什么? 答 、O(n^2) 72.写出 float x “”与 零值 比较的 if 语句。 答 、if(x>0.000001&&x<-0.000001) 73.Internet 采用哪种网络协议?该协议的主要层次结构? 答 、tcp/ip 应用层/传输层/网络层/数据链路层/物理层 74.Internet 物理地址和 IP 地址转换采用什么协议? 答 、ARP (Address Resolution Protocol)(地址解析协议) 75.IP 地址的编码分为哪俩部分? 答 、IP“”地址由两部分组成,网络号和主机号。不过是要和 子网掩码 按位与之后才能区分 哪些是网络位哪些是主机位。 76.用户输入 M,N 值,从 1 至 N 开始顺序循环数数,每数到 M 输出该数值,直至全部输 出。写出 C 程序。 答 、循环链表,用取余操作做 77.不能做 switch()的参数类型是: 答 、switch 的参数不能为实型。 华为 78.局部变量能否和全局变量重名? ”答、能,局部会屏蔽全局。要用全局变量,需要使用 ::” 局部变量可以与全局变量同名,在函数内引用这个变量时,会用到同名的局部变量,而 不会用到全局变量。对于有些编译器而言,在同一个函数内可以定义多个同名的局部变量, 比如在两个循环体内都定义一个同名的局部变量,而那个局部变量的作用域就在那个循 环体内 79.如何引用一个已经定义过的全局变量? 答 、可以用引用头文件的方式,也可以用 extern 关键字,如果用引用头文件方式来引用 某个在头文件中声明的全局变理,假定你将那个变写错了,那么在编译期间会报错,如 果你用 extern 方式引用时,假定你犯了同样的错误,那么在编译期间不会报错,而在连 接期间报错 80.全局变量可不可以定义在可被多个.C 文件包含的头文件中?为什么? 答 、可以,在不同的 C 文件中以 static 形式来声明同名全局变量。 可以在不同的 C 文件中声明同名的全局变量,前提是其中只能有一个 C 文件中对此变量 赋初值,此时连接不会出错 81.语句 for( ;1 ;)有什么问题?它是什么意思? 答 、和 while(1)相同。 82.do……while 和 while……do 有什么区别? 答 、前一个循环一遍再判断,后一个判断以后再循环 83.请写出下列代码的输出内容 #i nclude main() { int a,b,c,d; a=10; b=a++; c=++a; d=10*a++; printf(“b,c,d:%d,%d,%d”,b,c,d); return 0; } 答 、10,12,120 84.statac 全局变量、局部变量、函数与普通全局变量、局部变量、函数 static 全局变量与普通的全局变量有什么区别?static 局部变量和普通局部变量有什么 区别?static 函数与普通函数有什么区别? 答 、全局变量(外部变量)的说明之前再冠以 static 就构成了静态的全局变量。全局变量本 身就是静态存储方式, 静态全局变量当然也是静态存储方式。 这两者在存储方式上并无 不同。这两者的区别虽在于非静态全局变量的作用域是整个源程序, 当一个源程序由多 个源文件组成时,非静态的全局变量在各个源文件中都是有效的。 而静态全局变量则限 制了其作用域, 即只在定义该变量的源文件内有效, 在同一源程序的其它源文件中不能 使用它。由于静态全局变量的作用域局限于一个源文件内,只能为该源文件内的函数公用, 因此可以避免在其它源文件中引起错误。 从以上分析可以看出, 把局部变量改变为静态变量后是改变了它的存储方式即改变了它 的生存期。把全局变量改变为静态变量后是改变了它的作用域, 限制了它的使用范围。 static 函数与普通函数作用域不同。仅在本文件。只在当前源文件中使用的函数应该说明 为内部函数(static),内部函数应该在当前源文件中说明和定义。对于可在当前源文件以 外使用的函数,应该在一个头文件中说明,要使用这些函数的源文件要包含这个头文件 static 全局变量与普通的全局变量有什么区别:static 全局变量只初使化一次,防止在 其他文件单元中被引用; static 局部变量和普通局部变量有什么区别:static 局部变量只被初始化一次,下一次 依据上一次结果值; static 函数与普通函数有什么区别:static 函数在内存中只有一份,普通函数在每个被 调用中维持一份拷贝 程序的局部变量存在于(堆栈)中,全局变量存在于(静态区 )中,动态申请数据存在 于( 堆)中。 85.设有以下说明和定义: typedef union {long i; int k[5]; char c;} DATE; struct data { int cat; DATE cow; double dog;} too; DATE max; 则语句 printf(“%d”,sizeof(struct date)+sizeof(max));的执行结果是? 答 、结果是:___52____。DATE 是一个 union, 变量公用空间. 里面最大的变量类型是 int[5], 占用 20 个字节. 所以它的大小是 20 data 是一个 struct, 每个变量分开占用空间. 依次为 int4 + DATE20 + double8 = 32. 所以结果是 20 + 32 = 52. …当然 在某些 16 位编辑器下, int 可能是 2 字节, 那么结果是 int2 + DATE10 + double8 = 20 86.-1,2,7,28,,126 请问 28 和 126 中间那个数是什么?为什么? 答 、应该是 4^3-1=63 规律是 n^3-1(当 n 为偶数 0,2,4) n^3+1(当 n 为奇数 1,3,5) 87.用两个栈实现一个队列的功能?要求给出算法和思路! 答 、设 2 个栈为 A,B, 一开始均为空. 入队: 将新元素 push 入栈 A; 出队: (1)判断栈 B 是否为空; (2)如果不为空,则将栈 A 中所有元素依次 pop 出并 push 到栈 B; (3)将栈 B 的栈顶元素 pop 出; 这样实现的队列入队和出队的平摊复杂度都还是 O(1), 比上面的几种方法要好。 88.在 c 语言库函数中将一个字符转换成整型的函数是 atool()吗,这个函数的原型是什 么? 答 、函数名: atol 功 能: 把字符串转换成长整型数 用 法: long atol(const char *nptr); 程序例: #include #include int main(void) { long l; char *str = “98765432″; l = atol(lstr); printf(“string = %s integer = %ld\n”, str, l); return(0); } 89.对于一个频繁使用的短小函数,在 C 语言中应用什么实现,在 C++中应用什么实现? 答 、c 用宏定义,c++用 inline 90.用预处理指令#define 声明一个常数,用以表明 1 年中有多少秒(忽略闰年问题) #define SECONDS_PER_YEAR (60 * 60 * 24 * 365)UL 我在这想看到几件事情: 1). #define 语法的基本知识(例如:不能以分号结束,括号的使用,等等) 2). 懂得预处理器将为你计算常数表达式的值,因此,直接写出你是如何计算一年中有多 少秒而不是计算出实际的值,是更清晰而没有代价的。 3). 意识到这个表达式将使一个 16 位机的整型数溢出-因此要用到长整型符号 L,告诉编 译器这个常数是的长整型数。 4). 如果你在你的表达式中用到 UL(表示无符号长整型),那么你有了一个好的起点。记 住,第一印象很重要。 91. “”写一个 标准 宏 MIN,这个宏输入两个参数并返回较小的一个。 #define MIN(A,B) ((A) <= (B) (A) : (B)) 这个测试是为下面的目的而设的: 1). 标识#define 在宏中应用的基本知识。这是很重要的,因为直到嵌入(inline)操作符变 为标准 C 的一部分,宏是方便产生嵌入代码的唯一方法, 对于嵌入式系统来说,为了能达到要求的性能,嵌入代码经常是必须的方法。 2). 三重条件操作符的知识。这个操作符存在 C 语言中的原因是它使得编译器能产生比 if- then-else 更优化的代码,了解这个用法是很重要的。 3). 懂得在宏中小心地把参数用括号括起来 4). 我也用这个问题开始讨论宏的副作用,例如:当你写下面的代码时会发生什么事? least = MIN(*p++, b); 92.预处理器标识#error 的目的是什么? 如果你不知道答案,请看参考文献 1。这问题对区分一个正常的伙计和一个书呆子是很有 用的。只有书呆子才会读 C 语言课本的附录去找出象这种 问题的答案。当然如果你不是在找一个书呆子,那么应试者最好希望自己不要知道答案。 死循环(Infinite loops) 93.嵌入式系统中经常要用到无限循环,你怎么样用 C 编写死循环呢? 这个问题用几个解决方案。我首选的方案是: while(1) { } 一些程序员更喜欢如下方案: for(;;) { } 这个实现方式让我为难,因为这个语法没有确切表达到底怎么回事。如果一个应试者给出 这个作为方案,我将用这个作为一个机会去探究他们这样做的 “”基本原理。如果他们的基本答案是: 我被教着这样做,但从没有想到过为什么。 这会给 我留下一个坏印象。 第三个方案是用 goto Loop: ... goto Loop; 应试者如给出上面的方案,这说明或者他是一个汇编语言程序员(这也许是好事)或者 他是一个想进入新领域的 BASIC/FORTRAN 程序员。 数据声明(Data declarations) 94.用变量 a 给出下面的定义 a) 一个整型数(An integer) b) 一个指向整型数的指针(A pointer to an integer) c) 一个指向指针的的指针,它指向的指针是指向一个整型数(A pointer to a pointer to an integer) d) 一个有 10 个整型数的数组(An array of 10 integers) e) 一个有 10 个指针的数组,该指针是指向一个整型数的(An array of 10 pointers to integers) f) 一个指向有 10 个整型数数组的指针(A pointer to an array of 10 integers) g) 一个指向函数的指针,该函数有一个整型参数并返回一个整型数(A pointer to a function that takes an integer as an argument and returns an integer) h) 一个有 10 个指针的数组,该指针指向一个函数,该函数有一个整型参数并返回一个 整型数( An array of ten pointers to functions that take an integer argument and return an integer ) 答案是: a) int a; // An integer b) int *a; // A pointer to an integer c) int **a; // A pointer to a pointer to an integer d) int a[10]; // An array of 10 integers e) int *a[10]; // An array of 10 pointers to integers f) int (*a)[10]; // A pointer to an array of 10 integers g) int (*a)(int); // A pointer to a function a that takes an integer argument and returns an integer h) int (*a[10])(int); // An array of 10 pointers to functions that take an integer argument and return an integer 人们经常声称这里有几个问题是那种要翻一下书才能回答的问题,我同意这种说法。当我 写这篇文章时,为了确定语法的正确性,我的确查了一下书。 但是当我被面试的时候,我期望被问到这个问题(或者相近的问题)。因为在被面试的这 段时间里,我确定我知道这个问题的答案。应试者如果不知道 所有的答案(或至少大部分答案),那么也就没有为这次面试做准备,如果该面试者没 有为这次面试做准备,那么他又能为什么出准备呢? Static 95.关键字 static 的作用是什么? 这个简单的问题很少有人能回答完全。在 C 语言中,关键字 static 有三个明显的作用: 1). 在函数体,一个被声明为静态的变量在这一函数被调用过程中维持其值不变。 2). 在模块内(但在函数体外),一个被声明为静态的变量可以被模块内所用函数访问, 但不能被模块外其它函数访问。它是一个本地的全局变量。 3). 在模块内,一个被声明为静态的函数只可被这一模块内的其它函数调用。那就是,这 个函数被限制在声明它的模块的本地范围内使用。 大多数应试者能正确回答第一部分,一部分能正确回答第二部分,同是很少的人能懂得 第三部分。这是一个应试者的严重的缺点,因为他显然不懂得本地化数 据和代码范围的好处和重要性。 Const 96.关键字 const 是什么含意? “我只要一听到被面试者说: const ”意味着常数 ,我就知道我正在和一个业余者打交道。 去年 Dan Saks 已经在他的文章里完全概括了 const 的所有用法,因此 ESP(译者: Embedded Systems Programming)的每一位读者应该非常熟悉 const 能做什么和不 能做什么.如果你从没有读到那篇文章,只要能说出 const “”意味着 只读 就可以了。尽管 这个答案不是完全的答案,但我接受它作为一个正确的答案。(如果你想知道更详细的答 案,仔细读一下 Saks 的文章吧。)如果应试者能正确回答这个问题,我将问他一个附加 的问题:下面的声明都是什么意思? const int a; int const a; const int *a; int * const a; int const * a const; 前两个的作用是一样,a 是一个常整型数。第三个意味着 a 是一个指向常整型数的指针 (也就是,整型数是不可修改的,但指针可以)。第四个意思 a 是一个指向整型数的常指 针(也就是说,指针指向的整型数是可以修改的,但指针是不可修改的)。最后一个意味 着 a 是一个指向常整型数的常指针(也就是说,指针指向的整型数是不可修改的,同时 指针也是不可修改的)。如果应试者能正确回答这些问题,那么他就给我留下了一个好印 象。顺带提一句,也许你可能会问,即使不用关键字 const,也还是能很容易写出功能正 确的程序,那么我为什么还要如此看重关键字 const 呢?我也如下的几下理由: 1). 关键字 const 的作用是为给读你代码的人传达非常有用的信息,实际上,声明一个 参数为常量是为了告诉了用户这个参数的应用目的。如果你曾花很多时间清理其它人留下 的垃圾,你就会很快学会感谢这点多余的信息。(当然,懂得用 const 的程序员很少会留 下的垃圾让别人来清理的。) 2). 通过给优化器一些附加的信息,使用关键字 const 也许能产生更紧凑的代码。 3). 合理地使用关键字 const 可以使编译器很自然地保护那些不希望被改变的参数,防 止其被无意的代码修改。简而言之,这样可以减少 bug 的出现。 Volatile 97.关键字 volatile 有什么含意 并给出三个不同的例子。 一个定义为 volatile 的变量是说这变量可能会被意想不到地改变,这样,编译器就不会 去假设这个变量的值了。精确地说就是,优化器在用到这个变量时必须每次都小心地重新 读取这个变量的值,而不是使用保存在寄存器里的备份。下面是 volatile 变量的几个例子: 1). 并行设备的硬件寄存器(如:状态寄存器) 2). 一个中断服务子程序中会访问到的非自动变量(Non-automatic variables) 3). 多线程应用中被几个任务共享的变量 回答不出这个问题的人是不会被雇佣的。我认为这是区分 C 程序员和嵌入式系统程序员的 最基本的问题。嵌入式系统程序员经常同硬件、中断、RTOS 等等打交道,所用这些都要求 volatile 变量。不懂得 volatile 内容将会带来灾难。 假设被面试者正确地回答了这是问题(嗯,怀疑这否会是这样),我将稍微深究一下, 看一下这家伙是不是直正懂得 volatile 完全的重要性。 1). 一个参数既可以是 const 还可以是 volatile 吗?解释为什么。 2). 一个指针可以是 volatile 吗?解释为什么。 3). 下面的函数有什么错误: int square(volatile int *ptr) { return *ptr * *ptr; } 下面是答案: 1). 是的。一个例子是只读的状态寄存器。它是 volatile 因为它可能被意想不到地改变。它 是 const 因为程序不应该试图去修改它。 2). 是的。尽管这并不很常见。一个例子是当一个中服务子程序修该一个指向一个 buffer 的指针时。 3). 这段代码的有个恶作剧。这段代码的目的是用来返指针*ptr 指向值的平方,但是,由 于*ptr 指向一个 volatile 型参数,编译器将产生类似下面的代码: int square(volatile int *ptr) { int a,b; a = *ptr; b = *ptr; return a * b; } 由于*ptr 的值可能被意想不到地该变,因此 a 和 b 可能是不同的。结果,这段代码可能返 不是你所期望的平方值!正确的代码如下: long square(volatile int *ptr) { int a; a = *ptr; return a * a; } 位操作(Bit manipulation) 98.下面的代码输出是什么,为什么? void foo(void) { unsigned int a = 6; int b = -20; (a+b > 6) puts(“> 6″) : puts(“<= 6"); } 这个问题测试你是否懂得 C 语言中的整数自动转换原则,我发现有些开发者懂得极少这 “些东西。不管如何,这无符号整型问题的答案是输出是 >6”。原因是当表达式中存在有符 号类型和无符号类型时所有的操作数都自动转换为无符号类型。因此-20 变成了一个非常 大的正整数,所以该表达式计算出的结果大于 6。这一点对于应当频繁用到无符号数据类 型的嵌入式系统来说是丰常重要的。如果你答错了这个问题,你也就到了得不到这份工作 的边缘。 99.C 语言同意一些令人震惊的结构,下面的结构是合法的吗,如果是它做些什么? int a = 5, b = 7, c; c = a+++b; 这个问题将做为这个测验的一个愉快的结尾。不管你相不相信,上面的例子是完全合乎语 法的。问题是编译器如何处理它?水平不高的编译作者实际上会争论这个问题,根据最处 理原则,编译器应当能处理尽可能所有合法的用法。因此,上面的代码被处理成: c = a++ + b; 因此, 这段代码持行后 a = 6, b = 7, c = 12。 如果你知道答案,或猜出正确答案,做得好。如果你不知道答案,我也不把这个当作问题。 我发现这个问题的最大好处是:这是一个关于代码编写风格,代码的可读性,代码的可修 改性的好的话题 今天早上的面试题 9 道,比较难, 100.线形表 a、b 为两个有序升序的线形表,编写一程序,使两个有序线形表合并成一个 有序升序线形表 h; 答案在 请化大学 严锐敏《数据结构第二版》第二章例题,数据结构当中,这个叫做:两路 归并排序 Linklist *unio(Linklist *p,Linklist *q){ linklist *R,*pa,*qa,*ra; pa=p; qa=q; R=ra=p; while(pa->next!=NULL&&qa->next!=NULL){ if(pa->data>qa->data){ ra->next=qa; qa=qa->next; } else{ ra->next=pa; pa=pa->next; } } if(pa->next!=NULL) ra->next=pa; if(qa->next!=NULL) ra->next==qa; return R; } 101.用递归算法判断数组 a[N]是否为一个递增数组。 递归的方法,记录当前最大的,并且判断当前的是否比这个还大,大则继续,否则返回 false 结束: bool fun( int a[], int n ) { if( n= =1 ) return true; if( n= =2 ) return a[n-1] >= a[n-2]; return fun( a,n-1) && ( a[n-1] >= a[n-2] ); } 102.编写算法,从 10 亿个浮点数当中,选出其中最大的 10000 个。 用外部排序,在《数据结构》书上有《计算方法导论》在找到第 n 大的数的算法上加工 103.编写一 unix 程序,防止僵尸进程的出现. 同学的 4 道面试题,应聘的职位是搜索引擎工程师,后两道超级难,(希望大家多给一 些算发) 1.给两个数组和他们的大小,还有一动态开辟的内存,求交集,把交集放到动态内存 dongtai,并且返回交集个数 long jiaoji(long* a[],long b[],long* alength,long blength,long* dongtai[]) 2. ’单连表的建立,把 a'–’z’26 个字母插入到连表中,并且倒叙,还要打印! 方法 1: typedef struct val { int date_1; struct val *next; }*p; void main(void) { char c; for(c=122;c>=97;c–) { p.date=c; p=p->next; } p.next=NULL; } } 方法 2: node *p = NULL; node *q = NULL; node *head = (node*)malloc(sizeof(node)); head->data = ‘ ‘;head->next=NULL; node *first = (node*)malloc(sizeof(node)); first->data = ‘a’;first->next=NULL;head->next = first; p = first; int longth = ‘z’ – ‘b’; int i=0; while ( i<=longth ) { node *temp = (node*)malloc(sizeof(node)); temp->data = ‘b’+i;temp->next=NULL;q=temp; head->next = temp; temp->next=p;p=q; i++; } print(head); 104.可怕的题目终于来了 象搜索的输入信息是一个字符串,统计 300 万输入信息中的最热门的前十条,我们每次 输入的一个字符串为不超过 255byte,内存使用只有 1G, 请描述思想,写出算发(c 语言),空间和时间复杂度, 7.国内的一些帖吧,如 baidu,有几十万个主题,假设每一个主题都有上亿的跟帖子,怎 么样设计这个系统速度最好,请描述思想,写出算发(c 语言),空间和时间复杂度, #include string.h main(void) { char *src=”hello,world”; char *dest=NULL; dest=(char *)malloc(strlen(src)); int len=strlen(str); char *d=dest; char *s=src[len]; while(len–!=0) d++=s–; printf(“%s”,dest); } 找出错误!! #include “string.h” #include “stdio.h” #include “malloc.h” main(void) { char *src=”hello,world”; char *dest=NULL; dest=(char *)malloc(sizeof(char)*(strlen(src)+1)); int len=strlen(src); char *d=dest; char *s=src+len-1; while(len–!=0) *d++=*s–; *d=’\0′; printf(“%s”,dest); } 105.判断字符串是否为回文 bool IsSymmetry(const char* p) { assert(p!=NULL); const char* q=p; int len=0; while(*q++!=’\0′) { len++; } bool bSign=true; q=p+len-1; if (0=0||b>=0)&&(a<0||b<0) B、(a>=0&&b>=0)&&(a<0&&b<0) C、(a+b>0)&&(a+b<=0) D、a*b>0 以下关于运算符优先顺序的描述中正确的是(C)。 A、关系运算符<算术运算符<赋值运算符<逻辑与运算符 B、逻辑与运算符<关系运算符<算术运算符<赋值运算符 C、赋值运算符<逻辑与运算符<关系运算符<算术运算符 D、算术运算符<关系运算符<赋值运算符<逻辑与运算符 113.字符串倒序 写一个函数将"tom is cat" 倒序打印出来,即 "cat is tom" //a.ch #define SPACE ' ' #define ENDL '\0' char* str = "Tom is cat"; // 字符串 char* p1 = str+strlen(str)-1; char* p2 = p1; // 开始时,p1,p2 都指向字符串结尾处 char t=0; // 临时变量,用来保存被临时替换为 ENDL 的字符 while(str!=p1--) { if(SPACE!=*p1){ for(p2=p1+1;SPACE!=*p1; p1--, t=*p2, *p2=ENDL); // p1+1 指向单词的第一个字母,p2 指向单词的结尾,此时输出这个单词 printf("%s ",p1+1); *p2=t; p2=p1; } } Output: cat is Tom ---------------------------------------------------------------------- 1)写一个递归函数将内存中的字符串翻转"abc"->“cba” 2) ”写一个函数将 tom is cat” “将内存中的字符串翻转,即 cat is tomm” #include #define SPACE ‘ ‘ #define ENDL ‘\0′ char* s = “The quick brown fox jumps over the lazy dog”; void str_reverse(char* p1,char* p2){ if(p1==p2)return; *p1 = (*p1)+(*p2); *p2 = (*p1)-(*p2); *p1 = (*p1)-(*p2); if(p1==p2-1)return; else str_reverse(++p1,–p2); } void str_word_reverse(char* str){ char *q1=str, *q2=str, *t; while(*q1==SPACE)q1++; if(*q1==ENDL)return; //! else q2=q1+1; while( (*q2!=SPACE) && (*q2!=ENDL) )q2++; t=q2–; str_reverse(q1,q2); if(*t==ENDL)return; else str_word_reverse(t); } int main(int a ,char** b) { printf(“%s\n”,s); str_reverse(s,s+strlen(s)-1); printf(“%s\n”,s); str_word_reverse(s); printf(“%s\n”,s); return 0; } Output: The quick brown fox jumps over the lazy dog god yzal eht revo spmuj xof nworb kciuq ehT dog lazy the over jumps fox brown quick The ———————————————————————- 今天同学又问一道题,和上面有些类似,但是要求更严格了一些: ”写一个递归函数将内存中的字符串翻转 abc”->”cba”,并且函数原型已确定:void reverse(char* p) 其实,要求越多,思路越确定,我的解如下: #include #include char* s = “0123456789″; #define ENDL ‘\0′ void reverse(char* p){ //这是这种方法的关键,使用 static 为的是能用 str_reverse 的思路,但是不好 static char* x=0; if(x==0)x=p; char* q = x+strlen(p)-1; if(p==q)return; *q=(*p)^(*q); *p=(*p)^(*q); *q =(*p)^(*q); if(q==p+1)return; reverse(++p); } //这种方法就直观多了,但是当字符串很长的时候就很低效 void reverse2(char* p){ if(*(p+1)==ENDL)return; for(char* o=p+strlen(p)-1,char t=*o;o!=p;o–) *o=*(o-1); *p=t; reverse2(p+1); } int main(int c,char** argv){ reverse2(s); printf(“%s\n”,s); return 0; } 114.交换两个数的宏定义 交换两个参数值的宏定义为:. #define SWAP(a,b) (a)=(a)+(b);(b)=(a)-(b); (a)=(a)-(b); 115.Itearator 各指针的区别 游标和指针 我说过游标是指针,但不仅仅是指针。游标和指针很像,功能很像指针,但是实际上,游 ”标是通过重载一元的 *””和 ->”来从容器中间接地返回一个值。将这些值存储在容器中并 不是一个好主意,因为每当一个新值添加到容器中或者有一个值从容器中删除,这些值 就会失效。在某种程度上,游标可以看作是句柄(handle)。通常情况下游标 (iterator)的类型可以有所变化,这样容器也会有几种不同方式的转变: iterator——对于除了 vector 以外的其他任何容器,你可以通过这种游标在一次操作中 “在容器中朝向前的方向走一步。这意味着对于这种游标你只能使用 ++”操作符。而不能 “–” “使用 或 +=”操作符。而对于 vector “这一种容器,你可以使用 +=” “—” “、、 ++” “、 - =” “中的任何一种操作符和 <” “、 <=” “、 >” “、 >=” “、 ==” “、 !=”等比较运算符。 116. C++中的 class 和 struct 的区别 从语法上,在 C++中(只讨论 C++中)。class 和 struct 做类型定义时只有两点区别: (一)默认继承权限。如果不明确指定,来自 class 的继承按照 private 继承处理,来自 struct 的继承按照 public 继承处理; (二)成员的默认访问权限。class 的成员默认是 private 权限,struct 默认是 public 权 限。 除了这两点,class 和 struct 基本就是一个东西。语法上没有任何其它区别。 不能因为学过 C 就总觉得连 C++中 struct 和 class 都区别很大,下面列举的说明可能比 较无聊,因为 struct 和 class 本来就是基本一样的东西,无需多说。但这些说明可能有助 于澄清一些常见的关于 struct 和 class 的错误认识: (1)都可以有成员函数;包括各类构造函数,析构函数,重载的运算符,友元类,友元 结构,友元函数,虚函数,纯虚函数,静态函数; (2)都可以有一大堆 public/private/protected 修饰符在里边; (3)虽然这种风格不再被提倡,但语法上二者都可以使用大括号的方式初始化: A a = {1, 2, 3};不管 A 是个 struct 还是个 class,前提是这个类/结构足够简单,比如 所有的成员都是 public 的,所有的成员都是简单类型,没有显式声明的构造函数。 (4)都可以进行复杂的继承甚至多重继承,一个 struct 可以继承自一个 class,反之亦 可;一个 struct 可以同时继承 5 个 class 和 5 个 struct,虽然这样做不太好。 (5)如果说 class 的设计需要注意 OO 的原则和风格,那么没任何理由说设计 struct 就 不需要注意。 (6)再次说明,以上所有说法都是指在 C++语言中,至于在 C 里的情况,C 里是根本 “没有 class”,而 C 的 struct 从根本上也只是个包装数据的语法机制。 ————————————————————— 最后,作为语言的两个关键字,除去定义类型时有上述区别之外,另外还有一点点: “class” “这个关键字还用于定义模板参数,就像 typename” “。但关键字 struct”不用于 定义模板参数。 关于使用大括号初始化   class 和 struct 如果定义了构造函数的话,都不能用大括号进行初始化   如果没有定义构造函数,struct 可以用大括号初始化。   如果没有定义构造函数,且所有成员变量全是 public 的话,可以用大括号初始化。   关于默认访问权限   class 中默认的成员访问权限是 private 的,而 struct 中则是 public 的。   关于继承方式   class 继承默认是 private 继承,而 struct 继承默认是 public 继承。   关于模版   在模版中,类型参数前面可以使用 class 或 typename,如果使用 struct,则含义 不同,struct “后面跟的是 non-type template parameter”,而 class 或 typename 后面跟的是类型参数。 class 中有个默认的 this 指针,struct 没有 不同点:构造函数,析构函数 this 指针 117.有关重载函数 返回值类型不同构不成重载 参数参数顺序不同能构成重载 c++ 函数同名不同返回值不算重载!函数重载是忽略返回值类型的。 ——————————————— 成员函数被重载的特征有: 1) 相同的范围(在同一个类中); 2) 函数名字相同; 3) 参数不同; 4) virtual 关键字可有可无。 5) 成员函数中 有无 const (函数后面) 也可判断是否重载 118.数据库与 T-SQL 语言 关系数据库是表的集合,它是由一个或多个关系模式定义。SQL 语言中的数据定义功能包 括对数据库、基本表、视图、索引的定义。 119.关系模型的基本概念 关系数据库以关系模型为基础,它有以下三部分组成: ● ——数据结构 模型所操作的对象、类型的集合 ● ——完整性规则 保证数据有效、正确的约束条件 ● ——数据操作 对模型对象所允许执行的操作方式 关系(Relation)是一个由行和列组成的二维表格,表中的每一行是一条记录 (Record),每一列是记录的一个字段(Field)。表中的每一条记录必须是互斥的,字 段的值必须具有原子性。 120.SQL 语言概述 SQL(结构化查询语言)是关系数据库语言的一种国际标准,它是一种非过程化的语言。 通过编写 SQL,我们可以实现对关系数据库的全部操作。 ●数据定义语言(DDL——) 建立和管理数据库对象 ●数据操纵语言(DML——) 用来查询与更新数据 ●数据控制语言(DCL——) 控制数据的安全性 起来是一个很简单的问题,每一个使用过 RDBMS 的人都会有一个概念。 事务处理系统的典型特点是具备 ACID 特征。ACID 指的是 Atomic(原子的) 、 Consistent(一致的)、Isolated(隔离的)以及 Durable(持续的),它们代表着事务 处理应该具备的四个特征: 原子性:组成事务处理的语句形成了一个逻辑单元,不能只执行其中的一部分 一致性:在事务处理执行之前和之后,数据是一致的。 隔离性:一个事务处理对另一个事务处理没有影响。 持续性:当事务处理成功执行到结束的时候,其效果在数据库中被永久纪录下来。 121.C 语言中结构化程序设计的三种基本控制结构 顺序结构 选择结构 循环结构 122.CVS 是什么 cvs(Concurrent Version System ) 是一个版本控制系统。使用它,可以记录下你的 源文件的历史。 例如,修改软件时可能会不知不觉混进一些 bug,而且可能过了很久你才会察觉到它们 的存在。有了 cvs,你可以很容易地恢复旧版本,并从中看出到底是哪个修改导致了这个 bug 。有时这是很有用的。 CVS 服务器端对每个文件维护着一个修订号,每次对文件的更新,都会使得文件的修订号 加 1。在客户端中也对每个文件维护着一个修订号,CVS 通过这两个修订号的关系,来进 行 Update,Commit 和发现冲突等操作操作 123.三种基本的数据模型 按照数据结构类型的不同,将数据模型划分为层次模型、网状模型和关系模型。 =============================================== =============================================== =============================================== ================= Linux 应用: 可以远程登录到 192.168.0.26 机器上测试 shell 启动终端敲: telnet 192.168.0.26 输入用户名和密码 openlab open123 可以用 sh/bash/csh 切换 shell 在 csh 中,普通用户 命令提示符: % 在 sh/bash ,普通用户 命令提示符: $ 超级用户:root 命令提示符 # ctrl +d 退出命令。 可以用 echo $SHELL 命令查看当前 shell Uinx/Linux 区分大小写 unix 文件系统 文件系统一般由 文件和目录(文件夹) 组成。 所有文件、目录都挂在根目录下,根目录 就是/ /opt 放应用程序 /usr 用户通用文件 /bin Unix 命令 /sbin Unix 命令 /etc 系统配置文件 /dev 设备管理 /var 应用程序 /home 用户登录目录 ... 用 more 命令查看一下 几个 配置文件, more /etc/passwd (用户信息) more /etc/shadow (密码管理信息,限 root) more /etc/group (用户组信息) 空格翻页,回车换行,q 退出。 Unix 常见命令: 文件和目录有不同描述方式(路径),有绝对路径和相对路径。 绝对路径 从根目录出发的描述,以/开始 相对路径 从当前目录出发的描述,不以/开始 比如 用户登录目录可以描述成: /home/soft01 (绝对路径) ./ (相对路径,代表当前目录) home/sotf01 (当前在根目录下) 特殊用法: . 代表当前目录 .. 代表上一层目录 ../../ 上两层目录 ~ 代表用户的登录目录 下一层目录名 代表下一层目录 开发基本上使用相对路径,绝对路径无法移植代码。 命令格式: 命令 [-选项] [参数] 选项和参数 是可以没有的,中间有空格隔开 Unix/Linux 命令区分大小写 clear - 清屏幕 pwd - 打印当前目录(工作目录) cd - 改变当前目录 cd 不带参数 默认回到 登录目录 cd / 切换到根目录 ls - 列举某目录下的清单(所有子目录和子文件) ls 不带参数 默认查看当前目录的清单 -a 显示所有子文件 子目录 -l 显示 详细信息 -al 显示 所有子文件、子目录的详细信息 chmod 可以修改文件、目录的权限 chmod 权限(755/764/744) 文件名 touch 文件名 可以新建一个文件 mkdir 目录名 可以新建一层目录 -p 新建多层目录 cp 拷贝目录、文件 (2 个) -r 拷贝目录 mv 剪切/ 改名 目录、文件 (1 个) rm 删除文件、目录 -r 删除目录(谨慎使用) rmdir 删除空目录 (使用较少) 在命令行模式下,输入 vi 文本文件名,不存在就新建,存在就打开,vi 有 增强版 vim 。 vi 有三种状态,进入 vi 界面以后,进入状态,支持一些功能键,但不能直接编辑文件。 进入状态下按 i/a/o 其一可以切换到 编辑状态,编辑状态 可以编辑文件。编辑状态下按 Esc 回到进入状态。 进入状态下 按 : (shift+;) 切换到 命令行状态,命令行状态下,在左下角 出现 : 。命令 状态下可以执行命令,常见的就是 保存和退出。 w 保存 q 退出 wq 保存后退出 ! 强制 w! 强制保存 q ! 强制退出 w 新文件名 另存为 命令行状态下,按 回车就退回到进入状态。 保存退出后,可以用 cat 命令查看文件内容。 练习: 使用 vi 编辑 message.txt ,输入 个人信息,包括: name: xxx age: xx school: xxx profession: xxx 写好后保存退出,然后 用 cat 查看一下。 vi 的功能键: h j k l 左下上右 i 在当前光标之前 插入 a 在当前光标之后 追加 o 在当前光标下一行 编写 O 在当前光标上一行 编写 r 修改当前光标上的字符 cc 替换一行 C 替换当前光标到本行结束 x 删除当前光标所在的字符 dd 删除一行 (剪切一行) 3dd 删除三行 (剪切三行) yy 拷贝 1 行 nyy 拷贝 n 行 p 粘贴到当前光标下一行 ~ 切换字母的大小写 u 撤销上一步操作 (vi 不保证支持多步撤销) 1G 定位到第一行 nG 定位到第 n 行 G 定位到最后一行 / 字符串 从当前光标开始查找字符串(从前向后) ? 字符串 从当前光标开始查找字符串(从后向前) 如有多个,可以用 n 继续查找 ZZ 保存退出 在命令行状态下输入 set nu 显示行号 set nonu 取消行号的显示 练习: 使用 vi 进行 c 语言程序/java 程序的开发。 1 建立一个 myapp 目录,在 myapp 目录下建立: src bak classes 三个子文件夹 2 进入 src ,进行 开发。 3 vi hello.c , 输入内容如下: #include int main() { printf("hello c!\n"); return 0; } 保存退出后,用 gcc hello.c 进行编译连接,用 ./a.out 运行程序 4 vi HelloWorld.java,输入内容如下: public class HelloWorld{ public static void main(String[] args){ System.out.println("Hello java!"); } } 保存退出后,用 javac HelloWorld.java 进行编译,编译通过可以用 ls 看到 HelloWorld.class ln 命令可以建立链接文件,分 软链接和硬链接。 ln 源文件 链接文件 默认情况是硬链接,加-s 选项是软链接。 硬链接 相当于给文件 起了一个新名字,原来名字也可以用,就是一个文件区域,两个 文件名。 软链接 相当于给文件建立了一个快捷方式,此快捷方式指向源文件名,如果源文件名 的指向发生了改变,软链接 也会 随之改变。软链接文件 类型是以 l 开头。 more 命令可以分页 查看文本文件的内容 head -n 文件名 显示文件头 n 行 tail -n 文件名 显示文件最后 n 行 find 用来查找文件、目录,格式如下: find 要查找的目录 选项 参数 选项很多,具体 参考 文档或者 linux 命令手册。 如果卡住了,用 ctrl+d / ctrl +c 退出 -name 按文件名 查找 find . -name "*a*" 在当前目录下 查找名字中含 a 的 -size 按文件大小 查找 find . -size 55c 此外可以用 +55c 代表大于 55 字节的,-55c 代表小于 55 字节 grep 可以从文本文件中 查找某字符串 grep 字符串 文件名 ps 查看进程 ps -ef / -aux(只有 Linux 使用) 可以用管道 (|) more 进行分页显示 ps -ef | more kill 杀进程 kill -9 进程号 su 可以切换用户 su 用户名 切换用户,切换时需要输入密码 但: root 切换其他用户 不需要输入 密码 whoami 可以查看当前用户 who am i passwd 修改当前用户的密码 有些 Unix 对修改密码 限定很多。 man 命令 查看命令的帮助 输入输出重定向 输入重定向 < 输出重定向 > (替换模式) cal 2 2000 > cal.txt 效果就是把输出到屏幕的内容转到 cal.txt 文件中。 >> 追加模式的 输出重定向 管道| 管道可以把两个 unix 命令连接起来,把第一个命令的输出做第二个命令的输入。 ls -al | more cat xx.txt | more 修改配置文件,如果需要马上生效,source 文件名 可以实现。 vi ~/.bash_profile source ~/.bash_profile =============================================== =============================================== =============================================== ================= 常见难点: 1. sizeof 相关系列问题 a. 对于 struct s{char a;int b}; sizeof(s) = 8; 因为当结构体内元素长度 都小于处理器位数(32 位=4 字节)的时候,便以结构体中 最长的数据元素 为对 齐条件,a 按 1 字节对齐,b 按 4 字节对齐,所以 s 默认对其参数为 8 struct A{short a1;short a2;short a3};sizeof(A)=6; 原因同上,结构体按 最长元素 short(2 字节对齐) 。 b. 对于 int a[200]; sizeof(a) = 200* sizeof(int) = 800; 对 整个数组大小 评测 int* a = new int[200]; sizeof(a) = 4; 对 指针大小 进行评测 char str[]="012345"; sizeof(str) = 7; str 是一个字符数组,数组最初 大小未定,由具体值"012345"来决定,6*sizeof(char) = 6, 还有隐含的"\0" , 所以一共 7 位。 char str[]="s\n";sizeof(str) = 3; 回车"\n" 是一个字符,可以查看 ASCII 表。还有\r、\t 等等字符 c. 这种使用位域的也有,其中元素最大为 1 字节大小=8 bits, 元素按照 1 字节 进行对齐 , sizeof(b) = 1 + 1(4 bits+2bits < 8 bits) + 1(3bits) = 3. struct b { char a:8; char b:4; char c:2; char d:3; }; 写出运行结果: union V { struct X { unsigned char s1:2; unsigned char s2:3; unsigned char s3:3; } x; unsigned char c; } v; v.c = 100; printf("%d", v.x.s3); 100 的 2 进制是 1100100 去掉后面的 5 位余 11 放入 x.s3 中 结果:3 d. 对于空的类进行评测 class A {}; sizeof(A) = 1;默认 空类是有一个占位 符 的 ========================================第 2 页=========== ============================= 对于虚函数 class A{ virtual test()}; class B:public A{};sizeof(A)=4;sizeof(B) =4 任何 含有虚函数的类拥有一个 vptr 指针 ,用来 指向虚函数表 vtable。sizeof(vptr) = 4 class A{static int a;}; sizeof(A) = 1; 对于 静态成员变量是分配在全局 存储区 的,所以 A 还是相当于空类。 class C:public virtual A {}; sizeof(C)=4; 对于 虚拟继承的类拥有虚函数 表 ,所以空类 C 含有 vptr. e. 设有以下说明和定义: typedef union {long i; int k[5]; char c;} DATE; // sizeof(int)*5 = 20 struct data { int cat; DATE cow; double dog;} too; //4+20+8 = 32 DATE max; 则语句 printf("%d",sizeof(struct date)+sizeof(max));的执行结果是:52 对于 union 联合来说,取其中最大元素长度来做为联合大小。 f. 使用 malloc 或者 new 分配内存,void *p = malloc(10); sizeof(p) = 4; 跟指针一样,sizeof 只能测出静态数组的长度,无法检测动态分配的或外部数组 大小 h. 下面函数输出结果:对于 char str[100]或者 char str[] 参数都退化为 char* str,这样的函数即使传入 char s[10] 也是可以的 void Func(char str[100]) { printf("%d\n", sizeof(str)); } char s[10]; // 函数对数组长度并没有检验 Func(s); 结果:sizeof(char*)=4 如何强制 str 为 100 位数组? 可以如此声明 char (&str)[100] 理解顺序:1.str 声明为一个引用 2.引用一个 100 元素数组 3. 数组元素每个 为 char 大小 void Func(char (&str)[100]) { printf("%d\n", sizeof(str)); } char s[100]; Func(s); //这里必须给定 100 位长度 char 数组 结果:100*sizeof(char) = 100 2. CONST 常见题目: a. const 与 #define 有什么不同 答案: 1. const 常量有数据类型,而宏没有数据类型。编译器可以对 const 常 量进行类型检查,而对宏只进行字符替换没有类型检查。 2. 有些编译器可以对 const 常量进行调试,但不能对宏常量进行调试 3. const 可以用来修饰函数参数、函数返回值,C++还可以用来修饰函 数,定义内中某个成员函数为常量函数 3. 写一个标准宏 MIN ,这个宏输入两个参数并返回较小的一个 答案: #define MIN(A,B) ((A)<=(B) ? (A):(B)) ========================================第 3 页=========== ============================= 1. 宏是方便的产生嵌入代码的唯一方法。 2. 三重条件操作符对编译器来说可以产生比 if-then-else 更优化的 代码。 3. 必须把宏中的参数用括号括起来 4. 内联函数和宏的差别是什么? 答案: 内联函数和普通函数相比可以加快程序运行的速度,因为不需要中断调 用。编译时内联函数代码直接嵌入到目标代码中,而宏只是字符替换。内联函数 要做类型检查,相对于宏更安全可靠。 inline 只用于如下情况: 1. 一个函数被不断调用。 2. 函数只有简单的几行,且函数内不包含 for、while、switch 等语 句. 5. const 符号常量; (1)const char *p (2)char const *p (3)char * const p 说明上面三种描述的区别: 1 和 2 相同,如果 const 位于星号的左侧,则 const 就是用来修饰指针所指向的 变量,即指针指向为常量,不能修改指针指向对象的内容 如果 const 位于星号的右侧,const 就是修饰指针本身,即指针本身是常量,不 能修改指针的指向 2)写出二分查找的代码. int bfind(int* a,int len,int val) { int m = len/2; int l = 0; int r = len; while(l!=m && r!= m) { if(a[m] > val) { r = m; m = (m+l)/2; } else if(a[m] < val) { l = m; m = (m+r)/2; } else return m; } return -1; // 没有找到 ========================================第 4 页=========== ============================= } 常见字符串面试题 常见字符串面试题 常见字符串面试题 常见字符串面试题: :: : 1)写出在母串中查找子串出现次数的代码. int count1(char* str,char* s) { char* s1; char* s2; int count = 0; while(*str!='\0') { s1 = str; s2 = s; while(*s2 == *s1&&(*s2!='\0')&&(*s1!='\0')) { s2++; s1++; } if(*s2 == '\0') count++; str++; } return count; } 2)查找第一个匹配子串位置,如果返回的是 s1 长度 len1 表示没有找到 size_t find(char* s1,char* s2) { size_t i=0; size_t len1 = strlen(s1) size_t len2 = strlen(s2); if(len1-len2<0) return len1; for(;i *r) return 1; else if(*l == *r) return 0; return -1; } 5) 实现字符串翻转 void reserve(char* str) { assert(str != NULL); char * p1 = str; char * p2 = str-1; while(*++p2); //一般要求不能使用 strlen p2 -= 1; while(p1 #i nclude #i nclude char *commanstring(char shortstring[], char longstring[]) { int i, j; char *substring=malloc(256); if(strstr(longstring, shortstring)!=NULL) //如 ……果 ,那么返回 shortstring return shortstring; for(i=strlen(shortstring)-1;i>0; i--) //否则,开始 循环计算 { for(j=0; j<=strlen(shortstring)-i; j++) { memcpy(substring, &shortstring[j], i); substring[i]='\0'; if(strstr(longstring, substring)!=NULL) return substring; } } return NULL; } main() { char *str1=malloc(256); char *str2=malloc(256); char *comman=NULL; ========================================第 7 页=========== ============================= gets(str1); gets(str2); if(strlen(str1)>strlen(str2)) //将短的字 符串放前面 comman=commanstring(str2, str1); else comman=commanstring(str1, str2); printf("the longest comman string is: %s\n", comman); } 8) 判断一个字符串是不是回文 int IsReverseStr(char *str) { int i,j; int found=1; if(str==NULL) return -1; char* p = str-1; while(*++p!= '\0'); --p; while(*str==*p&&str src ) // 考虑覆盖情况 { d = (char *)dst + len - 1; s = (char *)src + len - 1; while ( len >= 4 ) // 循环展开,提高执行效率 { *d-- = *s--; *d-- = *s--; *d-- = *s--; *d-- = *s--; ========================================第 8 页=========== ============================= len -= 4; } while ( len-- ) { *d-- = *s--; } } else if ( dst < src ) { d = (char *)dst; s = (char *)src; while ( len >= 4 ) { *d++ = *s++; *d++ = *s++; *d++ = *s++; *d++ = *s++; len -= 4; } while ( len-- ) { *d++ = *s++; } } return dst; } 10)写一个函数,它的原形是 int continumax(char *outputstr,char *intputstr) 功能: 在字符串中找出连续最长的数字串,并把这个串的长度返回,并把这个最长数字 串付给其中一个函数参数 outputstr 所指内存。例如: "abcd12345ed125ss123456789"的首地址传给 intputstr 后,函数将返回 9,outputstr 所指的值为 123456789 int continumax(char *outputstr, char *inputstr) { char *in = inputstr, *out = outputstr, *temp, *final; int count = 0, maxlen = 0; while( *in != '\0' ) { if( *in > 47 && *in < 58 ) { for(temp = in; *in > 47 && *in < 58 ; in++ ) count++; ========================================第 9 页=========== ============================= } else in++; if( maxlen < count ) { maxlen = count; count = 0; final = temp; } } for(int i = 0; i < maxlen; i++) { *out = *final; out++; final++; } *out = '\0'; return maxlen; } 11) 编写一个 C 函数,该函数在一个字符串中找到可能的最长的子字符串,且 该字符串是由同一字符组成的。 char * search(char *cpSource, char ch) { char *cpTemp=NULL, *cpDest=NULL; int iTemp, iCount=0; while(*cpSource) { if(*cpSource == ch) { iTemp = 0; cpTemp = cpSource; while(*cpSource == ch) ++iTemp, ++cpSource; if(iTemp > iCount) iCount = iTemp, cpDest = cpTemp; if(!*cpSource) break; } ++cpSource; } return cpDest; } ======================================== 第 10 页========== ============================== 排序算法 排序算法 排序算法 排序算法: :: : *7) 写出快速排序或者某种排序算法代码 快速排序: int partition(int* a,int l,int r) { int i=l-1,j=r,v=a[r]; while(1) { while(a[++i]v) if(j<=i) break; if(i>=j) break; swap(a[i],a[j]); } swap(a[i],a[r]); return i; } void qsort(int* a,int l,int r) { if(r>l) { int i = partition(a,l,r); qsort(a,l,i-1); qsort(a,i+1,r); } } 有兴趣可以看看下面 有兴趣可以看看下面 有兴趣可以看看下面 有兴趣可以看看下面 2 个 个个 个。 。。 。一般面试不会要求的 一般面试不会要求的 一般面试不会要求的 一般面试不会要求的 改进 1: void qsort(int* a,int l,int r) { while(l=0&&a[i]>key;i--) { a[i+1] = a[i]; } a[i+1] = key; ======================================== 第 12 页========== ============================== } } 链表题目 链表题目 链表题目 链表题目: :: : 1) 将一个单链表逆序 struct list_node { list_node(int a,list_node* b):data(a),next(b) // 这个为了测试方便 {} int data; list_node* next; }; // 认为头节点存在,如果 list 类内函数不用判断头结点是否为空. 不是类内部 函数得判断头结点 void reserve(list_node* phead) { list_node* p = phead->next; if(p == NULL || p->next == NULL) return; //只有头节点或一个节 点 list_node* p1=p->next; p->next=NULL; while(p1!=NULL) { p = p1->next; p1->next = phead->next; phead->next = p1; p1 = p; } } 测试程序: list lt; lt.phead = new list_node(0,0); lt.phead->next = new list_node(1,0); lt.phead->next->next = new list_node(2,0); lt.phead->next->next->next = new list_node(3,0); lt.reserve(); list_node * p = lt.phead; while(p) { ======================================== 第 13 页========== ============================== cout<data<next; } 2) 循环链表的节点对换和删除。 // 双向循环 list_node* earse(list_node* node) { if(node == rear) return node->next; //对于头节点可判断也可不判 断。最好加上 list_node* next = node->next; next->prev = node->prev; node->prev->next = next; delete node; retrun next; } // 单项循环 list_node* earse(list_node* node) { // if(node == rear) return node->next; //对于头节点可判断也可不 判断。最好加上 list_node* p = rear; while(p->next != node) p=p->next; p->next = node->next; delete node; retrun p->next; } 3) 有双向循环链表结点定义为: struct node { int data; struct node *front,*next; }; 有两个双向循环链表 A,B,知道其头指针为:pHeadA,pHeadB,请写一函数将两 链表中 data 值相同的结点删除 BOOL DeteleNode(Node *pHeader, DataType Value) { if (pHeader == NULL) return; BOOL bRet = FALSE; Node *pNode = pHead; while (pNode != NULL) { ======================================== 第 14 页========== ============================== if (pNode->data == Value) { if (pNode->front == NULL) { pHeader = pNode->next; pHeader->front = NULL; } else { if (pNode->next != NULL) { pNode->next->front = pNode->front; } pNode->front->next = pNode->next; } Node *pNextNode = pNode->next; delete pNode; pNode = pNextNode; bRet = TRUE; / /不要 break 或 return, 删除所有 } else { pNode = pNode->next; } } return bRet; } void DE(Node *pHeadA, Node *pHeadB) { if (pHeadA == NULL || pHeadB == NULL) { return; } Node *pNode = pHeadA; while (pNode != NULL) { if (DeteleNode(pHeadB, pNode->data)) { if (pNode->front == NULL) ======================================== 第 15 页========== ============================== { pHeadA = pNode->next; pHeadA->front = NULL; } else { pNode->front->next = pNode->next; if (pNode->next != NULL) { pNode->next->front = pNode->front; } } Node *pNextNode = pNode->next; delete pNode; pNode = pNextNode; } else { pNode = pNode->next; } } } 4) 写出程序删除链表中的所有接点 void del_all(node *head) { node *p; while(head!=NULL) { p=head->next; free(head); head=p; } cout<<"释放空间成功!"<next!=NULL&&qa->next!=NULL) { if(pa->data>qa->data) { ra->next=qa; qa=qa->next; } else { ra->next=pa; pa=pa->next; } } if(pa->next!=NULL) ra->next=pa; if(qa->next!=NULL) ra->next==qa; return R; } 6 怎么判断链表中是否有环? bool CircleInList(Link* pHead) { if(pHead = = NULL || pHead->next = = NULL)//无节点或只有一个节点并 且无自环 return (false); if(pHead->next = = pHead)// 自环 return (true); Link *pTemp1 = pHead;//step 1 Link *pTemp = pHead->next;//step 2 while(pTemp != pTemp1 && pTemp != NULL && pTemp->next != NULL) { pTemp1 = pTemp1->next; pTemp = pTemp->next->next; } if(pTemp = = pTemp1) return (true); return (false); } 常识题 常识题 常识题 常识题: :: : 4 static 有什么用途?(请至少说明两种) ======================================== 第 17 页========== ============================== 1). 在函数体,一个被声明为静态的变量在这一函数被调用过程中维持其值不变。 2). 在模块内(但在函数体外),一个被声明为静态的变量可以被模块内所用函数访问,但不能 被模块外其它函数访问。它是一个本地的全局变量。 3). 在模块内,一个被声明为静态的函数只可被这一模块内的其它函数调用。那就是,这个函数 被限制在声明它的模块的本地范围内使用。 经常问 5. 引用与指针有什么区别? 1) 引用必须被初始化,不存在指向空值的引用,一个引用必须指向某个对象。 指针不必立即初始化。 2) 引用初始化以后不能改变使其指向其他对象,但可以修改其指向对象的内容。 指针可以被改变指向其他的对象。 3) 在使用引用之前不需要测试它是否为空,相反指针应该总被测试防止其为空。 4) 重载操作符必须使用引用才能完成串式操作 6. 全局变量和局部变量在内存中是否有区别?如果有,是什么区别? 全局变量储存在全局静态存储区,局部变量在堆栈 static 变量和 static 函数各有什么特点? 答:static 变量:在程序运行期内一直有效,如果定义在函数外,则在编译单元 内可见,如果在函数内,在在定义的 block 内可见;static 函数:在编译单元 内可见; static 全局变量与普通的全局变量有什么区别? static 全局变量只能在本模块中调用,不能在其他文件单元中被引用.而全局变 量可以使用 extern 在任何地方引用 static 函数与普通函数有什么区别: static 函数在内存中只有一份,普通函数在每个被调用中维持一份拷贝 程序的局部变量存在于(堆栈)中,全局变量存在于(静态区 )中,动态申请 数据存在于( 堆)中。 16. 什么是平衡二叉树? 左右子树都是平衡二叉树 且左右子树的深度差值的绝对值不大于 1 7. 什么构造函数不能声明为虚函数? 答案: 虚函数采用一种虚调用的办法。虚调用是一种可以在只有部分信息的情况 下工作的机制,特别允许我们调用一个只知道接口而不知道其准确对象类型的函 数。但如果要创建一个对象势必要知道其准确类型因此构造函数不能为虚。 8. 冒泡排序算法的时间复杂度是什么 O(n^2) 快速排序 o(nlgn) 9. 写出 float x “”与 零值 比较的 if 语句。 if(x>0.000001&&x<-0.000001) 其实浮点 0 在内存中也是 0,这个问题角度不太好。 ======================================== 第 18 页========== ============================== 10.进程间通信的方式有? 进程间通信的方式有 共享内存, 管道 ,Socket ,消息队列等 11. c 和 c++中的 struct 有什么不同? c 和 c++中 struct 的主要区别是 c 中的 struct 不可以含有成员函数,而 c++中 的 struct 可以。c++中 struct 和 class 的主要区别在于默认的存取权限不同, struct 默认为 public,而 class 默认为 private 12. 纯虚函数如何定义?使用时应注意什么? virtual void f()=0; 是接口,子类必须要实现 13. 数组和链表的区别 数组:数据顺序存储,固定大小 连表:数据可以随机存储,大小可动态改变 14. 线程与进程的区别和联系? 线程是否具有相同的堆栈? dll 是否有独立的堆 栈? 进程是死的,只是一些资源的集合,真正的程序执行都是线程来完成的,程序启 动的时候操作系统就帮你创建了一个主线程。 每个线程有自己的堆栈。 DLL 中有没有独立的堆栈,这个问题不好回答,或者说这个问题本身是否有问题。 因为 DLL 中的代码是被某些线程所执行,只有线程拥有堆栈,如果 DLL 中的代码 是 EXE 中的线程所调用,那么这个时候是不是说这个 DLL 没有自己独立的堆栈? 如果 DLL 中的代码是由 DLL 自 己创建的线程所执行,那么是不是说 DLL 有独立 的堆栈? 以上讲的是堆栈,如果对于堆来说,每个 DLL 有自己的堆,所以如果是从 DLL 中动态分配的内存,最好是从 DLL 中删除,如果你从 DLL 中分配内存,然后在 EXE 中,或者另外一个 DLL 中删除,很有可能导致程序崩溃 15. 一语句实现 x 是否为 2 的若干次幂的判断 int i = 512; cout << boolalpha << ((i & (i - 1)) ? false : true) << endl; 对于 2 取余快速做法就是 &(2-1), & 相对于除法、取余指令要快很多,一般+ - * 等只消耗很少 cpu 周期(个位数),而除法至少是 100 个 cpu 周期以上. cout<print(); // 多态 pb->print(); pc->print(); print(a); print(b); print(c); } 答案: A::print() B::print() C::print() A::print() B::print() C::print() A::print() A::print() A::print() 3. 写出程序运行结果 int sum(int a) { auto int c=0; static int b=3; c+=1; b+=2; return(a+b+c); } void main() { int I; ======================================== 第 21 页========== ============================== int a=2; for(I=0;I<5;I++) { printf("%d,", sum(a)); } } // static 会保存上次结果,记住这一点,剩下的自己写 输出:8,10,12,14,16, 4 求函数返回值,输入 x=9999; int func ( x ) { int countx = 0; while ( x ) { countx ++; x = x&(x-1); } return countx; } 结果呢? 知道了这是统计 9999 的二进制数值中有多少个 1 的函数,且有 9999=9×1024+512+256+15 9×1024 中含有 1 的个数为 2 ; 512 中含有 1 的个数为 1 ; 256 中含有 1 的个数为 1 ; 15 中含有 1 的个数为 4 ; 故共有 1 的个数为 8,结果为 8 。 1000 - 1 = 0111 ,正好是原数取反。这就是原理。 用这种方法来求 1 的个数是很效率很高的。 不必去一个一个地移位。循环次数最少。 算法题 算法题 算法题 算法题: :: : 1.用户输入 M,N 值,从 1 至 N 开始顺序循环数数,每数到 M 输出该数值,直至全 部输出。写出 C 程序。 循环链表,用取余操作做 //这样写感觉不是太好,置 1 表示被访问过。 void joe(int n,int m) { int *a = new int[n]; int i=0; ======================================== 第 22 页========== ============================== int pos=0; while(i #i nclude typedef struct Node { int index; struct Node *next; }JosephuNode; int Josephu(int n, int m) { int i, j; JosephuNode *head, *tail; head = tail = (JosephuNode *)malloc(sizeof(JosephuNode)); for (i = 1; i < n; ++i) { tail->index = i; tail->next = (JosephuNode *)malloc(sizeof(JosephuNode)); tail = tail->next; } tail->index = i; tail->next = head; for (i = 1; tail != head; ++i) { for (j = 1; j < m; ++j) { tail = head; head = head->next; } tail->next = head->next; printf("第%4d 个出局的人是:%4d 号\n", i, head->index); free(head); ======================================== 第 24 页========== ============================== head = tail->next; } i = head->index; free(head); return i; } int main() { int n, m; scanf("%d%d", &n, &m); printf("最后胜利的是%d 号!\n", Josephu(n, m)); system("pause"); return 0; } 2、有 10 亿个浮点数,求出其中最大的 10000 个 ,用了标准库的,不让用的话, 只能自己写堆函数 vector bigs(10000,0); vector::iterator it; for(it=bigs.begin();it!=bigs.end();it++) { *it = (float)rand()/7; // 数据都是用随机数模拟的 } cout<() ); float ff; time_t t1,t2; time(&t1); for(int i=0;i<1000000000;i++) { ff = (float) rand()/7; if(ff>bigs[0]) { pop_heap(bigs.begin(),bigs.end(),greater()); bigs.pop_back(); bigs.push_back(ff); push_heap(bigs.begin(),bigs.end(),greater()); } } time(&t2); cout<<(long)(t2-t1)<0;i--) { fixdown(p,k,n); } while(n>0) { swap(p[n],p[1]); fixdown(p,1,--n); } } 3 . 在不用第三方参数的情况下,交换两个参数的值 感觉比较:( , bt 而且还是 基础题。 方法一: i=i+j; j=i-j; i=i-j; 方法二: i^=j; j^=i; i^=j; 方法三: a = a+b-(b=a) 对于方法一、三 i=i+j 如果 i、j 是两个比较大的数,i+j 可能越界,所以方法 二更好一些 4 )输出和为一个给定整数的所有组合 ======================================== 第 26 页========== ============================== 例如 n=5 5=1+4;5=2+3 (相加的数不能重复) 则输出 1,4;2,3 。 #i nclude int main(void) { unsigned long int i,j,k; printf("please input the number\n"); scanf("%d",&i); if( i % 2 == 0) j = i / 2; else j = i / 2 + 1; printf("The result is \n"); for(k = 0; k < j; k++) printf("%d = %d + %d\n",i,k,i - k); return 0; } 5 ) 写一段程序,找出数组中第 k 大小的数,输出数所在的位置。例如{2,4,3, 4,7} 中,第一大的数 是 7,位置在 4。第二大、第三大的数都是 4,位置在 1、 3 随便输出哪一个均可。函数接口为:int find_orderk(const int* narry,const int n,const int k) 要求算法复杂度不能是 O(n^2), 应该 o(nlgn) 吧 n^2 也太容易了冒泡排序都可 以 谢谢! 可以先用快速排序进行排序,其中用另外一个进行地址查找 代码如下,在 VC++6.0 运行通过。给分吧^-^ ,鄙视明明是个 partial_sort, 全排 sort 效率差多了 贴一份标准库代码,以后把堆排序所有函数不上。 template inline void _Partial_sort(_RanIt _First, _RanIt _Mid, _RanIt _Last, _Ty *) { // order [First, _Last) up to _Mid, using operator< std::make_heap(_First, _Mid); for (_RanIt _Next = _Mid; _Next < _Last; ++_Next) if (*_Next < *_First) _Pop_heap(_First, _Mid, _Next, _Ty(*_Next), _Dist_type(_First)); // replace top with new largest ======================================== 第 27 页========== ============================== std::sort_heap(_First, _Mid); } ///////////////////////////////////////////////// 6)求 1000!的未尾有几个 0(用素数相乘的方法来做,如 72=2*2*2*3*3); 求出 1->1000 里,能被 5 整除的数的个数 n1,能被 25 整除的数的个数 n2,能被 125 整除的数的个数 n3, 能被 625 整除的数的个数 n4. 1000!末尾的零的个数=n1+n2+n3+n4; #i nclude #define NUM 1000 int find5(int num) { int ret=0; while(num%5==0) { num/=5; ret++; } return ret; } int main() { int result=0; int i; for(i=5;i<=NUM;i+=5) { result+=find5(i); } printf(" the total zero number is %d\n",result); return 0; } 7). 编程实现:把十进制数(long 型)分别以二进制和十六进制形式输出,不能 使用 printf 系列库函数 char* test3(long num) { char* buffer = (char*)malloc(11); buffer[0] = '0'; buffer[1] = 'x'; buffer[10] = '\0'; ======================================== 第 28 页========== ============================== char* temp = buffer + 2; for (int i=0; i < 8; i++) { temp[i] = (char)(num<<4*i>>28); temp[i] = temp[i] >= 0 ? temp[i] : temp[i] + 16; temp[i] = temp[i] < 10 ? temp[i] + 48 : temp[i] + 55; } return buffer; } 8)输入 N, 打印 N*N 矩阵 比如 N = 3 ,打印: 螺旋矩阵 1 2 3 8 9 4 7 6 5 N = 4 ,打印: 1 2 3 4 12 13 14 5 11 16 15 6 10 9 8 7 解答: 1 #define N 15 int s[N][N]; void main() { int k = 0, i = 0, j = 0; int a = 1; for( ; k < (N+1)/2; k++ ) { while( j < N-k ) s[i][j++] = a++; i++; j--; while( i < N-k ) s[i++][j] = a++; i--; j--; while( j > k-1 ) s[i][j--] = a++; i--; j++; while( i > k ) s[i--][j] = a++; i++; j++; } for( i = 0; i < N; i++ ) { for( j = 0; j < N; j++ ) cout << s[i][j] << '\t'; cout << endl; } } ======================================== 第 29 页========== ============================== 9) 斐波拉契数列递归实现的方法如下: int Funct( int n ) { if(n==0) return 1; if(n==1) return 1; retrurn Funct(n-1) + Funct(n-2); } 如何不使用递归,来实现上述函数? 解答:int Funct( int n ) // n 为非负整数 { int a=1; int b=1; int c; if(n==0 || n == 1) return 1; for(int i=1;i1234 int atoii(char* s) { assert(s!=NULL); int num = 0; int temp; while(*s>'0' && *s<'9') { num *= 10; num += *s-'0'; s++; } return num; } 出现次数相当频繁 11). 编程实现:把十进制数(long 型)分别以二进制和十六进制形式输出,不能 使用 printf 系列库函数 char* test3(long num) { ======================================== 第 30 页========== ============================== char* buffer = (char*)malloc(11); buffer[0] = '0'; buffer[1] = 'x'; buffer[10] = '\0'; char* temp = buffer + 2; for (int i=0; i < 8; i++) { temp[i] = (char)(num<<4*i>>28); temp[i] = temp[i] >= 0 ? temp[i] : temp[i] + 16; temp[i] = temp[i] < 10 ? temp[i] + 48 : temp[i] + 55; } return buffer; } 12) 实现任意长度的整数相加或者相乘功能。 void bigadd(char* num,char* str,int len) { for(int i=len;i>0;i--) { num[i] += str[i]; int j = i; while(num[j]>=10) { num[j--] -= 10; num[j] += 1; } } } 13、用递归算法判断数组 a[N] 是否为一个递增数组。 递归的方法,记录当前最大的,并且判断当前的是否比这个还大,大则继续,否 则返回 false 结束: bool fun( int a[], int n ) { if( n= =1 ) return true; if( n= =2 ) return a[n-1] >= a[n-2]; return fun( a,n-1) && ( a[n-1] >= a[n-2] ); } 14、运用四色定理,为 N 个局域举行配色,颜色为 1、2、3、4 四种,另有数组 adj[][N],如 adj[i][j]=1 则表示 i 区域与 j 区域相邻,数组 color[N],如 ======================================== 第 31 页========== ============================== color[i]=1,表示 i 区域的颜色为 1 号颜色。四色填充 正在看图的程序,以后补一个 15.给两个数组和他们的大小,还有一动态开辟的内存,求交集,把交集放 到动态内存 dongtai ,并且返回交集个数 long jiaoji(long* a[],long b[],long* alength,long blength,long* dongtai[]) 如果让用库,放入两个 set 中,然后调用 set_difference 函数。 不让的话就先排序,然后依次比较了。 16 象搜索的输入信息是一个字符串,统计 300 万输入信息中的最热门的前 十条,我们每次输入的一个字符串为不超过 255byte,内存使用只有 1G, 请描述思想,写出算发(c 语言),空间和时间复杂度. 跟 10 亿浮点数的相同,使用堆做部分排序时间复杂度 Nlog10 。 空间?? 用 10 的信息空间 ??????????????????????????????????????????????????????? 17.国内的一些帖吧,如 baidu,有几十万个主题,假设每一个主题都有上亿的跟 帖子,怎么样设计这个系统速度最好,请描述思想,写出算发(c 语言),空间 和时间复杂度, 每一个主题都有上亿的跟帖子,那 baidu 就崩溃了。 天知道想问什么?? 18. 用两个栈实现一个队列的功能?要求给出算法和思路! 设 2 个栈为 A,B, 一开始均为空. 入队: 将新元素 push 入栈 A; 出队: (1)判断栈 B 是否为空; (2)如果不为空,则将栈 A 中所有元素依次 pop 出并 push 到栈 B ; (3)将栈 B 的栈顶元素 pop 出; 19 求组合数: 求 n 个数(1....n)中 k 个数的组合.... 如:combination(5,3) 要求输出:543,542,541,532,531,521,432,431,421,321 , #i nclude int pop(int *); int push(int ); void combination(int ,int ); int stack[3]={0}; top=-1; ======================================== 第 32 页========== ============================== int main() { int n,m; printf("Input two numbers:\n"); while( (2!=scanf("%d%*c%d",&n,&m)) ) { fflush(stdin); printf("Input error! Again:\n"); } combination(n,m); printf("\n"); } void combination(int m,int n) { int temp=m; push(temp); while(1) { if(1==temp) { if(pop(&temp)&&stack[0]==n) //当栈底元素弹出&&为可能取的 最小值,循环退出 break; } else if( push(--temp)) { printf("%d%d%d ",stack[0],stack[1],stack[2]); pop(&temp); } } } int push(int i) { stack[++top]=i; if(top<2) return 0; else return 1; } int pop(int *i) { *i=stack[top--]; if(top>=0) ======================================== 第 33 页========== ============================== return 0; else return 1; } ///////////////////////////////////// ///////////////////////////////////// ///////////////////////////////////// ///////////////////////////////////// 找错题 找错题 找错题 找错题: :: : 1.下面是 C 语言中两种 if 语句判断方式。请问哪种写法更好?为什么? int n; if (n == 10) // 第一种判断方式 if (10 == n) // 第二种判断方式 如果少了个=号,编译时就会报错,减少了出错的可能行,可以检测出是否少了= 2. 下面的代码有什么问题? void DoSomeThing(...) { char* p; ... p = malloc(1024); // 分配 1K 的空间 if (NULL == p) return; ... p = realloc(p, 2048); // 空间不够,重新分配到 2K if (NULL == p) return; ... } A: p = malloc(1024); 应该写 成: p = (char *) malloc(1024*sizeof(char)); 没有释放 p 的空间,造成内存泄漏。 3. 下面的代码有什么问题?并请给出正确的写法。 void DoSomeThing(char* p) { char str[16]; int n; assert(NULL != p); sscanf(p, "%s%d", str, n); ======================================== 第 34 页========== ============================== if (0 == strcmp(str, "something")) { ... } } A: sscanf(p, "%s%d", str, n); 这句该写成:scanf(p, "%s%d", str, &n); 如果 %s 在前必须指定长度,不然 sscanf 不知何时取字符串结束 scanf(p) --------------------------------------------------------------------- ----- 4.下面代码有什么错误? Void test1() { char string[10]; char *str1="0123456789"; strcpy(string, str1); } 数组越界 --------------------------------------------------------------------- ----- 5.下面代码有什么问题? Void test2() { char string[10], str1[10]; for(i=0; i<10;i++) { str1[i] ='a'; } strcpy(string, str1); } str1 没有置字符串结束符'\0' , 数组越界 --------------------------------------------------------------------- ----- 6.下面代码有什么问题? Void test3(char* str1) { char string[10]; if(strlen(str1)<=10) { ======================================== 第 35 页========== ============================== strcpy(string, str1); } } == 数组越界 ==strcpy 拷贝的结束标志是查找字符串中的\0 因此如果字符串中没有遇到\0 的话 会一直复制,直到遇到\0,上面的 123 都因此产生越界的情况 建议使用 strncpy 和 memcpy --------------------------------------------------------------------- ----- 7.下面代码有什么问题? #define MAX_SRM 256 DSN get_SRM_no() { static int SRM_no; // 是不是这里没赋初值? int I; for(I=0;I=MAX_SRM) return (NULL_SRM); else return SRM_no; } // 网上有写:系统会初始化 static int 变量为 0,但该值会一直保存,所谓的 不可重入.. 扯淡. 答案: 函数永远返回 NULL_SRM ,因为最后 i 是等于 MAX_SRM 的,SRM_no 最大 会是 256 ,而不是 255。连个都是后++ 产生的问题。 8. 下面代码有什么问题? void GetMemory(char *p){ p=(char *)malloc(100); } void Test(void){ char *str=NULL; ======================================== 第 36 页========== ============================== GetMemory=(str); strcpy(str,"hello world"); printf(str); } A:错误--参数的值改变后,不会传回,GetMemory 并不能传递动态内存,Test 函 数中的 str 一直都是 NULL 。 strcpy(str, "hello world"); 将使程序崩溃。 9 下面这个程序执行后会有什么错误或者效果: #define MAX 255 int main() { unsigned char A[MAX]; //i 被定义为 unsigned char for (unsigned char i=0;i<=MAX;i++) A[i]=i; } 解答:死循环加数组越界访问(C/C++ 不进行数组越界检查) MAX=255 数组 A 的下标范围为:0..MAX-1,这是其一.. 其二.当 i 循环到 255 时,循环内执行: A[255]=255; 这句本身没有问题..但是返回 for (i=0;i<=MAX;i++)语句时, 由于 unsigned char 的取值范围在(0..255),i++以后 i 又为 0 了..无限循环下去. 10 、请找出下面代码中的所以错误 “说明:以下代码是把一个字符串倒序,如 abcd” “倒序后变为 dcba” 1、#include"string.h" 2、main() 3、{ 4 、 char*src="hello,world"; 5 、 char* dest=NULL; 6 、 int len=strlen(src); 7 、 dest=(char*)malloc(len);//len+1 8 、 char* d=dest; 9 、 char* s=src[len-1]; 10 、 while(len--!=0) 11 、 *d++=*s--; Dest[len]==’\0’; 12 、 printf("%s",dest); 13 、 return 0; 14、} 答: 方法 1 : int main(){ char* src = "hello,world"; ======================================== 第 37 页========== ============================== int len = strlen(src); char* dest = (char*)malloc(len+1);//要为\0 分配一个空间 char* d = dest; char* s = &src[len-1];// 指向最后一个字符 while( len-- != 0 ) *d++=*s--; *d = 0;//尾部要加\0 printf("%s\n",dest); free(dest);// 使用完,应当释放空间,以免造成内存汇泄露 return 0; } 11.找错题: 1.请问下面程序有什么错误? int a[60][250][1000],i,j,k; for(k=0;k<=1000;k++) for(j=0;j<250;j++) for(i=0;i<60;i++) a[i][j][k]=0; 把循环语句内外换一下 , 造成大量的内存页失效 stl stl stl stl 应用常见问题 应用常见问题 应用常见问题 应用常见问题 1. 编译器的解析 list data(istream_iterator(cin),istream_iterator()); 这不是声明一个 list 变量 data,而是被认为是一格函数声明. 可以使用如 下方法(effective stl 有讲) istream_iterator dataBeg(cin); 2. list data(dataBeg,istream_iterator()); 当然还有 stack> sk; >> 被解析为操作符.当然这个容易避免,中间 加个空格就解决了 stack > sk; 2 . front() 与 begin() 一般经常使用容器的 begin() 函数,因为常用 iterator 取得返回值,而 front() 返回的是容器内第一个变量的引用. vector iv; vector::iterator it = v.begin(); vector::reference ref = v.front(); 对于一些 api 函数如 fun(int *) 需要的参数,做为输入参数 &*begin()写起来 总是怪异。&front()相对好一点,当然&v[0] 更自然。特别是如需从第 3 个元素 开始。&v[3] 更是最佳选择。 vc6 里面 vector 的 begin()返回的是原类型(如 vector 是 int )。所以可 以写 fun(begin())不会出错, stl 源码分析里面讲的 sgi stl 版本也是,但 ======================================== 第 38 页========== ============================== dev c++, vc2003 里面都明确改成了对内部类 iterator 的返回 。所以任何时候 不要用 fun(begin()) ,尽管有时候他能工作 3. 名称过长的警告 对于 vc6 使用 stl(强烈建议换掉,巨多不标准,标准代码不过的地方,如 list 的 sort 不能指定排序比较函数 bool comp(参数 1,参数 2) 这样的函数) 才有这种 现象,产生过多得 c4786 警告,影响编译速度,可以使用如下命令关闭此编译选 项 #pragma warning(disable:4786) 4. 字符串 字符串长度变量要用 string::size_type 声明而不是 int。基本每种容器都有 这个 typedef. 如需返回长度基本都是 size_type 类型。 判断是否到字符串结尾与 string::npos 比较,跟'\0'比较时 char* 字符的行 为。 vc2003 的 string 并没有使用引用计数(dev 里面使用了引用计数,好像 vc6 也用 了), 这样 string str1 = str2; 的操作与 char* 的 strcopy 效率没什么区别。 但对于 char* s = "123"; string str = s;这样的语句任何版本 string 都不 会使用引用计数,引用计数只有在同类之间赋值才存在 如 string s1 = "hello"; string s2 = s1; 如果使用引用计数 s1[1]='q'时系统要重新给 s1 分配内存。早分配还是晚分配 区别并不明显? 假设 str2 没有使用,编译将其优化掉,那都是只分配一次内存。 但没有考虑引用计数 s1[1]='q' 操作,就可以省掉对引用计数的判断。相反倒 提升了速度。如果需要和 s1 相同指向的字符串,用引用就好了,string& s2=s1. 当然这种情况只是对字符串来说的 btw: mfc 的 CString 是采用引用计数的。c 字符串不像 pascal 把字符串长度放在开 头。但 CString 序列化到文件,是字符长度在前面的。提前知道字符串长度有 利于优化 5. map 插入元素,如果开始插入 map 中没有的元素使用 insert 函数比用 map[]=这样赋值好一些,如果是 hash_map 这样先查找元素,找不到才执行插入. 如: typedef map m_type; typedef m_type::value_type valType; map< string, string > m; string h = "hello"; string w = "world"; m.insert( valType(h,w)); 6. do while(0) 相信有人看到下面这样的宏,其实这样就强制你必须在应用宏时加上结束 符; 这样看起来才像是一个函数 #define xxx() do {xxx } while(0) 7. 初始化数组 int a[10]; memset(a,0,10*sizeof(int)); 相信肯定有人这么做过(int a[]={0,0,0,0...} 这么干的肯定也有),其实不必 ======================================== 第 39 页========== ============================== 这么麻烦 int a[10] = {0};这样就 ok 了。如果不是初始化的时候,而且不是 赋 0 , 还是老老实实用 fill(a,a+10,1); (其实对于 char 数组他调用的还是 memset,不存在效率问题) 当然对于 vector 容器定义时便可指定初始值 vector v(10,1); 8. 警惕函数参数 很多函数都是有偏特化版本的,对于特殊参数保证效率或有不同处理方法,这时 要警惕输入参数。想使用特化版本,参数要保持一直。虽然好的编译器在 release 版本可能帮你选中特化版本。但最好不要太依赖于编译器。例如 fill_n 版本 template inline void fill_n(_OutIt _First, _Diff _Count, const _Ty& _Val){ ..... } inline void fill_n(char *_First, size_t _Count, int _Val) { ::memset(_First, _Val, _Count); } inline void fill_n(signed char *_First, size_t _Count, int _Val) { ::memset(_First, _Val, _Count); } inline void fill_n(unsigned char *_First, size_t _Count, int _Val) { ::memset(_First, _Val, _Count); } 针对字符串选用 memset 进行赋值(能提升相当效率)。下面代码: char str[300]; fill_n(str,150,97); 看似调用了 fill_n(char*,size_t,int) (第二个函数), 其实调用的还是模板函 数(第一个)。因为 150 被编译器解释为 int 类型,与 fill_n(char*,size_t,int) 是不相符合的。还有 fill_n(str,(size_t)150,'a') 也没有调用优化版本,因 为'a' 是 char 而不是 int 类型。 所以 fill_n(str,150*sizeof(char),97) 这种写法才算完美。 主要注意有 size_t 参数的函数, 用 size_t 声明或者多*个 sizeof(type) 9. list 的 earse 对于 vc2003 来说,判断了如果是 end 节点 则不删除。但 sgi stl 没有判断。 所以注意你在 list 里面执行 erase 操作时是否会删掉这个节点。删掉的话整个 链表的基石也就垮了。可以在 dev 编译器(同 gcc)里面执行下面程序,这个循环 从 begin() 开始都没得到链表节点。 int a[5] ={2,3,5,4,1}; list lt(a,a+5); lt.erase(lt.end()); // 明显了点。 list::iterator it; for(it=lt.begin();it!=lt.end();it++) { cout<<*it<<" "; ======================================== 第 40 页========== ============================== } 或许不能删除 end 节点是使用者应该注意的。但看两个库的做法,即使牛人们也 难以统一意见。个人意见是不用 erase 判断是否为头节点,但如果去面试的话。 不介意你"画蛇添足" 。 对于某些面试经常抓着这点不放,你的链表删除怎么没判断是否为头节点。你可 以飞出一本 stl 源码剖析,砸在他头上,指着他的鼻子说: 你看人家 C++标准库 都可以这么做。 sgi stl 一般都没有对这些边界条件做判断,没人会使用 list earse()去删 end 节点。同样如果没有确定 front()是否有元素, 也不要在 deque 上执行 pop_front 操作。 10 deque 的空间 vc 中 deque 跟 vector 相似,自增长,但 deque 有多个大小相同的缓冲区,使用 空间只会增长不会降低。如果想删除弹出节点空间应该使用 list 。 而 sgi stl 实现的 deque (默认对于类型长度大于 int)如果一个缓冲区没有数据就删除。对 于 pj stl 保证了速度,sgi stl 保证了空间。比较也没有太大意义。 11 set 的比较函数对象 给 set 指定比较函数对象时,重载 operator() 要声明成常量函数。如 class less_key{ public: bool operator() (const foo &f1, const foo &f2) const { return (f1.key < f2.key); } }; 因为 set 取得比较函数对象类型后,把该类的常量类型传递给了底层的 rb_tree, 所以不声明 operator()为常量函数,编译便会失败。而且这种关系比较函数对 象并没有改类内成员,所以任何时候最好都声明为 const . 12 vector > 一般对于原始类型声明可变数组可以不指定长度如: vector iv; iv.push_back(1); 但对于类型也是 vector > vector > ivv; ivv[0].push_back(1); cout< > ivv; ivv.push_back(vector()); ivv[0].push_back(1); cout< > ivv(10); 便可以初始化 10 个,毕竟使 用 2 维数组,而且两个维度都可变的情况不多。这样在这 10 个之内就不需要调 用 ivv.push_back(vector()); ======================================== 第 41 页========== ============================== 13 resize 和 reserve 之区别 resize 扩展空间并且初始化元素,reserve 只扩展空间. 对于 vector 调用 resize() 函数后。对 empty () 检测为 false. 而 reserve 后检查 empty() 仍 为 true. 由于 visual assist 的帮助今天,一不小心就调用了 resize() .结果 白浪费好长时间调试 1 ,程序设计(可以用自然语言来描述,不编程): C/C++ “”源代码中,检查花括弧(是 ( 与 “”“), { ”“与 } ”)是否匹配,若不匹配,则输出不匹配花括弧所在的行与列。 2 ,巧排数字,将 1,2,...,19,20 这 20 个数字排成一排,使得相邻的两个数字之和为一个素数, 且 首尾两数字之和也为一个素数。编程打印出所有的排法。 3 ,打印一个 N*N 的方阵, N 为每边字符的个数( 3 〈 N 〈 20 “),要求最外层为 X ”, “第二层为 Y ”,从第三层起每层依次打印数字 0 , 1 , 2 , 3 , ... 例子:当 N =5 ,打印出下面的图形: X X X X X X Y Y Y X X Y 0 Y X X Y Y Y X X X X X X 第一题 可以用一个栈进行匹配 # 第二题 首先构造一个图 G, 图的顶点是 1-20 这些数字,如果两个数字的和是素数就相连。分别 从 1-20 开始,对 图进行周游,结果就是。 # 第三题 N 最大能取到 19 ,不妨设此时从最外层到内层的符号分别是 base[]= {X, Y, 0, 1, 2, 3, 4, 5, 6, 7}, 设 mid = (N+1)/2, 第 i 行第 j 列的值恰好是 base [ min(i%m,j%m) ] 。 const int max_n = 19; const int min_n = 4; static char base[(max_n+1)/2] = {'X', 'Y', '0', '1', '2', '3', '4', '5', ..., '7'}; void print(int n ) { ======================================== 第 42 页========== ============================== // assume that n is valid int i, j, t, m; m = (n + 1) / 2; for (i = 0; i < max_n; ++i) { for (j = 0; j < max_n; ++j) { cout << base [ min(i%m,j%m)]; } cout << "\n"; } } c/c++ 笔试题大解析 笔试题大解析 笔试题大解析 笔试题大解析 ( 转 转转 转 ) 1. 以下三条输出语句分别输出什么? [C 易 ] char str1[] = "abc"; char str2[] = "abc"; const char str3[] = "abc"; const char str4[] = "abc"; const char* str5 = "abc"; const char* str6 = "abc"; cout << boolalpha << ( str1==str2 ) << endl; // 输出什么? cout << boolalpha << ( str3==str4 ) << endl; // 输出什么? cout << boolalpha << ( str5==str6 ) << endl; // 输出什么? 答:分别输出 false,false,true 。 str1 和 str2 都是字符数组,每个都有其自己的存 储区,它们的值则是各存储区首地址,不等; str3 和 str4 同上,只是按 const 语义, 它们所指向的数据区不能修改。 str5 和 str6 并非数组而是字符指针,并不分配存储区, 其后的 “abc” 以常量形式存于静态数据区,而它们自己仅是指向该区首地址的指针,相 等。 12. 以下代码中的两个 sizeof 用法有问题吗? [C 易 ] void UpperCase( char str[] ) // 将 str 中的小写字母转换 成大写字母 { for( size_t i=0; i> temp; unsigned int const size2 = temp; char str2[ size2 ]; 答: str2 定义出错, size2 非编译器期间常量,而数组定义要求长度必须为编译期常 量。 2. 以下反向遍历 array 数组的方法有什么错误? [STL 易 ] vector array; array.push_back( 1 ); array.push_back( 2 ); array.push_back( 3 ); for( vector::size_type i=array.size()-1; i>=0; --i ) // 反 向遍历 array 数组 { cout << array[i] << endl; } 答:首先数组定义有误,应加上类型参数: vector array 。其次 vector::size_type 被定义为 unsigned int ,即无符号数,这样做为循环变量的 i 为 0 时再减 1 就会变成最大的整数,导致循环失去控制。 9. 以下代码中的输出语句输出 0 吗,为什么? [C++ 易 ] struct CLS { int m_i; CLS( int i ) : m_i(i) {} CLS() { CLS(0); } }; CLS obj; ======================================== 第 45 页========== ============================== cout << obj.m_i << endl; 答:不能。在默认构造函数内部再调用带参的构造函数属用户行为而非编译器行为, 亦即仅执行函数调用,而不会执行其后的初始化表达式。只有在生成对象时,初始化表 达式才会随相应的构造函数一起调用。 10. C++ 中的空类,默认产生哪些类成员函数? [C++ 易 ] 答: class Empty { public: Empty(); // 缺省构造函数 Empty( const Empty& ); // 拷贝构造函 数 ~Empty(); // 析构函数 Empty& operator=( const Empty& ); // 赋值运算符 Empty* operator&(); // 取址运算符 const Empty* operator&() const; // 取址运算 符 const }; 3. 以下两条输出语句分别输出什么? [C++ 难 ] float a = 1.0f; cout << (int)a << endl; cout << (int&)a << endl; cout << boolalpha << ( (int)a == (int&)a ) << endl; // 输出什么? float b = 0.0f; cout << (int)b << endl; cout << (int&)b << endl; cout << boolalpha << ( (int)b == (int&)b ) << endl; // 输出什么? 答:分别输出 false 和 true 。注意转换的应用。 (int)a 实际上是以浮点数 a 为参数 构造了一个整型数,该整数的值是 1 , (int&)a 则是告诉编译器将 a 当作整数看(并没 有做任何实质上的转换)。因为 1 以整数形式存放和以浮点形式存放其内存数据是不一 样的,因此两者不等。对 b 的两种转换意义同上,但是 0 的整数形式和浮点形式其内 存数据是一样的,因此在这种特殊情形下,两者相等(仅仅在数值意义上)。 注意,程序的输出会显示 (int&)a=1065353216 ,这个值是怎么来的呢?前面已经 说了, 1 以浮点数形式存放在内存中,按 ieee754 规定,其内容为 0x0000803F (已 考虑字节反序)。这也就是 a 这个变量所占据的内存单元的值。当 (int&)a 出现时,它 ======================================== 第 46 页========== ============================== 相当于告诉它的上下文: “ 把这块地址当做整数看待!不要管它原来是什么。 ” 这样,内 容 0x0000803F 按整数解释,其值正好就是 1065353216 (十进制数)。 通过查看汇编代码可以证实 “(int)a 相当于重新构造了一个值等于 a 的整型数 ” 之说, 而 (int&) 的作用则仅仅是表达了一个类型信息,意义在于为 cout<< 及 == 选择正确的 重载版本。 6. 以下代码有什么问题? [STL 易 ] typedef vector IntArray; IntArray array; array.push_back( 1 ); array.push_back( 2 ); array.push_back( 2 ); array.push_back( 3 ); // 删除 array 数组中所有的 2 for( IntArray::iterator itor=array.begin(); itor!=array.end(); + +itor ) { if( 2 == *itor ) array.erase( itor ); } 答:同样有缺少类型参数的问题。另外,每次调用 “array.erase( itor );” ,被 删除元素之后的内容会自动往前移,导致迭代漏项,应在删除一项后使 itor-- ,使之从 已经前移的下一个元素起继续遍历。 11. 写一个函数,完成内存之间的拷贝。 [ 考虑问题是否全面 ] 答: void* mymemcpy( void *dest, const void *src, size_t count ) { char* pdest = static_cast( dest ); const char* psrc = static_cast( src ); if( pdest>psrc && pdest>,这两个操作符常常希望被连续使用,例如:cout << "hello" << endl; 因此这两个操作符的返回值应该是一个仍然支持这两个操作符的流引用。可选的 其它方案包括:返回一个流对象和返回一个流对象指针。但是对于返回一个流对象,程序 必须重新(拷贝)构造一个新的流对象,也就是说,连续的两个 <<操作符实际上是针 对不同对象的!这无法让人接受。对于返回一个流指针则不能连续使用<<操作符。因此 返回一个流对象引用是惟一选择。这个唯一选择很关键,它说明了引用的重要性以及无可 替代性,也许这就是 C++语言中引入引用这个概念的原因吧。赋值操作符=。这个操作符 象流操作符一样,是可以连续使用的,例如:x = j = 10;或者(x=10)=100;赋值操作 符的返回值必须是一个左值,以便可以被继续赋值。因此引用成了这个操作符的惟一返回 值选择。 例 3 #i nclude int &put(int n); int vals[10]; int error=-1; void main() { put(0)=10; //以 put(0)函数值作为左值,等价于 vals[0]=10; put(9)=20; //以 put(9)函数值作为左值,等价于 vals[9]=20; cout<=0 && n<=9 ) return vals[n]; else { cout<<"subscript error"; return error; } } (5)在另外的一些操作符中,却千万不能返回引用:+-*/ 四则运算符。它们不能返回引 用,Effective C++[1]的 Item23 详细的讨论了这个问题。主要原因是这四个操作符没 有 side effect,因此,它们必须构造一个对象作为返回值,可选的方案包括:返回一个 对象、返回一个局部变量的引用,返回一个 new 分配的对象的引用、返回一个静态对象引 用。根据前面提到的引用作为返回值的三个规则,第 2、3 两个方案都被否决了。静态对象 的引用又因为((a+b) == (c+d))会永远为 true 而导致错误。所以可选的只剩下返回一个 对象了。 6. “ ”引用 与多态的关系? 引用是除指针外另一个可以产生多态效果的手段。这意味着,一个基类的引用可以指向它 的派生类实例。 例 4 Class A; Class B : Class A{...}; B b; A& ref = b; 7. “ ”引用 与指针的区别是什么? 指针通过某个指针变量指向一个对象后,对它所指向的变量间接操作。程序中使用指针, 程序的可读性差;而引用本身就是目标变量的别名,对引用的操作就是对目标变量的操 作。此外,就是上面提到的对函数传 ref 和 pointer 的区别。 8. “”什么时候需要 引用 ? 流操作符<<和>>、赋值操作符=的返回值、拷贝构造函数的参数、赋值操作符=的参数、 其它情况都推荐使用引用。 9. 结构与联合有何区别? 1. 结构和联合都是由多个不同的数据类型成员组成, 但在任何同一时刻, 联合中只存放了 一个被选中的成员(所有成员共用一块地址空间), 而结构的所有成员都存在(不同成员 的存放地址不同)。 2. 对于联合的不同成员赋值, 将会对其它成员重写, 原来成员的值就不存在了, 而对于结 构的不同成员赋值是互不影响的。 10. “”写出下面关于 联合 的题目的输出? a) #i nclude union { int i; char x[2]; }a; void main() { a.x[0] = 10; a.x[1] = 1; printf("%d",a.i); } 答案:266 (低位低地址,高位高地址,内存占用情况是 Ox010A) b) main() { union{ /*定义一个联合*/ int i; struct{ /*在联合中定义一个结构*/ char first; char second; }half; }number; number.i=0x4241; /*联合成员赋值*/ printf("%c%c\n", number.half.first, mumber.half.second); number.half.first='a'; /*联合中结构成员赋值*/ number.half.second='b'; printf("%x\n", number.i); getch(); } 答案: AB (0x41 对应'A',是低位;Ox42 对应'B',是高位) 6261 (number.i 和 number.half 共用一块地址空间) 11. 已 知 strcpy 的 函 数 原 型 : char *strcpy(char *strDest, const char *strSrc)其中 strDest 是目的字符串,strSrc 是源字符串。不调用 C++/C 的字符 串库函数,请编写函数 strcpy。 答案: char *strcpy(char *strDest, const char *strSrc) { if ( strDest == NULL || strSrc == NULL) return NULL ; if ( strDest == strSrc) return strDest ; char *tempptr = strDest ; while( (*strDest++ = *strSrc++) != ‘\0’) ; return tempptr ; } 12. 已知 String 类定义如下: class String { public: String(const char *str = NULL); // 通用构造函数 String(const String &another); // 拷贝构造函数 ~ String(); // 析构函数 String & operater =(const String &rhs); // 赋值函数 private: char *m_data; // 用于保存字符串 }; 尝试写出类的成员函数实现。 答案: String::String(const char *str) { if ( str == NULL ) //strlen 在参数为 NULL 时会抛异常才会有这步判断 { m_data = new char[1] ; m_data[0] = '\0' ; } else { m_data = new char[strlen(str) + 1]; strcpy(m_data,str); } } String::String(const String &another) { m_data = new char[strlen(another.m_data) + 1]; strcpy(m_data,other.m_data); } String& String::operator =(const String &rhs) { if ( this == &rhs) return *this ; delete []m_data; //删除原来的数据,新开一块内存 m_data = new char[strlen(rhs.m_data) + 1]; strcpy(m_data,rhs.m_data); return *this ; } String::~String() { delete []m_data ; } 13. .h 头文件中的 ifndef/define/endif 的作用? 答:防止该头文件被重复引用。 14. #i nclude 与 #i nclude "file.h"的区别? 答:前者是从 Standard Library 的路径寻找和引用 file.h,而后者是从当前工作路径搜 寻并引用 file.h。 15.在 C++ 程序中调用被 C 编译器编译后的函数,为什么要加 extern “C”? 首先,作为 extern 是 C/C++语言中表明函数和全局变量作用范围(可见性)的关键字, 该关键字告诉编译器,其声明的函数和变量可以在本模块或其它模块中使用。 通常,在模块的头文件中对本模块提供给其它模块引用的函数和全局变量以关键字 extern 声明。例如,如果模块 B 欲引用该模块 A 中定义的全局变量和函数时只需包含模 块 A 的头文件即可。这样,模块 B 中调用模块 A 中的函数时,在编译阶段,模块 B 虽然 找不到该函数,但是并不会报错;它会在连接阶段中从模块 A 编译生成的目标代码中找 到此函数 extern "C"是连接申明(linkage declaration),被 extern "C"修饰的变量和函数是按照 C 语言方式编译和连接的,来看看 C++中对类似 C 的函数是怎样编译的: 作为一种面向对象的语言,C++支持函数重载,而过程式语言 C 则不支持。函数被 C++ 编译后在符号库中的名字与 C 语言的不同。例如,假设某个函数的原型为: void foo( int x, int y );   该函数被 C 编译器编译后在符号库中的名字为 _foo,而 C++编译器则会产生像 _foo_int_int 之类的名字(不同的编译器可能生成的名字不同,但是都采用了相同的机 “制,生成的新名字称为 mangled name”)。 _foo_int_int 这样的名字包含了函数名、函数参数数量及类型信息,C++就是靠这种机 制来实现函数重载的。例如,在 C++中,函数 void foo( int x, int y )与 void foo( int x, float y )编译生成的符号是不相同的,后者为_foo_int_float。 同样地,C++中的变量除支持局部变量外,还支持类成员变量和全局变量。用户所编写 程序的类成员变量可能与全局变量同名,我们以"."来区分。而本质上,编译器在进行编 译时,与函数的处理相似,也为类中的变量取了一个独一无二的名字,这个名字与用户 程序中同名的全局变量名字不同。 未加 extern "C"声明时的连接方式 假设在 C++中,模块 A 的头文件如下: // 模块 A 头文件 moduleA.h #ifndef MODULE_A_H #define MODULE_A_H int foo( int x, int y ); #endif   在模块 B 中引用该函数: // 模块 B 实现文件 moduleB.cpp #i nclude "moduleA.h" foo(2,3); 实际上 , 在连 接阶 段 ,连 接器 会 从模 块 A 生 成 的目 标文 件 moduleA.obj 中寻找 _foo_int_int 这样的符号! 加 extern "C"声明后的编译和连接方式 加 extern "C"声明后,模块 A 的头文件变为: // 模块 A 头文件 moduleA.h #ifndef MODULE_A_H #define MODULE_A_H extern "C" int foo( int x, int y ); #endif   在模块 B 的实现文件中仍然调用 foo( 2,3 ),其结果是: (1)模块 A 编译生成 foo 的目标代码时,没有对其名字进行特殊处理,采用了 C 语言 的方式; (2)连接器在为模块 B 的目标代码寻找 foo(2,3)调用时,寻找的是未经修改的符号名 _foo。 如果在模块 A 中函数声明了 foo 为 extern "C"类型,而模块 B 中包含的是 extern int foo( int x, int y ) ,则模块 B 找不到模块 A 中的函数;反之亦然。 所以,可以用一句话概括 extern “C”这个声明的真实目的(任何语言中的任何语法特性 的诞生都不是随意而为的,来源于真实世界的需求驱动。我们在思考问题时,不能只停留 在这个语言是怎么做的,还要问一问它为什么要这么做,动机是什么,这样我们可以更 深入地理解许多问题):实现 C++与 C 及其它语言的混合编程。   明白了 C++中 extern "C"的设立动机,我们下面来具体分析 extern "C"通常的使用技 巧: extern "C"的惯用法 ( 1 ) 在 C++ 中 引 用 C 语 言 中 的 函 数 和 变 量 , 在 包 含 C 语 言 头 文 件 ( 假 设 为 cExample.h)时,需进行下列处理: extern "C" { #i nclude "cExample.h" } 而在 C 语言的头文件中,对其外部函数只能指定为 extern 类型,C 语言中不支持 extern "C"声明,在.c 文件中包含了 extern "C"时会出现编译语法错误。 C++引用 C 函数例子工程中包含的三个文件的源代码如下: /* c 语言头文件:cExample.h */ #ifndef C_EXAMPLE_H #define C_EXAMPLE_H extern int add(int x,int y); #endif /* c 语言实现文件:cExample.c */ #i nclude "cExample.h" int add( int x, int y ) { return x + y; } // c++实现文件,调用 add:cppFile.cpp extern "C" { #i nclude "cExample.h" } int main(int argc, char* argv[]) { add(2,3); return 0; } 如果 C++调用一个 C 语言编写的.DLL 时,当包括.DLL 的头文件或声明接口函数时,应 加 extern "C" { }。 (2)在 C 中引用 C++语言中的函数和变量时,C++的头文件需添加 extern "C",但 是在 C 语言中不能直接引用声明了 extern "C"的该头文件,应该仅将 C 文件中将 C++ 中定义的 extern "C"函数声明为 extern 类型。 C 引用 C++函数例子工程中包含的三个文件的源代码如下: //C++ 头文件 cppExample.h #ifndef CPP_EXAMPLE_H #define CPP_EXAMPLE_H extern "C" int add( int x, int y ); #endif //C++ 实现文件 cppExample.cpp #i nclude "cppExample.h" int add( int x, int y ) { return x + y; } /* C 实现文件 cFile.c */ /* 这样会编译出错:#i nclude "cExample.h" */ extern int add( int x, int y ); int main( int argc, char* argv[] ) { add( 2, 3 ); return 0; } 15 题目的解答请参考《C++中 extern“C”含义深层探索》注解。 16. 关联、聚合(Aggregation)以及组合(Composition)的区别? 涉及到 UML“”“”中的一些概念:关联是表示两个类的一般性联系,比如 学生 和 老师 就是 一种关联关系;聚合表示 has-a 的关系,是一种相对松散的关系,聚合类不需要对被聚 合类负责,如下图所示,用空的菱形表示聚合关系: 从实现的角度讲,聚合可以表示为: class A {...} class B { A* a; .....} 而组合表示 contains-a 的关系,关联性强于聚合:组合类与被组合类有相同的生命周期 组合类要对被组合类负责,采用实心的菱形表示组合关系: 实现的形式是: class A{...} class B{ A a; ...} 17.面向对象的三个基本特征,并简单叙述之? 1. 封装:将客观事物抽象成类,每个类对自身的数据和方法实行 protection(private, protected,public) 2. 继承:广义的继承有三种实现形式:实现继承(指使用基类的属性和方法而无需额外 编码的能力)、可视继承(子窗体使用父窗体的外观和实现代码)、接口继承(仅使用属 性和方法,实现滞后到子类实现)。前两种(类继承)和后一种(对象组合=>接口继承 以及纯虚函数)构成了功能复用的两种方式。 3. 多态:是将父对象设置成为和一个或更多的他的子对象相等的技术,赋值之后,父对 象就可以根据当前赋值给它的子对象的特性以不同的方式运作。简单的说,就是一句话: 允许将子类类型的指针赋值给父类类型的指针。 18. 重载(overload)和重写(overried “”,有的书也叫做 覆盖 )的区别? 常考的题目。从定义上来说: 重载:是指允许存在多个同名函数,而这些函数的参数表不同(或许参数个数不同,或 许参数类型不同,或许两者都不同)。 重写:是指子类重新定义复类虚函数的方法。 从实现原理上来说: 重载:编译器根据函数不同的参数表,对同名函数的名称做修饰,然后这些同名函数就 成了不同的函数(至少对于编译器来说是这样的)。如,有两个同名函数: function func(p:integer):integer;和 function func(p:string):integer;。那么编译器做过修饰后 的函数名称可能是这样的:int_func、str_func。对于这两个函数的调用,在编译器间就 已经确定了,是静态的。也就是说,它们的地址在编译期就绑定了(早绑定),因此,重 载和多态无关! 重写:和多态真正相关。当子类重新定义了父类的虚函数后,父类指针根据赋给它的不同 的子类指针,动态的调用属于子类的该函数,这样的函数调用在编译期间是无法确定的 (调用的子类的虚函数的地址无法给出)。因此,这样的函数地址是在运行期绑定的(晚 绑定)。 19. 多态的作用? 主要是两个:1. 隐藏实现细节,使得代码能够模块化;扩展代码模块,实现代码重用; 2. 接口重用:为了类在继承和派生的时候,保证使用家族中任一类的实例的某一属性时 的正确调用。 20. Ado 与 Ado.net 的相同与不同? “除了 能够让应用程序处理存储于 DBMS “中的数据 这一基本相似点外,两者没有太多共 同之处。但是 Ado 使用 OLE DB 接口并基于微软的 COM 技术,而 ADO.NET 拥有自己 的 ADO.NET 接口并且基于微软的.NET 体系架构。众所周知.NET 体系不同于 COM 体系, ADO.NET 接口也就完全不同于 ADO 和 OLE DB 接口,这也就是说 ADO.NET 和 ADO 是两种数据访问方式。ADO.net 提供对 XML 的支持。 21. new delete 与 malloc free 的联系与区别? 答案:都是在堆(heap)上进行动态的内存操作。用 malloc 函数需要指定内存分配的字节 数并且不能初始化对象,new 会自动调用对象的构造函数。delete 会调用对象的 destructor,而 free 不会调用对象的 destructor. 22. #define DOUBLE(x) x+x ,i = 5*DOUBLE(5) ; i 是多少? 答案:i 为 30。 23. 有哪几种情况只能用 intialization list 而不能用 assignment? 答案:当类中含有 const、reference 成员变量;基类的构造函数都需要初始化表。 24. C++是不是类型安全的? 答案:不是。两个不同类型的指针之间可以强制转换(用 reinterpret cast)。C#是类型 安全的。 25. main 函数执行以前,还会执行什么代码? 答案:全局对象的构造函数会在 main 函数之前执行。 26. 描述内存分配方式以及它们的区别? 1 ) 从静态存储区域分配。内存在程序编译的时候就已经分配好,这块内存在程序的整个 运行期间都存在。例如全局变量,static 变量。 2 ) 在栈上创建。在执行函数时,函数内局部变量的存储单元都可以在栈上创建,函数执 行结束时这些存储单元自动被释放。栈内存分配运算内置于处理器的指令集。 3 ) 从堆上分配,亦称动态内存分配。程序在运行的时候用 malloc 或 new 申请任意多 少的内存,程序员自己负责在何时用 free 或 delete 释放内存。动态内存的生存期由程序 员决定,使用非常灵活,但问题也最多。 27.struct 和 class 的区别 答案:struct 的成员默认是公有的,而类的成员默认是私有的。struct 和 class 在其他 方面是功能相当的。 从感情上讲,大多数的开发者感到类和结构有很大的差别。感觉上结构仅仅象一堆缺乏封 装和功能的开放的内存位,而类就象活的并且可靠的社会成员,它有智能服务,有牢固 的封装屏障和一个良好定义的接口。既然大多数人都这么认为,那么只有在你的类有很少 的方法并且有公有数据(这种事情在良好设计的系统中是存在的!)时,你也许应该使用 struct 关键字,否则,你应该使用 class 关键字。 28.当一个类 A 中没有声明任何成员变量与成员函数 ,这时 sizeof(A)的值是多少, 如果不是零,请解释一下编译器为什么没有让它为零。(Autodesk) 答案:肯定不是零。举个反例,如果是零的话,声明一个 class A[10]对象数组,而每一 个对象占用的空间是零,这时就没办法区分 A[0],A[1]…了。 29. 在 8086 汇编下,逻辑地址和物理地址是怎样转换的?(Intel) 答案:通用寄存器给出的地址,是段内偏移地址,相应段寄存器地址*10H+通用寄存器 内地址,就得到了真正要访问的地址。 30. 比较 C++中的 4 种类型转换方式? 重点是 static_cast, dynamic_cast 和 reinterpret_cast 的区别和应用。 请参考:http://blog.csdn.net/wfwd/archive/2006/05/30/763785.aspx 31.分别写出 BOOL,int,float,指针类型的变量 a “”与 零 的比较语句。 答案: BOOL : if ( !a ) or if(a) int : if ( a == 0) float : const EXPRESSION EXP = 0.000001 if ( a < EXP && a >-EXP) pointer : if ( a != NULL) or if(a == NULL) 32.请说出 const 与#define 相比,有何优点? 答案:1 ) const 常量有数据类型,而宏常量没有数据类型。编译器可以对前者进行类型 安全检查。而对后者只进行字符替换,没有类型安全检查,并且在字符替换可能会产生意 料不到的错误。 2 ) 有些集成化的调试工具可以对 const 常量进行调试,但是不能对宏常量进行调试。 33.简述数组与指针的区别? 数组要么在静态存储区被创建(如全局数组),要么在栈上被创建。指针可以随时指向任 意类型的内存块。 (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 字节 } 34.类成员函数的重载、覆盖和隐藏区别? 答案: a.成员函数被重载的特征: (1)相同的范围(在同一个类中); (2)函数名字相同; (3)参数不同; (4)virtual 关键字可有可无。 b.覆盖是指派生类函数覆盖基类函数,特征是: (1)不同的范围(分别位于派生类与基类); (2)函数名字相同; (3)参数相同; (4)基类函数必须有 virtual 关键字。 c.“ ”隐藏 是指派生类的函数屏蔽了与其同名的基类函数,规则如下: (1)如果派生类的函数与基类的函数同名,但是参数不同。此时,不论有无 virtual 关 键字,基类的函数将被隐藏(注意别与重载混淆)。 (2)如果派生类的函数与基类的函数同名,并且参数也相同,但是基类函数没有 virtual 关键字。此时,基类的函数被隐藏(注意别与覆盖混淆)。 35. 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 36. 如何打印出当前源文件的文件名以及源文件的当前行号? 答案: cout << __FILE__ ; cout<<__LINE__ ; __FILE__和__LINE__是系统预定义宏,这种宏并不是在某个文件中定义的,而是由编译 器定义的。 37. 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. 38. 如何判断一段程序是由 C 编译程序还是由 C++编译程序编译的? 答案: #ifdef __cplusplus cout<<"c++"; #else cout<<"c"; #endif 39.文件中有一组整数,要求排序后输出到另一个文件中 答案: #i nclude #i nclude using namespace std; void Order(vector& data) //bubble sort { 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 ) { vectordata; 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<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 ; } (3)已知两个链表 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 ; } 41. 分析一下这段程序的输出 (Autodesk) class B { public: B() { cout<<"default constructor"< instance of B) { cout<<"constructed by parameter " << data < maxnumber ) { sec_max = maxnumber ; maxnumber = data[i] ; } else { if ( data[i] > sec_max ) sec_max = data[i] ; } } return sec_max ; } 43. 写一个在一个字符串(n)中寻找一个子串(m)第一个位置的函数。 KMP 算法效率最好,时间复杂度是O(n+m)。 44. 多重继承的内存分配问题: 比如有 class A : public class B, public class C {} 那么 A 的内存结构大致是怎么样的? 这个是 compiler-dependent 的, 不同的实现其细节可能不同。 如果不考虑有虚函数、虚继承的话就相当简单;否则的话,相当复杂。 可以参考《深入探索 C++对象模型》,或者: http://blog.csdn.net/wfwd/archive/2006/05/30/763797.aspx 45. 如何判断一个单链表是有环的?(注意不能用标志位,最多只能用两个额外 指针) struct node { char val; node* next;} bool check(const node* head) {} //return false : 无环;true: 有环 一种 O(n)的办法就是(搞两个指针,一个每次递增一步,一个每次递增两步,如果有 环的话两者必然重合,反之亦然): bool check(const node* head) { if(head==NULL) return false; node *low=head, *fast=head->next; while(fast!=NULL && fast->next!=NULL) { low=low->next; fast=fast->next->next; if(low==fast) return true; } return false; } 46、一个学生的信息是:姓名,学号,性别,年龄等信息,用一个链表,把这些 学生信息连在一起, 给出一个 age, 在些链表中删除学生年龄等于 age 的学生信 息。 程序代码 #i nclude "stdio.h" #i nclude "conio.h" struct stu{ char name[20]; char sex; int no; int age; struct stu * next; }*linklist; struct stu *creatlist(int n) { int i; //h 为头结点,p 为前一结点,s 为当前结点 struct stu *h,*p,*s; h = (struct stu *)malloc(sizeof(struct stu)); h->next = NULL; p=h; for(i=0;inext = s; printf("Please input the information of the student: name sex no age n"); scanf("%s %c %d %d",s->name,&s->sex,&s->no,&s->age); s->next = NULL; 访问固定的内存位置(Accessing fixed memory locations ) C C++ Development 47. 嵌入式系统经常具有要求程序员去访问某特定的内存位置的特点。在某工程中, 要求设置一绝对地址为 0x67a9 的整型变量的值为 0xaa66。编译器是一个纯粹的 ANSI 编译器。写代码去完成这一任务。 这一问题测试你是否知道为了访问一绝对地址把一个整型数强制转换(typecast)为一 指针是合法的。这一问题的实现方式随着个人风格不同而不同。典型的类似代码如下: int *ptr; ptr = (int *)0x67a9; *ptr = 0xaa55; 一个较晦涩的方法是: *(int * const)(0x67a9) = 0xaa55; 即使你的品味更接近第二种方案,但我建议你在面试时使用第一种方案。 中断(Interrupts ) 48. 中断是嵌入式系统中重要的组成部分,这导致了很多编译开发商提供一种扩 —展 让标准 C 支持中断。具代表事实是,产生了一个新的关键字__interrupt。下面 的代码就使用了__interrupt 关键字去定义了一个中断服务子程序(ISR),请评论 一下这段代码的。 __interrupt double compute_area (double radius) { double area = PI * radius * radius; printf(" Area = %f", area); return area; } 这个函数有太多的错误了,以至让人不知从何说起了: 1). ISR 不能返回一个值。如果你不懂这个,那么你不会被雇用的。 2). ISR 不能传递参数。如果你没有看到这一点,你被雇用的机会等同第一项。 3). 在许多的处理器/编译器中,浮点一般都是不可重入的。有些处理器/编译器需要让额 处的寄存器入栈,有些处理器/编译器就是不允许在 ISR 中做浮点运算。此外,ISR 应该是 短而有效率的,在 ISR 中做浮点运算是不明智的。 4). 与第三点一脉相承,printf()经常有重入和性能上的问题。如果你丢掉了第三和第四点, 我不会太为难你的。不用说,如果你能得到后两点,那么你的被雇用前景越来越光明了。 代码例子(Code examples) 49 . 下面的代码输出是什么,为什么? void foo(void) { unsigned int a = 6; int b = -20; (a+b > 6) puts("> 6") : puts("<= 6"); } 这个问题测试你是否懂得 C 语言中的整数自动转换原则,我发现有些开发者懂得极少这 “些东西。不管如何,这无符号整型问题的答案是输出是 >6”。原因是当表达式中存在有符 号类型和无符号类型时所有的操作数都自动转换为无符号类型。因此-20 变成了一个非常 大的正整数,所以该表达式计算出的结果大于 6。这一点对于应当频繁用到无符号数据类 型的嵌入式系统来说是丰常重要的。如果你答错了这个问题,你也就到了得不到这份工作 的边缘。 50. 评价下面的代码片断: unsigned int zero = 0; unsigned int compzero = 0xFFFF; 对于一个 int 型不是 16 位的处理器为说,上面的代码是不正确的。应编写如下: unsigned int compzero = ~0; 这一问题真正能揭露出应试者是否懂得处理器字长的重要性。在我的经验里,好的嵌入式 程序员非常准确地明白硬件的细节和它的局限,然而 PC 机程序往往把硬件作为一个无 法避免的烦恼。 到了这个阶段,应试者或者完全垂头丧气了或者信心满满志在必得。如果显然应试者不是 很好,那么这个测试就在这里结束了。但如果显然应试者做得不错,那么我就扔出下面的 追加问题,这些问题是比较难的,我想仅仅非常优秀的应试者能做得不错。提出这些问题, …我希望更多看到应试者应付问题的方法,而不是答案。不管如何,你就当是这个娱乐吧 动态内存分配(Dynamic memory allocation) 51. 尽管不像非嵌入式计算机那么常见,嵌入式系统还是有从堆(heap)中动态 分配内存的过程的。那么嵌入式系统中,动态分配内存可能发生的问题是什么? 这里,我期望应试者能提到内存碎片,碎片收集的问题,变量的持行时间等等。这个主题 已经在 ESP 杂志中被广泛地讨论过了(主要是 P.J. Plauger, 他的解释远远超过我这里能 提到的任何解释),所有回过头看一下这些杂志吧!让应试者进入一种虚假的安全感觉 后,我拿出这么一个小节目:下面的代码片段的输出是什么,为什么? char *ptr; if ((ptr = (char *)malloc(0)) == NULL) puts("Got a null pointer"); else puts("Got a valid pointer"); 这是一个有趣的问题。最近在我的一个同事不经意把 0 值传给了函数 malloc,得到了一 “个合法的指针之后,我才想到这个问题。这就是上面的代码,该代码的输出是 Got a valid pointer”。我用这个来开始讨论这样的一问题,看看被面试者是否想到库例程这样 做是正确。得到正确的答案固然重要,但解决问题的方法和你做决定的基本原理更重要些。 Typedef 52. Typedef 在 C 语言中频繁用以声明一个已经存在的数据类型的同义字。也可 以用预处理器做类似的事。例如,思考一下下面的例子: #define dPS struct s * typedef struct s * tPS; 以上两种情况的意图都是要定义 dPS 和 tPS 作为一个指向结构 s 指针。哪种方法 更好呢? (如果有的话)为什么? 这是一个非常微妙的问题,任何人答对这个问题(正当的原因)是应当被恭喜的。答案是: typedef 更好。思考下面的例子: dPS p1,p2; tPS p3,p4; 第一个扩展为 struct s * p1, p2; 上面的代码定义 p1 为一个指向结构的指,p2 为一个实际的结构,这也许不是你想要的。 第二个例子正确地定义了 p3 和 p4 两个指针。 晦涩的语法 53. C 语言同意一些令人震惊的结构,下面的结构是合法的吗,如果是它做些什么? int a = 5, b = 7, c; c = a+++b; 这个问题将做为这个测验的一个愉快的结尾。不管你相不相信,上面的例子是完全合乎语 法的。问题是编译器如何处理它?水平不高的编译作者实际上会争论这个问题,根据最处 理原则,编译器应当能处理尽可能所有合法的用法。因此,上面的代码被处理成: c = a++ + b; 因此, 这段代码持行后 a = 6, b = 7, c = 12 。 如果你知道答案,或猜出正确答案,做得好。如果你不知道答案,我也不把这个当作问题。 我发现这个问题的最大好处是:这是一个关于代码编写风格,代码的可读性,代码的可修 改性的好的话题 What will print out? main() { char *p1=“name”; char *p2; p2=(char*)malloc(20); memset (p2, 0, 20); while(*p2++ = *p1++); printf(“%sn”,p2); } Answer:empty string. What will be printed as the result of the operation below: main() { int x=20,y=35; x=y++ + x++; y= ++y + ++x; printf(“%d%dn”,x,y); } Answer : 5794 What will be printed as the result of the operation below: main() { int x=5; printf(“%d,%d,%dn”,x,x< <2,x>>2); } Answer: 5,20,1 What will be printed as the result of the operation below: #define swap(a,b) a=a+b;b=a-b;a=a-b; void main() { int x=5, y=10; swap (x,y); printf(“%d %dn”,x,y); swap2(x,y); printf(“%d %dn”,x,y); } int swap2(int a, int b) { int temp; temp=a; b=a; a=temp; return 0; } Answer: 10, 5 10, 5 What will be printed as the result of the operation below: main() { char *ptr = ” Cisco Systems”; *ptr++; printf(“%sn”,ptr); ptr++; printf(“%sn”,ptr); } Answer:Cisco Systems isco systems What will be printed as the result of the operation below: main() { char s1[]=“Cisco”; char s2[]= “systems”; printf(“%s”,s1); } Answer: Cisco What will be printed as the result of the operation below: main() { char *p1; char *p2; p1=(char *)malloc(25); p2=(char *)malloc(25); strcpy(p1,”Cisco”); strcpy(p2,“systems”); strcat(p1,p2); printf(“%s”,p1); } Answer: Ciscosystems The following variable is available in file1.c, who can access it?: static int average; Answer: all the functions in the file1.c can access the variable. WHat will be the result of the following code? #define TRUE 0 // some code while(TRUE) { // some code } Answer: This will not go into the loop as TRUE is defined as 0. What will be printed as the result of the operation below: int x; int modifyvalue() { return(x+=10); } int changevalue(int x) { return(x+=1); } void main() { int x=10; x++; changevalue(x); x++; modifyvalue(); printf("First output:%dn",x); x++; changevalue(x); printf("Second output:%dn",x); modifyvalue(); printf("Third output:%dn",x); } Answer: 12 , 13 , 13 What will be printed as the result of the operation below: main() { int x=10, y=15; x = x++; y = ++y; printf(“%d %dn”,x,y); } Answer: 11, 16 What will be printed as the result of the operation below: main() { int a=0; if(a==0) printf(“Cisco Systemsn”); printf(“Cisco Systemsn”); } Answer: Two lines with “Cisco Systems” will be printed. /*****************************************************************************/ 1. 多态类中的虚函数表是 Compile-Time,还是 Run-Time 时建立的? 2. 将一个 1M -10M 的文件,逆序存储到另一个文件,就是前一个文件的最后一个 字符存到新文件的第一个字符,以此类推。 3.main 主函数执行完毕后,是否可能会再执行一段代码? 4.一个父类写了一个 virtual 函数,如果子类覆盖它的函数不加 virtual ,也能实现多态? 在子类的空间里,有没有父类的这个函数,或者父类的私有变量? 5. “给一个字符串、例如 ababc” “要求返回 ab”. “因为 ab”连续重复出现且最长。 用 C/C++语言写一函数完成该算法,给出复杂度 6.对序列 1、1、2、3、5、8、13 。。。。 是 Fab..数列 2、3、5、13...是 Fab..质数数列,因为他们与自己前面的 Fab...数列都互质 给出 k,返回第 k 小的 Fab..质数 7.101 个硬币 100 真、1 假,真假区别在于重量。请用无砝码天平称两次给出真币重还是 假币重的结论。 8. 完成字符串拷贝可以使用 sprintf、strcpy 及 memcpy 函数,请问这些函数有什么区 别,你喜欢使用哪个,为什么? 9.变量的声明和定义有什么区别? 10. 请写出下面代码在 32 位平台上的运行结果,并说明 sizeof 的性质: #include #include int main(void) { char a[30]; char *b = (char *)malloc(20 * sizeof(char)); printf("%d\n", sizeof(a)); printf("%d\n", sizeof(b)); printf("%d\n", sizeof(a[3])); printf("%d\n", sizeof(b+3)); printf("%d\n", sizeof(*(b+4))); return 0 ; } 12. 请完成以下题目。注意,请勿直接调用 ANSI C 函数库中的函数实现。 a) 请编写一个 C 函数,该函数给出一个字节中被置 1 的位的个数,并请 给出该题的至少一个不同解法。 b) 请编写一个 C 函数,该函数将给定的一个字符串转换成整数。 c) 请编写一个 C 函数,该函数将给定的一个整数转换成字符串。 d) 请编写一个 C 函数,该函数将一个字符串逆序。 e) 请编写一个 C 函数,该函数在给定的内存区域搜索给定的字符,并返回 该字符所在位置索引值。 f) 请编写一个 C 函数,该函数在一个字符串中找到可能的最长的子字符串, 该字符串是由同一字符组成的。 给出演示上述函数功能的一个简单程序,并请编写对应的 Makefile 文件 13.我们需要编写一个图形相关的应用程序,需要处理大量图形(Shape)信息, 图形有矩形(Rectangle),正方形(Square) ,圆形 (Circle)等种类,应用 需要计算这些图形的面积,并且可能需要在某个设备上进行显示(使用在标准 输出上打印信息的方式做为示意)。 a)请用面向对象的方法对以上应用进行设计,编写可能需要的类 b)请给出实现以上应用功能的示例性代码,从某处获取图形信息, 并且进行计算和绘制 c)如果你的 Square 继承自 Rectangle,请给出理由,如果不是, 请给出理由,并且请比较两种方式的优劣 d)请问你所编写的类,在如下代码中会有何表现,请解释 void test_rectangle_area(Rectangle& r) { r.set_width(10); r.set_height(15); assert(r.area() == 150); } 14.假设现有一个单向的链表,但是只知道只有一个指向该节点的指针 p,并且假设这个 节点不是尾节点,试编程实现删除此节点。 15.写一个程序,把一个 100 以内的自然数分解因数。(自然数分解因数就是将一个自然 数分解为几个素数的乘积,提示,由于该数不是很大,所以可以将质数保存在数组中,以加 快计算速度) 16.编写一个 Identify 的分配、释放的函数,为 1-10000 之间的自然数。 17.分别实现 itoa 和 atoi. 18.Consider the following code: #include #include int main(int argc, char *argv[]) { int i = 1; char buf[4]; strcpy(buf, "AAAA"); printf("%d\n", i); return 0; } a) When compiled and executed on x86, why does this program usually not output what the programmer intended? b) Name several ways in which the security problem that causes this program not to output what the programmer intended can be prevented WITHOUT changing the code. 19. 说出结果 int w=1,x=2,y=3,z=4; m=(w main() { FILE *fp; int i,a[4]={1,2,3,4},b; fp=fopen("data.dat","wb");//这里帮忙解释一下 for(i=0;i<4;i++) fwrite(&a[i],sizeof(int),1,fp);//这里也帮忙看一下 fclose(fp); fp=fopen("data.dat","rb"); fseek(fp,-2L*sizeof(int),SEEK_END);//还有这里 fread(&b,sizeof(int),1,fp);//这里还有也看一下 fclose(fp); printf("b=%d\n",b); } 21.有双向循环链表结点: typedef struct node { int date; struct node *front,*next; }_Node; 有两个双向循环链表 A,B,知道其头指针为:pHeadA,pHeadB,请写一函数将两上链 表中 date 值相同的结点删除 22. char * GetStr() { char *tmp; tmp = "123" return tmp; } void main() { printf("%s", GetStr()); } 会输出 123 吗?123 创建在堆上还是栈上呢?123 的空间是什么时候释放的? 23.字符指针、浮点数指针、以及函数指针这三种类型的变量哪个占用的内存最大?为什么? 类 ClassB 从 ClassA 派生,那么 ClassA *a = new ClassB(…); 试问该表达是否合法? 为什么? 如果 ClassA 中定义并实现虚函数 int func(void),ClassB 中也实现该函数,那么上述 变量 a->func()将调用哪个类里面的函数?如果 int func(void)不是虚函数,情况又如何? 为什么? char **p, a[16][8]; 问:p=a 是否会导致程序在以后出现问题?为什么? 如下所述的 if else 和 switch 语句哪个的效率高?为什么? 在同一个进程中,一个模块是否可以通过指针操作破坏其它模块的内存,为什么? 应用程序在运行时的内存包括代码区和数据区,其中数据区又包括哪些部分? 24.Assignment 2: Picture Processing Use C++, Java, or similar languages or/and any middleware such as EJB and J2EE to process a picture with a high resolution (3 Mega Pixels for example). Use some methodologies to degrade the resolution of the picture to make it quicker for browsing. Then divide the degraded picture into 9 sectors equally. Click any of the 9 sectors will result a detailed picture for this sector with the same resolution as that of the original picture. This assignment is designed for you to demonstrate your ability to handle pictures. 25.用<<, >>, |, &实现一个 WORD(2 个字节)的高低位交换!! 26.要开辟 P1,P2,P3,P4 内存来做缓冲,大小自定,但这四个缓冲的大小要一样,并且是连 续的! 27.有一浮点型数组 A,用 C 语言写一函数实现对浮点数组 A 进行降序排序,并输出结果,要 求要以数组 A 作为函数的入口.(建议用冒泡排序法) 28.找错: #include #include class Base { private: char * name; public: Base(char * className) { name = new char[strlen(className)]; strcpy(name, className); } ~Base() { delete name; } char * copyName() { char newname [256]; strcpy(newname, name); return newname; } char * getName() { return name; } static void print(Base base) { printf("name: %s\n" , base.name); } }; class Subclass : public Base { public: Subclass(char * className) : Base(className) { } }; int main() { Base * pBase = new Subclass("test"); Base::print(*pBase); printf("name: %s\n", pBase->getName()); printf("new name: %s\n", pBase->copyName()); return 0; } 29.编写一个函数,函数接收一个字符串,是由十六进制数组成的一组字符串,函数的功能 是把接到的这组字符串转换成十进制数字.并将十进制数字返回. 30.编写一个函数将一条字符串分成两部分,将前半部分按 ASCII 码升序排序,后半部分 不变,(如果字符串是奇数则中间的字符不变,)最后再将前后两部分交换,然后将该 “字符串输出,测试字符串 ADZDDJKJFIEJHGI” 31.找错 Void test1() { char string[10]; char* str1="0123456789"; strcpy(string, str1); } Void test2() { char string[10], str1[10]; for(I=0; I<10;I++) { str1[i] ='a'; } strcpy(string, str1); } Void test3(char* str1) { char string[10]; if(strlen(str1)<=10) { strcpy(string, str1); } } 32. 找错 #define MAX_SRM 256 DSN get_SRM_no() { static int SRM_no; int I; for(I=0;I{ SRM_no %= MAX_SRM; if(MY_SRM.state==IDLE) { break; } } if(I>=MAX_SRM) return (NULL_SRM); else return SRM_no; } 33. 写出程序运行结果 int sum(int a) { auto int c=0; static int b=3; c+=1; b+=2; return(a+b+C); } void main() { int I; int a=2; for(I=0;I<5;I++) { printf("%d,", sum(a)); } } 34. int func(int a) { int b; switch(a) { case 1: 30; case 2: 20; case 3: 16; default: 0 } return b; } 则 func(1)=? 35: int a[3]; a[0]=0; a[1]=1; a[2]=2; int *p, *q; p=a; q=&a[2]; 则 a[q-p]=? 36. 定义 int **a[3][4], 则变量占有的内存空间为:_____ 37.编写一个函数,要求输入年月日时分秒,输出该年月日时分秒的下一秒。如输入 2004 年 12 月 31 日 23 时 59 分 59 秒,则输出 2005 年 1 月 1 日 0 时 0 分 0 秒。 38.写一个函数,判断一个 int 型的整数是否是 2 的幂,即是否可以表示成 2^X 的形式 (不可以用循环) 我只知道是用递推,大概写了一下,如下: int IsTwoPow(int s) { if(s==1)return FALSE; s=s>>1; if(s>1)IsTwoPow(s); return (s==1)?TRUE:FALSE;//大概是这个意思,但是这一句似乎不该这么返回! } 39 A,B 从一堆玻璃球(共 100 个)里向外拿球,规则如下: (1)A 先拿,然后一人一次交替着拿; (2)每次只能拿 1 个或 2 个或 4 个; (3)谁拿最后一个球,谁就是最后的失败者; 问 A,B 谁将是失败者?写出你的判断步骤。 40.已知:无序数组,折半查找,各元素值唯一。 函数原型是:Binary_Seach(int array[], int iValue, int iCount) array 是 数 组 , 在 里 面 用 折 半 查 找 的 方 法 找 等 于 iValue 的 值 , 找 到 返 回 1 否 则 0,iCount 是元素个数 41.统计一个字符串中字符出现的次数 42.100 位以上的超大整数的加法(主要考虑数据结构和加法的实现)。 43.对如下电文:"CASTCASTSATATATASA"给出 Huffman 编码。 44.int (* (*f)(int, int))(int)表示什么含义? 45.x=x+1,x+=1,x++,为这三个语句的效率排序。并说明为什么。 46. 中缀表达式 A-(B+C/D)*E 的后缀形式是什么? 47.struct S1 { char c; int i; }; sizeof(S1) = ? class X{ public: X(); virtual ~X(); void myMemberFunc(); static void myStaticFunc(); virtual void myVirtualFunc(); private: int i; char * pstr; char a; } sizeof(X) = ? 48.找出两个字符串中最大子字符串 ,如"abractyeyt","dgdsaeactyey"的最大子串 为"actyet" 49.有一百个整数,其中有负数,找出连续三个数之和最大的部分. 50.写一程序实现快速排序. 假设数据输入为一文件 快速算法描述如下 Algorithm Partition Input: sequence a0, ..., an-1 with n elements Output: permutation of the sequence such that all elements a0, ..., aj are less than or equal to all elements ai, ..., an-1 (i > j) Method: choose the element in the middle of the sequence as comparison element x let i = 0 and j = n-1 while ij search the first element ai which is greater than or equal to x search the last element aj which is less than or equal to x if ij exchange ai and aj let i = i+1 and j = j-1 After partitioning the sequence, Quicksort treats the two parts recursively by the same procedure. The recursion ends whenever a part consists of one element only. 51.写一算法检测单向链表中是否存在环(whether there is a loop in a link list), 要求算法复杂度(Algorithm's complexity 是 O(n)) 并只使用常数空间(space is O(c)). 注意,你只知道一个指向单向链表头的指针。链表的长度是不定的,而且环出现的地方也 是不定的,环有可能在头,有可能在中间。而且要求是检测, 不能破坏环的结构. 52.设下列函数已经通过了调试 bool Sort_Array(ArrayType * Pinputarray, ArrayType * Poutarray); 该函数在内存中排序,能把字节数最大为 100M 字节的 ArrayType 类型的数组排序。其 中 ArrayType 是一个 预定义的数组类型(细节无关紧要),Pinputarray,Poutarray 分别为排序前的指针和 排序后的指针。 请用 c 语言的伪码风格设计一个算法,他调用上面给出的函数完成下列从输入到输出的 任务: 输入:排序前的大文件,名称为 char * pinoutfilename ,其内容为用分号分隔的 ArrayType 类型的数组元素,可装满 4 个 100M 字节的数组。 输出:排序后的大文件 char * poutoutfilename。 53.用最有效率的方法算出 2 乘以 8 等於几? 54. 1. 错误的转义字符是 ( c ) A.'\091' B.'\\' C.'\0' D.'\'' 2. 若数组名作实参而指针变量作形参,函数调用实参传给形参的是 ( d ) A. 数组的长度 B.数组第一个元素的值 C. 数组所有元素的值 D.数组第一个元素的地址 3. 变量的指针含意是指变量的 ( b ) A. 值 B.地址 C. 存储 D.名字 5.某文件中定义的静态全局变量(或称静态外部变量) 其作用域是 ( d ) A. 只限某个函数 B.本文件 C. 跨文件 D.不限制作用域 55. 1. 解二次方程:a*x*x+b*x+c int Quadratic( double a,double b,double c,double& x1,double& x2); 返回值:解的个数 2. 最大公约数 DWORD Divisor( DWORD dwFirst, DWORD dwSecond ); 返回值:最大公约数 3. 根据蒙特卡洛算法计算圆周率 double PI( DOWRD dwCount/*测试次数*/ ); 返回值:PI 4. 无符号整数乘法,乘数为 32bit,结果为 64bit 提示:32bit 整数分解为 16bit 相乘 void Multiply( DWORD dwFirst, DWORD dwSecond, DWORD& dwHigh, DWORD& dwLower ); 5. 链表排序(从小到大) 节点定义为: struct Node{ int nValue; struct Node* pNext; }; 最后一个节点的 pNext = NULL. Node* SortChain( Node* pHead ); 返回值:链表头 =============================================== =============================================== =============================================== ================= 有关链表的难点: ①链表反转 单向链表的反转是一个经常被问到的一个面试题,也是一个非常基础的问题。比 如一个链表是这样的: 1->2->3->4->5 通过反转后成为5->4->3->2->1。 最容易想到的方法遍历一遍链表,利用一个辅助指针,存储遍历过程中当前指针 指向的下一个元素,然后将当前节点元素的指针反转后,利用已经存储的指针往 后面继续遍历。源代码如下: 1. struct linka { 2. int data; 3. linka* next; 4. }; 5. void reverse(linka*& head) { 6. if(head ==NULL) 7. return; 8. linka *pre, *cur, *ne; 9. pre=head; 10.cur=head->next; 11.while(cur) 12.{ 13. ne = cur->next; 14. cur->next = pre; 15. pre = cur; 16. cur = ne; 17.} 18.head->next = NULL; 19.head = pre; 20.} 还有一种利用递归的方法。这种方法的基本思想是在反转当前节点之前先调用递 归函数反转后续节点。源代码如下。不过这个方法有一个缺点,就是在反转后 的最后一个结点会形成一个环,所以必须将函数的返回的节点的next域置为 NULL。因为要改变head指针,所以我用了引用。算法的源代码如下: 1. linka* reverse(linka* p,linka*& head) 2. { 3. if(p == NULL || p->next == NULL) 4. { 5. head=p; 6. return p; 7. } 8. else 9. { 10. linka* tmp = reverse(p->next,head); 11. tmp->next = p; 12. return p; 13.} 14.} ②已知String类定义如下: class String { public: String(const char *str = NULL); // 通用构造函数 String(const String &another); // 拷贝构造函数 ~ String(); // 析构函数 String & operater =(const String &rhs); // 赋值函数 private: char *m_data; // 用于保存字符串 }; 尝试写出类的成员函数实现。 答案: String::String(const char *str) { if ( str == NULL ) //strlen在参数为NULL 时会抛异常才会有这步判断 { m_data = new char[1] ; m_data[0] = '\0' ; } else { m_data = new char[strlen(str) + 1]; strcpy(m_data,str); } } String::String(const String &another) { m_data = new char[strlen(another.m_data) + 1]; strcpy(m_data,another.m_data); } String& String::operator =(const String &rhs) { if ( this == &rhs) return *this ; delete []m_data; //删除原来的数据,新开一块内存 m_data = new char[strlen(rhs.m_data) + 1]; strcpy(m_data,rhs.m_data); return *this ; } String::~String() { delete []m_data ; } ③网上流传的c++笔试题汇总 1.求下面函数的返回值(微软) int func(x) { int countx = 0; while(x) { countx ++; x = x&(x-1); } return countx; } 假定x = 9999。答案:8 思路:将x转化为2进制,看含有的1的个数。 2. “”“”什么是 引用 ?申明和使用 引用 要注意哪些问题? “”答:引用就是某个目标变量的 别名 (alias),对应用的操作与对变量直接操 作效果完全相同。申明一个引用的时候,切记要对其进行初始化。引用声明完毕 后,相当于目标变量名有两个名称,即该目标原名称和引用名,不能再把该引用 名作为其他变量名的别名。声明一个引用,不是新定义了一个变量,它只表示该 引用名是目标变量名的一个别名,它本身不是一种数据类型,因此引用本身不 占存储单元,系统也不给引用分配存储单元。不能建立数组的引用。 3. “”将 引用 作为函数参数有哪些特点? (1)传递引用给函数与传递指针的效果是一样的。这时,被调函数的形参就成 为原来主调函数中的实参变量或对象的一个别名来使用,所以在被调函数中对形 参变量的操作就是对其相应的目标对象(在主调函数中)的操作。 (2)使用引用传递函数的参数,在内存中并没有产生实参的副本,它是直接对 实参操作;而使用一般变量传递函数的参数,当发生函数调用时,需要给形参分 配存储单元,形参变量是实参变量的副本;如果传递的是对象,还将调用拷贝 构造函数。因此,当参数传递的数据较大时,用引用比用一般变量传递参数的效 率和所占空间都好。 (3)使用指针作为函数的参数虽然也能达到与使用引用的效果,但是,在被调 函数中同样要给形参分配存储单元,且需要重复使用"*指针变量名"的形式进行 运算,这很容易产生错误且程序的阅读性较差;另一方面,在主调函数的调用 点处,必须用变量的地址作为实参。而引用更容易使用,更清晰。 4. “”在什么时候需要使用 常引用 ? 如果既要利用引用提高程序的效率,又要保护传递给函数的数据不在函数中被改 变,就应使用常引用。常引用声明方式:const 类型标识符 &引用名=目标变量 名; 例1 int a ; const int &ra=a; ra=1; //错误 a=1; //正确 例2 string foo( ); void bar(string & s); 那么下面的表达式将是非法的: bar(foo( )); bar("hello world"); 原因在于foo( )和"hello world"串都会产生一个临时对象,而在C++中,这些 临时对象都是const类型的。因此上面的表达式就是试图将一个const类型的对 象转换为非const类型,这是非法的。 引用型参数应该在能被定义为const的情况下,尽量定义为const 。 5. “”将 引用 作为函数返回值类型的格式、好处和需要遵守的规则? 格式:类型标识符 &函数名(形参列表及类型说明){ // 函数体 } 好处:在内存中不产生被返回值的副本;(注意:正是因为这点原因,所以返回 一个局部变量的引用是不可取的。因为随着该局部变量生存期的结束,相应的引 用也会失效,产生runtime error! 注意事项: (1)不能返回局部变量的引用。这条可以参照Effective C++[1]的Item 31。 主要原因是局部变量会在函数返回后被销毁,因此被返回的引用就成为了"无所 指"的引用,程序会进入未知状态。 (2)不能返回函数内部new分配的内存的引用。这条可以参照Effective C++[1] 的Item 31。虽然不存在局部变量的被动销毁问题,可对于这种情况(返回函数 内部new分配内存的引用),又面临其它尴尬局面。例如,被函数返回的引用只 是作为一个临时变量出现,而没有被赋予一个实际的变量,那么这个引用所指 向的空间(由new分配)就无法释放,造成memory leak。 (3)可以返回类成员的引用,但最好是const。这条原则可以参照Effective C++[1]的Item 30。主要原因是当对象的属性是与某种业务规则(business rule) 相关联的时候,其赋值常常与某些其它属性或者对象的状态有关,因此有必要将 赋值操作封装在一个业务规则当中。如果其它对象可以获得该属性的非常量引 用(或指针),那么对该属性的单纯赋值就会破坏业务规则的完整性。 (4 “”)流操作符重载返回值申明为 引用 的作用: 流操作符<<和>>,这两个操作符常常希望被连续使用,例如:cout << "hello" << endl; 因此这两个操作符的返回值应该是一个仍然支持这两个操作符的流引 用。可选的其它方案包括:返回一个流对象和返回一个流对象指针。但是对于返 回一个流对象,程序必须重新(拷贝)构造一个新的流对象,也就是说,连续 的两个<<操作符实际上是针对不同对象的!这无法让人接受。对于返回一个流 指针则不能连续使用<<操作符。因此,返回一个流对象引用是惟一选择。这个唯 一选择很关键,它说明了引用的重要性以______________及无可替代性,也许这就是C+ +语言 中引入引用这个概念的原因吧。赋值操作符=。这个操作符象流操作符一样,是 可以连续使用的,例如:x = j = 10;或者(x=10)=100;赋值操作符的返回值必须 是一个左值,以便可以被继续赋值。因此引用成了这个操作符的惟一返回值选择。 例3 #i nclude int &put(int n); int vals[10]; int error=-1; void main() { put(0)=10; //以put(0)函数值作为左值,等价于vals[0]=10; put(9)=20; //以put(9)函数值作为左值,等价于vals[9]=20; cout=0 && n<=9 ) return vals[n]; else { cout<<"subscript error"; return error; } } (5)在另外的一些操作符中,却千万不能返回引用:+-*/ 四则运算符。它们不 能返回引用,Effective C++[1]的Item23详细的讨论了这个问题。主要原因是 这四个操作符没有side effect,因此,它们必须构造一个对象作为返回值,可 选的方案包括:返回一个对象、返回一个局部变量的引用,返回一个new分配的 对象的引用、返回一个静态对象引用。根据前面提到的引用作为返回值的三个 规则,第2、3两个方案都被否决了。静态对象的引用又因为((a+b) == (c+d)) 会永远为true而导致错误。所以可选的只剩下返回一个对象了。 6. “ ”引用 与多态的关系? 引用是除指针外另一个可以产生多态效果的手段。这意味着,一个基类的引用可 以指向它的派生类实例。 例4 Class A; Class B : Class A{...}; B b; A& ref = b; 7. “ ”引用 与指针的区别是什么? 指针通过某个指针变量指向一个对象后,对它所指向的变量间接操作。程序中使 用指针,程序的可读性差;而引用本身就是目标变量的别名,对引用的操作就是 对目标变量的操作。此外,就是上面提到的对函数传ref和pointer的区别。 8. “”什么时候需要 引用 ? 流操作符<<和>>、赋值操作符=的返回值、拷贝构造函数的参数、赋值操作符= 的参数、其它情况都推荐使用引用。 以上 2-8 参考: http://blog.csdn.net/wfwd/archive/2006/05/30/763551.aspx 9. 结构与联合有和区别? 1. 结构和联合都是由多个不同的数据类型成员组成, 但在任何同一时刻, 联合 中只存放了一个被选中的成员(所有成员共用一块地址空间), 而结构的所有成 员都存在(不同成员的存放地址不同)。 2. 对于联合的不同成员赋值, 将会对其它成员重写, 原来成员的值就不存在了, 而对于结构的不同成员赋值是互不影响的。 10. “”下面关于 联合 的题目的输出? a) #i nclude union { int i; char x[2]; }a; void main() { a.x[0] = 10; a.x[1] = 1; printf("%d",a.i); } 答案:266 (低位低地址,高位高地址,内存占用情况是Ox010A) b) main() { union{ /*定义一个联合*/ int i; struct{ /*在联合中定义一个结构*/ char first; char second; }half; }number; number.i=0x4241; /*联合成员赋值*/ printf("%c%c\n", number.half.first, mumber.half.second); number.half.first='a'; /*联合中结构成员赋值*/ number.half.second='b'; printf("%x\n", number.i); getch(); } 答案: AB (0x41对应'A',是低位;Ox42对应'B',是高位) 6261 (number.i和number.half共用一块地址空间) 11. 已知strcpy 的函数原型:char *strcpy(char *strDest, const char *strSrc)其中strDest 是目的字符串,strSrc 是源字符串。不 调用C++/C 的字符串库函数,请编写函数 strcpy。 答案: char *strcpy(char *strDest, const char *strSrc) { if ( strDest == NULL || strSrc == NULL) return NULL ; if ( strDest == strSrc) return strDest ; char *tempptr = strDest ; while( (*strDest++ = *strSrc++) != ‘\0’) return tempptr ; } 12. 已知String 类定义如下: class String { public: String(const char *str = NULL); // 通用构造函数 String(const String &another); // 拷贝构造函数 ~ String(); // 析构函数 String & operater =(const String &rhs); // 赋值函数 private: char *m_data; // 用于保存字符串 }; 尝试写出类的成员函数实现。 答案: String::String(const char *str) { if ( str == NULL ) //strlen在参数为NULL 时会抛异常才会有这步判断 { m_data = new char[1] ; m_data[0] = '\0' ; } else { m_data = new char[strlen(str) + 1]; strcpy(m_data,str); } } String::String(const String &another) { m_data = new char[strlen(another.m_data) + 1]; strcpy(m_data,other.m_data); } String& String::operator =(const String &rhs) { if ( this == &rhs) return *this ; delete []m_data; //删除原来的数据,新开一块内存 m_data = new char[strlen(rhs.m_data) + 1]; strcpy(m_data,rhs.m_data); return *this ; } String::~String() { delete []m_data ; } 13. .h 头文件中的ifndef/define/endif 的作用? 答:防止该头文件被重复引用。 14. #i nclude 与#i nclude "file.h"的区别? 答:前者是从Standard Library的路径寻找和引用file.h,而后者是从当前工 作路径搜寻并引用file.h。 15.在C++ 程序中调用被C 编译器编译后的函数,为什么要加extern “C”? 首先,作为extern是C/C++语言中表明函数和全局变量作用范围(可见性)的 关键字,该关键字告诉编译器,其声明的函数和变量可以在本模块或其它模块中 使用。 通常,在模块的头文件中对本模块提供给其它模块引用的函数和全局变量以关键 字extern声明。例如,如果模块B 欲引用该模块A 中定义的全局变量和函数时只 需包含模块A的头文件即可。这样,模块B中调用模块A中的函数时,在编译阶 段,模块B虽然找不到该函数,但是并不会报错;它会在连接阶段中从模块A 编译生成的目标代码中找到此函数 extern "C"是连接申明(linkage declaration),被extern "C"修饰的变量和函 数是按照C语言方式编译和连接的,来看看C++中对类似C的函数是怎样编译的: 作为一种面向对象的语言,C++支持函数重载,而过程式语言C则不支持。函数 被C++编译后在符号库中的名字与C语言的不同。例如,假设某个函数的原型为: void foo( int x, int y ); 该函数被C编译器编译后在符号库中的名字为_foo,而C++编译器则会产生像 _foo_int_int之类的名字(不同的编译器可能生成的名字不同,但是都采用了 “相同的机制,生成的新名字称为 mangled name”)。 _foo_int_int 这样的名字包含了函数名、函数参数数量及类型信息,C++就是靠 这种机制来实现函数重载的。例如,在C++中,函数void foo( int x, int y ) 与void foo( int x, float y )编译生成的符号是不相同的,后者为 _foo_int_float。 同样地,C++中的变量除支持局部变量外,还支持类成员变量和全局变量。用户 所编写程序的类成员变量可能与全局变量同名,我们以"."来区分。而本质上,编 译器在进行编译时,与函数的处理相似,也为类中的变量取了一个独一无二的名 字,这个名字与用户程序中同名的全局变量名字不同。 未加extern "C"声明时的连接方式 假设在C++中,模块A的头文件如下: // 模块A头文件moduleA.h #ifndef MODULE_A_H #define MODULE_A_H int foo( int x, int y ); #endif 在模块B中引用该函数: // 模块B实现文件moduleB.cpp #i nclude "moduleA.h" foo(2,3); 实际上,在连接阶段,连接器会从模块A生成的目标文件moduleA.obj中寻找 _foo_int_int这样的符号! 加extern "C"声明后的编译和连接方式 加extern "C"声明后,模块A的头文件变为: // 模块A头文件moduleA.h #ifndef MODULE_A_H #define MODULE_A_H extern "C" int foo( int x, int y ); #endif 在模块B的实现文件中仍然调用foo( 2,3 ),其结果是: (1)模块2ė_______A编译生成foo的目标代码时,没有对其名字进行特殊处理,采用了 C语言的方式; (2)连接器在为模块B的目标代码寻找foo(2,3)调用时,寻找的是未经修改的 符号名_foo。 如果在模块A中函数声明了foo为extern "C"类型,而模块B中包含的是extern int foo( int x, int y ) ,则模块B找不到模块A中的函数;反之亦然。 所以,可以用一句话概括extern “C”这个声明的真实目的(任何语言中的任 何语法特性的诞生都不是随意而为的,来源于真实世界的需求驱动。我们在思考 问题时,不能只停留在这个语言是怎么做的,还要问一问它为什么要这么做, 动机是什么,这样我们可以更深入地理解许多问题):实现C++与C及其它语言 的混合编程。 明白了C++中extern "C"的设立动机,我们下面来具体分析extern "C"通常的 使用技巧: extern "C"的惯用法 (1)在C++中引用C语言中的函数和变量,在包含C语言头文件(假设为 cExample.h)时,需进行下列处理: extern "C" { #i nclude "cExample.h" } 而在C语言的头文件中,对其外部函数只能指定为extern类型,C语言中不支 持extern "C"声明,在.c文件中包含了extern "C"时会出现编译语法错误。 C++引用C函数例子工程中包含的三个文件的源代码如下: /* c语言头文件:cExample.h */ #ifndef C_EXAMPLE_H #define C_EXAMPLE_H extern int add(int x,int y); #endif /* c语言实现文件:cExample.c */ #i nclude "cExample.h" int add( int x, int y ) { return x + y; } // c++实现文件,调用add:cppFile.cpp extern "C" { #i nclude "cExample.h" } int main(int argc, char* argv[]) { add(2,3); return 0; } 如果C++调用一个C语言编写的.DLL时,当包括.DLL的头文件或声明接口函数 时,应加extern "C" { }。 (2)在C中引用C++语言中的函数和变量时,C++的头文件需添加extern "C", 但是在C语言中不能直接引用声明了extern "C"的该头文件,应该仅将C文件 中将C++中定义的extern "C"函数声明为extern类型。 C引用C++函数例子工程中包含的三个文件的源代码如下: //C++ 头文件 cppExample.h #ifndef CPP_EXAMPLE_H #define CPP_EXAMPLE_H extern "C" int add( int x, int y ); #endif //C++ 实现文件 cppExample.cpp #i nclude "cppExample.h" int add( int x, int y ) { return x + y; } /* C 实现文件 cFile.c /* 这样会编译出错:#i nclude "cExample.h" */ extern int add( int x, int y ); int main( int argc, char* argv[] ) { add( 2, 3 ); return 0; } 15 题目的解答请参考《C++中extern “C”含义深层探索》注解: 16. 关联、聚合(Aggregation)以及组合(Composition)的区别? 涉及到UML“”中的一些概念:关联是表示两个类的一般性联系,比如 学生 和 “”老师 就是一种关联关系;聚合表示has-a 的关系,是一种相对松散的关系, 聚合类不需要对被聚合类负责,如下图所示,用空的菱形表示聚合关系: 从实现的角度讲,聚合可以表示为: class A {...} class B { A* a; .....} 而组合表示contains-a的关系,关联性强于聚合:组合类与被组合类有相同的 生命周期,组合类要对被组合类负责,采用实心的菱形表示组合关系: 实现的形式是: class A{...} class B{ A a; ...} 参考文章:http://blog.csdn.net/wfwd/archive/2006/05/30/763753.aspx http://blog.csdn.net/wfwd/archive/2006/05/30/763760.aspx 17.面向对象的三个基本特征,并简单叙述之? 1. 封装:将客观事物抽象成类,每个类对自身的数据和方法实行 protection(private, protected,public) 2. 继承:广义的继承有三种实现形式:实现继承(指使用基类的属性和方法而 无需额外编码的能力)、可视继承(子窗体使用父窗体的外观和实现代码)、接 口继承(仅使用属性和方法,实现滞后到子类实现)。前两种(类继承)和后 一种(对象组合=>接口继承以及纯虚函数)构成了功能复用的两种方式。 3. 多态:是将父对象设置成为和一个或更多的他的子对象相等的技术,赋值之 后,父对象就可以根据当前赋值给它的子对象的特性以不同的方式运作。简单的 说,就是一句话:允许将子类类型的指针赋值给父类类型的指针。 18. 重载(overload)和重写(overried “”,有的书也叫做 覆盖 )的区 别? 常考的题目。从定义上来说: 重载:是指允许存在多个同名函数,而这些函数的参数表不同(或许参数个数不 同,或许参数类型不同,或许两者都不同)。 重写:是指子______________类重新定义复类虚函数的方法。 从实现原理上来说: 重载:编译器根据函数不同的参数表,对同名函数的名称做修饰,然后这些同名 函数就成了不同的函数(至少对于编译器来说是这样的)。如,有两个同名函数: function func(p:integer):integer;和function func(p:string):integer;。 那么编译器做过修饰后的函数名称可能是这样的:int_func、str_func。对于这 两个函数的调用,在编译器间就已经确定了,是静态的。也就是说,它们的地 址在编译期就绑定了(早绑定),因此,重载和多态无关! 重写:和多态真正相关。当子类重新定义了父类的虚函数后,父类指针根据赋给 它的不同的子类指针,动态的调用属于子类的该函数,这样的函数调用在编译期 间是无法确定的(调用的子类的虚函数的地址无法给出)。因此,这样的函数地 址是在运行期绑定的(晚绑定)。 19. 多态的作用? 主要是两个:1. 隐藏实现细节,使得代码能够模块化;扩展代码模块,实现代 码重用;2. 接口重用:为了类在继承和派生的时候,保证使用家族中任一类的 实例的某一属性时的正确调用。 20. Ado 与Ado.net的相同与不同? 除了“能够让应用程序处理存储于DBMS 中的数据“这一基本相似点外,两者没有太多共 同 之处。但是Ado 使用OLE DB 接口并基于微软的COM 技术,而ADO.NET 拥有自己的 ADO.NET 接口并且基于微软的.NET 体系架构。众所周知.NET 体系不同于COM 体系, ADO.NET 接口也就完全不同于ADO 和OLE DB 接口,这也就是说ADO.NET 和ADO 是 两种数据访问方式。ADO.net 提供对XML 的支持。 21. New delete 与malloc free 的联系与区别? 答案:都是在堆(heap)上进行动态的内存操作。用malloc函数需要指定内存分配的字节 数并 且不能初始化对象,new 会自动调用对象的构造函数。delete 会调用对象的 destructor,而 free 不会调用对象的destructor. 22. #define DOUBLE(x) x+x ,i = 5*DOUBLE(5) ; i 是多少? 答案:i 为30。 23. 有哪几种情况只能用intialization list 而不能用assignment? 答案:当类中含有const、reference 成员变量;基类的构造函数都需要初始化表。 24. C++是不是类型安全的? 答案:不是。两个不同类型的指针之间可以强制转换(用reinterpret cast)。C#是类型安 全的。 25. main 函数执行以前,还会执行什么代码? 答案:全局对象的构造函数会在main 函数之前执行。 26. 描述内存分配方式以及它们的区别? 1 ) 从静态存储区域分配。内存在程序编译的时候就已经分配好,这块内存在程序的整个 运 行期间都存在。例如全局变量,static 变量。 2 ) 在栈上创建。在执行函数时,函数内局部变量的存储单元都可以在栈上创建,函数执 行 结束时这些存储单元自动被释放。栈内存分配运算内置于处理器的指令集。 3 ) 从堆上分配,亦称动态内存分配。程序在运行的时候用malloc 或new 申请任意多少 的 内存,程序员自己负责在何时用free 或delete 释放内存。动态内存的生存期由程序员决 定, 使用非常灵活,但问题也最多。 27.struct 和class 的区别 答案:struct 的成员默认是公有的,而类的成员默认是私有的。struct 和class 在其他方 面 是功能相当的。 从感情上讲,大多数的开发者感到类和结构有很大的差别。感觉上结构仅仅象一堆缺乏封 装 和功能的开放的内存位,而类就象活的并且可靠的社会成员,它有智能服务,有牢固的 封 装屏障和一个良好定义的接口。既然大多数人都这么认为,那么只有在你的类有很少的方 法 并且有公有数据(这种事情在良好设计的系统中是存在的!)时,你也许应该使用struct 关 键字,否则,你应该使用class 关键字。 28.当一个类A 中没有生命任何成员变量与成员函数,这时sizeof(A)的值是多少,如 果不是 零,请解释一下编译器为什么没有让它为零。(Autodesk) 答案:肯定不是零。举个反例,如果是零的话,声明一个class A[10]对象数组,而每一 个对 象占用的空间是零,这时就没办法区分A[0],A[1]…了。 29. 在8086 汇编下,逻辑地址和物理地址是怎样转换的?(Intel) 答案:通用寄存器给出的地址,是段内偏移地址,相应段寄存器地址*10H+通用寄存器 内地 址,就得到了真正要访问的地址。 30. 比较C++中的4种类型转换方式? 请 参考: http://blog.csdn.net/wfwd/archive/2006/05/30/763785.aspx , 重点是 static_cast, dynamic_cast和reinterpret_cast的区别和应用。 31.分别写出BOOL,int,float,指针类型的变量a 与“零”的比较语句。 答案: BOOL : if ( !a ) or if(a) int : if ( a == 0) float : const EXPRESSION EXP = 0.000001 if ( a < EXP && a >-EXP) pointer : if ( a != NULL) or if(a == NULL) 32.请说出const与#define 相比,有何优点? 答案:1 ) const 常量有数据类型,而宏常量没有数据类型。编译器可以对前者进行类型 安 全检查。而对后者只进行字符替换,没有类型安全检查,并且在字符替换可能会产生意料 不 到的错误。 2 ) 有些集成化的调试工具可以对const 常量进行调试,但是不能对宏常量进行调试。 33.简述数组与指针的区别? 数组要么在静态存储区被创建(如全局数组),要么在栈上被创建。指针可以随时指向任 意 类型的内存块。 (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 字节 } 34.类成员函数的重载、覆盖和隐藏区别? 答案: a.成员函数被重载的特征: (1)相同的范围(在同一个类中); (2)函数名字相同; (3)参数不同; (4)virtual 关键字可有可无。 b.覆盖是指派生类函数覆盖基类函数,特征是: (1)不同的范围(分别位于派生类与基类); (2)函数名字相同; (3)参数相同; (4)基类函数必须有virtual 关键字。 c.“隐藏”是指派生类的函数屏蔽了与其同名的基类函数,规则如下: (1)如果派生类的函数与基类的函数同名,但是参数不同。此时,不论有无virtual关键 字, 基类的函数将被隐藏(注意别与重载混淆)。 (2)如果派生类的函数与基类的函数同名,并且参数也相同,但是基类函数没有 virtual 关 键字。此时,基类的函数被隐藏(注意别与覆盖混淆) 35. 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 36. 如何打印出当前源文件的文件名以及源文件的当前行号? 答案: cout << __FILE__ ; cout<<__LINE__ ; __FILE__和__LINE__是系统预定义宏,这种宏并不是在某个文件中定义的,而是由编译 器 定义的。 37. 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. 38. 如何判断一段程序是由C 编译程序还是由C++编译程序编译的? 答案: #ifdef __cplusplus cout<<"c++"; #else cout<<"c"; #endif 39.文件中有一组整数,要求排序后输出到另一个文件中 答案: #i nclude #i nclude using namespace std; void Order(vector& data) //bubble sort { 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 ) { vectordata; 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<40. 链表题:一个链表的结点结构 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 ; } (3)已知两个链表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 ; } 41. 分析一下这段程序的输出(Autodesk) class B { public: B() { cout<<"default constructor"<} ~B() { cout<<"destructed"<} B(int i):data(i) //B(int) works as a converter ( int -> instance of B) { cout<<"constructed by parameter " << data <} private: int data; }; B Play( B b) { return b ; } (1) results: int main(int argc, char* argv[]) constructed by parameter 5 { destructed B(5)形参析构 B t1 = Play(5); B t2 = Play(t1); destructed t1 形参析构 return 0; destructed t2 注意顺序! } destructed t1 (2) results: int main(int argc, char* argv[]) constructed by parameter 5 { destructed B(5)形参析构 B t1 = Play(5); B t2 = Play(10); constructed by parameter 10 return 0; destructed B(10)形参析构 } destructed t2 注意顺序! destructed t1 42. 写一个函数找出一个整数数组中,第二大的数(microsoft) 答案: const int MINNUMBER = -32767 ; int find_sec_max( int data[] , int count) { 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 ; } 43. 写一个在一个字符串(n)中寻找一个子串(m)第一个位置的函数。 KMP算法效率最好,时间复杂度是O(n+m)。 44. 多重继承的内存分配问题: 比如有 class A : public class B, public class C {} 那么A的内存结构大致是怎么样的? 这个是 compiler-dependent 的, 不同的实现其细节可能不同。 如果不考虑有虚函数、虚继承的话就相当简单;否则的话,相当复杂。 可以参考《深入探索C++对象模型》,或者: http://blog.csdn.net/wfwd/archive/2006/05/30/763797.aspx 45. 如何判断一个单链表是有环的?(注意不能用标志位,最多只能用两个额外指 针) struct node { char val; node* next;} bool check(const node* head) {} //return false : 无环;true: 有环 一种 O(n)的办法就是(搞两个指针,一个每次递增一步,一个每次递增两步,如果 有环 的话两者必然重合,反之亦然): bool check(const node* head) { if(head==NULL) return false; node *low=head, *fast=head->next; while(fast!=NULL&& fast->next!=NULL) { low=low->next; fast=fast->next->next; if(low==fast) return true; } return false; } Oracle 的知识(必备) 一、基础 1、说明:创建数据库 CREATE DATABASE database-name 2、说明:删除数据库 drop database dbname 3、说明:备份 sql server --- 创建 备份数据的 device USE master EXEC sp_addumpdevice 'disk', 'testBack', 'c:\mssql7backup\MyNwind_1.dat' --- 开始 备份 BACKUP DATABASE pubs TO testBack 4、说明:创建新表 create table tabname(col1 type1 [not null] [primary key],col2 type2 [not null],..) 根据已有的表创建新表: A:create table tab_new like tab_old (使用旧表创建新表) B:create table tab_new as select col1,col2… from tab_old definition only 5、说明:删除新表 drop table tabname 6、说明:增加一个列 Alter table tabname add column col type 注:列增加后将不能删除。 DB2 中列加上后数据类型也不能改变,唯一能改变 的是增加 varchar 类型的长度。 7、说明:添加主键 : Alter table tabname add primary key(col) 说明:删除主键: Alter table tabname drop primary key(col) 8、说明:创建索引:create [unique] index idxname on tabname(col….) 删除索引:drop index idxname 注:索引是不可更改的,想更改必须删除重新建。 9、说明:创建视图:create view viewname as select statement 删除视图:drop view viewname 10、说明:几个简单的基本的 sql 语句 选择:select * from table1 where 范围 插入:insert into table1(field1,field2) values(value1,value2) 删除:delete from table1 where 范围 更新:update table1 set field1=value1 where 范围 查找:select * from table1 where field1 like ’%value1%’ ---like 的语法 很精妙,查资料! 排序:select * from table1 order by field1,field2 [desc] 总数:select count as totalcount from table1 求和:select sum(field1) as sumvalue from table1 平均:select avg(field1) as avgvalue from table1 最大:select max(field1) as maxvalue from table1 最小:select min(field1) as minvalue from table1 11、说明:几个高级查询运算词 A : UNION 运算符 UNION 运算符通过组合其他两个结果表(例如 TABLE1 和 TABLE2)并消去 表中任何重复行而派生出一个结果表。当 ALL 随 UNION 一起使用时(即 UNION ALL),不消除重复行。两种情况下,派生表的每一行不是来自 TABLE1 就是来自 TABLE2 。 B : EXCEPT 运算符 EXCEPT 运算符通过包括所有在 TABLE1 中但不在 TABLE2 中的行并消除所 有重复行而派生出一个结果表。当 ALL 随 EXCEPT 一起使用时 (EXCEPT ALL),不消除重复行。 C : INTERSECT 运算符 INTERSECT 运算符通过只包括 TABLE1 和 TABLE2 中都有的行并消除所有 重复行而派生出一个结果表。当 ALL 随 INTERSECT 一起使用时 (INTERSECT ALL) ,不消除重复行。 注:使用运算词的几个查询结果行必须是一致的 。 12 、说明:使用外连接 A、left (outer ) join : 左外连接(左连接):结果集几包括连接表的匹配行,也包括左连接表的所 有行。 SQL: select a.a, a.b, a.c, b.c, b.d, b.f from a LEFT OUT JOIN b ON a.a = b.c B:right (outer ) join: 右外连接(右连接):结果集既包括连接表的匹配连接行,也包括右连接表的所 有行。 C:full/cross (outer ) join : 全外连接:不仅包括符号连接表的匹配行,还包括两个连接表中的所有记录。 12、分组:Group by: 一张表,一旦分组 完成后,查询后只能得到组相关的信息。 组相关的信息: (统计信息) count,sum,max,min,avg 分组的标准) 在 SQLServer 中分组时:不能以 text,ntext,image 类型的字段作 为分组依据 在 selecte 统计函数中的字段,不能和普通的字段放在一起; 13、对数据库进行操作: 分离数据库: sp_detach_db; 附加数据库:sp_attach_db 后接表 明,附加需要完整的路径名 14.如何修改数据库的名称: sp_renamedb 'old_name', 'new_name' 二、提升 1、说明:复制表(只复制结构,源表名:a 新表名:b) (Access 可用) 法一:select * into b from a where 1<>1(仅用于 SQlServer) 法二:select top 0 * into b from a 2、说明:拷贝表(拷贝数据,源表名:a 目标表名:b) (Access 可用) insert into b(a, b, c) select d,e,f from b; 3、说明:跨数据库之间表的拷贝(具体数据使用绝对路径) (Access 可 用) insert into b(a, b, c) select d,e,f from b in ‘ ’ 具体数据库 where 条件 例子:..from b in '"&Server.MapPath(".")&"\data.mdb" &"' where.. 4、说明:子查询(表名 1:a 表名 2:b) select a,b,c from a where a IN (select d from b ) 或者: select a,b,c from a where a IN (1,2,3) 5、说明:显示文章、提交人和最后回复时间 select a.title,a.username,b.adddate from table a,(select max(adddate) adddate from table where table.title=a.title) b 6、说明:外连接查询(表名 1:a 表名 2:b) select a.a, a.b, a.c, b.c, b.d, b.f from a LEFT OUT JOIN b ON a.a = b.c 7、说明:在线视图查询(表名 1:a ) select * from (SELECT a,b,c FROM a) T where t.a > 1; 8、说明:between 的用法,between 限制查询数据范围时包括了边界 值,not between 不包括 select * from table1 where time between time1 and time2 select a,b,c, from table1 where a not between 数值 1 and 数值 2 9、说明:in 的使用方法 select * from table1 where a [not] in (‘值 1’,’值 2’,’值 4’,’值 6’) 10 、说明:两张关联表,删除主表中已经在副表中没有的信息 delete from table1 where not exists ( select * from table2 where table1.field1=table2.field1 ) 11、说明:四表联查问题: select * from a left inner join b on a.a=b.b right inner join c on a.a=c.c inner join d on a.a=d.d where ..... 12 、说明:日程安排提前五分钟提醒 SQL: select * from 日程安排 where datediff('minute',f 开始时 间,getdate())>5 13、说明:一条 sql 语句搞定数据库分页 select top 10 b.* from (select top 20 主键字段, 排序字段 from 表名 order by 排序 字段 desc) a, 表名 b where b. 主键字段 = a. 主键字段 order by a.排序字段 具体实现: 关于数据库分页: declare @start int,@end int @sql nvarchar(600) set @sql=’select top’+str(@end-@start+1)+’+from T where rid not in(select top’+str(@str-1)+’Rid from T where Rid>-1)’ exec sp_executesql @sql 注意:在 top 后不能直接跟一个变量,所以在实际应用中只有这样的进 行特殊的处理。Rid 为一个标识列,如果 top 后还有具体的字段,这样 做是非常有好处的。因为这样可以避免 top 的字段如果是逻辑索引的, 查询的结果后实际表中的不一致(逻辑索引中的数据有可能和数据表中 的不一致,而查询时如果处在索引则首先查询索引) 14、说明:前 10 条记录 select top 10 * form table1 where 范围 15、说明:选择在每一组 b 值相同的数据中对应的 a 最大的记录的所有 信息(类似这样的用法可以用于论坛每月排行榜,每月热销产品分析,按科 目成绩排名,等等.) select a,b,c from tablename ta where a=(select max(a) from tablename tb where tb.b=ta.b) 16 、说明:包括所有在 TableA 中但不在 TableB 和 TableC 中的行并消 除所有重复行而派生出一个结果表 (select a from tableA ) except (select a from tableB) except (select a from tableC) 17、说明:随机取出 10 条数据 select top 10 * from tablename order by newid() 18、说明:随机选择记录 select newid() 19、说明:删除重复记录 1),delete from tablename where id not in (select max(id) from tablename group by col1,col2,...) 2),select distinct * into temp from tablename delete from tablename insert into tablename select * from temp 评价: 这种操作牵连大量的数据的移动,这种做法不适合大容量但数 据操作 3),例如:在一个外部表中导入数据,由于某些原因第一次只导入了一 部分,但很难判断具体位置,这样只有在下一次全部导入,这样也就产 生好多重复的字段,怎样删除重复字段 alter table tablename --添加一个自增列 add column_b int identity(1,1) delete from tablename where column_b not in( select max(column_b) from tablename group by column1,column2,...) alter table tablename drop column column_b 20、说明:列出数据库里所有的表名 select name from sysobjects where type='U' // U 代表用户 21、说明:列出表里的所有的列名 select name from syscolumns where id=object_id('TableName') 22、说明:列示 type、vender、pcs 字段,以 type 字段排列,case 可 以方便地实现多重选择,类似 select 中的 case。 select type,sum(case vender when 'A' then pcs else 0 end),sum(case vender when 'C' then pcs else 0 end),sum(case vender when 'B' then pcs else 0 end) FROM tablename group by type 显示结果: type vender pcs 电脑 A 1 电脑 A 1 光盘 B 2 光盘 A 2 手机 B 3 手机 C 3 23、说明:初始化表 table1 TRUNCATE TABLE table1 24、说明:选择从 10 到 15 的记录 select top 5 * from (select top 15 * from table order by id asc) table_ 别名 order by id desc 三、技巧 1、1=1,1=2 的使用,在 SQL 语句组合时用的较多 “where 1=1” 是表示选择全部 “where 1=2”全部不选, 如: if @strWhere !='' begin set @strSQL = 'select count(*) as Total from [' + @tblName + '] where ' + @strWhere end else begin set @strSQL = 'select count(*) as Total from [' + @tblName + ']' end 我们可以直接写成 set @strSQL = 'select count(*) as Total from [' + @tblName + '] where 1=1 安定 '+ @strWhere 2、收缩数据库 --重建索引 DBCC REINDEX DBCC INDEXDEFRAG --收缩数据和日志 DBCC SHRINKDB DBCC SHRINKFILE 3、压缩数据库 dbcc shrinkdatabase(dbname) 4、转移数据库给新用户以已存在用户权限 exec sp_change_users_login 'update_one','newname','oldname' go 5、检查备份集 RESTORE VERIFYONLY from disk='E:\dvbbs.bak' 6、修复数据库 ALTER DATABASE [dvbbs] SET SINGLE_USER GO DBCC CHECKDB('dvbbs',repair_allow_data_loss) WITH TABLOCK GO ALTER DATABASE [dvbbs] SET MULTI_USER GO 7、日志清除 SET NOCOUNT ON DECLARE @LogicalFileName sysname, @MaxMinutes INT, @NewSize INT USE tablename -- 要操作的数据库名 SELECT @LogicalFileName = 'tablename_log', -- 日志文件名 @MaxMinutes = 10, -- Limit on time allowed to wrap log. @NewSize = 1 -- 你想设定的日志文件的大小(M) Setup / initialize DECLARE @OriginalSize int SELECT @OriginalSize = size FROM sysfiles WHERE name = @LogicalFileName SELECT 'Original Size of ' + db_name() + ' LOG is ' + CONVERT(VARCHAR(30),@OriginalSize) + ' 8K pages or ' + CONVERT(VARCHAR(30),(@OriginalSize*8/1024)) + 'MB' FROM sysfiles WHERE name = @LogicalFileName CREATE TABLE DummyTrans (DummyColumn char (8000) not null) DECLARE @Counter INT, @StartTime DATETIME, @TruncLog VARCHAR(255) SELECT @StartTime = GETDATE(), @TruncLog = 'BACKUP LOG ' + db_name() + ' WITH TRUNCATE_ONLY' DBCC SHRINKFILE (@LogicalFileName, @NewSize) EXEC (@TruncLog) -- Wrap the log if necessary. WHILE @MaxMinutes > DATEDIFF (mi, @StartTime, GETDATE()) -- time has not expired AND @OriginalSize = (SELECT size FROM sysfiles WHERE name = @LogicalFileName) AND (@OriginalSize * 8 /1024) > @NewSize BEGIN -- Outer loop. SELECT @Counter = 0 WHILE ((@Counter < @OriginalSize / 16) AND (@Counter < 50000)) BEGIN -- update INSERT DummyTrans VALUES ('Fill Log') DELETE DummyTrans SELECT @Counter = @Counter + 1 END EXEC (@TruncLog) END SELECT 'Final Size of ' + db_name() + ' LOG is ' + CONVERT(VARCHAR(30),size) + ' 8K pages or ' + CONVERT(VARCHAR(30),(size*8/1024)) + 'MB' FROM sysfiles WHERE name = @LogicalFileName DROP TABLE DummyTrans SET NOCOUNT OFF 8、说明:更改某个表 exec sp_changeobjectowner 'tablename','dbo' 9、存储更改全部表 CREATE PROCEDURE dbo.User_ChangeObjectOwnerBatch @OldOwner as NVARCHAR(128), @NewOwner as NVARCHAR(128) AS DECLARE @Name as NVARCHAR(128) DECLARE @Owner as NVARCHAR(128) DECLARE @OwnerName as NVARCHAR(128) DECLARE curObject CURSOR FOR select 'Name' = name, 'Owner' = user_name(uid) from sysobjects where user_name(uid)=@OldOwner order by name OPEN curObject FETCH NEXT FROM curObject INTO @Name, @Owner WHILE(@@FETCH_STATUS=0) BEGIN if @Owner=@OldOwner begin set @OwnerName = @OldOwner + '.' + rtrim(@Name) exec sp_changeobjectowner @OwnerName, @NewOwner end -- select @name,@NewOwner,@OldOwner FETCH NEXT FROM curObject INTO @Name, @Owner END close curObject deallocate curObject GO 10、SQL SERVER 中直接循环写入数据 declare @i int set @i=1 while @i<30 begin insert into test (userid) values(@i) set @i=@i+1 end 案例: 有如下表,要求就裱中所有沒有及格的成績,在每次增長 0.1 的基礎上,使他們 剛好及格: Name score Zhangshan 80 Lishi 59 Wangwu 50 Songquan 69 while((select min(score) from tb_table)<60) begin update tb_table set score =score*1.01 where score<60 if (select min(score) from tb_table)>60 break else continue end 数据开发-经典 1.按姓氏笔画排序: Select * From TableName Order By CustomerName Collate Chinese_PRC_Stroke_ci_as //从少到多 2.数据库加密: select encrypt('原始密码') select pwdencrypt('原始密码') select pwdcompare('原始密码','加密后密码') = 1--相同;否则不相同 encrypt('原始密码') select pwdencrypt('原始密码') select pwdcompare('原始密码','加密后密码') = 1--相同;否则不相同 3.取回表中字段: declare @list varchar(1000), @sql nvarchar(1000) select @list=@list+','+b.name from sysobjects a,syscolumns b where a.id=b.id and a.name='表 A' set @sql='select '+right(@list,len(@list)-1)+' from 表 A' exec (@sql) 4.查看硬盘分区: EXEC master..xp_fixeddrives 5.比较 A,B 表是否相等: if (select checksum_agg(binary_checksum(*)) from A) = (select checksum_agg(binary_checksum(*)) from B) print '相等' else print '不相等' 6.杀掉所有的事件探察器进程: DECLARE hcforeach CURSOR GLOBAL FOR SELECT 'kill '+RTRIM(spid) FROM master.dbo.sysprocesses WHERE program_name IN('SQL profiler',N'SQL 事件探查器') EXEC sp_msforeach_worker '?' 7.记录搜索: 开头到 N 条记录 Select Top N * From 表 ------------------------------- N 到 M 条记录(要有主索引 ID) Select Top M-N * From 表 Where ID in (Select Top M ID From 表) Order by ID Desc ---------------------------------- N 到结尾记录 Select Top N * From 表 Order by ID Desc 案例 例如 1 :一张表有一万多条记录,表的第一个字段 RecID 是自增长字段, 写一个 SQL 语句, 找出表的第 31 到第 40 个记录。 select top 10 recid from A where recid not in(select top 30 recid from A) 分析:如果这样写会产生某些问题,如果 recid 在表中存在逻辑索引。 select top 10 recid from A where……是从索引中查找,而后面的 select top 30 recid from A 则在数据表中查找,这样由于索引中的顺序有可能和数据表中的不一致, 这样就导致查询到的不是本来的欲得到的数据。 解决方案 1, 用 order by select top 30 recid from A order by ricid 如果该字段不 是自增长,就会出现问题 2, 在那个子查询中也加条件:select top 30 recid from A where recid>-1 例 2:查询表中的最后以条记录,并不知道这个表共有多少数据,以及表结构。 set @s = 'select top 1 * from T where pid not in (select top ' + str(@count-1) + ' pid from T)' print @s exec sp_executesql @s 9:获取当前数据库中的所有用户表 select Name from sysobjects where xtype='u' and status>=0 10:获取某一个表的所有字段 select name from syscolumns where id=object_id('表名') select name from syscolumns where id in (select id from sysobjects where type = 'u' and name = '表名') 两种方式的效果相同 11:查看与某一个表相关的视图、存储过程、函数 select a.* from sysobjects a, syscomments b where a.id = b.id and b.text like '%表名%' 12:查看当前数据库中所有存储过程 select name as 存储过程名称 from sysobjects where xtype='P' 13:查询用户创建的所有数据库 select * from master..sysdatabases D where sid not in(select sid from master..syslogins where name='sa') 或者 select dbid, name AS DB_NAME from master..sysdatabases where sid <> 0x01 14:查询某一个表的字段和数据类型 select column_name,data_type from information_schema.columns where table_name = '表名' 15:不同服务器数据库之间的数据操作 -- 创建链接服务器 exec sp_addlinkedserver 'ITSV ', ' ', 'SQLOLEDB ', '远程服务器名或 ip 地址 ' exec sp_addlinkedsrvlogin 'ITSV ', 'false ',null, ' 用户名 ', ' 密码 ' -- 查询示例 select * from ITSV.数据库名.dbo. 表名 -- 导入示例 select * into 表 from ITSV.数据库名.dbo. 表名 -- 以后不再使用时删除链接服务器 exec sp_dropserver 'ITSV ', 'droplogins ' --连接远程/局域网数据(openrowset/openquery/opendatasource) --1、openrowset -- 查询示例 select * from openrowset( 'SQLOLEDB ', 'sql 服务器名 '; ' 用户名 '; ' 密码 ',数据库名. dbo.表名) -- 生成本地表 select * into 表 from openrowset( 'SQLOLEDB ', 'sql 服务器名 '; ' 用户名 '; ' 密码 ', 数据库名.dbo.表名) -- 把本地表导入远程表 insert openrowset( 'SQLOLEDB ', 'sql 服务器名 '; ' 用户名 '; ' 密码 ',数据库名.dbo.表 名) select *from 本地表 -- 更新本地表 update b set b.列 A=a.列 A from openrowset( 'SQLOLEDB ', 'sql 服务器名 '; ' 用户名 '; ' 密码 ',数据库名.dbo.表 名)as a inner join 本地表 b on a.column1=b.column1 --openquery 用法需要创建一个连接 -- 首先创建一个连接创建链接服务器 exec sp_addlinkedserver 'ITSV ', ' ', 'SQLOLEDB ', '远程服务器名或 ip 地址 ' -- 查询 select * FROM openquery(ITSV, 'SELECT * FROM 数据库.dbo. 表名 ') -- 把本地表导入远程表 insert openquery(ITSV, 'SELECT * FROM 数据库.dbo. 表名 ') select * from 本地表 -- 更新本地表 update b set b.列 B=a.列 B FROM openquery(ITSV, 'SELECT * FROM 数据库.dbo. 表名 ') as a inner join 本地表 b on a.列 A=b.列 A --3、opendatasource/openrowset SELECT * FROM opendatasource( 'SQLOLEDB ', 'Data Source=ip/ServerName;User ID= 登陆名;Password= 密码 ' ).test.dbo.roy_ta -- 把本地表导入远程表 insert opendatasource( 'SQLOLEDB ', 'Data Source=ip/ServerName;User ID= 登陆名;Password= 密码 ').数据库.dbo. 表名 select * from 本地表 SQL Server 基本函数 SQL Server 基本函数 1. 字符串函数 长度与分析用 1,datalength(Char_expr) 返回字符串包含字符数,但不包含后面的空格 2,substring(expression,start,length) 取子串,字符串的下标是从 “1”,start 为起始位置,length 为字符串长度,实际应用中以 len(expression)取得其长度 3,right(char_expr,int_expr) 返回字符串右边第 int_expr 个字符,还用 left 于之相反 4,isnull( check_expression , replacement_value )如果 check_expression 為空,則返回 replacement_value 的值,不為空,就返 回 check_expression 字符操作类 5,Sp_addtype 自定義數據類型 例如:EXEC sp_addtype birthday, datetime, 'NULL' 6,set nocount {on|off} 使返回的结果中不包含有关受 Transact-SQL 语句影响的行数的信息。如果存 储过程中包含的一些语句并不返回许多实际的数据,则该设置由于大量减少 了网络流量,因此可显著提高性能。SET NOCOUNT 设置是在执行或运行时设 置,而不是在分析时设置。 SET NOCOUNT 为 ON 时,不返回计数(表示受 Transact-SQL 语句影响的 行数)。 SET NOCOUNT 为 OFF 时,返回计数 常识 在 SQL 查询中:from 后最多可以跟多少张表或视图:256 在 SQL 语句中出现 Order by,查询时,先排序,后取 在 SQL 中,一个字段的最大容量是 8000,而对于 nvarchar(4000),由于 nvarchar 是 Unicode 码。 SQLServer2000 同步复制技术实现步骤 一、 预备工作 1.发布服务器,订阅服务器都创建一个同名的 windows 用户,并设置相同的密码,做为发布 快照文件夹的有效访问用户 --管理工具 --计算机管理 --用户和组 --右键用户 --新建用户 --建立一个隶属于 administrator 组的登陆 windows 的用户(SynUser) 2.在发布服务器上,新建一个共享目录,做为发布的快照文件的存放目录,操作: 我的电脑--D:\ 新建一个目录,名为: PUB --右键这个新建的目录 --属性--共享 --选择"共享该文件夹" --通过"权限"按纽来设置具体的用户权限,保证第一步中创建的用户(SynUser) 具有对该 文件夹的所有权限 --确定 3.设置 SQL 代理(SQLSERVERAGENT)服务的启动用户(发布/订阅服务器均做此设置) 开始--程序--管理工具--服务 --右键 SQLSERVERAGENT --属性--登陆--选择"此账户" --输入或者选择第一步中创建的 windows 登录用户名(SynUser) --"密码"中输入该用户的密码 4.设置 SQL Server 身份验证模式,解决连接时的权限问题(发布/订阅服务器均做此设置) 企业管理器 --右键 SQL 实例--属性 --安全性--身份验证 --选择"SQL Server 和 Windows" --确定 5.在发布服务器和订阅服务器上互相注册 企业管理器 --右键 SQL Server 组 --新建 SQL Server 注册... --下一步--可用的服务器中, 输入你要注册的远程服务器名 --添加 --下一步--连接使用,选择第二个"SQL Server 身份验证" --下一步--输入用户名和密码(SynUser) --下一步--选择 SQL Server 组,也可以创建一个新组 --下一步--完成 6.对于只能用 IP,不能用计算机名的,为其注册服务器别名(此步在实施中没用到) (在连接端配置,比如,在订阅服务器上配置的话,服务器名称中输入的是发布服务器的 IP) 开始--程序--Microsoft SQL Server--客户端网络实用工具 --别名--添加 --网络库选择"tcp/ip"--服务器别名输入 SQL 服务器名 --连接参数--服务器名称中输入 SQL 服务器 ip 地址 --如果你修改了 SQL 的端口,取消选择"动态决定端口",并输入对应的端口号 二、 正式配置 1、配置发布服务器 打开企业管理器,在发布服务器(B、C、D)上执行以下步骤: (1) 从[工具]下拉菜单的[复制]子菜单中选择[配置发布、订阅服务器和分发]出现配置发布 和分发向导 (2) [下一步] 选择分发服务器 可以选择把发布服务器自己作为分发服务器或者其他 sql 的服务器(选择自己) (3) [下一步] 设置快照文件夹 采用默认\\servername\Pub (4) [下一步] 自定义配置 可以选择:是,让我设置分发数据库属性启用发布服务器或设置发布设置 否,使用下列默认设置(推荐) (5) [下一步] 设置分发数据库名称和位置 采用默认值 (6) [下一步] 启用发布服务器 选择作为发布的服务器 (7) [下一步] 选择需要发布的数据库和发布类型 (8) [下一步] 选择注册订阅服务器 (9) [下一步] 完成配置 2、创建出版物 发布服务器 B、C、D 上 (1)从[工具]菜单的[复制]子菜单中选择[创建和管理发布]命令 (2)选择要创建出版物的数据库,然后单击[创建发布] (3)在[创建发布向导]的提示对话框中单击[下一步]系统就会弹出一个对话框。对话框上的 内容是复制的三个类型。我们现在选第一个也就是默认的快照发布(其他两个大家可以去 看看帮助) (4)单击[下一步]系统要求指定可以订阅该发布的数据库服务器类型, SQLSERVER 允许在不同的数据库如 orACLE 或 ACCESS 之间进行数据复制。 但是在这里我们选择运行"SQL SERVER 2000"的数据库服务器 (5)单击[下一步]系统就弹出一个定义文章的对话框也就是选择要出版的表 注意: 如果前面选择了事务发布 则再这一步中只能选择带有主键的表 (6)选择发布名称和描述 (7) 自定义发布属性 向导提供的选择: 是 我将自定义数据筛选,启用匿名订阅和或其他自定义属性 否 根据指定方式创建发布 (建议采用自定义的方式) (8)[下一步] 选择筛选发布的方式 (9)[下一步] 可以选择是否允许匿名订阅 1)如果选择署名订阅,则需要在发布服务器上添加订阅服务器 方法: [工具]->[复制]->[配置发布、订阅服务器和分发的属性]->[订阅服务器] 中添加 否则在订阅服务器上请求订阅时会出现的提示:改发布不允许匿名订阅 如果仍然需要匿名订阅则用以下解决办法 [企业管理器]->[复制]->[发布内容]->[属性]->[订阅选项] 选择允许匿名请求订阅 2)如果选择匿名订阅,则配置订阅服务器时不会出现以上提示 (10)[下一步] 设置快照 代理程序调度 (11)[下一步] 完成配置 当完成出版物的创建后创建出版物的数据库也就变成了一个共享数据库 有数据 srv1.库名..author 有字段:id,name,phone, srv2.库名..author 有字段:id,name,telphone,adress 要求: srv1.库名..author 增加记录则 srv1.库名..author 记录增加 srv1.库名..author 的 phone 字段更新,则 srv1.库名..author 对应字段 telphone 更新 --*/ -- 大致的处理步骤 --1. 在 srv1 上创建连接服务器, 以便在 srv1 中操作 srv2, 实现同步 exec sp_addlinkedserver 'srv2','','SQLOLEDB','srv2 的 sql 实例名或 ip' exec sp_addlinkedsrvlogin 'srv2','false',null,'用户名','密码' go --2. 在 srv1 和 srv2 这两台电脑中, 启动 msdtc(分布式事务处理服务),并且设置为自动 启动 。我的电脑--控制面板--管理工具--服务-- 右键 Distributed Transaction Coordinator-- 属性--启动-- 并将启动类型设置为自动启动 go -- 然后创建一个作业定时调用上面的同步处理存储过程就行了 企业管理器 -- 管理 --SQL Server 代理 -- 右键作业 -- 新建作业 --"常规" 项中输入作业名称 --"步骤" 项 -- 新建 --"步骤名" 中输入步骤名 --"类型"中选择"Transact-SQL 脚本(TSQL)" --"数据库" 选择执行命令的数据库 --"命令"中输入要执行的语句: exec p_process -- 确定 --"调度" 项 -- 新建调度 --"名称" 中输入调度名称 --"调度类型" 中选择你的作业执行安排 --如果选择"反复出现" --点"更改" 来设置你的时间安排 然后将 SQL Agent 服务启动,并设置为自动启动, 否则你的作业不会被执行 设置方法: 我的电脑--控制面板--管理工具--服务-- 右键 SQLSERVERAGENT--属性--启动类型--选 择"自动启动"--确定. --3.实现同步处理的方法 2, 定时同步 --在 srv1 中创建如下的同步处理存储过程 create proc p_process as -- 更新修改过的数据 update b set name=i.name,telphone=i.telphone from srv2.库名.dbo.author b,author i where b.id=i.id and (b.name <> i.name or b.telphone <> i.telphone) -- 插入新增的数据 insert srv2.库名.dbo.author(id,name,telphone) select id,name,telphone from author i where not exists( select * from srv2.库名.dbo.author where id=i.id) --删除已经删除的数据(如果需要的话) delete b from srv2.库名.dbo.author b where not exists( select * from author where id=b.id) go
还剩242页未读

继续阅读

下载pdf到电脑,查找使用更方便

pdf的实际排版效果,会与网站的显示效果略有不同!!

需要 8 金币 [ 分享pdf获得金币 ] 1 人已下载

下载pdf

pdf贡献者

千王之王

贡献于2012-08-03

下载需要 8 金币 [金币充值 ]
亲,您也可以通过 分享原创pdf 来获得金币奖励!
下载pdf