C ++ esas cosas básicas notas de estudio de parte avanzada

cálculo del tamaño de la clase

  • Las clases vacías tienen un tamaño de 1 byte.
  • En una clase, la función virtual en sí, las funciones miembro (incluidas las estáticas y no estáticas) y los miembros de datos estáticos no ocupan el espacio de almacenamiento del objeto de clase.
  • Para una clase que contiene funciones virtuales, no importa cuántas funciones virtuales haya, solo hay un puntero virtual, del tamaño de vptr.
  • Herencia ordinaria, la clase derivada hereda todas las funciones y miembros de la clase base, y el tamaño debe calcularse de acuerdo con la alineación de bytes.
  • La herencia de función virtual, ya sea herencia única o herencia múltiple, hereda el vptr de la clase base. (4 bytes para sistema operativo de 32 bits, 8 bytes para sistema operativo de 64 bits)!
  • Herencia virtual, heredando el vptr de la clase base.

virtual

Funciones virtuales puras y clases abstractas

¡Las funciones virtuales puras (o funciones abstractas) en C++ son funciones virtuales que no implementamos! ¡Simplemente lo declaramos!¡Declaramos una función virtual pura asignando un valor de 0 en la declaración!

// 抽象类
Class A {
    
    
public: 
    virtual void show() = 0; // 纯虚函数
    /* Other members */
}; 

Función virtual pura: una función virtual sin cuerpo de función
Clase abstracta: una clase que contiene una función virtual pura

Los constructores no pueden ser virtuales y los destructores pueden ser virtuales

Cuando un puntero de clase base apunta a un objeto de clase derivado y el objeto se elimina, es posible que deseemos llamar al destructor apropiado. Solo se pueden llamar destructores de clase base si el destructor no es virtual.

Funciones virtuales y polimorfismo en tiempo de ejecución

Las llamadas a funciones virtuales dependen del tipo de objeto al que se apunta o al que se hace referencia, no del tipo del puntero o la referencia en sí.

Las funciones estáticas no se pueden declarar como funciones virtuales, ni se pueden modificar mediante palabras clave const y volatile

Las funciones miembro estáticas no pertenecen a ningún objeto de clase o instancia de clase, por lo que incluso agregar virtual a esta función no tiene sentido. Las
funciones virtuales son manejadas por vptr y vtable. vptr es un puntero, creado y generado en el constructor de la clase, y solo se puede acceder con este puntero.Las funciones miembro estáticas no tienen este puntero, por lo que no se puede acceder a vptr.

Funciones virtuales y tablas virtuales

Para implementar funciones virtuales, C ++ usa una forma especial de enlace tardío llamada tabla virtual. La tabla virtual es una tabla de búsqueda para resolver llamadas a funciones de forma dinámica/enlazada en tiempo de ejecución. Las tablas virtuales a veces reciben otros nombres, como "vtable", "tabla de funciones virtuales", "tabla de métodos virtuales" o "tabla de despacho".

Las mesas virtuales son en realidad muy simples, aunque un poco complicadas de describir con palabras. Primero, cada clase que usa funciones virtuales (o se deriva de una clase que usa funciones virtuales) tiene su propia tabla virtual . La tabla es solo una matriz estática establecida por el compilador en el momento de la compilación . La tabla virtual contiene una entrada para cada función virtual que pueden llamar los objetos de la clase . Cada entrada en esta tabla es simplemente un puntero de función a una función derivada accesible por esa clase.
En segundo lugar, el compilador también agrega un puntero oculto a la clase base, que llamamos vptr. vptr se establece automáticamente cuando se crea una instancia de clase para que apunte a la tabla virtual para esa clase . A diferencia del puntero this, que en realidad es un parámetro de función utilizado por el compilador para resolver las autorreferencias, vptr es un puntero real.
Por lo tanto, hace que la asignación de cada objeto de clase sea mayor en el tamaño de un puntero. Esto también significa que las clases derivadas heredan vptr, lo cual es importante.

