面经一

来源:https://www.nowcoder.com/discuss/57978

如何消除隐式转换?

面经解释:使用explicit关键字进行修饰

自己补充:explicit关键字修饰,一般来说加了肯定比不加好,主要用来针对隐式的强转

例子:

class A{
     public:
          A(const string &a ):x(a),y(0),z(0);
          int compare_to(A z){
               return this.x>z.x;   
         }  
       string x;
       int y,z;
}       
int main(){
    A z;
    z.compare_to("123"); //这里因为拷贝构造函数只是一个string ,会隐式的创建成 A对象,一般根据业务的需要判断要不要着这种强转
}

重载,重写和隐藏的区别?

面经解释:重载:即函数重载

重写【覆盖】:即用于虚函数

隐藏:只要派生类的函数名与基类相同就会隐藏

补充:

重载作用域只是限于同一个类中,然后要求是参数列表和原来的有所不同,无论是类型还是个数,返回值无所谓

重写作用域发生在不同类中,限于父类中加了virual关键字,然后子类重写,要求名字参数列表返回值都完全一样,只能修改执行体,重写完这个函数默认属于父类

隐藏作用域发生在不同类中,要求只要名字一样,实现出来,就会隐藏掉父类中的方法,无法调用

volatile表示什么?有什么作用?

面经解释:易变的,不会被编译器进行优化,让程序取数据直接去内存中的。

补充:编译器有些时候会自带一些优化,把没有必要的一些代码给优化掉,编译器每次取值会优先从缓存里面去读取,如果是多线程和汇编的一些命令情况下,缓存会来不及修改

而产生错误,所以我们要表明这个变量是经常改变的, 所以我们让他略过这个去缓存的过程直接去内存取值

Malloc和new的区别?

面经解释:

New:

内存分配错误时,抛出bad_alloc异常,可以定义set_new_handler函数来在产生异常时进行处理;本身是一个运算符;分配内存的地方为自由存储区【为一个抽象概念】;对于对象而言,会先申请内存空间然后调用构造函数;无需指定大小

Malloc:

内存分配错误时,返回NULL;本身是一个库函数;分配内存的地方为堆;只申请内存空间;需要指定申请多大的内存;

free和delete的区别?

Delete:

本身是一个运算符

Free:

本身是一个库函数

free一个数组时如何知道要释放多大的内存呢?

一般在数组前面几个字节中存在某一个结构体来保存当前申请的数组大小。

引用和指针的区别?

指针:是一个变量类型;指针可以不进行初始化;指针初始化后可以改变,在写代码时需要大量的检测

引用:是一个别名;引用必须要初始化;引用初始化后不可改变,无需检测

Linux 内部有哪些调试宏

_FILE_        :  表示在哪个文件

_LINE_     : 表示在哪一行

_FUNCTION_  :    表示在哪个函数

出现异常时  try,catch在做什么

try  作用域里面的代码也叫保护代码,如果里面的代码发送错误,就会由catch检测出来,具体什么错误看catch里面捕捉的类型,然后再由throw抛出异常,抛给上一级

C++如何处理多次异常

多次catch

常对象的成员变量一定不可以修改吗?为什么?

不一定,可以使用mutable关键字修饰,代表是可变的,无论什么情况下,该变量都是可以改变的

应用场景:如果你一个常函数,你里面大部分都不能修改,但是有一个要,为了保证整体的常属性,就可以用mutable进行修饰

虚函数调用过程

现在内存对象中找到虚函数指针,然后利用虚函数指针找到虚函数表,在虚函数表中找到对应的虚函数

单继承,多继承,菱形继承内存怎么样,如果存在虚函数怎么样

都是直接进行继承的,内存里面都会存在,像多继承,如果调用相同名函数,前面就要加类名强调,有虚函数的话会继承多个虚函数表

实现一个vector?是1.5还是2倍,各有什么优缺点?

