Disponible la nueva versión "donationware" 7.3 de OrganiZATOR
Descubre un nuevo concepto en el manejo de la información.
La mejor ayuda para sobrevivir en la moderna jungla de datos la tienes aquí.

Curso C++

[Home]  [Inicio]  [Índice]


2.1 Atributos de las entidades C++

§1 Sinopsis

Hemos señalado que, en un sentido amplio, un programa tiene dos tipos de entidades: datos e instrucciones, y que a su vez, los datos pueden ser constantes y variables. Una característica importante de los datos (en especial los variables) es que pueden tener varios atributos, estos atributos o propiedades pueden ser relativas al tiempo de compilación y/o al tiempo de ejecución (runtime).

En Declaraciones ( 4.1) se exponen estos conceptos de forma más general. Por ahora consideremos que el objeto-dato puede tener los siguientes atributos:

  • Identificador 
  • Tipo de dato
  • Clase de almacenamiento
  • Enlazado 
  • Dirección  (Lvalue )
  • Valor  (Rvalue )

Nota: estos atributos no son exclusiva de los objetos; las funciones (algoritmos) también tienen los atributos identificador, tipo , enlazado, dirección y valor (el que devuelven en su caso).

§2 Identificador

El identificador es el nombre con el que el programador reconoce un objeto-dato específico, constante o variable [3] ( 3.2.2 Identificadores). En la práctica, el identificador puede ser un simple nombre o una expresión que represente unívocamente al objeto (un objeto se puede conocer por su dirección, sin que se sepa o preocupe su nombre). Ejemplo:

int x = 5;

x es el identificador de un miembro de la clase de los enteros de valor 5.

char* ptr = "String";

ptres el identificador de un miembro de la clase de los punteros-a-carácter.  Apunta a una constante de cadena de la que no conocemos su nombre, solo su dirección.

§3 Tipo

El tipo (también conocido como tipo de dato) define cuanta memoria es necesaria para almacenar el objeto, como se interpreta el datagrama de bits que hay en el lugar de almacenamiento del objeto; que rango de valores son posibles, y que operaciones les son permitidas a los objetos de dicho tipo.

El concepto tipo de dato tiene una importancia capital en el ámbito de la programación y es la piedra angular sobre la que está construido el propio lenguaje C++ [1]. Es pues de suma importancia asimilar íntimamente el significado de este concepto para comprender y utilizar C++ con un mínimo de soltura.

Cada tipo de dato tiene un sentido particular para el compilador (y para e programador). El tipo puede considerarse como un conjunto de propiedades (dependiente a veces de la implementación) que, junto con un conjunto de operaciones que les son permitidas y el rango de valores posibles, es asumido por los miembros de dicho tipo.  Como se verá más adelante, el compilador C++ deduce este atributo a partir del código; bien de forma implícita, bien mediante declaraciones explícitas [4].

En C++ existen dos clases de tipos: los definidos intrínsecamente en el lenguaje (tipos preconstruidos en el lenguaje, fundamentales o básicos), y los definidos por el usuario (también llamados abstractos).  Precisamente esta capacidad de permitir al usuario "inventar" nuevos tipos a la medida de sus necesidades, es una de las características del lenguaje. En cuanto a los primeros, se suponen 5 tipos básicos y muchos más agregados. En realidad, los tipos constituyen en una estructura muy compleja que incluye 4 categorías básicas con varias subcategorías ( 2.2 Tipos de datos).

En mi opinión, nunca se insistirá bastante en la importancia fundamental de entender bien el concepto tipo de dato en el estudio del C++; aparecerá con frecuencia en muchas definiciones y explicaciones. De hecho, gran parte de la estructura del propio lenguaje y del sistema de seguridad y comprobación del compilador gravitan sobre este concepto. Representa algo así como el "Who is who" de las entidades del universo C++ (por lo demás, una sociedad muy "clasista").

Nota: conviene recordar que C++ dispone de dos operadores específicos que permiten obtener información sobre los tipos en tiempo de ejecución: sizeof ( 4.9.13), que permite averiguar el tamaño en bytes de cualquier tipo estándar o definido por el usuario, y typeid ( 4.9.14), con el que pueden obtenerse algunos datos adicionales.

§4 Clase

