面试学习总结

1.使用struct和class定义类以及类外的继承有什么区别?html

  1)使用struct定义结构体它的默认数据成员类型是public,而class关键字定义类时默认成员时private.react

  2) 在使用继承的时候,struct定义类时它的派生类的默认具备public继承,而class的派生类默认是private继承。linux

 

2.派生类和虚函数概述?c++

1)基类中定义虚函数的目的是想在派生类中从新定义函数的新功能,若是派生类中没有从新对虚函数定义,那么使用派生类对象调用这个虚函数的时候,默认使用基类的虚函数。程序员

2)派生类中定义的函数必须和基类中的函数定义彻底相同。算法

3)基类中定义了虚函数,那么在派生类中一样也是。shell

 

3.虚函数和纯虚函数的区别?数据库

  1)虚函数和纯虚函数都使用关键字virtual。若是在基类中定义一个纯虚函数,那么这个基类是一个抽象基类,也称为虚基类,不能定义对象,只能被继承。当被继承时,必须在派生类中从新定义纯虚函数。编程

class Animal
{
     public:
         virtual void getColol() = 0; // 纯虚函数
};

class Animal
{
    public:
         virtual void getColor(); // 虚函数
};

4.深拷贝和浅拷贝的区别网页爬虫

  举个栗子~:

浅拷贝:

char p[] = "hello";
char *p1;
p1 = p;

深拷贝:

char p[] = "hello";
char *p1 = new char[];
p1 = p;

解释:

   浅拷贝就是拷贝以后两个指针指向同一个地址,只是对指针的拷贝。

   深拷贝是通过拷贝后,两个指针指向两个地址,且拷贝其内容。

2)浅拷贝可能出现的问题

   a. 由于两个指针指向同一个地址,因此在一个指针改变其内容后,另外一个也可能会发生改变。

   b.浅拷贝只是拷贝了指针而已,使得两个指针指向同一个地址,因此当对象结束其生命期调用析构函数时,可能会对同一个资源释放两次,形成程序奔溃。

 

4.STL中vector实现原理,是1.5倍仍是两倍?各有什么优缺点?

    vector是顺序容器,他是一个动态数组。

    1.5倍优点:能够重用以前分配但已经释放的内存。

    2倍优点: 每次申请的内存都不能够重用

5.STL中map,set的实现原理。

   map是一个关联容器,它是以键值对的形式存储的。它的底层是用红黑树实现。平均查找复杂度是O(logn)

   set也是一个关联容器,它也是以键值对的形式存储,但键值对都相同。底层也是以红黑树实现。平均查找复杂度O(logn)。

6. C++特色是什么,多态实现机制?多态做用?两个必须条件?

    C++中多态机制主要体如今两个方面,一个是函数的重载,一个是接口的重写。接口多态指的是“一个接口的多种形态”。每个对象内部都有一个虚表指针,该虚表指针被初始化为本类的虚表。因此在程序中,无论对象类型如何转换,但该对象内部的虚表指针始终不变,才能实现动态的对象函数调用,这是c++多态的实现原理。

   多态的基础是继承,须要虚函数的支持。子类继承父类的大部分资源,不能继承的有构造函数,析构函数,拷贝构造函数,operator = ,友元函数等。

做用:

     1.隐藏实现细节,代码可以模块化。 2.实现接口重用:为了类在继承和派生过程当中正确调用。

两个必要条件:

     1.虚函数 2.一个基类的指针或者引用指向子类对象。

7.多重继承有什么问题?怎样消除多重继承中的二义性?

   1)增长程序的复杂性,使程序的编写和维护都比较困难,容易出错;

   2)继承类和基类的同名函数产生了二义性,同名函数不知道调用基类仍是继承类,c++ 中用虚函数解决这个问题。

   3)继承过程当中可能会继承一些没必要要的数据,可能会产生数据很长。

可使用成员限定符和虚函数解决多重继承中的函数的二义性问题。

8.什么叫动态关联,什么叫静态关联?

   多态中,程序在编译时肯定程序的执行动做叫作静态关联,在运行时才能肯定叫作动态关联。

9.那些库函数属于高危函数?为何?

   strcpy赋值到目标区间可能会形成缓冲区溢出!

10.求两个数的乘积和商数,用宏定义来实现

#define Chen(a,b) ((a) * (b))
#define Divide(a,b) ((a) / (b))

11.枚举和#define宏的区别

1)用#define定义的宏在预编译的时候进行简单替换。枚举在编译的时候肯定其值。

