面试真题附上详细答案(1)

一、函数重载的规则,让你来写怎么实现
·函数名称必须相同。
·参数列表必须不同(个数不同,类型不同,参数排列顺序不同等)。
·函数的返回类型可以相同也可以不同。
·仅仅返回类型不同不足以成为函数的重载。
C++函数重载底层原理是基于编译器的name mangling机制。编译器需要为C++所有函数,在符号表中生成唯一的标识符,来区分不同的函数。而对于同名不同参的函数,编译器在进行name mangling操作时,会通过函数名和其参数类型生成位移标识符,来支持函数重载。
注:name mangling后得到的函数标识符与返回值类型是无关的,因此函数重载与返回值类型无关。
只依赖于函数名及其参数类型,与返回值类型无关!!!
二、单例模式有几种写法,安全的要怎么写
单例模式的好处:
·单例模式在内存中只有一个实例,减少了内存开支。
·单例模式值生成一个实例,所以减少了系统的性能开销。
·单例模式可以避免对资源的多重占用。
·单例模式可以在系统设置全局的访问点。
单例模式的缺点:
·单例模式一般没有接口,扩展很困难。
·单例模式不利于测试。
·单例模式与单一职责原则有冲突。
什么情况下要用单例模式:
·要求生成唯一序列号的环境
·在整个项目中需要一个共享访问点或共享数据
·创建一个对象需要消耗的资源过多
·需要定义大量的静态常量和静态方法(如工具类)的环境
1.饿汉式(线程安全)

饿汉式,就像它的名字,饥不择食,定义的时候直接初始化。因为instance是个静态变量,所以它会在类加载的时候完成实例化,不存在线程安全的问题。这种方式不是懒加载,不管我们的程序会不会用到,它都会在程序启动之初进行初始化。
2.懒汉式(线程不安全)

懒汉式是什么呢?只有用到的时候才会加载,这就实现了我们心心念念的懒加载。
但是这样又增加了线程安全的问题。

多线程的情况下,可能会出现这样的问题:
一个线程判断instancenull,开始初始化对象;
还没来得及初始化对象的时候,领一个线程访问,判断instance
null,也创建对象。
最后的结果,就是实例化了两个Singleton对象。
但是这种情况不符合我们的要求。
3.懒汉式(加锁)

最直接的办法,直接上锁。但是这种办法会导致所有的资源全部需要获取锁,导致了资源的浪费。
4.懒汉式(双重校验锁)

这是比较推荐的一种,双中校验锁。
进步点就在于,我们把synchronized加在了方法的内部,一般的访问是不加锁的,只有在instance==null的时候才加锁。
·首先我们看第一个问题,为什么要双重校验?
如果不双重校验,如果两个线程一起调用getinstance方法,并且都通过了第一个线程获取了锁,然后实例化了instance,然后释放锁,然后第二个线程得到了线程,然后马上也实例化了instance。这就不符合我们的单例要求了。
·为什么要用volatile修饰instance?
防止指令重排
重排指的是instance=new Singleton(),我们感觉是一步操作的实例化对象,实际上对于JVM指令,是分为三步的:
1.分配内存空间
2.初始化对象
3.将对象指向刚分配的内存空间

所以不使用volatile防止指令重排可能会发生什么情况呢?

在这种情况下,T7时刻线程B对instance的访问,访问的是一个初始化未完成的对象。
所以需要在instance前加入关键字volatile。
·使用了volatile关键字后,可以保证有序性,指令重排序被禁止;
·volatile还可以保证可见性,Java内存模型会确保所有线程看到的变量值是一致的。
5.单例模式(静态内部类)

