c++面试知识点
1、变量存储位置
栈:局部变量,函数参数
堆:由new和malloc分配的内存块 ,堆是向上增长的,由地地址到高地址;栈是向下增长的,由高地址到低地址。
全局/静态存储区(bss):未初始化的全局变量和静态变量
常量存储区(data):已经初始化的全局变量
命名规则:第一个字符必须是字母或下划线,不能以数字开头,不能包含除了“_”以外的任何特殊字符。c/c++是区分大小写的。
2、const和引用
const 在成员函数中的作用:
对数据成员的作用: 对成员数据的操作都是只读的,不能修改,直接初始化,或者在初始化列表中初始化。
对成员函数的作用: const 成员函数不能调用非 const 成员函数。而且不能修改对象的值。对于指针成员来说。就是不能修改指针的向。
对对象的作用: const 对象 只能 调用 const 成员函数,而非 const 对象也能调用const 成员函数
const 起的是形参的保护作用。相当于是一个常量指针。const test *;
const int a = 10;//如果想对一个常量进行引用,必须是一个 const 引用。
int b= 20;//int b 安全性低 ,const int&re 安全性高
const int & re = b;//如果一个普通变量,用一个 const 引用接收是可以的。
引用 int&re =a;//int&使用引用数据类型,re 就是 a 的别名
1、引用没有定义,是一种关系型声明。声明它和原有某一变量(实体)的关系。故而类型与原类型保
持一致,且不分配内存。与被引用的变量有相同的地址。
2 、声明的时候必须初始化,一经声明,不可变更。
3、可对引用,再次引用。多次引用的结果,是某一变量具有多个别名。
4、&符号前有数据类型时,是引用。其它皆为取地址 int &re2=b;
引用所占用的大小,跟指针是相等的。
常量要初始化,引用也要初始化,引用可能是常量,引用是常指针。
使用引用需要注意的点:
- 引用必须被初始化
- 不要返回局部变量的引用,因为局部变量在栈空间
- 同时也不要轻易返回一个堆空间的引用,除非有回收空间的策略
Q:如果在const修饰的成员函数中还是想改变成员变量怎么办?
使用 mutable 关键字。
violate的作用:
每次都是从内存中去读取这个数值,而不是从缓存中直接使用。主要应用于多线程编程中。
3、内联函数
要求:
1)内联函数声明时 inline 关键字必须和函数定义结合在一起,否则编译器会直接忽略内联请求
2)C++编译器直接将函数体插入在函数调用的地方
3)内联函数没有普通函数调用时的额外开销(压栈,跳转,返回)
4)内联函数是一种特殊的函数,具有普通函数的特征(参数检查,返回类型等)
5)**内联函数由编译器处理,直接将编译后的函数体插入调用的地方;**宏代码片段由预处理器处理,进行简单的文本替换,没有任何编译过程。
6)C++中内联编译的限制:
不能存在任何形式的循环语句
不能存在过多的条件判断语句
函数体不能过于庞大
不能对函数进行取址操作
函数内联声明必须在调用语句之前
4、函数指针的三种定义方式
普通函数 :int func(int a,int b){}
4、1 定义函数指针:
1、定义第一种函数指针方式**(建议使用) **
typedef int(MY_FUNC)(int,int)
MY_FUNC fp=NULL;**
fp=func;
*fp(10,20);
2、定义第二种函数指针方式
*typedef int(MY_FUNC)(int,int)
MY_FUNC fp=NULL;
fp=func;
fp(10,20);
3、定义第三种函数指针方式**(建议使用)**
*Int (fp3)(int,int)=NULL;
fp3=func;
fp3(10,30);
4、2 野指针
野指针是指向一段我们不知道的内存位置处的指针。
-
比如指向的对象已经被销毁。
-
指向函数栈中的变量。
解决方法
1、new的时候要给定一个初值。delete的时候要置为NULL。
2、不要返回指向栈空间的指针。
3、尽量避免在函数中分配空间并返回一个指针。
说明:
实际上在给函数指针赋值的时候,是会发生函数重载匹配的
在调用函数指针的时候,所调用的函数就已经固定了,这个时候就不会发生重载了
5、c和c++区别
5.1extern C
A:C++调用C函数需要extern C,因为C语言没有函数重载。实际上就是取消了name mangling。让编译器能够在库中找到正确的符号
解释下:就是C的库是按照C编译器编译的。所以其是没有C++的name mangling(即C++编译器带来的)。如果通过C++编译器的话, 头
文件中函数的签名就会被name mangling,就会加一些多余的东西。这样的话,就相当于和C库中的符号不对应了。
5.2函数重载
C++之所以可以支持函数,就是因为name mangling机制。通过函数名字,参数类型,数量构成的能让编译器识别的名字(没有返回类
型)。
重载规则:
1,函数名相同
2,参数个数不同,参数的类型不同,参数顺序不同,均可构成重载
3,返回值类型不同则不可以构成重载
5.3 malloc/free与new/delete
重载条件:
1、同一个类中的同名成员函数才存在重载关系
2、这些函数的参数列表和返回类型不一样,从而实现了重载。
3、重载和成员函数是否为虚函数无关
差别:
1.malloc/free是标准库函数, new/delete 是表达式;
2.malloc只负责开辟空间,但并不会进行初始化;
3.new表达式开辟空间之后,也会构造函数进行初始化;
4.malloc 返回的指针类型是 void* ,而 new 返回的指针是有类型的
5.delete会调用析构函数。
malloc和new的本质
- malloc和free是C语言下的,其本质是调用系统调用。
- new和delete的实际上是一个表达式
- new先调用operator new去分配空间。
- 然后将得到的指针作类型转换
- 然后调用构造函数。一句话来说,就是先分配空间,然后调用构造函数。
- delete先调用析构函数
- 然后去释放内存。一句话来说,就是先析构,然后释放空间。
- operator new/delete的本质还是去调用malloc/free
new/delete,**new []/delete []**必须匹配。
6、友元
友元的利弊:
友元不是类成员,但是它可以访问类中的私有成员。友元的作用在于提高程序的运行效率,但是,它破坏了类的封装性和隐藏性使得非成员函数可以访问类的私有成员。不过类的访问权限确实在某些应用场合显得有些呆板,从而容忍了友元这一特别语法现象。
注意事项
(1)友元关系不能被继承。
(2)友元关系是单向的,不具有交换性。若类B是类A的友元,类A不一定是类B的友元,要看在类中是否有相应的声明。
(3)友元关系不具有传递性。若类B是类A的友元,类C是B的友元,类C不一定是类A的友元,同样要看类中是否有相应的声明
当重载左移,右移运算符作为友元函数时,只能写在全局,不能够写在成员方法中,否则调用顺序会变相反!
补充:
a++不能作为左值,++a可以作为左值。左值要求可写,右值要求可读。
7、数据类型转换(4种)
四种类型转换
static_cast
用于内置的数据类型和具有继承关系的指针和引用转换
dynamic_cast
通常在基类和派生类之间转换时使用(继承关系)在转换前会进行对象类型检查,基础数据类型不能转换
dynamic_cast 会做类型的安全检查,子类指针(引用)只能转换成基类指针(引用)。
const_cast
主要针对const的转换,增加或去除变量的const 性。
reinterpret_cast
用于进行没有任何关联之间的转换
Q:那么如何避免单个参数的构造函数发生隐式类型转换?
A:使用 explicit 关键字来修饰该构造函数
8、继承和多态(重点)
8.1 、 C++如何实现多态
-
虚表指针(只要基类有虚函数),具体存在位置因编译器而定。
-
虚表指针指向虚表。虚表中动态绑定虚函数。
-
类的非虚函数是静态的。地址不变。虚函数则是动态绑定。即如果子类重写了虚函数。就指向自己的虚函数地址。否则指向基类该函数的地址。
静态函数在编译器就能确定,而虚函数需要在程序运行的时候才能确定(动态绑定)。
1、当类中存在虚函数里.则编译器会在编译期自动的给该类生成一个虚函数表,并在所有该类的对像中放入一个隐式变量vptr,该变量
是一个指针变量,它的值指向那个类中的由编译器生成的虚函数表.
2、每个类自己的虚函数入口都在这张表中维护,调用方法的时候会隐式的传入一个this指针,然后系统会根据this指针找到对应的
vptr,进而找到对应的虚函数表,找到真正方法的地址,然后才去调用这个方法,这可以叫动态绑定。
3、虚函数表存放重写的虚函数,当基类的指针指向派生类的对象时,调用虚函数时都会根据vptr来选择虚函数,而基类的虚函数在
派生类、2里已经被改写或者说已经不存在了,所以也就只能调用派生类的虚函数版本.
8、2构造函数能设置为虚函数吗?为什么
**结论:**构造函数不能设置为虚函数
原因:根据虚函数实现原理与虚函数激活机制可知:使用和激活虚函数要先有对象,而对象的创建首先就需要构造函数,因此如果构
造函数为虚函数的话,那么连对象都创建不了,就无所谓虚函数机制了。
白话版: 不能,其实这是一个鸡生蛋还是蛋生鸡的问题,虚函数是为了实现多态是吧?那想要实现多态,首先要先构造一个具有多态
性质的对象给你实现吧?那你对象都还没有构造好,怎么去实现多态呢?所以肯定不能把构造函数设置为虚函数。
###在什么情况下析构函数要设置成虚函数?为什么?
结论:一旦基类中有虚函数,那么析构函数就要设为虚函数
原因:如果用一个基类指针指向派生类对象,那么在delete该指针的时候:
如果虚函数不是析构函数。那么相当于没有多态。就不会去虚表中查找。那么就会直接执行基类的析构函数。这时候,如果在子类中申
请了资源。因为没有执行子类的析构函数,就会导致内存泄漏。
9、new/ delete 与malloc/free的区别
1.malloc/free 是c/c++的标准库函数,new/delete是c++的运算符。
2.new 和 delete 会自动调用对象的构造函数和析构函数。而malloc不行
3.new的返回值不需要进行强制类型转换,malloc返回值是void *类型指针。
4、malloc/free需要头文件stdlib.h支持,new/delete则不进行库文件支持、
操作系统
进程:进程是一个具有一定独立功能的程序在一个数据集上的一次动态执行的过程,是操作系统进行资源分配和调度的一个独立单位,是应用程序运行的载体,任何进程都可以同其他进行一起并发执行;
线程:线程是程序执行中一个单一的顺序控制流程,是程序执行流的最小单元,是处理器调度和分派的基本单位。线程本身不拥有系统资源,只拥有一些在运行中必不可少的资源。(寄存器,栈)
-
线程是程序执行的最小单位,而进程是操作系统分配资源的最小单位;
-
一个进程由一个或多个线程组成,线程是一个进程中代码的不同执行路线;
-
进程之间相互独立,但同一进程下的各个线程之间共享程序的内存空间(包括代码段,数据集,堆等)及一些进程级的资源(如打开文件和信号等),某进程内的线程在其他进程不可见;
-
调度和切换:线程之间切换比进程之间切换要快得多。
一个线程必定属于也只能属于一个进程,而一个进程可以拥有多个线程并且至少拥有一个线程
同属于一个进程的所有线程共享该进程的所有资源,不同的进程互相独立
每个进程都有独立的内存空间,而线程共享其所属进程的内存空间。
并发与并行
-
并发是指单CPU在俩个程序间不停切换。看起来像是在同时工作,只不过是人反应不
过来。- 并行则是俩个CPU真正同时地工作。
软连接和硬连接
文件名存在唯一标识inode值。inode表中有一个引用计数。代表多少个文件名映射这个inode值。
-
软连接实际上即是快捷方式。软连接则是存储了一个文件名(绝对路径)的映射。即
修改源文件的名字,或者move均会导致连接失效。- 硬链接则是完全拷贝了一个一模一样的文件。我们知道文件一般用inode唯一标识。
硬连接就是不同文件名映射一个inode。有点像多个名字不同的指针指向一个内存地
址。因此修改硬连接文件实际上是在修改同一个文件。
静态连接
即静态库拷贝。
链接器从库中拷贝函数和数据到自身之中。最终建立一个可执行文件。程序运行时,即不再需要静态库。
如果多个程序同时使用一个静态链接库。则均含有这样的拷贝。 不再依赖库。程序本身含有库的拷贝。
动态连接
- 硬链接则是完全拷贝了一个一模一样的文件。我们知道文件一般用inode唯一标识。
即静态库共享。
链接器不会从库中拷贝目标模块。而是在第一个需要共享库的模块的程序启动时。库的单个副本就会在运行时候加载到内存中。后序使用该共享库的程序会共享这个库。1 、减少了内存和磁盘的使用。
线程同步的机制:
临界区:在任意时刻只允许一个线程访问共享资源
互斥量:只有一个互斥量,只有拥有互斥量的线程才有权限访问系统的公共资源
信号量:允许多个线程在同一时刻去访问同一个资源。
事件:通知线程有一些事件要发生,从而启动后继任务的开始。
进程间的通信方式
信号,信号量,管道,命名管道,消息队列,共享内存
OSI模型分层以及TCP三次握手的过程以及原因
物理层:网卡
作用:定义物理设备标准(网线类型、光纤接口类型、各种介质的传输速率等),用于计算机之间的数据传输 传输数据:比特流(0101,bit单位比特,数模转换和模数转换)
数据链路层(交换机)
定义了如何格式化数据以进行传输,和控制对物质的访问,该层将比特数据组成帧。(交换机) 它提供错误检测和纠正 传输数据:帧
网络层(路由器)
作用,将网路地址翻译对应的物理地址;两个节点的路径发送(发送路径-接受路径) 路由器:单位是数据包。IP协议 传输数据:数据包 协议:IP协议
传输层(最重要的一层)
作用:为上层协议提供端到端的可靠和透明的数据传输服务,包括处理差错控制和流量控制等问题。该层向高层屏蔽了下层数据通信的细节 使高层用户看到的只是在两个传输实体间的一条主机到主机的、可由用户控制和设定的、可靠的数据通路 传输数据:当数据达到最大限度、将数据分割成信息段 协议:TCP、UDP
会话层
建立和管理不同设备之间的通信
表示层
解决不同系统的通信语法的问题
应用层
规定消息头,方便接收方正确解析发送方发送的信息;为计算机用户提供应用接口,也为用户直接提供各种网络服务 协议:HTTP,FTP
三次握手:客户端和服务器端建立TCP连接的过程,主要原因是为了防止已经失效的连接请求报文又突然传到了服务器端
第一次
第一次握手:建立连接时,客户端发送syn包到服务器,并进入SYN_SENT状态,等待服务器确认;SYN:同步序列编号(Synchronize Sequence Numbers)。
第二次
第二次握手:服务器收到syn包,必须确认客户的SYN(ack=j+1),同时自己也发送一个SYN包(seq=k),即SYN+ACK包,此时服务器进入SYN_RECV状态。
第三次
第三次握手:客户端收到服务器的SYN+ACK包,向服务器发送确认包ACK(ack=k+1),此包发送完毕,客户端和服务器进入ESTABLISHED(TCP连接成功)状态,完成三次握手
三次握手的目的是“为了防止已经失效的连接请求报文段突然又传到服务端,因而产生错误”,这种情况是:一端(client)A发出去的第一个连接请求报文并没有丢
失,而是因为某些未知的原因在某个网络节点上发生滞留,导致延迟到连接释放以后的某个时间才到达另一端(server)B。本来这是一个早已失效的报文段,但是B
收到此失效的报文之后,会误认为是A再次发出的一个新的连接请求,于是B端就向A又发出确认报文,表示同意建立连接。如果不采用“三次握手”,那么只要B端
发出确认报文就会认为新的连接已经建立了,但是A端并没有发出建立连接的请求,因此不会去向B端发送数据,B端没有收到数据就会一直等待,这样B端就会白
白浪费掉很多资源。如果采用“三次握手”的话就不会出现这种情况,B端收到一个过时失效的报文段之后,向A端发出确认,此时A并没有要求建立连接,所以就不
会向B端发送确认,这个时候B端也能够知道连接没有建立。