9.06腾讯初试记录

总体发挥很拉闸,第一次面试感觉方方面面都怪怪的,不过腾讯的面试官非常好,很亲和也很耐心,给出了很多有用的建议。另外也发现了自己很多基础知识点的不足,后续该补补,之前一直刷题好像有点把劲使错方向了。

在牛客上视频面试,需要自己写标准输入输出,不像leetcode上那样自己实现方法就可以了,有点不熟悉,所以平时还是应该都用一用。

下面仅做个人记录,不保证正确性。

1、删除链表中的第n个节点
做过的,但是需要自己写标准输入输出,对牛客又不太熟悉,汗。。。
好在面试官小姐姐人非常好。
LeetCode第 19 题:删除链表的倒数第N个节点(C++)_zj-CSDN博客

#include <bits/stdc++.h>
using namespace std;
struct ListNode{
    int val;
    ListNode *next;
    ListNode(int data) : val(data), next(NULL){}
};

void delete_n(ListNode *root, int k){
    ListNode *p = root, *q = root;
    while(k){
        q = q->next;
        --k;
    }
    while(q->next){
        p = p->next;
        q = q->next;
    }
    auto tmp = p->next;
    p->next = p->next->next;
    delete tmp;
    tmp = NULL;
}

int main(){
    int m; //链表节点个数
    cin >> m;
    auto dummy = new ListNode(-1);
    auto p = dummy;
    int value;
    for(int i = 0; i < m; ++i){//创建链表
        cin >> value;
        p->next = new ListNode(value);
        p = p->next;
    }
    p = dummy->next;
    while(p){
        cout << p->val << " ";
        p = p->next;
    }
    cout << endl;

    int n;//删除倒数第n个节点
    cin >> n;
    delete_n(dummy, n);

    p = dummy->next;
    while(p){
        cout << p->val << " ";
        p = p->next;
    }
    cout << endl;
    return 0;
}

2、已知随机数生成函数random(),返回0的概率是60%,返回1的概率是40%。根据random()实现一个随机数函数f(),使返回0和1的概率是50%。

其实也蛮简单的,但是自己临场的时候卡壳了。。。

一开始是想调用两次然后进行相乘,试了下行不通。

然后给出的思路是:调用一次random() ,如果得到0的话,以1/3的概率进行丢弃;如果得到的是1的话,以1/2的概率丢弃。这样两者的概率也是均匀的,实现就是调用rand()%3或者rand()%2处理一下就可以。但是面试官好像不是很满意,希望不要调用其他函数。

然后我就纠结是要乘几次或者加几次。。。其实调用两次的时候我都把结果写出来了,但是自己好粗心

调用两次:
第一次为0,第二次为1:返回0
第一次为1,第二次为0:返回1
不就均匀了吗?

之前做过一个rand7()生成rand10()的:LeetCode第 470 题:用 Rand7() 实现 Rand10()(C++)_zj-CSDN博客

当时就想到这个题,然后思路就僵化了

#include <bits/stdc++.h>
using namespace std;

int main(){
    while(true){
        int a = random();
        int b = random();
        if(a== 0 && b == 1){
            cout << 1 << endl;
            break;
        }else if(a == 1 && b == 0){
            cout << 0 << endl;
            break;
        }   
    }
    return 0;
}

3、实现函数:void * memmove(void *dst, const void *src, size_t n);

函数功能是从源地址src拷贝n个字节到目的地址dst。

其实这个函数就是c/c++里面的拷贝函数 memcpy(),参数都是一致的。

面试官描述到拷贝n个字节的时候,其实我是有点懵逼的,因为我没想起来怎么样去一个字节一个字节的拷贝。最后面试官提醒我char类型占用一个字节,呜呜呜,我当时还纠结了一下,因为脑子已经有点嗡了,犯这种错误真的让我有点无地自容(总之char类型占一个字节这个是我是一辈子记住了)。

然后还有一个注意的点是,传入参数是void*指针,需要进行类型转换,而且返回值也需要考虑,然后我查阅了一下资料,发现我还是想的太简单了,这个函数还需要进行参数检查、地址比较等好多细节。

参考:c++中内存拷贝函数(C++ memcpy)详解_Guo_guo-CSDN博客

void* Mymemcpy(void *dst, const void *src, size_t n){
    void *ret = dst;
    assert(dst);
    assert(src);
    while(n--){
        *(char*)dst = *(char*)src;
        dst = (char*)dst + 1;
        src = (char*)src + 1;
    }  
    return ret;//增加返回值,方便实现链式表达式
}

但是还会出现个问题就是:源地址和目的地址重叠的时候,输出会出错。比如一下的测试:

#include <bits/stdc++.h>
using namespace std;
void* Mymemcpy(void *dst, const void *src, size_t n){
    void *ret = dst;
    assert(dst);
    assert(src);
    while(n--){
        *(char*)dst = *(char*)src;
        dst = (char*)dst + 1;
        src = (char*)src + 1;
    }  
    return ret;//增加返回值,方便实现链式表达式
}
int main(){
    char s[100] = "hello world!"; 
    char d[15];
    Mymemcpy(s+1, s, strlen(s));
    cout << string(s, strlen(s)) << endl;
    return 0;
}