1.5倍好,这个倍数也叫扩容因子,当vector当前内存到边界的时候就要动态扩容,扩容1.5倍或者2倍,扩容的时候会把之前那段内存舍弃,然后去找

一段新的内存来存储,会把之前的值都赋值过来,这样的话1.5倍后面申请就有可能再次用到之前的空间(如果多次申请,然后前几次堆在一起成了一块的话)

总结:复用之前申请的内存,不用去开辟新的内存,这里体现空间优势

2倍,一次申请这么多,就能节省申请次数,因为申请空间也要时间,这里体现时间优势

如果用map删除了一个元素,迭代器还能用吗?为什么?怎样做可以接着用?

可以使用,你只是删除了当前迭代器,但是不影响你进行++操作移到下一个去

红黑树五大特征?

1,节点非红即黑

2,红色节点的儿子都是黑色

3,根节点为黑色

4,叶子为黑色

5,任意节点到叶子节点的所有路径经过的黑色节点数不会差一倍

红黑树如何插入和删除的?

插入:

(1)如果父节点为黑色,直接插入不处理

(2)如果父节点为红色,叔叔节点为红色,则父节点和叔叔节点变为黑色,祖先节点变为红色,将节点操作转换为祖先节点

(3)如果当前节点为父亲节点的右节点,则以父亲结点为中心左旋操作

(4)如果当前节点为父亲节点的左节点,则父亲节点变为黑色,祖先节点变为红色,以祖先节点为中心右旋操作

删除:

(1)先按照排序二叉树的方法,删除当前节点,如果需要转移即转移到下一个节点

(2)当前节点,必定为这样的情况:没有左子树。

(3)删除为红色节点,不需要处理,直接按照删除二叉树节点一样

(4)如果兄弟节点为黑色,兄弟节点的两个子节点为黑色,则将兄弟节点变为红色,将着色转移到父亲节点

(5)如果兄弟节点为红色,将兄弟节点设为黑色,父亲结点设为红色节点,对父亲结点进行左旋操作

(6)如果兄弟节点为黑色,左孩子为红色,右孩子为黑色,对兄弟节点进行右旋操作

(7)如果兄弟节点为黑色,右孩子为红色,则将父亲节点的颜色赋值给兄弟节点,将父亲节点设置为黑色,将兄弟节点的右孩子设为黑色,对父亲节点进行左旋

线程同步几种方式?

互斥锁  信号量   临界区

手写strcpy,memcpy,memmove函数?

三个的区别

strcpy是对字符进行的拷贝

memcpy是对任何类型的拷贝,但是他没有考虑到两个指针指向区域有重合的情况

memmove 任何类型的拷贝,考虑到重合情况,目标地址在左边的话,那么高地址赋值的来

char *mystrcpy(char *p,char *q){
    char *h=p;
    while((*p++=*q++)!='');
    return h;
}
void *mymemcpy(void *p,void *q,int n){
    char *x=(char *)p,*y=(char *)q;
    while(n--){
        *x++=*y++;
    }
    return p;
}
void *mymemmove(void *p,void *q,int n){
    char *x=(char *)p,*y=(char *)q;
    if(x<y&&x+n>=y){
        x=x+n-1;
        y=y+n-1;
       while(n--){
            *x--=*y--;
       }
    }
    else{
        while(n--){
            *x++=*y++;
        }
    }
}

手写快排?时间复杂度?空间复杂度?能进行优化吗?还有吗?能进行尾递归优化吗?

优化:

选基准:三种方法 (第一个,随机化,三数取中->也就是选第一个最后一个中间一个的中间值)

其他优化:尾递归,和key相同的值处理一下,当排序到一定长度的时候用插入排序(大约长度在5-20之间就可以用了,因为经过多次快排分割后,数列已经基本有序,这个时候用快排几乎O(n))

#include <iostream>
#include <stdlib.h>
using namespace std;

#define MAX_LENGTH_INSERT_SORT 7

#define MAXSIZE 10