La tabla virtual de la subclase copia la tabla virtual de la clase principal y el Func1 de la subclase sobrescribe el Func1 de la clase principal. (Sobrescribir se refiere a la cobertura de la función virtual en la tabla virtual)
Reescritura de la función virtual : el concepto de la capa de sintaxis, la subclase reescribe la implementación de la función virtual heredada de la clase principal.
Cobertura de función virtual : el concepto de la capa principal, la tabla virtual de la subclase, copiar la tabla virtual de la clase principal y modificarla, y anular la función virtual.
Reescritura y cobertura de funciones virtuales, la reescritura se llama en el nivel de sintaxis y la cobertura se llama en el nivel de principio.

Cuando el programa se está ejecutando, cuando un puntero llama a una función, primero determinará el tipo de objeto al que apunta el puntero y luego llamará a la función correspondiente de acuerdo con la tabla virtual del objeto.
inserte la descripción de la imagen aquí

estructura

estructura en C

  • En C, struct se usa simplemente como un tipo compuesto de datos, es decir, solo se pueden colocar miembros de datos en la declaración de estructura, pero no se pueden colocar funciones en ella.
  • Los modificadores de acceso de C++, como public, protected y private, no se pueden usar en las declaraciones de estructura de C, pero se pueden usar en C++.
  • Para definir una variable de estructura en C, si se usa la siguiente definición, se debe agregar struct. Las estructuras C no se pueden heredar (no existe tal concepto).
  • Si el nombre de la estructura es el mismo que el nombre de la función, ¡puede ejecutarse normalmente y llamarse normalmente! Por ejemplo: void Base() {} que no entre en conflicto con struct Base se puede definir.

estructura en C++

Compare con C de la siguiente manera:

  • En la estructura de C++, no solo se pueden definir datos, sino también funciones.
  • Los modificadores de acceso se pueden usar en estructuras de C++, como: public, protected, private.
  • La estructura de C++ se puede usar directamente sin estructura.
  • herencia C++
  • Si el nombre de la estructura es el mismo que el nombre de la función, ¡puede ejecutarse normalmente y llamarse normalmente! Pero al definir variables de estructura, ¡solo se pueden usar aquellas con estructura!

estructura y clase

En general, struct es más adecuado para ser considerado como el cuerpo de realización de una estructura de datos, y class es más adecuado para ser considerado como el cuerpo de realización de un objeto.

Diferencia:
una de las diferencias más importantes es el control de acceso predeterminado
y los derechos de acceso heredados predeterminados. La estructura es pública y la clase es privada.
Struct es el cuerpo de realización de la estructura de datos, su control de acceso a datos predeterminado es público, y clase es el cuerpo de realización de objeto, su control de acceso a variables miembro predeterminado es privado.

Unión

Union (unión) es una clase especial que ahorra espacio. Una unión puede tener varios miembros de datos, pero solo un miembro de datos puede tener un valor en cualquier momento. Cuando se asigna un valor a un miembro, los demás miembros quedan indefinidos. Los sindicatos tienen las siguientes características:

  • El carácter de control de acceso predeterminado es público.
  • Puede contener constructores, destructores
  • No puede contener miembros del tipo de referencia
  • No se puede heredar de otras clases y no se puede usar como clase base
  • No puede contener funciones virtuales
  • El sindicato anónimo puede acceder directamente a los miembros del sindicato en el ámbito donde se encuentra la definición
  • La unión anónima no puede contener miembros protegidos o miembros privados
  • Las uniones anónimas globales deben ser estáticas

explícito

La palabra clave explicit en C++ solo se puede usar para modificar un constructor de clase con un solo parámetro. Su función es indicar que el constructor es explícito, no implícito. Otra palabra clave correspondiente es implícita, es decir, está oculto, se declara el constructor de clase. como implícito por defecto

  • Cuando explícitamente decora el constructor, puede evitar la conversión implícita y copiar la inicialización.
  • Cuando explícitamente decora una función de conversión, se pueden evitar las conversiones implícitas, excepto las conversiones por contexto.

referencias y punteros

inserte la descripción de la imagen aquí

referencia de valor l y referencia de valor r

Un lvalue es una expresión que representa datos, como un nombre de variable, una variable de puntero sin referencia. Generalmente, podemos obtener su dirección y asignarle un valor, pero el lvalue modificado por const no puede asignarle un valor, pero aún se puede obtener su dirección.
En general, un objeto cuya dirección se puede tomar es un lvalue.

// 以下的a、p、*p、b都是左值
int a = 3;
int* p = &a;
*p;
const int b = 2;

