腾讯客户端一面面经(附答案~~~)

主题一:C++

t1. C++如何判断机器是64位还是32位?

  1. 直接使用 sizeof 方法判断指针大小, 32位机指针就4个字节,64位机指针是8个字节
void* number = 0;
printf("%d \n", sizeof(&number));
  1. 通过一个指针数组内相邻元素的偏移量来找出一个指针元素的大小
int* ps[2];
int n = (char*)&ps[1] - (char*)&ps[0];
  1. 查看宏定义,32位机有宏 i386, 64位机有宏 x86_64
#ifdef __x86_64__
  printf("64bits machine \n");
#elif __i386__
  printf("32 bits machine \n");
#endif

t2. C++的链接是怎么回事?

  1. 链接就是把编译好的目标文件和其他的一些目标文件和库链接在一起,形成最终的可执行文件。
  2. 链接过程:

(1)第一步:读取输入的多个目标的段信息,合并相同或类似的段,确定各个合并后的段的虚拟地址。

(2)第二步:收集各个输入目标文件的符号用来建立全局表。

(3)第三步:因为第一步中的已经确定了合并段的虚拟地址了,所以可以确定全局符号的虚拟地址

(4)第四步:重定位——修正代码指令中的符号的地址。在链接时,通过看目标文件的重定位表,知道有哪些地方需要重定位。因为上一步已经知道了外部符号的虚拟地址,就可以修正代码指令中的符号的地址。


t3. 引用了解嘛?如何理解引用?

  1. 概念:引用是给已经存在的变量取了一个别名,编译器不会为引用变量开辟内存空间,它和它引用的变量共用同一块内存空间。
  2. 表示方式:类型 &引用变量名 = 已定义过的变量名
  3. 特点:(1)引用在定义时必须初始化(2)一个变量可以有多个引用(3)引用一旦引用一个实体,再不能引用其他实体
  4. 使用引用传参的好处:直接操作,在内存中不产生副本,效率更高更安全


主题二:操作系统

t1. 虚拟地址有什么好处?虚拟内存页表中有什么东西?

  1. 好处:(1)方面编译器和操作系统安排程序的地址(2)方便实现各个进程空间之间的隔离,互不干扰,因为每个进程都对应自己的虚拟地址空间(3)实现虚拟存储,从逻辑上扩大了内存。
  2. 虚拟地址到物理地址的映射:虚拟地址被分成虚拟页号和偏移量两部分。虚拟页号可用做页表的索引,以找到该虚拟页面对应的页表项。由页表项可以找到页框号。然后把页框号拼接到偏移量的高位端,以替换掉虚拟页号,形成送往内存的物理地址。
  3. 页表的目的:把虚拟页面映射为页框
  4. 页表的结构:

(1)页号,即页框号

(2)“在/不在” 位:是1时表示该表项是有效的,可以使用;0则表示该表项对应的虚拟页面现在不在内存中,访问该页面会引起一个缺页中断

(3)”保护“ 位:指出一个页允许什么类型的访问。最简单的形式是这个域只有一位,0表示读/写,1表示只读。一个更先进的方法是使用三位,各位分别对应是否启用读、写、执行该页面。

(4)“修改” 位和 “访问” 位:用于记录页面的使用状况。

(5)高速缓存禁止位:最后一位用于禁止该页面被高速缓存。


t2. 如果访问内存中的页表不命中需要进行什么操作,有什么页面置换算法?

  • 会产生缺页中断,OS响应缺页中断,把所需页面调入内存,引起缺页中断的指令被重新执行。

  • 最优页面置换:标记下次要被访问之前要执行的指令数,淘汰标记最大的页面。

  • 先进先出置换:置换最先进入内存的页面,替代的也可能会很快使用,可能有Belady异常

  • LRU置换:置换未使用时间最长的页面,使用计数器近似实现


t3. 说一下线程公有和线程私有?

  • 线程公有的存储区域包括:堆区、常量池、堆外内存

  • 线程私有的存储区域包括:程序计数器、栈区



主题三:计算机网络:

t1. https的请求过程

1.客户端向服务器发起HTTPS的请求,连接到服务器的443端口;

2.服务器将包含非对称加密的公钥的数字证书传递给客户端

3.客户端对证书进行验证,如果有问题,则HTTPS请求无法继续;如果有效,则客户端随机生成一个client key,使用之前得到的公钥对client key进行非对称加密;

4.进行二次HTTP请求,将加密之后的client key传递给服务器;

5.服务器使用私钥进行解密,得到client key,使用client key对数据进行对称加密

