C++面试中容易问到的知识点

来源:互联网 发布:手机剪裁照片的软件 编辑:程序博客网 时间:2024/05/19 17:59

1.请实现一个单例模式的类,要求线程安全!
2.如何定义一个只能在堆上生成对象的类?!
3.如何定义一个只能在栈上生成对象的类
4.引用和指针有什么区别?!
5.const和define有什么区别?!
6.define和inline有什么区别?!
7.malloc和new有什么区别?!
8.C++中static关键字作用有哪些?!
9.C++中const关键字作用有哪些?
10.C++中包含哪几种强制类型转换?他们有什么区别和联系
11.简述C++虚函数作用及底层实现原理!
12.一个对象访问普通成员函数和虚函数哪个更快?!
13.在什么情况下,析构函数需要是虚函数?!
14.内联函数、构造函数、静态成员函数可以是虚函数吗?!
15.构造函数中可以调用虚函数吗?!
16.简述C++中虚继承的作用及底层实现原理!

【一】:请实现一个单例模式的类,要求线程安全!

//懒汉模式#pragma once#include<Windows.h>#include<mutex>class LazySingleton{public:    static LazySingleton* GetInstance() //获取唯一对象的实例接口    {        if (_inst == NULL) //使用双重检查提高效率,避免高并发下每次获取实例都加锁        {            std::lock_guard<std::mutex> lock(_mtx);            if (_inst == NULL)            {                //_inst=new LazySingleton   分为三部分 1.分配空间 2.调用构造函数3.赋值                LazySingleton* tmp = new LazySingleton();                MemoryBarrier();                _inst = tmp;            }        }        return _inst;    }    static void DelLazySingleton() //删除实例对象    {        std::lock_guard<std::mutex>lock(_mtx);        if (_inst)        {            delete _inst;            _inst = NULL;        }    }    void Print()    {        cout << "data=" << _data << endl;    }private:    LazySingleton()        :_data(0)    {}    LazySingleton(const LazySingleton&);    LazySingleton& operator=(const LazySingleton&);    int _data; //单例类里面的数据    static LazySingleton* _inst;     //指向实例的指针定义为静态私有,这样定义静态成员函数获取类的实例    static mutex _mtx;};LazySingleton* LazySingleton::_inst = NULL;mutex LazySingleton::_mtx;void TestLazySingleton(){    LazySingleton::GetInstance()->Print();    LazySingleton::DelLazySingleton();}
//饿汉模式#pragma onceclass HungrySinglenton{public:    static HungrySinglenton* GetInstance()    {        assert(_inst);        return _inst;    }    void Print()    {        cout << _data << endl;    }    static void DelHungry()    {        if (_inst)        {            delete _inst;            _inst = NULL;        }    }private:    HungrySinglenton()        :_data(0)    {}    HungrySinglenton(const HungrySinglenton&);    HungrySinglenton& operator=(const HungrySinglenton&);    int _data;    static HungrySinglenton* _inst;};HungrySinglenton* HungrySinglenton::_inst = new HungrySinglenton();void TestHungrySinglenton(){    HungrySinglenton::GetInstance()->Print();    HungrySinglenton::GetInstance()->Print();    HungrySinglenton::GetInstance()->Print();    HungrySinglenton::GetInstance()->Print();    HungrySinglenton::DelHungry();}

懒汉模式和饿汉模式的优缺点:

懒汉:不到万不得已,就不会去实例化类,也就是说在第一次用到类实例的
时候才去实例化,所以被归为懒汉实现;

饿汉:饿了肯定要饥不择食。所以在单例类定义的时候就进行实例化。

特点与选择:
1.由于要进行线程同步,所以在访问量较大时,或者可能访问的线程较多时,
采用饿汉实现,为了实现更好的性能 ,以空间换时间

2.在访问量较小时,采用懒汉模式,以时间换空间。

【二】:如何定义一个只能在堆上生成对象的类?!

//如何定义一个只能在堆上生成的类class Heap{public:    static Heap* GetInstance()    {        return new Heap();    }    void DelHeap()    {        delete this;    }private:    Heap()        :_data(0)    {}    ~Heap()    {}    Heap(const Heap&);    Heap& operator=(const Heap&);    int _data;};void TestHeap(){    Heap* tmp = Heap::GetInstance();    tmp->DelHeap();}

