C++ 面试题总结

1.C和C++的区别?各有什么优势和劣势?

C/C++ 的本质区别在于,C 面向过程,C++ 面向对象。面向过程就像是将输入通过一定的过程处理然后再输出。面向对象,主要的特性在于它的“封装、继承、多态”,封装隐藏了实现细节,使得代码模块化,一定程度上提高了安全性。一个子类继承了父类的所有成员函数,并且扩展了父类,实现自己特有特性,使得C++ 的包含复用性,扩展性。而在子类通过重写了父类的虚函数,使得一个接口多种使用,体现了C++的多态性。
非要说C++的劣势,那么个人就认为,我们在使用C++类的时候,我们需要将类实例化,一定程度上消耗了资源,而C语音就不需要,所以C语音想比较C++,性能上占优势。
C++ 在C 语音的基础,拓展了新的功能,比如重载,引用,bool 等。
在动态管理内存时,C语音使用mallco/free,C++使用new/delete。

2.是什么是重载(overload)/覆盖(override)/重写(overwrite)?

overload:将几个语义相同的函数,使用相同的函数名,但是以参数和返回值不同来做区分,这就是重载的特征(函数名相同,参数不同,不可以只用返回值作为区分)。
override覆盖:派生类覆盖基类的虚函数,实现接口的重用特征,作用于基类和派生类,函数名和参数相同,函数体不同。基类必须使用virtural,派生类可用可不用。
overwrite 重写和覆盖类似,派生类重写基类的同名函数,基类的同名函数可以不适用virtual修饰,函数名相同,参数不相同,函数体不同。

3.指针和引用的区别?

指针是一个实体,需要分配内存空间,而引用只是一个变量的别名,不需要分配内存空间。
指针作为一个变量时,用来记录另一个变量的地址,我们可以通过该地址来修改对应的变量。
引用在使用时,引用的变量必须初始化。引用作为别名,我们可以修改别名,从而修改对应的变量。
引用只有一级,而指针存在多级。
引用自增时,增加的是其绑定的变量。而指针自增则该指针会指向相邻地址的下一块地址。
引用传值时,传递的是其对应的变量本身,改别名,就是改变量本身。
指针传递的是地址,我们需要将该指针进行解引用才能对该指针指向的对象进行改变。

4.malloc/free与new/delete的区别?

我们在学习C语言的时候,使用的是mallco/free 来动态管理内存。在C++ 中,引入了new/delete来静态动态管理对象。
malloc/free在开辟内存时,我们需要计算我们需要开辟内存的大小,而且我们需要进行强制类型转换,转换成我们想要的类型。而new/delete管理对象时,需要开辟内存的时候不需要计算类型大小,系统会自动计算类型的大小,返回对应的类型。
malloc/free是C标准库函数,new/delete是C++ 中的操作符,在new/delete的底层实现上,new/delete封装了malloc/free,使用new/delete 不光进行内存的开辟和释放,还要调用类的构造和析构函数。
使用时值得注意的是,一定要匹配使用,即malloc和free匹配使用,new/delete匹配使用,如果不匹配使用,会导致内存泄漏等问题。
如果使用开辟内存失败时,malloc返回NULL,而new会抛出异常,如果我们设置了new_hander函数,系统则会尝试性的分配内存。

5.#define与const的区别?

#define 定义的常量,没有类型,预处理的时候,没有类型检测,const定义的常量在编译的时候有实际的类型,相对来说比较安全一些。
工作原理不同,define定义的内容在预处理的适合,进行内容替换。const在编译时,已经确定了他的值。
#define不可以定义指针,const可以定义指针。
#deine可以定义简单的函数,const不行。

6.内联inline呢?

在C++ 中使用inline修饰的函数就是内联函数,内联函数的功能是:在编译的时候,内联函数函数体内的代码,会在调用内联函数的地方展开,没有函数压栈的开销,提高了程序的运行效率。在使用内联函数时,值得注意的是,inline必须和函数定义放在一起才有用,根声明在一起没有用。内的成员函数默认为内联函数。

7.const的用法?

在C/C++ 中,const的作用之一就是修饰常量,C修饰的常量,被保存在静态本文区,权限为只读,不可改。在C++ 中,经过编译器的优化,该常量被放置寄存器中,同样不可以被修改。如果我们硬要修改,我们可以使用关键字volatile保证const修饰的变量在内存中的可见性,我们通过指针可以对其进行修改。
const可以修饰参数,作用是为了让参数对应的变量不可被改变。
const可以修饰指针,两种情况:
        const在指针左边,修饰指针所指的对象不接发生改变。
        const在指针右边,修饰的是指针的指向,不可发生改变,但是指针指向的变量可以改变。
const还可以修饰函数,功能在于改变其访问权限,缩小了权限,比如非const修饰的函数可以访问const修饰的函数,const修饰的函数不可以访问非const修饰的函数。
const修饰的成员变量,必须在初始化列表里初始化。

7.static的用法?

