C++常考问题

1. 多态是什么(语义上什么意思,语法上怎么实现)?虚函数是什么?虚函数底层怎么实现的?

多态简单来说就是“一个接口,多种方法”,程序在运行时才决定具体调用哪一个函数,因此程序运行有不同的状态,称为多态、语法上通过虚函数来实现。
虚函数即声明时加了vitual关键字的函数,告知编译器不要在编译阶段将函数声明静态绑定到某个具体的函数实现上去,这样子类可以重写(override)父类的方法。底层实现上通过虚表,即每个多态类中有一个虚表,存储着当前类各个函数的入口地址,每个对象都有一个指向当前类虚表的指针。虚表在编译阶段就构造完毕,在一个多态类对象构造时构造函数为对象的虚指针赋值,当调用其某个虚函数时,通过虚指针vptr找到具体所调用的虚函数的入口地址,进而调用具体的虚函数。

进阶版本:

  1. 析构函数可以是虚函数吗? 可以,子类父类不同的析构方法,且最好都是虚的
  2. 构造函数可以是虚函数吗? 不可以
  3. 内联函数可以是虚函数吗? 不可以,内联函数需要在编译时处理并绑定
  4. 静态函数可以是虚函数吗?不可以,静态函数没有this指针,而vptr和vtable依靠this指针来访问
  5. 构造函数可以调用虚函数吗?不可以. 调用构造函数后如果有多层继承关系,实际上会从最顶层的基类从上往下构造,如果此时调用了一个子类的虚函数,其尚未被构造出来,这样的函数行为就是完全不可预测的。

什么是虚继承?为什么要使用它

  • 菱形继承导致的二义性的问题

2. 指针和引用的区别

  • 指针是一个变量(有内存),引用是一个别名(不分配内存)
  • 指针可以爱运行时改变其所指向的值,引用一旦和某个对象绑定就不能再改变
  • 引用没有const, 指针有const
  • 在参数传递时,引用会做类型检查,而指针不会
  • 引用不能为空,指针可以为空

3.new和malloc的区别

  • new/delete会调用构造/析构函数,malloc只分配空间
  • new申请内存无需指定内存块的大小,malloc则必须分配空间
  • new返回对象类型指针

new步骤

  1. 调用operator函数,分配内存
  2. 调用相关的构造函数构造对象
  3. 返回该对象指针

例如:无法new一个抽象类,但是可以malloc出一块与sizeof(抽象类)的空间大小

4.vector实现原理,list, map, set,unordered_map unordered_set底层的数据结构?

即一块动态数组,如果数组长度不足以存放数据就会动态扩容,其过程为重新new一块更大的空间(教科书上写2倍,实际不同编译器不一样,如1.5等),再将原有元素一一拷贝过去,同时添上新增的元素

  • list: 双向链表
  • map/set: 红黑树
  • unordered_map/unordered_set: 哈希表
  • deque 底层数据结构为一个*控制器和多个缓冲区,详细见STL源码剖析P146,支持首尾(中间不能)快速增删,也支持随机访问
  • stack/queue 底层一般用list或deque实现,封闭头部/尾部即可,不用vector的原因应该是容量大小有限制,扩容耗时
  • priority_queue 的底层数据结构一般为vector为底层容器,堆heap为处理规则来管理底层容器实现

5.static有哪些作用?(C语言本来就有的,c++中有的)

C:

  1. 静态变量,存储在静态变量区的变量,生存期是整个程序运行周期,并且只能再本文件(.c)中访问,同工程的其他.c文件无法访问
  2. 函数内部声明的static变量,可以作为不同线程中的共享变量,只在执行线程第一次到达时进行初始化。(不仅限于线程,单线程多次调用某函数同理)

C++:

  1. 静态类成员,所有同类型的对象唯一共享
  2. 静态成员函数,与任何对象都无瓜葛,不访问任何non-static member

6.const关键字有哪些作用

  1. 修饰函数参数: 函数形参声明加const保护某些值在操作过程中不会改变
  2. 修饰成员函数: 类的成员函数加上const限定可以声明此函数不会更改类对象的内容
  3. 修饰普通变量: 该变量值不可更改,比如代替宏定义,有一些函数如节省空间、给常量指定具体类型等
  4. 修饰返回值,表明返回的数据是不可修改的
  5. 修饰指针:
    • char* const ptr; //指针本身是常量不可变
    • const char* ptr; //指针所指向的内容是常量不可变
    • const char* const ptr //二者均不可变

衍生问题

  1. 成员函数能否同时修饰为static和const成员变量是可以的
    • 否,static不依赖于任何对象,而const成员函数强调不修改对象的成员,这两者针对的点不同,不能同时修饰
  2. static函数能否调用非static函数呢?
    • 否, static函数操作的整个类的,不依赖于某个对象,非static函数改变的是某个对象的成员变量,static不可以调用非static函数

