c++常见面试问题

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

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


2、基类为什么需要虚析构函数?

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


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。这里有很多的经典笔试题,一起来观摩下:

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() 返回在重新进行内存分配以前所能容纳的元素数量。


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

答: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());
}

如上就是。


6、 auto_ptr能作为vector的元素吗?为什么?

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


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

答:通常在类外初始化static数据成员,但是 static const 的整型(bool,char,int,long)可以再类声明中初始化,static const的其他类型也必须在类外初始化(包括整型的数组)。


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

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

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

  2. 使用智能指针。


9、 为什么需要private继承?

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

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

10、如何实现单例模式?如何避免发生对象的用户复制行为?如何实现线程安全的单例模式?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解决,他能将读写操作同步到易变数据中,但这样在多线程环境下仍旧存在问题。


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

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


12、如何实现仿函数?为什么需要通过继承自unary_function 或者 binary_function来实现仿函数?

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

posted @ 2016-12-12 17:14  叫我二当家  阅读(265)  评论(0)    收藏  举报