Un valor r también es una expresión que representa datos, como: constante literal, valor de retorno de expresión, valor de retorno de una función por valor (retorno por valor, no retorno por referencia), valor r no puede aparecer en el lado izquierdo del símbolo de asignación e Incapaz para obtener la dirección.
En general, los objetos que no se pueden direccionar son valores r.

double x = 1.3, y = 3.8;
// 以下几个都是常见的右值
10;                 // 字面常量
x + y;             // 表达式返回值
fmin(x, y);        // 传值返回函数的返回值

Una referencia de lvalue es una referencia a un lvalue, que crea un alias para el lvalue.

// 以下几个是对上面左值的左值引用
int& ra = a;
int*& rp = p;
int& r = *p;
const int& rb = b;

Una referencia de rvalue es una referencia a un rvalue, creando un alias para el rvalue.
La expresión de referencia de rvalue es agregar dos & después del nombre de tipo de variable específico, por ejemplo: int&& rr = 4;.

// 以下几个是对上面右值的右值引用
int&& rr1 = 10;
double&& rr2 = x + y;
double&& rr3 = fmin(x, y);

Resumen de las referencias de lvalue:

  1. Las referencias de Lvalue solo pueden referirse a lvalues, no a rvalues ​​​​directamente.
  2. Pero las referencias const lvalue pueden referirse tanto a lvalues ​​como a rvalues.
// 1.左值引用只能引用左值
int t = 8;
int& rt1 = t;

//int& rt2 = 8;  // 编译报错,因为10是右值,不能直接引用右值

// 2.但是const左值引用既可以引用左值
const int& rt3 = t;

const int& rt4 = 8;  // 也可以引用右值
const double& r1 = x + y;
const double& r2 = fmin(x, y);

P: ¿Por qué una referencia const lvalue también puede referirse a un rvalue?
Respuesta: Antes de que se creara el estándar C++ 11, no existía el concepto de referencias de valor r. En ese momento, si deseaba un tipo que pudiera recibir tanto valores l como valores r, necesitaba usar referencias constantes de valor l, como push_back para contenedores estándar Interfaz: void push_back (const T& val).
En otras palabras, si las referencias const lvalue no pueden hacer referencia a rvalues, algunas interfaces no son compatibles.

Resumen de las referencias de rvalue:

  1. Las referencias de Rvalue solo pueden referirse a rvalues, no a lvalues ​​​​directamente.
  2. Pero una referencia de rvalue puede referirse a un lvalue que se mueve.

move, este artículo hace referencia a std::move (C++11), se usa para forzar un valor l a un valor r para lograr la semántica de movimiento.
Después de mover el valor l, se convierte en un valor r, por lo que se puede hacer referencia a una referencia de valor r.

// 1.右值引用只能引用右值
int&& rr1 = 10;
double&& rr2 = x + y;
const double&& rr3 = x + y;

int t = 10;
//int&& rrt = t;  // 编译报错,不能直接引用左值


// 2.但是右值引用可以引用被move的左值
int&& rrt = std::move(t);
int*&& rr4 = std::move(p);
int&& rr5 = std::move(*p);
const int&& rr6 = std::move(b);

Importancia de la referencia lvalue
Pasar parámetros por valor y regresar por valor generará copias, algunas incluso copias profundas, lo cual es muy costoso. La importancia real de las referencias de lvalue es que tanto como parámetros como valores de retorno pueden reducir la copia, mejorando así la eficiencia.
Deficiencias de las referencias de lvalue
Aunque las referencias de lvalue resuelven perfectamente la mayoría de los problemas, algunos problemas aún no se pueden resolver bien.
Cuando el objeto aún existe después del alcance de la función, puede ser devuelto por referencia lvalue, lo cual no es un problema.
Pero cuando el objeto (el objeto es un objeto local en la función) no existe después de dejar el alcance de la función, no se puede devolver usando una referencia de lvalue.
Por lo tanto, para el segundo caso, la referencia lvalue no puede hacer nada, solo puede devolver por valor.
La importancia de las referencias de valor r
Entonces, para resolver el problema de copia mencionado anteriormente de devolver por valor, el estándar C++ 11 agrega referencias de valor r y semántica de movimiento.
Mover semántica : Mover recursos de un objeto a otro (transferencia de control de recursos).
Mover construcción : transfiere el recurso del parámetro rvalue para construirse a sí mismo.

