C++常见面试问题总结

这次整理来自QQ群186588041,全部资料来自谭校长、张教主、H神、Robort、fight for dream、刀刀、二货(也就是我)
主要是总结了一些常常被问道的面试题
经验不足,水平有限,但愿读者能提出宝贵的意见~~~~
这个和CSDN上个人blog是同步的:http://blog.csdn.net/charles_r_chiu/article/details/47858885html


一、什么是虚函数?什么是纯虚函数?

答:虚函数声明以下: virtual ReturnType FunctionName(Parameter);引入虚函数是为了动态绑定。
纯虚函数声明以下:virtual ReturnType FunctionName()= 0;引入纯虚函数是为了派生接口。python


二、基类为何须要虚析构函数?

答:标准规定:当derived class经由一个base class指针被删除而该base class的析构函数为non-virtual时,将发生未定义行为。一般将发生资源泄漏。
解决方法即为:为多态基类声明一个virtual 析构函数。ios


3.、当i是一个整数的时候++i和i++那个更快一点?i++和++i的区别是什么?

答:理论上++i更快,实际与编译器优化有关,一般几乎无差异。
i++实现的代码为:程序员

//i++实现代码为:                                    
int operator++(int)                                  
{
    int temp = *this;                                     
    ++*this;                                             
    return temp;                                    
}//返回一个int型的对象自己

++i的实现代码:面试

// ++i实现代码为:
int& operator++()
{
    *this += 1;
    return *this;
}//返回一个int型的对象引用

i++和++i的考点比较多,简单来讲,就是i++返回的是i的值,而++i返回的是i+1的值。也就是++i是一个肯定的值,是一个可修改的左值,以下使用:算法

cout << ++(++(++i)) << endl;
    cout << ++ ++i << endl;

能够不停的嵌套++i。
这里有不少的经典笔试题,一块儿来观摩下:express

int main()
{
    
    int i = 1;
    printf("%d,%d\n", ++i, ++i);    //3,3
    printf("%d,%d\n", ++i, i++);    //5,3
    printf("%d,%d\n", i++, i++);    //6,5
    printf("%d,%d\n", i++, ++i);    //8,9
    system("pause");
    return 0;
}

首先是函数的入栈顺序从右向左入栈的,计算顺序也是从右往左计算的,不过都是计算完之后在进行的压栈操做:
对于第5行代码,首先执行++i,返回值是i,这时i的值是2,再次执行++i,返回值是i,获得i=3,将i压入栈中,此时i为3,也就是压入3,3;
对于第6行代码,首先执行i++,返回值是原来的i,也就是3,再执行++i,返回值是i,依次将3,5压入栈中获得输出结果
对于第7行代码,首先执行i++,返回值是5,再执行i++返回值是6,依次将5,6压入栈中获得输出结果
对于第8行代码,首先执行++i,返回i,此时i为8,再执行i++,返回值是8,此时i为9,依次将i,8也就是9,8压入栈中,获得输出结果。
上面的分析也是基于vs搞的,不过准确来讲函数多个参数的计算顺序是未定义的(the order of evaluation of function arguments are undefined)。笔试题目的运行结果随不一样的编译器而异。
参考:http://www.stroustrup.com/bs_faq2.html#evaluation-order
下面是使用其余编译器输出的结果:编程

//------ C++ version ------
#include <cstdio>
int main()
{
    int i = 1;
    printf("%d %d\n", ++i, ++i);
    printf("%d %d\n", ++i, i++);
    printf("%d %d\n", i++, i++);
    printf("%d %d\n", i++, ++i);
    return 0;
}
 
//------ C version ------
#include <stdio.h>
int main()
{
    int i = 1;
    printf("%d %d\n", ++i, ++i);
    printf("%d %d\n", ++i, i++);
    printf("%d %d\n", i++, i++);
    printf("%d %d\n", i++, ++i);
    return 0;
}

上面是参考的执行代码。
gcc-5.1(C++) gcc-5.1(C++14) gcc-5.1(C) 的执行结果:数组

3 3
5 3
6 5
8 9

gcc-4.3.2(C++) clang-3.7(C++) clang-3.7(C) 的执行结果安全

2 3
4 4
5 6
7 9

gcc-5.1(C99 strict) 的编译结果:编译不经过。

C99 strict prog.c: In function 'main':
prog.c:6:28: error: operation on 'i' may be undefined [-Werror=sequence-point]
     printf("%d %d\n", ++i, ++i);
                            ^
prog.c:7:29: error: operation on 'i' may be undefined [-Werror=sequence-point]
     printf("%d %d\n", ++i, i++);
                             ^
prog.c:8:29: error: operation on 'i' may be undefined [-Werror=sequence-point]
     printf("%d %d\n", i++, i++);
                             ^
prog.c:9:28: error: operation on 'i' may be undefined [-Werror=sequence-point]
     printf("%d %d\n", i++, ++i);
                            ^
cc1: all warnings being treated as errors

由上可见,这种比较毫无心义。


4.、vector的reserve和capacity的区别?

答:reserve()用于让容器预留空间,避免再次内存分配;capacity() 返回在从新进行内存分配之前所能容纳的元素数量。


五、在模板中,如何声明嵌套从属类型(即模板嵌套类型)?

答:template内出现的类型若是依赖于某个template参数,则称之为从属类型;若是从属类型在class内呈嵌套状,则称之为嵌套从属类型。

template<typename C>
void doSomething(const C& container)
{
    if(container.size() > 0)
        C::iterator iter(container.begin());
}

此时,根据C++的规则,编译器先假设C::iterator不是一个类型。然而iter的声明只有在C::iterator是一个类型时才合理。所以须要咱们本身告诉编译器。
那么,就须要再C::iterator以前加上typename,告诉编译器C::iterator是一个类型。

template<typename C>
void doSomething(const C& container)
{
    if(container.size() > 0)
        typename C::iterator iter(container.begin());
}

如上就是。