void ISort( int k[], int n )
{
    int i, j,temp;
    
    for( i=1; i < n;i++ )
    {
        if( k[i] < k[i-1] )
        {
            temp = k[i];
            
            for( j=i-1; k[j] > temp;j-- ) //找位置并且向后推移 
            {
                k[j+1] = k[j];
            }
            
            k[j+1] = temp;
        }
    }
}

void InserSort(int k[], int low,int high)
{
    ISort(k+low, high-low+1);
}


void swap(int k[], int low,int high)
{
    int temp;
    
    temp = k[low];
    k[low] = k[high];
    k[high] = temp;
}

int Partition(int k[], int low, int high)
{
    int point;
    
    int m = low + (high - low)/2;
    
    if(k[low] > k[high])
    {
        swap(k, low ,high);
    } 
    if(k[m] > k[high])
    {
        swap(k, m ,high);
    }
    if(k[m] > k[low]) // 使得low存放中间的值 
    {
        swap(k, m, low);
    }
    
    point = k[low];
    
    while(low < high)
    {
        while( low < high && k[high] >= point ) //过滤掉比low大的 
        {
            high--;
        }//出了循环说明要进行移动 
        k[low] = k[high]; 
        
        while( low < high && k[low] <= point ) 
        {
            low++;
        } //出了循环说明左边大于右边 要进行移动 
        k[high] = k[low]; 
        
    }
    
    k[low] = point;
    
    return low; 
}


void QSort(int k[], int low, int high)
{
    int point;
    
    if( high - low > MAX_LENGTH_INSERT_SORT )
    {
        while( low < high )
        {
         
            point = Partition(k , low , high);
            
            if( point-low < high-point )//如果左边要小于右边的话
            {
                QSort(k, low,point-1); //左边 
                
                low = point + 1 ; //尾递归形式 
            }
            else
            {
                QSort(k, point+1,high);
                high = point -1; 
            }
        
        }
         
    }
    else
    {
        InserSort(k, low, high);
    }
}

void QuickSort( int k[], int n )
{
    QSort( k, 0, n-1 );
}

int main()
{
    int i ,a[10] = {5,2,6,0,3,9,1,7,4,8};
    
    QuickSort(a,10);
    
    for( i=0; i < 10 ;i++ )
    {
        cout << a[i];
    }
    
    cout << endl;
    
    return 0;
}
View Code

线程池的作用是什么?

线程池和内存池是一样的,都是为了避免申请的销毁的开销,就是用一个数组装一堆线程,然后如果要用就唤醒其中一个,然后使用

这是为了大量请求的时候,避免创建线程和销毁的空间,以前都是即请求即创建即销毁

还有个作用就是同时运行的线程数是有限制的,如果申请请求多余同时运行数,这个时候线程池就可以比原始少创建很多线程

Pthread_cond_signal和pthread_cond_broadcast的区别

Pthread_cond_signal    唤醒一个睡眠进程

pthread_cond_broadcast  唤醒所有的睡眠进程

线程有几种状态?进程又有几种状态?

线程只有基本的     就绪,运行,阻塞

进程 有 就绪 (动态就绪,静态就绪) 运行   阻塞(动态阻塞,静态阻塞)

动态和静态的区别也就是当前进程在不在内存中

TCP三次握手和四次挥手及各自的状态?

三次握手:

CLOSE          LISTEN

SYN_SENT

SYN_RCVD

ESTABLISHED

ESTABLISHED

四次挥手:

FIN_WAIT1

CLOSE_WAIT

FIN_WAIT2

LAST_ACK

TIME_WAIT

CLOSE

CLOSE

TCP如果两次握手会出什么问题?那三次握手又会造成什么问题?有什么好的解决方法没?

两次握手:客户端发送的连接请求可能在网络中滞留了,如果没有三次握手,可能会再次创建一个连接。

三次握手:引起SYN flood

不断发送同步报文段会因为传输控制模块TCB【处于半连接状态】从而消耗服务器资源