2)能够调试枚举常量,但不能够调试宏常量。

3)枚举能够一次定义大量的常量,而宏只能定义一个。

12.介绍下函数的重载:

   重载是对不一样类型的函数进行调用而使用相同的函数名。重载至少要在参数类型,参数个数,参数顺序中有一个不一样。

13.派生新类要通过三个步骤

   1)继承基类成员 2)改造基类成员 3)添加新成员

14.面向对象的三个基本性质?并简单描述

  1)封装  : 将客观事物抽象成类,每一个类对自身的数据和方法进行封装。

   2)继承

   3)多态  容许一个基类的指针或引用指向一个派生类的对象。

15.多态性体现都有那些?动态绑定怎么实现?

  多态性是一个接口,多种实现,是面向对象的核心。

   动态绑定是指在编译器在编译阶段不知道调用那个方法。

  编译时多态性:经过重载函数实现。

  运行时多态性:经过虚函数实现,结合动态绑定。

16.动态联编和静态联编

   静态联编是指程序在编译连接时就知道程序的执行顺序

   动态联编是指在程序运行时才知道程序调用函数的顺序。

17.为何析构函数要定义成虚函数?

    若是不将析构函数定义成虚函数,那么在释放内存的时候,编译器会使用静态联编,认为p就是一个基类指针,调用基类析构函数,这样子类对象的内存没有释放,形成内存泄露。定义成虚函数后,使用动态联编,先调用子类析构函数,再调用基类析构函数。

18.那些函数不能被定义成虚函数?

1)构造函数不能被定义成虚函数,由于构造函数须要知道全部信息才能构造对象,而虚函数只容许知道部分信息。

2)内联函数在编译时被展开,而虚函数在运行时才能动态绑定。

3)友元函数 由于不能够被继承。

4)静态成员函数 只有一个实体,不能被继承,父子都有。

19.类型转换有那些?各适用什么环境?dynamic_cast转换失败时,会出现什么状况?(对指针,返回NULL,对引用抛出bad_cast异常)

   1)  static_cast静态类型转换,适用于基本类型之间和具备继承关系的类型。

    i.e. 将double类型转换为int 类型。将子类对象转换为基类对象。

   2)常量类型转换 const_cast, 适用于去除指针变量的常量属性。没法将非指针的常量转换为普一般量。

   3)动态类型转换 dynamic_cast。运行时进行转换分析的,并不是是在编译时。dynamic_cast转换符只能用于含有虚函数的类。

   dynamic_cast用于类层次间的向上转换和向下转换,还能够用于类间的交叉转换。在类层次间进行向上转换,即子类转换为父类,此时完成的功能和static_cast相同,由于编译器默认向上转换老是安全的。向下转换时,由于dynamic_cast具备类型检查的功能,因此更加安全。类间的交叉转换指的是子类的多个父类之间指针或引用的转换。

20.为何要用static_cast而不用c语言中的类型转换?

    static_cast转换,它会检查看类型可否转换,有类型安全检查。

21.如何判断一段程序是由c编译的仍是由c++编译的?

#ifdef _cplusplus
   cout << "c++" << endl;
#else
   cout << "c" << endl;
#endif

22.操做符重载,具体如何定义?

 除了类属关系运算符 "." ,成员指针运算符 "*",做用域运算符 “'::”,sizeof运算符和三目运算符"?:"之外,C++中全部运算符均可以重载。

  <返回类型说明符>operator<运算符符号>(<参数表>){}

   重载为类的成员函数或类的非成员函数时,参数个数会不一样,应为this指针。

23.内联函数和宏定义的区别

    内联函数是用来消除函数调用时的时间开销的。频繁被调用的短小函数很是受益。

  A.宏定义不检查函数参数,返回值之类的,只是展开,相对来讲,内联函数会检查参数类型,因此更安全。

 B.宏是由预处理器替换实现的,内联函数是经过编译器控制来实现的。

24.动态分配内存和静态分配内存的区别?

   动态分配内存是使用运算符new来分配的,在堆上分配内存。

   静态分配就是像A a;这样的由编辑器来建立一个对象,在栈上分配内存。

25.explicit 是干什么用的?

  构造器,阻止不该该容许的由转换构造函数进行的隐式类型转换。explicit是用来阻止外部非正规的拷贝构造。

26.既然new/delete的功能彻底覆盖了malloc()和free()那么为何还要保留malloc/free呢?

     由于C++程序要常常调用C程序,而C程序只能使用malloc/free来进行动态内存管理。