6.将对称加密的数据传递给客户端,客户端使用client key解密,得到服务器发送的数据


t2. http状态码,每个状态码的意义,404是啥,302是啥?

  • 1**:信息,服务器收到请求,需要请求者继续执行操作

  • 2**:成功,操作被成功接收并处理

  • 3**:重定向,需要进一步的操作以完成请求

    301 Permanently Moved 永久性转移

    302 Temporarily Moved 暂时性转移

  • 4**:客户端错误,请求包含语法错误或无法完成请求

    400 Bad Request 服务器不能理解请求的语法

    401 Unauthorized 要求用户身份认证

    403 Forbidden 理解但拒绝执行

    404 Not Found 找不到资源

  • 5**:服务器错误,服务器在处理请求的过程中发生了错误

    500 Internal Server Error 服务器内部错误

    501 Not Implemented 不支持请求

    502 Bad Gateway 作为代理的服务器从远程服务器接收到了无效响应



主题四:算法

t1. 使用数组实现栈和循环队列?

#define MAX_ELEMENT 10000
#define ERR_OVERFLOW -1
#define ERR_UNDERFLOW -2
#define ERR_EMPTY -3
  • 使用数组s存放栈中元素,通过指向栈顶的变量p控制栈中元素进出。
class stack{
    
    
public:
    int s[MAX_ELEMENT];
    int p;
    stack(){
    
    
        p = 0;
    }
    int push(int x){
    
    
        if(p == MAX_ELEMENT)
            return ERR_OVERFLOW;
        else{
    
    
            s[p++] = x;
            return x;
        }
    }

    int pop(){
    
    
        if(p == 0)
            return ERR_UNDERFLOW;
        else
            return s[p--];
    }

    int top(){
    
    
        if(p == 0)
            return ERR_EMPTY;
        else
            return s[p];
    }

    bool empty(){
    
    
        return p == 0; 
    }
}
  • 使用数组q存放队列中元素。为了节省空间,使用了循环队列的方式来存储。front指向队头,rear指向队尾的下一个元素。通过front和rear来控制元素在队列中的进出。
class queue{
    
    
public:
	int *q;
	int rear;
	int front;
	int size;
	queue(){
    
    }
	queue(int size){
    
    
		rear = front = 0;
		this->size = size + 1;
		q = new int[size + 1];
	}

	int push(int x){
    
    
		if((rear + 1) % size != front){
    
    
			q[(rear++) % size] = x;  
            return x;
        }
		else{
    
    
			cout << "error" << endl;
            return ERR_OVERFLOW
		}
	}
	
	int dequeue(){
    
    
		if(!empty()){
    
    
			front = (front + 1) % size;
            return front;
		}else{
    
    
			cout << "error" << endl;
            return ERR_UNDERFLOW
		}
	}
	
	int Front(){
    
    
		if(!empty())
			return q[front]; 
        else
            return ERR_EMPTY;
	}
	
	bool empty(){
    
    
		return rear == front;
	}
	void show(){
    
    
		for(int i = 0; i < rear - front; i++){
    
    
			cout << q[(i + front) % size] << ' ';
		}
		cout << endl;
	}
};

t2. 数组中第K个最大元素

利用Partition(分割)算法的特性,每一次分割可以将大于pivot的元素置于pivot的右方,小于pivot的元素置于pivot的左方。通过判断pivot的位置与k的关系,利用二分法不断重复分割过程,最终找到数组中第k大的数。

class Solution {
    
    
public:
    int partition( vector<int>& nums, int lo, int hi ) {
    
    
        int pivot  = nums[lo];

        while ( lo < hi ) {
    
    
            while ( lo < hi && nums[hi] >= pivot )
                hi--;

            nums[lo] = nums[hi];
            while ( lo < hi && nums[lo] <= pivot )
                lo++;

            nums[hi] = nums[lo];
        }

        nums[lo] = pivot;

        return lo;
    }

    int findKthLargest(vector<int>& nums, int k) {
    
    
        int index = -1;
        int size = nums.size();
        int left = 0, right = nums.size() -1 ;
        while ( index != size - (k) ) {
    
    
            
            
            index = partition( nums, left, right );
            if ( k > size - index ) right = index - 1;
            if ( k < size - index ) left = index + 1;
        }

        return nums[size - (k)];
    }
};
  • 时间复杂度: O ( n ) O(n) O(n)

  • 空间复杂度: O ( 1 ) O(1) O(1)


关注GTAlgorithm,专注周赛、面经题解分享,陪大家一起攻克算法难关~
在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/weixin_42396397/article/details/115403621