六、 auto_ptr能做为vector的元素吗?为何?

答:不能够。
当复制一个auto_ptr时,它所指向的对象的全部权被交到复制的auto_ptr上面,而它自身将被设置为null。复制一个auto_ptr意味着改变它的值。


七、如何初始化const和static数据成员?

答:一般在类外初始化static数据成员,可是 static const 的整型(bool,char,int,long)能够再类声明中初始化,static const的其余类型也必须在类外初始化(包括整型的数组)。


八、如何确保对象在抛出异常时也能被删除?什么是RAII?

答:总的思想是RAII:设计一个class,令他的构造函数和析构函数分别获取和释放资源。
有两个方法:

  1. 利用“函数的局部对象不管函数以何种方式(包括因异常)结束都会被析构”这一特性,将“必定要释放的资源”放进局部对象的析构函数;

  2. 使用智能指针。


九、 为何须要private继承?

答:在这里把各类继承都说一下:
补充:不一样继承描述符下,基类的各类修饰符下的成员转换为子类成员的修饰符示意:
参考:《TC++PL》 p363

继承描述符 父public成员 父protected成员 父private成员
public 子public成员 子protected成员 -
protected 子protected成员 子protected成员 -
private 子private成员 子private成员 -

十、如何实现单例模式?如何避免发生对象的用户复制行为?如何实现线程安全的单例模式?DCLP是什么,有什么问题?

答:
(1)、将构造函数、析构函数、复制构造函数、赋值操做符声明为私有,便可实现单例模式
单例模式实现代码一般为:

class Singleton
{
public:
    static Singleton* Instance();
protected:
    Singleton();
private:
    static Singleton* _instance;
};
Singleton::Singleton(){}
Singleton* Singleton::_instance = nullptr;
Singleton* Singleton::Instance()
{
    if(_instance == nullptr)
        _instance = new Singleton;
    return _instance;
}

(2)、避免用户的复制行为,能够将复制构造函数声明为private或者使用C++11中的delete语法。
(3)、实现线程安全的单例模式:上面实现中的GetInstance()不是线程安全的,由于在单例的静态初始化中存在竞争条件。若是碰巧有多个线程在同时调用该方法,那么就有可能被构造屡次。
比较简单的作法是在存在竞争条件的地方加上互斥锁。这样作的代价是开销比较高。由于每次方法调用时都须要加锁。
比较经常使用的作法是使用双重检查锁定模式(DCLP)。可是DCLP并不能保证在全部编译器和处理器内存模型下都能正常工做。如,共享内存的对称多处理器一般突发式提交内存写操做,这会形成不一样线程的写操做从新排序。这种状况一般能够采用volatile解决,他能将读写操做同步到易变数据中,但这样在多线程环境下仍旧存在问题。


十一、相等和等价的区别?哪些类型的容器使用相等或等价?

答: 相等(equality)是以operator==为基础,若是x==y为真,则断定x和y相等。
等价(equavalence)是以operator<为基础,若是!(x < y) && !(y < x)为真,则断定x和y等价。
一般,关联容器采用“等价”,而顺序容器采用“相等”。


十二、如何实现仿函数?为何须要经过继承自unary_function 或者 binary_function来实现仿函数?

答:function object就是重载了函数调用操做符 operator()的一个struct或者class
全部内置一元仿函数均继承自unary_function,全部内置二元仿函数均继承自binary_function
继承自unary_function和binary_function的仿函数能够成为“可配接“的仿函数。可配接的仿函数,可以与其余STL组件更”和谐“地协同工做。


1三、若是在构造函数和析构函数中抛出异常会发生什么?什么是栈展开?

答: (1)、构造函数抛异常:不会发生资源泄漏。假设在operator new()时抛出异常,那么将会因异常而结束这次调用,内存分配失败,不可能存在内存泄露。假设在别处(operator new() )执行以后抛出异常,此时析构函数调用,已构造的对象将得以正确释放,且自动调用operator delete()释放内存
析构函数抛异常:
能够抛出异常,但该异常必须留在析构函数;若析构函数因异常退出,状况会很糟糕(all kinds of bad things are likely to happen)
a、可能使得已分配的对象未能正常析构,形成内存泄露;
b、例如在对像数组的析构时,若是对象的析构函数抛出异常,释放代码将引起未定义行为。考虑一个对象数组的中间部分在析构时抛出异常,它没法传播,由于传播的话将使得后续部分不能正常释放;它也没法吸取,由于这违反了”异常中立“原则(异常中立,就是指任何底层的异常都会抛出到上层,也就至关因而异常透明的)。
(2)、抛出异常时,将暂停当前函数的执行,开始查找匹配的catch子句。首先检查throw自己是否在try块内部若是是,检查与该try相关的catch子句,看是否能够处理该异常。若是不能处理,就退出当前函数,而且释放当前函数的内存并销毁局部对象,继续到上层的调用函数中查找,直到找到一个能够处理该异常的catch。


1四、如何在const成员函数中赋值?

答:使用mutable去掉const的成员函数的const性质
const_cast和mutable的比较
const_cast:
1) 强制去掉对象的const属性。
2) 缺点:对const对象,调用包含const_cast的const成员函数,属于未定义行为。
mutable:
1) 使用场景:对可能要发生变化的成员前,加上存储描述符mutable。
2) 实质:对加了mutable的成员,无视全部const声明。
为何要有这种去除常量标志的需求?
答:两个概念:物理常量性和逻辑常量性
物理常量性:实际上就是常量。
逻辑常量性:对用户而言是常量,但在用户不能访问的细节上不是常量。
参考:《TC++PL》 p206


1五、两种经常使用的实现隐式类类型转换的方式是什么?如何避免隐式类型转换?

答:(1)、
a、使用单参数的构造函数或N个参数中有N-1个是默认参数的构造函数,如:

class A
{
public:
      A(stirng s);
      A(string s,int a = 0);
};

b、使用operator what_you_want_to_convert_type() const

