首页 > 其他 > 详细

面试学习总结

时间:2019-04-17 22:57:38      阅读:111      评论:0      收藏:0      [点我收藏+]

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

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

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

 

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

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

2)派生类中定义的函数必须和基类中的函数定义完全相同。

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

 

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类型的常量指针

 

面试学习总结

原文:https://www.cnblogs.com/PrayG/p/10709931.html

(0)
(0)
   
举报
评论 一句话评论(0
关于我们 - 联系我们 - 留言反馈 - 联系我们:wmxa8@hotmail.com
© 2014 bubuko.com 版权所有
打开技术之扣,分享程序人生!