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]


1.2.1  Léxico y conceptos fundamentales

 "Este ha sido mi último curso. Me acabo de jubilar después de cerca de 40 años dedicado a la enseñanza. A pesar de la poca consideración que mi profesión ha tenido económica y socialmente, acabo con la misma ilusión con la que comencé. E igualmente convencido de la trascendencia de la tarea de educar. Sólo me duele un poco el comprobar la menor eficacia que ésta ha tenido estos últimos años.

¿Cuáles han sido las causas? Ni la mentalidad hedonista que rehuye cualquier tipo de sacrificio, ni el permisivismo de los padres que satisfacen todos los caprichos de los hijos, ni los planes de estudios que se fundamentan en una visión lúdica de la educación, ni las políticas educativas con pretensiones de progresismo que priman el uniformismo sobre la búsqueda de la excelencia han favorecido en absoluto la cultura del esfuerzo, imprescindible en cualquier proceso personal de mejora, como es la educación
". Federico Gómez Pardo en "La voz del Lector" de "El Confidencial Digital" (22-06-2006)   ECD.

§1  Presentación

Creo que buena parte de la dificultad del principiante respecto a algunos conceptos del lenguaje C++, y de muchas otras áreas de la ciencia informática, proviene de un conocimiento incompleto o vago de algunos conceptos fundamentales. En realidad se trata de una cuestión semántica; de conocer el significado exacto de algunas palabras;  de disponer de un vocabulario mínimo que sirva de soporte para entender el resto. Ocurre con frecuencia que los textos informáticos están plagados de términos que supuestamente son de uso común, pero que en realidad no lo son tanto y cuyo significado tampoco está claramente explicado en ningún sitio. Se dan por sabidos, pero las más de las veces el estudiante es incapaz de verbalizar correctamente su significado. Es importante que este "corpus" mínimo sea conocido sin ambigüedades, de forma que los conceptos construidos sobre él no tengan fisuras y resulten de una solidez conceptual a toda prueba.

Estas cuestiones semánticas son evidentemente el objeto fundamental de los gramáticos y de los puristas del lenguaje (aunque sean lenguajes artificiales como los de programación), de forma que en el caso del C++, la primera preocupación del Estándar es definir sin ambigüedad una serie de términos. Introduciré aquí algunos de los que creo más importantes, en la seguridad de que su conocimiento atañe no solo al lenguaje C++, sino al acerbo cultural de cualquier interesado en esta disciplina.

§2  Algunos conceptos

RTFM  Siglas por las que se conoce un principio universal y de gran interés en la ciencia informática actual [5].

Escribir un programa es establecer el comportamiento de una máquina mediante una serie de algoritmos que definirán su funcionamiento ( 1.4). Según el DRAE (Diccionario de la Real Academia Española de la Lengua) un algoritmo es un conjunto ordenado y finito de operaciones que permite hallar la solución de un problema [4]. En informática se utiliza en el sentido de un conjunto ordenado y finito de instrucciones que gobiernan el comportamiento de una máquina para conseguir un comportamiento determinado. Las instrucciones se expresan en un lenguaje artificial (inventado conscientemente por el hombre) denominado lenguaje de programación.

Al llegar a cierto grado de madurez y universalidad algunos lenguajes son estandarizados, lo que significa que un comité internacional ( 1) se encarga de establecer sus reglas de uso. En el caso de los lenguajes (naturales o artificiales) estas reglas constituyen lo que se denomina su gramática. El lenguaje C++ alcanzó oficialmente esta madurez y universalidad en 1989, de forma que a partir de entonces, el Estándar C++ establece la gramática del lenguaje que nos ocupa.

Aunque todos los lenguajes de programación tienen su "Gramática", el enfoque respecto a la misma varía grandemente de unos a otros. En ciertos casos, como en C++, estas reglas son tremendamente estrictas, lo que tiene sus pros y sus contras. Por ejemplo, una gramática rígida permite afinar mucho en los matices de lo que deseamos hacer; manipular los conceptos (principalmente los datos) con gran precisión, y ser advertidos de posibles errores involuntarios. Pero hay que estar muy atento a los detalles del código, y hasta que no se tiene cierta práctica, los mensajes de aviso y errores del compilador pueden desesperar a cualquiera.

Otro extremo está representado por los lenguajes de sintaxis más o menos "borrosa". Por ejemplo JavaScript o Perl, en los que el compilador, o intérprete, pretende adivinar qué se supone que queremos hacer. Este comportamiento se conoce como DWIM ("Do what I mean") [7].


La gramática se concreta en una serie de reglas y condiciones,  aunque puede haber otras. Por ejemplo, de estilo. Las que imprescindiblemente debe cumplir un programa C++ para ser correcto, son de dos clases:  semánticas (de significado) y sintácticas (de forma 1.3.1a). Justamente es el contenido de estas normas lo que hace que un programa C++ sea distinto de un programa Java o Fortran por ejemplo. Las reglas sintácticas son muchas, pero comienzan por las que definen el alfabeto del lenguaje. Es decir: el conjunto de grafos que pueden utilizarse para escribir su código.

