C++面试题汇总
- 1. new/delete和malloc/free:
- 2. delete和delete[]:
- 3. 常引用:
- 4. overload、override、overwrite的介绍
- 5. C++是不是类型安全的?
- 6. main 函数执行以前,还会执行什么代码?
- 7. 数组与指针的区别:
- 8. 引用与指针有什么区别?
- 9. 基类的析构函数不是虚函数,会带来什么问题?
- 10. 为什么数组名作为参数,会改变数组的内容,而其它类型如int却不会改变变量的值?
- 11. 为什么需要使用堆,使用堆空间的原因?
- 12. const关键字有哪些作用?
- 13. 面向对象的三个基本特征,并简单叙述之?
- 14. 多态的作用?
- 15. 当一个类A 中没有声命任何成员变量与成员函数,这时sizeof(A)的值是多少,如果不是零,请解释一下编译器为什么没有让它为零。
- 16. 在C++中有没有纯虚构造函数?
- 17. 在C++的一个类中声明一个 static 成员变量有没有用?
- 18. 函数重载,我们靠什么来区分调用的那个函数?靠返回值判断可以不可以?
- 19. 所有的运算符都能重载吗?
- 20. extern关键字的作用:
- 21. static关键字的作用:
- 22. 常用的设计模式:
- 23. i++是否为原子操作?
- 24.什么情况下需要将析构函数定义为虚函数?
- 25. explicit关键字的作用?
- 26. 内存溢出,内存泄漏的原因?
- 27. 模板的特例化:
- 28. C++11新特性:
- 29. 稳定和不稳定排序:
- 30. static_cast:
- 31. 大小端:
- 32. VC 中,编译工具条内的 Debug 与 Release 选项是什么含义?
- 33. 函数 assert 的用法?
- 34. const 与 #define 的比较 ,const有什么优点?
- 35. 引用和指针的区别:
- 36. 如何判断一个操作系统是16位还是32位的?
- 37. 多态类中的虚函数表是 Compile-Time,还是 Run-Time 时建立的?
- 38. 多态的作用?
- 39. 函数模板与类模板有什么区别?
- 40. 如何打印出当前源文件的文件名以及源文件的当前行号?
- 41. 重入和不可重入
- 42. c++11 最简单的线程安全的单例模式
- 43. 在c++中,运算符和函数有什么区别?
- 44. c++和c的不兼容性
1. new/delete和malloc/free:
delete会调用对象的析构函数,和new对应。
free只会释放内存。
malloc与free是C++/C语言的标准库函数,new/delete是C++的运算符。
它们都可用于申请动态内存和释放内存。
由于malloc/free是库函数而不是运算符,不在编译器控制权限之内,不能够把执行构造函数和析构函数的任务强加于malloc/free。
因此C++语言需要一个能完成动态内存分配和初始化工作的运算符new,以及一个能完成清理与释放内存工作的运算符delete。注意new/delete不是库函数。
2. delete和delete[]:
delete只会调用一次析构函数,而delete[]会调用每一个成员的析构函数。delete与new配套,delete []与new []配套
3. 常引用:
如果既要利用引用提高程序的效率,又要保护传递给函数的数据不在函数中被改变,就应使用常引用。
const int &a;
4. overload、override、overwrite的介绍
(1)overload(重载),即函数重载:
①在同一个类中;
②函数名字相同;
③函数参数不同(类型不同、数量不同,两者满足其一即可);
④不以返回值类型不同作为函数重载的条件。
(2)override(覆盖,子类改写父类的虚函数),用于实现C++中多态:
①分别位于父类和子类中;
②子类改写父类中的virtual方法;
③与父类中的函数原型相同。
(3)overwrite(重写或叫隐藏,子类改写父类的非虚函数,从而屏蔽父类函数):
①与overload类似,但是范围不同,是子类改写父类;
②与override类似,但是父类中的方法不是虚函数。
5. C++是不是类型安全的?
不是。两个不同类型的指针之间可以强制转换(用reinterpret cast)。C#是类型安全的。
6. main 函数执行以前,还会执行什么代码?
全局对象的构造函数会在main 函数之前执行。
7. 数组与指针的区别:
数组要么在静态存储区被创建(如全局数组),要么在栈上被创建。指针可以随时指向任意类型的内存块。
8. 引用与指针有什么区别?
引用必须被初始化,指针不必。
引用初始化以后不能被改变,指针可以改变所指的对象。
不存在指向空值的引用,但是存在指向空值的指针。
9. 基类的析构函数不是虚函数,会带来什么问题?
派生类的析构函数用不上,会造成资源的泄漏。
10. 为什么数组名作为参数,会改变数组的内容,而其它类型如int却不会改变变量的值?
当数组名作为参数时,传递的实际上是地址。
11. 为什么需要使用堆,使用堆空间的原因?
直到运行时才知道一个对象需要多少内存空间;不知道对象的生存期到底有多长。
12. const关键字有哪些作用?
(1)欲阻止一个变量被改变,可以使用const关键字。在定义该const变量时,通常需要对它进行初始化,因为以后就没有机会再去改变它了;
(2)对指针来说,可以指定指针本身为const,也可以指定指针所指的数据为const,或二者同时指定为const;
(3)在一个函数声明中,const可以修饰形参,表明它是一个输入参数,在函数内部不能改变其值;
(4)对于类的成员函数,若指定其为const类型,则表明其是一个常函数,不能修改类的成员变量;
(5)对于类的成员函数,有时候必须指定其返回值为const类型,以使得其返回值不为“左值”。
13. 面向对象的三个基本特征,并简单叙述之?
- 封装:将客观事物抽象成类,每个类对自身的数据和方法实行protection(private, protected, public)
- 继承:广义的继承有三种实现形式:
实现继承(指使用基类的属性和方法而无需额外编码的能力)、
可视继承(子窗体使用父窗体的外观和实现代码)、
接口继承(仅使用属性和方法,实现滞后到子类实现)。
前两种(类继承)和后一种(对象组合=>接口继承以及纯虚函数)构成了功能复用的两种方式。 - 多态:是将父对象设置成为和一个或更多的与他的子对象相等的技术,赋值之后,父对象就可以根据当前赋值给它的子对象的特性以不同的方式运作。
14. 多态的作用?
- 隐藏实现细节,使得代码能够模块化;扩展代码模块,实现代码重用;
- 接口重用:为了类在继承和派生的时候,保证使用家族中任一类的实例的某一属性时的正确调用。
15. 当一个类A 中没有声命任何成员变量与成员函数,这时sizeof(A)的值是多少,如果不是零,请解释一下编译器为什么没有让它为零。
sizeof(A) = 1;编译器不允许一个类的大小为0,会为它分配1字节的内存。若不这样做,那2个类A的实例在内存中将会无法区分。
16. 在C++中有没有纯虚构造函数?
构造函数不能是虚的。只能有虚的析构函数。
17. 在C++的一个类中声明一个 static 成员变量有没有用?
在C++类的成员变量被声明为 static(称为静态成员变量),意味着它为该类的所有实例所共享。类的静态成员函数也只能访问静态成员(变量或函数)。static是加了访问控制的全局变量,不被继承。
18. 函数重载,我们靠什么来区分调用的那个函数?靠返回值判断可以不可以?
只能靠参数而不能靠返回值类型的不同来区分重载函数。
19. 所有的运算符都能重载吗?
在 C++运算符集合中,有一些运算符是不允许被重载的。这种限制是出于安全方面的考虑,可防止错误和混乱。
(1)不能改变 C++内部数据类型(如 int,float 等)的运算符。
(2)不能重载‘.’,因为‘.’在类中对任何成员都有意义,已经成为标准用法。
(3)不能重载目前 C++运算符集合中没有的符号,如#,@,$等。原因有两点,一是难以理解,二是难以确定优先级。
(4)对已经存在的运算符进行重载时,不能改变优先级规则,否则将引起混乱。
20. extern关键字的作用:
extern置于变量或函数前,用于标示变量或函数的定义在别的文件中,提示编译器遇到此变量和函数时在其他模块中寻找其定义。
21. static关键字的作用:
21.1 修饰局部变量
static修饰局部变量时,使得被修饰的变量成为静态变量,存储在静态区。存储在静态区的数据生命周期与程序相同,在main函数之前初始化,在程序退出时销毁。(无论是局部静态还是全局静态)
21.2 修饰全局变量
全局变量本来就存储在静态区,因此static并不能改变其存储位置。但是,static限制了其链接属性。被static修饰的全局变量只能被该包含该定义的文件访问(即改变了作用域)。
21.3 修饰函数
static修饰函数使得函数只能在包含该函数定义的文件中被调用。对于静态函数,声明和定义需要放在同一个文件夹中。
21.4 修饰成员变量
用static修饰类的数据成员使其成为类的全局变量,会被类的所有对象共享,包括派生类的对象,所有的对象都只维持同一个实例。 因此,static成员必须在类外进行初始化(初始化格式:int base::var=10;),而不能在构造函数内进行初始化,不过也可以用const修饰static数据成员在类内初始化。
21.5 修饰成员函数
用static修饰成员函数,使这个类只存在这一份函数,所有对象共享该函数,不含this指针,因而只能访问类的static成员变量。静态成员是可以独立访问的,也就是说,无须创建任何对象实例就可以访问。例如可以封装某些算法,比如数学函数,如ln,sin,tan等等,这些函数本就没必要属于任何一个对象,所以从类上调用感觉更好,比如定义一个数学函数类Math,调用Math::sin(3.14);还可以实现某些特殊的设计模式:如Singleton;
21.6 最重要的特性:隐藏
当同时编译多个文件时,所有未加static前缀的全局变量和函数都具有全局可见性,其它的源文件也能访问。利用这一特性可以在不同的文件中定义同名函数和同名变量,而不必担心命名冲突。static可以用作函数和变量的前缀,对于函数来讲,static的作用仅限于隐藏。
22. 常用的设计模式:
单例模式:保证一个类仅有一个实例,并提供一个访问它的全局访问点;
工厂模式:定义一个用于创建对象的接口,让子类决定实例化哪一个类。
22.1 单例模式:
22.1.1 饿汉式:
class CSingleton{
private:
CSingleton(){}
public:
static CSingleton * getinstance(){
static CSingleton instance;
return &instance;
}
};
22.1.2 懒汉式:
class CSingleton
{
public:
static CSingleton* GetInstance()
{
if(m_pInstance == NULL)
m_pInstance = new CSingleton();
return m_pInstance;
}
private:
CSingleton(){};
static CSingleton * m_pInstance;
};
23. i++是否为原子操作?
不是。
i++分为三个阶段:
内存到寄存器
寄存器自增
写回内存
这三个阶段中间都可以被中断分离开。
24.什么情况下需要将析构函数定义为虚函数?
当基类指针指向派生类的对象(多态性)时。如果定义为虚函数,则就会先调用该指针指向的派生类析构函数,然后派生类的析构函数再又自动调用基类的析构函数,这样整个派生类的对象完全被释放。如果析构函数不被声明成虚函数,则编译器实施静态绑定,在删除基类指针时,只会调用基类的析构函数而不调用派生类析构函数,这样就会造成派生类对象析构不完全。所以,将析构函数声明为虚函数是十分必要的。
25. explicit关键字的作用?
在构造器前面加上explicit修饰, 指定这个构造器只能被明确的调用/使用, 不能作为类型转换操作符被隐含的使用。
注意:只有一个参数的构造函数,或者构造函数有n个参数,但有n-1个参数提供了默认值,这样的情况才能进行类型转换。
26. 内存溢出,内存泄漏的原因?
26.1 内存溢出:
内存溢出是指程序在申请内存时,没有足够的内存空间供其使用。原因可能如下:
内存中加载的数据量过于庞大,如一次从数据库取出过多数据;
代码中存在死循环或循环产生过多重复的对象实体;
递归调用太深,导致堆栈溢出等;
内存泄漏最终导致内存溢出。
26.2 内存泄漏:
内存泄漏是指向系统申请分配内存进行使用(new),但是用完后不归还(delete),导致占用有效内存。常见的几种情况:
(1) 在类的构造函数和析构函数中没有匹配的调用new和delete函数;
(2) 在释放对象数组时在delete中没有使用方括号;
(3)没有将基类的析构函数定义为虚函数。
27. 模板的特例化:
是对单一模板提供的一个特殊实例,它将一个或多个模板参数绑定到特定的类型或值上。
template <typename T>
void fun(T a)
{
cout << "The main template fun(): " << a << endl;
}
template <> // 对int型特例化
void fun(int a)
{
cout << "Specialized template for int type: " << a << endl;
}
int main()
{
fun<char>('a');
fun<int>(10);
fun<float>(9.15);
return 0;
}
对于除int型外的其他数据类型,都会调用通用版本的函数模板fun(T a);对于int型,则会调用特例化版本的fun(int a)。注意,一个特例化版本的本质是一个实例,而非函数的重载。
28. C++11新特性:
28.1 可变参数模板(Variadic Template):(?)
template<typename… Types>
28.2 右尖括号(Right Angle Brackets):
在C++11之前,两个右尖括号之间必须加个空格。
C++ 11中,加或者不加空格,都可以识别了。
28.3 空指针(nullptr):
C++11之前没有关键字nullptr(其对应的类型为std::nullptr_t)。
nullptr用于替换0或者NULL,因为NULL本身是定义为0的,有时候使用会产生歧义。
28.4 auto关键字:
在C++11中,可以auto定义变量,而不指定变量的类型。
编译器会从等号右边的表达式或变量来推断auto变量的类型。
auto 一般用于替代变量类型名字太长,或表达式类型太复杂的场景。
28.5 统一初始化(Uniform Initialization):
C++11之前,有三种为变量或对象初始化的方式,大括号,小括号, 赋值符号。
C++11引入统一的初始化方式 ————统一使用大括号。
C++11也兼容之前的小括号和赋值符号的初始化方式。
28.6 关键字explicit:
见25条。
28.7 基于范围的for循环(range-based for statement):
for(int i : {1,2,3,4})
{
std::cout<<i<<std::endl;
}
std::vector<int> vec{4,5,6};
for(auto& j : vec)
{
std::cout<<j<<std::endl;
std::cout<<"j的地址:"<<&j<<",vec[0]的地址"<<&vec[0]<<std::endl;
}
28.8 =default/=delete:(?)
29. 稳定和不稳定排序:
29.1 稳定排序:
冒泡排序、插入排序、归并排序 、基数排序。
29.2 不稳定排序:
选择排序、快速排序、希尔排序(shell) 、堆排序。
30. static_cast:
static_cast相当于传统的C语言里的强制转换,该运算符把expression转换为new_type类型,用来强迫隐式转换如non-const对象转为const对象,编译时检查,用于非多态的转换,可以转换指针及其他,但没有运行时类型检查来保证转换的安全性。
static_cast不能转换掉expression的const、volatile、或者__unaligned属性。
char a = 'a';
int b = static_cast<char>(a);//正确,将char型数据转换成int型数据
double *c = new double;
void *d = static_cast<void*>(c);//正确,将double指针转换成void指针
int e = 10;
const int f = static_cast<const int>(e);//正确,将int型数据转换成const int型数据
const int g = 20;
int *h = static_cast<int*>(&g);//编译错误,static_cast不能转换掉g的const属性
进行上行转换(把派生类的指针或引用转换成基类表示)是安全的;
进行下行转换(把基类指针或引用转换成派生类表示)时,由于没有动态类型检查,所以是不安全的。
31. 大小端:
大端指的就是把字节序的尾端(0xcd)放在高内存地址,而小端指的就是把字节序的尾端(0xcd)放在低内存地址。
32. VC 中,编译工具条内的 Debug 与 Release 选项是什么含义?
Debug 通常称为调试版本,它包含调试信息,并且不作任何优化,便于程序员调试程序。
Release 称为发布版本,它往往是进行了各种优化,使得程序在代码大小和运行速度上都是最优的,以便用户很好地使用。
Debug 带有大量的调试代码,运行时需要相应的运行库,发布模式程序紧凑不含有调试代码和信息,直接可以运行(如果不需要运行库)
33. 函数 assert 的用法?
断言assert是仅在debug版本起作用的宏,用于检查“不应该“发生的情况。
程序员可以把assert看成一个在任何系统状态下都可以安全使用的无害测试手段。
34. const 与 #define 的比较 ,const有什么优点?
(1) const 常量有数据类型,而宏常量没有数据类型。编译器可以对前者进行类型安全检查。
而对后者只进行字符替换,没有类型安全检查,并且在字符替换可能会产生意料不到的错误(边际效应) 。
(2)有些集成化的调试工具可以对 const 常量进行调试,但是不能对宏常量进行调试。
35. 引用和指针的区别:
(1)引用被创建的同时必须被初始化(指针则可以在任何时候被初始化) 。
(2)不能有 NULL 引用,引用必须与合法的存储单元关联(指针则可以是 NULL) 。
(3)一旦引用被初始化,就不能改变引用的关系(指针则可以随时改变所指的对象) 。
36. 如何判断一个操作系统是16位还是32位的?
定义一个指针p,打印出sizeof(p),如果结果是4,则表示该操作系统是32位,打印结果是2,表示是16位。
37. 多态类中的虚函数表是 Compile-Time,还是 Run-Time 时建立的?
虚拟函数表是在编译期就建立了,各个虚拟函数这时被组织成了一个虚拟函数的入口地址的数组.而对象的隐藏成员–虚拟函数表指针是在运行期–也就是构造函数被调用时进行初始化的,这是实现多态的关键。
38. 多态的作用?
- 隐藏实现细节,使得代码能够模块化;扩展代码模块,实现代码重用;
- 接口重用:为了类在继承和派生的时候,保证使用家族中任一类的实例的某一属性时的正确调用。
39. 函数模板与类模板有什么区别?
函数模板的实例化是由编译程序在处理函数调用时自动完成的,而类模板的实例化必须由程序员在程序中显式地指定。
40. 如何打印出当前源文件的文件名以及源文件的当前行号?
cout << __FILE__ ;
cout<<__LINE__ ;
__FILE__和__LINE__是系统预定义宏,这种宏并不是在某个文件中定义的,而是由编译器定义的。(C也有)
41. 重入和不可重入
这种情况出现在多任务系统当中,在任务执行期间捕捉到 信号 并对其进行处理时,进程正在执行的指令序列就被信号处理程序临时 中断 。如果从信号处理程序返回,则继续执行进程断点处的正常指令序列,从重新恢复到断点重新执行的过程中,函数所依赖的环境没有发生改变,就说这个函数是 可重入 的,反之就是 不可重入 的。
满足下面条件之一的多数是不可重入函数:
(1)使用了静态数据结构;
(2)调用了malloc或free;
(3)调用了标准I/O函数;标准io库很多实现都以不可重入的方式使用全局数据结构。
(4)进行了浮点运算.许多的处理器/编译器中,浮点一般都是不可重入的 (浮点运算大多使用协处理器或者软件模拟来实现)。
42. c++11 最简单的线程安全的单例模式
https://blog.csdn.net/lgfun/article/details/105810039
43. 在c++中,运算符和函数有什么区别?
语法形式上有区别;
运算符只能重载,不能自定义;
任何函数都可以重载或者覆盖,但通常你不能改变运算符作用于内置类型的行为。
44. c++和c的不兼容性
44.1 const修饰符
const 变量是否可以用作常量表达式呢? C不行,C++ 可以。
44.2 void *指针
作为通用指针, void * 可以和其他任意类型的指针相互转换, 但 C 语言中这种类型转换是隐式的(implicit conversion), 而在 C++ 中必须有显式的类型转换(explicit conversion)。
44.3 auto 关键字
auto 关键字在 C 语言中早就存在, 它用来修饰变量, 表示变量拥有自动存储 (automatic storage), 和静态存储相反。 但是呢, 在函数内, 静态存储的变量需要用 static关键字修饰, 其他变量默认都是自动存储的, 所以 auto 这个关键字不用也可以.