En los capítulos anteriores, las variables se han explicado como ubicaciones en la memoria del ordenador a las que se puede acceder mediante su identificador (nombre. De esta manera, el programa no necesita preocuparse por la dirección física de los datos en memoria; simplemente utiliza el identificador cada vez que tiene que referirse a la variable.

    Para un programa C++, la memoria de un ordenador es similar a una sucesión de celdas de memoria, cada una con un tamaño en bytes, cada una con una dirección única. Estas celdas de memoria de un byte están ordenadas de forma que las representaciones de datos de más de un byte ocupen celdas de memoria con direcciones consecutivas.

    De esta manera, cada célula puede ser fácilmente localizada en la memoria por medio de su dirección única. Por ejemplo, la celda de memoria con la dirección 1776 siempre sigue inmediatamente después de la celda con la dirección 1775 y antes de la celda con la dirección 1777. Contiene exactamente mil células después de 776 y exactamente mil células antes de 2776.

    Cuando se declara una variable, a la memoria necesaria para almacenar su valor se le asigna una ubicación específica en la memoria (su dirección de memoria. Generalmente, los programas C++ no deciden activamente las direcciones de memoria exactas donde se almacenan sus variables. Afortunadamente, esta tarea se deja en manos del entorno de tiempo de ejecución del programa, normalmente un sistema operativo que determina las ubicaciones específicas de la memoria en tiempo de ejecución. Sin embargo, puede ser útil que un programa pueda obtener la dirección de una variable durante la ejecución para acceder a las celdas de datos que se encuentran en una determinada posición relativa a la misma.

    Las direcciones del operador (&)

    La dirección de una variable puede obtenerse precediendo el nombre de una variable con el signo &, llamado operador de dirección. Por ejemplo:

    foo = & myvar;

    Esto asignaría la dirección de la variable myvar a foo; al prefijar el nombre de la variable myvar con la dirección del operador (&), ya no asignamos el contenido de la variable misma a foo, sino su dirección.

    La dirección real de una variable en memoria no se puede conocer antes de la ejecución, pero suponga, para aclarar ciertos conceptos, que myvar se coloca durante la ejecución en la dirección de memoria 1776.

    En este caso, considere el siguiente fragmento de código:

    myvar = 25;foo = & myvar;bar = myvar;

    Los valores contenidos en cada variable después de su ejecución se ilustran en el siguiente diagrama:

    Punteros - referencia

    Primero, asignamos el valor 25 a myvar (una variable cuya dirección en memoria supuesta es 1776.

    La segunda declaración atribuye a foo la dirección de myvar, supuestamente 1776.

    Finalmente, la tercera instrucción asigna el valor contenido en myvar a bar. Se trata de una operación de asignación estándar, como ya se ha hecho muchas veces en capítulos anteriores.

    La principal diferencia entre la segunda y la tercera declaración es la apariencia de la dirección del operador (&.

    La variable que almacena la dirección de otra variable (como foo en el ejemplo anterior) se llama en C ++ un puntero. Los punteros son una característica muy poderosa del lenguaje que tiene muchos usos en la programación de bajo nivel. Un poco más tarde, veremos cómo declarar y usar los punteros.

    Operador de referencia (*)

    Como acabamos de ver, una variable que almacena la dirección de otra variable se llama puntero. Se dice que los punteros «apuntan» a la variable cuya dirección almacenan.

    Una propiedad interesante de los punteros es que pueden ser utilizados para acceder directamente a la variable sobre la que apuntan. Para ello, preceda el nombre del puntero con el operador de referencia (*. El propio operador puede leerse como «valor indicado por».

    Por lo tanto, siguiendo los valores del ejemplo anterior, la siguiente instrucción:

    baz = * foo;

    Esto podría interpretarse de la siguiente manera: «baz es igual al valor indicado por foo», y la sentencia asignaría el valor 25 a baz, ya que foo corresponde a 1776 y el valor indicado en 1776 (siguiendo el ejemplo anterior) sería 25.

    Es importante distinguir claramente que foo se refiere al valor 1776, mientras que * foo (seguido de un asterisco * que precede al identificador) corresponde al valor almacenado en la dirección 1776, que es en el caso 25. Nótese la diferencia entre incluir o no incluir el operador de descodificación (añadí un comentario explicativo sobre cómo leer cada una de estas dos expresiones):

    baz = foo; // baz igual a foo (1776)baz = * foo; // baz igual al valor indicado por foo (25)

    Los operadores de referencia y de referencia son, por lo tanto, complementarios:

    • & es la dirección del operador y puede leerse simplemente como «dirección de».
    • * es el operador de referencia y puede leerse como «valor indicado por».

    Por lo tanto, tienen un significado opuesto: una dirección obtenida con & puede ser referenciada con *.

    Anteriormente, habíamos realizado las siguientes dos operaciones de asignación:

    myvar = 25;foo = & myvar;

    Justo después de estas dos declaraciones, todas las siguientes expresiones darían un resultado verdadero:

    myvar == 25& myvar == 1776to == 1776* foo == 25

    La primera expresión es bastante clara, ya que la operación de atribución realizada en myvar fue myvar = 25. El segundo utiliza el operador de dirección (&), que devuelve la dirección de myvar, que supusimos que tenía un valor de 1776. La tercera es algo obvia, ya que la segunda expresión era verdadera y la operación de asignación realizada en foo era foo = & myvar. La cuarta expresión utiliza el operador de referencia (*) que puede leerse como «valor indicado por», y el valor indicado por foo es 25.

    Así que, después de todo esto, también se puede deducir que mientras la dirección dada por foo permanezca sin cambios, la siguiente expresión también será verdadera:

    * foo == myvar

    Declarar punteros

    Debido a la capacidad de un puntero para referirse directamente al valor al que apunta, un puntero tiene propiedades diferentes cuando apunta a un carácter que cuando apunta a un número entero o flotante. Una vez que se realiza la derivación, se debe conocer el tipo. Y para ello, la declaración de un puntero debe incluir el tipo de datos sobre los que el puntero apuntará.

    La declaración del puntero sigue esta sintaxis:

    escriba * nombre;

    donde tipo es el tipo de datos apuntados por el puntero. Este tipo no es el tipo de puntero en sí, sino el tipo de datos a los que apunta el puntero. Por ejemplo:

    int * número;char *;char *;char *;doble * decimal;

    Estas son tres frases de orientación. Cada uno de ellos tiene la intención de apuntar a un tipo diferente de datos, pero de hecho, todos son punteros y probablemente todos ocuparán el mismo espacio de memoria (el tamaño de la memoria de un puntero depende de la plataforma. donde funciona el programa. Sin embargo, los datos a los que indican no ocupan ni la misma cantidad de espacio ni el mismo tipo: el primero apunta a un int, el segundo a un carácter y el último a un doble. Por lo tanto, aunque estos tres ejemplos de variables son todos punteros, en realidad tienen diferentes tipos: int *, char * y double *, respectivamente, dependiendo del tipo al que apuntan.

    Tenga en cuenta que el asterisco (*) utilizado al declarar un puntero sólo significa que es un puntero (forma parte de su tipo prescriptor de compuestos), y no debe confundirse con el operador de descodificación visto anteriormente, sino que también debe escribirse con un asterisco (*.

    Son simplemente dos cosas diferentes representadas con el mismo signo.

    Veamos un ejemplo de los indicadores:

    // mi primer puntero#incluye <iostream>usando namespace std;int main (){ int firstvalue, secondvalue; int * singlepointer; singlepointer= & firstvalue; * singlepointer= & firstvalue; * singlepointer= 10; singlepointer= & segundo valor; * puntuación única = 20; coste <<<<<<< << < < < n'; coste <<<<< "el segundo valor es" <<<< < n'; retorno 0;}.
    

    Resultado de la ejecución

    el primer valor es 10el segundo valor es 20
    

    Tenga en cuenta que incluso si ni el primer ni el segundo valor definen directamente un valor en el programa, a ambos se les asigna un valor definido indirectamente mediante el uso de un único puntero. Así es como funciona:

    En primer lugar, la primera dirección de valor se asigna al monpointeur utilizando la dirección del operador (&. A continuación, al valor designado por el monpointeur se le asigna el valor 10. Puesto que el puntero individual apunta a la posición de memoria de primer valor, esto cambia realmente el valor de primer valor.

    Para demostrar que un puntero puede apuntar a diferentes variables durante su vida útil en un programa, el ejemplo repite el proceso con un segundo valor y el mismo puntero, un solo puntero .

    He aquí un ejemplo más elaborado:

    // más punteros#incluyen <iostream>usando namespace std;int principal (){ int primer valor = 5, segundo valor = 15; int * p1, * p2; p1 = & primer valor ; // p1 = dirección del primer valor p2 = & segundo valor p2 = & segundo valor p2 = // p2 = dirección del segundo valor * p1 = 10; // valor señalado por p1 = 10 * p2 = * p1; // valor indicado por p2 = valor indicado por p1 p1 = p2; // p1 = p2 (se copia el valor del puntero) * p1 = 20; // valor indicado por p1 = 20 costes <<<< "el primer valor es" <<<<< << < < < n'; coste << "el segundo valor es" <<< < < \ n'; retorno 0;}}.
    

    Resultado de la ejecución

    el primer valor es 10el segundo valor es 20
    

    Cada operación de asignación incluye un comentario sobre cómo puede leerse cada línea: es decir, sustituir los amperios (&) por «dirección de» y los asteriscos (*) por «valor designado por».

    Tenga en cuenta que hay expresiones con los punteros p1 y p2, con y sin el operador de descodificación (*. El significado de una expresión que utiliza el operador de referencia (*) es muy diferente de la que no lo hace. Cuando este operador precede al nombre del puntero, la expresión se refiere al valor a apuntar, mientras que cuando un nombre de puntero aparece sin este operador, se refiere al valor del propio puntero (es decir, la dirección del objeto apuntado por el puntero.

    Otra cosa que puede atraer su atención es la línea:

    int * p1, * p2;

    Esto declara los dos punteros usados en el ejemplo anterior. Pero tenga en cuenta que hay un asterisco (*) para cada puntero, de modo que ambos tienen el tipo int * (puntero a int. Esto es necesario debido a las reglas de prelación. Tenga en cuenta que si, en su lugar, el código era:

    int * p1, p2;

    p1 sería de tipo int *, pero p2 sería de tipo int. Los espacios no cuentan para eso en absoluto. En cualquier caso, es suficiente recordar poner un asterisco por puntero para la mayoría de los usuarios de punteros interesados en declarar múltiples punteros por declaración. O mejor aún: utilice una instrucción diferente para cada variable.

    Lea también C+++ Ejercicios con solucionesPuntos

    y tablas

    El concepto de tablas está vinculado al de punteros. De hecho, los arreglos funcionan muy bien como punteros a sus primeros elementos y, en realidad, un arreglo siempre puede ser convertido implícitamente en un puntero del tipo apropiado.

    Por ejemplo, considere estas dos afirmaciones:

    int myarray[20];int * monpointeur;

    La siguiente operación de asignación sería válida:

    monpointeur = myarray;

    Después de eso, monpointeur y myarray serían equivalentes y tendrían propiedades muy similares. La principal diferencia es que al monpointeur se le puede asignar una dirección diferente, mientras que al myarray nunca se le puede asignar nada y siempre representará el mismo bloque de 20 elementos de tipo int. Por lo tanto, la siguiente asignación no sería válida:

    myarray = monpointeur;

    Veamos un ejemplo que mezcla tablas y punteros:

    // más punteros#incluyen <iostream>usando namespace std;int main (){ int numbers[5]; int * p; p; p = números; * p = 10; p ++; * p = 20; p = & números[2]; * p = 30; p = números + 3; * p = 40; p = números; * (p + 4) = 50; para (int n = 0; n <5; n ++) coste <<<< números[n] <<",","; devolución 0;}}.

    Resultado de la ejecución

    10, 20, 30, 40, 50,

    Los punteros y las tablas soportan el mismo conjunto de operaciones, con el mismo significado para ambos. La principal diferencia es que a los punteros se les pueden asignar nuevas direcciones, mientras que a los arrays no.

    En el capítulo sobre los cuadros, los corchetes ([]) se han explicado para especificar el índice de un elemento del cuadro. Bueno, en realidad, estos medios constituyen un operador de descentralización llamado operador offset. Derivan la variable que siguen como *, pero también añaden el número entre paréntesis a la dirección que se está derivando. Por ejemplo:

    a[5] = 0; // a[5 offset] = 0* (a + 5) = 0; // apuntando a (a + 5) = 0

    Estas dos expresiones son equivalentes y válidas, no sólo si a es un puntero, sino también si a es una matriz. Recuerde que si se trata de un array, su nombre puede ser utilizado como puntero a su primer elemento.

    Inicialización del puntero

    Los punteros se pueden inicializar para apuntar a ubicaciones específicas al mismo tiempo que se definen:

    int myvar;int * myptr = & myvar;

    El estado resultante de las variables después de este código es el mismo que el de después:

    int myvar;int * myptr;myptr = & myvar;

    Cuando los punteros se inicializan, lo que se inicializa es la dirección a la que apuntan (es decir, myptr), nunca el valor que se está señalando (es decir, * myptr. Por lo tanto, el código anterior no debe confundirse con:

    int myvar;int * myptr;* myptr = & myvar;

    Lo que de todos modos no tendría mucho sentido (y no es un código válido.

    El asterisco (*) en la declaración del puntero (línea 2) sólo indica que es un puntero, no es el operador de referencia (como en la línea 3. Ambas cosas usan el mismo signo: *. Como siempre, los espacios no son relevantes y nunca cambian el significado de una expresión.

    Los punteros pueden ser inicializados a la dirección de una variable (como en el caso anterior) o al valor de otro puntero (o array):

    int myvar;int * foo = & myvar;int * bar = foo;

    Puntero aritmético

    Realizar operaciones aritméticas en punteros es un poco diferente a realizarlas en tipos enteros regulares. Para empezar, sólo se permiten operaciones de suma y resta; las demás no tienen sentido en el mundo de los punteros. Pero la suma y la resta se comportan de manera ligeramente diferente con los punteros, dependiendo del tamaño del tipo de datos a los que apuntan.

    Al introducir los tipos de datos fundamentales, encontramos que los tipos tenían diferentes tamaños. Por ejemplo: char siempre tiene un tamaño de 1 byte, short es usualmente más grande que eso y int y long son aún más grandes; el tamaño exacto de estos depende del sistema. Por ejemplo, supongamos que en un sistema dado, el carácter toma 1 byte, el corto toma 2 bytes y el largo 4.

    Ahora supongamos que definimos tres punteros en este compilador:

    char * mychar;short * myshort;long * mylong;

    y sabemos que apuntan a las posiciones de memoria 1000, 2000 y 3000, respectivamente.

    Así que, si escribimos:

    ++ mychar;++++ myshort;+++ mylong;

    Como era de esperar, mychar contendría el valor 1001. Pero no tan obvio, myshort contendría el valor 2002 y mylong contendría 3004, incluso si se incrementaran sólo una vez. Esto se debe a que, al añadir un puntero a un puntero, apunta al siguiente elemento del mismo tipo y, por lo tanto, el tamaño en bytes del tipo al que apunta se añade al puntero.

    punteros aritméticos

    Esto es aplicable tanto al sumar como al restar cualquier número de un puntero. Esto sucedería exactamente de la misma manera si escribiéramos:

    mychar = mychar + 1;myshort = myshort + 1;mylong = mylong + 1;

    Para los operadores incremento (++) y decremento (-), ambos pueden ser utilizados como prefijo o sufijo de una expresión, con una ligera diferencia de comportamiento: como prefijo, el incremento ocurre antes de la evaluación de la expresión, y como sufijo, el incremento ocurre después de la evaluación de la expresión. Esto también se aplica a las expresiones que incrementan y disminuyen los punteros, que pueden formar parte de expresiones más complejas que también incluyen operadores de descodificación (*. Al recordar las normas de prioridad de los operadores, podemos recordar que los operadores posfijos, como el incremento y la disminución, tienen una prioridad mayor que los operadores prefijados, como el operador de referencia (*. Por lo tanto, la siguiente expresión:

    * p ++

    es equivalente a * (p ++++. Y lo que hace es aumentar el valor de p (para que ahora apunte al siguiente elemento), pero como ++ se usa como postfijo, toda la expresión se evalúa como el valor originalmente indicado por el puntero (la dirección a la que apunta. antes de ser incrementada.

    Estas son esencialmente las cuatro combinaciones posibles del operador de derivación con las versiones de prefijo y sufijo del operador de incremento (las mismas son aplicables al operador de decremento):

    * p ++ // idéntico a * (p ++): puntero de incremento y dirección de no referencia, referenciado * ++ p // idéntico a * (++ p): puntero de incremento y dirección de referencia+++ * p // idéntico a ++ (* p): puntero de referencia e incremento del valor indicado (* p) ++ // descodificación del puntero, y post-incremento del valor al que apunta

    A continuación se presenta una declaración típica, aunque no tan sencilla, en la que participan estos operadores:

    * p ++++ = * q +++;

    Dado que ++ tiene una prioridad mayor que *, p y q se incrementan, pero debido a que los dos operadores de incrementos (++) se utilizan como sufijo de postfijo y no como prefijo, el valor asignado a * p es * q antes de que se incrementen los dos valores. Y luego ambos se incrementan. Sería más o menos lo mismo que:

    * p = * q;++ p;++ q;

    Como siempre, los paréntesis reducen la confusión al añadir legibilidad a las expresiones.

    Punteros y const

    Se pueden utilizar punteros para acceder a una variable en función de su dirección. Este acceso puede incluir la modificación del valor apuntado. Pero también es posible declarar punteros que pueden acceder al valor apuntado para leerlo, pero no modificarlo. Para ello, basta con calificar el tipo señalado por el puntero como una const.

    Por ejemplo:

    int x;int y = 10;const int * p = & y;x = * p; // ok: lectura p* p = x; // error: modificación de p, que se califica como const
    

    Aquí, p apunta a una variable, pero la señala como una const, lo que significa que puede leer el valor apuntado, pero no puede cambiarlo. También tenga en cuenta que la expresión & y es de tipo int *, pero está asignada a un puntero de tipo const int *. Esto está permitido: un puntero a non-const puede convertirse implícitamente en un puntero a const, ¡pero no al revés! Como característica de seguridad, los punteros a const no son implícitamente convertibles a punteros a non-const.

    Uno de los casos en los que se utilizan punteros en los elementos const es el de los parámetros de función: una función que lleva un puntero a non-const como parámetro puede cambiar el valor transmitido como argumento, mientras que una función que lleva un puntero a const como parámetro no puede.

    // punteros como argumentos:#include <iostream>using namespace std;void increment_all (int * start, int * stop){ int * current = start; while (current! = stop) { ++ (* current); // increment value pointed ++ current; // increment point }}void print_all (const int * start, const int * stop){ const int * current = start; while (current! = stop) { cost <<<<< * current << \ n'; ++ current; // increment point }}}int main (){ int numbers [] = {10,20,30}; increment_all (numbers, numbers + 3); print_all (numbers, numbers + 3); return 0;}}.

    Tenga en cuenta que print_all utiliza punteros que apuntan a elementos constantes. Estos punteros apuntan a un contenido constante que no pueden modificar, pero que no son ellos mismos: es decir, los punteros siempre pueden ser incrementados o asignados a direcciones diferentes, aunque no pueden modificar el contenido al que apuntan.

    Y aquí es donde se añade una segunda dimensión a la constancia a los punteros: los punteros también pueden ser const ellos mismos, y esto se especifica añadiendo const al tipo indicado (después del asterisco):

    int x; int * p1 = & x; // non-const pointer to int non-constconst int * p2 = & x; // non-const pointer to int int * const p3 = & x; // pointer to int non-constconst int * const p4 = & x; // pointer to int
    

    La sintaxis con constantes y punteros es ciertamente delicada, y reconocer los casos más apropiados para cada uso tiende a requerir algo de experiencia. En cualquier caso, es importante dominar los punteros (y referencias) tan pronto como sea posible, pero no se preocupe demasiado por ingresar todo si es la primera vez que se expone a la mezcla de constantes y punteros. Otros casos de uso aparecerán en capítulos futuros.

    Para

    añadir un poco más de confusión a la sintaxis de las constantes con punteros, la const del calificador puede preceder o seguir al tipo punteado, con exactamente el mismo significado:

    const int * p2a = & x; // non-const pointer to const intint const * p2b = & x; // also a non-const pointer to const int
    

    Al igual que con los espacios alrededor del asterisco, el orden de const es simplemente una cuestión de estilo. Este capítulo utiliza un prefijo const, porque por razones históricas parece ser más extenso, pero los dos son exactamente equivalentes. Los méritos de cada estilo se siguen debatiendo intensamente en Internet.

    Punteros y cadenas de caracteres

    Como se mencionó anteriormente, los strings de caracteres son tablas que contienen secuencias de caracteres terminadas por un carácter nulo. En las secciones anteriores, se utilizaron literales de cadena para ser insertados directamente en el coste, para inicializar cadenas y para inicializar tablas de caracteres.

    Pero también pueden consultarse directamente. Los literales de cadena son matrices del tipo apropiado de matriz que deben contener todos los caracteres más el carácter final nulo, siendo cada elemento del tipo const char (como literales, nunca pueden ser modificados. Por ejemplo:

    const char * foo = "hola";

    Esto declara una matriz con la representación literal de «hola», luego se asigna un puntero a su primer elemento a foo. Si imaginamos que «hello» se almacena en posiciones de memoria a partir de la dirección 1702, podemos representar la declaración anterior de la siguiente manera:

    cadenas de caracteres

    Nótese que aquí, foo es un puntero y contiene el valor 1702, no «h» o «hola», aunque 1702 es en realidad la dirección de estos dos elementos.

    El puntero de pie apunta a una secuencia de caracteres. Y como los punteros y matrices se comportan esencialmente igual en expresiones, foo puede usarse para acceder a los caracteres de la misma manera que las matrices de secuencias de caracteres con terminación nula. Por ejemplo:

    * (toto + 4)foo[4]

    Ambas expresiones tienen el valor’o’ (el quinto elemento de la tabla.

    Punteros con punteros

    C ++ le permite utilizar punteros que apuntan a punteros, que a su vez apuntan a datos (o incluso a otros punteros. La sintaxis simplemente requiere un asterisco (*) para cada nivel de indirección en la declaración del puntero:

    char a;char * b;char ** c;a ='z';b = & a;c = & b;

    Con el valor de cada variable representada en su correspondiente celda y sus respectivas direcciones de memoria representadas por el valor de abajo.

    La nueva característica en este ejemplo es la variable c, que es un puntero a un puntero, y puede ser utilizada en tres niveles de indirección diferentes, cada uno de los cuales corresponde a un valor diferente:

    • c es del tipo char ** y tiene un valor de 8092
    • * es del tipo de tanque * y tiene un valor de 7230
    • ** es del tipo de tanque y tiene un valor de’z’.

    Punteros vacíos (vacío)

    El tipo de puntero vacío es un tipo de puntero especial. En C++, void representa la ausencia de tipo. Por lo tanto, los punteros vacíos son punteros que apuntan a un valor que no tiene tipo (y por lo tanto también una longitud indeterminada y propiedades de referencia indeterminadas.

    Esto da gran flexibilidad a los punteros vacíos, permitiéndoles apuntar a cualquier tipo de datos, desde un valor entero o un número de punto flotante hasta una cadena de caracteres. Por otro lado, tienen una limitación importante: los datos que señalan no pueden ser directamente descodificados (lo cual es lógico, ya que no tenemos ningún tipo de descodificación), y por esta razón, cualquier dirección de un puntero vacío debe transformarse en otro tipo de puntero que apunte a un tipo de datos concreto antes de ser descodificado.

    Uno de sus posibles usos puede ser pasar de parámetros genéricos a una función. Por ejemplo:

    // multiplicador#incluye <iostream>usando namespace std; void increase (void * data, int psize){ if (psize == sizeof (char)) {char * pchar; pchar = (char *) data; ++ (* pchar); } otherwise if (psize == sizeof (int)) {int * pint; pinte = (int *) datos; ++ (* pinte); }}}int principal (){ char a ='x'; int b = 1602; aumento (& a, tamaño de (a)); aumento (& b, tamaño de (b)); coste <<<<<"," << b << < \ n'; retorno 0;}}.

    Resultado de la ejecución:

    sí, 1603

    sizeof es un operador integrado en el lenguaje C++ que devuelve el tamaño en bytes de su argumento. Para los tipos de datos no dinámicos, este valor es una constante. Por lo tanto, por ejemplo, sizeof (char) es igual a 1, porque char siempre tiene un tamaño de un byte.

    Punteros no válidos y punteros nulos

    En principio, los punteros apuntan a direcciones válidas, como la dirección de una variable o la dirección de un elemento en una matriz. Pero los punteros pueden apuntar a cualquier dirección, incluidas las direcciones que no hacen referencia a ningún elemento válido. Ejemplos típicos de esto son los punteros no inicializados y los punteros inexistentes en una matriz:

    int * p; // puntero no inicializado (variable local)int miarray[10];int * q = miarray + 20; // elemento fuera de rango

    Ni p ni q apuntan a direcciones que se sabe que contienen un valor, pero ninguna de las afirmaciones anteriores causa errores. En C++, los punteros pueden tomar cualquier valor de dirección, haya o no algo en esa dirección. Lo que puede causar un error es descodificar tal puntero (es decir, acceder al valor al que apuntan. Acceder a un puntero de este tipo resulta en un comportamiento indefinido, que va desde un error durante la ejecución hasta el acceso a un valor aleatorio.

    Pero a veces, sin embargo, un puntero debe indicar explícitamente en ninguna parte, no sólo una dirección inválida. En este caso, hay un valor especial que puede tomar cualquier tipo de puntero: el valor del puntero nulo. Este valor puede expresarse en C ++ de dos maneras: o bien con un número entero igual a cero, o bien con la palabra clave nullptr:

    int * p = 0;int * q = nulltr;

    Aquí, p y q son los dos punteros cero, lo que significa que ambos apuntan explícitamente a ninguna parte, y se comparan por igual: todos los punteros cero se comparan por igual con otros punteros cero. También es muy común ver que la constante NULL definida se usa en código antiguo para referirse al valor nulo del puntero:

    int * r = NULL;

    NULL se define en varias cabeceras de la biblioteca estándar y se define como un alias con un valor de puntero constante de cero (como 0 o nullptr.

    No confunda los punteros vacíos con punteros vacíos! Un puntero nulo es un valor que cualquier puntero puede tomar para indicar que apunta a «ninguna parte», mientras que un puntero vacío es un tipo de puntero que puede apuntar a algún lugar sin un tipo específico. Uno se refiere al valor almacenado en el puntero y el otro al tipo de datos apuntados.

    Indicadores de funciones

    C ++ permite operaciones con punteros a funciones. El uso típico de esto es transmitir una función como argumento a otra función. Los punteros a funciones se declaran con la misma sintaxis que una declaración de función estándar, excepto que el nombre de la función se coloca entre paréntesis () y se inserta un asterisco (*) antes del nombre:

    // puntero a las funciones#include <iostream>using namespace std;int addition (int a, int b){retorno (a + b); }subtraction int (int a, int b){retorno (a-b); }int operation (int x, int y, int (* functocall) (int, int int)){int g;g = (* functocall) (x, y);return (g);}en la mano (){int m, n;int (* minus) (* minus) (int, int) = resta;m = operación (7, 5, suma);n = operación (20, m, menos);coste <<< n;return 0;}.

    Resultado de la ejecución:

    8

    En el ejemplo anterior, el signo menos es un puntero a una función que tiene dos parámetros de tipo int y se inicializa directamente para apuntar a la función resta:

    int (* menos) (int, int) = resta;

    Otros consejos interesantes:

    1. Estructuras de datos en C++ Una estructura de datos es un grupo de elementos de datos agrupados bajo el mismo nombre. Estos elementos de datos, llamados miembros, pueden tener diferentes tipos y longitudes. Se pueden declarar las estructuras de datos…
    2. Memoria dinámica en C++ En los programas descritos en los capítulos anteriores, todos los requisitos de memoria se determinaron antes de ejecutar el programa mediante la definición de las variables necesarias. Pero puede suceder que las necesidades de memoria de un….
    3. Las variables en C # Una variable es sólo un nombre dado a un área de almacenamiento que nuestros programas pueden manipular. Cada variable en C # tiene un tipo específico, que determina el tamaño y la disposición de la memoria……
    4. C++ Ejercicios con soluciones Ejercicios corregidos en el lenguaje C++, descargue también la lista completa de ejercicios (lenguaje C, C++, tablas, punteros,…. Vea a continuación una serie de ejercicios para descargar. Ejercicios 1: Escribir un programa en C….
    5. Estilos en HTML Aprendamos a aplicar estilos a un elemento HTML, por ejemplo, asignar un color de fuente a un texto, color de fondo a una página o cambiar el tamaño de una imagen y muchas otras cosas….