C++ permite utilizar un alfabeto de 96 caracteres ASCII en la escritura de sus programas. De ellos 91 son los caracteres gráficos (representables por un grafo) que se incluyen en la tabla;  los cinco restantes son caracteres no representables:  Espacio; tabulación horizontal; tabulación vertical; salto de formato y nueva línea.

Caracteres gráficos del alfabeto C++:
a b c d e f g h i j k l m n o p q r s t u v w x y z
A B C D E F G H I J K L M N O P Q R S T U V W X Y Z
0 1 2 3 4 5 6 7 8 9
_ { } [ ] # ( ) < > % : ; . ? * + ­/ ^ & | ~ ! = , \ " ’

La norma C++ establece que cuando en un programa no se respetan las formas (reglas sintácticas) el compilador debe lanzar un mensaje de error o indicación de que no entiende lo que queremos decir. Por ejemplo, el caso de que olvidemos el punto y coma al final de una sentencia. Sin embargo, con las reglas semánticas no ocurre siempre así. El estándar distingue dos tipos de reglas de significado:  diagnosticables y no diagnosticables. En cuanto a las primeras, denominadas reglas de significado diagnosticable ("Diagnosable semantic rules"), la Norma establece que en caso de infracción, el compilador enviará al menos un mensaje de error. En el caso de las segundas no se requiere esta exigencia.

Nota: en principio, todo el conjunto de reglas semánticas definidas en el Estándar pueden considerarse de significado diagnosticable, a excepción de aquellas que tienen una indicación explícita de que no necesitan diagnóstico.


Las reglas están definidas de modo que cuando se establece una condición diagnosticable y la forma del programa no cumple este requisito, deberá generarse un mensaje de error correspondiente. Sin embargo, cuando se establece un requisito diagnosticable para un dato y este no se cumple durante la ejecución, el Estándar no establece ningún comportamiento especial para el programa.


  Comportamiento dependiente de la implementación
:  Cuando el comportamiento de un programa correcto con datos correctos, depende de la implementación. Este comportamiento debe estar documentado en cada implementación.

  Comportamiento indefinido:  Es el comportamiento de un programa en aquellas circunstancias o situaciones para las que el Estándar no especifica un comportamiento específico. Sería el caso del comportamiento en runtime de un programa que recibe datos incorrectos. Observe que, según lo indicado al principio, muchos casos de programas erróneos no generan un comportamiento indefinido, sino un aviso de diagnóstico.

  Implementación:  Un compilador concreto para una plataforma determinada.

  Límites de implementación:  Restricciones impuestas a los programas por una implementación.

  Programa correcto ("Well-formed"):  Programa C++ construido de acuerdo con las reglas sintácticas de este lenguaje, con reglas de semántica diagnosticable, y que sigue la regla de una sola definición ( 4.1.2). Un programa incorrecto ("Ill-formed") es el que no sigue los condicionantes anteriores.

  Un identificador es un conjunto de caracteres alfanuméricos de cualquier longitud que sirve para identificar las entidades del programa. Los identificadores pueden ser combinaciones de letras y números, y cada lenguaje tiene sus propias reglas que definen como pueden estar construidos ( 3.2.2  Identificadores C++).

Nota: algunas combinaciones de caracteres, las denominadas palabras-clave ("Keywords"), tienen un significado específico para el lenguaje y no pueden ser utilizadas en otro contexto ( 3.2.1).


Generalmente los identificadores se introducen en el programa para designar una entidad;  entonces se dice de ellos que son un nombre. C++ permite utilizar el mismo identificador para designar entidades diferentes a condición de que estén en ámbitos ( 4.1.3) diferentes, o que sean funciones con distinto número y/o tipo de argumentos.

Nota: otro tipo especial de identificadores lo constituyen las etiquetas;  en vez de representar entidades sirven para significar sentencias [1]. Se utilizan para identificar el destino en sentencias de salto ( 4.10.4).


La introducción de un nombre en el programa puede efectuarse mediante alguna de estas formas:

  • Si el nombre representa a una entidad, mediante una declaración ( 4.1.2).
  • Si el nombre representa a una sentencia (es una etiqueta), mediante una sentencia etiquetada ( 4.10.1) o una instrucción de salto goto [2].

Que un mismo nombre utilizado en dos unidades de compilación designe o no a una misma entidad, depende del tipo de enlazado que tenga el identificador en cada una de ellas ( 1.4.4).

Cuando compilador encuentra un identificador en un programa, supone que es un nombre o una etiqueta y antes de realizar el "parsing" ( 1.4), intenta determinar a que entidad representa. Esta operación, que asocia sin ambigüedad cada nombre con una declaración de dicho nombre, se denomina búsqueda de nombres ("Name-lookup"). Si al final del proceso no ha aparecido la declaración correspondiente entonces el programa es incorrecto ("Ill-formed" ).

Cuando el identificador corresponde a una función, puede ocurrir que el "Name-lookup" asocie un mismo nombre con más de una declaración. Es el caso de funciones sobrecargadas, en las que un mismo nombre corresponde a varias definiciones de la "misma" función. En estos casos, después de esta primera búsqueda tiene lugar otro proceso denominado de resolución de sobrecarga ("Overload resolutión" 4.4.1a) en el que se intenta averiguar a que definición concreta corresponde el identificador.

