13 Operadores II: Más operadores

Veremos ahora más detalladamente algunos operadores que ya hemos mencionado, y algunos nuevos.

Operadores de Referencia (&) e Indirección (*)

El operador de referencia (&) nos devuelve la dirección de memoria del operando.

Sintaxis:

&<expresión simple>

Por ejemplo:

int *punt;
int x = 10;
 
punt = &x;

El operador de indirección (*) considera a su operando como una dirección y devuelve su contenido.

Sintaxis:

*<puntero>

Ejemplo:

int *punt;
int x;
 
x = *punt;

Operadores . y ->

Operador de selección (.). Permite acceder a objetos o campos dentro de una estructura.

Sintaxis:

<variable_estructura>.<nombre_de_variable>

Operador de selección de objetos o campos para estructuras referenciadas con punteros. (->)

Sintaxis:

<puntero_a_estructura>-><nombre_de_variable>

Ejemplo:

struct punto {
   int x;
   int y;
};
 
punto p1;
punto *p2;
 
p1.x = 10;
p1.y = 20;
p2->x = 30;
p2->y = 40;

Operador de preprocesador

El operador "#" sirve para dar órdenes o directivas al compilador. La mayor parte de las directivas del preprocesador se verán en capítulos posteriores.

El preprocesador es un programa auxiliar, que forma parte del compilador, y que procesa el fichero fuente antes de que sea compilado. En realidad se limita a seguir las órdenes expresadas en forma de directivas del preprocesador, modificando el programa fuente antes de que sea compilado.

Veremos, sin embargo dos de las más usadas.

Directiva define

La directiva define, sirve para definir macros. Cada macro es una especie de fórmula para la sustitución de texto dentro del fichero fuente, y puede usar, opcionalmente parámetros.

Sintaxis:

#define <identificador_de_macro> <secuencia>

El preprocesador sustituirá cada ocurrencia del <identificador_de_macro> en el fichero fuente, por la <secuencia>, (aunque con algunas excepciones). Cada sustitución se denomina "expansión de la macro", y la secuencia se suele conocer como "cuerpo de la macro".

Si la secuencia no existe, el <identificador_de_macro> será eliminado cada vez que aparezca en el fichero fuente.

Después de cada expansión individual, se vuelve a examinar el texto expandido a la búsqueda de nuevas macros, que serán expandidas a su vez. Esto permite la posibilidad de hacer macros anidadas. Si la nueva expansión tiene la forma de una directiva de preprocesador, no será reconocida como tal.

Existen otras restricciones a la expansión de macros:

  • Las ocurrencias de macros dentro de literales, cadenas, constantes alfanuméricas o comentarios no serán expandidas.
  • Una macro no será expandida durante su propia expansión, así #define A A, no será expandida indefinidamente.

Ejemplo:

#define suma(a,b) ((a)+(b))

Los paréntesis en el cuerpo de la macro son necesarios para que funcione correctamente en todos los casos, lo veremos mucho mejor con otro ejemplo:

#include <iostream> 
using namespace std;
 
#define mult1(a,b) a*b 
#define mult2(a,b) ((a)*(b)) 
 
int main() { 
   // En este caso ambas macros funcionan bien: (1)
   cout << mult1(4,5) << endl; 
   cout << mult2(4,5) << endl; 
   // En este caso la primera macro no funciona, ¿por qué?: (2)
   cout << mult1(2+2,2+3) << endl; 
   cout << mult2(2+2,2+3) << endl; 
   
   return 0;
}

¿Por qué falla la macro mult1 en el segundo caso?. Para averiguarlo, veamos cómo trabaja el preprocesador.

Cuando el preprocesador encuentra una macro la expande, el código expandido sería:

int main() { 
   // En este caso ambas macros funcionan bien: 
   cout << 4*5 << endl; 
   cout << ((4)*(5)) << endl; 
   // En este caso la primera macro no funciona, ¿por qué?: 
   cout << 2+2*2+3 << endl; 
   cout << ((2+2)*(2+3)) << endl;
   
   return 0;
}

Al evaluar "2+2*2+3" se asocian los operandos dos a dos de izquierda a derecha, pero la multiplicación tiene prioridad sobre la suma, así que el compilador resuelve 2+4+3 = 9. Al evaluar "((2+2)*(2+3))" los paréntesis rompen la prioridad de la multiplicación, el compilador resuelve 4*5 = 20.

Directiva include

La directiva include, como ya hemos visto, sirve para insertar ficheros externos dentro de nuestro fichero de código fuente. Estos ficheros son conocidos como ficheros incluidos, ficheros de cabecera o "headers".

Sintaxis:

#include <nombre de fichero cabecera>
#include "nombre de fichero de cabecera"
#include identificador_de_macro 

El preprocesador elimina la línea include y la sustituye por el fichero especificado. El tercer caso halla el nombre del fichero como resultado de aplicar la macro.

