C/C++面试题

来源:互联网 发布:申请网络记者 编辑:程序博客网 时间:2024/06/08 13:31

1.malloc和new有什么区别?

1)malloc是C/C++的库函数,new是C++的运算符,两者都能够用来申请动态内存。

2)new不仅会分配内存,还会调用构造函数,而malloc只分配内存,不进行成员初始化的工作。

3)new出来的指针是带类型信息的,而malloc返回的是void指针。

4)内存泄漏对new和malloc都可以检测出来,但是new可以指明文件哪一行,malloc没有这些信息。(注意:使用_CrtDumpMemoryLeaks函数检测,按F5,结果在Output窗口)


2.实现strcpy、memcpy、strcat、strstr函数。

1)strcpy函数

char *Strcpy(char *strDest, const char *strSrc){assert((strDest!=NULL)&&(strSrc!=NULL));char *address = strDest;while ((*strDest++ = *strSrc++) != '\0') NULL;return address;}
上面代码没有考虑重叠情况:

char str[10] = "abc";Strcpy(str + 1, str);
这段代码是造成死循环。

改成这样:

char *Strcpy(char *strDest, const char *strSrc){assert((strDest!=NULL)&&(strSrc!=NULL));char *address = strDest;memcpy(strDest, strSrc, strlen(strSrc) + 1);return address;}

注意:strlen返回的长度不包括'\0'

重新运行:

char str[10] = "abc";Strcpy(str + 1, str);cout << str << endl;
输出:

aabc

注意:

strcpy函数有返回值是为了实现链式操作:

int length=strlen(strcpy(str1,str2));

2)memcpy函数

考虑重叠情况的实现如下:

void *Memcpy(void *des, const void *src, size_t size){char *pdes = static_cast<char *>(des);const char *psrc = static_cast<const char*>(src);if (pdes > psrc && pdes < (psrc + size))  //地址重叠,从后面开始复制{for (size_t i = size - 1; i != -1; i--)  //注意:此处不能为i>=0pdes[i] = psrc[i];}else{for (size_t i = 0; i < size; i++) pdes[i] = psrc[i];}return pdes;}

注意:因为采用的是逐字节复制,所以采用char*来访问。

strcpy与memcpy的区别:

a)复制的内容不同。strcpy只能复制字符串,memcpy可以复制任意内容。

b)复制的方法不同。strcpy不需要指定长度,遇到'\0'结束。memcpy需要指定长度。

其实上面实现的是memmove函数,因为memcpy和memmove的唯一区别是,当内存发送局部重叠时,memmove保证拷贝安全,memcpy不保证拷贝安全。

3)strcat函数

char *strcat(char *strDest, const char *strSrc){assert((strDest != NULL) && (strSrc != NULL));char *address = strDest;while (*strDest != '\0') ++strDest;while (*strSrc != '\0') *strDest++ = *strSrc++;return address;}


4)strstr函数

功能:从一个字符串中,查找另一个字符串的位置(const char *)。

const char *Strstr(const char *src, const char *sub){if (src == NULL || sub == NULL) return src;const char *cp;const char *bp;while (*src != '\0'){cp = src;bp = sub;do{if (*bp == '\0') return src;} while (*bp++ == *cp++);++src;}return NULL;}


3.static和const的作用。

static的作用:

1)函数内的static变量在函数内部可以使用,函数退出时占用内存不被释放,再次进入函数时,static变量的值维持上次退出时的值,程序退出时内存被系统回收。

2)模块内的static全局变量可以被模块内的所有函数使用,占用内存在程序退出时被系统回收。

3)模块内的static函数只能被模块内的函数调用。

4)类里面的static成员变量与类绑定,类的所有对象对static成员变量只有一份拷贝。

5)类里面的static成员函数与类绑定,该函数不接收this指针,因此只能调用static成员变量。

const作用:

1)变量为const表示该变量只能被访问,不能被修改。

2)对于指针,const既可以修饰该指针不可以被修改,也可以修饰该指针所指对象不能被修改,也可以修饰两者都不可以被修改。

3)对于函数的输入参数,const表明在函数内部该变量不能被修改。

4)对于函数的返回参数,const表明该函数的返回值不能用作左值。

5)const可以修饰类成员函数,表明在该成员函数内,类的成员不能被修改。



4.static全局变量与普通全局变量区别?static函数与普通函数区别?

static全局变量和全局变量都存储在全局(静态)区,区别在于非静态全局变量的作用域是整个源程序,当一个源程序由多个源文件组成时,非静态全局变量在各个源文件中都有效(其他文件用extern声明后就可以使用该变量),而静态全局变量只在定义该变量的源文件有效。


5.sizeof和strlen的区别。

1)sizeof是操作符,strlen是函数

2)sizeof可以用类类型做参数,strlen只能用char*作参数,且必须是以'\0'结尾的。

3)大部分程序在编译时sizeof就被计算过了,因此可以用sizeof来定义数组维数,strlen要在运行的时候才能计算出来。

