360面试准备

360面试准备

  1. C++新特性

1)lambda表达式
for_each (array, array + SIZE,
[] (int a){ cout << a << ” “; });
lambda 表达式可以方便地构造匿名函数,如果你的代码里面存在大量的小函数,而这些函数一般只被调用一次,那么不妨将他们重构成 lambda 表达式
作用:使得代码更加简洁

2)自动类型推导和 decltype
auto x=0, 0是int类型,故x也是int类型
auto ci = vi.begin();

decltype用于从对象或表达式中俘获类型,如:
const vector vi;
typedef decltype (vi.begin()) CIT;
CIT another_const_iterator;

3)统一的初始化语法
class C
{
int a;
int b;
public:
C(int i, int j);
};
C c {0,0}; //C++11 only. 相当于 C c(0,0);
int* a = new int[3] { 1, 2, 0 }; /C++11 only
class X {
int a[4];
public:
X() : a{1,2,3,4} {} //C++11, 初始化数组成员
};

4)还有一大好事就是对于容器来说,终于可以摆脱 push_back() 调用了,C++11中可以直观地初始化容器了:
// C++11 container initializer
vector vs={ “first”, “second”, “third”};
map singers =
{ {“Lady Gaga”, “+1 (212) 555-7890”},
{“Beyonce Knowles”, “+1 (212) 555-0987”}};

5)C++11 标准的两个新特性:defaulted 和 deleted 函数。
对于 defaulted 函数,编译器会为其自动生成默认的函数定义体,从而获得更高的代码执行效率,
也可免除程序员手动定义该函数的工作量。对于 deleted 函数, 编译器会对其禁用,
从而避免某些非法的函数调用或者类型转换,从而提高代码的安全性。

struct A
{
A()=default; //C++11
virtual ~A()=default; //C++11
};

struct NoCopy
{
NoCopy & operator =( const NoCopy & ) = delete;
NoCopy ( const NoCopy & ) = delete;
};
NoCopy a;
NoCopy b(a); //编译错误,拷贝构造函数是 deleted 函数

6)nullptr类型
nullptr 是一个新的 C++ 关键字,它是空指针常量,它是用来替代高风险的 NULL 宏和 0 字面量的。nullptr 是强类型的

void f(int); //#1
void f(char *);//#2
//C++03
f(0); //调用的是哪个 f?
//C++11
f(nullptr) //毫无疑问,调用的是 #2

所有跟指针有关的地方都可以用 nullptr,包括函数指针和成员指针

const char *pc=str.c_str(); //data pointers
if (pc!=nullptr)
cout<

   例如:

                    std::unique_ptr<int> p1(new int(5));
                    std::unique_ptr<int> p2 = p1; // 编译会出错
                    std::unique_ptr<int> p3 = std::move(p1); // 转移所有权, 现在那块内存归p3所有, p1成为无效的指针.

C++11或boost的shared_ptr,基于引用计数的智能指针。可随意赋值,直到内存的引用计数为0的时候这个内存会被释放。

C++11或boost的weak_ptr,弱引用。 引用计数有一个问题就是互相引用形成环,这样两个指针指向的内存都无法释放。
需要手动打破循环引用或使用weak_ptr。顾名思义,weak_ptr是一个弱引用,只引用,不计数。
如果一块内存被shared_ptr和weak_ptr同时引用,当所有shared_ptr析构了之后,不管还有没有weak_ptr引用该内存,
内存也会被释放。所以weak_ptr不保证它指向的内存一定是有效的,在使用之前需要检查weak_ptr是否为空指针。

3),智能指针的简单实现。

template <class T> 
class myAutoPtr
{
private:
    T *myPtr;

public:
    explicit myAutoPtr( T *p = 0 )
    {
        myPtr = p;
    }

    myAutoPtr &operator=( myAutoPtr &a )
    {
        if( this == &a )
        {
            return *this;
        }

        delete myPtr;
        myPtr = a.relase();
        return *this;
    }

    ~myAutoPtr()
    {
        delete myPtr;
    }

    T& operator*()
    {
        return *myPtr;
    }