27.头文件中#idfef/define/endif干什么用?

   预处理,防止头文件被重复使用,包括program once都这样。

28.预处理器标识#error的做用是什么?

   抛出错误提示,标识预处理宏是否被定义。

29.怎样用C编写死循环?

while(1)
{
}

for(;;)
{
}

30.用变量a给出以下定义:

  一个有10个指针的数组,该指针指向一个函数,该函数有一个整型参数并返回一个整型数 int (*a[10])(int);

31.volatile是干啥用的?(必须将cpu的寄存器缓存机制回答的很透彻)使用实例有哪些?(重点)

    1)访问寄存器比访问内存要快,编译器会优化减小内存的读取,可能会读脏数据。声明变量为volatile,编译器不在对访问该变量的代码进行优化,仍然从内存读取,使访问稳定。

总结:volatile声明的变量会影响编译器编译的结果,用volatile声明的变量表示这个变量随时均可能发生改变,与该变量有关的运算,不在编译优化,以避免出错。

  2)使用实例:(区分c程序员和嵌入式系统程序员的基本问题)

   并行设备的硬件寄存器 (如:状态寄存器)

   一个中断服务子程序会访问到的非自动变量

   多线程应用中被几个任务共享的变量

 3)一个参数既能够是volatile又能够是const吗?

  能够。好比状态寄存器,声明为volatile表示它的值可能随时改变,声明为const 表示它的值不该该被程序试图修改。

  4) 一个指针能够被声明为volatile吗?解释为何?

    能够。尽管这并非很常见。一个例子是当中断服务子程序修改一个指向一个buffer的指针时。

   下面的函数有什么错误:

   

int square(volatile int *ptr) {
return *ptr * *ptr;
}

下面是答案:
这段代码有点变态。这段代码的目的是用来返指针*ptr指向值的平方,可是,因为*ptr指向一个volatile型参数,编译器将产生相似下面的代码:

int square(volatile int *ptr){
int a,b;
a = *ptr;
b = *ptr;
return a * b;
}

因为*ptr的值可能被意想不到地该变,所以a和b多是不一样的。结果,这段代码可能返不是你所指望的平方值!正确的代码以下

int square(volatile int *ptr){
int a;
a = *ptr;
return a * a;
}

 

32.引用和指针的区别

  1)引用是直接访问,指针是间接访问

  2)引用是变量的别名,自己不单独分陪本身的内存空间。而指针有本身的内存空间。

  3)引用绑定内存空间必须赋初值。是一个变量别名,不能更改绑定,能够改变对象的值

 

33.关于动态内存分配和静态内存分配的区别:

  1)静态内存分配是在编译时完成的,不占cpu资源;动态内存分配是在运行时完成的,分配和释放占用cpu资源。

   2)静态内存分配是在栈上分配的,动态内存分配是在堆上分配的。

  3)动态内存分配须要指针或引用数据类型的支持,而静态内存分配不须要。

  4)静态内存分配是按计划分配,在编译前肯定内存块的大小,而动态内存分配是按需分配

  5)静态内存分配是把控制权交给编译器,而动态内存分配是把控制权交给程序员

  6)静态内存分配运行效率要比动态的高,由于动态内存分配在分配和释放时都须要cpu资源

 

34.分别设置和清除一个整数的第三位?

#define BIT3 (0x1 << 3)

static int a;
 
void set_3()
{
    a |= BIT3;
}

void clear_3()
{
   a &= ~BIT3;
}

 

35.memcpy函数的实现

void* my_memcpy(void *dest , const void * src, size_t size)
{
      if(dest == NULL && src == NULL)
     {
            return NULL;
     }
     char* dest1 = dest;
     char* src1 = src;
     while(size-- >0)
     {
            *dest1 = *src1;
            dest1++;
            src1++;
     }
     return dest;
}

36.strcpy函数的实现

char* my_strcpy(char* dest, const char* src)
{
     assert(dest != NULL && src != NULL);
     char* dest1 = dest;
     while((*dest++ = *src++) != '\0');
     return dest1;
}

37.strcat函数的实现

char* strcat(char* dest, const char* src)
{
      assert(dest != NULL && src != NULL);
      char* dest1 = dest;
      while(*dest != '\0' )
           dest++;
       while((*dest++ = *src++) != '\0');
      return dest1;
}

38.strncat函数的实现

