C/C++面试常见问题(一)
C/C++面试常见问题归纳(一)
1、new和malloc的区别
new是操作符,malloc是库函数
a. 内存申请位置:new从自由存储区(可以是堆,可以是静态存储区),malloc从堆中申请。
b. 返回类型安全性:new返回的是对象类型的指针,malloc返回的是void*型,需要进行强制类型转换,不是类型安全型的。
c. 申请内存失败时返回值:new抛出异常,malloc返回NULL。
d. 是否需要指定内存大小:new不需要,编译器自行计算;malloc需要显式指定。
具体区别如下:
特征 | new/delete | malloc/free |
---|---|---|
内存分配位置 | 自由存储区 | 堆 |
内存分配成功返回值 | 完整类型指针 | void* |
内存分配失败返回值 | 默认抛出异常 | 返回NULL |
分配内存的大小 | 右编译器根据类型计算得出 | 必须显示指定字节数 |
处理数组 | 处理数组的new版本new[] | 需要用户计算数组的大小后进行内存分配 |
已分配内存的扩充 | 无法直观地处理 | 使用realloc简单完成 |
是否相互调用 | 可以,看具体的operator new/delete实现 | 不可调用new |
分配内存时内存不足 | 客户能够指定处理函数或重新制定分配器 | 无法通过用户代码进行处理 |
函数重载 | 允许 | 不允许 |
构造函数与析构函数 | 调用 | 不调用 |
2、sizeof和strlen的区别
a. sizeof是运算符,strlen是库函数
b. sizeof计算的是变量占的字节长度,strlen是字符串长度。
c. sizeof可以用任何类型的参数,strlen只能是char*行,且必须以‘\0’结尾
3、野指针
a. 未初始化的指针变量。
b. 指向的内存被释放没有置NULL的指针。
c. 超过了变量作用范围的指针
4、指针能否重复delete?
不能重复delete,delete之后要及时置NULL,置NULL之后第二次delete将不会执行。
原因如下:
第一次delete之后如果没有置NULL,这个指针就成了野指针,此时再进行delete可能会delete掉该指针指向的正在使用的内存空间,从而导致程序出错。
5、为什么构造函数不能是虚函数?
虚函数对应一个虚函数表,虚函数表是存在在对象的内存空间的,如果构造函数是虚函数,就需要有一个虚函数表来调用,但是类还没有实例化就没有内存空间,也就没有虚函数表。形成一个死循环。
6、为什么要把析构函数定义成虚函数?
动态多态。当父类指针指向子类对象,在析构时,编译器只知道这是一个父类指针,所以只将父类的内存进行了析构,而不会去析构子类的内存,造成了内存泄漏。
基类析构函数定义为虚拟函数的时候,在子类的对象的首地址开始会有一块基类的虚函数表拷贝,在析构子类对象的时候会删除此虚函数表,此时会调用基类的析构函数,所以此时内存是安全的。
7、为什么虚函数比普通函数慢?
虚函数在调用时需要通过查找虚函数表的方式来访问,故而比普通函数慢。
8、头文件和源文件
a. 类的声明在头文件,头文件是定义者和使用者之间的协议。
b. 头文件里只能有声明,不能有定义。避免重复定义的错误。
c. #include 是一个来自C语言的宏命令,它在编译器进行编译之前,即在预编译的时候就会起作用。#include的作用是把它后面所写的那个文件的内容,完完整整地、 一字不改地包含到当前的文件中来。值得一提的是,它本身是没有其它任何作用与副功能的,它的作用就是把每一个它出现的地方,替换成它后面所写的那个文件的内容。简单的文本替换,别无其他。
d. 头文件中可以写:const类型的定义;类的定义;内联函数的定义。
9、extern
extern int i; // 声明,不是定义
int i; // 声明,也是定义
函数的声明extern关键词是可有可无的,因为函数本身不加修饰的话就是extern。但是引用的时候一样需要声明的。
10、extern “C”
extern "C"是用来实现C和C++混合编译的。
extern "C"的主要作用就是为了能够正确实现C++代码调用其他C语言代码。
加上extern "C"后,会指示编译器这部分代码按C语言的进行编译,而不是C++的。
由于C++支持函数重载,因此编译器编译函数的过程中会将函数的参数类型也加到编译后的代码中,而不仅仅是函数名;
而C语言并不支持函数重载,因此编译C语言代码的函数时不会带上函数的参数类型,一般只包括函数名。
假如,某个函数的原型为void foo(int x, int y);该函数被C编译器编译后在符号库中的名字为_foo,而C++编译器则会产生_foo_int_int之类的名字。_foo_int_int这样的名字是包含了函数名以及形参,C++就是靠这种机制来实现函数重载的。
使用场景:
a. C++代码调用C语言代码
b. 在C++的头文件中使用
c. 在多个人协同开发时,可能有的人比较擅长C语言,而有的人擅长C++,这样的情况下也会有用到
a. C++调用C语言代码:
/* c语言头文件:cExample.h */
#ifndef C_EXAMPLE_H
#define C_EXAMPLE_H
extern int add(int x, int y);
#endif
/* c语言的实现文件:cExample.c */
#include "cExample.h"
int add(int x, int y)
{
return x + y;
}
/* c++实现文件,调用add:cppFile.cpp */
extern "C"
{
#include "cExample.h";
}
int main()
{
add(2, 3);
return 0;
}
b. 在C++头文件中使用。
// C中引用C++语言中的函数或者变量时,C++的头文件需要加上extern “C”,
// 但是C语言中不能直接引用声明了extern “C”的该头文件,应该仅在C中将C++中定义的extern “C”函数声明为extern类型。
/* c++头文件cppExample.h */
#ifndef CPP_EXAMPLE_H
#define CPP_EXAMPLE_H
extern "C" int add(int x, int y);
#endif
/* c实现文件cFile.c */
extern int add(int x, int y);
int main()
{
add(2, 3);
return 0;
}
11、数据结构和类的区别
a. 实例化的结构体存储在站内,实例化的类存储在堆内,结构体的效率较高。
b. 结构体没有析构函数。
c. 结构体不可以继承。
d. 结构体的默认成员类型为public,类为private。