2023 C/C++ Software Development Engineer School Recruitment Interview Frequently Asked Knowledge Points Review Part 6

41. DHCP protocol (Dynamic Host Configuration Protocol)

The DHCP protocol is an application layer protocol that uses the services provided by the transport layer protocol UDP

The port of the DHCP server isUDP 67

The port for the DHCP client isUDP 68

insert image description here

The working process of the DHCP protocol and how a computer without an IP address dynamically configures an IP through a DHCP server in the network

Prerequisite: There is a DHCP server in the LAN, or there is a DHCP relay agent , and the DHCP client software runs automatically after the computer is turned on.


①DHCP client actively looking for DHCP server:DHCP DISCOVER

  • The DHCP client sends a DHCP discovery message by broadcasting (destination address: 255.255.255.255source address 0.0.0.0destination port: ) to find a DHCP server;67

  • Because the port is 67, only the DHCP server will respond to the discovery message, and other hosts will discard the message; after the DHCP server receives the message, it will analyze the message layer by layer to obtain the client MAC, the transaction ID of the message and other information , and then query whether there is already configured network configuration information locally according to the client MAC

  • If it exists, DHCP OFFERreply in the message, if it does not exist, generate configuration information in the default wayDHCP OFFER and reply in the message


②DHCP server sends configuration information providing message:DHCP OFFER

  • The DHCP server sends messages by broadcasting (destination address: 255.255.255.255source address DHCP服务器自己的IP地址destination port: 68) .DHCP OFFER

  • Because the port is 68, only the host running the DHCP client will receive the message, and then analyze the message to obtain the transaction ID , so as to determine whether it is a message sent to itself, and discard it if not.

  • Provide configuration information in DHCP OFFERthe message, IP地址 子网掩码 地址租期 默认网关 DNS服务器etc.

  • If there are multiple DHCP servers in the network, the client may obtain multiple DHCP OFFERmessages, usually the DHCP client will choose the one that arrives first, and then send a DHCP request message to the DHCP server


③DHCP client sends DHCP request message :DHCP REQUEST

  • The DHCP client sends messages by broadcasting (destination address: 255.255.255.255source address 0.0.0.0destination port: 67) .DHCP REQUEST

  • The broadcast is sent because there is no need to unicast each DHCP server to send its own selection, and the source address is 0.0.0.0because the DHCP client has not yet set its own IP address.

  • DHCP REQUESTIn the message 事务ID DHCP客户的MAC地址 接收的租约中的IP地址 提供此租约的DHCP服务器的IP地址, the DHCP server can know whether its offer has been selected after receiving the message

  • Then the DHCP server sends a confirmation message to the DHCP client


SelectedOFFERThe DHCP server sends a confirmation message to the DHCP client:DHCP ACK

  • The DHCP server sends a confirmation message by broadcasting (destination address: 255.255.255.255source address DHCP服务器自己的IP地址destination port: ).68

  • After the DHCP client receivesDHCP ACKAfter receiving the message, you can use the network configuration information such as the leased IP address, and then communicate with other hosts in the network.

  • Then there is normal use and communication...


Renewal phase of DHCP client (destination IP: DHCP服务器的IP地址source IP address: 租用的IP地址port: 67)

  • a) 0.5 times the lease period: The DHCP client actively renews the contract, sends DHCP REQUESTa message, and then the DHCP response has three situations

    • Case 1: The DHCP server agrees to renew the contract and sends DHCP ACKa message, so the DHCP client has a new lease

    • Situation 2: The DHCP server does not agree to renew the contract and sends DHCP NACKa denial message, so the DHCP client immediately discards the current configuration information such as the IP address, and then re-executes the above step ①

    • Case 3: If the DHCP server does not respond, continue to use the previous IP address configuration information and wait for the arrival of 0.875 times the lease period

  • b) 0.875 times the lease period: Repeat the action of 0.5 times the lease period, if the DHCP server still does not respond, then wait for the lease period to be exhausted

  • c) when the lease expires: Immediately give up the current configuration information such as IP address, and then re-execute step ①


The DHCP client can terminate the lease period provided by the DHCP server at any time during the lease period :DHCP RELEASE

At this time, the message is sent in broadcast mode (destination address: 255.255.255.255source address 0.0.0.0destination port: 67)DHCP RELEASE

42. allocate and deallocate in STL

There are two levels of memory space allocation in STL: the first level configurator and the second level configurator

First first question :为什么需要两级空间配置器?

①Frequently applying to release memory on the heap will cause a lot of memory fragmentation ;

② Calling malloc and free to apply to release memory will add a lot of additional information to the space, reducing the utilization of space ;

Then the second question :二级空间配置器与一级空间配置器之间的协调合作关系?