La clase de almacenamiento ("storage class"), determina donde se guarda la información; su duración (por cuanto tiempo), que puede ser toda la vida del programa o solo durante la ejecución de ciertos bloques de código, y algunos aspectos de la visibilidad y el ámbito.

  • Situación; es el sitio donde se aloja el dato (constante o variable). Este atributo es significativo porque el programa dispone de varios sitios distintos donde guardar los objetos; sitio que se elige en función de ciertas características del objeto ( 1.3.2)

  • Duración ( 4.1.5 "Lifetime") es el periodo durante el que la variable tiene existencia real (datos físicamente alojados en memoria). Es un atributo de tiempo de ejecución.

  • Ámbito ( 4.1.3), también llamado campo de acción o ámbito léxico ("Lexical scope") es la parte del programa en que un objeto es conocido por el compilador. Es una propiedad de tiempo de compilación.

  • Visibilidad ( 4.1.4 ) es la región de código fuente desde la que se puede acceder a la información asociada a un objeto.

La clase de almacenamiento puede ser definida de forma explícita o implícita ( 4.1.8 Especificadores de clase de almacenamiento).

§5 Enlazado

Como se ha indicado ( 1.4), enlazado es el proceso de creación de un programa que sigue a la compilación. Permite que a cada instancia de un identificador sea asociada correctamente con una función u objeto particular. Cada objeto tiene un tipo de enlazado que puede ser de dos clases: estático y dinámico. A su vez, cada objeto tiene además un atributo de enlazado; atributo que está estrechamente relacionado con el ámbito, y que puede ser de tres clases: externo, interno y sin enlazado ( 1.4.4).

§6 Dirección (Lvalue)

La dirección, localización, localizador o Lvalue del objeto-dato es una dirección de memoria donde comienza el almacenamiento. Puede ser expresada directamente por un valor (que represente una dirección de memoria en la arquitectura de la computadora utilizada) o una constante, variable, o expresión que pueda traducirse en una dirección.

Cuando queramos referirnos específicamente a la dirección de un objeto-dato de nombre x utilizaremos la expresión Lvalue(x), aunque más adelante veremos que C++ tienen su forma específica para referirse a ella.

El nombre viene históricamente de “left value” valor a la izquierda, en referencia a que legalmente puede estar a la izquierda (el extremo receptor) en una expresión de asignación ( 4.9.2). Es decir, el miembro que "recibe" el valor involucrado en la asignación. Esto tiene sentido porque en C++ una expresión del tipo x = 3; que definiríamos coloquialmente como: “hacer el valor de x igual a tres”, puede enunciarse más formalmente diciendo: “poner un 3 en la dirección de memoria señalada por x”, lo que también podríamos expresar abreviadamente como [2]:

Lvalue(x) ←  3

El Lvalue de un objeto puede ser fijo (constante) o variable. Un Lvalue modificable significa que la dirección puede ser accedida y su contenido legalmente modificado. Por ejemplo: x = 4; es una expresión válida si x es una variable de tipo entero; en su dirección puede ponerse un patrón de bits que significará un valor 4 para el compilador (precisamente porque asume que ahí se aloja un int). En otro caso el mismo patrón de bits puede significar algo muy distinto.

Un Lvalue constante significa lo contrario, por ejemplo, la expresión 4 = x no es correcta porque el Lvalue de 4 no es modificable.  La expresión a+b = 4 tampoco es correcta, porque a+b no tiene Lvalue (no es un "objeto", no puede interpretarse como una dirección de memoria).

El Lvalue de las variables estáticas se asigna en tiempo de compilación, tienen una dirección fija y conocida desde el principio en una zona de memoria especial reservada para las variables estáticas y globales ( 1.3.2).

En las variables automáticas (dinámicas) el Lvalue se asigna en tiempo de ejecución, la dirección (y por supuesto el valor) se conocen solamente durante la ejecución del bloque de código correspondiente. Ocurre incluso, que cuando una función es llamada recursivamente, para cada activación de la misma se crean distintos Lvalues para el mismo nombre.

§7  Valor (Rvalue)

El Rvalue es la interpretación que hace el programa del patrón de bits alojado en la dirección asignada al objeto-dato. Históricamente Rvalue es la abreviatura del inglés “right value”, valor a la derecha, en referencia a los valores que legalmente pueden estar a la derecha (extremo emisor) en una expresión de asignación.

Un Rvalue es una constante, variable, o expresión que pueda traducirse en un valor. Por ejemplo 4 + 3 es un Rvalue.  Cuando queramos referirnos específicamente al Rvalue de un objeto de nombre x lo expresaremos: Rvalue(x).

