2020 ByteDance Autumn Recruitment C ++ Preguntas y respuestas seleccionadas de la entrevista (Parte I)

1. Cómo resuelven los punteros inteligentes C ++ el problema de pérdida de memoria

Puntero inteligente compartido 1.shared_ptr

std :: shared_ptr usa el recuento de referencias, y cada copia de shared_ptr apunta a la misma memoria. La memoria se liberará cuando se destruya el último shared_ptr.

Shared_ptr se puede inicializar a través del constructor, la función auxiliar std_make_shared y el método de reinicio:

// Inicialización del constructor

std :: shared_ptrp (nuevo int (1));

std :: shared_ptrp2 = p;

// Para un puntero inteligente no inicializado, se puede inicializar mediante el método de reinicio.

std :: shared_ptrptr; ptr.reset (nuevo int (1));

if (ptr) {cout << “ptr no es nulo. \ n”; }

No puede asignar directamente un puntero sin formato a un puntero inteligente:

std :: shared_ptrp = new int (1); // Error de compilación, no se permite la asignación directa

Obtenga el puntero sin procesar:

Devuelve el puntero original a través del método get

std :: shared_ptrptr (nuevo int (1));

int * p = ptr.get ();

Supresor de puntero:

La inicialización del puntero inteligente puede especificar un eliminador

void DeleteIntPtr (int * p) {

eliminar p;

}

std :: shared_ptrp (nuevo int, DeleteIntPtr);

Cuando la técnica de referencia de p es 0, se llama automáticamente al borrador para liberar la memoria del objeto. El eliminador también puede ser una expresión lambda, por ejemplo

std :: shared_ptrp (nuevo int, [] (int * p) {eliminar p});

Precauciones:

(1). No inicialice varios shared_ptrs con un puntero sin formato.

(2) No cree shared_ptr en los parámetros reales de la función, defínalo e inicialícelo antes de llamar a la función.

(3) No devuelva este puntero como shared_ptr.

(4) Evite las referencias circulares.

2. Puntero inteligente exclusivo Unique_ptr

Unique_ptr es un puntero inteligente exclusivo. No permite que otros punteros inteligentes compartan sus punteros internos, y no está permitido asignar un unique_ptr a otro unique_ptr mediante asignación.

No se permite copiar el unique_ptr, pero se puede devolver a otro unique_ptr a través de una función, y también se puede transferir a otro unique_ptr a través de std :: move, de modo que ya no sea el propietario del puntero original.

Si solo desea un puntero inteligente para administrar recursos o administrar matrices, use unique_ptr. Si desea que varios punteros inteligentes administren el mismo recurso, use shared_ptr.

3.Puntero inteligente de referencia débil Weak_ptr

El puntero inteligente débilmente referenciado débil_ptr se usa para monitorear shared_ptr y no aumenta el recuento de referencias en uno. No administra los punteros dentro de shared_ptr, principalmente para monitorear el ciclo de vida de shared_ptr, y es más como un asistente de shared_ptr.

Weak_ptr no tiene operadores sobrecargados * y -> porque no comparte punteros y no puede manipular recursos. Se utiliza principalmente para obtener derechos de monitoreo de recursos a través de shared_ptr. Su construcción no aumenta el recuento de referencias y su destrucción no reduce el recuento de referencias. , Meramente como un espectador para monitorear la existencia del recurso Guanlide en shared_ptr.

Débil_ptr también se puede utilizar para devolver este puntero y resolver el problema de las referencias circulares.

Necesita un conjunto completo de preguntas de la entrevista y la cuenta pública de las principales preguntas de primera línea de la entrevista por Internet. ¡Siga a Zero Sound Academy para recibirla gratis!
Inserte la descripción de la imagen aquí

2. La diferencia entre enlace suave y enlace duro en Linux