class A
        {
        public:
                operator char*() const
                {
                    return data;//当从其余类型转换到char*时自动调用
                }
        private:
                char* data;
        };

(2)、在单参数的构造函数或N个参数中有N-1个是默认参数的构造函数声明以前加上explicit。


1六、STL中的vector:增减元素对迭代器的影响

解答:这个问题主要是针对连续内存容器和非连续内存容器。
a、对于连续内存容器,如vector、deque等,增减元素均会使得当前以后的全部迭代器失效。所以,以删除元素为例:因为erase()老是指向被删除元素的下一个元素的有效迭代器,所以,能够利用该连续内存容器的成员erase()函数的返回值。常见的编程写法为:

for(auto iter = myvec.begin(); iter != myvec.end())  //另外注意这里用 "!=" 而非 "<"
    {
        if(delete iter)
            iter = myvec.erase(iter);
        else ++iter;
    }

还有两种极端的状况是:
(1)、vector插入元素时位置过于靠前,致使须要后移的元素太多,所以vector增长元素建议使用push_back而非insert;
(2)、当增长元素后整个vector的大小超过了预设,这时会致使vector从新分分配内存,效率极低。所以习惯的编程方法为:在声明了一个vector后,当即调用reserve函数,令vector能够动态扩容。一般vector是按照以前大小的2倍来增加的。
b、对于非连续内存容器,如set、map等。增减元素只会使得当前迭代器无效。仍以删除元素为例,因为删除元素后,erase()返回的迭代器将是无效的迭代器。所以,须要在调用erase()以前,就使得迭代器指向删除元素的下一个元素。常见的编程写法为:

for(auto iter = myset.begin(); iter != myset.end())  //另外注意这里用 "!=" 而非 "<"
    {
        if(delete iter)
            myset.erase(iter++);  //使用一个后置自增就OK了
        else ++iter;
    }

其实在C++11中erase的返回值就是下一个节点,也能够利用函数的返回值。


1七、New和malloc的区别

解答:这个问题答案过于复杂,建议直接百度这个问题,网上的说法已经足够完善。
不过针对网上的一些说法补充而且纠正一下。网上在回答这个问题的时候没有对new操做符进行深刻的解释,在这里大体说一下。
new可分为operator new(new 操做)、new operator(new 操做符)和placement new(定位 new)。其中operator new执行和malloc相同的任务,即分配内存,但对构造函数一无所知;而 new operator则调用operator new,分配内存后再调用对象构造函数进行对象的构造。
其中operator new是能够重载的。placement new,就是operator new的一个重载版本,容许你在一个已经分配好的内存中构造一个新的对象。而网上对new说法,大多针对operator new而言,所以说new是带有类型的(觉得调用了类的构造函数),不过若是直接就说new是带有类型的话,明显是不合适的,好比原生的operator new。能够参考个人一个程序,这个程序是用代理模式实现一个自定义二维数组,在第二个维度拷贝构造的时候, 拷贝构造须要深拷贝(固然第一个维度也须要),执行深拷贝时代码大体以下:

class Array2D    //二维数组模板
    {
    private:
            size_t length2,length1; //数组各个维的大小
            Array1D<T>* data;
    }
    void* raw  =::operator new[](length2*sizeof(Array1D<T>));
        data = static_cast<Array1D<T>*>(raw);

可见执行operator new的时候申请的原生内存是能够不带有类型的。
1) malloc()分配指定字节数的未经初始化的内存空间,返回的是void指针;new操做符为一个指定类型的对象分配空能,并调用其构造函数初始化,返回的是该对象的指针。
2) malloc()必需要作初始化,以及将void指针转换成合适类型的指针。同时要考虑到分配的内存大小正好是你所须要的大小。当new操做符使用"(value)" notation,便可获得值为value的初始化。若是考虑上初始化的开销,malloc()和new没有性能上的差异。


1八、C++如何避免内存泄漏

解答:这其实能够看作是一个编程风格的问题。
a、使用RAII(Resource Acquisition Is Initialization,资源获取即初始化)技法,以构造函数获取资源(内存),析构函数释放。
b、相比于使用原生指针,更建议使用智能指针,尤为是C++11标准化后的智能指针。
c、注意delete和delete[]的使用方法。
d、这是很复杂的一种状况,是关于类的copy constructor的。首先先介绍一些概念。
同default constructor同样,标准保证,若是类做者没有为class声明一个copy constructor,那么编译器会在须要的时候产生出来(这也是一个常考点:问道"若是类做者未定义出default/copy constructor,编译器会自动产生一个吗?"答案是否认的)
不过请注意!!这里编译器即便产生出来,也是为知足它的需求,而非类做者的需求!!
而何时是编译器"须要"的时候呢?是在当这个class 【不表现出】bitwise copy semantics(位逐次拷贝,即浅拷贝)的时候。
在4中状况下class【不表现出】bitwise copy semantics
(1)、当class内含一个member object且该member object声明了一个copy constructor(不管该copy ctor是类做者本身生明的仍是编译器合成的);
(2)、当class继承自一个base class且该base class有一个copy constructor(不管该copy ctor是类做者本身生明的仍是编译器合成的);
(3)、当class声明了virtual function;
(4)、当class派生自一个继承链,且该链中存在virtual base class时。
言归正传,若是class中仅仅是一些普通资源,那么default memberwise copy是彻底够用的;然而,挡在该class中存在了一块动态分配的内存,而且在以后执行了bitwise copy semantics后,将会有一个按位拷贝的对象和原来class中的某个成员指向同一块heap空间,当执行它们的析构函数后,该内存将被释放两次,这是未定义的行为。所以,在必要的时候须要使用user-defined explicit copy constructor,来避免内存泄露。


1九、STL中排序算法的实现是什么

解答:STL中的sort(),在数据量大时,采用quicksort,分段递归排序;一旦分段后的数量小于某个门限值,改用Insertion sort,避免quicksort深度递归带来的过大的额外负担,若是递归层次过深,还会改用heapsort。