输出结果是 : “hhhhhhhhh…”,原因是源地址区间和目的地址区间有重叠的地方,代码无意之中将源地址区间的内容修改了。
所以需要对地址覆盖进行判断,分别采取正向、反向拷贝

void* Mymemcpy(void *dst, const void *src, size_t n){
    void *ret = dst;
    assert(dst);
    assert(src);
    if(dst <= src || (char*)dst >= (char*)src + n){
        while(n--){//从低地址往高地址拷贝
            *(char*)dst = *(char*)src;
            dst = (char*)dst + 1;
            src = (char*)src + 1;
        }  
    }
    else{//从高地址往低地址拷贝
        dst = (char *)dst + n - 1; 
		src = (char *)src + n - 1; 
		while (n--) { 
			*(char *)dst = *(char *)src; 
			dst = (char *)dst - 1; 
		    src = (char *)src - 1; 
		} 
    }
    return ret;//增加返回值,方便实现链式表达式
}

最后这样也行(不确定行不行):

void* Mymemcpy(void *dst, const void *src, size_t n){
    void *ret = dst;
    assert(dst);
    assert(src);
    if(dst <= src || (char*)dst >= (char*)src + n){
        while(n--){//从低地址往高地址拷贝
            *(char*)dst = *(char*)src;
            dst = dst + 1;
            src = src + 1;
        }  
    }
    else{//从高地址往低地址拷贝
        dst = dst + n - 1; 
		src = src + n - 1; 
		while (n--) { 
			*(char *)dst = *(char *)src; 
			dst = dst - 1; 
		    src = src - 1; 
		} 
    }
    return ret;//增加返回值,方便实现链式表达式
}

这个函数看似简单,实际上考虑的东西是真的多。

4、二叉树两个结点的最近公共祖先(是二叉树而不是二叉搜索树),节点值不重复。
这个当时感觉有点难的,很麻烦,因为向面试官确认了是二叉树而不是二叉搜索树。而且给出的二叉树不一定存在在子树中。

是二叉搜索树的话,之前做过:LeetCode第 235 题:二叉搜索树的最近公共祖先(C++)_zj-CSDN博客

如果仅仅是二叉树的话,我一开始是想进行遍历存在数组中去操作,但是面试官的意思是最好使用递归。。。

这题最后就没写出来,感觉当时一直想不清楚递归中止条件。

后来发现这个是剑指offer上的原题,还记得腾讯捞我的那天我刚好在刷剑指offer,还差几个题就全部刷完了,但是由于面试准备时间紧张,我就没有看后面剩下的几个题目,其中就包含了这一道(其实当时就剩几题就刷完了,我还纠结了一下,最后决定先准备准备项目介绍、个人介绍这些东西)。然后面试的时候项目啥的根本就没有问,完全没有问,我都懵逼了。可能对我这种比较菜的人来说,这也算是一种警醒吧。要时刻准备啊,不要拖,不要拖!!!

leetcode第 236 题:二叉树的最近公共祖先(C++)_zj-CSDN博客

后面的基本都是计网和操作系统的东西,非科班的我,实在是不会啊,留下了不学无术的泪水。。

5、tcp的time_wait状态, time_wait状态过多有什么影响?怎样消除大量的time_wait状态
这儿我知道2*MSL(最长报文寿命),但是把time_wait状态和FIN_WAIT状态弄混了,结果。。。

首先调用close()发起主动关闭的一方,在发送最后一个ACK之后会进入time_wait的状态,也就说该发送方会保持2MSL时间之后才会回到初始状态。MSL值得是数据包在网络中的最大生存时间。产生这种结果使得这个TCP连接在2MSL连接等待期间,定义这个连接的四元组(客户端IP地址和端口,服务端IP地址和端口号)不能被使用。
在这里插入图片描述
参考:TIME_WAIT过多的危害以及解决TIME_AWAIT过多方案_努力奔跑-CSDN博客
危害:

  • 网络情况不好时,如果主动方无TIME_WAIT等待,关闭前一个连接后,主动方与被动方又建立起新的TCP连接,这时被动方重传或延时过来的FIN包过来后会直接影响新的TCP连接;
  • 同样网络情况不好并且无TIME_WAIT等待,关闭连接后无新连接,当接收到被动方重传或延迟的FIN包后,会给被动方回一个RST包,可能会影响被动方其它的服务连接。
  • 过多的话会占用内存,一个TIME_WAIT占用4k大小
  • 在高并发短连接的TCP服务器上,当服务器处理完请求后立刻主动正常关闭连接。这个场景下会出现大量socket处于TIME_WAIT状态。如果客户端的并发量持续很高,此时部分客户端就会显示连接不上