静态内部类是更进一步的写法,不仅能实现懒加载,线程安全,而且JVM还保持了指令优化的能力。
Singleton类被装载时并不会立即实例化,而是在需要实例化时,调用getInstance方法,才会加载静态内部类InnerSingleton类,从而完成Singleton的实例化。
类的静态属性只会在第一次加载类的时候初始化,同时类加载的过程又是线程互斥的,JVM帮助我们保证了线程安全。
6.单例模式(CAS)
public class Singleton_6 { private static final AtomicReference<Singleton_6> INSTANCE = new AtomicReference<Singleton_6>(); private Singleton_6() { } public static final Singleton_6 getInstance() { //等待 while (true) { Singleton_6 instance = INSTANCE.get(); if (null == instance) { INSTANCE.compareAndSet(null, new Singleton_6()); } return INSTANCE.get(); } } }

这种CAS式的单例模式算是懒汉式直接加锁的一个变种,Sychronized是一种悲观锁,而CAS是乐观锁,相比较更轻量级。

当然这种写法也比较罕见,CAS存在忙等的问题,可能会造成CPU资源的浪费。

7.单例模式(枚举)

调用方式:

这种写法解决了最主要的问题:线程安全,自由串行化,单一实例。
总结:
从使用的角度来讲,如果不需要懒加载的话,直接饿汉式就行了;如果需要懒加载的话,可以考虑静态内部类,或者尝试一下枚举的方式。
从面试的角度,饿汉式,懒汉式,双重校验锁饿汉式,这三点是重点。双重校验锁方式一定要知道指令重排是在哪,会导致什么问题。
三、C++struct和class的区别?
1.struct可以包括成员函数
2.Struct可以实现继承。
3.Struct可以实现多态
·默认的继承访问权。Class默认的是private,struct默认的是public。
·默认访问权限:struct作为数据结构的实现体,它默认的数据访问控制是public的,而class作为对象的实现体,它默认的成员变量访问控制的是private的。
·“class”这个关键字还用于定义模板参数,就像“typename”。但关键字“struct”不用于定义模板参数。
·class和struct在使用大括号{}上的区别
1)class和struct如果定义了构造函数的话,都不能用大括号进行初始化
2)如果没有定义构造函数,struct可以用大括号初始化。
3)如果没有定义构造函数,且所有成员变量全是public的话,class可以用大括号初始化。

四、C++内部缺省的成员函数
C++中,定义函数的时候可以让最右的连续若干个参数有缺省值,在调用函数的时候,如果不写相应位置的参数,则调用的参数就为缺省值。
函数缺省的作用在于提高程序的可扩充性。比如某个已经写好了的函数需要添加新的参数,而原来调用的那些语句未必需要新增加参数,为了避免对原来的所有调用该函数的地方进行修改,就可以使用函数缺省参数了。
半缺省就是一部分为缺省参数,有一部分为非缺省参数,缺省函数只能是最右边的若干个。
五、模板的实例化的时机
函数模板C++新增的一种性质,它允许只定义一次函数的实现,即可使用不同类型的参数来调用该函数。这样做可以减少代码的书写的复杂度,同时也便于修改。但是,在代码中包含函数模板本身并不会生成函数定义,他只是一个用于生成函数定义的方案。编译器使用模板为特定类型生成函数定义时,得到的是模板实例。
1.实例化
实例化有有两种形式,分为显示实例化和隐式实例化。模板并非函数定义,实例式函数定义。
2.显示实例化(explicit instantiation)
显示实例化意味着可以直接命令编译器创建特定的实例,有两种显示声明的方式。
比如存在这么一个模板函数:
Template
Void Swap(T &a,T &b)
第一种方式是声明所需的种类,用<>符号来指示类型,并在声明前加上关键词template,如下:template void Swap(int &,int &);
第二种方式是直接在程序中使用函数创建,如下:
Swap(a,b);
显式实例化直接使用了具体的函数定义,而不是让程序去自动判断。
3.隐式实例化(implicit instantiation)
隐式实例化比较简单,就是正常的调用,Swap(a,b),直接导致程序生成一个Swap()的实例,该实例使用的类型即参数a和b的类型,编译器根据参数来定义函数实例。
4.具象化(显式)
思考这么一个问题,当前的Swap模板交换输入的两个对象,可能是基本类型也可能是自定义类。如果有这么一个需求,需要交换自定义类里的某一个属性而不是整个类,那么Swap模板就不可用,因为Swap模板交换的是整个类。
显示具体化不会使用Swap()模版来生成函数定义,而应该使用专门为该特定类型显示定义的函数类型。有两种定义形式,如下,其中job为用户自定义类
Template<>void swap(job &a,job &b)
Template<>void swap(job &a,job &b)
显式具体化在声明后,必须要有具体的实现,这是与显式实例化不同的地方。
六、详细的说一下kdp,就是udp的安全模式
详细解释参考本文
https://blog.csdn.net/systemino/article/details/108218004
七、动态链接和静态链接
静态链接和动态链接两者最大的区别就是在于链接时机不一样,静态链接是在形成可执行程序前,而动态链接的进行则是在程序执行时。
1.为什么要进行静态链接?
我们在实际开发中,不可能将所有的代码放在一个源文件中,所以会出现多个源文件,而且多个源文件之间不是独立的,而且会出现多种依赖关系,如一个源文件可能要调用另一个源文件中定义的函数,但是每个源文件都是独立编译的,即每个*.c文件会形成一个*.o文件,为了满足前面说得依赖关系,则需要将这些源文件产生的目标文件进行链接,从而形成一个可以执行的程序。这个链接过程就是静态链接。
2.静态链接的原理
由很多目标文件进行链接形成的是静态库,反之静态库也可以简单地看成一组目标文件的集合,即有很多目标文件经过压缩打包后形成一个文件