7.智能指针的原理是什么? C++11有哪些智能指针?

  • 主要用来处理忘记delete或者程序运行中抛异常无法执行到delete等导致的内存泄露问题
    简单来说即为将指针类型封装为对象,当其“彻底”消亡时将调用析构函数,从而自动地将其从堆中分配的内存释放掉,而不用去关心内存泄露的问题。
  • 两种智能指针 shared_ptr 和 unique_ptr, 还有一个名为weak_ptr的伴随类
  • shared_ptr, 共享所有权,允许多个指针指向同一个对象,其内部有一个关联的引用计数,用来记录有多少个其他的shared_ptr指向相同的对象,当引用计数为0时将调用析构函数释放对应空间
  • unique_ptr遵循独占语义,在任何时间点,资源只能唯一地被一个unique_ptr占有,当其离开作用域,所包含的资源被释放。
  • weak_ptr解决shared_ptr循环引用时的bug

场景:

  1. A类中有shared_ptr指向B, B类中有shared_ptr指向A类型
  2. main中new了两个堆对象A和B,分别用两根指针来存(记sA, sB),两个对象的内部指针互指,此时两块内存的引用计数均为2(均有两根指针指向它)
  3. 当main结束后,sA离开,A内存引用计数降为1;sB离开,B内存引用计数降为1;因为没有降为0,资源不释放
  • weak_ptr是一种不控制所指向对象生存期的智能指针,他指向一个由shared_ptr管理的对象,且不会改变其引用计数。在刚才的场景中,将类内的指针改为weak_ptr即可解决

8.C++几种强制类型转换

  1. static_cast: 内置基本数据类型之间的相互转换
  2. const_cast: 把表达式转化成常量,或者把const属性去掉
  3. reinterpret_cast: 转化任何内置的数据类型为其他任何的数据类型,也可以转化指针类型为其他类型,甚至可以转化内置的数据类型为指针,无需考虑类型安全或者常量的情形。不到万不得已绝对不用
  4. dynamic_cast: 运行时类型转化,只能转换多态类的指针和引用,比如基类型与派生类类型指针的相互转换,成功返回转换成功的指针,失败返回NULL或者抛异常

9.栈上的对象与堆上的对象有什么不同

生命期不同,栈上的对象在定义对象时即在栈空间生成,离开作用域(右大括号)后即自动调用析构函数销毁
堆上的空间需要进行动态内存分配(new)且需要手动释放(delete),否则会一直存在,造成内存泄露

10.const和define有什么不同

const常量可以给常量加上类型,从而在编译阶段可以进行类型检查。
const在编译运行阶段使用,define宏在预处理阶段展开
const可以节省空间,避免不必要的内存分配
如#define PI = 3.14
const double pi = 3.14
double i = Pi; //此时为i分配内存之后不再分配
double I = PI; //编译期间进行宏替换,分配内存,之后每次有宏定义需要展开的地方都需要分配内存

11.了解C++11的哪些新特性?

  1. 易用性:nullptr / auto / 范围for循环 / 初始化列表 / override和final / lambda表达式
  2. 右值引用和移动语义
  3. 智能指针
  4. 标准库扩充,新增array/forward_list/两个unordered/tuple新容器,语言级线程支持,thread/mutex/unique_lock等

12.C++写一个线程安全的单例模式

  1. 饿汉实现(自身线程安全)
class singleton{
public:
    static singleton* initance();
private:
    singleton(){}
    static singleton* p;
}
singleton* singleton::p = new singleton;
singleton* singleton::initance(){
    return p;
}
  1. 懒汉实现(加锁)
class singleton{
public:
    static pthread_mutex_t mutex;
    static singleton* initance();
private:
    singleton(){
        pthread_mutex_init(&mutex); //初始化
    }
    static singleton* p;
    static singleton* initance();
}
pthread_mutex_t singleton::mutex;
singleton* singleton::p = NULL;
singleton* singleton::initance(){
    if(p == NULL){
        pthread_mutex_lock(&mutex); //加锁
        if(p == NULL)
            p = new singleton();
        pthread_mutex_unlock(&mutex);
    }
    return p;
}

3.懒汉实现(内部静态变量)

class singleton{
public:
    static singleton& initance(){
        static singleton obj; //隐含初始化
        return obj;
    }
private:
    singleton(){}
    singleton(const singleton &);
    singleton& operater=(const singleton &){}
}

13.成员函数可以同时被static和const修饰吗?

NO
C++编译器在实现const的成员函数的时候为了确保该函数不能修改类的中参数的值,会在函数中添加一个隐式的参数const this*。但当一个成员为static的时候,该函数是没有this指针的。也就是说此时const的用法和static是冲突的。

上一篇:android 自定义ViewGroup和对view进行切图动画实现滑动菜单SlidingMenu


下一篇:android.content.res.Resources$NotFoundException: String resource ID #0x1