    T* operator->()
    {
        return myPtr;
    }

    T *get()
    {
        return myPtr;
    }

    T *relase()
    {
        T *tmp = myPtr;
        myPtr = NULL;
        return tmp;
    }

    void reset( T *p )
    {
        if( p != myPtr )
        {
            delete myPtr;
            myPtr = p;
        }

        return;
    }


};

5.非递归求树的高度,链表逆序

typedef struct Node
{
    int data;
    struct Node *left;
    struct Node *right;

    struct Node()
    {
        data = 0;
        left = right = NULL;
    }

}Node, *pNode;

int treeDepth( pNode root )
{
    int depth = 0;

    if( NULL == root )
    {
        return depth;
    }

    queue<pNode> qu;
    qu.push(root);

    while( !qu.empty() )
    {
        int curSize = qu.size();
        depth++;

        for( int i = 0; i < curSize; i++ )
        {
            pNode cur = qu.front();
            qu.pop();

            if( cur->left != NULL )
            {
                qu.push( cur->left );
            }

            if( cur->right != NULL )
            {
                qu.push( cur->right );
            }

        }
    }

    return depth;
}


// 逆序一个链表
Node *reverseLink( Node *head )
{
    if( NULL == head )
    {
        return head;
    }

    Node *cur = NULL;

    while( head != NULL )
    {
        Node *pNext = head->next;
        head->next = cur;
        cur = head;
        head = pNext;
    }

    return cur;
}

  1. 红黑树相关
    1)红黑树的数据结构定义
enum Color
{
    RED = 0,
    BLACK = 1
};

struct redBlackNode
{
    int key;
    int data;
    struct redBlackNode *left;
    struct redBlackNode *right;
    Color color;
};

2)红黑树性质
节点颜色为红色或黑色,根节点为黑色,叶子节点(Nil,指树尾端NIL指针或NULL结点)为黑色,红节点的子节点为黑色,对于任一节点,其到叶子叶子节点的任一路径含有的黑节点数相同;

3)时间复杂度:能保证在最坏情况下,时间复杂度为O(lgn)

4)红黑树相比于BST和AVL树有什么优点?

红黑树是牺牲了严格的高度平衡的优越条件为代价,它只要求部分地达到平衡要求,降低了对旋转的要求,
从而提高了性能。红黑树能够以O(log2 n)的时间复杂度进行搜索、插入、删除操作。此外,由于它的设计
,任何不平衡都会在三次旋转之内解决。当然,还有一些更好的,但实现起来更复杂的数据结构能够做到
一步旋转之内达到平衡,但红黑树能够给我们一个比较“便宜”的解决方案。

相比于BST,因为红黑树可以能确保树的最长路径不大于两倍的最短路径的长度,所以可以看出它的查找效果
是有最低保证的。在最坏的情况下也可以保证O(logN)的,这是要好于二叉查找树的。因为二叉查找树最坏情
况可以让查找达到O(N)。

红黑树的算法时间复杂度和AVL相同,但统计性能比AVL树更高,所以在插入和删除中所做的后期维护操作肯定
会比红黑树要耗时好多,但是他们的查找效率都是O(logN),所以红黑树应用还是高于AVL树的. 实际上插入 AVL
树和红黑树的速度取决于你所插入的数据.如果你的数据分布较好,则比较宜于采用 AVL树(例如随机产生系列数),
但是如果你想处理比较杂乱的情况,则红黑树是比较快的

5)红黑树相对于哈希表,在选择使用的时候有什么依据?
权衡三个因素: 查找速度, 数据量, 内存使用,可扩展性。
重于查找速度,内存消耗小:用hash
可扩展性:用红黑树
如何数据基本是静态的,用红黑树,如果数据完全是静态的,做一个哈希表,性能可能会更好一些。

但若你对内存使用特别严格, 希望程序尽可能少消耗内存,那么一定要小心,hash可能会让你陷入尴尬,
特别是当你的hash对象特别多时,你就更无法控制了而且 hash的构造速度较慢。