char* my_strncat(char* dest, const char * src, int n)
{
      assert(dest != NULL && src != NULL);
      char* dest1  = dest;
      while(*dest != '\0')
           dest++;
      while(n--)
      {
            *dest = *src;
            dest++;
            src++;
      }
      dest++;
      *dest = '\0';
      return dest1;
}

39.strcmp函数的实现

int my_strcmp(const char* str1,const char* str2)
{
      int ret = 0;
      while(!(ret =*(unsigned char*) str1 -*(unsigned char*) str2) && *str1)
      {
              str1++;
              str2++;
      }
      if(ret < 0)
           return -1;
      else if(ret >0)
           return 1;
      else
           return ret;
}

40.strncmp函数的实现

int my_strncmp(const char* str1, const char* str2,int n)
{
       assert(str1!= NULL && str2 != NULL);
       while(*str1 && *str2 && (*str1 == *str2) && n--)
       {
              str1++;
              str2++;
       }
       int ret = *str1 - *str2;
       if(ret >0)
            return 1;
       else if(ret < 0)
            return -1;
       else return ret;
}

41.strlen函数的实现

int my_strlen(const char *str)
{
       assert(str != NULL);
       int len = 0;
       while(*str++ != '\0')
       {
            len++;
       }
       return len;
}

42.strstr函数的实现

char* my_strstr(const char* str1, const char* str2)
{
       int len2;
       if(!(len2 = strlen(str2)))
             return (char *)str1;
       for(;*str1; str1++)
       {
              if(*str1 == *str2 && strncmp(str1,str2,len))
                    return (char *) str1;
       }
       return NULL;
}

43.String类的实现

class String
{
      public:
          String(const char *str = NULL);
          String(const String &other);
          ~String();
          String & operator = (const String &other);
          String & operator + (const String &other);
          bool operator == (const String &other);
          int getlength();
     private:
           char *m_data; 
};

String ::String(const char* str)
{
        if(str == NULL)
        {
                m_data = new char[1];
                m_data = '\0';
        }
        else
        {
               m_data = new char[strlen(str)+1];
               strcpy(m_data,str);
        }
}

String :: String(const String &other)
{
     if(!other.m_data)
    {
         m_data = 0;
    }
    else
    {
          m_data = new char[strlen(other.m_data)+1];
          strcpy(m_data,other.m_data);
    }  
}

String :: ~String()
{
      if(m_data)
      {
            delete[] m_data;
            m_data = 0;
      }
}

String & String :: operator = (const String & other)
{
       if(this != &other)
       {
             delete[] m_data;
             if(!other.m_data)
             {
                   m_data = 0;
             }
             else
             {
                   m_data = new char[strlen(other.m_data)+ 1];
                   strcpy(m_data, other.m_data);
             }
       }
       return *this;
}

String & String :: operator + (const String & other)
{
           String newString;
           if(!other)
           {
                 newString = *this;
           }
           else if(!m_data)
           {
                 newString = other;
           }
           else
           {
                   m_data = new char[strlen(m_data) + strlen(other.m_data) + 1];
                   strcpy(newString.m_data,m_data);
                   strcat(newString.m_data,other.m_data);
           }
           return newString;
}

bool String::operator == (const String &other)
{
          if(strlen(m_data) != strlen(other.m_data))
          {
                  return false;
          }
          else
          {
                  return strcmp(m_data,other.m_data) ? false : true;
          }
}

int String :: getlengrh()
{
          return strlen(m_data);
}

 

44.sizeof()的大小

  http://www.myexception.cn/program/1666528.html

 

45.do{}while(0);的用法有那些?

  1)能够将语句做为一个独立的域  2)对于多语句能够正常访问  3)能够有效的消除goto语句,达到跳转语句的效果。

46.请用c/c++实现字符串反转(不调用库函数)“abc”类型的

char* reverse_str(char * str)
{
      if(str == NULL)
      {
             return NULL;
      }
      char* begin;
      char* end;
      begin = end = str;
      while(*end != '\0')
      {
            end++;
      }
      --end;
      while(begin < end)
      {
              char tmp = *begin;
              *begin = *end;
              *end = tmp;
              begin++;
              end--;
      }
      return str;
}

 二.服务器编程

   1.多线程和多进程的区别(重点必须从cpu调度,上下文切换,数据共享,多核cpu利用率,资源占用等方面来回答。有一个问题必会问到,哪些东西是线程私有的,答案中必须包含寄存器!!)

  1)进程数据是分开的:共享复杂,须要用IPC,同步简单; 多线程共享进程数据:共享简单,同步复杂。

  2)进程建立销毁,切换复杂。速度慢;线程建立销毁,切换简单,速度快。

  3)进程占用内存多,cpu利用率低;线程占用内存少,cpu利用率高;

  4)进程编程简单,调试简单;线程编程复杂,调试复杂;

  5)进程间不会相互影响,但当一个线程挂掉将致使整个进程死掉

  6)进程适用于多核,多机分布;线程适用于多核