20、指针和引用的区别

本质:指针是一个变量,存储内容是一个地址,指向内存的一个存储单元。而引用是原变量的一个别名,实质上和原变量是一个东西,是某块内存的别名。
指针的值能够为空,且非const指针能够被从新赋值以指向另外一个不一样的对象。而引用的值不能为空,而且引用在定义的时候必须初始化,一旦初始化,就和原变量“绑定”,不能更改这个绑定关系。
不过以下的写法也是同的过编译器的:

int *iptr = NULL;
    int& iref = *iptr;

可是,上面的写法是非人类的。
对指针执行sizeof()操做获得的是指针自己的大小(32位系统为4,64位系统为8)。而对引用执行sizeof()操做,因为引用自己只是一个被引用的别名,因此获得的是所绑定的对象的所占内存大小。
指针的自增(++)运算表示对地址的自增,自增大小要看所指向单元的类型。而引用的自增(++)运算表示对值的自增。
在做为函数参数进行传递时的区别:指针做为函数传输做为传递时,函数内部的指针形参是指针实参的一个副本,改变指针形参并不能改变指针实参的值,经过解引用*运算符来更改指针所指向的内存单元里的数据。而引用在做为函数参数进行传递时,实质上传递的是实参自己,即传递进来的不是实参的一个拷贝,所以对形参的修改实际上是对实参的修改,因此在用引用进行参数传递时,不只节约时间,并且能够节约空间。
上面是引用和指针的区别,总的来讲,若是你须要一个可能会为空,或者还会指向别的值的时候,就使用指针,若是是要一开始就要指向一个object,并不会改变的时候就能够只用引用。


2一、指针数组和数组指针的区别

顾名思义,数组指针应该是指向数组的指针,而指针数组则是指该数组的元素均为指针。
数组指针,是指向数组的指针,其本质为指针,形如int (*p)[10],p即为指向数组的指针;数组指针是指向数组首元素的地址的指针,其本质为指针,能够当作是二级指针

指针数组,在C语言和C++中,数组元素全为指针的数组称为指针数组,其中一维指针数组的定义形式为:
类型名 *数组标识符[数组长度]
指针数组中每个元素均为指针,其本质为数组,例如咱们常用的动态数组的就是基于此的使用,以下示例:

size_t row,col;
//输入row和col的数值
int **MathTable = new int*[row];
for( int i = 0 ; i < row ; i++ )
    MathTable[i] = new int[col];
//code
for( int i = 0 ; i < row ; i++ )
    delete [] MathTable[i];
delete []MathTable;

也就是形如int p[10]这样的声明,就是咱们这里的指针数组,从声明形态上来说,是因为[]的优先级高于,又有诸以下面的指针:

*ptr_arry[i]

指针数组中的元素能够表示为:

*(*(ptr_arry+i))

()的优先级较高,又因为又结合的缘由,能够化简为:

**(ptr_arry+i)

因为数组元素均为指针,所以prt_array[i]是指第i+1个元素的指针。


2二、指针函数和函数指针

此处还有两个须要区分的概念,就是函数指针和指针函数。
函数指针
函数指针:指向函数的指针变量,在C编译时,每个函数都有一个入口地址,那么指向这个函数的函数指针即是指向这个地址。函数指针主要有两个做用:用做调用函数和作函数的参数。
int (*func)(int x);
诸如上面的代码这是申明了一个函数指针,代码(*func)中括号是必须的,这会告诉编译器这是一个函数指针而不是声明一个具备返回类型为指针的函数,后面的形参要是这个函数所指向的函数形参而定。使用以下面的代码:

#include <iostream>
 
using namespace std;
 
int(*func)(int a, int b);
int bar(int a, int b)
{
    return a + b;
}
 
 
 
int foo(int a, int b)
{
    return a;
}
int _tmain(int argc, _TCHAR* argv[])
{
    func = bar;
    cout << func(12, 34) << endl;
    system("pause");
    func = foo;
    cout << func(12, 34) << endl;
    system("pause");
    return 0;
}

这样的声明有些繁琐,其实可使用typedef来进行简化:

#include <iostream>
 
using namespace std;
 
typedef int(*PF)(int, int);
//int(*func)(int a, int b);
int bar(int a, int b)
{
    return a + b;
}
 
 
 
int foo(int a, int b)
{
    return a;
}
int _tmain(int argc, _TCHAR* argv[])
{
    PF func;
    func = bar;
    cout << func(12, 34) << endl;
    system("pause");
    func = foo;
    cout << func(12, 34) << endl;
    system("pause");
    return 0;
}

函数指针的另外一个做用就是做为函数的参数,能够在一个函数的形参列表中传入函数指针,而后边能够在这个函数中使用这个函数指针所指向的函数,这样边可使程序变得更加清晰和简洁。

#include <iostream>
 
using namespace std;
 
typedef int(*PF)(int, int);
//int(*func)(int a, int b);
int bar(int a, int b)
{
    return a + b;
}
 
 
 
int foo(int a, int b)
{
    return a;
}
 
 
void func(int a, int b, PF ptr)
{
    cout << ptr(a, b) << endl;
    return;
}
int _tmain(int argc, _TCHAR* argv[])
{
    PF ptr;
    ptr = bar;
    func(12, 34, ptr);
    system("pause");
    ptr = foo;
    func(12, 34, ptr);
    system("pause");
    return 0;
}

一旦知道函数指针是如何工做的,咱们就能够构建一些复杂的定义,例如:

void *(*(*fp1)(int))[10];

fp1是一个指向函数的指针,该函数接受一个整型参数,而且返回类型是一个指向包含了10个void指针数组的指针。是否是很绕?

float (*((*fp2)(int,int,float)))(int);

fp2是一个指向函数的指针,该函数接受三个参数(int,int,float),返回值是一个指向函数的指针,该函数接受一个整型参数并返回一个float。

typedef doubele (*(*(*fp3)())[10])();

