C/C++笔试题1(基础题)
为了便于温故而知新,特于此整理 C/C++ 方面相关面试题。分享,共勉。
(备注:各题的重要程度与先后顺序无关。不断更新中......欢迎补充)
(1)分析下面程序的输出(* 与 -- 运算符优先级问题)
程序1:原题程序
1 #include<iostream> 2 using namespace std; 3 4 void main() 5 { 6 static int a[5] = {1, 2, 3, 4, 5}, b = 6; 7 int *p = (int *)(&a + 1); 8 cout << "*(a + 1) :: " << *(a + 1) << endl; 9 cout << "*p :: " << *p << endl; 10 cout << "*p-- :: " << *p-- << endl; 11 cout << "*p---b :: " << *p---b << endl; 12 cout << "*p :: " << *p << endl; 13 cout << "*p-- :: " << *p-- << endl; 14 15 system("pause"); 16 } 17 // run out 18 /* 19 *(a + 1) :: 2 20 *p :: 6 21 *p-- :: 6 22 *p---b :: -1 23 *p :: 4 24 *p-- :: 4 25 请按任意键继续. . . 26 */
总结:参考《C++操作符的优先级》
程序2:对比分析
1 #include<iostream> 2 using namespace std; 3 4 void main() 5 { 6 static int ar[5] = {1, 2, 3, 4, 5}; 7 int cr = 6; 8 int *pr = (int *)(&ar + 1); 9 cout << "*(ar + 1) :: " << *(ar + 1) << endl; 10 cout << "*pr :: " << *pr << endl; 11 cout << "*pr-- :: " << *pr-- << endl; 12 cout << "*pr :: " << *pr << endl; 13 cout << "*pr---cr :: " << *pr---cr << endl; 14 cout << "*pr :: " << *pr << endl; 15 cout << "*pr-- :: " << *pr-- << endl; 16 17 system("pause"); 18 } 19 20 // run out 21 /* 22 *(ar + 1) :: 2 23 *pr :: 0 24 *pr-- :: 0 25 *pr :: 5 26 *pr---cr :: -1 27 *pr :: 4 28 *pr-- :: 4 29 请按任意键继续. . . 30 */
(2)指针函数与函数指针的区别
指针函数是指一个返回指针类型的函数。函数指针是指一个指向函数的指针。
(3)函数模板与模板函数的区别
函数模板指由关键字template、typename(或class)定义的通用函数体。
将类型形参实例化的参数成为模板实参。模板函数指用模板实参实例化的函数。
即模板函数指将函数模板的类型形参实例化的过程。
(4)static 在 C/C++ 语言中的作用
1、static在C语言中的作用
第一、修饰局部变量。static修饰的局部变量只执行一次初始化,且延长了局部变量的生命周期,直到程序运行结束以后才释放。
static修饰的局部变量存放在全局数据区的静态变量区。初始化的时默认值为0;
第二、修饰全局变量。static修饰的全局变量只能在本文件中访问,不能在其它文件中访问,即便是加extern外部声明也不可以。
第三、修饰函数。若static修饰一个函数,则这个函数的只能在本文件中调用,不能在其他文件被调用。即具有文件作用域。
2、static在C++语言中的作用
第一、类中静态成员分为 静态成员变量 和 静态成员函数。
第二、静态成员变量的名字在类的作用域中,可以避免命名冲突。
第三、静态成员变量独立于该类的任何对象而存在。
第四、静态成员变量可以声明为任意类型:常量、引用、数组、类本身类型等。
第五、静态成员变量必须在类的定义体外部初始化值。
第六、静态成员函数与一般普通成员函数最大的区别在于不存在this指针。因为这个函数是与类相关的函数,而不是与某一个对象相关。
第七、声明静态成员函数时在前面加关键字static,当在类外实现这个函数时,不允许加关键字。
第八、可以通过作用域操作符直接调用static静态成员函数。或通过类的对象、引用或指向类对象的指针间接的调用static静态成员函数。
第九、static静态成员函数不是任何对象的组成部分,所以static成员函数不能被声明为const。
(函数声明为const是对函数this指针的进一步限定,而static成员函数本身就不存在this指针,所以再加const是没有意义。)
第十、static静态成员函数不可以被声明为虚函数。虚函数是为实现多态的一种特殊成员函数,因为static函数没有this指针,因此是没有意义的。
总结以上内容:
3、参见随笔《static关键字(1)》
4、参见随笔《static关键字(C/C++中的作用)》
(5)相比于C函数,C++函数有哪些特色?
C++函数增加重载、内联、const、virtual四种新机制。
重载和内联机制既可以用于全局函数也可以用于类的成员函数。
const和virtual机制仅用于类的成员函数。
(6)重载、覆盖、隐藏的区别
重载是指在同一个类中,同名函数,参数不同。重载的核心在于参数,参数有三个基本点:1、类型 2、数量 3、顺序。(切记与返回值无关)。
注意:第一、作用域(全局函数与类中成员函数同名不算重载)。第二、类中成员函数加const修饰也算作重载范畴。
覆盖是指派生类重写(遵循三同原则)基类的虚函数。
隐藏分两种情况:
一则:派生类与基类的函数同名,但参数不同,与关键字virtual无关;(不同于重载)
二则:派生类与基类的函数同名,并且同参,但无关键字virtual;(不同于覆盖)
具体代码可参见随笔《 重载 覆盖 隐藏 》
(7)函数strlen与操作符sizeof的区别:参见随笔《strlen与sizeof》
(8)函数声明。声明函数原型时,函数每个形参的名称可省略,主要声明清楚每个参数的类型和返回值类型就可以了。
(9)表达式。所有的表达式都有值。
(10)编译。程序的编译是以文件为单位的,因此将程序分到多个文件中可以减少每次对程序修改后再编译所带来的工作量。
(11)静态成员变量。类的静态数据成员变量需要在类定义外进行初始化。
(12)友元。当将一个类S定义为另一个类A的友元类时,类S的所有成员函数都可以直接访问类A的所有成员(成员变量和成员函数)。
(13)C/C++内存分配区别:
1、malloc / free是C语言标准的库函数;new / delete是C++中的运算符。
2、都是在堆(heap)上进行动态的内存操作。
3、用malloc函数需要指定内存分配的字节数并且不能初始化对象;new 会自动调用对象的构造函数。
4、delete会调用对象的destructor,而free不会调用对象的destructor。
(14)如果创建的是静态局部或全局对象,则对象的位模式全部为0,否则默认值将会是随机的。
(15)析构函数是特殊的类成员函数,它没有返回类型、没有参数、不能随意调用、也没有重载,只有在类对象的生命期结束时,由系统自动调用。
(16)类中成员对象的构造是按照在类中定义的顺序进行的,而不是按照构造函数冒号后的初始化列表顺序进行构造的(这点尤其需要注意)。
(17)explicit关键字。普通构造函数可以被隐式调用,而被关键字explicit修饰的构造函数只能被显式调用。
(18)拷贝构造函数使用情况:
1、一个对象以值传递的方式传入函数体。
2、一个对象以值传递的方式从函数返回。
3、一个对象需要通过另外一个对象进行初始化。
(19)在ANSI C 语言中用什么来定义常量呢?答案是enum类型和#define宏,这两个都可以用来定义常量。而在C++的类中实现常量,需要考虑使用枚举。
(20)一个类的构造和析构函数都不能用const修饰。
(21)const修饰成员函数。const在类中对成员函数的三种作用(1、参数;2、返回值;3、函数体)。
(22)枚举详解
1、枚举是一种类型。
2、默认的,第一个枚举成员赋值为0,后面的每个枚举成员的值比前面的大1。
3、枚举成员本身是一个常量表达式,不可以改变其值。
4、可以显式的定义枚举成员的值,当随机指定其中某个成员值后,位于其前的成员值仍为默认值,位于其后的成员值比前面的大一。
(23)引用与指针的区别
1、初始化要求不同。前者必须要初始化。
2、可修改性不同。前者一旦被初始化,它就不能被另一个对象引用,而指针在任何时候都可以指向另一个对象。
3、不存在NULL引用。引用必须要确定具体引用的是某个对象。
4、测试的区别。引用不会指向空值,使用引用前不需要测试它的合法性。指针可能为空值,使用前需要进行测试,避免空指针导致程序崩溃。
5、应用的区别。如果指向一个对象后就不会再改变指向,那么选择使用引用。如果在不同的时刻需要指向不同的对象,应该使用指针。
(24)内联是以代码膨胀为代价的,仅仅省去了函数调用的开销,从而提高了函数的执行效率,一般适合于使用频率高,并且代码量简单的函数。
(25)内联与宏的区别:
内联函数在编译时展开,宏在预编译时展开。
在编译时内联函数可以直接被嵌入到目标代码中,而宏在预处理是只是仅仅的文本替换。
内联函数可以完成类型匹配,语句正确的判断,而宏不具有。
宏不属于函数,内联函数属于函数。
宏在定义时要小心处理宏参数,以免出现二义性。
(26)头文件中的ifndef/define/endif干什么用?防止头文件被重复引用。
(27)#include <filename.h> 和 #include “filename.h” 有什么区别?
对于#include <filename.h> ,编译器从标准库路径开始搜索 filename.h
对于#include “filename.h”,编译器从用户的工作路径开始搜索 filename.h
(28)const 有什么用途?
1、const即“只读”。可以定义 const 常量。
2、const可以修饰成员函数的参数、返回值,甚至函数的定义体。
被const 修饰的东西都受到强制保护,可以预防意外的变动,能提高程序的健壮性。
3、参见随笔《const》
(29)在C++ 程序中调用被 C 编译器编译后的函数,为什么要加 extern “C”?
C++语言支持函数重载,C 语言不支持函数重载。函数被C++编译后在库中的名字与C 语言的不同。假设某个函数的原型为:
void foo(int x, int y);该函数被C 编译器编译后在库中的名字为_foo ,而C++编译器则会产生像_foo_int_int 之类的名字。
C++提供了C 连接交换指定符号extern“C”来解决名字匹配问题。
(30)内存思考题1,代码如下:
1 void GetMemory(char *p) 2 { 3 p = (char *)malloc(100); 4 } 5 6 void Test() 7 { 8 char *str = NULL; 9 GetMemory(str); 10 strcpy(str, "hello world"); 11 printf(str); 12 }
请问运行Test 函数会有什么样的结果?
程序崩溃。因为GetMemory 并不能传递动态内存,
Test 函数中的 str 一直都是 NULL。
执行strcpy(str, "hello world"); 将使程序崩溃。
(31)内存思考题2,代码如下:
1 char *GetMemory(void) 2 { 3 char p[] = "hello world"; 4 return p; 5 } 6 7 void Test(void) 8 { 9 char *str = NULL; 10 str = GetMemory(); 11 printf(str); 12 }
请问运行Test 函数会有什么样的结果?
可能是乱码。
因为GetMemory 返回的是指向“栈内存”的指针,该指针的地址不是 NULL,
但其原来的内容已经被清除,新内容不可知。
(32)内存思考题3,代码如下:
1 void GetMemory(char **p, int num) 2 { 3 *p = (char *)malloc(num); 4 } 5 6 void Test(void) 7 { 8 char *str = NULL; 9 GetMemory(&str, 100); 10 strcpy(str, "hello"); 11 printf(str); 12 }
请问运行Test 函数会有什么样的结果?
(1)能够输出hello
(2)内存泄漏
(33)内存思考题4,代码如下:
1 void Test(void) 2 { 3 char *str = (char *) malloc(100); 4 strcpy(str, "hello"); 5 free(str); 6 if(str != NULL) 7 { 8 strcpy(str, "world"); 9 printf(str); 10 } 11 }
请问运行Test 函数会有什么样的结果?
篡改动态内存区的内容,后果难以预料,非常危险。
因为free(str);之后,str 成为野指针,if (str != NULL)判断语句不起作用。
(34)全局变量和局部变量有什么区别?怎么实现的?操作系统和编译器是怎么知道的?
全局变量的生命周期是整个程序运行期间,而局部变量的生命周期则是局部函数或过程调用的时间段。
其实现是由编译器在编译时采用不同内存分配方法。
全局变量在main函数调用前,就开始分配,如果是静态变量则是在main函数前就已经初始化了。而局部变量则是在用户栈中动态分配的。
(35)C语言文件读写程序
1 #include "stdio.h" 2 #include "stdlib.h" 3 4 void main() 5 { 6 FILE *fp = NULL; 7 char filename[10] = "test.txt"; 8 scanf("%s", filename); 9 if ((fp = fopen(filename, "w")) == NULL) 10 { 11 printf("cann't open file\n"); 12 exit(0); 13 } 14 char ch = getchar(); 15 while (ch != '#') 16 { 17 fputc(ch, fp); 18 putchar(ch); 19 ch = getchar(); 20 } 21 22 fclose(fp); 23 }
(36)数组越界问题
下面这个程序执行后会有什么错误或者效果:
1 #define MAX 255 2 3 void main() 4 { 5 unsigned char A[MAX], i; 6 for (i = 0; i <= MAX; i++) 7 A[i] = i; 8 }
解答:
MAX = 255,数组A的下标范围为: 0..MAX-1,这是其一。
其二,当i循环到255时,循环内执行: A[255] = 255;
这句本身没有问题,但是返回for (i = 0; i <= MAX; i++) 语句时,
由于unsigned char的取值范围在(0..255),i++以后i又为0了,无限循环下去。
备注:char类型为一个字节,取值范围是[-128,127],unsigned char [0 ,255]
(37)C 和 C++ 中的 struct 有什么不同?
C 和 C++中struct的主要区别是 C 中的struct不可以含有成员函数,而 C++ 中的struct可以。
(38)C++中的struct 与 class 有什么不同?
C++中的struct 和 class的区别是默认成员访问权限不同。struct的默认访问权限是public,而class的默认访问权限是private。
(39)Heap与Stack的区别:
Heap是堆,Stack是栈。
Stack的空间由操作系统自动分配/释放,Heap上的空间手动分配/释放。
Stack空间有限,Heap是很大的自由存储区
C中的malloc函数分配的内存空间即在堆上。C++中对应的是new操作符。
程序在编译期对变量和函数分配内存都在栈上进行,且程序运行过程中函数调用时参数的传递也在栈上进行。
(40)请定义一个宏,比较两个数的a、b的大小,不能使用大于、小于、if语句。
1 #include<iostream> 2 using namespace std; 3 4 #define max0(a, b) (((a)-(b)) & (1<<31)) == 1 ? (a) : (b); 5 #define max1(a, b) ((((a)-(b)) & (1<<31)) ? (b) : (a)) 6 #define max2(a, b) ((((long)((a)-(b))) & 0x80000000) ? (b) : (a)) 7 #define max3(a, b) (((abs((a)-(b))) == ((a)-(b))) ? (a) : (b)) 8 9 void main() 10 { 11 int n0 = max0(5, 6); 12 cout << n0 << endl; 13 14 int n1 = max1(10, 26); 15 cout << n1 << endl; 16 17 int n2 = max2(5, 1); 18 cout << n2 << endl; 19 20 int n3 = max3(100, 47); 21 cout << n3 << endl; 22 23 system("pause"); 24 } 25 // run out 26 /* 27 6 28 26 29 5 30 100 31 请按任意键继续. . . 32 */
(41)交换两个数的方法。参见随笔《交换两个数的方法》
(42)设置或者清除某位。参见随笔《面试题(1)》
(43)求最大字段和。参见随笔《面试题(1)》
(44)字节对齐。参见随笔《面试题(1)》
(45)大小端判断。参见随笔《面试题(1)》
(46)查找数组中的最小和次小项。参见随笔《面试题(1)》
(47)用预处理指令#define 声明一个常数,用以表明1年中有多少秒(忽略闰年问题)。
1 #define SECONDS_PER_YEAR (60 * 60 * 24 * 365)UL
分析以下几点:
1、 #define 语法的基本知识(例如:不能以分号结束,括号的使用等等)。
2、 懂得预处理器将为你计算常数表达式的值,因此,直接写出你是如何计算一年中有多少秒而不是计算出实际的值,是更清晰而没有代价的。
3、 意识到这个表达式将使一个16位机的整型数溢出-因此要用到长整型符号L,告诉编译器这个常数是的长整型数。
4、 如果你在你的表达式中用到UL(表示无符号长整型),那么你有了一个好的起点。记住,第一印象很重要。
(48)用变量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 )
1 a) int a; // An integer 2 b) int *a; // A pointer to an integer 3 c) int **a; // A pointer to a pointer to an integer 4 d) int a[10]; // An array of 10 integers 5 e) int *a[10]; // An array of 10 pointers to integers 6 f) int (*a)[10]; // A pointer to an array of 10 integers 7 g) int (*a)(int); 8 // A pointer to a function a that takes an integer argument and returns an integer 9 h) int (*a[10])(int); 10 // An array of 10 pointers to functions that take an integer argument and return an integer
(49)关键字typedef 与 宏的区别。参见随笔《typedef关键字》
(50)如何引用一个已经定义过的全局变量?
可以用引用头文件的方式,或者可以用extern关键字。
如果用引用头文件方式来引用某个在头文件中声明的全局变量,假定你将那个变量写错了,那么在编译期间会报错。
如果你用extern方式引用时,假定你犯了同样的错误,那么在编译期间不会报错,而在链接期间报错。
(51)全局变量和局部变量在内存中有什么区别?
全局变量储存在静态数据区,局部变量在堆栈中。
(52)零值比较
(53)sizeof的使用
(54)堆栈溢出一般是由什么原因导致的?
没有回收垃圾资源。
(55)什么函数不能声明为虚函数?
constructor构造函数
(56)不能做switch()的参数类型是:
switch的参数不能为实型。
(57)局部变量能否和全局变量重名?
能,局部会屏蔽全局。要用全局变量,需要使用"::"
局部变量可以与全局变量同名,在函数内引用这个变量时,会用到同名的局部变量,而不会用到全局变量。
对于有些编译器而言,在同一个函数内可以定义多个同名的局部变量,比如在两个循环体内都定义一个同名的局部变量,而那个局部变量的作用域就在那个循环体内。
(58)全局变量可不可以定义在可被多个.C文件包含的头文件中?为什么?
可以。在不同的C文件中以static形式来声明同名全局变量。
可以在不同的C文件中声明同名的全局变量,前提是其中只能有一个C文件中对此变量赋初值,此时链接不会出错。
(59)do……while和while……do有什么区别?
前一个循环一遍再判断,后一个判断以后再循环。
(60)语句for( ;1 ;)有什么问题?它是什么意思?
无限循环,和while(1)相同。
(61)写出下面程序的输出内容
1 #include <stdio.h> 2 #include <stdlib.h> 3 void main() 4 { 5 int a,b,c,d; 6 a = 10; 7 b = a++; 8 c = ++a; 9 d = 10 * a++; 10 printf("b,c,d:%d,%d,%d\n", b, c, d); 11 system("pause"); 12 } 13 //run out: 14 /* 15 b,c,d:10,12,120 16 请按任意键继续. . . 17 */
(62)写出下列代码的输出内容
1 #include <stdio.h> 2 #include <stdlib.h> 3 4 typedef union 5 { 6 long i; 7 int k[5]; 8 char c; 9 } DATE; 10 11 struct data 12 { 13 int cat; 14 DATE cow; 15 double dog; 16 }too; 17 18 void main() 19 { 20 DATE max; 21 printf("%d, %d\n", sizeof(long), (sizeof(struct data) + sizeof(max))); 22 system("pause"); 23 } 24 // run out: 25 /* 26 4, 52 27 请按任意键继续. . . 28 */
分析:DATE是一个union, 变量共用空间,里面最大的变量类型是int[5], 占用20个字节,所以它的大小是20。
data是一个struct, 每个变量分开占用空间. 依次为int(4)+ DATE(20)+ double(8) = 32
所以结果是 20 + 32 = 52
(63)写出下列代码的输出内容
1 #include <stdio.h> 2 #include <stdlib.h> 3 4 int inc(int a) 5 { 6 return (++a); 7 } 8 int multi(int* a, int* b, int* c) 9 { 10 return (*c = *a * *b); 11 } 12 13 typedef int(*FUNC1) (int in); 14 typedef int(*FUNC2) (int*, int*, int*); 15 16 void show(FUNC2 fun, int arg1, int*arg2) 17 { 18 FUNC1 p = &inc; 19 int temp = p(arg1); 20 fun(&temp, &arg1, arg2); 21 printf("%d\n", *arg2); 22 } 23 24 void main() 25 { 26 int a; 27 show(multi, 10, &a); 28 system("pause"); 29 } 30 // run out: 31 /* 32 110 33 请按任意键继续. . . 34 */
(64)找出下面代码中的所以错误(下面为正常标准程序)
说明:以下代码是把一个字符串倒序,如“abcd”倒序后变为“dcba”
1 #include <stdio.h> 2 #include <stdlib.h> 3 #include <string.h> 4 5 void main() 6 { 7 char* src = "hello,world"; 8 int len = strlen(src); 9 char* dest = (char*)malloc(len + 1); // 要为\0分配一个空间 10 char* d = dest; 11 char* s = &src[len-1]; // 指向最后一个字符 12 while ( len-- != 0 ) 13 *d++ = *s--; 14 *d = 0; // 尾部要加\0 15 printf("%s\n", dest); 16 free(dest);// 使用完,应当释放空间,以免造成内存泄露 17 18 system("pause"); 19 }
(65)对于一个频繁使用的短小函数,在C语言中应用什么实现,在C++中应用什么实现?
C语言用宏定义,C++用inline实现。
(66)下面的程序是否有错误,如果有错,请说明原因。
1 char* const pszHelp = "hello"; 2 pszHelp[0] = 'a';
因为pszHelp指向一个常量字符串,所以根本不允许修改字符串内容。除非使用一个字符数组。
(67)什么是抽象类
包含抽象函数的类是抽象类,满足virtual fun() = 0; 的语法的函数是抽象函数。主要用于提供接口。
(68)什么时候需要使用虚析构函数
一般情况下,类的析构函数都定义成虚函数,主要是考虑在使用基类指针操作派生类对象时保证类的析构顺序。
(69)请指出下面代码存在的潜在问题
1 class CC 2 { 3 int* m_pCount; 4 5 public: 6 void clear() 7 { 8 if ( m_pCount ) 9 delete m_pCount; 10 } 11 12 CC() 13 { 14 m_pCount = new int; 15 } 16 17 ~CC() 18 { 19 clear(); 20 } 21 };
主要存在的问题是clear函数在delete m_pCount; 后并没有置指针为空( m_pCount = NULL),这样当第二次调用 clear 时,会出现问题。
(70)请写出下列程序运行的结果
1 #include <iostream> 2 using namespace std; 3 4 class A 5 { 6 public: 7 virtual void func() { cout << "I am in base" << endl; }; 8 }; 9 10 class B : public A 11 { 12 public: 13 virtual void func() { cout << "I am in derived" << endl; } 14 }; 15 16 void main() 17 { 18 B* bb = new B; 19 bb->func(); 20 A* aa = (A*)bb; 21 aa->func(); 22 system("pause"); 23 } 24 // run out 25 /* 26 I am in derived 27 I am in derived 28 请按任意键继续. . . 29 */
(71)Debug版本中经常使用ASSERT进行断言,在Release版本中有一个起同样作用的函数,请说明。
VERIFY,而且要注意ASSERT中的语句在Release版本中会忽略。
(72)描述内存分配方式以及它们的区别?
1) 从静态存储区域分配。内存在程序编译的时候就已经分配好,这块内存在程序的整个运行期间都存在。例如全局变量,static 变量。
2) 在栈上创建。在执行函数时,函数内局部变量的存储单元都可以在栈上创建,函数执行结束时这些存储单元自动被释放。栈内存分配运算内置于处理器的指令集。
3) 从堆上分配,亦称动态内存分配。程序在运行的时候用malloc 或new 申请任意多少的内存,程序员自己负责在何时用free 或delete 释放内存。
动态内存的生存期由程序员决定,使用非常灵活。
(73)C++四种类型转换方式
1、static_cast 2、const_cast 3、dynamic_cast 4、reinterpret_cast.
关于各种方式区别参见随笔《C++类型转换》
(74)类成员函数的重载、覆盖和隐藏区别?
a、成员函数被重载的特征:
1、相同的范围(在同一个类中);
2、函数名字相同;
3、参数不同;
4、virtual 关键字可有可无。
b、覆盖是指派生类函数覆盖基类函数,特征是:
1、不同的范围(分别位于派生类与基类);
2、函数名字相同;
3、参数相同;
4、基类函数必须有virtual 关键字。
c、“隐藏”是指派生类的函数屏蔽了与其同名的基类函数,规则如下:
1、如果派生类的函数与基类的函数同名,但是参数不同。此时,不论有无virtual关键字,基类的函数将被隐藏(注意别与重载混淆)。
2、如果派生类的函数与基类的函数同名,并且参数也相同,但是基类函数没有virtual关键字。此时,基类的函数被隐藏(注意别与覆盖混淆)。
三者关系总结:
具体代码可参见随笔《 重载 覆盖 隐藏 》
(75)如何打印出当前源文件的文件名以及源文件的当前行号?
cout << __FILE__ ;
cout << __LINE__ ;
__FILE__和__LINE__是系统预定义宏,这种宏并不是在某个文件中定义的,而是由编译器定义的。
(76)main 函数执行以前,还会执行什么代码?
全局对象的构造函数会在main 函数之前执行。
(77)main 主函数执行完毕后,是否可能会再执行一段代码,给出说明?
可以,可以用_onexit 注册一个函数,它会在main 之后执行。示例代码如下:
1 #include <stdio.h> 2 #include <stdlib.h> 3 4 int fn1() 5 { 6 printf( "next. " ); 7 system("pause"); 8 return 0; 9 } 10 11 int fn2() 12 { 13 printf( "executed " ); 14 return 0; 15 } 16 17 int fn3() 18 { 19 printf( "is " ); 20 return 0; 21 } 22 23 int fn4() 24 { 25 printf( "This " ); 26 return 0; 27 } 28 29 void main() 30 { 31 _onexit( fn1 ); 32 _onexit( fn2 ); 33 _onexit( fn3 ); 34 _onexit( fn4 ); 35 printf( "This is executed first. " ); 36 system("pause"); 37 } 38 // run out 39 /* 40 This is executed first. 请按任意键继续. . . 41 This is executed next. 请按任意键继续. . . 42 */
(78)如何判断一段程序是由C 编译程序还是由C++编译程序编译的?
1 #ifdef __cplusplus 2 cout << "c++"; 3 #else 4 cout << "c"; 5 #endif
(79)写出下列程序的输出内容
1 #include <stdio.h> 2 #include <stdlib.h> 3 void main() 4 { 5 int x = 3, y, z; 6 x *= (y = z = 4); 7 printf("x = %d\n", x); 8 z = 2; 9 x = (y = z); 10 printf("x = %d\n", x); 11 x = (y == z); 12 printf("x = %d\n", x); 13 system("pause"); 14 } 15 16 //Out put : 17 /* 18 x = 12 19 x = 2 20 x = 1 21 请按任意键继续. . . 22 */
(80)写出下列程序的输出内容
1 #include<iostream> 2 using namespace std; 3 4 class Test 5 { 6 private: 7 int value; 8 9 public: 10 Test(int x = 0) : value(x) 11 {} 12 ~Test() 13 {} 14 operator int() 15 { 16 return value; 17 } 18 operator bool() 19 { 20 return value > 0; 21 } 22 }; 23 24 void main() 25 { 26 Test t1(1); 27 28 if (t1) 29 { 30 cout << "if(t1)" << endl; 31 } 32 33 switch (t1.operator int()) 34 { 35 case 1: 36 cout << "switch(t1)" << endl; 37 break; 38 }; 39 40 system("pause"); 41 } 42 43 //run out: 44 /* 45 if(t1) 46 switch(t1) 47 请按任意键继续. . . 48 */
(81)写出下列程序的输出内容
1 #include<iostream> 2 #include<cstring> 3 using namespace std; 4 5 void main() 6 { 7 char ch[] = {"abcd"}; 8 char *r = ch + 1; 9 ch[0] = (*r++) + 2; 10 cout << "第一部分结果:" << endl; 11 cout << sizeof(ch) << endl; 12 cout << sizeof(r) <<endl; 13 cout << sizeof(*r) <<endl; 14 cout << ch << endl; 15 cout << *r << endl; 16 cout << ch[0] << endl; 17 cout << endl; 18 19 20 char *ptr = "abcdef"; 21 cout << ptr[3] << endl << endl; 22 23 cout << "第二部分结果:" << endl; 24 // A B C D 25 char *str[] = {"Welcome","To","Fortemedia","Nanjing"}; 26 char **p = str + 1; 27 //执行结束 p指向B 28 str[0] = (*p++) + 2; 29 //执行结束 p指向C 但是str[0]指向D后面的元素,因此内容为空 30 str[1] = *(p+1); 31 //执行结束 p指向C不变 str[1]指向p后一个元素的地址,即就是D 32 str[2] = p[1] + 3; 33 //p是二级指针,此时p[1]指向D p[1]+3即就是字符串元素的第四个字符,即‘j’ str[2]='jing' 34 str[3] = p[0] + (str[2] - str[1]); 35 //str[2] - str[1] = 3, 而p[0]指向的是‘j’的地址,所以str[4] = 'g' 36 cout << sizeof(str) << endl; // 16 37 cout << p << " :: " << sizeof(p) << endl; // 4 38 cout << *p << " :: " << sizeof(*p) << endl; // jing::4 39 cout << **p << " :: " << sizeof(**p) << endl; // j::1 40 cout << str[0] << endl; 41 cout << str[1] << endl; // Nanjing 42 cout << str[2] << endl; // jing 43 cout << str[3] << endl; // g 44 45 cout << str << endl; 46 system("pause"); 47 } 48 //run out: 49 /* 50 第一部分结果: 51 5 52 4 53 1 54 dbcd 55 c 56 d 57 58 d 59 60 第二部分结果: 61 16 62 0018F83C :: 4 63 jing :: 4 64 j :: 1 65 66 Nanjing 67 jing 68 g 69 0018F834 70 请按任意键继续. . . 71 */
(82)C++中虚函数和纯虚函数的区别?
1、声明的差异。纯虚函数的声明除过像虚函数加关键字virtual而外,还必须加 = 0;
声明为虚函数的作用是为了能让这个函数在它的派生类里面被覆盖或隐藏,这样编译器就可以使用后期绑定来达到多态性。
声明纯虚函数,有一种接口的规范作用。派生类必须且只能原样实现。
2、定义的差异。虚函数在基类中必须实现(哪怕空操作),派生类里面也可以不覆盖或隐藏。但纯虚函数必须在派生类里面去实现。
3、虚基类或抽象类。带纯虚函数的类叫做虚基类,或抽象类。这种基类不能直接声明对象,只能被继承,并且只有在实现其纯虚函数后才能使用。
(83)什么时候需要“引用”?
流操作符<< 和 >>、赋值操作符=的返回值、拷贝构造函数的参数、赋值操作符=的参数、其它情况都推荐使用引用。
(84)结构与联合的区别?
1. 结构和联合都是由多个不同的数据类型成员组成。
但在任何同一时刻, 联合中只存放了一个被选中的成员(所有成员共用一块地址空间), 而结构的所有成员都存在(不同成员的存放地址不同)。
2. 对于联合的不同成员赋值, 将会对其它成员重写, 原来成员的值就不存在了, 而对于结构的不同成员赋值是互不影响的。
(85)下列关于联合的程序输出
1 #include <stdio.h> 2 #include <stdlib.h> 3 4 union 5 { 6 int i; 7 char x[2]; 8 } a; 9 10 void main() 11 { 12 a.x[0] = 10; 13 a.x[1] = 1; 14 printf("%d", a.i); // 266 15 system("pause"); 16 }
低位低地址,高位高地址,内存占用情况是Ox010A
(86)New delete 与 malloc free 的联系与区别
都是在堆(heap)上进行动态的内存操作。用malloc函数需要指定内存分配的字节数并且不能初始化对象,new 会自动调用对象的构造函数。
delete 会调用对象的destructor,而free不会调用对象的destructor。
关于new 和 delete详情内容请参见随笔《new 与 delete》
(87)C++是不是类型安全的?
不是。两个不同类型的指针之间可以强制转换(用reinterpret cast)。C#是类型安全的。
(88)当一个类A中没有生命任何成员变量与成员函数,这时sizeof(A)的值是多少,如果不是零,请解释一下编译器为什么没有让它为零。
肯定不是零。举个反例,如果是零的话,声明一个class A[10]对象数组,而每一个对象占用的空间是零,这时就没办法区分A[0],A[1]…了。
(89)简述数组与指针的区别?
数组要么在静态存储区被创建(如全局数组),要么在栈上被创建。指针可以随时指向任意类型的内存块。
(90)strcpy与memcpy的区别
1、复制的内容不同。strcpy仅仅复制字符串,而memcpy可以复制任何类型的数据内容
2、复制的方法不同。strcpy不需要指定长度,它遇到字符串结束符"\0"便结束。memcpy则是根据其第三个参数决定复制的长度。
3、用途不同。通常在复制字符串时用strcpy,而复制其它类型数据时一般用memcpy()
(91)写出下列程序的输出内容:
1 #include<iostream> 2 using namespace std; 3 4 void main() 5 { 6 unsigned short int i = 0; 7 unsigned char ii = 255; 8 int j = 8, p, q; 9 10 i = i - 1; 11 ii = ii + 1; 12 p = j << 1; 13 q = j >> 1; 14 15 cout << i << endl; // 65535 16 cout << ii << endl; 17 cout << p << endl; // 16 18 cout << q << endl; // 4 19 20 system("pause"); 21 }
(92)写出下列程序的输出内容:
1 #include<iostream> 2 using namespace std; 3 4 class Base 5 { 6 public: 7 virtual void fun1() 8 { 9 cout << "Base :: fun1()" << endl; 10 } 11 virtual void fun2() 12 { 13 cout << "Base :: fun2()" << endl; 14 } 15 virtual void fun3() 16 { 17 cout << "Base :: fun3()" << endl; 18 } 19 20 private: 21 int num1; 22 int num2; 23 }; 24 25 typedef void (*Fun)(); 26 27 void main() 28 { 29 Base b; 30 Fun pFun; 31 pFun = (Fun)*((int *)*(int *)(&b) + 0); 32 pFun(); 33 pFun = (Fun)*((int *)*(int *)(&b) + 1); 34 pFun(); 35 pFun = (Fun)*((int *)*(int *)(&b) + 2); 36 pFun(); 37 38 system("pause"); 39 } 40 // run out: 41 /* 42 Base :: fun1() 43 Base :: fun2() 44 Base :: fun3() 45 请按任意键继续. . . 46 */
分析:
typedef void( *Fun )( void ); // Fun定义为一个没有参数,返回void类型的函数指针
针对 *( ( int* ) * ( int* )( &b ) + i ); 这一段:
(int*)* 相当于没有进行任何操作,所以等同于:
*( ( int* )( &b ) + i )
这里先取b的地址,然后把地址转换成int*,之后+i是指针算术,也就是在b的地址上加一个int的长度。
最后,前面的*是解指针,这段最后返回的是“b的地址 + i个int长度”的地址值。
最前面的(Fun)是强制把“b的地址 + i个int长度”的地址值转换为一个“没有参数,返回void类型的函数指针”,
所以pFun就是一个函数指针,其指向的位置从一开始的b的地址,每次循环加一个int类型的长度。
然后,我们来看实际情况:
( Fun )*( ( int* ) * ( int* )( &b ) + i );
这里*( ( int* ) * ( int* )( &b ) + i )最前面的*是其前面结果进行解指针,也就取b的地址+i个int长度的这个地址的值。
并把它转换为Fun类型,也就是一个没有参数,返回void类型的函数指针,所以最后得到的就是一个函数指针。
再来看循环,循环3次,pFun变量分别被赋了3次值,每次都是一个函数指针。
由于Base类型中有virtual虚函数,所以b的地址指向的是b的vtbl(虚函数表),vtbl虚函数表可以看作是一个保存了函数指针的数组,
每个元素就是一个int长度,在vtbl虚函数表中Base::fun1, Base::fun2, Base::fun3是按照如上顺序排列的,所以第一次循环指向Base::fun1。
那么后两次就指向Base::fun2 和 Base::fun3了。编码以明志,调试而致远。调试结果如下:
至于为什么是按照这样的顺序排列的,因为其声明顺序。
(93)写出下列程序的输出内容
1 #include <iostream> 2 using namespace std; 3 4 class A 5 { 6 virtual void g() 7 { 8 cout << "A::g" << endl; 9 } 10 private: 11 virtual void f() 12 { 13 cout << "A::f" << endl; 14 } 15 }; 16 17 class B : public A 18 { 19 void g() 20 { 21 cout << "B::g" << endl; 22 } 23 virtual void h() 24 { 25 cout << "B::h" << endl; 26 } 27 }; 28 29 typedef void( *Fun )( void ); 30 31 int main() 32 { 33 B b; 34 Fun pFun; 35 for (int i = 0 ; i < 3; ++i) 36 { 37 pFun = ( Fun )*( ( int* ) * ( int* )( &b ) + i ); 38 pFun(); 39 } 40 41 system("pause"); 42 } 43 */ 44 // run out: 45 /* 46 B::g 47 A::f 48 B::h 49 */
(94)在什么时候需要使用“常引用”?
如果既要利用引用提高程序的效率,又要保护传递给函数的数据不在函数中被改变,就应使用常引用。
常引用声明方式:const 类型标识符 & 引用名 = 目标变量名;
(95)流操作符重载返回值申明为“引用”的作用:
流操作符< <和>>,这两个操作符常常希望被连续使用,例如:cout << "hello" << endl; 因此这两个操作符的返回值应该是一个仍然支持这两个操作符的流引用。
可选的其它方案包括:返回一个流对象和返回一个流对象指针。
但是对于返回一个流对象,程序必须重新(拷贝)构造一个新的流对象,也就是说,连续的两个<<操作符实际上是针对不同对象的!
这无法让人接受。对于返回一个流指针则不能连续使用<<操作符。因此,返回一个流对象引用是惟一选择。
这个唯一选择很关键,它说明了引用的重要性以及无可替代性,也许这就是C++语言中引入引用这个概念的原因吧。赋值操作符=。
这个操作符象流操作符一样,是可以连续使用的,例如:x = j = 10; 或者 (x = 10) = 100;赋值操作符的返回值必须是一个左值,以便可以被继续赋值。
因此引用成了这个操作符的惟一返回值选择。
(96)系统为变量赋的默认值打印结果:(系统:win32 + VS2010)
1 #include <iostream> 2 using namespace std; 3 4 static int g_sn; 5 int g_n; 6 7 void func() 8 { 9 static int inner_sn; 10 int inner_n; 11 12 cout << "inner_sn :: " << inner_sn << endl; 13 // cout << inner_n << endl; 打印崩溃!系统不会自动为局部变量赋初值。 14 } 15 16 class A 17 { 18 private: 19 char m_ch; 20 int m_n; 21 bool m_b; 22 short m_sh; 23 long m_lg; 24 float m_fl; 25 double m_d; 26 int* m_pN; 27 28 public: 29 void printDefaultValue() 30 { 31 cout << "char :: " << m_ch << endl; 32 cout << "int :: " << m_n << endl; 33 cout << "bool :: " << m_b << endl; 34 cout << "short :: " << m_sh << endl; 35 cout << "long :: " << m_lg << endl; 36 cout << "float :: " << m_fl << endl; 37 cout << "double :: " << m_d << endl; 38 cout << "int* :: " << m_pN << endl; 39 } 40 }; 41 42 void main() 43 { 44 cout << "g_sn :: " << g_sn << endl; 45 cout << "g_n :: " << g_n << endl; 46 func(); 47 A objA; 48 objA.printDefaultValue(); 49 system("pause"); 50 } 51 52 // run out: 53 /* 54 g_sn :: 0 55 g_n :: 0 56 inner_sn :: 0 57 char :: 58 int :: -858993460 59 bool :: 204 60 short :: -13108 61 long :: -858993460 62 float :: -1.07374e+008 63 double :: -9.25596e+061 64 int* :: CCCCCCCC 65 请按任意键继续. . . 66 */
注意:系统不会为局部变量赋默认值。因此直接打印局部变量的默认值导致崩溃!
(97)有哪几种情况只能用intialization list 而不能用assignment?
类中含有const、reference 成员变量;基类的构造函数都需要初始化表。
(98)面向对象的三个基本特征,并简单叙述之?
1、封装:类。将客观事物抽象成类,每个类对自身的数据和方法实行受权限访问protection (private, protected, public)。
2、继承:广义的继承有三种实现形式:
实现继承(指使用基类的属性和方法而无需额外编码的能力)、
可视继承(子窗体使用父窗体的外观和实现代码)、
接口继承(仅使用属性和方法,实现滞后到子类实现)。前两种(类继承)和后一种(对象组合 => 接口继承以及纯虚函数)构成了功能复用的两种方式。
3、多态:是将父对象设置成为和一个或更多的他的子对象相等的技术,赋值之后,父对象就可以根据当前赋值给它的子对象的特性以不同的方式运作。
简单的说,就是一句话:允许将子类类型的指针赋值给父类类型的指针。
(99)找出下列代码的问题:
1 void test() 2 { 3 char string[10], str1[10]; 4 int i; 5 for (i = 0; i < 10; i++) 6 { 7 str1[i] = 'a'; 8 } 9 strcpy(string, str1); 10 }
如果面试者指出字符数组str1不能在数组内结束可以给3分;
如果面试者指出strcpy(string, str1)调用使得从str1内存起复制到string内存起所复制的字节数具有不确定性可以给7分,在此基础上指出库函数strcpy工作方式的给10 分;
str1不能在数组内结束:因为str1的存储为:{a, a, a, a, a, a, a, a, a, a},没有'\0'(字符串结束符),所以不能结束。
strcpy( char *s1, char *s2)他的工作原理:
扫描s2指向的内存,逐个字符付到s1所指向的内存,直到碰到'\0',因为str1结尾没有'\0'。所以具有不确定性,不知道他后面还会赋值什么东东。
正确程序如下:
1 void test() 2 { 3 char string[10], str1[10]; 4 int i; 5 for (i = 0; i < 9; i++) 6 { 7 str1[i] = 'a' + i; // 把abcdefghi赋值给字符数组 8 } 9 str1[i] = '\0'; // 加上结束符 10 strcpy(string, str1); 11 }
(100)C++中为什么用模板类?
1、可用来创建动态增长和减小的数据结构。
2、它是类型无关的,因此具有很高的可复用性。
3、它在编译时而不是运行时检查数据类型,保证了类型安全。
4、它是平台无关的,可移植性。
5、可用于基本数据类型。
(101)MFC中CString是类型安全类么?
不是,其它数据类型转换到CString可以使用CString的成员函数Format来转换
(102)函数模板与类模板有什么区别?
函数模板的实例化是由编译程序在处理函数调用时自动完成的,而类模板的实例化必须由程序员在程序中显式地指定。
(103)动态连接库的两种方式?
调用一个DLL中的函数有两种方法:
1、载入时动态链接(load-time dynamic linking),模块非常明确调用某个导出函数,使得他们就像本地函数一样。
这需要链接时链接那些函数所在DLL的导入库,导入库向系统提供了载入DLL时所需的信息及DLL函数定位。
2、运行时动态链接(run-time dynamic linking),运行时可以通过LoadLibrary或LoadLibraryEx函数载入DLL。
DLL载入后,模块可以通过调用GetProcAddress获取DLL函数的出口地址,然后就可以通过返回的函数指针调用DLL函数了。
(104)类中函数详解(构造函数、析构函数、拷贝构造函数、赋值构造函数)。请参见随笔《类中函数》
(105)写出下列程序编译错误问题的原因
程序代码如下:
1 #define charPtr char * 2 3 void main() 4 { 5 typedef char* charPtrDef; 6 char str[4] = {"abc"}; 7 const char* p1 = str; 8 const charPtr p2 = str; 9 const charPtrDef p3 = str; 10 char * const p4 = str; 11 const char * const p5 = str; 12 p1++; 13 p2++; // OK 宏定义仅仅只是文本替换,其本质与p1相同。可以编译通过 14 // *p2 = 'A'; // error! const 修饰的是p2指向的内容,只读不允许修改 15 // p3++; // error! const修饰的是p3指针,只读不允许修改 16 *p3 = 'B'; // OK 因此指针指向的内容可以修改 17 // p4++; // error! const修饰的是p4指针,只读不允许修改 18 *p4 = 'C'; // OK 因此指针指向的内容可以修改 19 // p5++; // error! *号后的const修饰的是p5指针,只读不允许修改 20 // *p5 = 'D'; // error! *号前的const修饰的是p5指针指向的内容,只读不允许修改 21 }
注释为原因分析。
(106)“引用”与多态的关系?
引用是除指针外另一个可以产生多态效果的手段。这意味着,一个基类的引用可以指向它的派生类实例。
(107)多态的作用?
主要是两个:
1、 隐藏实现细节,使得代码能够模块化;扩展代码模块,实现代码重用;
2、 接口重用:为了类在继承和派生的时候,保证使用家族中任一类实例的某一属性时的正确调用。
(108)STL中容器map,map有几种赋值方式?每种方式有何区别?
详情请参见随笔《STL容器之map 》
(109)STL中容器list,使用list容器的merge方法需要注意什么?
详情请参见随笔《STL容器之list 》
(110)介于vector和list之间的一种容器deque,请参见随笔《STL容器之deque》
(111)C++语言中如何访问一个类私有成员变量?
两种方式。第一,接口,利用访问成员变量相应的set/get公共成员函数;第二,友元,友元类和友元函数。
(112)由于本文篇幅太长,编辑后保存太慢。下面续《 C++笔试题2(基础题) 》
Good Good Study, Day Day Up.
顺序 选择 循环 总结