El proceso de búsqueda de nombres sigue un conjunto relativamente extenso de reglas que cubren todas las posibilidades que pueden presentarse;  además existen modificadores que alteran la forma de esta búsqueda,  los denominados especificadores o modificadores de acceso. Ver en la hoja adjunta una somera descripción del proceso ( Name-lookup).


  El concepto de entidad es muy amplio; corresponde a: un valor; clase; elemento de una matriz; variable; función; miembro de clase; instancia de clase; enumerador; plantilla, o espacio de nombres del programa (más detalles en 4.1.1).

  Un objeto es una entidad a la que corresponde una zona de almacenamiento [3], razón por la que se dice de ellos que tienen existencia real o física. Los objetos son introducidos en el programa mediante una declaración, y creados mediante una definición ( 4.1.2). Gozan de una serie de propiedades que vienen definidas desde el momento de su creación, entre ellas está la duración de su almacenamiento, que tiene influencia en su ciclo vital ("Lifetime"), y puede ser de tres tipos: estática, dinámica y automática ( 4.1.5). Otra propiedad no menos importante de los objetos es su tipo, que también viene determinado desde el momento de su creación ( 2.2).


  Las entidades del programa pueden tener otros atributos o propiedades cuya comprensión es igualmente importante:  dirección (Lvalue) y valor (Rvalue). Sus características se detallan en el apartado correspondiente ( 2.1), pero adelantemos aquí que Rvalue se asigna al concepto de "valor"; algo que tenga valor es un Rvalue (o tiene esta propiedad). Por su parte Lvalue se asimila al concepto de dirección; espacio de almacenamiento que pueda recibir un valor [6].

Una entidad puede tener uno o ambos de estos atributos. Por ejemplo:  

char func () { ... }

char c = '5';

const int k = 2 + 3;

La función func puede ser considerada un Rvalue en el sentido que devuelve un valor:  también la variable c; la constante k, o la expresión  2 + 3 pueden ser consideradas Rvalues porque tienen o representan un valor.

Tanto la  variable c como la constante k son también Lvalues, en el sentido que ambas disponen de una dirección y un espacio de almacenamiento correspondiente (que en el primer caso puede ser alterado y en segundo no). Sin embargo, ni la función func, ni las expresións  '5' o 2 + 3 pueden considerarse como tales porque no les corresponde una dirección o espacio de almacenamiento donde pueda colocarse un Rvalue. En este contexto tiene sentido decir que un Lvalue modificable es aquel que puede cambiar el valor contenido en su almacenamiento.


  Una expresión es un conjunto de operadores, operandos y signos de puntuación que especifican una computación (suponen una secuencia de instrucciones concretas para la máquina);  generalmente, como resultado de esta computación producen un valor, aunque también, como consecuencia de ella pueden producirse otros efectos denominados efectos laterales ( 1.3.1).

  Inicio.


[1]   Cuando a principios de los 70 la programación no-estructurada cayó en descrédito, las etiquetas, que en forma de número de sentencia acompañaba a estas en algunos lenguajes, desaparecieron. Como a pesar de todo en algunas ocasiones persiste la necesidad de acudir al denostado GOTO, se introdujeron estos identificadores para señalar el destino de estos saltos imprescindibles  (las sentencias catch del mecanismo de excepciones son otro vestigio de aquello).

[2]  El lenguaje C++ establece que antes de utilizar un identificador hay que declararlo, excepto en el caso de etiquetas ("Labels").

[3]  Observe que esta definición es más amplia que la tradicional de la POO, en la que un objeto es la instancia de una clase.

[4]  El término algoritmo y la propia ciencia del álgebra, son otra aportación cultural que debemos al Islám. Esta ciencia debe mucho a Muhammad Ibn Musa, que fue miembro de la “Casa de la Sabiduría”, una notable academia científica de Bagdad en época del califa Al-Ma'amun (813-833). El propio vocablo "algoritmo" deriva del sobrenombre  "Al-Khwarizmi" de Ibn Musa. Los algoritmos informáticos siguen de cerca el concepto matemático de función. Aunque estas últimas pueden exigir un número infinito de pasos para llegar a la solución.

[5]  Acrónimo inglés de "Read the fucking manual". El creador del lenguaje nos informa que generalmente es una buena idea y que la "F" es muda (no se pronuncia). Glosario de términos C++ de Bjarne Stroustrup    www.research.att.com

[6]  Formalmente un Lvalue es una expresión que se refiere a un objeto o a una función (una invocación a función es un Lvalue solo si el valor devuelto es una referencia). Un Lvalue es modificable si no se refiere a una función, una matriz, o un objeto constante.

[7]  Desde luego, lo ideal sería un comportamiento DWIW ("Do What I Want"), pero ya hemos señalado que los lenguajes actuales son del tipo DWIS ("Do What I Say"). Quizás lo contrario sería extremadamente peligroso...