En principio, el número de nodo de inodo del enlace físico y el archivo fuente son el mismo, y los dos son enlaces físicos entre sí. Los números de nodo de inodo del enlace suave y el archivo de origen son diferentes, y el bloque al que apuntan también es diferente.El nombre de la ruta del archivo de origen se almacena en el bloque de enlace suave.

De hecho, el vínculo físico y el archivo de origen son el mismo archivo, mientras que el vínculo suave es un archivo separado, similar a un acceso directo, que almacena la información de ubicación del archivo de origen para señalarlo fácilmente.

En términos de restricciones de uso, no se pueden crear enlaces duros a directorios, enlaces duros a diferentes sistemas de archivos, enlaces duros a archivos no existentes; se pueden crear enlaces blandos a directorios, y se pueden crear enlaces blandos a través de sistemas de archivos.

Cree un enlace flexible a un archivo que no existe.

3. ¿Cuál es el mecanismo de control de congestión de TCP? Por favor hable brevemente

Sabemos que TCP muestrea el RTT a través de un temporizador y calcula el RTO. Sin embargo, si el retraso en la red aumenta repentinamente, la única respuesta de TCP es retransmitir los datos. Sin embargo, la retransmisión causará problemas en la red. La carga más pesada dará lugar a mayores retrasos y más pérdida de paquetes, lo que conduce a un círculo vicioso y, en última instancia, forma una "tormenta de red": el mecanismo de control de congestión de TCP se utiliza para hacer frente a esta situación.

Primero, debe comprender un concepto. Para ajustar la cantidad de datos que se enviarán al remitente, se define una "Ventana de congestión". Al enviar datos, el tamaño de la ventana de congestión se compara con el tamaño de la ventana de ack del receptor. El menor se utiliza como límite superior de la cantidad de datos enviados.

El control de la congestión consta principalmente de cuatro algoritmos:

1. Inicio lento: Significa que la conexión que acaba de incorporarse a la red, se acelera poco a poco, no llena la carretera en cuanto surge.

Una vez establecida la conexión, primero se inicializa cwnd = 1, lo que indica que se pueden transmitir datos del tamaño de MSS.

Siempre que se recibe un ACK, cwnd ++; aumenta linealmente

Siempre que ha pasado un RTT, cwnd = cwnd * 2; aumenta exponencialmente

El umbral ssthresh (umbral de inicio lento) es un límite superior. Cuando cwnd> = ssthresh, entrará en el "algoritmo para evitar la congestión".

2. Evitación de la congestión: cuando la ventana de congestión cwnd alcanza un umbral, el tamaño de la ventana ya no aumenta exponencialmente, sino que aumenta linealmente para evitar la congestión de la red causada por un crecimiento excesivo.

Siempre que se recibe un ACK, cwnd = cwnd + 1 / cwnd

Siempre que haya pasado un RTT, cwnd = cwnd + 1

Se produce congestión: cuando se produce la pérdida de paquetes para la retransmisión de paquetes de datos, significa que la red está congestionada. Hay dos casos para procesar:

Espere hasta que expire el RTO y retransmita el paquete

sshthresh = cwnd / 2

cwnd restablecer a 1

3. Ingrese al proceso de inicio lento

La retransmisión está habilitada cuando se reciben 3 ACK duplicados, en lugar de esperar el tiempo de espera de RTO

sshthresh = cwnd = cwnd / 2

Ingrese al algoritmo de recuperación rápida-Fast Recovery

4. Recuperación rápida: Se han recibido al menos 3 Acks duplicados, lo que indica que la red no es tan mala y se puede recuperar rápidamente.

cwnd = sshthresh + 3 * MSS (3 significa confirmar que se han recibido 3 paquetes)

Vuelva a transmitir el paquete de datos especificado por ACK duplicados

Si vuelve a recibir Acks duplicados, entonces cwnd = cwnd +1

Si se recibe un nuevo Ack, entonces cwnd = sshthresh y luego ingrese el algoritmo para evitar la congestión.

4. Utilice la clasificación rápida para ordenar la lista enlazada individualmente

