Home » C++ » Lenguaje C++ resumido

Lenguaje C++ resumido

Codificación C++

En este breve resumen se explican los elementos básicos del lenguaje C++, sus tipos de datos y sus operadores.

Uso de comentarios

En este programa de ejemplo hay cuatro líneas que poseen comentarios:

// programa Hola.cpp
#include <iostream> // una directiva al preprocesador
int main() // la función main()
cout<<«Hola\n»;
// un mensaje de salida

Los comentarios estándar de C++ comienzan con una doble barra (//). Cuando aparece una barra doble, el compilador considera que el resto de la línea es un comentario. Este tipo de comentarios termina al final de la línea. Por ejemplo, éstas son todas sentencias válidas:

// esto es un comentario
int a; // aquí empieza el comentario
// aquí empieza el comentario // int e;
int b; //////////////////////////////////////////

El compilador no toma en consideración los comentarios, sólo son útiles para el lector del programa. Si a las sentencias citadas anteriormente se les quitan los comentarios podemos comprobar qué es lo que ve el compilador:

int a;
int b;

Pero C++ también admite el estilo de comentarios de C. El comentario de estilo C empieza por /* y se cierra con */. Pero el comentario no se cierra al final de la línea, sólo se cierra cuando aparece */. No es un estilo recomendable ya que no es tan legible como el estilo de comentario //. No obstante, se puede utilizar. Por ejemplo, éstas son todas sentencias válidas:

/***** esto es un comentario */
/* éste es otro comentario que termina en la siguiente línea
int x; ****/
/* aquí hay una sentencia después del fin de comentario*/ int a;
int b; /* aquí empieza y termina el comentario*/
/* aquí empieza el comentario*/ int e;

Si a las sentencias citadas anteriormente se les quitan los comentarios podemos comprobar qué es lo que ve el compilador:

int a;
int b;
int e;

Uso de directivas del preprocesador

En el programa de ejemplo hay una directiva para el preprocesador, que se identifica por el símbolo #:

#include <iostream> // una directiva al preprocesador

C++ utiliza un preprocesador, al igual que su predecesor, el lenguaje C. Un preprocesador es un programa que actúa sobre el código fuente antes que el propio compilador. En la sentencia para el preprocesador #include, su acción es simple, reemplaza la sentencia por el contenido de los archivos citados en la sentencia #include. El nombre del archivo está encerrado entre los símbolos “<” y “>”.
En este caso se ha hecho referencia al archivo iostream. Este archivo posee las definiciones utilizadas por cout para visualizar el mensaje. Sin esta directiva la compilación no hubiera procesado la sentencia cout ni hubiera reconocido los símbolos de los operadores de inserción (<<) ni de extracción (>>). Hay otras sentencias para el preprocesador que se verán más adelante.
Los compiladores C++ traen consigo muchos archivos de encabezados (header files) que brindan soporte a distintas ramas de funcionalidades. En C este tipo de archivos lleva la extensión .h (por header file), pero en C++ no lleva ninguna extensión en particular. En el ejemplo se hace referencia al archivo de encabezado <iostream>, un archivo que brinda soporte a las funciones de entrada y salida.
La versión C de <iostream> es <iostream.h>. Pero en este caso se ha incluido la versión C++, por eso se debe incluir la directiva using namespace.

#include <iostream> // una directiva al preprocesador
using namespace std;

namespace es una colección de nombres y de identificadores C++. El uso de namespace impide la aparición de conflictos entre bibliotecas que usan un mismo nombre o identificador. Esto significa que la variable cout que se usa en la función y que está definida en <iostream> se llama en realidad std::cout. De esta manera se puede hacer referencia a cout sin tener que codificar siempre el prefijo std.

Definición de función main()

El programa de ejemplo tiene una definición de función y es donde realmente empieza la ejecución:

int main() // la función main()
{


return 0;
}

La definición de la función está compuesta por la cabecera de la función y por el cuerpo de la función, en este caso:

int main() // la función main()

es la cabecera de la función define el nombre de la función main())y el tipo de dato devuelto por la función (int).
El cuerpo de la función es el conjunto de sentencias que están encerradas entre llaves ({}) y que siguen a la cabecera de la función.
Cada instrucción de C++ se denomina sentencia y termina con un punto y coma. La sentencia return actúa como terminación de la función.
La cabecera de la función tiene tres partes:
TipoDeRetorno   NombreDeFunción(Argumentos)

 

  • TipoDeRetorno: define el tipo de datos que devuelve la función. Los valores posibles se verán en detalle cuando se estudien los tipos de datos del lenguaje.
  • NombreDeFunción: define el nombre de la función. Un programa C++ puede tener definidas muchas funciones. Pero sólo una es obligatoria, y esa es la función main(). Como el compilador de C++ considera el uso de las minúsculas y mayúsculas, la función debe llamarse main() ya que si se denomina Main(), para C++ será otra función diferente. ¿Por qué debe haber siempre una función main()? Es la función por la que empieza la ejecución del programa y si no existiera se produciría un error en la compilación.
  •  Argumentos: lista de parámetros que se reciben desde la rutina llamante. En este caso simple, la función main no recibe ningún parámetro. También se encontrará la forma (void), que indica lo mismo: la función no recibe parámetros.

Sentencia que produce salida de datos