【三】:如何定义一个只能在栈上生成对象的类

class Stack{public:    Stack()        :_data(0)    {}    ~Stack()    {}private:    void * operator new(size_t Size)    {}    void  operator delete(void* ptr)    {}private:    int _data;};void TestStack(){    Stack s;}

【四】:引用和指针有什么区别?!
1.引用只能在定义的时候初始化一次,之后不能改变指向其它变量;
指针变量的值可变。

2.引用必须指向有效的变量,指针可以指向空

3.sizeof指针对象和引用对象的意义不同,sizeof引用得到的是所指向变量的大小,
sizeof指针是对象地址的大小。

4.指针和引用自增自减意义不一样

5.指针占用内存 而引用不占内存

【五】:const和define有什么区别?
1.编译器处理方式不同
define宏是在预处理阶段展开
const常量是在编译运行阶段使用
2.类型和安全检查
define宏没有类型,不做任何类型检查,仅仅是展开
const常量具有具体的类型,在编译阶段会执行类型检查。
3.存储方式不同
define宏仅仅是展开,有多少地方使用,就展开多少次,不会分配内存
const常量会在内存中分配(可以是在堆中也可以是在栈中)

4.const可以节省空间,避免不必要的内存分配
#define PI 3.14//常量宏
const double pi=3.14; //此时未将pi放到ROM当中
double i=pi; //此时未pi分配内存以后不会分配
double I=PI; //编译期间进行宏替换,分配内存
double j=pi; //没有分配内存
double J=PI; //再次进行宏替换,又一次分配内存空间。
const定义常量从汇编的角度来看,只是给出了对应的内存地址,而不是、
像define一样给出的是立即数,所以const常量在程序运行过程中只有一份
拷贝,而define定义的常量在内存中有若干份拷贝。

5.提高了效率,编译器不会为普通const常量分配存储空间,而是将它们保存在
符号表中,这是它成为一个编译期间的常量,没有了存储于读内存的操作,
使得他们效率更高。

【六】:inline
以inline修饰的函数称为内联函数,编译时C++编译器会在调用内联函数的地方
展开,没有函数压栈的开销,内联函数可以提升程序运行的效率

1.inline是一种以空间换时间的做法,省去调用函数开销,所以代码很长或者
有循环、递归的函数不适合使用内联。

2.inline对于编译器而言只是一个建议,编译器会自动优化,如果定义为inline
函数的内部有循环或者递归等,编译器优化时会忽略内联。

3.inline必须函数定义放在一起,才能成为内联函数,仅将inline
放在声明处是不起作用的。

4.定义在类内的成员函数默认定义为内联函数;

【七】:malloc和new有什么区别?

1.malloc/free是C/C++中的库函数,new/delete 是C++中的操作符

2.它们都是管理动态内存的入口

3.malloc和free只会动态的开辟内存和释放内存,而new和delete
不仅会开辟内存还会调用构造函数进行初始化还有析构函数进行清理工作。

4.mallo需要手动计算需要多大空间返回是void*,new会自动计算需要的空间
以及返回相应的类型。

【八】:static关键字的作用
static定义静态全局变量
1.全局变量和静态全局变量的区别
全局变量默认是有外部链接属性的,作用域是整个工程,在一个文件内定义
在另一个文件中通过extern全局变量的声明可以使用全局变量
全局静态变量是显示的用static修饰的全局变量,作用域是这个文件,
其他文件使用extern也不能使用。

static定义局部变量:
2.静态局部变量的特点:
该变量在数据段分配内存,首次被初始化,以后函数调用不再进行初始化
静态局部变量一般在声明的地方进行初始化,如果没有显示初始化,程序
会自动初始化为0;
它始终驻留在数据段,直到程序运行结束,但其作用域为局部作用域,当定义
它的函数结束时,其作用域随之结束。

声明成静态函数:
静态函数不能被其他文件使用;
其他文件可以定义同名函数,不会发生冲突;

