Articulo de referencia

Clases de C++

Una clase en C++ es un tipo o estructura de datos definido por el usuario, declarado con cualquiera de las palabras clave , o (las dos primeras se denominan colectivamente clase...

Una clase en C++ es un tipo o estructura de datos definido por el usuario, declarado con cualquiera de las palabras clave , o (las dos primeras se denominan colectivamente clases no uniones) que tiene datos y funciones (también llamadas variables miembro y funciones miembro ) como miembros cuyo acceso está regido por los tres especificadores de acceso private , protected o public . Por defecto, el acceso a los miembros de una clase de C++ declarada con la palabra clave es private . Los miembros privados no son accesibles fuera de la clase; solo se puede acceder a ellos a través de las funciones miembro de la clase. Los miembros públicos forman una interfaz para la clase y son accesibles fuera de ella.classstructunionclass

Las instancias de un tipo de datos de clase se conocen como objetos y pueden contener variables miembro, constantes , funciones miembro y operadores sobrecargados definidos por el programador.

Diferencias entre a structy a classen C++

En C++, una clase definida con la classpalabra clave tiene miembros y clases base privados por defecto. Una estructura es una clase definida con la structpalabra clave. [ 1 ] Sus miembros y clases base son públicos por defecto. En la práctica, las estructuras suelen reservarse para datos sin funciones. Al derivar una estructura de una clase/estructura, el especificador de acceso por defecto para una clase/estructura base es público. Y al derivar una clase, el especificador de acceso por defecto es privado.

Clases agregadas

Una clase agregada es una clase sin constructores declarados por el usuario, sin miembros de datos no estáticos privados o protegidos, sin clases base y sin funciones virtuales. [ 2 ] Dicha clase puede inicializarse con una lista de cláusulas de inicialización separadas por comas y encerradas entre llaves. [ 3 ] El siguiente código tiene la misma semántica tanto en C como en C++. Desde C++20 , los tipos agregados también pueden inicializarse con inicializadores designados al estilo C99 , pero a diferencia de C, estos campos no pueden reordenarse en la declaración. [ 4 ]

usando std :: string ;struct Dirección { string ciudad ; int código postal ; };struct Persona { string nombre ; int edad ; Dirección ; } ;// Lista de inicialización ordinaria Dirección addr1 = { "Los Ángeles" , 12345 }; Dirección addr2 { "Seattle" , 10001 };// Inicializador designado Address addr3 = { . city = "Toronto" , . zip = 13853 };// Los inicializadores designados también funcionan con otro agregado como miembro: Person john { . name = "John" , . age = 20 , . address = { . city = "New York" , . zip = 54321 } };

Al igual que en C, un punto y coma debe seguir a la definición de una barra inclinada ( struct/) class. Entre la definición de la structbarra classinclinada y el punto y coma, se pueden declarar instancias de la misma.

// Declara dos puntos, llamados a y b // a = {1, 2} y b = {3, 4} struct Point { int x ; int y ; } a { 1 , 2 }, b { 3 , 4 };

Estructuras POD

Una POD-struct (Plain Old Data Structure) es una clase agregada no union que no tiene miembros de datos no estáticos de tipo non-POD-struct, non-POD-union (o array de dichos tipos) o referencia, y no tiene operador de asignación definido por el usuario ni destructor definido por el usuario . [ 1 ] Se podría decir que una POD-struct es el equivalente en C++ de una C. structEn la mayoría de los casos, una POD-struct tendrá la misma disposición de memoria que una struct correspondiente declarada en C. [ 5 ] Por esta razón, a las POD-structs a veces se las denomina coloquialmente "structs de estilo C". [ 6 ]

Propiedades compartidas entre estructuras en C y estructuras POD en C++

  • Los miembros de datos se asignan de manera que los miembros posteriores tengan direcciones más altas dentro de un objeto, excepto cuando estén separados por un especificador de acceso. [ 7 ]
  • Dos tipos de estructuras POD son compatibles en cuanto a su disposición si tienen el mismo número de miembros de datos no estáticos y los miembros de datos no estáticos correspondientes (en orden) tienen tipos compatibles en cuanto a su disposición. [ 8 ]
  • Una estructura POD puede contener relleno sin nombre . [ 9 ]
  • Un puntero a un objeto POD-struct, convertido adecuadamente mediante una conversión de reinterpretación , apunta a su miembro inicial y viceversa, lo que implica que no hay relleno al principio de un POD-struct. [ 9 ]
  • Se puede utilizar una estructura POD con la macro offsetof . [ 10 ]

Declaración y uso

Las clases de C++ tienen sus propios miembros. Estos miembros incluyen variables (incluidas otras estructuras y clases), funciones (identificadores específicos u operadores sobrecargados) conocidas como funciones miembro, constructores y destructores. Los miembros se declaran como de acceso público o privado mediante los especificadores de acceso `@Public` public:y `@ private:Private`, respectivamente. Cualquier miembro que se encuentre después de un especificador `@Public` tendrá el acceso asociado hasta que se encuentre otro especificador. También existe herencia entre clases que pueden usar el protected:especificador `@Public`.

La thispalabra clave

Para facilitar la capacidad de las clases de referenciarse a sí mismas, C++ implementa la thispalabra clave para todas las funciones miembro. La thispalabra clave actúa como un puntero al objeto actual. [ 11 ] Su tipo es el de un puntero al objeto actual.

La thispalabra clave es especialmente importante para las funciones miembro cuyo valor de retorno es la propia clase:

clase Punto { privado : doble x ; doble y ; público : // ...Punto & operador += ( const Punto & rhs ) noexcept { this -> x += rhs . x ; this -> y += rhs . y ; return * this ; } };

Como se indicó anteriormente, thises un puntero, por lo que el uso del asterisco (*) es necesario para convertirlo en una referencia que se devolverá.

Clase global y local

Una clase definida fuera de todas las funciones es una clase global porque sus objetos pueden crearse desde cualquier parte del programa. Si se define dentro del cuerpo de una función, entonces es una clase local, ya que los objetos de dicha clase son locales al ámbito de la función.

Declaración básica y variables miembro

Las clases no sindicalizadas se declaran con la palabra claveclass o . La declaración de miembros se coloca dentro de esta declaración.struct

Las definiciones anteriores son funcionalmente equivalentes. Ambos códigos definirán los objetos de tipo Personcomo poseedores de dos miembros de datos públicos, namey age. Los puntos y coma después de las llaves de cierre son obligatorios.

Después de una de estas declaraciones (pero no ambas), Personse puede utilizar de la siguiente manera para crear variables recién definidas del Persontipo de datos:

importar std ;usando std :: string ;struct Persona { string nombre ; int edad ; };int main () { Persona a ; Persona b ; a.nombre = " Calvin " ; b.nombre = " Hobbes " ; a.edad = 30 ; b.edad = 20 ; std :: println ( " { } : { } " , a.nombre , a.edad ) ; std :: println ( " { } : { } " , b.nombre , b.edad ) ; }

Al ejecutar el código anterior se obtendrá la siguiente salida:

Calvino: 30 Hobbes: 20 

Funciones de los miembros

Una característica importante de la clase C++ son las funciones miembro . Cada tipo de dato puede tener sus propias funciones integradas (denominadas funciones miembro) que tienen acceso a todos los miembros (públicos y privados) del tipo de dato. En el cuerpo de estas funciones miembro no estáticas, thisse puede usar la palabra clave para referirse al objeto para el que se llama a la función. Esto se implementa comúnmente pasando la dirección del objeto como primer argumento implícito a la función. [ 12 ] Tomemos el Persontipo anterior como ejemplo nuevamente:

importar std ;usando std :: string ;clase Persona { privado : string nombre ; int edad = 5 ; público : // "nombre" y "edad" son las variables miembro. La palabra clave "this" es una // expresión cuyo valor es la dirección del objeto para el cual se invocó el miembro. Su tipo es "const Persona*", porque la función se declara como // const. void printData () const { std :: println ( "{}: {}" , nombre , edad ); } };

En el ejemplo anterior, el printDatamétodo se declara en el cuerpo de la clase y se define calificándolo con el nombre de la clase seguido de ::. Tanto namecomo ageson privados (por defecto para la clase) y printDatase declara como público, lo cual es necesario si se va a utilizar desde fuera de la clase.

Con la función miembro printData, la impresión se puede simplificar a:

a . imprimirDatos (); b . imprimirDatos ();

donde ay barriba se denominan remitentes, y cada uno de ellos hará referencia a sus propias variables miembro cuando printData()se ejecute la función.

Antes de la introducción de los módulos (es decir, el uso de archivos de cabecera ), la declaración de la clase o estructura (denominada su interfaz) y su definición (denominada su implementación) se separaban en unidades independientes. La interfaz, necesaria para el usuario, se almacenaba en un archivo de cabecera, mientras que la implementación se guardaba por separado, ya sea en código fuente o compilado.

Herencia

La disposición en memoria de las clases que no son POD no está especificada por el estándar de C++. Por ejemplo, muchos compiladores populares de C++ implementan la herencia simple mediante la concatenación de los campos de la clase padre con los de la clase hija, pero esto no es un requisito del estándar. Esta elección de disposición hace que referirse a una clase derivada a través de un puntero al tipo de la clase padre sea una operación trivial.

Por ejemplo, considere

struct Scalar { int x ; };
struct Vec2 : public Scalar { int y ; };

Una instancia de Scalarcon un Scalar* papuntando a ella podría verse así en la memoria:

┏━━━━━━━━━┓ ┃Scalar::x┃ ┗━━━━━━━━━┛ ↑ pag

Un ejemplo Vec2con un Scalar* ppuntero que lo apunta podría verse así:

┏━━━━━━━━━┳━━━━━━━┓ ┃Scalar::x┃Vec2::y┃ ┗━━━━━━━━━┻━━━━━━━┛ ↑ pag

Por lo tanto, cualquier código que manipule los campos de un objeto Scalarpuede hacerlo sin tener en cuenta la definición de dichos campos. Un programa C++ bien escrito no debería asumir nada sobre la estructura de los campos heredados. El uso de los operadores de conversión de tipos `static_cast` o `dynamic_cast` garantiza que los punteros se conviertan correctamente de un tipo a otro.ScalarVec2Vec2

La herencia múltiple no es tan simple. Si una clase Zextiende las clases Xy Y, entonces los campos de ambas clases padre deben almacenarse en algún orden, pero (como máximo) solo una de las clases padre puede ubicarse al principio de la clase derivada. Siempre que el compilador necesite convertir un puntero del Ztipo a Xo Y, el compilador proporcionará una conversión automática de la dirección de la clase derivada a la dirección de los campos de la clase base (normalmente, esto es un cálculo de desplazamiento simple).

La finalpalabra clave limita las formas en que una clase puede ser subclase . [ 13 ] Las subclases de una clase no pueden sobrescribir métodos marcados como finalpor la clase padre. [ 14 ] [ 15 ] Las clases finales no pueden ser heredadas. [ 15 ] Esto permite la desvirtualización , la eliminación del uso de tablas virtuales para la búsqueda de métodos, lo que permite la inserción en línea de llamadas a métodos en clases finales. [ 16 ] [ 17 ]

// final en una declaración de clase declara que una clase no puede ser extendida class Z final : public X , public Y { public : // final en la firma de un método declara que un método no puede ser sobrescrito más void someOperation () override final { // hacer algo aquí } };

finalno es una palabra reservada en C++, sino que se define como una palabra clave contextual para no entrar en conflicto con el uso del identificador 'final' en bases de código existentes. [ 18 ] [ 19 ]

Clase anónima

Una clase puede ser anónima, es decir, sin identificador ni nombre. [ 20 ]

Las estructuras anónimas existieron por primera vez en C11 :

struct { int x ; int y ; } coords ;coordenadas . x = 5 ;

En C++, las clases anónimas se pueden definir de manera similar. Sin embargo, estas clases pueden no tener constructores, ya que no tienen nombre. Sus tipos, que son generados por el compilador, se pueden obtener usando decltype .

clase { privado : int valor ; público : int obtenerValor () { devolver valor ; }void setValue ( int x ) { valor = x ; }void display () { std :: println ( "Valor: {}" , value ); } } obj ;obj.setValue ( 15 ) ; int x = obj.getValue ( ); obj.display ( ) ; // Valor : 15

Las subclases anónimas al estilo Java pueden emularse de forma similar:

usando std :: chrono :: system_clock ;// Una clase tipo interfaz class Speaking { public : virtual void speak () = 0 ; };// Implementación de la clase de "interfaz" Speaking : public Speaking { public : void speak () override { std :: println ( "La hora actual es {}" , system_clock :: now ()); } } timeTeller ;

Las expresiones lambda en C++ son similares a una clase anónima; de hecho, son objetos de función , que tienen un nombre generado por el compilador e implementan una función operator(). [ 21 ]

Operadores sobrecargados

En C++, los operadores , como + - * /, se pueden sobrecargar para adaptarse a las necesidades de los programadores. Estos operadores se denominan operadores sobrecargables .

Por convención, los operadores sobrecargados deberían comportarse casi igual que en los tipos de datos integrados ( int, float, etc.), pero esto no es obligatorio. Se puede declarar una estructura llamada Integeren la que la variable realmente almacena un entero, pero al llamar a Integer * Integerse podría devolver la suma, en lugar del producto, de los enteros:

struct Integer { int i = 0 ;Entero () = valor predeterminado ; Entero ( int i ) : i { i } {}Operador entero * ( const Entero & rhs ) const { return Entero ( i + rhs . i ); } };

El código anterior utilizaba un constructor para "construir" el valor de retorno. Para una presentación más clara (aunque esto podría disminuir la eficiencia del programa si el compilador no puede optimizar la instrucción a la equivalente anterior), el código anterior se puede reescribir como:

struct Integer { // ...Operador entero * ( const Entero & rhs ) const { Entero m ; m . i = i + rhs . i ; return m ; } };

Los programadores también pueden incluir un prototipo del operador en la structdeclaración y definir la función del operador en el ámbito global:

struct Integer { int i = 0 ;Entero () = valor predeterminado ; Entero ( int i ) : i { i } {}Operador entero * ( const Integer & rhs ) const ; }; Integer Integer :: operator * ( const Integer & rhs ) const { return Integer ( i * rhs . i ); }

iarriba representa la variable miembro del propio remitente, mientras que rhs.irepresenta la variable miembro de la variable de argumento rhs.

La constpalabra clave aparece dos veces en el código anterior. La primera aparición, en el argumento const integer& rhs, indica que la variable de argumento no será modificada por la función. La segunda aparición, al final de la declaración, le asegura al compilador que el remitente no será modificado por la ejecución de la función.

En const integer& rhs, el signo de ampersand ( & ) significa "paso por referencia". Cuando se llama a la función, se le pasa una referencia a la variable, en lugar del valor de la variable.

Tenga en cuenta que la asociatividad y la precedencia de los operadores no se pueden cambiar.

Operadores binarios sobrecargables

Los operadores binarios (operadores con dos argumentos) se sobrecargan declarando una función con un operador "identificador" (algo) que llama a un único argumento. La variable a la izquierda del operador es el emisor, mientras que la de la derecha es el argumento.

Entero i = 1 ;// Podemos inicializar una variable de estructura de esta manera como // si llamamos a un constructor con solo el primer // argumento especificado. Integer j = 3 ;// Los nombres de las variables son independientes de los nombres de las // variables miembro de la estructura. Integer k = i * j ; std :: println ( "{}" , k . i );

Se imprimiría '3'.

La siguiente es una lista de operadores binarios sobrecargables:

El operador ' = ' (de asignación) entre dos variables del mismo tipo de estructura está sobrecargado por defecto para copiar todo el contenido de las variables de una a otra. Se puede sobrescribir con otro valor si es necesario.

Los operadores deben sobrecargarse uno por uno; en otras palabras, ninguna sobrecarga está asociada entre sí. Por ejemplo, <no es necesariamente lo opuesto a >.

operadores sobrecargables unarios

Mientras que algunos operadores, como se especificó anteriormente, toman dos términos (el remitente a la izquierda y el argumento a la derecha), otros operadores solo tienen un argumento (el remitente) y se les denomina "unarios". Ejemplos de ello son el signo negativo (cuando no se coloca nada a su izquierda) y la " NOT lógica " ( signo de exclamación , !).

El emisor de los operadores unarios puede estar a la izquierda o a la derecha del operador. A continuación se muestra una lista de operadores unarios sobrecargables:

La sintaxis de una sobrecarga de un operador unario, donde el emisor está a la derecha, es la siguiente:

T operatorX()

Cuando el remitente está a la izquierda, la declaración es:

T operatorX(int)

XLo anterior indica que el operador se va a sobrecargar. Reemplace Tcon el tipo de dato del valor de retorno ( int, bool, estructuras, etc.).

Este intparámetro no significa esencialmente nada más que una convención para indicar que el remitente se encuentra a la izquierda del operador.

constSe pueden añadir argumentos al final de la declaración, si procede.

Soportes de sobrecarga

En las clases de C++ , los corchetes []y los paréntesis ()pueden sobrecargarse. Los corchetes deben contener exactamente un argumento, una lista de inicializadores (desde C++11) o un conjunto de parámetros arbitrario (desde C++23), mientras que los paréntesis pueden contener cualquier número específico de argumentos o ninguno.

La siguiente declaración sobrecarga el corchete.

T operator[] (x1, x2, ...))

El contenido dentro del corchete se especifica en la argumentparte.

El paréntesis redondo se sobrecarga de forma similar.

T operator() (x1, x2, ...)

El contenido del corchete en la llamada al operador se especifica en el segundo corchete.

Además de los operadores mencionados anteriormente, el operador de flecha ( ), la flecha con estrella ( ), la palabra clave y la palabra clave también pueden sobrecargarse. Estos operadores relacionados con la memoria o los punteros deben procesar funciones de asignación de memoria después de la sobrecarga. Al igual que el operador de asignación ( ), también se sobrecargan por defecto si no se realiza ninguna declaración específica.->->*newdelete=

Constructores

A veces, los programadores pueden querer que sus variables tomen un valor predeterminado o específico al ser declaradas. Esto se puede hacer declarando constructores .

usando std :: string ;clase Persona { privado : cadena nombre ; entero edad ; público : Persona ( cadena nombre , entero edad ) { this- > nombre = nombre ; this- > edad = edad ; } };

Las variables miembro se pueden inicializar en una lista de inicialización, utilizando dos puntos, como en el ejemplo siguiente. Esto difiere de lo anterior en que inicializa (usando el constructor), en lugar de usar el operador de asignación. Esto es más eficiente para los tipos de clase, ya que solo necesita construirse directamente; mientras que con la asignación, primero deben inicializarse usando el constructor predeterminado y luego asignarse un valor diferente. Por lo tanto, la inicialización de miembros generalmente se prefiere sobre la asignación en el cuerpo del constructor siempre que sea posible. [ 22 ] Además, algunos tipos (como referencias y consttipos) no se pueden asignar y, por lo tanto, deben inicializarse en la lista de inicialización.

usando std :: string ;clase Persona { privado : cadena nombre ; entero edad ; público : Persona ( cadena nombre , entero edad ) : nombre { nombre }, edad { edad } {} };

Tenga en cuenta que las llaves no se pueden omitir, incluso si están vacías.

Se pueden proporcionar valores predeterminados a los últimos argumentos para ayudar a inicializar dichos valores.

usando std :: string ;clase Persona { // ...Persona ( cadena nombre = "" , entero edad = 0 ) : nombre { nombre }, edad { edad } {} };

Cuando no se le pasan argumentos al constructor en el ejemplo anterior, es equivalente a llamar al siguiente constructor sin argumentos (un constructor predeterminado):

usando std :: string ;clase Persona { // ...Persona () : nombre { "" }, edad { 0 } {} };

La declaración de un constructor se asemeja a una función con el mismo nombre que el tipo de dato. De hecho, una llamada a un constructor puede adoptar la forma de una llamada a una función. En ese caso, una Personvariable de tipo inicializada puede considerarse como el valor de retorno:

int main () { Persona r = Persona ( "Gales" , 40 ); r . print (); }

Una sintaxis alternativa que hace lo mismo que el ejemplo anterior es

int main () { Persona r ( "Gales" , 40 ); r.printData ( ) ; }

Dentro del cuerpo del constructor se pueden agregar acciones específicas del programa, que pueden o no estar relacionadas con la variable.

usando std :: string ;clase Persona { // ...Persona ( cadena nombre , entero edad ) : nombre { nombre }, edad { edad } { std :: println ( "¡Hola! Mi nombre es {} y tengo {} años." , nombre , edad ); }; }

Con el constructor anterior, se imprimirá un "¡Hola!" cuando Personse invoque el constructor predeterminado.

Constructor predeterminado

Los constructores predeterminados se llaman cuando no se han definido constructores para las clases.

struct Scalar { int i ; };// Objeto creado usando paréntesis. Scalar * a = new Scalar (); // Llama al constructor predeterminado, y b se inicializará con '0'.// Objeto creado sin paréntesis. Scalar * b = new Scalar ; // Asigna memoria, luego llama al constructor predeterminado y b tendrá el valor '0'.// Creación de objeto sin new. Escalar c ; // Reserva espacio para a en la pila, y b tendrá un valor basura desconocido.// Los objetos asignados deben ser liberados delete a ; delete b ;

Sin embargo, si se definió un constructor definido por el usuario para la clase, ambas declaraciones anteriores llamarán a este constructor definido por el usuario, cuyo código definido se ejecutará, pero no se asignarán valores predeterminados a la variable b.

El constructor predeterminado proporcionado por el compilador se puede mantener usando , mientras que también se puede deshabilitar explícitamente usando . Esto también es cierto para cualquier otra función miembro especial generada por el compilador .=default;=delete;

Destructores

Un destructor es lo opuesto a un constructor. Se llama cuando se destruye una instancia de una clase; por ejemplo, cuando se elimina un objeto de una clase creado dentro de un bloque (entre llaves "{}") después de la llave de cierre, el destructor se llama automáticamente. Se llamará al vaciar la ubicación de memoria que almacena las variables. Los destructores se pueden usar para liberar recursos, como la memoria asignada en el montón y los archivos abiertos, cuando se destruye una instancia de esa clase.

La sintaxis para declarar un destructor es similar a la de un constructor. No devuelve ningún valor y el nombre de la función es el mismo que el de la clase, con una tilde (~) delante.

clase Persona { // ...~ Persona () { std :: println ( "Estoy eliminando {} con edad {}" , nombre , edad ); } }

Similitudes entre constructores y destructores

  • Ambas tienen el mismo nombre que la clase en la que se declaran.
  • Si el usuario no los declara, ambos están disponibles en una clase por defecto, pero ahora solo pueden asignar y liberar memoria de los objetos de una clase cuando se declara o se elimina un objeto.
  • Para una clase derivada: Durante la ejecución del constructor de la clase base, el constructor de la clase derivada aún no se ha llamado; durante la ejecución del destructor de la clase base, el destructor de la clase derivada ya se ha llamado. En ambos casos, las variables miembro de la clase derivada se encuentran en un estado no válido.

Plantillas de clase

En C++, las declaraciones de clase se pueden generar a partir de plantillas de clase. Estas plantillas representan una familia de clases. Una declaración de clase se obtiene instanciando la plantilla con uno o más argumentos. Una plantilla instanciada con un conjunto específico de argumentos se denomina especialización de plantilla.

Propiedades

La sintaxis de C++ intenta que cada aspecto de una clase se parezca al de los tipos de datos básicos . Por lo tanto, los operadores sobrecargados permiten manipular las clases como si fueran números enteros y de punto flotante, los arreglos de clases se pueden declarar con la sintaxis de corchetes ( , donde es el nombre de la clase, es el nombre del arreglo y es su longitud), y los punteros a clases se pueden desreferenciar de la misma manera que los punteros a tipos de datos integrados.Xa[N]={/* ... */};XaN

consumo de memoria

El consumo de memoria de una estructura es al menos la suma de los tamaños de memoria de las variables que la componen. Tomemos Pointcomo ejemplo la siguiente estructura.

struct Punto { int x ; int y ; };

La estructura consta de dos enteros. En muchos compiladores de C++ actuales, los enteros son de 32 bits por defecto , por lo que cada una de las variables miembro consume cuatro bytes de memoria. Por lo tanto, la estructura completa consume al menos (o exactamente) ocho bytes de memoria, como se muestra a continuación.

+----+----+ | x | y | +----+----+

Sin embargo, el compilador puede agregar relleno entre las variables o al final de la estructura para asegurar una alineación de datos adecuada para una arquitectura de computadora determinada, a menudo rellenando las variables para que estén alineadas a 32 bits. Por ejemplo, la estructura

struct Data { char p ; char q ; char r ; short s ; int i ; double d ; };

puede representarse como

+-+-+-+-+--+--+----+--------+ |p|q|r|X|s |XX| i | d | +-+-+-+-+--+--+----+--------+

en memoria, donde X representa bytes de relleno basados ​​en una alineación de 4 bytes.

Dado que las estructuras pueden utilizar punteros y matrices para declarar e inicializar sus variables miembro, el consumo de memoria de las estructuras no es necesariamente constante . Otro ejemplo de tamaño de memoria no constante son las estructuras de plantilla.

Campos de bits

Los campos de bits se utilizan para definir los miembros de la clase que pueden ocupar menos espacio de almacenamiento que un tipo entero. Este campo solo es aplicable a tipos enteros ( int, char, short, long, etc.) y tipos de enumeración (por ejemplo std::byte, una enumeración no algebraica que representa datos numéricos sin procesar) y excluye float o double.

struct Data { uint32_t a : 2 ; // Valores posibles 0..3, ocupa los primeros 2 bits de int uint32_t b : 3 ; // Valores posibles 0..7, ocupa los siguientes 3 bits de int uint32_t : 0 ; // Se mueve al final del siguiente tipo entero uint32_t c : 2 ; uint32_t : 4 ; // Rellena 4 bits entre c y d uint32_t d : 1 ; uint32_t e : 3 ; };
  • Estructura de memoria
 entero de 4 bytes entero de 4 bytes [1][2][3][4][5][6][7][8] [1] [2] [3] [4] [a][a][b][b][b][ ][ ][ ] [ ][ ][ ][ ][ ][ ][ ][ ] [ ][ ][ ][ ][ ][ ][ ][ ] [ ][ ][ ][ ][ ][ ][ ][ ] [5] [6] [7] [8] [c][c][ ][ ][ ][ ][d][e] [e][e][ ][ ][ ][ ][ ][ ] [ ][ ][ ][ ][ ][ ][ ] [ ][ ][ ][ ][ ][ ][ ][ ] 

Los sindicatos también pueden tener miembros que representen campos de bits:

unión Datos { uint32_t a : 2 ; uint32_t b : 3 ; uint32_t : 0 ; // No hace nada uint32_t c : 2 ; uint32_t : 4 ; // No hace nada en este caso; si el ancho del campo de bits fuera lo suficientemente grande, cambiaría el tamaño de la unión para ajustarse uint32_t d : 1 ; uint32_t e : 3 ; };

Pasar por referencia

Muchos programadores prefieren usar el signo de ampersand ( & ) para declarar los argumentos de una función que involucra estructuras. Esto se debe a que, al usar el ampersand de desreferenciación, solo se requiere pasar una palabra (normalmente 4 bytes en una máquina de 32 bits, 8 bytes en una máquina de 64 bits) a la función: la ubicación de memoria de la variable. De lo contrario, si se usa el paso por valor, el argumento debe copiarse cada vez que se llama a la función, lo cual resulta costoso con estructuras grandes.

Dado que el paso por referencia expone la estructura original para que la función la modifique, constse debe usar la palabra clave para garantizar que la función no modifique el parámetro (ver const-correctness ), cuando esto no sea lo que se pretende.

Véase también

Referencias

  1. 1 2 ISO / IEC (2003). ISO/IEC 14882:2003(E): Lenguajes de programación - C++ §9 Clases [class] párr. 4
  2. ISO / IEC (2003). ISO/IEC 14882:2003(E): Lenguajes de programación - C++ §8.5.1 Agregados [dcl.init.aggr] párr. 1
  3. ISO / IEC (2003). ISO/IEC 14882:2003(E): Lenguajes de programación - C++ §8.5.1 Agregados [dcl.init.aggr] párr. 2
  4. cppreference.com. "Inicialización agregada" . cppreference.com . cppreference.com . Consultado el 13 de mayo de 2026 .
  5. "¿Qué es esto de "POD" en C++ del que tanto oigo hablar?" . Comeau Computing. Archivado del original el 19 de enero de 2009. Consultado el 20 de enero de 2009 .
  6. Henricson, Mats; Nyquist, Erik (1997). Industrial Strength C++ . Prentice Hall. ISBN 0-13-120965-5.
  7. ISO / IEC (2003). ISO/IEC 14882:2003(E): Lenguajes de programación - C++ §9.2 Miembros de clase [class.mem] párr. 12
  8. ISO / IEC (2003). ISO/IEC 14882:2003(E): Lenguajes de programación - C++ §9.2 Miembros de clase [class.mem] párr. 14
  9. 1 2 ISO / IEC (2003). ISO/IEC 14882:2003(E): Lenguajes de programación - C++ §9.2 Miembros de clase [class.mem] párr. 17
  10. ISO / IEC (2003). ISO/IEC 14882:2003(E): Lenguajes de programación - C++ §18.1 Tipos [lib.support.types] párr. 5
  11. "esto" . Referencia de C++ .
  12. "thiscall (C++)" . Consultado el 26 de enero de 2009 .
  13. Norma internacional ISO/IEC 14882:2020(E) – Lenguaje de programación C++.
  14. Browning, J. Burton; Sutherland, Bruce (2020), "Inheritance" , en Browning, J. Burton; Sutherland, Bruce (eds.), C++20 Recipes: A Problem-Solution Approach , Berkeley, CA: Apress, pp. 205–207 , doi : 10.1007/978-1-4842-5713-5_6 , ISBN  978-1-4842-5713-5, consultado el 24 de abril de 2024
  15. 1 2 Lakos, John; Romeo, Vittorio; Khlebnikov, Rostislav; Meredith, Alisdair (16 de diciembre de 2021). Adoptando C++ moderno de forma segura . Addison-Wesley Professional. ISBN 978-0-13-738051-0.
  16. "16BPP.net: Blog / El impacto en el rendimiento de la palabra clave 'final' de C++" . 16bpp.net . Consultado el 23 de abril de 2024 .
  17. Brand, Sy (2020-03-02). "Los beneficios de rendimiento de las clases finales" . Blog del equipo de C++ . Recuperado el 23 de abril de 2024 .
  18. TylerMSFT (03/08/2021). "Especificador final" . learn.microsoft.com . Consultado el 23/04/2024 .
  19. Stroustrup, Bjarne (10 de julio de 2013). El lenguaje de programación C++ . Addison-Wesley. ISBN 978-0-13-352285-3.
  20. Microsoft Learn (11 de agosto de 2025). "Tipos de clases anónimas" . learn.microsoft.com . Microsoft Learn.
  21. cppreference.com. "Expresiones lambda (desde C++11)" . cppreference.com . cppreference.com . Consultado el 16 de mayo de 2026 .
  22. Bjarne Stroustrup, Herb Sutter (8 de julio de 2025). "Directrices principales de C++ - C.49: Preferir la inicialización a la asignación en los constructores" . isocpp.github.io . isocpp.github.io.

Referencias generales:

  • Tutorial de Cplusplus.com, lección 5.2. Archivado el 3 de abril de 2009 en Wayback Machine , consultado en enero de 2006.
  • Tutorial de Cplusplus.com, lección 2.5. Archivado el 30 de marzo de 2009 en Wayback Machine , consultado en febrero de 2006.