fp3是一个函数指针,该函数无参数,且返回一个指向含有10个指向函数指针指针数组的指针,这些函数不接收参数,且返回值是double值

int (*(*fp4())[10])();

fp4是一个返回指针的函数,该指针指向含有10个函数指针的数组,这些函数的返回值是整型。

指针函数
与函数指针相区别的定义应该就是指针函数,指针函数本质上是一个函数,是指函数的返回值为指针的函数,通常是形以下的函数:
int* func(int x,int y);
如上就是一个返回值是指针的函数,很常见。
函数对象
上面谈到了函数指针以及应用,这里涉猎下函数对象。从通常函数回调意义上来讲,函数对象和函数指针是相同的,可是函数对象却具备许多函数指针不具备的优势,函数对象使程序设计更加灵活,并且可以实现函数的内联(inline)调用,使整个程序实现性能加速。


2三、二维动态数组的申请和删除

首先是如何申请二维的数组,这里咱们先申请一个指针数组,而后令指针数组中的每个元素都指向一个数组,这样二维数组就成了:

size_t row, col;
    //输入row和col的数值
    int **MathTable = new int*[row];
    for (int i = 0; i < row; i++)
        MathTable[i] = new int[col];

而后是释放空间的过程:

//code
    for (int i = 0; i < row; i++)
        delete[] MathTable[i];
    delete[]MathTable;

符合new和delete配对的原则,怎么new出来就怎么delete掉。


2四、关于shared_ptr使用须要记住什么?

总结下来须要注意的大概有下面几点:
1)、尽可能避免使用raw pointer构建shared_ptr,至于缘由此处不便于多讲,后续还有讲解
2)、shared_ptr使得依据共享生命周期而经行地资源管理进行垃圾回收更为方便
3)、shared_ptr对象的大小一般是unique_ptr的两倍,这个差别是因为Control Block致使的,而且shared_ptr的引用计数的操做是原子的,这里的分析也会在后续看到
4)、默认的资源销毁是采用delete,可是shared_ptr也支持用户提供deleter,与unique_ptr不一样,不一样类型的deleter对shared_ptr的类型没有影响。


2五、C和C++的区别

  1. 标准:分别隶属于两个不一样的标准委员会。C以C99标准为主流,C11已经发布;C++以C++98/03为主流,C++11/14也日趋流行。

  2. 语言自己:

    1. C++是面向对象语言,C是面向过程语言。

    2. 结构:C以结构体struct为核心结构;C++以类class为核心结构。

    3. 多态:C能够以宏定义的方式“自定义”部分地支持多态;C++自身提供多态,并以模板templates支持编译期多态,以虚函数virtual function支持运行期多态。

    4. 头文件的调用:C++用< >代替" "表明系统头文件;且复用C的头文件时,去掉".h"在开头加上"C"。

    5. 输入输出:鉴于C++中以对象做为核心,输入和输出都是在流对象上的操做。

    6. 封装:C中的封装因为struct的特性所有为公有封装,C++中的封装因为class的特性更加完善、安全。

    7. 常见风格:C中经常使用宏定义来进行文本替换,不具备类型安全性;C++中常建议采用常量定义,具备类型安全性。

    8. 效率:常见的说法是同等目的C一般比C++更富有效率(这其实有必定的误解,主要在于C++代码更难于优化且少有人使用编译期求值的特性)。

    9. 经常使用语言/库特性:

      1. 数组:C中采用内建数组,C++中建议采用vector。相比之下vector的大小能够动态增加,且使用一些技巧后增加并不低效,且成员函数丰富。

      2. 字符串 C中采用C风格的string(实则为字符串数组),C++中建议采用string,对比与上一条相似。

      3. 内存分配:C中使用malloc与free,它们是是C标准库函数,C++中建议使用new/delete代替前者,他们说是C++的运算符(这是笔试面试常考点)以C++中的new为例,new可分为operator new(new 操做)、new operator(new 操做符)和placement new(定位 new)。其中operator new执行和malloc相同的任务,即分配内存,但对构造函数一无所知;而 new operator则调用operator new,分配内存后再调用对象构造函数进行对象的构造。其中operator new是能够重载的。placement new,就是operator new的一个重载版本,容许你在一个已经分配好的内存中构造一个新的对象。

      4. 指针:C中一般使用的是原生指针(raw pointer),因为常出现程序员在申请后忘记释放形成资源泄漏的问题,在C++98中加入了“第一代”基于引用计数的智能指针auto_ptr,因为初代的各类问题(主要是没法解决循环指针),在03标准也就是TR1中引入了shared_ptr,weak_ptr和unique_ptr这三个功能各异的智能指针,并与11标准中正式肯定,较好的解决了上述问题。

  3. 仅有C++才有的经常使用特性:

    1. 语言(范式)特性:

      1. 面向对象编程:C++中以关键字class和多态特性支持的一种编程范式;

      2. 泛型编程:C++中以关键字template支持的一种编程范式;

      3. 模板元编程 :C++中以模板特化和模板递归调用机制支持的一种编程范式。

      4. C++中以对象和类型做为整个程序的核心,在对象方面,时刻注意对象建立和析构的成本,例若有一个很经常使用的(具名)返回值优化((N)RVO);
        在类型方面,有运行时类型信息(RTTI)等技术做为C++类型技术的支撑。

      5. 函数重载:C++容许拥有不一样变量但具备相同函数名的函数(函数重载的编译器实现方式、函数重载和(主)模板特化的区别都曾考过)。

      6. 异常:以catch、throw、try等关键字支持的一种机制。

      7. 名字空间:namespace,能够避免和减小命名冲突且让代码具备更强的可读性。

      8. 谓词用法:一般以bool函数或仿函数(functor)或lambda函数的形式,出如今STL的大多数算法的第三个元素。

    2. 常见关键字(操做符)特性:

      1. auto:在C中,auto表明自动类型一般均可省略;而在C++11新标准中,则起到一种“动态类型”的做用——一般在自动类型推导和decltype搭配使用。

      2. 空指针:在C中常以NULL表明空指针,在C++中根据新标准用nullptr来表明空指针。

      3. &: 在C中仅表明取某个左值(lvalue)的地址,在C++中还能够表示引用(别名)。

      4. &&:在C中仅能表示逻辑与,在C++中还能够表示右值引用。

      5. []:在C中仅能表示下标操做符,在C++中还能够表示lambda函数的捕捉列表。

      6. {}:在C中仅能用于数组的初始化,在C++中因为引入了初始化列表(initializer_list),可用于任何类型、容器等的初始化。

      7. 常量定义:C中常以define来定义常量,C++中用const来定义运行期常量,用constexpr来定义编译器常量。

    3. 经常使用新特性:

      1. 右值引用和move语义(太多内容,建议自查)。

      2. 基于范围的for循环(与python中的写法相似,经常使用于容器)。

      3. 基于auto——decltype的自动类型推导。

      4. lambda函数(一种局部、匿名函数,高效方便地出如今须要局部、匿名语义的地方)。

      5. 标准规范后的多线程库。