La diferencia entre escribir el nombre del fichero entre "<>" o """", está en el algoritmo usado para encontrar los ficheros a incluir. En el primer caso el preprocesador buscará en los directorios "include" definidos en el compilador. En el segundo, se buscará primero en el directorio actual, es decir, en el que se encuentre el fichero fuente, si el fichero no existe en ese directorio, se trabajará como el primer caso. Si se proporciona el camino como parte del nombre de fichero, sólo se buscará es ese directorio.

El tercer caso es "raro", no he encontrado ningún ejemplo que lo use, y yo no he recurrido nunca a él. Pero el caso es que se puede usar, por ejemplo:

#define FICHERO "trabajo.h"
 
#include FICHERO
 
int main()
{
...
}

Es un ejemplo simple, pero en el capítulo 23 veremos más directivas del preprocesador, y verás el modo en que se puede definir FICHERO de forma condicional, de modo que el fichero a incluir puede depender de variables de entorno, de la plataforma, etc.

Por supuesto la macro puede ser una fórmula, y el nombre del fichero puede crearse usando esa fórmula.

Comentarios de los usuarios (12)

orlando
2012-09-21 23:10:26

mi pregunta es a que reemplaza los operadores de referencia e indireccion (& , *)

Adri
2015-01-04 23:42:42

Si yo declarara un puntero dentro de una estructura,¿Como uso ese puntero?

#include <iostream>
using namespace std;
int main()
{
	struct
	{
		int a = 3;
		int *b = &a;
	}B;
	cout<<B.*b; //Esto me da error
	return 0;
}
Steven R. Davidson
2015-01-05 01:47:17

Hola Adri,

Ten presente que el operador de acceso a miembro (.) se evalúa antes que el operador unitario (*). Por lo tanto, la expresión debería ser:

cout << *B.b;

El orden de evaluación es:

#1  B.b
#2  *(B.b)
#3  cout << (*(B.b))

Espero que esto te aclare la duda.

Steven

Jesus Leyva
2015-02-14 08:56:51

Hola, gracias por los buenos temas y por ser tan puntuales en el tema a tratar. Mi pregunta es la sgte.! : He visto como se asigna punteros a un array(de tipo entero), he querido hacer lo mismo con otro array(de tipo caracter). Es un poco confuso el resultado que me arrojo, pero quisiera saber el porque del resultado obtenido.Gracias.! :D

int main(){
	int array_entero[3] = {1, 2, 3};
	int *puntero_entero = &array_entero[0];
	
	cout<<puntero_entero<<endl;
	cout<<puntero_entero+1<<endl;
	cout<<puntero_entero+2<<endl;
	
	cout<<*puntero_entero<<endl;
	cout<<*(puntero_entero+1)<<endl;
	cout<<*(puntero_entero+2)<<"\n\n\n";
	
	
	char array_caracter[11] = {'h','o','l','a',' ','m','u','n','d','o'};
	char *puntero_caracter = &array_caracter[0];
	
	cout<<&array_caracter[0]<<"\n\n\n";
	
	
	cout<<puntero_caracter<<endl;
	cout<<puntero_caracter+1<<endl;
	cout<<puntero_caracter+2<<endl;
	cout<<endl;
	cout<<*(puntero_caracter)<<endl;
	cout<<*(puntero_caracter+1)<<endl;
	cout<<*(puntero_caracter+2)<<endl;
	cout<<endl;
	cout<<&puntero_caracter<<endl;
	cout<<&puntero_caracter+1<<endl;
	cout<<&puntero_caracter+2<<endl;
	return 0;
}
Steven R. Davidson
2015-02-14 14:09:22

Hola Jesús,

Como no explicas la confusión de los resultados, no podré ser de mucha ayuda. Sospecho que el problema es a la hora de mostrar el puntero a 'char'. En lugar de mostrar las direcciones de memoria, muestra el contenido de la cadena.

Esto es porque 'cout <<' interpreta datos de tipo 'char *' como una cadena de caracteres y no meramente un puntero a 'char'. Si quieres mostrar las direcciones de memoria de 'puntero_caracter', entonces haz un "cásting" a un puntero genérico, 'void *', para obligar a 'cout <<' a tratar el puntero como un puntero y así mostrar la dirección de memoria guardada en ello. Esto es,

cout << (void *) puntero_caracter    << endl;
cout << (void *)(puntero_caracter+1) << endl;
cout << (void *)(puntero_caracter+2) << endl;
cout << endl;

Espero que esto te oriente.

Steven

Jesus Leyva
2015-02-15 10:32:15

Si, esa era mi confusión.! el por que el puntero char, al querer mostrar la dirección de memoria éste me arrojaba la cadena en sí.!. Gracias por la explicación, justo lo que queria saber.! :D

winsu
2015-03-14 21:10:32

¿Como puedo preguntar en los respectivos apartados del curso C++?

Daniel
2015-03-14 21:16:53

Lo siento por lo de antes, lo acabo de comprobar,ejejeje.

Antes de nada felicitaciones por el curso, esta siendo de una gran ayuda.Tengo una duda que me gustaría resolver, en el ejemplo que se usa este operador ->, se explica que con el se accede a datos miembro de estructuras, las cuales estan referenciadas con punteros. En el ejemplo se hace algo asi:

struct objeto{

int a;

int b;

}

main(){

objeto *prueba;

prueba->a = 10;

prueba->b = 13;

}

Mi pregunta es: realmente no hay ninguna estructura llamada prueba, el crear un puntero a una estructura no crea un objeto de esa estructura, o al usar ese operador se crearía un objeto llamad prueba??.

Gracias y un saludo.

Steven R. Davidson
2015-03-18 03:20:46

Hola Daniel,

Correcto. En tu ejemplo, 'prueba' no es más que un puntero, el cual contiene basura y por tanto no se debería usar para acceder al 'objeto' apuntado.

Si lo prefieres, modifca el ejemplo para que sí exista tal estructura de tipo 'objeto'; por ejemplo,

objeto obj;
objeto *prueba = &obj;
...

Ahora no hay problemas de punteros.

Espero haber aclarado la duda.

Steven

maria
2015-06-02 02:50:52

#include <stdio.h>

#include <stdlib.h>

void main()

{

int n1;

int c=n1;

while(c <=n1)

{

printf("\nBienvenido a Visual C++ ");

printf("\n cuantas veces quieres que se repita/n ? ",c);

scanf ("%d",&c);

c++;

}

printf("\n");

system("pause");//me gustaria que me diera las veces que se repita pero me marca error

}

franklin
2015-06-18 15:54:51

Hola, estoy iniciando en el mundo de la programacion, estoy viendo un tema llamado "clase", vi un codigo que me parecio razonable de comprender pero fue lo contrario.

#include <iostream>

using namespace std;

class pareja {

private:

// Datos miembro de la clase "pareja"

int a, b;

public:

// Funciones miembro de la clase "pareja"

void Lee(int &a2, int &b2);//que significa esta linea, el &a2,&b2?

void Guarda(int a2, int b2) {

a = a2;//porque a vale a2?

b = b2;

}

};

void pareja::Lee(int &a2, int &b2) {

a2 = a;// Porque a2 vale a nuevamente

b2 = b;

}

int main() {

pareja par1;

int x, y;//porque x,y no se declararon arriba?

par1.Guarda(12, 32);//estos valores son X y Y?

par1.Lee(x, y);

cout << "Valor de par1.a: " << x << endl;

cout << "Valor de par1.b: " << y << endl;

return 0;

}

Steven R. Davidson
2015-06-24 21:01:18

Hola Franklin,

Veamos las dudas que tienes:

1. que significa esta linea, el &a2,&b2?

Realmente se trata de 'int &' que es el tipo de dato para crear una referencia, que en este caso se usa para pasar datos por referencia a una función. Puedes consultar nuestro capítulo 15 ( http://c.conclase.net/curso/index.php?cap=015#inicio ) acerca del tema del paso por referencia.

En breve, 'a2' se refiere directamente a la variable pasada a 'Lee()'; al igual que 'b2'. Por ejemplo, al invocar:

par1.Lee(x,y);

'a2' refiere a la variable 'x' y 'b2' refiere a 'y'. Esto significa que usando 'a2' y 'b2', podemos modificar directamente los valores de 'x' e 'y', respectivamente. Es más fácil pensar que 'a2' y 'b2' son apodos - otros nombres - de las mismas variables de 'x' e 'y', respectivamente.

2. Porque a2 vale a nuevamente

Para prevenir confusión, aclararé que no se trata de una igualación, sino de una asignación. Esto significa que el valor en 'a2' se copia a 'a', y respectivamente, el valor en 'b2' se copia a 'b'. Recuerda que 'a' y 'b' son datos miembro de esta clase. Esto implica que cualquier objeto instanciado de 'pareja' también crea memoria para SUS respectivas variables de 'a' y 'b'.

3. porque x,y no se declararon arriba?

Si con "arriba" te refieres a definir estas variables fuera de 'main()', entonces estarías definiendo variables globalmente, y en general esto es demasiado problemático, por lo que favorecemos definiciones locales. Si con "arriba" te refieres a definirlas dentro de 'pareja', entonces la explicación es que no son necesarias porque ya tenemos 'a' y 'b'. Nos interesa crear las variables independientemente porque el programa principal - 'main()' - requiere su uso.

4. estos valores son X y Y?

No. '12' y '32' son literales que no tienen nada que ver con 'x' ni con 'y'. Ahora bien, la función miembro, 'Guarda()', obtendrá estos valores literales mediante sus variables locales, 'a2' y 'b2'. Según la definición de esta función, asignaremos los valores en estos parámetros a los valores privados, 'a' y 'b', respectivamente. El resultado es equivalente a si pudiéramos hacer lo siguiente:

par1.a = 12;
par1.b = 32;

Sin embargo, por motivos de diseño, generalmente queremos un sistema más seguro y definir una gestión que internamente se responsabilice de sus datos; también nos interesa una comunicación con objetos, mediante una interfaz pública, que en este ejemplo es 'Lee()' y 'Guarda()'.

Posteriormente, leemos estos dos valores y los guardamos en 'x' e 'y', respectivamente, al invocar 'Lee()'. Así que ahora 'x' e 'y' sí contiene estos dos valores.

Espero que esto te aclare las dudas.

Steven