4)数组名做sizeof参数不退化,传递给strlen就退化为指针了。

什么意思呢?就是说数组名char a[10]="abc"作为sizeof的参数时,不退化成指针char *a,输出10,如果退化成char *,则求的是sizeof(char *),就输出4了。

而strlen(a)时,因为strlen的参数是char *类型,所以参数退化成指针char *了。

5)strlen计算的是字符串的长度,而sizeof计算的是字符指针占用空间大小。

#include <iostream>using namespace std;void func(char *str){cout << sizeof(str) << endl;cout << strlen(str) << endl;}int main(){char a[] = "123456789";cout << sizeof(a) << endl;cout << strlen(a) << endl;func(a);return 0;}//输出://10//9//4//9




6.内联函数与宏有什么区别?

1)宏在预编译展开,内联函数在编译时展开。

2)在编译的时候,内联函数可以被镶嵌到目标代码中,而宏只是简单的文本替换。

3)内联函数可以完成类型检测,语法检测等编译功能,宏不具有这样的功能。

4)宏不是函数,内联函数是函数。

5)宏在定义时要小心处理宏参数(把参数用括号括起来),否则容易出现二义性,而内联函数不会出现二义性。

补充:

优点:内联函数省去了函数调用的开销,提高函数执行效率。

缺点:使程序的总代码量增大,消耗更多的内存空间。


7.volatile有什么含义?

如果变量被声明为volatile,优化器在用到这个值时,必须每次小心地重新读取这个变量的值,而不是使用保存在寄存器里的备份。

用在以下几个地方:
1)中断程序修改的供其他程序检测的变量需要加上volatile。

2)多任务环境下各任务共享的标志位应该加上volatile。

3)存储器映射的硬件寄存器通常也要加上volatile。


8.什么情况只能初始化列表而不能赋值?

当类中含有const、reference成员变量和需要调用基类构造函数时。

注意:静态成员不能在类内部初始化,所以也不能在初始化成员列表中。


9.复制构造函数和赋值函数区别?

1)复制构造函数是用一个对象来初始化一块内存区域,即新对象的内存区;赋值函数就是对一个已经存在的对象进行赋值操作。

2)如果数据成员包含指针对象时,一般复制构造函数是复制指针对象,而赋值函数引用指针对象。

3)实现不一样。复制构造函数首先是构造函数,通过传进来的参数初始化产生一个对象。赋值函数则是把一个对象赋值给另一个对象。


10.什么是临时变量?临时变量在什么情况下产生?

临时变量是仅仅需要一小段时间的变量。

通常在以下两种情况下产生:
1)参数按值传递。

2)返回值按值传递。


11.私有继承和组合的区别:

相同点:都可以表示“有一个”关系

不同点:私有继承中派生类能够访问基类的protected成员,并且可以重写基类的虚函数,当基类是抽象类时也可以使用。组合不具有这些功能。


12.内联函数占不占用运行时间。

内联函数跟宏定义一样,在编译时直接进行代码替换。内联函数只是不消耗时间去调用,而不是不占用运行时间。


13.父类的析构函数为什么要定义为虚函数?

如果一个继承类对象经由基类指针被删除时,如果析构函数是非虚函数,则只能调用基类的析构函数,从而使对象的继承类成分没被销毁。而如果析构函数是虚函数,则可以销毁整个对象。


14.override(重写/覆盖)和overload(重载)的区别

1)override的方法名,返回值,参数相同,voerload的参数类型,个数,顺序至少有一个不同。

2)override存在于子类和父类之间,overload存在于子类和父类,同类之间。

3)override时子类方法不能缩小父类方法的访问权限。

4)重写的方法抛出的异常必须和被重写的方法抛出的异常一致,或者是其子类。

5)子类static静态方法隐藏父类的static静态方法是可以的,但不晓得是不是override。(有人说只是隐藏)


15.指针和引用的区别
1)初始化要求不同。引用定义时必须初始化,指针定义时可以不初始化。
2)可修改性不同。引用一旦被初始化为指向一个对象,就不能修改为其他对象的引用。而指针可以改变为指向另一个对象。
3)指向NULL不同。引用不能指向空值,指针可以指向NULL。
4)测试需要的区别。因为引用不会指向空值,因此使用引用前不需要测试其是否指向空值,但是指针可以指向空值,因此使用指针经常要进行测试。
注意:引用比指针安全的原因是,不存在空引用,且一旦引用被初始化为指向一个对象,就不能改变为另一个对象的引用。


16.为什么析构函数不能抛出异常

1)如果析构函数抛出异常,则异常后面的程序不会执行。如果析构函数在异常点之后执行了一些必要的动作比如释放资源,这些动作不会执行,会造成资源泄露。

2)通常异常发生时,C++机制会调用已经构造对象的析构函数来释放资源,此时若析构函数也抛出异常,则前面一个异常未处理,又有新的异常,会造成程序崩溃问题。