Este programa de ejemplo tiene una sentencia C++ para producir un mensaje, es la siguiente:
cout<<«Hola\n»; // un mensaje de salida
cout es un objeto que sabe cómo visualizar texto. La sentencia tiene tres partes:
      objeto    operador de inserción            cadena

  • objeto: en este caso, cout. Un objeto que sabe cómo visualizar texto.
  • operador de inserción: (<<) indica que la cadena se inserta en el objeto cout. El uso de los operadores se verá en detalle más adelante, por ahora basta con saber esto.
  • cadena: puede ser un texto encerrado entre comillas, como en el ejemplo, o una variable de caracteres que represente a ese valor. Si se observa el texto que está encerrado entre comillas se observará la notación especial «\n» que significa carácter de nueva línea. Ese par de caracteres no se visualizan, simplemente indican que luego de imprimir el texto en la salida se debe saltar una línea.

En el ejemplo, se ha incluido este carácter de control al final de la palabra «Hola», pero también se puede utilizar en el medio de la cadena para producir saltos de línea. Por ejemplo, la cadena «yo adivino el parpadeo\nde las luces, que a lo lejos,\nvan marcando mi retorno.\n» se visualizará:
yo adivino el parpadeo
de las luces que, a lo lejos,
van marcando mi retorno.

Por el contrario, si no se utilizan los caracteres de nueva línea, el texto se visualizará todo seguido, incluso aunque provenga de distintas sentencias cout, como en este ejemplo:

cout << «yo adivino»;
cout <<« el parpadeo\n»;

Esto se visualizará de la siguiente manera:

yo adivino el parpadeo

Normas generales del formato del código fuente

En C++ una sentencia termina con un punto y coma. Si no aparece el punto y coma la sentencia sigue en las líneas sucesivas. Obviamente, el olvido de un punto y coma provoca errores de compilación. Esta subdivisión de una sentencia entre varias líneas tiene unas limitaciones. En una línea de código hay elementos indivisibles (por ejemplo, el nombre de un tipo de dato, el nombre de una función) que no admiten empezar en una línea y continuar en la siguiente. Pero entre dos elementos indivisibles puede haber un espacio o un tabulador. Donde puede haber estos separadores puede haber un control de carro que hace que la línea continúe en la siguiente. Veamos esta sentencia:

cout << «Hola\n»;

Los elementos indivisibles son:

cout, <<, «Hola\n» y ;

Esta sentencia se podría escribir:

cout
<<
«Hola\n»
;

Y seguiría siendo válida. Pero no lo sería si se escribiese:

co
ut // Inválido, se ha dividido un elemento indivisible
«Hola
/n» // Inválido, se ha dividido una cadena

Las cadenas no pueden contener en su interior un control de carro o de nueva línea. Como regla general, donde puede haber un espacio, puede haber un control de carro. Salvo cuando se trata del interior de una cadena, en donde no puede haber un control de carro y por lo tanto es indivisible.
También hay ciertos caracteres que actúan como separadores de elementos indivisibles y que, por lo tanto, no requieren un espacio entre ellos y el elemento anterior. Éste es el caso de los paréntesis y las comas. Por ejemplo:

int main()
int main () // el espacio en blanco es opcional cuando el elemento
// siguiente es un paréntesis
int main ( )

Estas líneas, todas válidas y equivalentes, tienen cuatro elementos indivisibles: int, main, “(” y “)”. Obviamente resulta un poco redundante denominar indivisible a un elemento de un único carácter. Pero en este caso lo que debe quedar claro es que los dos paréntesis pueden formar parte de dos líneas, o que pueden tener espacios u otros caracteres entre sí, porque son elementos distintos.
C++ es un lenguaje que da muchas libertades en la escritura del código fuente. De todas maneras, a pesar de todas esas posibilidades que se han citado (por ejemplo, la subdivisión de una sentencia en varias líneas), hay unas recomendaciones que, aunque limiten un poco la libertad de escritura, hacen que los programas sean más legibles y más fáciles de seguir. En general, los programadores esperan encontrar estas normas:

  • Una sentencia por línea
  • Inserción de sangrías en los bloques (funciones, bucles, etc.)
  • Los corchetes de apertura y cierre, en líneas independientes
  • Los comentarios, que cumplan la sangría de su bloque.

Tipos de datos

Esta sentencia es una pseudo declaración de una variable en C++:
         TipoDeDato    NombreDeVariable;
En el lenguaje C++ existen dos categorías de tipos de datos: fundamentales y derivados. Los tipos de datos fundamentales son los propios del lenguaje, mientras que el otro tipo de dato lo forma el usuario por derivaciones de los datos fundamentales y combinaciones de tipos.
Antes de entrar en el análisis de los distintos tipos debemos considerar que hay algo que es común a todos los tipos: la necesidad de dar un nombre a la variable. El nombre de la variable es lo que permite poder recuperar su valor luego de almacenarla en alguna dirección de la memoria. Como sucederá a lo largo del análisis de los distintos componentes del lenguaje, C++ otorga gran flexibilidad para asignar nombre a las variables. Quizá más de la que muchos programadores se merecen. Por eso se recomienda que las variables tengan nombres significativos y legibles para quien no escribió el programa original: es mucho mejor una variable que se denomina CódigoPaís que otra que se denomina cp. La nomenclatura de variables de C++ tiene estas reglas básicas:

  • Se pueden usar letras, números y el guión bajo. Pero no puede empezar por un número.
  • La longitud de un nombre de variable no tiene un límite.
  • C++ distingue entre minúsculas y mayúsculas. Contador es una variable y contador es otra variable. No obstante, por razones de legibilidad, no se recomienda que en el mismo programa haya dos nombres de variables que sólo difieran por el uso de minúsculas y mayúsculas.
  • No se pueden usar palabras claves de C++.
  • Los nombres que empiezan por guión bajo (_) están reservados para funcionalidades especiales de la implementación y no deberían utilizarse.

Aquí resumiremos algunas definiciones válidas e inválidas para C++:

int Contador; // correcta
int Contador_2; // correcta
int 2contador; // incorrecta no debe empezar con un número
int int; // incorrecta, usa una palabra reservada de C++
int Contador#2; // incorrecta, usa un carácter no permitido
int _Contador; // correcta, pero su uso está reservado
int Contador_para_sumar_todos_los_casos_que_pasan_por_aquí;
// correcta, pero algo exagerada

Tipos fundamentales

Básicamente, los tipos fundamentales representan números enteros y los reales (coma flotante). Los tipos enteros representan números sin parte fraccionaria. En C++ hay diversos tipos de enteros que se diferencian principalmente por su capacidad de almacenamiento y, por consecuencia, por el espacio de memoria ocupado. Otro factor que determina el tipo es su posibilidad de almacenar el signo del número. En la tabla 1.1 se detallan los tipos fundamentales.

 

TIPO TAMAÑO CARACTERÍSTICAS
chr 1 byte Normalmente un ASCII. Se describe entre apóstrofos. Se considera como tipo
entero, lo que implica que se pueden hacer operaciones lógicas o aritméticas.
Puede ser con signo (-128 a 127) o sin signo (0 a 255), signed o unsigned,
respectivamente.
Ejemplos:
char a; // variable a sin asignación de valor
char b = “b”;
short 2 bytes Es un tipo entero. Puede ser con signo o sin signo (signed o unsigned). Por
defecto se asume con signo, rango de valores -32768 y 32767. Si es sin signo
el rango de valores de la variable es 0 a 65535. También se puede especificar
short int (es un sinónimo a short).
Ejemplos:
short n; // variable n ocupa 2 bytes y tiene signo
short m = 3000; // definición y asignación en una sentencia
int 4 bytes Es un tipo entero. Puede ser con signo o sin signo (signed o unsigned). Por
defecto se asume con signo. Es igual o mayor a short e igual o menor a long. Ejemplos:
int n; // variable n ocupa 4 bytes y tiene signo
int m = 3000; // definición y asignación en // una sentencia
unsigned int p; // variable p entero sin signo
_intn var. Es un tipo entero. Puede ser con signo o sin signo (signed o unsigned). Por
defecto se asume con signo. n indica la longitud en bits de la variable. Puede
ser 8, 16, 32 y 64 bits.
Ejemplos:
_int16 n; // variable n ocupa 2 bytes y tiene signo
_int64 m; // variable de 64 bits y tiene signo
long 4 bytes Es un tipo entero. Puede ser con signo o sin signo (signed o unsigned). Por
defecto se asume con signo. También se puede especificar long int (es un
sinónimo a long).
Ejemplos:
unsigned long int n; // variable n ocupa 4 bytes y no tiene signo
signed long m; // long m sería una definición idéntica
long n = 3000L; // el sufijo L define con seguridad el tipo
unsigned long p ;
float 4 bytes Es de tipo flotante. Es el de menor precisión dentro de los tipos flotantes.
Permite representar valores entre números enteros.
Ejemplo: float q ;
double 8 bytes Es de tipo flotante. Es de precisión mayor que el tipo float. Permite
representar valores entre números enteros.
Ejemplo: double r ;
long double 8 bytes Es de tipo flotante. Es de precisión mayor que el tipo double. Permite
representar valores entre números enteros.
Ejemplo: long double r ;
void Tipo nulo. No se pueden definir variables de tipo void, pero es un tipo de
dato que tiene diversos usos especiales dentro un programa C++. Permite
especificar que una función no devuelve valores. Permite definir el tipo base
de apuntadores a objetos de tipo desconocido.
Ejemplo:
void suma(); //la función suma no devuelve valor
void* xx; // apuntador a un objeto de tipo desconocido
void var2; // error: no hay objetos de tipo void
     

 

La enumeración es un tipo de dato especial que permite contener un conjunto de valores especificado por el usuario. En realidad, es un tipo que guarda muchas semejanzas con el tipo entero ya que con este tipo de dato se pueden hacer operaciones aritméticas.
Ejemplo:

enum color {ROJO, VERDE, AZUL}; // VERDE es de tipo color
// cada enumeración es de un tipo distinto

De manera predeterminada, el compilador asigna 0 a ROJO, 1 a VERDE y 2 a AZUL. Pero también se podría codificar:

enum color {ROJO=3, VERDE=1, AZUL}; //AZUL queda como 2
color nuevocolor;
nuevocolor = ROJO + AZUL; // podemos hacer operaciones aritméticas

Tipos derivados

Estos tipos se derivan directamente de otros. Estos tipos permiten hacer referencias a otros tipos de datos o permiten realizar la transformación de los tipos de datos, tal como ocurren con las funciones. Los tipos derivados son los siguientes:

  • Apuntadores
  • Matrices
  • Referencias
  • Estructuras

También se consideran tipos derivados a las funciones y a las clases, pero esos temas se tratarán más adelante.

Apuntadores

Para cualquier tipo T, T* es el tipo «apuntador a T» (también conocido como «puntero a T»). Dicho de otra manera, una variable de tipo T* puede contener una dirección de un objeto de tipo T. No puede haber apuntadores a referencias (se verá luego). Por ejemplo:

int k = 2;
int* pak = &k; // pak contiene la dirección de k

La operación principal de un apuntador es la indirección, o sea, devolver el valor del apuntador.

int k = 2;
int* pak = &k; // pak contiene la dirección de k
int j = *pak; // el valor apuntado por pak (o sea, 2) se asigna a j