En general, si estas dos funciones están definidas en la clase, al construir el objeto:
si el lvalue se usa como parámetro, entonces se llamará al constructor de copias para hacer una copia (si está en el espacio de almacenamiento dinámico como cadena Si hay es una clase con recursos, entonces se hará una copia profunda cada vez que se llame a la construcción de la copia).
Si se usa un valor r como parámetro, entonces se llamará a la construcción de movimiento, y llamar a la construcción de movimiento reducirá la copia (si es una clase como una cadena que tiene recursos en el espacio de almacenamiento dinámico, entonces cada vez que se llame a la construcción de movimiento, se hará una copia menos profunda).

Mover asignación : transfiere los recursos del rvalue del parámetro para asignarse a sí mismo.

En general, si estas dos funciones están definidas en la clase, al asignar un objeto:
si se usa un lvalue como parámetro, entonces se llamará a la asignación de copia y se realizará una copia (si está en el montón como cadena Si hay son clases de recursos en el espacio, se realizará una copia en profundidad cada vez que se llame a la asignación de copia).
Si se usa un valor r como parámetro, se llamará a la asignación de movimiento, y la asignación de movimiento de llamada reducirá la copia (si es una clase como una cadena que tiene recursos en el espacio del montón, entonces cada llamada de asignación de movimiento guardará una copia profunda ).

Referencia universal:
&& de cierto tipo representa una referencia de valor real (por ejemplo: int&&, string&&),
pero && en una plantilla de función no representa una referencia de valor real, sino una referencia universal. El tipo de plantilla debe determinarse por inferencia, y recibirá un lvalue se deduce como una referencia de lvalue y se deducirá como una referencia de rvalue después de recibir un rvalue.
Sin embargo, en las referencias universales, las referencias de valor r perderán las propiedades de las referencias de valor r.
El reenvío perfecto
significa que en una plantilla de función, los parámetros se pasan a otra función en la plantilla de función actual completamente de acuerdo con el tipo de parámetro de la plantilla.
Por lo tanto, para lograr un reenvío perfecto, además de usar referencias universales, también necesitamos usar std::forward (C++11), que conserva las propiedades de tipo originales del objeto durante el proceso de transferencia de parámetros.
De esta forma, la referencia de valor puede mantener las propiedades del valor durante el proceso de transferencia.

void Func(int& x) {
    
     cout << "左值引用" << endl; }

void Func(const int& x) {
    
     cout << "const左值引用" << endl; }

void Func(int&& x) {
    
     cout << "右值引用" << endl; }

void Func(const int&& x) {
    
     cout << "const右值引用" << endl; }


template<typename T>
void PerfectForward(T&& t)  // 万能引用
{
    
    
	Func(std::forward<T>(t));  // 根据参数t的类型去匹配合适的重载函数
}

int main()
{
    
    
	int a = 4;  // 左值
	PerfectForward(a);

	const int b = 8;  // const左值
	PerfectForward(b);

	PerfectForward(10); // 10是右值

	const int c = 13;
	PerfectForward(std::move(c));  // const左值被move后变成const右值

	return 0;
}

inserte la descripción de la imagen aquí
Además de los escenarios de uso anteriores, las funciones de interfaz relevantes del contenedor STL estándar de C++ 11 también logran un reenvío perfecto, de modo que el valor de las referencias de rvalue se puede realizar realmente.
Por ejemplo, la lista de contenedores en la biblioteca STL

Resumen
Las referencias de Rvalue hacen que los programas de C++ se ejecuten de manera más eficiente.

publicación de blog de referencia

La introducción de operaciones de referencia en C++ garantiza la seguridad y la comodidad del uso de referencias al tiempo que agrega más restricciones en el uso de referencias y también puede mantener la elegancia del código. Use operaciones apropiadas en situaciones apropiadas, y el uso de referencias puede evitar la situación de "punteros volando por todo el cielo" hasta cierto punto, y también tiene un cierto significado positivo para mejorar la estabilidad del programa. Finalmente, las implementaciones subyacentes de punteros y referencias son las mismas, por lo que no hay necesidad de preocuparse por la brecha de rendimiento entre los dos.

Supongo que te gusta

Origin blog.csdn.net/weixin_45184581/article/details/129491269
Recomendado
Clasificación