Las variables que no son constantes pueden modificar su Rvalue a lo largo del programa. Una expresión del tipo x = a; coloquialmente: “hacer el valor de la variable x igual al valor de la variable (o constante)  a”, puede enunciarse más formalmente: “copiar el Rvalue de la variable cuyo nemónico es a en la dirección de memoria (Lvalue) de la variable cuyo nemónico es x”, lo que podríamos expresar con:

Lvalue(x)  ←  Rvalue(a)

§8 Algunas ideas complementarias

En C++ existen formas de referirse específica y distintamente a la dirección (Lvalue) y al valor (Rvalue) de un objeto. Incluso existen variables destinadas a contener los Lvalues (direcciones) de otras variables, es decir, variables cuyos Rvalues son los Lvalues de otras, son los denominados punteros ( 4.2). Precisamente se dice que pnt es un puntero-a-x si:

Rvalue(pnt)  == Lvalue(x)

  pnt

Nemónico que identifica una variable; suponemos que esta variable es de tipo puntero, y que en este caso apunta a una variable que llamaremos x. Entonces, por definición de puntero:

Rvalue(pnt) == Lvalue(x)

  *pnt

El asterisco * es el operador de indirección ( 4.9.11);  el resultado de aplicarlo a un puntero es el objeto señalado por el puntero. En este caso sería el objeto apuntado por pnt; es decir: *pnt == x. La expresión *pnt solo tiene sentido si el Rvalue(pnt) == Lvalue(x), en otras palabras: Si el valor de pnt es la dirección de almacenamiento de un objeto, o lo que es lo mismo, si pnt es un puntero.

Debe tenerse en cuenta, que *pnt devuelve el objeto apuntado por pnt a todos los efectos. Es decir, es un objeto con Rvalue y Lvalue, por lo que las dos expresiones de asignación siguientes tienen sentido:

X = *pnt;    //equivale a: X = x
*pnt = Y;    // equivale a: x = Y

  &pnt

& es el operador de referencia ( 4.9.11); al aplicarlo a una variable, da como resultado la dirección (Lvalue) de la variable. En este caso sería la dirección de pnt, es decir: &punt es sinónimo de Lvalue(pnt). Esta expresión siempre tiene sentido si pnt es un objeto válido, ya que por definición, cualquier objeto tiene un almacenamiento. A la luz de lo dicho, es evidente que: Rvalue(pnt) == &x

Precisamente la asignación de un puntero es una expresión del tipo:

int* pnt = &x;

en este caso, int* pnt indica que pnt es un puntero-a-entero; después se le asigna la dirección de la variable x (se asume por tanto que x es de tipo int). A partir de este momento se dice que pnt apunta o señala a x.

Cuando * y & se utilizan en esta forma se denominan operadores de puntero. Téngase en cuenta, que el asterisco *, además de ser el operador de indirección, también puede ser especificador de tipo (de tipo puntero 4.2.1a) y operador de multiplicación ( 4.9.1). Por su parte, & además de operador de referencia, también puede ser el operador AND en operaciones de bits ( 4.9.3).

Nota: Consecuencia inmediata es deducir que en C++ hay casos en que diferentes operadores comparten el mismo identificador. Podemos decir entonces que algunos operadores C++ están sobrecargados ya de origen. Precisamente el compilador distingue de que operador concreto se trata en cada caso, por el tipo y número de los operandos involucrados. Más información al respecto en 4.9 Operadores.

  Inicio.


[1] C++, al igual que muchos de los actuales lenguajes de programación "orientados a objetos", obedecen a un diseño centrado principalmente en el "dato", aunque también existen lenguajes en los que se pone mayor énfasis en el algoritmo.

[2]  Utilizamos el símbolo ←  para indicar una asignación de derecha a izquierda (en C++ el símbolo del operador de asignación simple es “=”  4.9.2); utilizaremos “==” como indicador de igualdad (este si es el símbolo C++ del operador relacional "igual que" 4.9.12). Finalmente utilizaremos el símbolo → para señalar que lo que sigue se deduce o es "consecuencia de" lo anterior (símbolo de inferencia).

[3]  El identificador no es solo privativo de los datos, también las instrucciones pueden tener un identificador. Por ejemplo, las funciones y los operadores. En el caso de las funciones el identificador se dice que es el "nombre" de la función; en el caso de los operadores, el identificador se dice que es el "símbolo" del operador.

[4]  El tipo de dato en los lenguajes de programación se corresponde con el concepto matemático de "dominio". Más concretamente, en la teoría de conjuntos.