Se podría pensar que si el apuntador es siempre una dirección, ¿para qué hay que declarar el tipo del apuntador? El apuntador es una dirección, por lo tanto, por su sólo contenido (una dirección) es imposible indicar la longitud y el formato de los datos que hay almacenados en esa dirección. Se sabe la dirección de comienzo, pero no la dirección de final, ni tampoco su formato interno. Por ese motivo, la única manera de conocer lo que hay en la dirección apuntada por un apuntador es mediante el tipo del apuntador.

Matrices de variables

Una matriz representa un conjunto de datos con un tipo y nombre común y en la cual se puede hacer referencia a sus elementos por medio de un campo índice. Puede haber, por ejemplo, matrices de elementos de tipo de dato float o matrices de tipo de dato char. La cantidad de elementos de la matriz se define mediante un valor entre corchetes. La matriz puede una dimensión (un vector) o las que permita el compilador. Los elementos de la matriz se direccionan desde 0, por lo cual si se define [8], el último elemento será [7]. A continuación se muestran varios ejemplos de definición de matrices:

int campo1[8]; // campo1 tiene 8 elementos de tipo int
float campo2[4] [10]; // La matriz es de 4 * 10: 40 ítems
char campo3[2] [5] [12]; // aquí tiene 2 * 5 * 12 ítems: 120 ítems
float campo4[2] ={5.1, 1.3};// Matriz de dos elementos inicializados
float campo5[] = {0.1, 3.2};// Define implícitamente que son 2 ítems
char campo6[4] ={‘C’, ‘+’, ‘+’, ‘\0’};// cadena C++
char campo7[4] = “C++”; // equivalente al anterior

El nombre de una matriz puede usarse como apuntador a su elemento inicial.
Si se tiene una matriz de caracteres (podría ser de otro tipo):

char z[]={‘h’,’o’,’l’,’a’);
char* pach = z; // apuntador al elemento inicial (‘h’)
// se produce una conversión implícita

double deudas[3] = {15000.0, 22000.0, 50000.0};
double * ptrd = deudas; // apuntador a deudas (0)

ptrd tiene la dirección de deudas, del primer elemento de deudas. Con el apuntador se pueden realizar operaciones aritméticas, pero se debe tener en cuenta que si se suma uno a un apuntador a tipo de dato double, ese apuntador se posicionará en la siguiente dirección double, o sea, 8 bytes más adelante.

ptrd = ptrd + 1; // incrementará el valor de ptrd en 8 bytes

Ahora ptrd apunta a la dirección de deudas (1).
Si ptrd hubiese apuntado a un tipo de dato short, ese incremento sólo hubiera sido de 2 bytes, pero la operación de suma tendría el mismo aspecto:

ptrs = ptrs + 1; // incrementará el valor de ptrs en 2 bytes

Referencias

Una referencia es una manera de llamar a un objeto con un nombre alternativo. Su principal uso es para especificar argumentos y valores de retorno de funciones. Por ejemplo:

char l1 = ‘H’;
char& lg = l1; // lg y l1 hacen referencia al mismo dato
// se pueden usar indistintamente

En el momento de la declaración de la referencia se asigna el valor y luego ya no puede hacer referencia a otro campo. Se puede hacer referencias a cualquier tipo, salvo a referencias. La referencia se puede asimilar a un apuntador constante a un objeto, pero sin las posibilidades de manipulación de los apuntadores.

Estructuras

Anteriormente se había visto que una matriz es un conjunto de elemento de igual tipo. Una estructura es un conjunto de elementos de distinto tipo. Por ejemplo:

struct inventario {
int nro;
char* descrip;
long int cantidad;
};

Aquí se ha definido un tipo de dato de usuario compuesto por datos de distinto tipo. Las variables del tipo definido se pueden declarar de la misma manera que otras variables y a sus miembros componentes (en este caso, número, descripción y cantidad) se puede acceder de la siguiente manera:


inventario p3;
p3.nro = 12 // se usa el punto para referirnos
// a un miembro de la estructura
p3.descrip = ‘clavos’;

Para acceder a la estructuras se suelen usar apuntadores usando el operador -> (indirección de apuntador de estructura).
p->nro es lo mismo que escribir (*p).nro.

Uniones

Una unión es similar sintácticamente a una estructura pero conceptualmente funciona distinto. Todas las variables que se definen dentro de una unión comparten el espacio físico. La longitud de una Unión es igual a la de la variable más larga. Dicho de otra manera, todos los elementos de la unión tiene desplazamiento cero. En la práctica, sólo se puede usar un elemento de la unión por vez.

union defineunion
{
int v1;
double v2;
char v3;
}

La longitud de esta unión coincide con la longitud de la variable double, o sea, ocho bytes.

defineunion redefine;
redefine.v1 = 8;
cout <<redefine.v1;
redefine.v2 = 3.1415;
cout <<redefine.p2;

Constantes

A la declaración de un objeto se puede agregar el calificador Const para indicar que ese objeto no cambiará después de su inicialización. Como un objeto constante no puede ser asignado, debe ser inicializado en su declaración. Nunca puede estar en el lado izquierdo de una asignación.
La declaración de una constante tiene esta sintaxis:

const int IVA1 = 16; // define el porcentaje de IVA como constante
const int IVA2[] = {0, 4, 4, 16, 33};
// define los porcentajes de IVA como una
// matriz de constantes enteras
const int IVA3; // error, no está inicializada
IVA1 = 12; // error, no se puede modificar

Sentencias

Sentencias declarativas:

Son las sentencias que sirven para definir componentes del programa: variables, tipos, funciones, etc. El compilador las procesa pero no producen código ejecutable. Por ejemplo:

char bandera[50] ;
int orden(int);

Sentencias ejecutables:

Son las que el compilador convierte en código ejecutable. Son acciones de programa: asignaciones, operaciones aritméticas, bifurcaciones, llamadas a funciones, etc. Por ejemplo:

a = b;
c = a / 200;

Bucles

C++ posee tres sentencias para realizar iteraciones o procesos repetitivos: For, While, Do-While.

Sentencia for

La sentencia for tiene la siguiente sintaxis:
for (expresión de inicialización; expresión de evaluación; expresión de actualización)
cuerpo del bucle

  • El cuerpo del bucle se ejecuta mientras la expresión de evaluación se mantenga True. El cuepro del bucle está compuesto por una sentencia o por un bloque de sentencias encerrado por corchetes.
  • Expresión de inicialización, es un proceso que se hace una vez al principio de la ejecución.
  • Expresión de evaluación: normalmente es una expresión relacional.
  • Expresión de actualización: es un proceso que se realiza al final de cada bucle. Normalmente, aquí se incrementa o decrementa el valor que luego se comprueba.

Por ejemplo:

for (j= 0, k =7; j + k < 10; j++, k++)
cout << j << «, « << k << ‘\n’;

La ejecución daría este resultado:
0, 7
1, 8

Sentencia While

El bucle conducido por una sentencia While es similar a una sentencia For simplificada: no tiene expresión de inicialización ni expresión de actualización. Sólo hay una expresión de evaluación. La sintaxis es la siguiente:
while ( expresión de evaluación)
cuerpo del bucle
Cuando se ejecuta la sentencia While, primero, se evalúa la expresión, si es True se ejecutan las sentencias codificadas en el cuerpo del bucle, en caso contrario, no se toman en cuenta ninguna de las sentencias del cuerpo del bucle y se continúa en secuencia.
El cuerpo del bucle se ejecuta mientras la expresión de evaluación se mantenga True. El cuerpo del bucle, igual que en la sentencia for, está compuesto por una sentencia o por un bloque de sentencias encerrado por corchetes. Al finalizar la ejecución de la/s sentencia/s del cuerpo del bucle se repite la evaluación de la expresión. El proceso se repite hasta que la evaluación sea False.

Sentencia Do-While

El bucle conducido por una sentencia Do-While es similar a una sentencia While con una diferencia fundamental: la evaluación de la expresión se hace luego de haber ejecutado el cuerpo del bucle. O sea, el bucle se ejecuta siempre al menos una vez. Luego funciona igual que la sentencia While. La sintaxis es la siguiente:
Do
cuerpo del bucle

while ( expresión de evaluación);

Sentencias condicionales y de bifurcación

Las sentencias condicionales y de bifurcación son las siguientes: if, if else, switch, break y continue.

Sentencia If

La sentencia If se utiliza cuando se quiere tomar alguna decisión acerca del camino a seguir en la seciencia del programa. La sentencia If permite decidir si se ejecuta u omite una sentencia o bloque de sentencias. La sintaxis es similar a la de la sentencia While:
if ( expresión de evaluación)
cuerpo;
Se evalúa la expresión, si es True se ejecuta la sentencia o bloque de sentencias (cuerpo) y si es False se salta la sentencia o bloque de sentencias que hay a continuación.
Éste es un ejemplo:

if (a > 5)
a++; // si a es mayor a cinco se incrementa en uno
k++; // esta sentencia se ejecuta siempre, independientemente
// del resultado de la sentencia if

Sentencia If-else

La sentencia If-else permite definir dos sentencias o bloques de sentencias que se ejecutarán de manera excluyente según sea el resultado de la evaluación de la expresión. Permite expresar lógicamente esta situación: si se cumple la condición hágase esto, en caso contrario, hágase aquello.La sintaxis es la siguiente:
if ( expresión de evaluación)
cuerpo1;
else
cuerpos2;
Éste es un ejemplo:

if (a > 5)
// se define una sentencia única
a++; // si a es mayor a cinco se incrementa en uno
else
{
// Se define un bloque de sentencias
a—; // si a no es mayor a cinco se le resta uno
cout <<«Ha sido False\n»;
}
k++; // esta sentencia se ejecuta siempre, independientemente
// del resultado de la sentencia if

Sentencia switch

La sentencia switch es una alternativa a if-else. Tiene esta sintaxis:

switch (expresión)
{
case valor1:
sentencia1;
sentencia2;
case valor2:


default:
sentencian1;
sentencian2;
}

Si la expresión evalúa valor1 se ejecutan las sentencias que hay a partir de ese bloque case y hasta el final (o sea, hasta sentencian2) o hasta encontrar la primera sentencia break. Si en cambio evalúa valor2 se ejecutarán las sentencias que hay a partir de esa sentencia y hasta el final. Esto es algo muy particular de la sentencia switch y es lo que la diferencia de una sentencia SELECT-CASE de otros lenguajes. La sentencia switch puede funcionar igual que la lógica SELECT-CASE pero para eso requiere utilizar sentencias break. Finalmente, si la expresión no coincidió con ningún valor case se procesará el bloque Default.

switch (expresión)
{
case valor1:
sentencia1;
sentencia2;
break; // pasa la ejecución a la primera sentencia después
// del corchete
case valor2:

break; // pasa la ejecución a la primera sentencia después
// del corchete

default:
sentencian1;
sentencian2;
break; // sentencia innecesaria
}

Éste es un ejemplo válido de la sentencia switch-case:

switch (color)
{
case rojo: cout << «El color es rojo.\n»;
break;
case verde: cout << «El color es verde.\n»;
break;
case azul:
case negro:
cout << «El color es negro o azul.\n»;
break;
default: cout << «Color no previsto.\n»;
}

Sentencia break

La sentencia break provoca la salida de un bucle o de una sentencia switch, no de todos los que pueda haber. Si hay anidamientos pueden hacer falta más sentencias break.