#include<iostream>
#include<ctime>
using namespace std;
 
struct ListNode {
    
    
 int val;
 ListNode *next;
 ListNode(int x) : val(x), next(NULL) {
    
    }
};
 
class Solution {
    
    
public:
 ListNode *sortList(ListNode *head) {
    
    
 if (head == NULL)
 return head;
 
 ListNode* tail=head;
 ListNode* end = tail;
 while (tail->next != NULL) {
    
     
 //if (tail->next->next == NULL)
 // end = tail;
 tail = tail->next; 
 
 }
 qSortList(head, tail);
 return head;
 }
 ListNode* Partition(ListNode* head, ListNode* tail)
 {
    
    
 
 ListNode* newHead = new ListNode(0);
 newHead->next = head;
 ListNode* ipt = newHead, *jpt = head;
 int ar = tail->val;
 while (jpt != tail)
 {
    
    
 if (jpt->val < ar)
 {
    
    
 ipt = ipt->next;
  swap(ipt->val, jpt->val);
 }
 jpt = jpt->next;
 }
 ipt = ipt->next;
 swap(ipt->val, tail->val);
 return ipt;
 }
 void qSortList(ListNode*head, ListNode*tail)
 {
    
    
 
 if (head == NULL)
 return;
 if (tail == NULL)
 return;
 if (tail->next!=NULL && tail->next == head)
 return;
 else if (tail->next!=NULL &&
tail->next->next!=NULL && 
tail->next->next== head)
 return;
 
 if (head == tail)
 return;
 if (head->next == tail)
 {
    
    
 if (head->val > tail->val)
 swap(head->val,tail->val);
 return;
 }
 
 ListNode* mid = Partition(head, tail);
 ListNode*tail1 = head;
 if (tail1!=mid)
 while (tail1->next != mid) tail1 = tail1->next;
 ListNode*head2 = mid->next;
 qSortList(head, tail1);
 qSortList(head2, tail);
 }
};
int main()
{
    
    
 ListNode* head0 = new ListNode(200);
 ListNode* head = head0;
srand(time(NULL));
 for (int i = 0; i < 10000; i++)
 {
    
    
 
 ListNode* mNode = new ListNode(rand() % 4);
 head0->next = mNode;
 head0 = head0->next;
 
 }
 Solution sln;
 ListNode*res=sln.sortList(head);
 while (res->next != NULL)
 {
    
    
 cout << res->val << " ";
 res = res->next;
 }
 return 0;
}

5. ¿Por qué la computadora puede reconocer el código binario de la máquina?

1. La base teórica de las computadoras

El álgebra booleana es la base teórica de las computadoras,

Boolean (operación booleana) obtiene nuevas formas de objetos realizando operaciones de unión, diferencia e intersección en más de dos objetos. El sistema proporciona 4 operaciones booleanas: Unión (Unión), Intersección (Intersección) y Resta (Resta, incluidas AB y BA).

1) Y lógica y multiplicación

En el principio de multiplicación, la variable independiente es una condición necesaria para el establecimiento de la variable dependiente La definición de lógica es exactamente la misma que la descripción del principio de multiplicación, por lo que corresponde a lógica y multiplicación.

2) O lógica y suma

En el principio de adición, la variable independiente es una condición suficiente para el establecimiento de la variable dependiente, o la definición de lógica es simplemente consistente con la descripción del principio de adición, por lo que la lógica de o corresponde a la suma.

La multiplicación es la operación lógica Y generalizada y la suma es la operación lógica O generalizada. Y la operación lógica puede considerarse como un caso especial de multiplicación. O las operaciones lógicas pueden considerarse casos especiales de suma.

En resumen, el principio de multiplicación y suma se puede considerar como una expresión cuantitativa de la lógica Y / o la lógica; y la lógica y / o la lógica se puede considerar como una expresión cualitativa del principio de multiplicación y suma.