线程所私有的: 线程id,寄存器的值,线程的优先级和调度策略,栈,线程的私有数据,error变量,信号屏蔽字。

 

   2.多线程锁的种类有那些?

    1)互斥锁(mutex) 2)递归锁 3)自旋锁 4)读写锁

   自旋锁(spinlock):是指当一个线程在获取锁的时候,若是锁已经被其它线程获取,那么该线程将循环等待,而后不断的判断锁是否可以被成功获取,直到获取到锁才会退出循环。

   3.自旋锁和互斥锁的区别?

       自旋锁:当锁被其余线程占用时,其它线程并非睡眠状态,而是不停地消耗cpu,获取锁;互斥锁则否则,保持睡眠,直到互斥锁被释放唤醒。

        自旋锁递归调用容易形成死锁,对长时间才能得到锁的状况,容易形成cpu利用率低,只有内核可抢占式或SMP状况下才真正须要自旋锁。

 

   4.进程间通讯:

     1)管道  2)命名管道 3)消息队列  4)共享内存  5)信号量 6)套接字

 

    5.多线程程序架构,线程数量应该如何设置?

        应尽可能和cpu核数相等,或者为cpu核数+1的个数。

    

    6.网络编程设计模式,reactor/proactor/半同步半异步模式?

       reactor模式:同步阻塞I/O模式,注册对应读写事件处理器,等待事件发生,进而调用事件处理器处理事件。

       proactor模式:异步I/O模式。

       reactor模式和proactor模式的主要区别是读取和写入是由谁来完成的。reactor模式须要应用程序本身读取或者写入数据,proactor模式中,应用程序不须要实际读写的过程。

        半同步半异步模式:

            上层的任务(如数据库查询,文件传输)经过同步I/O模型,简化了编写并行程序的难度。

            底层的任务(如网络控制器的中断处理)经过异步I/O模型,提供了更好的效率。

    

    7.有一个计数器,多个线程都须要更新,会遇到什么问题,缘由是什么应该如何作,怎样优化?

         有可能一个线程更新的数据已经被另外一个线程更新了,读脏数据/丢失修改,更新的数据出现异常。能够经过加锁来解决,保证数据更新只会被一个线程更新。

    

    8.若是select返回可读,结果只读到0字节,什么状况?

         某个套接字集合中没有准备好,可能会select内存用FD_CLR清为0。

 

    9.CONNECT可能会长时间阻塞,怎么解决?

         1)使用定时器,最经常使用也是最有效的一种方法。

         2)使用非阻塞;设置非阻塞,返回以后使用select检测状态。

 

    10.keepalive是什么东西?如何使用?

       keepalive,是在tcp中能够检测死链接的机制。

      1)若是主机可达,对方就会响应ACK应答,就认为链接是存活的。

      2)若是可达,但应用程序退出,对方就发RST应答,发送TCP撤销链接。

      3)若是可达,但应用程序崩溃,对方就发FIN应答。

      4)若是对方主机不响应ACK,RST,那么继续发送直到超时,就撤销链接。默认2小时。

 

  11.udp调用connect有什么做用?

    1)由于udp能够是一对一,一对多,多对一,多对多的通讯,因此每次调用sendto/recvfrom都须要指定目标ip地址和端口号。经过调用connect函数创建一个端到端链接,就能够和tcp同样使用send()/recv()传递数据,而不须要每次都指定目标IP地址和端口号。可是它和TCP不一样的是他不须要三次握手的过程。

    2)能够经过在已经创建UDP链接的基础上,调用connect()来指定新的目标IP地址和端口号以及断开链接。

  

  12.socket编程,若是client断电了,服务器如何快速知道?

    1)使用定时器(适合有数据流动的状况)

    2)使用socket选项SO_KEEPALIVE(适合没有数据流动的状况)

       a. 本身编写心跳包程序,简单说就是给本身的程序加入一个线程,定时向对端发送数据包,查看是否有ACK,根据ACK的返回状况来管理链接。这个方法比较通用,但改变了现有的协议;

       b.使用TCP的KEEPALIVE机制,UNIX网络编程不推荐使用SO_KEEPALIVE来作心跳测试。

       keepalive原理:TCP内嵌心跳包,以服务端为例,当server检测到超过必定时间(2小时)没有数据传输,就会向client传送一个keepalive pocket

 