for (int i = 3; i < 20; i++)
{

if (i = 7) break; // si pasa por esta sentencia con I = 7,
// saldrá del bucle for

};

Pero puede hacer falta más de una sentencia break:


do {
do {

if (I = 7) break; // sale sólo de este bucle do
} while(…);
// la sentencia break provoca el salto hasta aquí
} while (…);

Sentencia continue

La sentencia continue se usa dentro de bucles (for, while, do-while) y provoca la bifurcación a la sentencia de evaluación del bucle. Mientras la sentencia break provoca la salida del bucle, la sentencia continue provoca una nueva iteración cuando la evaluación es True. En el caso de un bucle de tipo for, la sentencia continue salta el resto del bucle pero no deja de ejecutar la expresión de actualización antes de realizar la evaluación.
Éstos son dos ejemplos del uso de la sentencia continue:

do {

if (I = 7) continue; // bifurca a la sentencia while

} while(…); // la sentencia continue bifurca a esta sentencia

while (…) // la sentencia continue provoca la evaluación

if (I = 7) continue; // bifurca a la sentencia while

} ;
sentencia…

Bloques de sentencias:

Una o más sentencias puede definir un bloque de sentencias mediante su encierro con llaves. Por ejemplo:

{
a = 1;
b = a + b;
};

Incluso un bloque puede estar definido sin ninguna sentencia:

{}

Reserva de memoria

Las variables definidas de la manera que se ha visto hasta ahora ocupan una posición estática en memoria que se resuelve en tiempo de compilación. En C se utiliza la función malloc() para reservar memoria de manera dinámica. Aunque en C++ también se puede segior aplicando esta función, C++ tiene un operador específico para la reserva dinámica de memoria durante la ejecución, el operador new. El operador new encuentra un bloque de memoria de la longitud necesaria y devuelve la dirección de ese bloque. La sintaxis de este operador es la siguiente:

           nombreTipo      nombreApuntador = new nombreTipo;

int *pint = new int; // define un espacio para un entero

Pero también se puede definir un tipo int de la manera acostumbrada:

int varint;
int *pvarint = &varint;

¿Cuál es la diferencia entre el primer y el segundo método? El primer método reserva la memoria de manera dinámica durante la ejecución, en cambio, en el segundo método la memoria se reserva durante la compilación. Conceptualmente también hay otra diferencia: el operador new reserva memoria para un objeto de dato, lo que representa una idea más amplia que el término variable.
Como el operador new se resuelve en tiempo de ejecución, podría suceder que no se dispone de la memoria solicitada. En tal caso, C++ asigna el apuntador null para indicar esta situación. Si se comprueba esta situación después del requerimiento de memoria, el programa puede tomar una decisión para resolver el problema, o en el peor de los casos, finalizar.
La memoria reservada con el operador new se puede liberar con el operador delete.

delete pint; // libera la memoria apuntada por pint

El operador delete se puede usar para liberar memoria reservada únicamente con el operador new. No obstante, no es obligatorio utilizar el mismo nombre de apuntador que se usó en la reserva de la memoria: lo importante es la dirección apuntada.
Normalmente, el operador new se suele utilizar con bloques de memoria de mayor tamaño. Por ejemplo, la declaración de una simple variable entera se puede hacer de manera estática (vinculación estática) y no representa ningún problema, así como tampoco no representa niguna gran ventaja hacerlo con el operador new. Donde cobra realmente importancia el operador new es cuando se declaran matrices, estructuras o cadenas. Especialmente cuando se trata de variables cuya definición está supeditada a la lógica del proceso. ¿Para qué vamos a definir una matriz de manera estática (en tiempo de compilación) cuando sólo se usa en algunas ejecuciones del programa? Otra gran ventaja de la reserva dinámica de la memoria es que se pueden dimensionar las matrices según las necesidades y no de una manera rígida y predeterminada.
También se puede reservar memoria para una matriz:

int *pint = new int [20]; // reserva memoria para 20 enteros

En este caso, la liberación de memoria debe utilizar corchetes:

delete [] pint;

Operandos y Operadores

El lenguaje C++ tiene una cantidad apreciable de operadores. Podemos definir los operadores básicos:

  • Aritméticos
  • Relacionales
  • Lógicos
  • De bit
  • Asignación
  • De indirección
  • Condicional
  • sizeof
  • De incremento/decremento

Los operadores actúan sobre operandos y los operandos pueden tener distintas formas: pueden ser variables, constantes, expresiones, etc. Antes de entrar en el tema de los operadores debemos definir el concepto del operando LValue. Cuando se indica que un operando es LValue se quiere expresar que es un operando que puede estar a la izquierda de una operación de asignación. Representa una dirección de memoria y es un objeto al que se le pueden asignar valores. Hay operandos que no cumplen estas condiciones (una constante o una expresión).

operadores aritméticos

Como es de suponer, C++ usa operadores para hacer los cálculos. Tiene cinco operadores aritméticos básicos: suma, resta, multiplicación, división y módulo. Una expresión es el conjunto de los operandos con su operador. Por ejemplo, las siguiente son expresiones:

int deuda = 100 – 40; // deuda es 60
int cuota = deuda / 10; // los operandos pueden ser variables

Orden de precedencia

i

nt cuota = deuda / 10 + 10; // ¿cuál es el resultado? 16 ó 3

 

Cuando en una operación aritmética hay más de un operador surge el problema de la precedencia. Por suerte, C++ sigue las mismas reglas que el álgebra. Primero se resuelven las operaciones de multiplicación, división y módulo y luego la suma y la resta. En el ejemplo anterior, el resultado correcto es 16. Tal como se utiliza en el álgebra, los paréntesis permiten modificar las prioridades.

 