2六、C++的内存对齐

为何要内存对齐?这个网上讲得更全面,就再也不多说了。简而言之,为了速度和正确性。详见网页:http://blog.csdn.net/lgouc/article/details/8235471,英文原版:(http://www.ibm.com/developerworks/library/pa-dalign/

  1. C/C++中的内存对齐之基础篇
    这里主要以【不带有】虚继承链和虚函数的struct/class为例,【注意:全部的空间均须要为最大类型大小的整数倍】

    1. 这里有一个C和C++不一样的状况:在C中,空struct/class不占有内存,在C++中,空struct/class一般占有1byte的空间,缘由是编译器强行在里面放入了一个char,这样可使得这个class的不一样实例化在内存中分配到独一无二的地址。

    2. 最基本的内存对齐状况(其中注释表明该类型实际大小)

struct A
        {
                char   c; //1byte
                double d; //8byte
                int i;       //4byte
        };

在64位g++和vs2013下运行sizeof(A)结果均为24。这种状况下的计算都比较简单,首先肯定最大类型的大小,这里是double,所以Max = 8,所以占据的空间就应该是8的倍数(相应的,若struct内最大的类型为int,那么占据的空间就应该是4的倍数)。补齐的大小就根据最大类型的长度来肯定。一般在内存中按照变量声明的顺序来分配空间,先为char分配,占据1byte, 8 - 1 = 7,剩余空间小于下一个变量double的须要空间,所以另外开辟一个8byte用于安放double,紧接着安放int,它占据另外一个8byte空间的4个byte。而char后面的7byte、int后面的4byte都用于内存对齐。
所以总大小为8+8+8 = 24(能够当作1+7 + 8 + 4+4)。

struct A
        {
                double d; //8byte
                char   c; //1byte
                int i; //4byte
        };

在64位g++和vs2013下运行sizeof(A)结果均为16。根据上述说明,很容易获得 8 + 1+4+3 = 16,其中3为char、int以后的补齐。

  1. 稍复杂一点的内存对其状况

class A
        {
        public:
                static double dd;
                char   c; //1byte
                double d; //8byte
                static A a;
                int i; //4byte
        };

在64位g++和vs2013下运行sizeof(A)结果均为24。这里只须要注意一下,static data member会被放在class object以外。所以sizeof(A)时,不会计算他们的大小在内。其他计算同 2 中的第一个例子相同。

  1. 只有一种类型时的状况:如一个struct中仅有一个char或int等,因为“全部的空间均须要为最大类型大小的整数倍”这个原则,struct的空间大小就是这些类型各自的大小,而不用再进行补齐。
    C/C++中的内存对齐之深刻篇——虚继承和虚函数

class A
        {
        public:
                virtual ~A();
                char   c; //1byte
                double d; //8byte
                int i; //4byte
        };

在32位g++下运行sizeof(A)结果为24,在64位g++下运行sizeof(A)结果为32,在vs2013下运行sizeof(A)结果为32。
32位g++下:一般将vptr放在object的最前面,能够肯定该内存空间与data member的内存空间不须要独立。也就是说,该例中,不管虚析构函数被声明在哪里,都会在分配空间时最早给一个vptr分配4byte的空间,且该空间后是能够紧接着分配char的1byte的空间。以此类推,结合上面的例子,能够得出4(vptr)+1(char)+3(补齐) + 8 + 4+4 = 24
64位g++下:一般将vptr放在object的最前面,没法肯定该内存空间与data member的内存空间是否须要独立。也就是说,该例中,不管虚析构函数被声明在哪里,都会在分配空间时最早给一个vptr分配8byte的空间,且不清楚该空间后是否能够紧接着分配char的1byte的空间(因为该vptr占据8byte,不管是否须要间隔,效果都同样),以此类推,结合上面的例子,能够得出

8(vptr)+ 1(char)+7(补齐) + 8 + 4+4 = 32

在vs2013下:一般将vptr放在object的最前面,vptr的大小与实际最大类型的大小相关。也就说说,该例中,不管虚析构函数被声明
在哪里,都会在分配空间时最早给一个vptr分配4byte的空间,因为后面存在double类型,须要将vptr补齐。结合上面的例子,能够得出

4(vptr)+4(补齐) + 1+7 + 8 +4+4 = 32

二、带有普通继承的class的内存对齐状况

class A
        {
                int i;    //4byte
                char c1;//1byte
        };
        class B :  public A
        {
                char c2;//1byte
        };
        class C :  public B
        {
             char c3;//1byte
        };

在64位g++下,调用sizeof(A)、sizeof(B)、sizeof(C)后的结果均为8;在vs2013下分别为8,12,16

g++下:普通继承时,派生类和基类的内存空间没有间隔。
    A:4+1+3(补齐) = 8
    B:4+1+1(c2)+2(补齐) = 8
    C:4+1+1(c2)+1(c3)+1(补齐) = 8
