C++面试题

本文涵盖了多种C++面试题目,包括内存管理、数据结构、算法、操作系统、多线程、网络编程等方面。例如,讨论了char a[] = new char[10]分配内存的情况,如何判断二叉树是否为完全二叉树,C++类何时不会生成默认构造函数,内存分配策略,以及Redis和Zookeeper的使用和注意事项。同时,提到了动态内存分配、信号处理、单例模式、内存对齐、线程安全等核心概念。
摘要由CSDN通过智能技术生成

char a[] = new char[10];会不会分配物理内存

系统此时分配的是虚拟地址,并不会分配真正的物理内存

但是当出现读a的内容时,比如char c = a[0]

系统怎样触发呢分配真正的无力内存呢?采用中断方式,机制如同 int d = 1/0,系统中断取真正的物理内存,抛出一个SIGFPE。读a的内容机制也是这样的。这样做的好处是类似于copy-on-write。

另外,当linux fork的时候和类std::string的复制也是写时拷贝。

调用malloc函数后,OS会马上分配实际的内存空间吗?

答:不会,只会返回一个虚拟地址,待用户要使用内存时,OS会发出一个缺页中断,此时,内存管理模块才会为程序分配真正的内存。


判断二叉树是否为完全二叉树

采用层级遍历,当为空时压入NULL,然后遍历完queue,发现NULL后面有非NULL的元素,即为非完全二叉树

那怎么判断平衡二叉树呢。左右子树的深度不超过1.


c++的类什么时候不会生成默认构造函数

当时空类或者类中的成员变量没有构造函数,如类中只有一个int型成员变量


绝对值排序 {1 -1 2 3 3 -4 4},找出key值,没有则返回-1


10T文件,存储着 “url\tpv",有重复,请找出pv前十

    先用map装载url-key,pv-value,map的find和sort很高效(红黑树存储)

iterator遍历map,动态更新最小堆(最小堆存储pv前10,堆的大小为10)

(能一次完成吗?)纠结这个问题,结果一行代码都没写,还不如先把上面的思路实现了呢

    BTW,内存不够用,切割文件,散列(找到合适的散列函数)到多台机器

在merge sort进一个文件    


单例模式

没有考虑线程安全啊,原子操作or自旋锁,互斥锁


寻找二叉树的最远距离,求二叉树的宽度,已知二叉树的前序遍历和中序遍历,求二叉树

typedef struct Node {  
    struct Node *pleft;     //左孩子  
    struct Node *pright;    //右孩子  
    char chValue;           //该节点的值  
  
    int leftMaxValue;       //左子树最长距离  
    int rightMaxValue;      //右子树最长距离  
}LNode, *BinTree;  
  
void findMaxLen(BinTree root, int *maxLen) {  
    //遍历到叶子结点,返回  
    if(root == NULL)  
        return;  
  
    //如果左子树为空,那么该节点左边最长距离为0  
    if(root->pleft == NULL)  
        root->leftMaxValue = 0;  
  
    //如果右子树为空,那么该节点右边最长距离为0  
    if(root->pright == NULL)  
        root->rightMaxValue = 0;  
  
    //如果左子树不为空,递归寻找左子树最长距离  
    if(root->pleft != NULL)  
        findMaxLen(root->pleft, maxLen);  
  
    //如果右子树不为空,递归寻找右子树最长距离  
    if(root->pright != NULL)  
        findMaxLen(root->pright, maxLen);  
  
    //计算左子树中距离根节点的最长距离  
    if(root->pleft != NULL) {  
        if(root->pleft->leftMaxValue > root->pleft->rightMaxValue)  
            root->leftMaxValue = root->pleft->leftMaxValue + 1;  
        else  
            root->leftMaxValue = root->pleft->rightMaxValue + 1;  
    }  
  
    //计算右子树中距离根节点的最长距离  
    if(root->pright != NULL) {  
        if(root->pright->leftMaxValue > root->pright->rightMaxValue)  
            root->rightMaxValue = root->pright->leftMaxValue + 1;  
        else  
            root->rightMaxValue = root->pright->rightMaxValue + 1;  
    }  
  
    //更新最长距离  
    if(root->leftMaxValue + root->rightMaxValue > *maxLen)  
        *maxLen = root->leftMaxValue + root->rightMaxValue;  
}

//求二叉树的深度  
int GetDepth(tagBiNode *pRoot)  
{  
    if (pRoot == NULL)  
    {  
        return 0;  
    }  
  
    //  int nLeftLength = GetDepth(pRoot->m_left);  
    //  int nRigthLength = GetDepth(pRoot->m_right);  
    //  return nLeftLength > nRigthLength ? (nLeftLength + 1) : (nRigthLength + 1);  
  
    return GetDepth(pRoot->left) > GetDepth(pRoot->right) ?   
        (GetDepth(pRoot->left) + 1) : (GetDepth(pRoot->right) + 1);  
}  
  
//求二叉树的宽度  
int GetWidth(tagBiNode *pRoot)  
{  
    if (pRoot == NULL)  
    {  
        return 0;  
    }  
  
    int nLastLevelWidth = 0;//记录上一层的宽度  
    int nTempLastLevelWidth = 0;  
    int nCurLevelWidth = 0;//记录当前层的宽度  
    int nWidth = 1;//二叉树的宽度  
    queue<BiNode *> myQueue;  
    myQueue.push(pRoot);//将根节点入队列  
    nLastLevelWidth = 1;      
    tagBiNode *pCur = NULL;  
  
    while (!myQueue.empty())//队列不空  
    {  
        nTempLastLevelWidth = nLastLevelWidth;  
        while (nTempLastLevelWidth != 0)  
        {  
            pCur = myQueue.front();//取出队列头元素  
            myQueue.pop();//将队列头元素出对  
  
            if (pCur->left != NULL)  
            {  
                myQueue.push(pCur->left);  
            }  
  
            if (pCur->right != NULL)  
            {  
                myQueue.push(pCur->right);  
            }  
  
            nTempLastLevelWidth--;  
        }  
  
        nCurLevelWidth = myQueue.size();  
        nWidth = nCurLevelWidth > nWidth ? nCurLevelWidth : nWidth;  
        nLastLevelWidth = nCurLevelWidth;  
    }  
  
    return nWidth;  
}  