6).如何扩展红黑树来获得比某个结点小的元素有多少个?
每个节点添加一个size域,表示以该节点x为根的子树的节点数大小

1)找到树中第i小的节点

findIthNode( x, i)
{
  r = size[ left[x] ] + 1;

  if( i == r )
    return x;
  else if( i < r )
        return findIthNode( left[x], i )
  else  return findIthNode( right[x], i );

}思路:size[left[x]]表示在对x为根的子树进行中序遍历时排在x之前的个数,递归调用的深度不会超过O(lgn);

2).确定某个结点之前有多少个结点,也就是我们要解决的问题;

OS-Rank( T, x )
{
   r = x.left.size + 1;
   y = x;

   while( y != T.root )
   {
    if( y == y.p.right )
        r = r + y.p.left.size + 1;

        y = y.p;
   }

   return r;
}

思路:x的秩可以视为在对树的中序遍历种,排在x之前的结点个数加上一。最坏情况下,OS-RANK运行时间与树高成正比,所以为O (lgn).

7.扩展数据结构有什么步骤?
1).选择基础数据结构;

2).确定要在基础数据结构种添加哪些信息;

3).验证可用基础数据结构上的基本修改操作来维护这些新添加的信息;

4).设计新的操作。

8.为什么一般hashtable的桶数会取一个素数

设有一个哈希函数
H( c ) = c % N;
当N取一个合数时,最简单的例子是取2^n,比如说取2^3=8,这时候
H( 11100(二进制) ) = H( 28 ) = 4
H( 10100(二进制) ) = H( 20 )= 4

这时候c的二进制第4位(从右向左数)就”失效”了,也就是说,无论第c的4位取什么值,都会导致H( c )的值一样.这时候c的第四位就根本不参与H( c )的运算,这样H( c )就无法完整地反映c的特性,增大了导致冲突的几率.

取其他合数时,都会不同程度的导致c的某些位”失效”,从而在一些常见应用中导致冲突.
但是取质数,基本可以保证c的每一位都参与H( c )的运算,从而在常见应用中减小冲突几率..

8.线程池
一个线程池包括以下四个基本组成部分:
1、线程池管理器(ThreadPool):用于创建并管理线程池,包括 创建线程池,销毁线程池,添加新任务;
2、工作线程(PoolWorker):线程池中线程,在没有任务时处于等待状态,可以循环的执行任务;
3、任务接口(Task):每个任务必须实现的接口,以供工作线程调度任务的执行,它主要规定了任务的入口,
任务执行完后的收尾工作,任务的执行状态等;
4、任务队列(taskQueue):用于存放没有处理的任务。提供一种缓冲机制。

  1. hash处理冲突的方法
    1)再散列法,基本思想是:当关键字的哈希地址出现冲突时,以p=H(key)为基础,产生另外一个哈希地址p1,一直找到不冲突的哈希
    地址pi为止,如:线性探测法,二次探测法

2)再哈希法,同时构造多个不同的哈希函数,第一个哈希函数出现冲突,用第二个函数函数;

3)链地址法

4)建立公共溢出区,将哈希表分为基本表和溢出表,凡是和基本表发生冲突的元素,一律填入溢出表;

  1. 内存泄露的情况?如何检测内存泄露?
    内存泄漏的原因:分配了未释放;程序代码有问题,如:
    Temp1 = new BYTE[100];
    Temp2 = new BYTE[100];
    Temp2 = Temp1;
    某些API函数操作不正确

如何检测:
1)包括手动检测和静态工具分析,代码静态扫描和分析的工具比较多,比如 splint, PC-LINT, BEAM 等
2)动态运行检测,实时检测工具主要有 valgrind, Rational purify 等

  1. Nginx是一个轻量级,高性能的HTTP和反向代理服务器,也是一个IMAP/POP3/SMTP服务器,
    因它的稳定性、丰富的功能集、示例配置文件和低系统资源的消耗而闻名。