处理方法:
断网关闭时先调用shutdown函数
SO_REUSEPORT的使用
setsockopt 设置 SO_LINGER 选项
设置一些超时参数

6、tcp连接的数量受什么限制?如何标识一个TCP连接?
系统用一个4四元组来唯一标识一个TCP连接:{local ip, local port,remote ip,remote port}。

client最大tcp连接数
client每次发起tcp连接请求时,除非绑定端口,通常会让系统选取一个空闲的本地端口(local port),该端口是独占的,不能和其他tcp连接共享。tcp端口的数据类型是unsigned short,因此本地端口个数最大只有65536,端口0有特殊含义,不能使用,这样可用端口最多只有65535,所以在全部作为client端的情况下,最大tcp连接数为65535,这些连接可以连到不同的server ip。

server最大tcp连接数
server通常固定在某个本地端口上监听,等待client的连接请求。不考虑地址重用(unix的SO_REUSEADDR选项)的情况下,即使server端有多个ip,本地监听端口也是独占的,因此server端tcp连接4元组中只有remote ip(也就是client ip)和remote port(客户端port)是可变的,因此最大tcp连接为客户端ip数×客户端port数,对IPV4,不考虑ip地址分类等因素,最大tcp连接数约为2的32次方(ip数)×2的16次方(port数),也就是server端单机最大tcp连接数约为2的48次方。

7、进程间通信,进程之间的共享内存

管道pipe:管道是一种半双工的通信方式,数据只能单向流动,而且只能在具有亲缘关系的进程间使用。进程的亲缘关系通常是指父子进程关系。
命名管道FIFO:有名管道也是半双工的通信方式,但是它允许无亲缘关系进程间的通信。
消息队列MessageQueue:消息队列是由消息的链表,存放在内核中并由消息队列标识符标识。消息队列克服了信号传递信息少、管道只能承载无格式字节流以及缓冲区大小受限等缺点。
共享存储SharedMemory共享内存就是映射一段能被其他进程所访问的内存,这段共享内存由一个进程创建,但多个进程都可以访问。共享内存是最快的 IPC 方式,它是针对其他进程间通信方式运行效率低而专门设计的。它往往与其他通信机制,如信号量,配合使用,来实现进程间的同步和通信。
信号量Semaphore:信号量是一个计数器,可以用来控制多个进程对共享资源的访问。它常作为一种锁机制,防止某进程正在访问共享资源时,其他进程也访问该资源。因此,主要作为进程间以及同一进程内不同线程之间的同步手段。
套接字Socket:套解口也是一种进程间通信机制,与其他通信机制不同的是,它可用于不同及其间的进程通信。
信号 ( sinal ) : 信号是一种比较复杂的通信方式,用于通知接收进程某个事件已经发生。

8、自旋锁,互斥锁的区别
互斥锁的起始原始开销要高于自旋锁,但是基本是一劳永逸,临界区持锁时间的大小并不会对互斥锁的开销造成影响,而自旋锁是死循环检测,加锁全程消耗cpu,起始开销虽然低于互斥锁,但是随着持锁时间,加锁的开销是线性增长。

参考:互斥锁和自旋锁的区别 - TakeTry - 博客园

9、了解MySQL?
10、epoll?
11、redis

连答几个不了解是真的蛮尴尬的。。。

12、虚函数实现原理
13、以下代码的问题:

class Base{
public:Base(int val){
    m_num = 0;
    cout << "create Base(int val)" << endl;
}
    private:
    int m_num;
};

class Child: public Base{
    public:
Child(){
     m_num = 0;
    cout << "create is Child()" << endl;
}
private:
    int m_num;
};

int main()
{
    Child child;
}

这个也很懵
问题在于:基类中没有默认的构造函数

如果有带参构造函数,最好定义一个无参构造函数。因为我们一旦定义了构造函数,系统就不自动提供无参构造函数了。总之,我们在定义完带参构造函数之后,最好定义一个无参构造函数,以防会有调用无参构造函数创建对象的情况发生。

应该这样:

class Base{
public:
	Base(){}//必须要有一个无参构造函数
	Base(int val){
    	m_num = 0;
    	cout << "create Base(int val)" << endl;
	}
private:
   	int m_num;
};

class Child: public Base{
public:
	Child(){
     	m_num = 0;
    	cout << "create is Child()" << endl;
	}
private:
    int m_num;
};

int main()
{
    Child child;
}

或者这样:

class Base{
public:
	Base(int val = 0){//提供一个默认值
    	m_num = 0;
    	cout << "create Base(int val)" << endl;
	}
private:
   	int m_num;
};

class Child: public Base{
public:
	Child(){
     	m_num = 0;
    	cout << "create is Child()" << endl;
	}
private:
    int m_num;
};

int main()
{
    Child child;
}

只有当类没有声明任何构造函数的时候,编译器才会主动的生成默认构造函数

这种简单问题,书上都看过好多遍了,还是面试就大脑嗡嗡嗡。。。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值