这里有个小问题,就是从上面的图中可以看到静态运行库里面的一个目标文件只包含一个函数,如libc.a里面的printf.o只有printf()函数,strlen.o里面只有strlen()函数

我们知道,链接器在链接静态链接库的时候是以目标文件为单位的。比如我们引用了静态库中的printf()函数,那么链接器就会把库中包含print()函数的那个目标文件链接进来,如果很多函数都放在一个目标文件中,很可能很多没有的函数都被一起链接进了输出结果中。由于运行库有成百上千个函数,数量非常庞大,每个函数独立放在一个目标文件中可以尽量减少空间的浪费,那些没有用到的目标文件就不要链接到最终的输出文件当中。

3.静态链接的优缺点
静态链接的缺点很明显,一是浪费空间,因为每个可执行程序中对所有需要的文件都要有一份副本,所以如果多个程序对同一个目标文件都有依赖,如多个程序中都调用了printf()函数,则这多个程序中都含有printf.o,所以同一个目标文件都在内存存在多个副本;另一方面就是更新比较困难,因为每当库函数的代码修改了,这个时候就需要重新进行编译连接形成可执行程序。但是静态链接的优点就是,在可执行程序中已经具备了所有执行程序所需要的任何东西,在执行的时候运行速度快。

4.为什么会出现动态链接?
动态链接出现的原因就是为了解决静态链接中提到的两个问题,一方面是空间浪费,另一方面是更新困难。
5.动态链接的原理
动态链接的优点显而易见,就是即使需要每个程序都依赖同一个库,但是该库不会像静态链接那样在内存中存在多份副本,而是这多个程序在执行时共享同一份副本;另一个优点是,更新也比较方便,更新时只需要替换原来的目标文件,而无需将所有的程序再重新连接一遍。当程序下一次运行时,新版本的目标文件会被自动加载到内存并且连接起来,程序就完成了升级的目标。但是动态链接也是有缺点的,因为把链接推迟到了程序运行时,所以每次执行程序都需要进行链接,所以性能一定会有损失。

据估算,动态链接和静态链接相比,,想能损失大约在5%以下。经过实践证明,这点性能损失来换取程序在空间上的节省和程序构建和升级时的灵活性是值得的。
6.动态链接地址是如何重定位的呢?
前面我们讲过静态链接时地址的重定位,那我们现在就在想动态链接的地址又是如何重定位的呢?虽然动态链接把链接过程推迟到了程序运行时,但是在形成可执行文件时(注意形成可执行文件和执行程序是两个概念),还是需要用到动态链接库。比如我们在形成可执行程序时,发现引用了一个外部的函数,此时会检查动态链接库,发现这个函数名是一个动态链接符号,此时可执行程序就不对这个符号重新定位,而把这个过程留到装载时在进行。

八、C++面向对象的理解
面向对象的究极目标:代码复用(可扩展性本质也是代码复用)
面向对象的三大特性:封装(模块化)、继承、多态
·封装。继承:实现代码复用,继承就是在一个类的基础上继续扩展,一个类看成一个模块
·多态:包含静态多态(函数重载)和动态多态(子类重写父类虚函数,也只能是虚函数),一般指动态多态
1)背景:封装使代码模块化,模块之间会存在耦合(模块A依赖模块B)
2)解决办法:动态多态就是接触模块之间的耦合,让模块A不依赖一个具体的模块,而是依赖一个抽象的模块,这个抽象模块叫做抽象类,或者接口
3)怎么实现这个功能/想法呢?
虚函数,虚函数指针,虚函数表

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值