三.LINUX操做系统

    1.熟练netstat,tcpdump,ipcs,ipcrm

        netstat:检查网络状态  tcpdump : 截取数据包  ipcs:检查共享内存  ipcrm:解除共享内存

 

     2.共享内存段被映射进进程空间以后,存在于进程空间的什么位置?共享内存段最大限制是多少?

        将一块内存空间映射到两个或者多个进程地址空间。经过指针访问该共享内存区。通常经过mmap将文件映射到进程地址共享区。

        存在于进程数据段,最大限制是0x2000000Byte。

    

    3.写一个C程序判断是大端仍是小端字节序

        

union
{
    short value;
    char a[sizeof(short)];
}test;

int main()
{
    test.value = 0x0102;
    if(test.a[0] == 1 && test.a[1] == 2) 
cout << "big" << endl; else cout << "little" << endl; return 0; }

      4.i++操做是不是原子操做?并解释为何?

         确定不是,i++主要看三个步骤:

    首先把数据从内存放到寄存器,在寄存器上进行自增,而后返回到内存中,这三个操做中均可以被断开。

     

       5.标准库函数和系统调用?

         系统调用:是操做系统为用户态运行的进程和硬件设备(如cpu,磁盘,打印机等)进行交互提供的一组接口,即就是设置在应用程序和硬件设备之间的一个接口层。Linux内核是单内核,结构紧凑,执行速度快,各个模块之间是直接调用的关系。Linux系统上到下依次是用户进程->linux内核->硬件。其中系统调用接口是位于Linux内核中的,整个linux系统从上到下能够是:用户进程->系统调用接口->linux内核子系统->硬件,也就是说Linux内核包括了系统调用接口和内核子系统两部分;或者从下到上能够是:物理硬件->OS内核->OS服务->应用程序,操做系统起到“承上启下”做用,向下管理物理硬件,向上为操做系服务和应用程序提供接口,这里的接口就是系统调用了。

          库函数:把函数放到库里。把一些常用的函数编完放到一个一个lib文件里,供别人用。别人用的时候把它所在的文件名用#include<>加到里面就能够了。一类是c语言标准规定的库函数,一类是编译器特定的库函数。

          系统调用是为了方便使用操做系统的接口。库函数是为了人们方便编程而使用。

 

    6.fork()一个子进程后,父进程的全局变量可否被子进程使用?

       fork()一个子进程后,子进程将会拥有父进程的几乎一切资源,可是他们各自拥有各自的全局变量,不能通用,不像线程。对于线程,各个线程共享全局变量。

 

   7.线程同步几种方式?

        互斥锁,信号量,临界区。

 

  8.为何要字节对其?

    1)有些特殊的cpu只能处理4倍开始的内存地址。

    2)若是不是整倍数读取会致使读取屡次。

    3)数据总线为读取数据作了基础。

 

  9.对一个数组而言,delete a和delete[] a有什么区别?

    对于基础数据类型没有什么区别,可是对于对象而言,delete a只调用一次析构函数,delete[] a才会析构全部。

  10.什么是协程?

    用户态的轻量级线程,有本身的寄存器和栈。

  11.线程安全的几种方式?

       1)原子操做 2)同步与锁  3)可重入 4)阻止过分优化的volatile。

  

   12.int (*f(int,void(*)()))(int,int)是什么意思?

    一个名为f的参数为int 和指向void的指针函数的指针函数,返回值为int,参数是int和int

    (一个函数,参数为int和指向返回值为void的无参数的函数指针,返回值为一个指向返回值为int,参数为int和int的函数指针)

   

    13.什么是幂等性?举个栗子?

      其任意屡次执行所产生的影响与一次执行的影响相同。

 

   14.当接收方的接收窗口为0时还能接收数据吗?为何?还能接收什么数据?那怎么处理这些数据呢?

      能够接收。

      数据:零窗口探测报文,确认报文段;携带紧急数据的报文段。

       可能会被抛弃。

   

     15.当接收方返回的接收窗口为0时?发送方怎么作?

        开启计时器,发送零窗口探测报文。

 

    16.下面两个函数早执行时有什么区别?

         int f(string &str); f("abc"); //报错

         int f(const string &str); f("abc"); // 正常

 

   17.char *const* (*next)()是什么?

       next是一个指针,指向一个函数,这个函数返回一个指针,这个指针指向char类型的常量指针

 

   18.如何判断一个数是2的次方?

         思想: 直接用这个数和这个数减一的数进行按位与操做就能够。若是等于0则TRUE,不然false。

    

   19. 布隆过滤器 (Bloom Filter)

        Q:一个网站有 100 亿 url 存在一个黑名单中,每条 url 平均 64 字节。这个黑名单要怎么存?若此时随便输入一个 url,你如何快速判断该 url 是否在这个黑名单中

        布隆过滤器:

它其实是一个很长的二进制矢量和一系列随机映射函数

能够用来判断一个元素是否在一个集合中。它的优点是只须要占用很小的内存空间以及有着高效的查询效率。

对于布隆过滤器而言,它的本质是一个位数组:位数组就是数组的每一个元素都只占用 1 bit ,而且每一个元素只能是 0 或者 1。

一开始,布隆过滤器的位数组全部位都初始化为 0。好比,数组长度为 m ,那么将长度为 m 个位数组的全部的位都初始化为 0。

0 0 0 0 0 0 0 0 0 0
0 0 1 m-2 m-1

在数组中的每一位都是二进制位。

布隆过滤器除了一个位数组,还有 K 个哈希函数。当一个元素加入布隆过滤器中的时候,会进行以下操做:

•使用 K 个哈希函数对元素值进行 K 次计算,获得 K 个哈希值。•根据获得的哈希值,在位数组中把对应下标的值置为 1。

 

举个例子,假设布隆过滤器有 3 个哈希函数:f1, f2, f3 和一个位数组 arr。如今要把 2333 插入布隆过滤器中:

•对值进行三次哈希计算,获得三个值 n1, n2, n3。•把位数组中三个元素 arr[n1], arr[n2], arr[3] 都置为 1。

当要判断一个值是否在布隆过滤器中,对元素进行三次哈希计算,获得值以后判断位数组中的每一个元素是否都为 1,若是值都为 1,那么说明这个值在布隆过滤器中,若是存在一个值不为 1,说明该元素不在布隆过滤器中。

很明显,数组的容量即便再大,也是有限的。那么随着元素的增长,插入的元素就会越多,位数组中被置为 1 的位置所以也越多,这就会形成一种状况:当一个不在布隆过滤器中的元素,通过一样规则的哈希计算以后,获得的值在位数组中查询,有可能这些位置由于以前其它元素的操做先被置为 1 了

如图 1 所示,假设某个元素经过映射对应下标为4,5,6这3个点。虽然这 3 个点都为 1 ,可是很明显这 3 个点是不一样元素通过哈希获得的位置,所以这种状况说明这个元素虽然不在集合中,也可能对应的都是 1,这是误判率存在的缘由。

因此,有可能一个不存在布隆过滤器中的会被误判成在布隆过滤器中。

这就是布隆过滤器的一个缺陷:存在误判。

可是,若是布隆过滤器判断某个元素不在布隆过滤器中,那么这个值就必定不在布隆过滤器中。总结就是:

•布隆过滤器说某个元素在,可能会被误判•布隆过滤器说某个元素不在,那么必定不在

用英文说就是:False is always false. True is maybe true。

    误判率:

布隆过滤器能够插入元素,但不能够删除已有元素。其中的元素越多,false positive rate(误报率)越大,可是false negative (漏报)是不可能的。

 

假设布隆过滤器有m比特,里面有n个元素,每一个元素对应k个指纹的Hash函数

      

补救方法

布隆过滤器存在必定的误识别率。常见的补救办法是在创建白名单,存储那些可能被误判的元素。 

 

使用场景

布隆过滤器的最大的用处就是,可以迅速判断一个元素是否在一个集合中。所以它有以下三个使用场景:

•网页爬虫对 URL 的去重,避免爬取相同的 URL 地址•进行垃圾邮件过滤:反垃圾邮件,从数十亿个垃圾邮件列表中判断某邮箱是否垃圾邮箱(同理,垃圾短信)•有的黑客为了让服务宕机,他们会构建大量不存在于缓存中的 key 向服务器发起请求,在数据量足够大的状况下,频繁的数据库查询可能致使 DB 挂掉。布隆过滤器很好的解决了缓存击穿的问题。

以上来自公众号:5分钟学算法。

 

