C++
作为一门重要的编程语言,其在面试中常常是热门的考察对象。本文将会介绍一些常见的C++
面试题,帮助C++
面试者避免很多不必要的困惑和迷惑。每个问题都有相对应的答案,以便各位同学快速查阅。
C++
是C
的超集,也就是说,C++
包括了C
的所有基础特性,并且还增加了一些新的特性。下面列举一些C
和C++
之间的主要区别:
C++
是一种面向对象的编程语言,而 C
不是。因此,C++
支持类、继承、封装、多态等一系列面向对象的概念和特性,这些能力使 C++
更加灵活和强大。
C++
标准库比 C
标准库更加完善和强大。C++
标准库包括了很多容器类,如 vector
、map
、set
等,以及输入输出流、字符串处理等常用功能。这些库函数可以在许多情况下提高开发效率。
C++
引入了命名空间的概念,可以避免函数命名相同的冲突。使用命名空间可以将代码按照逻辑分组,并更好地组织代码。
C++
支持异常处理机制,这个机制可以增强程序的容错性和可靠性。当程序发生异常时,可以抛出异常并在可控范围内进行处理,避免程序崩溃。而 C
不支持异常处理机制。
C++
允许对运算符进行重载,可以使得运算符在处理特定类型的数据时更具有描述性。而 C
不支持运算符重载。什么是指针?
指针是C++
中的一种数据类型,指针变量存储了一个内存地址,该地址指向某个变量或者对象。指针可以用来访问和修改内存中的数据,同时也可以通过指针来传递参数和返回值。对于C++
程序员来说,精通指针的使用是非常重要的。
重写指的是在派生类中重新定义基类的虚函数的行为。当基类中的某个虚函数在派生类中被重新定义时,如果派生类对象调用该函数,则会覆盖掉基类中的实现,执行派生类中的实现代码。在进行对象的多态性转换时,重写非常重要。 重载则指的是在同一个作用域内声明几个同名但是参数列表不同的函数。通过函数名相同但参数类型、个数或顺序的不同,可以让多个函数具有不同的行为。例如,C++
中可以重载函数来处理不同类型的数据,如整数、浮点数等。在使用函数时,根据传递给函数的参数类型和个数来自动选择对应的函数进行调用。 因此,重写和重载的主要区别在于,重写是通过派生类重新定义基类虚函数的行为,以实现运行时多态性;而重载是在同一作用域内声明几个相同名称的函数,以实现编译时多态性。
面向对象编程有3大特性:
这三个特性是面向对象编程的核心,它们相互配合,共同组成了一个完整的面向对象编程体系,能够有效地提高程序的可靠性、可重用性、可扩展性等方面。
引用也是C++
中的一种数据类型,它提供了一种简洁而高效的方式来操作变量和对象,而不需要拷贝它们本身。引用被视为原变量的一个别名,其操作类似于指针,但是引用不能被赋值为NULL
,也不能进行指针运算。
指针是一个实体,需要分配内存空间;引用只是变量的别名,不需要分配内存空间。引用在定义的时候必须进行初始化,并且不能够改变;指针在定义的时候不一定要初始化,并且指向的空间可变。有多级指针,但是没有多级引用,只能有一级引用。指针和引用的自增运算结果不一样。
内联函数和普通函数的区别在于是否进行了“内联优化”。内联函数是一种特殊的函数,编译器会在编译时将其整个函数体插入到调用该函数的地方,从而节省了函数调用的开销。普通函数则需要在调用时进行函数栈的压栈和出栈操作,这样会带来额外的时间和空间开销。因此,对于简单的函数或者频繁被调用的函数,我们可以考虑使用内联函数来提高程序的性能。
常量指针是指针指向的地址不能改变,但是可以通过指针修改地址对应的值。而指针常量则是指针指向的地址可以改变,但是不能通过指针修改地址对应的值。
在C
语言中,常量指针的定义方式为const int* ptr
,表示指向int类型的常量指针,指针所指向的地址不能改变,但是可以通过指针修改地址对应的值。
而指针常量的定义方式为int* const ptr
,表示指向int类型的指针常量,指针所指向的地址可以改变,但是不能通过指针修改地址对应的值。
如果想要定义既不能修改地址,也不能修改地址对应的值的指针,可以使用const int* const ptr
。
常量指针和指针常量的区别在于指针所指向的内容可不可变,需要根据具体情况而定。
P.S. 感兴趣的同学可以看我之前帖子,有详细介绍
野指针是指指向已经被释放或者无效的内存空间的指针,这是 C++
中常见的一个程序错误。当我们访问野指针时,程序会出现不可预期的行为,甚至崩溃。
为了避免野指针,我们可以采取以下措施:
在定义一个指针变量的时候,我们应该立即将其初始化为一个有效的地址。如果不能确定指针的初始值,可以将其初始化为 nullptr
或 0,避免野指针的产生。
int* p = nullptr; // 初始化为空指针
当指针变量不再使用时,我们应该将其置为空指针,防止误用。这样可以有效地避免产生野指针。
int* p = new int;
*p = 10;
delete p;
p = nullptr; // 置空指针,避免野指针产生
在释放指针所指向的内存空间之后,我们应该将该指针赋值为 NULL
或 nullptr
,以防止该指针被误用。
int* p = new int;
delete p;
p = nullptr; // 置空指针,避免野指针产生// 以下代码会产生错误,因为指针 p 已经被释放delete
当一个指针变量超出了其所在作用域或者被删除时,它就成为了“悬空指针”,这是一种常见的野指针。我们应该避免使用悬空指针,同时要注意存储指针所指向的对象生命周期的问题。
总之,避免野指针是 C++
中一个很重要的问题,可以通过初始化、及时置空、避免释放已经释放的内存、避免使用悬空指针等措施来避免产生野指针,从而保证程序的正确性和稳定性。
C++
多态是指在继承关系中,子类可以重写父类的虚函数,从而使得一个指向子类对象的指针能够调用子类的函数而不是父类的函数。其底层原理涉及到虚函数表、虚指针等概念。虚函数表是一个存储类的虚函数地址的数据结构,每个包含虚函数的类都有自己的虚函数表。
虚指针是一个指向虚函数表的指针,每个含有虚函数的对象都有一个虚指针。虚指针的设置是由编译器来完成的,当一个类中含有虚函数时,编译器就会在类中增加一个虚指针来指向虚函数表,每个对象都有一个虚指针,指向它所属的类的虚函数表。
通过虚函数表和虚指针,使得程序能够在运行时根据对象的实际类型来确定调用哪个函数。
虚函数是C++
中的一种特殊函数,它可以实现多态性。当一个类中包含至少一个虚函数时,它就被称为虚类或抽象类。这些虚函数由子类重写,使得它们可以根据需要对基类的行为进行扩展和修改。通过使用虚函数可以实现动态绑定和运行时多态。
以上是一些常见的C++
面试题及其答案,当然可能还有其他的问题涉及到了更深入的知识点。无论何种情况,我们应该保持谦虚、认真和热情,去面对每一个机会,以便在面试中显示出自己的技能和才能。
C++基类的析构函数声明为虚函数是为了确保在通过基类的指针或引用删除派生类对象时,可以正确地释放派生类对象所占用的内存。如果基类的析构函数不是虚函数,则在这种情况下只会调用基类的析构函数,而不会调用派生类的析构函数,从而可能导致内存泄漏和未定义行为。因此,将基类的析构函数声明为虚函数是一种良好的编程实践,可以确保在多态情况下正确地释放内存。
它们虽然在某些方面相似,但是有很多区别。
数组名是一个指向数组首元素的常量指针,它存储的是数组首元素的地址。而指针是一个变量,它存储的是某个对象的地址。
数组名是一个常量指针,不能修改,而指针可以被重新赋值,指向其他对象。因此使用指针比使用数组名更加灵活,可以在运行时动态确定指向的对象。
数组名的大小等于数组中元素的总数乘以每个元素的大小,而指针的大小是与系统架构有关的,通常是一个字长(word length
)。
如果将数组名作为函数参数传递,实际上传递的是一个指向数组首元素的指针。而如果将指针作为函数参数传递,可以方便地修改指针所指向的对象。
可以通过数组下标访问数组元素,也可以使用指针进行访问,但是需要注意的是,使用指针访问数组元素需要先将指针解引用,即使用 *
运算符。例如:*p
表示 p
指向的对象。
内存分为堆、栈、程序代码区、全局/静态存储区和常量存储区。C++中还有自由存储区(new
)。
P.S. 感兴趣的同学可以看我之前帖子,有详细介绍
堆存放动态分配的对象,生存期由程序控制;栈用来保存定义在函数内的非static
对象,仅在其定义的程序块运行时才存在;静态内存用来保存static
对象,类static
数据成员以及定义在任何函数外部的变量,static
对象在使用之前分配,程序结束时销毁;栈和静态内存的对象由编译器自动创建和销毁。
程序编译的过程经过预处理、编译、汇编和链接四个过程。
之前帖子有详细介绍
负数通过一个标志位和补码来表示,浮点数采用单精度类型(float
)和双精度类型(double
)来存储,float
数据占用32bit
,double
数据占用64bit
。
在C++
中,extern
关键字用于声明一个已经在别处定义的变量、函数或类的引用,从而允许在一个文件中使用在其他文件中定义的全局变量、函数或类。
比如,如果在一个.cpp
文件中定义了一个全局变量int globalVar = 10;
,那么在另一个.cpp
文件中可以通过使用extern int globalVar;
来引用这个全局变量,从而可以使用它的值。
此外,extern
关键字还可以用于在多个文件中共享一个函数或类的定义。例如,如果有一个类的定义在一个.h文件中,那么在多个.cpp
文件中可以使用extern
关键字来声明这个类的引用,从而可以在这些文件中使用这个类的成员函数。
C++
函数调用是编程中常见的一个操作,其过程可以分为以下几个阶段:
在函数调用之前,需要进行一些准备工作。首先,需要将函数的参数压入栈中,以向函数传递参数。其次,需要保存当前函数的返回地址,以便在函数调用结束后返回到正确的位置。
调用函数时,程序会跳转到函数代码的入口点。此时,程序会为函数创建一个新的栈帧,用于存储函数的局部变量、返回值等信息。栈帧包含了多个部分,例如函数参数、局部变量、返回地址等等。函数参数通过栈传递,在栈的顶部。局部变量则被分配在栈帧的底部。返回地址保存在栈帧中,这样函数调用结束后程序才能正确返回。
函数内部会执行具体的操作,包括参数的读取、局部变量的声明和使用、逻辑计算、循环或者条件语句等等。函数将根据其实现过程来计算参数并进行其他操作,然后返回一个结果,该结果通常被保存在寄存器中。
当函数执行完毕时,需要将返回值存储,并恢复主函数的栈帧及处理状态。函数返回时,会跳转回调用它的函数的位置。此时,程序会弹出函数栈帧,将返回值传递给调用者,并恢复调用者的寄存器和栈。
左值是可以寻址的,有名字的,非临时的变量或表达式;右值是不能寻址的,没有名字的,临时的,生命周期在某个表达式之内的变量或表达式。
P.S. 感兴趣的同学可以看我之前帖子,有详细介绍
内存泄漏是指用动态存储分配函数动态开辟的空间,在使用完毕后未释放,导致一直占据该内存单元的情况。避免和减少内存泄漏和指针越界的错误,可以注意指针的长度、malloc
时需要确定在哪里free
、对指针赋值时注意被赋值指针需要不需要释放、动态分配内存的指针最好不要再次赋值、在C++
中优先考虑使用智能指针等。
malloc
和new
都是用于动态分配内存的函数,但它们在使用方法和效果上有一些区别:
malloc
是C
语言标准库中的函数,需要以函数调用形式调用,并且需要指定要分配的内存大小。而new
是C++
关键字,在使用时直接在类型后面添加括号即可,无需显式地指定内存大小。malloc
只负责分配内存空间,并返回该内存空间的起始地址,但不会进行初始化。而new
除了分配内存空间外,还会自动调用构造函数对对象进行初始化。malloc
返回void
类型的指针,需要进行强制类型转换,才能够使用;而new
返回一个指向已分配内存空间的指针,且不需要进行强制类型转换。malloc
会返回NULL
,并需要手动释放已经分配的内存空间;而new
会抛出std::badalloc
异常,程序可以通过异常捕获机制来处理。delete
和free
都可以用于释放动态分配的内存,但是它们之间有以下几点区别:
delete
是C++
中的运算符,而free
是C
语言中的函数。delete
会自动调用对象的析构函数来清理资源;而free
只是简单地释放指针所指向的内存块。delete
必须要用于new
动态分配的内存;而free
必须要用于malloc
动态分配的内存。因此,在C++
中应该使用delete
来释放内存,而不是使用free
。
看完如果觉得有帮助,欢迎 点赞、收藏、关注
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。