注意这里全部成员均为私有成员,若是改为public或protected则大小会有变化
vs2013下:普通继承时,派生类和基类的内存空间须要独立,即先补齐基类,再分配派生类。
    A:4+1+3(补齐) = 8
    B:4+1+3(补齐) + 1(c2)+3(补齐) = 12
    C:4+1+3(补齐) + 1(c2)+3(补齐) + 1(c3)+3(补齐) = 16

三、带有虚拟继承链的class的内存对齐状况

class A
        {
                int i;    //4byte
                char c1;//1byte
        };
        class B : virtual public A
        {
                char c2;//1byte
        };
        class C : virtual public B
        {
             char c3;//1byte
        };

调用sizeof(A)、sizeof(B)、sizeof(C)后,32位g++下,分别为8,16,24;64位g++下,分别为:8,24,40;vs2013下分别为8,16,24

32位g++下:
    A:仍然是4+1+3(补齐) = 8
    B:4+1+3 + 4(vptr)+1(c2)+3(补齐) = 16
    C;4+1+3 + 4(vptr)+1(c2)+3(补齐) + 4(vptr)+1(c3)+3(补齐) = 24
64位g++下:
    A:仍然是4+1+3(补齐) = 8
    B:4+1+3 + 8(vptr)+1(c2)+7(补齐) = 24
    C;4+1+3 + 8(vptr)+1(c2)+7(补齐) + 8(vptr)+1(c3)+7(补齐) = 40
vs2013下:
    A:仍然是4+1+3(补齐) = 8
    B:4+1+3 + 4(vptr)+1(c2)+3(补齐) = 16
    C;4+1+3 + 4(vptr)+1(c2)+3(补齐) + 4(vptr)+1(c3)+3(补齐) = 24
注意这里vs2013的状况表面看上去和32位g++相同,实则否则。例如去掉class B对于A的虚拟继承性
class A
        {
                int i;    //4byte
                char c1;//1byte
        };
        class B :  public A    /*注意这里跟上面相比不是虚拟继承了*/
        {
                char c2;//1byte
        };
        class C : virtual public B
        {
             char c3;//1byte
        };

调用sizeof(A)、sizeof(B)、sizeof(C)后,32位g++下:分别为8,8,16;vs2013下分别为8,12,20

32位g++下:
    A:仍然是4+1+3(补齐) = 8
    B:B:4+1+1(c2)+2(补齐) = 8(由于不是虚拟继承)
    C;4+1+1(c2)+2(补齐) + 4(vptr)+1(c3)+3(补齐) = 16
vs2013下:
    A:仍然是4+1+3(补齐) = 8
    B:4+1+3(补齐) + 1(c2)+3(补齐) = 12
    C;4+1+3(补齐) + 1(c2)+3(补齐) + 4(vptr)+1(c3)+3(补齐) = 20

虚基类的继承是C++中为了多重继承而产生的,可是虚基类的继承有带来了新的问题,如何可以实现这种动态绑定呢?


2七、为何函数参数的入栈的顺序是从右往左

由于好多函数是不定参数个数的,好比最经常使用的printf,因此须要参数的入栈顺序是从右往左。


2八、C++的多态机制

inside C++ object


2九、C++中的转化机制?各适用于什么环境?dynamic_cast转换失败时,会出现什么状况?

对指针,返回NULL.对引用,抛出bad_cast异常more Effective C++
C++引入了4种类型转化操做符(cast operator):static_cast,const_cast,dynamic_cast和reinterpret_cast,使用方法与C语言中略有不一样:

(type)expression;   //这是C语言的

而后引入C++的:

static_cast<type>(expression);//这是C++的

而后看一下各自的适用范围:
static_cast:static_cast基本上拥有与C旧式转型相同的威力和意义,以及相同的限制。可是,该类型转换操做符不能移除常量性,由于有一个专门的操做符用来移除常量性。
const_cast:用来改变表达式中的常量性(constness)或者易变形(volatileness),只能用于此功能。
dynamic_cast:将指向基类basic class object的pointer或者reference转型为指向派生类derived(或这sibling base)class object的pointer或者reference中,而且能够获知是否转型成功:若是转型失败,当转型对象是指针的时候会返回一个null指针;当转型对象是reference会抛出一个异常exception。dynamic_cast没法应用在缺少虚函数的类型上,也不能改变类型的常量性。
此外,dynamic_cast还有一个用途就是找出被对象占用的内存的起始点。
reinterpret_cast:这个操做符的转换结果几乎老是和编译器平台相关,因此不具备移植性。reinterpret_cast的最经常使用用途是转换“函数指针”类型,以下:

typedef void(*FuncPtr)();
int doSomething();
int main()
{
    FuncPtr funcPtrArray[10];
    funcPtrArray[0] = reinterpret_cast<FuncPtr>(&doSomething);
    return 0;
}

经过reinterpret_cast强迫编译器了,并成功的将不一样的类型的函数&doSomething转换为须要的类型。不过这个操做符进行的转换动做不具备移植性(C++不保证全部的函数指针都能以此方式从新呈现),某些状况下这样的转型可能会致使不正确的结果,因此这种操做不到万不得已不要使用。


30、 拷贝构造函数做用及用途?何时须要自定义拷贝构造函数?

答:

  1. 在C++中,有下面三种对象须要拷贝的状况:

    1. 一个对象以值传递的方式传入函数体

    2. 一个对象以值传递的方式从函数返回

    3. 一个对象须要经过另一个对象进行初始化
      以上的状况就须要拷贝构造函数的调用。

  2. 当类中的数据成员须要动态分配存储空间时,不能够依赖default copy constructor。当default copy constructor被因编译器须要而合成时,将执行default memberwise copy语义。此时若是类中有动态分配的存储空间时,将会发生惨重的灾情。在须要时(包括这种对象要赋值、这种对象做为函数参数要传递、函数返回值为这种对象等状况),要考虑到自定义拷贝构造函数。