特点:热部署(在不停止服务的情况下可以升级ngnix的可执行文件,修改配置文件),可以高并发连接,占用内存少,
处理响应请求很快,具有很高的可靠性。

  1. DOS的攻击原理,以及与DDOS的区别
    DOS攻击原理:
    1)发生大量SYN标志的TCP请求,导致服务器端的缓冲区队列满了,导致无法服务其他正常请求;
    2)利用IP欺骗,迫使服务器将正常合法用户的连接复位,影响合法用户的连接。
    这种攻击利用RST位来实现。假设现在有一个合法用户(1.1.1.1)已经同服务器建立了正常的连接,
    攻击者构造攻击的TCP数据,伪装自己的IP为1.1.1.1,并向服务器发送一个带有RST位的TCP数据段。
    服务器接收到这样的数据后,认为从1.1.1.1发送的连接有错误,就会清空缓冲区中建立好的连接。
    这时,如果合法用户1.1.1.1再发送合法数据,服务器就已经没有这样的连接了,该用户就必须从新开始建立连接。
    3)带宽DOS攻击, 消耗服务器的带宽;
    4)塞满服务器的硬盘,如果web服务器和邮件服务器在同一台机器上,我就发送大量垃圾邮件,消耗其硬盘空间;

分布式拒绝服务(DDoS:Distributed Denial of Service)攻击指借助于客户/服务器技术,将多个计算机联合起来作为攻击平台,
对一个或多个目标发动DDoS攻击,从而成倍地提高拒绝服务攻击的威力。

区别:DDOS是DOS攻击中的一种方法,DOS攻击一般是一对一攻击,而DDOS攻击是多台机器同时向一个或多个目标进行攻击,
就是控制多台电脑对同一目标进行DOS攻击。

  1. DMA原理:
    原理
    一个设备接口试图通过总线直接向另一个设备发送数据(一般是大批量的数据),
    它会先向CPU发送DMA请求信号。外设通过DMA的一种专门接口电路――DMA控制器(DMAC),
    向CPU提出接管总线控制权的总线请求,CPU收到该信号后,在当前的总线周期结束后,
    会按DMA信号的优先级和提出DMA请求的先后顺序响应DMA信号。CPU对某个设备接口响应DMA请求时,
    会让出总线控制权。于是在DMA控制器的管理下,外设和存储器直接进行数据交换,而不需CPU干预。
    数据传送完毕后,设备接口会向CPU发送DMA结束信号,交还总线控制权。

基本操作
实现DMA传送的基本操作如下:
1、外设可通过DMA控制器向CPU发出DMA请求;
2、CPU响应DMA请求,系统转变为DMA工作方式,并把总线控制权交给DMA控制器;
3、由DMA控制器发送存储器地址,并决定传送数据块的长度;
4、执行DMA传送;
5、DMA操作结束,并把总线控制权交还CPU。

  1. 键盘上输入字符到屏幕上显示它,期间发生了什么?
    键盘扫描,扫描码,产生中断,有一个中断号,查找中断向量,键盘的中断处理程序,将扫描码转换为ASCII码,然后就是显示的过程,通过bios调用,
    字符发生器将该字符以及属性转换为一个光点矩阵,该光点矩阵可以作为显示器的输入信号。每个点都有3把电子枪发射(分别发rgb)的光叠加成的。

15.Unix/Windows中进程通信的几种方式

现在linux使用的进程间通信方式:
(1)管道(pipe)和有名管道(FIFO)
(2)信号(signal)
(3)消息队列
(4)共享内存
(5)信号量
(6)套接字(socket)

Windows中进程通信的几种方式
1)剪贴板
2)文件映射
3)使用共享内存方式
4)windows套接字
5)管道
6)邮件槽

  1. 临界区与信号量的区别
    保证在某一时刻只有一个线程能访问数据的简便办法。在任意时刻只允许一个线程对共享资源进行访问。
    信号允许多个线程同时使用共享资源 ,这与操作系统中的PV操作相同。它指出了同时访问共享 资源的线程 最大数目。
    它允许多个线程在同一时刻访问同一资源,但是需要限制在同一时刻访问此资源的最大线程数目。

  2. BST树转换为双向链表

  3. 单例模式实现