①The secondary space configurator is responsible for the application of small blocks of memory , generally less than or equal to 128 bytes ; STL selects the secondary space configurator by default, and if the requested space is greater than 128 bytes, then use the primary configurator

The first-level space configurator is responsible for the application of larger memory , generally larger than 128 bytes ;

Third question :二级空间配置

Use an array to store a total of 16 block chain tables ranging in size from 8 to 128, and maintain 16 of them free-list.

1. The first-level configurator directly uses the malloc, free, and relloc functions

2. The second-level configurator calls the first-level configurator according to the size of the requested block, if it is greater than 128 bytes; if it is less than or equal to 128 bytes, the first-level configurator is not used;

3. allocate()It is a space configuration function. First judge the size of the requested block. If it is greater than 128 bytes, it will call the first-level configurator; if it is less than or equal to 128 bytes, it will check free-listwhether there are available blocks, and use it directly if there is any; If you want to free-listapply for memory space

4. dellocate()It is a space release function. First judge the size of the space. If it is greater than 128 bytes, it will call the first-level configurator; if it is less than or equal to 128 bytes, it will find the corresponding one free-listand release the memory .

43. Virtual function table, virtual function table pointer
  • The function of the virtual function in C++ is mainly to realize the function of polymorphism ; and the so-called polymorphism means that when the parent class pointer points to the subclass , using the pointer to call the same function will show various behaviors because of the different subclasses pointed to. form

  • Implementation mechanism of virtual function:

    The implementation of the virtual function mechanism mainly depends on the compiler:

    1. If a class has a virtual function, then there will be a corresponding virtual function table (usually placed in the constant area ) of the class, which is referred to here as 虚表the function address of the virtual function虚表 of the class.
      • The existence of the virtual function table is related to the class
    2. An object instantiated by this class has a virtual function table pointer at the head of the object storage space , referred to as vptr; vptrit points to the virtual function table of this class, and the address of the virtual function of this class is stored in the virtual function table, and then there is The address of the virtual function can be used to find the virtual function and execute it. (Virtual functions are stored in the code area )
    3. In general, the virtual functions stored in the virtual function table will be stored in the order of declaration

    If the subclass Deriveinherits the parent class Base: (assuming both the parent class and the subclass have virtual functions)

    1. The subclass Dreivewill also have its own virtual function table . In the virtual function table, the virtual function of the parent class comes first, and the virtual function of the subclass comes after. Note that this is the virtual function table (usually stored in the constant area )

    2. The head of the memory space of the subclass object is the virtual function table pointer , which points to the virtual function table it owns.

    3. If the subclass rewrites the virtual function of the parent class, the address of the rewritten virtual function 子类的虚函数表will be replaced with the function newly written by the subclass (this feature determines that if the parent class pointer points to the object of the subclass, Polymorphic effects can be achieved )

    4. For the virtual function of the parent class that has not been overridden, the function pointers in the virtual function table of the subclass and the virtual function table of the parent class are the same, which is equivalent to copying, but not the same

    If the subclass Deriveinherits from multiple parent classes Base1, Base2,Base3

    1. A subclass has its own virtual function table, and if it inherits multiple parent classes, it will have multiple virtual function tables of its own ;

    2. The head of the subclass object is a pointer to the virtual function table. If there are multiple parent classes, there will be multiple virtual function table pointers , pointing to multiple virtual function tables owned by the subclass.

    3. For the virtual functions newly defined by the subclass itself, the addresses of these virtual functions will be stored in the first virtual function table

    4. If the subclass rewrites the virtual function of the parent class, which virtual function of the parent class is rewritten, the corresponding function address will be modified in the corresponding virtual function table

    About initialization timing:

    • When the virtual function table is compiled , the compiler will create it. The virtual function table exists in the constant area , and the virtual function exists in the code area ;
    • The virtual function table pointer is a part of the object and is initialized when the object is constructed; the virtual function and the virtual function table belong to the class, so they will be established at compile time.
    • We often say that the compiler will call a certain function balabala ( I think this expression is a bit misleading ). I think what it wants to express is: when the compiler encounters the code to create an object in the process of compiling the source file into a binary file, it will automatically generate the binary code that calls the constructor, which is the so-called compiler call ...
  • Are the virtual function tables of the subclass and the parent class independent?

    答:

    1. Regardless of whether the subclass has its own newly created virtual function and whether it overrides the virtual function of the parent class, the subclass has its own virtual function table ;
    2. Because if the subclass rewrites the virtual function or newly defines the virtual function, it will change the content of the virtual function table. If the virtual function table is shared with the parent class, then the parent class cannot call its own virtual function?
44. Related content of lvalue and rvalue

左值与右值:

Generally, on the left side of the equal sign, the address that can be taken is the lvalue ;

Generally, temporary values, future values, and literal values ​​without addresses are rvalues ;