int cuota = deuda / (10 + 10); // ¿cuál es el resultado? Ahora es 3.

Asociatividad

El orden de precedencia no resuelve todos los problemas. Si no se utilizan paréntesis pueden presentarse dudas sobre cómo actúa C++ ante ciertas expresiones. Por ejemplo,

int cuota = 600 * 10 / 10; // ¿cuál es el resultado? 60 o 600

La multiplicación y la división comparten el mismo orden de precedencia. En estos casos C++ utiliza la regla de asociatividad (izquierda a derecha o derecha a izquierda). La asociatividad izquierda a derecha indica que si dos operadores que actúan sobre un mismo operando tienen el mismo orden de precedencia, se aplica primero el operador de la izquierda. En el ejemplo anterior, el resultado correcto es 600, ya que la multiplicación y la división tienen asociatividad izquierda a derecha.
Más adelante se resumen los operadores y se indica, entre otras cosas, el tipo asociatividad.

Particularidad de la división

Al realizar una división se puede presentar la circunstancia que los dos operandos sean enteros, ental caso el resultado será entero y se perderá la parte fraccionaria. Si alguno (o los dos) de los operandos es de coma flotante, el resultado mantendrá esa precisión.

El operador módulo

El operador módulo nos permite obtener el resto de una división entre enteros.

int flanes = 5;
int hijos = 3;
int paraelpadre = flanes % hijos; // el padre come 2 flanes

Conversiones

Las operaciones aritméticas se pueden realizar con más de una docena de tipos diferentes. C++ simplifica gran parte del problema realizando una serie de conversiones de manera automática.

conversiones en asignaciones

Como regla general, cuando una variable se asigna a otra de distinto tipo, C++ se encarga de convertir la variable al tipo de la variable receptora antes de hacer la asignación.
Si el tipo de la variable receptora tiene mayor capacidad que el tipo de la variable emisora, no supone ningún problema. Por ejemplo, asignar una variable de tipo short a una variable de tipo long simplemente hará que la variable ocupe 2 bytes más.
El problema surge cuando el tipo de la variable receptora no permite almacenar el valor por excederse en el rango (en cuyo caso, el comportamiento de C++ varía entre las distintas implementaciones) o cuando el tipo de la variable receptora puede almacenar el valor pero perdiendo precisión en el resultado (por ejemplo, por perder la parte fraccionaria).

conversiones en expresiones

Cuando en una expresión se combinan dos tipos aritméticos diferentes C++ puede realizar conversiones automáticas. Hay dos tipos de conversiones automáticas. Primero, C++ realiza una conversión de promoción a int para eliminar una serie de tipos. Estos tipos que promueven a tipo int son los siguientes: short, char y bool (True es promovido a 1 y False es promovido a 0). Segundo, C++ realiza otra conversión automática cuando se encuentra con dos operandos de distinto tipo. Esta conversión generalmente supone que el tipo más pequeño se convierte al más grande.
El compilador realiza la conversión aplicando la siguiente lista en el orden en que se detalla. Es decir, cuando se encuentra el caso para el par de tipos aritméticos de la operación se realiza la conversión y ya no se sigue comprobando el resto de la lista.

Si un operando es y el segundo es se convierte a
long double resto (*) long double
double resto double
float resto float
unsigned long resto unsigned long
long unsignet int unsigned int olong (**)long
long resto long
unsigned int resto insigned int
int int no hay conversión

(*) Resto significa cualquier otro tipo.

(**) Depende del tamaño relativo de los tipos, en lo psible se convierte a long.

Conversión explícita (CAST)

C++ nos permite realizar conversiones de manera explícita. De esa manera, podemos utilizar el tipo deseado para una operación. Por ejemplo, si para una operación nos conviene que una variable int sea considerada como char tendremos que codificar lo siguiente:

int k1;
char d;
….
d = char (k1);

También se puede codificar, con el mismo resultado, de la siguiente manera:

d = (char) k1; // sintaxis propia de C pero válida en C++

Pero se prefiere la primera forma que se parece a la llamada de una función.

Operadores relacionales

C++ provee seis operadores relacionales para poder comparar números. El resultado de la comparación se reduce a dos posibilidades: True y False. Los operadores relacionales son los siguientes:

  • > mayor que
  • < menor que
  • >= mayor o igual
  • <= menor o igual
  • == igual
  • != no igual

Los operadores tienen una precedencia menor que los operadores aritméticos. Por ejemplo, si se tiene la siguiente expresión:

a + 3 > 5 * k

Esta expresión podría leerse de la siguiente manera:

(a + 3) > (5 * k) // correcta

Pero si se convirtiese de esta manera, el resultado sería incorrecto:

a + ( 3 > 5) * k // incorrecta

Operadores lógicos

El operador lógico AND (&&) realiza el producto lógico entre dos expresiones. El operador lógico AND tiene menor precedencia que los operadores relacionales. Si la expresión de la izquierda es False, el resultado será False sin necesidad de evaluar la expresión de lado derecho. Estos son algunos ejemplos del operador &&:

-3 && 3 // expresión True, valor 1
6 < 2 && 8 > 7 // expresión False, valor 0
7 > 1 && 7 == 7 // expresión True, valor 1

El operador OR || indica una suma lógica de dos expresiones. El operador lógico OR tiene menor precedencia que los operadores relacionales y que el operador lógico AND. Estos son algunos ejemplos del operador OR:
4 == 4 || 2 > 4 // expresión True
0 || 0 // expresión False
El operador OR actúa como un separador de expresiones, es decir, se evalúa la expresión que queda a la izquierda del operador y luego la expresión de la derecha.
El operador lógico NOT (!) invierte el valor de la expresión que tiene a continuación. Este operador tiene una precedencia mayor que todas las operaciones aritméticas (y obviamente que las relacionales). Estos son algunos ejemplos del uso del operador NOT:

!(k > m) // si k es mayor a m, el resultado será False
!k > 1 // k puede ser True o False (0 o 1)
// según sea el valor de k 0 o no cero
// el resultado siempre será False

Operadores de bit

Los operadores de bit sólo operan con valores enteros. Los operadores de bit son los siguientes:

  • & producto binario de bits
  • | suma binaria de bits (OR)
  • ^ suma exclusiva de bits (XOR)
  • << desplazamiento de bits hacia la izquierda
  • >> desplazamiento de bits hacia la derecha
  • ~ unario. complemento a 1
  • – unario. complemento a 2

operador AND de bit (&)

El operador & multiplica bit a bit dos números enteros y da como resultado un nuevo valor entero. Cada bit resultante será cero si alguno de los dos bits de la multiplicación son cero.
Los operadores a nivel de bit son análogos los operadores lógicos normales, pero actúan bit a bit. Por ejemplo,

2 & 1 // False, es cero
2 && 1 // true, valor 1

operador OR de bit (|)

El operador | suma bit a bit dos números enteros y da como resultado un nuevo valor entero. Cada bit resultante será uno si alguno (o ambos) de los dos bits de la suma es uno.

operador XOR de bit (^)

El operador ^ suma exclusiva bit a bit dos números enteros y da como resultado un nuevo valor entero. Cada bit resultante será uno sólo si uno de los dos bits de la suma es uno.

operador desplazamiento de bits a la izquierda (<<)

El operador << actúa sobre valores enteros y tiene la siguiente sintaxis:

valor1 << desp1

En donde desp1 es un valor entero que indica la cantidad de bits que hay que desplazar hacia la izquierda en valor1. Se produce un descarte de bits por la izquierda y una entrada de bits (en cero) por la derecha.

operador desplazamiento de bits a la derecha (>>)

El operador >> actúa sobre valores enteros y tiene la siguiente sintaxis:

valor1 >> desp1

En donde desp1 es un valor entero que indica la cantidad de bits que hay que desplazar hacia la derecha en valor1. Se produce un descarte de bits por la derecha y una entrada de bits (en cero) por la izquierda.

Operador unario negación de bit (~)

El operador ~ actúa sobre los bits realizando el complemento 1, o sea, cambia unos por ceros.
Hay una analogía en el funcionamiento del operador de negación NOT (si la expresión evalúa True, el resultado será False) con el operador de negación de bit (los bits que están en cero pasan a 1 y viceversa).

Operador unario (-)

El operador – actúa sobre los bits realizando el complemento 2, o sea, cambia el signo.

Operadores de asignación

El operador de asignación hace que el operando que está a la izquierda del signo igual tome el valor de la expresión que está a la derecha del signo igual.

a = b + 2;
a = a + 2;
a+= 2; // es igual al anterior. El operador de asignación
a*=2; // también se pueden combinar con otros operadores
a/=2;

Operadores de indirección

El apuntador contiene la dirección de un valor y el nombre de un apuntador representa esa dirección. El operador *, denominado operador de indirección (dereferencing operator), se refiere al valor de que hay en esa dirección. Por ejemplo, si ptr1 es un apuntador, entonces ptr1 representa una dirección y *ptr1 representa el valor que hay almacenado en esa dirección.
El símbolo * es el mismo que se utiliza para la multiplicación, C++ utiliza el contexto para determinar si lo que estamos haciendo es una multiplicación o una indirección.

Operador condicional

El operador condicional ? se suele utilizar como alternativa a la sentencia if else. Es el único operador que requiere tres operandos. La sintaxis es la siguiente:
expresión1 ? expresión2 : expresión3
Por ejemplo,

cmdOK = (ingresos > egresos) ? True : False;

Si la expresión1 es verdadera se asigna (en este caso) True a cmdOK, en caso contrario, se asigna False.
Se podría haber expresado de la siguiente manera:

if ingresos > egresos
cmdOK = True;
else
cmdOK = False;

La sintaxis con el operador ? es más concisa pero a veces no tan legible como la conocida sentencia if else.

Operador sizeof

El operador sizeof nos permite conocer el tamaño en bytes de un tipo o de una variable. Supóngase que se tienen estas definiciones:

int k;
long j:
char b;

sizeof (k) no da el valor 2
sizeof (j) nos da el valor 4
sizeof (b) nos da el valor 1
sizeof (float) nos da el valor 4 (tipo float ocupa 4 bytes)
Si se aplica sizeof a una referencia, nos dará como resultado la cantidad de bytes ocupados por el objeto que apunta la referencia.

Operador de incremento/decremento

Éste es un operador muy peculiar de C++. El operador ++ suma uno a la variable, la que debe ser de tipo LValue (variable que puede estar a la izquierda de una expresión, o sea, que puede recibir una asignación de valor).

i++; // suma uno a i
i = i +1; // expresión equivalente

k = 1;
k++; // k es igual a 2

j = k++; // j es igual a 2 pero k vale 3 ¿Porqué?

Existen dos tipos de incrementos:

  • Preincremento: suma y asigna (++ está a la izquierda de la variable)
  • Postincremento: asigna y suma (++ está a la derecha de la variable)

 

j = ++k; // j es igual a 4 y k es igual a 4
j = k++; // j es igual a 4 y k es igual a 5

 

Todo lo dicho para el operador incremento es transladable para el operador decremento (—), la diferencia es que este operador resta en lugar de sumar.

 

j = (++k)—; // j es igual a 6, k sigue siendo 5

 

Deja un comentario