class CSingleInstance
{
private:
    CSingleInstance(){}
    CSingleInstance( const CSingleInstance & ){}
    CSingleInstance& operator=( const CSingleInstance & ){}
    static CSingleInstance *pInstance;

public:
    static CSingleInstance *getInstance()
    {
        return pInstance;
    }
};

CSingleInstance * CSingleInstance::pInstance = new CSingleInstance();
  1. string类简单实现
    assert(index>=0 && index<=strLength);

  2. free和delete如何知道应该释放多少内存
    分配内存的算法有关,依赖于实现,在分配每块内存的时候,都有额外的空间来记录分配内存的大小的
    用前面四个字节来保存大小
    分配时会簿记,删除时先查询是否存在
    一般的做法是在分配的内存前边加一个长度值,假如你申请4字节的内存,那系统很可能是分配了8字节,然后4字节记录长度,
    另4字节给你用。但这个是compiler specific的,也不排除有编译器会使用查表法或其它更高明的办法。

21.new和malloc的区别
运算符和函数的区别
返回值不同,一个需要强制转换,一个不需要
是否调用构造函数
分配失败一个抛出异常,一个不抛出而返回NULL
new自动计算需要分配的空间,而malloc需要手工计算字节数
new是类型安全的,而malloc不是,比如:
int* p = new float[2]; // 编译时指出错误
int* p = malloc(2*sizeof(float)); // 编译时无法指出错误

既然new/delete的功能完全覆盖了malloc/free,为什么C++还保留malloc/free呢?因为C++程序经常要调用C函数,
而C程序只能用malloc/free管理动态内存。

  1. 堆和栈的区别
    堆区,栈区,全局区,代码区,常量区

23.写个类A,声明类A指针指向NULL,调用类A的方法会有什么后果,编译通过吗?
可以编译通过,但是运行时,如果调用的是普通函数,如果普通函数中访问了非静态成员变量,则运行时会出现
错误,没有访问成员变量或访问的是静态成员变量的话,正常调用而没有错误,但是如果调用的是是virtual函数,
则会出现运行时错误。
原因:普通函数放在全局内存区中,可以访问,虚函数是通过虚函数表来调用的,首先要查找到虚函数表,而
指向虚函数表的指针存放在对象中,又此时没有对象生成,故出现错误;

  1. sizeof空类为多少?为什么这么设计?
    为1,因为空类也可以实例化,我们可以对其取地址,故要分配一个字节,如果sizeof(A)等于0则无法分
    配内存来在运行时区分多个同类型的对象

25.vim的替换语句,abc换成def
语法为 :[addr]s/源字符串/目的字符串/[option]
全局替换命令为::%s/源字符串/目的字符串/g

  1. 疑问:
    已解决:2^31表示2与31异或,而不是2的31次方,2^31-3按照优先级应该为: 2^(31-3)
#include <iostream>
using namespace std;


void func( int m )
{
    cout << m << endl;
    return;
}

int main()
{
    int ret = 0;
    int n = 2^31;
    cout << sizeof(n) << endl; // 4

    long long n1 = 2^31; 
    cout << sizeof(n1) << endl; // 8

    cout << n1 << endl; // 29
    cout << n << endl;  // 29
    func( 2^31 - 3 );  // 30

    return ret;
}

==================================================================================
360 企业安全集团,服务端开发工程师-C++——珠海
本来视频面试,后来他说他那边有问题,就电话面试
1. 自我介绍
2. 问了下hadoop, 研究生有学过没,storm和spark的区别
3. 讲下memcached, memcached集群中有一个节点失效了,怎么处理,哈希一致性算法实现;
4. 看了libevent,讲下同步,异步,阻塞,非阻塞的区别
5. 有了解服务端开发的相关技术吗,讲下;
6. 有github账号吗,有看源代码吗
7. 问了我喜欢做服务端的哪方面;
8. 有用过哪些数据库没,我说研究生期间没有
9. 最后他说没有什么问题 ,就结束了,全程14分钟左右。

阅读更多
换一批

没有更多推荐了,返回首页