static可以修饰变量和函数。

  • 修饰变量
    • 全局变量:内部链接指定,使该变量的可见性为本文件可见。
    • 局部变量:修改了其生命周期,但是作用域不变。
    • 成员变量:修饰的成员变量不属于对象,必须在类外单独初始化,如果同时被const修饰时,可以直接初始化。
  • 修饰函数
    • 普通函数:指定内部链接,使得该函数只能在本文可见。
    • 成员函数:使用static修饰的成员函数称为静态成员函数,静态函数主要用来处理静态数据,因为静态成员函数没有this指针,所以静态成员不属于对象,我们可以使用类名,或者对象名来引用静态成员函数,静态成员函数不可以调用非静态成员函数,非静态成员函数可以调用静态成员函数。如果我们使用静态成员函数访问非静态成员函数,我们可以间接使用对象来访问:
#include<iostream>

using namespace std;

class A {
public:
	void func() {	
		cout << "非静态" << endl;
	}
	static void funb(A a) {
		a.func();
	}
private:

	static int a;
};
int A::a = 10;
int main() {
	A a;
	a.func();
	A b;
	a.funb(b);
	return 0;
}
8.定义与声明的区别?

定义一个函数时,我们要将该函数的实现过程用代码描述出来,需要分配适当的内存。而声明一个函数时,不要具体的实现,只是告诉编译器这个函数的名字,不需要分配内存资源。

9.什么是继承?

继承就是:在已经存在的类的基础上创建一个新的类,这个类获得了所有基类的成员。继承试面向对象中实现代码复用的重要手段,通过继承可以共享公有的东西,实现各自的特性。值得注意的是继承时,不可以重载具有相同参数类型的静态和非静态成员函数。
点击查看我对继承详细的认识

虚函数如何实现?

凡是包含了虚函数的类,都至少包含一个虚函数表。虚函数表是用来存放类包含的所有虚函数的对应的函数指针。对象实例化以后,对象只有虚指针没有虚函数表,派生类会生成一个兼容基类的虚函数表。

10.什么是多态?

多态就是:派生类通过重写基类的虚函数,在调用的时候,根据实际的类型调用相应的函数。实现了同一接口,多种实现的功能。
点击查看我对多态的认识

11.STL 容器有哪些?算法呢?

有两种容器:一种是关联时容器,还有一种是序列式容器。
序列式容器有:vector,list,queue,stack等。虽然存入的数据可能是无序的,但是可以排序。
关联式容器有:map,set等,他们的底层都是由平衡二叉树实现的,每一个结点都是由键值和实际值组成。
算法:排序算法和查找算法等。
迭代器:迭代器是STL的精髓所在,它提供了一个算法,能够让我们有序的访问容器每一个结点,但是不用暴露容器本身。

STL中的map和set的原理?

map和set的底层实现都是由红黑树来实现的。
红黑树是一种特殊的二叉搜索树,它在每一个结点上都增加了一个存储位来表示结点的颜色,红色或者黑色。通过对任意一个叶子结点到根结点路径上的颜色的约束,来保证最长路径不超过最短路径的两倍,近而使红色树近似平衡。
红黑树有5中特性:
一、结点不是红色就是黑色,不能出现两个相同红色的结点。
二、根结点一定是黑的。
三、如果一个结点是红色的,他的两个孩子结点都是黑色的。
四、对于每个结点的,该结点到任何一个后代叶子结点的路径上的黑色结点数是相同的。
五、每个叶子结点都是黑色的(空结点)。

map与unordered_map的区别?

map 是STL的中关联式容器中的一种,提供了键值对的管理方式,底层是由红色树实现的,实际上是二叉排序树或者说是近似平衡的二叉树,所以存储的数据都是有序的。并且先关查找,插入,删除的时间复杂度都是logN 。
unordered_map和map相似,都是存储kay-value对,但是他的底层是一个放冗余的哈希表实现的,他不会想map一样将key排序,而是在存储时根据key的hash值判断元素是否相同,所以存储的数据是无序的。

vector如何实现的?频繁使用push_back()会导致什么样的后果?

vector就是一个动态的数组,它有一个指针,指向一块连续的内存,当原空间装不下时,它会申请一块新的空间,将原来的数据拷贝到新的空间,并释放原来的就空间。删除的时候,他不会删除原来的空间,只是清空了原来的数据。相比array是静态的空间,一旦配置了就不能改变了。
vector动态扩容时,并不是在原有的空间持续扩容,因为它无法保证剩余的空间能满足我们的需求,而是以原来空间2倍的大小重新配置一块更大的空间,然后将原来的内容拷贝过来,将原来的空间释放。在VS平台下,vector扩容为原大小的1.5倍,gcc则是2倍。
在原空间不够存储新内容时,每次调用push_back(),都会重新分配空间,频繁使用push_back()会导致性能下降。

12.智能指针了解嘛?