第一点有时候不能保证,可以照下面方法,不让异常跑出析构函数。

~ClassName(){  try{      do_something();  }  catch(){  //这里可以什么都不做,只是保证catch块的程序抛出的异常不会被扔出析构函数之外。   }}

17.几种常见的库函数

1)字符串连接函数:strcat

2)查找字符串子串函数:strstr

3)数字转字符串:itoa


18.想引用外部文件的函数怎么做?

使用extern+加上函数名(带上返回类型)。

#include <iostream>//#include "func.h"using namespace std;extern void test();int main(){test();return 0;}
注意:不需要包含对应的文件,只需要将文件放到相同目录下。

19.两个头文件可以相互引用吗?

可以,但是最好不要这样,用前置声明来代替引用。


20.C++程序调用C程序该怎么做?

如果在C++文件里调用C函数,会找不到函数体,报链接错误。

原因是编译器产生C++函数名和C函数名时,涉及到重载,编译后在库中的名字不同。假设某个函数的声明如下:

void foo(int x, int y);
该函数被C编译器编译后在库中的名字为_foo,而C++编译器则会产生像_foo_int_int之类的名字用来支持函数重载。由于编译后的名字不同,C++程序不能直接调用C函数。

要解决这个问题,就要在C++文件里面显示声明哪些函数是C写的,要用C的方式来处理。

1)在引用头文件前需要加上extern "C",如果引用多个,按下面方法

extern “C”{#include “ s.h”#include “t.h”#include “g.h”#include “j.h”};
然后在调用这些函数之前,需要将函数也全部声明一遍。

2)C++调用C函数的方法,将用到的函数全部重新声明一遍。

extern “C”{extern void A_app(int);extern void B_app(int);extern void C_app(int);extern void D_app(int);}


21.构造函数和析构函数与虚函数的关系。

1)构造函数不能是虚函数。

原因:

a.构造一个对象的时候,必须知道对象的实际类型,而虚函数行为实在运行期确定实际类型的。而在构造一个对象时,由于对象还没有构造成功,编译器无法知道对象的实际类型是该类本身还是该类的一个派生类。

b.虚函数的执行依赖于虚函数表。而虚函数表在构造函数中进行初始化工作,即初始化vptr,让他指向正确的虚函数表。而在构造对象期间,虚函数表还没有被初始化,将无法进行。

2)析构函数可以声明为虚函数,而且有时必须声明为虚函数。

解释:

在类的继承中,如果有基类指针指向派生类,那么用基类指针delete时,如果不定义成虚析构函数,派生类中派生的那部分无法析构。

3)不建议在构造函数和析构函数里面调用虚函数。(一般指基类构造函数或析构函数调用虚函数)

不在构造函数调用虚函数原因:在基类构造的时候,派生类对象还没有构造,不会走到派生类中。

不在析构函数调用虚函数原因:在基类析构的时候,派生类已经析构完成,调用虚函数会导致其调用已经析构了的子类对象对象里的函数,这非常危险。



22.拷贝构造函数为什么使用引用类型?

如果不使用引用类型,在传递参数的过程中会调用拷贝构造函数,这样会造成无限递归,最终导致栈溢出。所以拷贝构造函数必须使用引用类型。


23.静态成员函数为什么不能声明为虚函数?

静态成员函数,可以不通过对象来调用,即没有隐藏的this指针。

虚函数一定要通过对象来调用,即具有隐藏的this指针。


24.C++代码如何从源代码到可执行文件的?

1)预处理:对一些伪指令(以#开头的指令)和特殊符号(如LINE表示当前行号,FILE表示当前编译的C++程序名称)进行处理;

2)编译:进行词法分析和语法分析;

3)优化:

4)汇编:把汇编语言代码翻译成机器指令。


25.构造函数构造失败应该怎样处理?

1)在构造函数中抛出异常,让上层知道构造函数失败。也可以传入一个引用值,然后在构造函数里面设置状态。

2)构造函数抛出异常的话将会导致对象的析构函数不被执行。

3)当对象发生部分构造时,已经构造完毕的子对象将会逆序地被析构。


26.虚函数的特性

1)一旦将某个成员函数声明为虚函数后,它在类的继承体系中就永远为虚函数了。

2)如果基类定义了虚函数,当通过基类指针或引用调用派生类对象时,将访问派生类的虚函数版本(多态)。

3)派生类中的虚函数要保持其虚特性,必须与基类虚函数的函数原型完全相同,否则就只是普通的重载函数,与基类的虚函数无关。

4)只有类的非静态成员函数才能被定义为虚函数,类的构造函数和静态成员函数不能定义为虚函数。

5)内联函数也不能是虚函数。因为内联函数采用的是静态联编的方式,而虚函数是程序运行时才与具体函数动态绑定的,采用的是动态联编的方式。(即使虚函数在类里面被定义,编译器也将它视为非内联函数,不能用inline来声明虚函数。)

1 0
原创粉丝点击