左值引用与右值引用

Lvalue references : Lvalue references can point to lvalues, but cannot point to rvalues

  • 原因:Because the lvalue reference is equivalent to the alias of the variable, the lvalue reference should have the ability to modify the variable, but the rvalue has no address, so it cannot be modified, so the lvalue reference cannot point to the rvalue

  • But const 左值引用the rvalue is not modified, so const 左值引用it can point to the rvalue, for example const int& ref_left_a = 5;,

  • In vector's push_back(const int& val) —vec.push_back(5) uses lvalue reference to rvalue


Rvalue reference:&& , can point to an rvalue, but cannot point to an lvalue . After having an rvalue reference, you can use an rvalue reference to modify the rvalue

  • Why can an rvalue reference point to an rvalue, and its value can be modified?

  • Because the process of an rvalue reference pointing to an rvalue is essentially promoting an rvalue to an lvalue, and then defining an rvalue reference to point to an lvalue through the move function.

  • After the rvalue reference points to an lvalue through the move function, the lvalue can be modified.

右值引用如何指向左值?

Answer: Use the move() function to convert an lvalue to an rvalue , allowing an rvalue reference to point to an lvalue ; this is why an rvalue reference can modify an rvalue.

左值引用和右值引用本身是左值!

As long as the declared lvalue references and rvalue references are themselves lvalues!

But an rvalue reference returned by a function is an rvalue

So rvalue references can be either lvalues ​​(direct declarations: int && ref b = 5;) or rvalues ​​(functions returning rvalues: std::move(a)return values)

In short

1. An lvalue reference can only point to an lvalue, but an lvalue reference with const can point to an rvalue

2. An rvalue reference can point directly to an rvalue, or use the move() function to point to an lvalue

3. The rvalue reference as a function parameter is more flexible: According to the second item above, you can receive left and right values, and you can also modify the incoming parameters . According to the first item, although you can use const to receive rvalues, you cannot modify them. .

右值引用和move的应用场景

Mainly implement move semantics in STL and custom classes to avoid copying

Note: Is it possible to provide one 移动构造函数to move the data of the copied person over, and the copied person will not need it later , so that deep copying can be avoided

场景1:Used to implement the move constructor, directly move the copied data over

When there is no copy construction when there is no rvalue reference , the const lvalue reference is used const type& val: in this way , both lvalue and rvalue can be received .

  • Using lvalue reference, because the reference is used when the parameter is passed into the function, it avoids the value copy when passing the parameter once . However, when deep copying is performed inside an object, a copy operation is unavoidable .

  • Then if a certain value is no longer needed, the above copy operation is a repeated operation, if the copied data can be directly transferred to the new object . Then the only remaining copy operation can also be avoided, thereby improving efficiency. But if you still use the const lvalue reference , it cannot be realized, because constthere are , so the lvalue reference cannot be modified, and it is impossible to directly transfer the copied data to the new object safely .


② At this time, rvalue references come in handy : because the parameter of the move constructor is an rvalue reference type&& val , the function can receive rvalues ​​and lvalues ​​(when receiving lvalues, use move()a function to convert lvalues ​​​​to rvalues)

  • In this way, the lvalue can be modified inside the function. Because when an rvalue reference points to an lvalue , the rvalue reference can modify the contents of the lvalue.

  • If the parameter is an rvalue, and the rvalue reference points to the rvalue, it is also logical and grammatical to apply move semantics to a will-xvalue, and the rvalue reference can also be modified after using an rvalue reference to refer to a will - xvalue

场景2:push_back()In functions similar to those in vector empalce_back(), there will be overloading of rvalue reference parameters, and active use of move()functions will trigger move semantics

In this case, copying can be reduced and the contents of the dying object can be moved directly to the container:

vector<string> vec;
string a = "hello";
// 直接push_back()左值
vec.push_back(a);  // a不变
vec.push_back(move(a)); // 触发了移动语义,将a中的内容“移动”到了容器内部,a将变成一个空字符串

// 也可以直接填右值
vec.push_back("hello");

In addition, 智能指针unique_ptrthere is only a move constructor, which can only move the internal ownership of the object, and cannot copy

场景3:It is in the forward() function, but this is not commonly used, so I don’t know much about it

45. Implementation of Binary Heap
1. Binary heap
  • The essence of the binary heap is a complete binary tree , and it is stored in an array

  • Therefore, the binary heap stored in the array has the following properties:

    If it idx = 0is stored in the position in the array root, the relationship between the index of the parent node and the left and right child nodes is as follows:

    • Parent node is i, left child node 2i + 1right child node2i+2

    If it idx = 1is stored in the position in the array root, the relationship between the index of the parent node and the left and right child nodes is as follows:

    • Parent node is i, left child node 2iright child node2i+1

  • Maximum heap (big top heap): each node is larger than its two child nodes

  • Min heap (small top heap): each node is smaller than his two child nodes