En términos sencillos: este es un uso de "y" o "no", "falso" y "verdadero" para describir la relación lógica entre dos cantidades cualesquiera (que pueden ser cualquier concepto concreto o abstracto).

2. Álgebra lógica y circuitos informáticos

Aplicado a la lógica, se interpreta que 0 es falso, 1 es verdadero, ∧ es y, ∨ es o, y ¬ no es. Las expresiones que involucran variables y operaciones booleanas representan la forma de enunciados. Se puede probar que dos de tales expresiones son equivalentes utilizando los axiomas anteriores si y solo si las formas de enunciado correspondientes son lógicamente equivalentes. Dado que la pequeña unidad lógica del álgebra lógica es altamente compatible con el sistema binario, y la apertura y el cierre más simples del circuito también corresponden a 0 y 1, existe una serie de circuitos basados ​​en la teoría del álgebra lógica para expresar la teoría lógica básica. Es la base para que la computadora tenga la capacidad de juzgar y calcular.

3. Proceso de reconocimiento de máquina binaria

De acuerdo con los dos puntos anteriores, podemos saber que si se usa el principio binario como base para el juicio y cálculo por computadora, hará posible la realización de la fabricación de circuitos. Sin embargo, no existe una naturaleza binaria. Para enfrentar este problema, una estipulación humana unificada para representar otros datos no binarios Convertido en código de máquina binario para que las computadoras lo lean. sin embargo. A medida que los requisitos para las capacidades de procesamiento de datos son cada vez más altos, el procesamiento de datos es cada vez más grande. Para resolver este problema, apareció el ensamblador, en lugar de convertir datos no binarios en datos binarios, pero esto está lejos de ser suficiente, para mejor Procesando, combine directamente el hardware y el ensamblador, y desarrolle un ensamblador de nivel superior por separado (esencialmente todo tipo de programas que ahora son bien conocidos), de modo que el hardware y el software estén completamente separados. En esencia, separa completamente la conversión de datos del juicio y la entrada, almacenamiento y salida de datos, por lo que los usuarios de computadoras no necesitan prestar atención a operaciones específicas de la computadora.

En otras palabras, la razón por la que la computadora puede reconocer el código binario de la máquina es porque hay circuitos digitales fabricados según el principio del álgebra lógica, y se ha explicado por qué se utiliza el sistema binario. Al mismo tiempo, también debe comprender por qué el programa es falso, 1 es verdadero, ∧ es y, ∨ es o, ¬ son los cinco elementos, y por qué proviene el algoritmo, que se refiere a la relación algebraica lógica entre los datos de optimización.

6. Para un árbol binario, cómo recorrerlo jerárquicamente y generarlo en filas.

解法1:
class Solution {
    
    
public:
 vector<vector<int>> levelOrder(Node* root) {
    
    
 BFS(root, 0);
 return res;
 }
public:
 void BFS(Node* node, int level)
 {
    
    
 if(node == nullptr) return;
 if(level == res.size())
 res.push_back(vector<int> ());
 res[level].push_back(node->val);
 for(auto n:node->children)
 BFS(n, level+1);
 }
private:
 vector<vector<int>> res;
};
 
解法2:
class Solution {
    
    
public:
 vector<vector<int>> levelOrder(Node* root) {
    
    
 vector<vector<int>> output;
 if (!root) return output;
 queue<Node*> treeTemp;
 treeTemp.push(root);
 auto treeNum = treeTemp.size();
 
 while (treeNum) {
    
    
 vector<int> valTemp;
 for (auto i=0; i<treeNum; ++i) {
    
    
 root = treeTemp.front();
 valTemp.push_back(root->val);
 treeTemp.pop();
 for (auto childTemp:root->children)
 treeTemp.push(childTemp);
 }
 output.push_back(valTemp);
 treeNum = treeTemp.size();
 }
 return output;
 }
};

Supongo que te gusta

Origin blog.csdn.net/lingshengxueyuan/article/details/108383262
Recomendado
Clasificación