1)auto_ptr() 每次只能指向一个对象,可以拷贝赋值,但是原对象将被摧毁。每次释放,用临时变量保存指向对象的地址,然后内部原本指向的指针为空,返回原始对象。如果不接受返回值,将会造成内存泄漏。
2)unique_ptr() 拷贝构造函数,操作符重载均被设置为删除的。但是右值的拷贝构造函数与重载=是可以调用的。通常是移动拷贝函数,保证了指向对象的唯一性。
3)shared_ptr()使用了引用计数,当为0时才会被释放。
4)week_ptr()是shared_ptr()的助手
他不想其余三种,可以通过构造函数直接分配对象内存;他必须通过shared_ptr来共享内存。
没有重载operator*和->操作符,也就意味着即使分配到对象,他也没有办法使用该对象。
不主动参与引用计数,即share_ptr释放了,那么week_ptr所存的对象也释放了。
使用函数use_count()可以查看当前引用计数,expired()判断引用计数是否为空。
lock()函数,返回一个shared_ptr智能指针。

13.union和struct的区别?

联合体是将不同类型的数据存放在同一块内存里,各成员不可以同时存在。
结构体是自定义类型,将不同类型的数据整合到一起,各成员可以同时存在。
当我们在使用sizeof计算结构体时,计算的是内存对齐后,所有类型占用的空间长度的总和。
计算联合体时,计算的是内存对齐后所有类型中占用空间最大的类型所占用的长度。

14.为什么要内存对齐?

内存对齐通过空间换时间的方法来提高处理在内存中读取数据的效率,如果我们不内存对齐,处理器在读取数据的时候读取内存无法一次直接就讲数据成功读取,内存对齐以后,处理器同个对齐数可以直接读取数据。
内存对齐提高了可移植性,在有的硬件平台上,处理器要求读取指定内存中的指定数据,否则就会抛出异常。

15.堆和栈有什么区别?

栈一般用来存放参数,局部变量等,需要操作系统来管理。
堆内存一般由程序员来分配使用,在使用的适合一定要记得内存释放,否则会导致内存泄漏等问题,如果程序员忘了的话,当本程序执行完毕,将有操作系统来释放。

16.C++ 文件编译和执行的4个阶段?

1)预处理:根据文件中的预处理指令,修改源文件内容。
2)编译:将源代码编译成汇编代码。
3)汇编:把汇编代码翻译成机器指令。
4)链接:链接目标代码,生成可执行程序。

17. C++ 的内存管理

C++ 内存被分为5个区域:
栈区:存放临时变量,参数等,有内核管理释放。
堆区:一般有程序员分配释放,如果忘记释放,将由内核接手管理,在程序执行完毕后由内核释放。分配方式类似于链表。
全局区(静态区):用来存放常量,全局变量,静态变量等。程序结束后由操作系统释放。
文字常量区:常量字符串就是放这的,由系统释放。
代码区:存放函数体的二进制代码。

18.C/C++ 函数调用的过程。

1)先从栈上分配空间。
2)先将实参的内存空间拷贝数据到形参的内存空间。
3)执行运算
形参在未调用函数之前是不会分配空间的,在函数调用后,形参弹出栈空间,然后再清除形参空间;
数组作为参数的调用方式是地址传递,形参和实参都指向相同的内存,调用完毕后,形参指针被销毁,但是指向的空间不会被销毁,依然存在。
当函数有多个返回值的时候,不要使用return直接返回,要使用传址的方式传递。
传值:将实参的值copy给形参。因此对形参的修改不会导致修改实参。
传址:实际上是一种特殊的传值方式,它传递的是地址,而不是实际对应的值,实参和形参都指向同一地址,所以对形参做出的改变同样会使实参发生改变。

19.友元函数和友元类

通过友元,可以使非本类的成员访问到本类的私有成员。
比如普通函数通过友元,可以访问相应类的私有成员。
再比如不同的类通过友元,可以是自己访问到其他类的私有成员。
友元关系只是单向的,不是相互的。真确使用友元一定程度上提高的程序运行效率,但是也破坏了封装性,导致代码的可维护性变差。
1)友元函数
友元函数可以访问类的私有成员,一个函数可以被多个类声明为友元函数。它不是类的成员函数,它是类外的普通函数,使用时一定要在类定义的时候加以声明。
2)友元类
友元类所有的成员函数都是另一个类的友元函数,都可以访问另一个类的隐藏信息(包括保护和私有的)
需要注意的是:
1))友元关系不能继承。
2))友元关系是单向的,不具有交换性。
3))友元关系同样不能被传递,比如B是A的友元,C是B的友元,如果没有声明A和C之间的关系,A和C没有任何关系。

20. C++ 线程安全问题。

多线中,如果存在多个线程同时访问类的某个数据,如果我们采用了锁机制,将该数据加以保护,只有当一个线程访问完毕以后,其他线程才能访问,这样就不存数据异常或者脏数据等问题,那么线程是安全的,如果我们没有锁机制,存在多个线程同时访问一个数据,会导致该数据同时被更改引发的脏数据等问题。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值