2. Use binary heap to implement priority queue
  • In fact, the so-called priority queue is a binary heap.

  • Since it is a queue, it is natural to implement some basic functions:

    1. Structure

    2. Enter the queuepush()

    3. Get out of the queue (get the first element of the queue and pop it up)get_pop_front()

    4、size()

    5. Is it empty?empty()

  • In fact, there are two functions that are very important to realize the above functions, that is, the sum of nodes 上浮swim(). 下沉sink()These two functions are the essence of the binary heap!

Show Me Code!

class PriorityQueue {
    
    
private:
	int* m_pArray;
    int m_length;
    int m_cap;
    
    // 节点上浮--大顶堆
    void swim_max(int idx){
    
    
        while(idx > 1 && m_pArray[idx] > m_pArray[idx / 2]) {
    
     // 当前节点大于父节点
            int temp = m_pArray[idx];						  // 交换
            m_pArray[idx] = m_pArray[idx / 2];
            m_pArray[idx / 2] = temp;
            
            idx = idx / 2;	 								  // 节点更新
        }
    }
    
    // 节点上浮--小顶堆
    void swim_small(int idx){
    
    
    	while(idx > 1 && m_pArray[idx] < m_pArray[idx / 2]) {
    
    
            int temp = m_pArray[idx];
            m_pArray[idx] = m_pArray[idx / 2];
            m_pArray[idx / 2] = temp;
            
            idx = idx / 2;
        }
    }
    
    // 节点下沉--大顶堆
    void sink_max(int idx){
    
    
        while(2 * idx <= m_length) {
    
    
            int l = 2 * idx;// 左节点
            if(l < m_length && m_pArray[l] < m_pArray[l + 1]) // 求左右节点的最大值(大顶堆)/最小值(小顶堆)
                l++;
            if(m_pArray[l] < m_pArray[idx])		// 如果已经满足的条件--那么break; 下沉结束
                break;
            
            int temp = m_pArray[idx];			// 交换
            m_pArray[idx] = m_pArray[l];
            m_pArray[l] = temp;
            
            idx = l;							// idx = l; 更新idx
        }
    }
    
    // 节点下沉--小顶堆
    void sink_max(int idx){
    
    
        while(2 * idx <= m_length) {
    
    
            int l = 2 * idx;
            if(l < m_length && m_pArray[l] > m_pArray[l + 1])
                l++;
            if(m_pArray[l] > m_pArray[idx])
                break;
            
            int temp = m_pArray[idx];
            m_pArray[idx] = m_pArray[l];
            m_pArray[l] = temp;
            
            idx = l;
        }
    }
public:
    // 构造,参数N表示队列或二叉堆的最大容量
    PriorityQueue(int N) {
    
    
        m_pArray = new int[N + 1]; // 因为从数组的下标1开始存节点的
        m_length = 0;
        m_cap = N;
    }
    
    // 析构
    ~PriorityQueue() {
    
    
        delete []m_pArray;
        m_pArray = nullptr;
        m_length = 0;
        m_cap = 0;
    }
    
    int size() {
    
    
        return m_length;
    }
    
    bool empty() {
    
    
        return m_length == 0;
    }
    
    // 入队--大顶堆
    void push(int ele) {
    
    
        if(m_length >= m_cap) return;
        m_pArray[++m_length] = ele;
        swim_max(m_length);
        // 或者小顶堆时用 swim_min(m_length); 
    }
    
    // 出队--大顶堆
    int get_pop_front() {
    
    
        int ret = m_pArray[1];
        m_pArray[1] = m_pArray[m_length--];// 将最后的值交换到root节点
        m_pArray[m_length + 1] = 0;			// 尾部置空
        sink_max(1);
        // 或者小顶堆时用 sink_min(1);
        
        return ret;
    }
};
3. Use binary heap to implement heap sorting
  • For heap sorting using a binary heap, the above priority queue can be used
  • In the sorting algorithm function, create a priority queue, that is, a binary heap, and then add all array elements to the heap (queue)
  • Then take each element from the priority queue in turn and put it in the original array
  • return the original array

For large top heaps, sort them in descending order; for small top heaps, sort them in ascending order;

void headSort(vector<int> &nums){
    
    
    PriorityQueue PQ(nums.size() + 1);  // PQ是一个局部变量
    //PriorityQueue* pPQ = new PQ(nums.size() + 1);
    for(auto it : nums) PQ.push(it);
    for(auto & it : nums)  it = PQ.get_pop_front();
    
    // delete pPQ;
}

Guess you like

Origin blog.csdn.net/qq_40459977/article/details/127518921
Recommended