----------

3一、构造函数能够调用虚函数吗?语法上经过吗?语义上能够经过吗?

不能,语法上经过,语义上有问题。
derived class对象内的base class成分会在derived class自身构造以前构造完毕。所以,在base class的构造函数中执行的virtual函数将会是base class的版本,决不会是derived class的版本。
即便目前确实正在构造derived class。


3二、深拷贝和浅拷贝的区别

答:浅拷贝:若是在类中没有显式地声明一个拷贝构造函数,那么,编译器将会根据须要生成一个默认的拷贝构造函数,完成对象之间的位拷贝。default memberwise copy即称为浅拷贝。
此处须要注意,并不是像大多数人认为的“若是class未定义出copy constructor,那么编译器就会为之合成一个执行default memberwise copy语义的copy constructor”。
一般状况下,只有在default copy constructor被视为trivial时,才会发生上述状况。一个class,若是既没有任何base/member class含有copy constructor,也没有任何virtual base class或 virtual functions,
它就会被视为trivial。
一般状况下,浅拷贝是够用的。
深拷贝:然而在某些情况下,类内成员变量须要动态开辟堆内存,若是实行位拷贝,也就是把对象里的值彻底复制给另外一个对象,如A=B。
这时,若是B中有一个成员变量指针已经申请了内存,那A中的那个成员变量也指向同一块内存。若是此时B中执行析构函数释放掉指向那一块堆的指针,这时A内的指针就将成为悬挂指针。
所以,这种状况下不能简单地复制指针,而应该复制“资源”,也就是再从新开辟一块一样大小的内存空间。


3三、动态绑定和静态绑定的区别

  1. 对象的静态类型:对象在声明时采用的类型。是在编译期肯定的。

  2. 对象的动态类型:目前所指对象的类型。是在运行期决定的。对象的动态类型能够更改,可是静态类型没法更改。

  3. 静态绑定:绑定的是对象的静态类型,某特性(好比函数)依赖于对象的静态类型,发生在编译期。

  4. 动态绑定:绑定的是对象的动态类型,某特性(好比函数)依赖于对象的动态类型,发生在运行期。


3四、函数模板特化与重载决议中的陷阱

这个题目的答案来自张教主的整理
1:

template<typename T> void f(T);/* a */          
template<typename T> void f(T*);/* b */         
template< > void f<int>(int*);/* c */           
int* p;                                         
f(p);

2:

template<typename T> void f(T);/* a */
template< > void f<int*>(int*);/* b */
template<typename T> void f(T*);/* c */
int* p;
f(p);

如今请问1和2中的f(p)分别会调用a、b、c中的哪个?
解答:
若是你认为1中的f(p)调用的是c的话,恭喜答对了。然而若是按绝大部分人的想法再来回答2中的调用状况时,他会回答调用b(也就是和1中同样的模板,这里
顺序交换了一下编号也换了,请注意),那么狠抱歉,错了。
分析前先回顾一下模板特化的东西。

  1. 非特化的模板也被称为主模板;

  2. 类模板能全特化和偏特化;

  3. 函数模板只能全特化,不过因为函数重载的缘由,能达到偏特化的效果。

如今分析以下:
对于第1个:

template<typename T> void f(T);/* a */          
    template<typename T> void f(T*);/* b */        
    template< > void f<int>(int*);/* c */          
    int* p;                                        
    f(p);

这里,a是第一个主模板,b是第二个主模板,且b是第一个主模板a的重载而非偏特化(函数模板没有偏特化)。c是第二个主模板b的显式特化(全特化)。
在f(p)调用时,发生重载决议,会无视特化存在(标准规定:重载决议无视模板特化,重载决议只会发生在主模板之间)。在主模板a和b中决议出b,即第二个主模板被决议选中,而后再调用其全特化版本c。
对于第2个:

template<typename T> void f(T);/* a */          
    template< > void f<int*>(int*);/* b */        
    template<typename T> void f(T*);/* c */          
    int* p;                                        
    f(p);

这里a是第一个主模板,b是第一个主模板a的全特化,c是第二个主模板。在f(p)调用时,发生重载决议,一样会无视特化存在,在主模板a和c中决议出c,而c并没有全特化版本,所以直接调用c。


3五、virtual函数能声明为内联吗?为何?

答:一般状况下是不能的
缘由:inline是编译期决定,他意味着在执行前就将调用动做替换为被调用函数的本体;
virtual是运行期决定,他意味着直道运行期才决定调用哪一个函数。
这二者之间一般是冲突的。
然而也有特例,就是当编译阶段就已经知道调用虚函数的指针为多态指针。这里就再也不敖述了。


3六、哪些类型的对象不能够做为union的成员?为何有这种限制?

答: 标准规定,凡是具备non-trivial constructor、non-trivial destructor、non-trivial copy constructor、non-trivial assignment operator的class对象都不能做为union的成员。
便是说,这个class的以上四种成员必须均经由编译器合成且该class无虚函数和虚基类。
有这种限制是为了兼容C。


3七、C++11有哪些进步?有哪些新的东西?

lambda
线程库
智能指针
auto


3八、如何实现一个不能在堆分配的类,如何实现一个不能被继承的类

如何实现一个不能在堆上分配的类,若是要在堆上分配就是会使用new,因此能够重载new 操做符,并将其重载于class A的private内:

class A 
{
public:
    A(int a):_x(a){}
    int Display() {
        return _x;
    }
    void setVal(int x) {
        _x = x;
        return;
    }
private:
    //
    int _x;
    void* operator new(size_t t){
    }
};

如何实现一个不能被继承的类,这里有一个比较简单的方法,利用C++11的新关键字final:

class B final {
public:
    B(int a) {
    }
};

如今就不能继承该类。


下面是第二期的地址:http://blog.csdn.net/charles_r_chiu/article/details/48227281该期正在撰写~~~~最近找工做好累啊

相关文章
相关标签/搜索