static在类中使用:
类中用static修饰的成员,称为静态类成员
类的静态成员是该类型的所有对象所共享
类的静态成员函数需要在类外定义;
静态成员函数没有隐含this指针,我们可以使用类型::作用域直接访问静态成员函数。

【九】:C++中const关键字的作用有哪些?
1.可用于定义常量,阻止定义的变量被改变,定义时需要进行初始化,
在类中通常在构造函数进行初始化。
2.const定义的指针可以指定指针本身为const不可变的,也可以指定
指针所指向的数据为const不可改变,还可以同时指定二者都不可以改变

const char*p="Hello world";//指针指向的数据为常量char* cosnt p=“hello world”;//指针本身为常量const char* const p="Hello World" //指针本身与其所指向的数据都为常量

3.在一个函数的声明当中,const可修饰形参,表明是一个输入参数,
在函数内部不能改变其值;

4.对于类的成员函数,若指定为const类型,则表明该成员函数为
常函数,不能修改类的数据成员。

5.对于类的成员函数,有时必须指定其返回值为const类型,
以使得其返回值不能是“左值”。

【十】: C++中包含哪几种强制类型转换?它们有什么区别和联系
static_cast reinterpret_cast const_cast dynamic_cast

static_cast:基本类型转换方式,功能上和C中的差不多。

reinterpret_cast:转换一个指针为其他类型的指针,一个指针到另一个
指针值的二进制拷贝,不做类型检查

const_cast:用于类型转换掉表达式中的const和volatile属性

dynamic_cast:它被用于安全的沿着类的继承关系向下进行类型转换。
多态情况下使用,进行转换后的类型安全检查。

【十一】:简述C++虚函数作用及其底层实现原理。

C++中的虚函数的主要作用就是实现多态,简单的来说就是父类的指针
引用调用了重写的虚函数,当父类指针或引用指向父类对象时调用的是
父类的虚函数,指向子类指针、或引用时调用的就是子类的虚函数。

它底层是通过一张虚函数表来完成指向问题的,在有虚函数的对象实例当中
都存在一张虚函数表,虚函数表指明了实际应该调用的虚函数

【十二】:一个对象访问普通成员函数和虚函数那个够快?
看是否构成虚函数重写,如果不构成 则一样快
如果构成了,那就访问普通成员函数更快,
因为构成了在编译阶段就不能确定地址的在运行的时候在虚表里查找;
调用函数的真实地址。

【十三】:什么情况下析构函数需要是虚函数?
父类空间里边存储的是子类对象

#pragma onceclass AA{public:    AA(int a = 2)        :_a(a)    {}    ~AA()    {        cout << "~AA" << endl;    }protected:    int _a;};class B :public AA{public:    B(int b=3)        :_b(b)    {}    ~B()    {        cout << "~B()" << endl;    }protected:    int _b;};void Testaa(){    AA* tmp = new B;    delete tmp;}

解决方法把子类的析构函数声明称虚函数
一个对象去调 new B开空间调用构造函数 p->~A()
不构成–静态连编 按对象类型去调用
构成– 动态连编 跟对象的类型无关 跟指向对象有关

【十四】:内联函数,构造函数,静态函数,友元函数能声明为虚函数吗?
内联函数是在编译阶段就展开的,而虚函数是在运行阶段动态绑定的,因此不可以

构造函数本身就是为了明确初始化对象才产生的,而虚函数主要是为了不再完全理解细节
也能正常处理对象,另外虚函数是在不同的类型产生不同的动作,现在没有对象何谈动作。

静态函数不属于某一个对象,而是属于类的本身,而虚函数是属于对象的所以不支持;

因为C++不支持友元函数的继承,对于没有继承特性的函数何来虚函数的说法?

【十五】:构造函数可以调用虚函数吗?

不行因为在构造函数和析构函数中,对象是不完整的,可能会发生未定义的行为。

【十六】:简述C++中虚继承的作用及底层实现原理!
虚继承解决了–菱形继承的二义性和数据冗余的问题,
底层存在一张虚基表存的是对象的偏移地址,但是不是直接存储的是在那位置
的后四个字节存储的 为什么不直接存储,是因为他为了给虚表留位置

原创粉丝点击