(1)【处理连接和半连接】定时释放监控系中无效的连接

(2)Syn ***技术【处理半连接状态】,接受到的SYN先不创建TCB,而是用一个hash表来表示,当前连接,如果接收到ACK然后再创建TCB

(3)Syn cookie技术【处理连接】通过一个cookie值来确定当前连接是否合法,合法就连接,一般的验证方法是,服务器接受到一个syn包,服务器通过syn产生一个cookie数据作为初始化序列,接收到ACK包时,序列-1就是得到的cookie,然后进行相应的验证。

TCP四次挥手为什么要有TIME_WAIT状态?为什么?

有两个原因:

(1)保证TCP协议全双工连接能够可靠关闭,直接关闭的话,如果服务器没有收到ACK,会重复发FIN。

(2)保证这次连接的重复数据从网络中消失,如果上次的socket和这次的socket处理的程序一样,就会导致这次连接把上次的数据加进来了。

死锁的原因?条件?如何预防?又如何避免?如何解除?

原因:系统资源不足;进程运行推进顺序不合适;资源分配不当

条件:互斥;不剥夺;循环等待;请求与保持

预防:破坏任意一个条件

避免:银行家算法

检测:资源分配图简化法

解决hash冲突的方法?

线性探测法;开链法;再哈希法;

C++分为内存分为哪几部分?

堆;栈;静态全局;常量;自由存储区

如果new申请内存失败了,如何去解决?如果让你实现一个new,你会怎么实现?

可以执行set_new_handle函数

这个函数的作用是如果申请失败,先调用这个函数进行处理然后再进行申请,这个时候通常操作就是在这个函数里面可以释放掉一些不必要的内存,不加的话会直接报bad_alloc异常

#include <iostream>
#inc
lude <new>
using namespace std;
/// buffer to be allocated after custom new handler has been installed
char* g_pSafetyBuffer = NULL;

/// exceptional one time release of a global reserve
void my_new_handler()
{
    if (g_pSafetyBuffer) {
        delete [] g_pSafetyBuffer;
        g_pSafetyBuffer = NULL;
        std::cout << "[Free some pre-allocated memory]";
        return;
    }
    std::cout << "[No memory to free, throw bad_alloc]";
    throw std::bad_alloc();
}

/// illustrates how a custom new handler may work
int main()
{
    enum { MEM_CHUNK_SIZE = 1000*1000 }; // adjust according to your system
    std::set_new_handler(my_new_handler);
    g_pSafetyBuffer = new char[801*MEM_CHUNK_SIZE];
    try {
        while (true) {
            std::cout << "Trying another new... ";
            new char[200*MEM_CHUNK_SIZE];
            std::cout << " ...succeeded.
";
        }
    } catch (const std::bad_alloc& e) {
        std::cout << " ...failed.
";
    }
    return 0;
}
View Code

如何得到一个结构体内成员的偏移量?

1,直接使用要求的成员的地址减去第一个成员的地址就是偏移量

struct  ha{
    char a;
    int b;        
};
struct ha x;
offset=(long long)&x.b-(long long)&x.a;

2,可以直接对NULL进行强转,然后&就好

进程与线程的区别?

进程有自己的内存空间,线程只有少量的栈和程序计数器的空间,线程共享进程的内存空间

进程是分配资源的最小单位,线程是调度的最小单位

进程是线程的包含关系,进程内的所有线程共享进程的空间

构造函数能不能虚函数?为什么?那拷贝构造函数能不能为虚函数?为什么?

因为,调用虚函数需要虚表指针,但是虚表指针存在于具体对象中,所以自相矛盾

析构函数可不可以为虚函数?

可以:

什么是RAII资源管理?

作用:将c++中内存管理上升到对象资源管理的层面

就是利用对象自动析构的原理,然后将申请的内存转化到对象指向上,然后达到和资源管理指针一样的生命周期,用于智能指针中

原文地址:https://www.cnblogs.com/Lis-/p/12187366.html