BT* conBT(int* startPre, int* endPre, int* startIn, int* endIn)
{
    int rootValue = startPre[0];
    BT* root = new BT; 
    root->value = rootValue;
    root->left = root->right = NULL;

    if(startPre == endPre){
        if(startIn == endPre && *startPre == *startIn){
            return root;
        } else {
            throw std::exception("invalid input");
        }   
    }   

    int* rootIn = startIn;
    while(rootIn <= endIn && *rootIn != rootValue){
        ++rootIn;
    }   

    if(rootIn == endIn && *rootIn != rootValue){
        throw std::exception("invalid input");
    }   

    int leftLen = rootIn - startIn;
    int* leftPreEnd = startPre + leftLen;

    if(leftLen > 0){ 
        root->left = conBT(startPre + 1, leftPreEnd, startIn, rootIn - 1); 
    }   

    if(leftlen < endPre - startPre){
        root->right = conBT(leftPreEnd + 1, endPre, rootIn + 1, endIn);
    }   

    return root;
}



进程组和会话的概念

进程组是一个或多个进程的集合,通常,他们与同一个作业相关联,可以接收来自同一终端的各种信号。

每个进程都有一个组长进程(其pid=pgid)

组长进程可以创建一个进程组,创建该组中的进程,然后终止。只要在某个进程组中有一个进程存在,则改进程组就存在,与其组长进程是否终止无关。其生存期为从进程组创建开始到其中最后一个进程离开位置。进程组最后一个进程可以终止,或转移到另一个进程组。

pid_t setsid(void);

如果调用此函数的不是一个进程组的组长,则此函数就会创建一个新会话,

1、该进程变成新会话首进程。此时该进程是该会话的唯一的进程

2、该进程成为一个新进程组的组长进程

3、该进程没有控制终端。如果在调用setsid之前该进程有一个控制终端,该联系也会被中断。

如果该调用进程已经是一个进程组的组长,则此函数会出错。为了保证不会发生,通常先调用fork,然后使其父进程终止,因为子进程继承了父进程的进程组ID,而其进程ID则是新分配的,两者不可能相等。


c的内存布局,堆的成长方向是什么

堆是动态分配的,用户先后提出申请请求,经过一段时间,用户释放内存后,内存变成空闲块,这就使整个内存出现占用块和空闲块交错的转态
假如此时又有新的用户申请内存,系统该如何做呢?
一种策略是系统继续从高地址的空闲块中进行分配,直到分配无法进行,系统才去回收所有用户释放的内存,并且重新组织成一个大的空闲块。
另一种策略是用户释放了内存,这块内存变成空闲块,同时,当有新的申请时,系统需要遍历整个内存区的空闲块,并从中找出一个合适的空闲块。因此,系统需要建立一张记录所有空闲块的可利用空间表。可以是dict,也可以是链表。
可利用空间表分为3种:
1. 系统运行期间所有用户请求分配的存储量大小相同。
2. 系统运行期间用户请求的存储量有若干种大小的规格
3. 大小不固定,可以随请求而变
然而当可利用空间表中有若干个符合条件的空闲块时,分配策略
1. 首次拟合法:从表头开始查找链表,第一个大小不小于n的空闲块分配给用户
2. 最佳拟合法:将可利用空间表中一个不小于n的且最接近n的空闲块的一部分分配给用户
3. 最差拟合法:将不小于n的且是链表中最大的空闲块分配给用户
首次拟合法在分配时需查询可利用空间表,回收时仅需插入到表头
最差拟合法分配时无需查找,回收时为将空闲块插入到聊表的适当的位置,需先进行查找
最佳拟合法无论分配和回收,都需查找,最费时间
边界标识法:
在每个内存区的头部和底部两个边界上分别设有标识,以标识该区域为占用块或空闲块,使得在回收用户释放的空闲块时易于判断在物理位置上与其相邻的内存区域是否为空闲块,以便将所有地址连续的空闲存储区组成一个更大的空闲块。
伙伴系统
不同于边界标识法,无论是占用块还是空闲块,其大小均是2的k次幂。
在分配时经常需将一个大的空闲块分裂成两个大小相等的存储区,这两个小块互称为伙伴。在回收过程中,只当起伙伴为空闲块时才归并成大块。也就是说,若有两个空闲块,即使大小相同且地址相邻,但不是同一个大块分裂出来的,也不归并在一起。


程序通常catch哪些signal,忽略哪些信号?

redis处理的信号

    act.sa_flags =SA_NODEFER | SA_RESETHAND | SA_SIGINFO;

    act.sa_sigaction = sigsegvHandler;

    sigaction(SIGSEGV, &act, NULL);

    sigaction(SIGBUS, &act, NULL);

    sigaction(SIGFPE, &act, NULL);

    sigaction(SIGILL, &act, NULL);

ignore的信号

    signal(SIGHUP,SIG_IGN);

    signal(SIGPIPE, SIG_IGN);


nginx处理的信号

SIGHUP - reload

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值