20. 排序算法

    1)冒泡  平均O(n^2),最好O(n)。稳定的排序算法。

void m_sort(vector<int> &a)
{
      int len = a.size();
      bool flag = true;
      for(int i = 0;  i <len; i++)
      {
             for(int j = i+1; j < len - i - 1; j++)
            {
                  if(a[j-1] > a[j])
                 {
                       swap(a[j-1],a[j]);
                       flag = false;
                 }
            }
            if(flag) break;
      }
}

 

      2)选择排序 最好O(n^2) 平均O(n^2)

  

void select_sort(vector<int> &a)
{
       int len = a.size();
       for(int  i = 0;  i <len; i++)
      {
             int min = i;
             for(int j = i +1;  j < len; j++)
            {
                   if(a[j] < a[min])
                  {
                        min = j;
                  }
            }
            if(i != min)
           {
                swap(a[i],a[min]);
           }
      }
}

 

   3)插入排序 平均O(n^2) , 最好O(n)

      

void insert_sort(vector<int>& a)
{
       int len  = a.size();
       for(int i = 1; i < len; i++)
      {
             int tmp = a[i];
             int j = i;
             while(j > 0 && tmp < a[j-1])
             {
                   a[j] = a[j-1];
                   j--;
             }
             if(j != i)
            {
                   a[j] = tmp;
            }
      }
}

        4)希尔排序 平均O(nlogn) 最坏O(nlong^2 n)

    

void shell_select(vector<int>& a)
{
      int len = a.size();
      int gap = 1;
      while(gap < len)
     {
            gap = gap * 3 +1;
     }
     while(gap > 0)
    {
           for(int i = gap; i< len; i++)
          {
                  int tmp = a[i];
                  int j = i  - gap;
                  while(j >= 0 && tmp < a[j])
                 {
                          a[j+gap] = a[j];
                          j -= gap;
                 }
                 a[j + gap] = tmp;
          }
          gap = floor(gap/3);
    }
}

      5) 归并排序  平均O(nlogn)   最坏O(nlogn)

vector<int> merge_sort(vector<int> &a,int left,int right)
{
       if(left < right)
      {
             int mid = (left + right) / 2;
             a = merge_sort(a,left,mid);
             a = merge_sort(a,mid+1,right);
             merge(a,left,mid,right);
      }
      return arr;
}

void merge(int[] &arr,int left,int mid,int right)
{
      int[] a = new int[right - left +1];
      int i = left;
      int j = mid+1;
      int k = 0;
      while(i <= mid && j <= right)
     {
           if(arr[i] < arr[j])
          {
               a[k++] = arr[i++];
          }
          else
         {
              a[k++] = arr[j++];
         }
     }
     while(i <= mid) a[k++] = arr[i++];
     while(j <= mid) a[k++] = arr[j++];
     for(i = 0; i < k; i++)
    {
          arr[left++] = a[i];
    }
}

 

  6)快速排序 平均O(nlogn)  最坏O(n^2)

  

void quick_sort(vector<int> &a,int left,int right)
{
       if(left < right)
      {
            int pre = site(a, 0,right);
            quick_sort(a,left,pre-1);
            quick_sort(a,pre+1,right);
      }
}

void my_swap(vector<int>& a,int i,int j)
{
       int tmp = a[i];
       a[i] = a[j];
       a[j] = tmp;
}

int site(vector<int>& a,int left,int right)
{
      int pivot = left;
      int index = pivot + 1;
      for(int i = index; i<= right; i++)
     {
           if(a[i] < a[pivot])
          {
                my_swap(a, i,index);
                index++;
          }
     }
     my_swap(a, pivot, index-1);
     return index-1;
}

 

    7) 堆排序 平均O(nlogn)  最好O(nlogn)

  

int heap[maxn],sz = 0;

void push(int x)
{
    int i = sz++;
    while(i > 0)
   {
        int p = (i-1)/2;
        while(heap[p] <= x) break;
        heap[i] = heap[p];
        i = p;     
   }
   heap[i] = x;
}

int pop()
{
     int ret = heap[0];
     int x = heap[--sz];
     int i = 0;
     while(i * 2 +1 < sz)
    {
           int a = i * 2+1, b = i * 2+ 2;
           if(b < sz && heap[b] < heap[a]) a = b;
           if(heap[a] >= x) break;
           heap[i] = heap[a];
           i = a;
    }
    heap[i] = x;
    return ret;
}
相关文章
相关标签/搜索