La diferencia entre punteros y notaciones de arrays en C/C++

Ilja Kostenko 12 octubre 2023
  1. Array en C++
  2. Puntero de C++
La diferencia entre punteros y notaciones de arrays en C/C++

Los punteros y los arrays son, sin duda, uno de los aspectos más importantes y complejos de C++. Admiten listas vinculadas y asignación de memoria dinámica, y permiten que las funciones cambien el contenido de sus argumentos.

Array en C++

Un array es un conjunto de elementos del mismo tipo al que accede el índice: el número ordinal del elemento en el array. Por ejemplo:

int ival;

Define ival como una variable de tipo int y la instrucción.

int ia[10];

Establece un array de diez objetos int. Se puede acceder a cada uno de estos objetos, o elementos del array, mediante la operación de tomar un índice.

ival = ia[2];

Asigna a la variable ival el valor de un elemento del array ia con índice 2. De igual forma.

ia[7] = ival;

Asigna el valor ival al elemento con índice 7.

Una definición del array consta de un especificador de tipo, un nombre del array y un tamaño. El tamaño especifica el número de elementos de el array (al menos 1) y está entre corchetes. El tamaño de el array debe conocerse ya en la etapa de compilación y, por lo tanto, debe ser una expresión constante, aunque no necesariamente se establece mediante un literal.

La numeración de los elementos comienza desde 0, por lo que para un array de 10 elementos, el rango de índice correcto no es del 1 al 10, sino del 0 al 9. Aquí hay un ejemplo de clasificación de todos los elementos de el array.

int main() {
  const int array_size = 10;
  int ia[array_size];
  for (int ix = 0; ix < array_size; ++ix) ia[ix] = ix;
}

Al definir un array, puede inicializarla explícitamente enumerando los valores de sus elementos entre llaves, separados por comas.

const int array_size = 3;
int ia[array_size] = {0, 1, 2};

Si especificamos explícitamente una lista de valores, es posible que no especifiquemos el tamaño de el array: el compilador mismo contará la cantidad de elementos.

Puntero de C++

Un puntero es un objeto que contiene la dirección de otro objeto y permite la manipulación indirecta de este objeto. Los punteros generalmente se usan para trabajar con objetos creados dinámicamente, crear estructuras de datos relacionadas, como listas vinculadas y árboles jerárquicos, y pasar objetos grandes -array y objetos de clase- a funciones como parámetros.

Cada puntero está asociado con algún tipo de datos. Su representación interna no depende del tipo interno: tanto el tamaño de memoria ocupado por un objeto de tipo puntero como el rango de valores son los mismos. La diferencia es cómo el compilador percibe el objeto direccionable. Los punteros a diferentes tipos pueden tener el mismo valor, pero el área de memoria de los tipos correspondientes puede ser diferente.

Aquí hay unos ejemplos:

int *ip1, *ip2;
complex<double> *cp;
string *pstring;
vector<int> *pvec;
double *dp;

El puntero se indica con un asterisco antes del nombre. Al definir variables mediante una lista, se debe colocar un asterisco antes de cada puntero (ver arriba: ip1 e ip2). En el siguiente ejemplo, lp es un puntero a un objeto de tipo long y lp2 es un objeto de tipo long.

long *lp, lp2;

En el siguiente caso, fp se interpreta como un objeto flotante, y fp2 es un puntero a él:

float fp, *fp2;

Sea una variable de tipo int:

int ival = 1024;

Los siguientes son ejemplos de definición y uso de punteros a int pi y pi2.

[//] pi is initialized with the null address
int *pi = 0;
[//] pi2 is initialized with the address ival
int *pi2 = &ival;
[//] correct: pi and pi2 contain the ival address
pi = pi2;
[//] pi2 contains the null address
pi2 = 0;

A un puntero no se le puede asignar un valor que no sea una dirección.

[//] error: pi cannot take the value int
pi = ival

De manera similar, no puede asignar un valor a un puntero de un tipo que sea la dirección de un objeto de otro tipo si se definen las siguientes variables.

double dval;
double *ps = &dval;

Entonces, ambas expresiones de asignación dadas a continuación provocarán un error de compilación.

[//] compilation errors
[//] invalid assignment of data types: int* <== double*
pi = pd
pi = &dval;

No es que la variable pi no pueda contener las direcciones del objeto dval - las direcciones de objetos de diferentes tipos tienen la misma longitud. Estas operaciones de combinación de direcciones están deliberadamente prohibidas porque la interpretación de los objetos por parte del compilador depende del tipo de puntero.

Por supuesto, hay casos en los que nos interesa el valor de la dirección en sí, no el objeto al que apunta (digamos que queremos comparar esta dirección con alguna otra). Para resolver tales situaciones, podemos introducir un puntero no válido especial, que puede apuntar a cualquier tipo de datos, y las siguientes expresiones serán correctas:

[//] correct: void* can contain
[//] addresses of any type
void *pv = pi;
pv = pd;

Se desconoce el tipo de objeto al que apunta el void*, y no podemos manipular este objeto. Todo lo que podemos hacer con un puntero de este tipo es asignar su valor a otro puntero o compararlo con algún valor de dirección.

Para acceder a un objeto con su dirección, es necesario aplicar una operación de desreferenciación, o direccionamiento indirecto, indicado por un asterisco (*). Por ejemplo,

int ival = 1024;
, ival2 = 2048;
int *pi = &ival;

Podemos leer y almacenar el valor de ival aplicando la operación de desreferenciación al puntero pi.

[//] indirect assignment of the ival variable to the ival2 value
*pi = ival2;
[//] value indirect use of variable value and pH value value
*pi = abs(*pi); // ival = abs(ival);
*pi = *pi + 1; // ival = ival + 1;

Cuando aplicamos la operación de tomar una dirección (&) a un objeto de tipo int, obtenemos un resultado de tipo int*

int *pi = &ival;

Si la misma operación se aplica a un objeto de tipo int* (puntero a tipo int C) y obtenemos un puntero a un puntero a tipo int y, es decir, tipo int**. int** es la dirección de un objeto que contiene la dirección de un objeto del tipo int.

Desreferenciando ppi, obtenemos un objeto int* que contiene la dirección ival. Para obtener el objeto val en sí, la operación de desreferenciación debe aplicarse dos veces al PPI.

int **ppi = &pi;
int *pi2 = *ppi;
cout << "ival value\n"
     << "explicit value: " << ival << "\n"
     << "indirect addressing: " << *pi << "\n"
     << "double indirect addressing: " << **ppi << "\n"
     << end;

Los punteros se pueden utilizar en expresiones aritméticas. Preste atención al siguiente ejemplo, donde dos expresiones realizan acciones completamente diferentes.

int i, j, k;
int *pi = &i;
[//] i = i + 2
*pi = *pi + 2;
[//] increasing the address contained in pi by 2
pi = pi + 2;

Puede agregar un valor entero al puntero y también restarlo. Agregar 1 al puntero aumenta su valor por el tamaño del área de memoria asignada al objeto del tipo correspondiente. Si el tipo char ocupa 1 byte, int – 4 y double - 8, entonces agregar 2 a los punteros al carácter, entero y doble aumentará su valor en 2, 8 y 16, respectivamente.

¿Cómo se puede interpretar esto? Si los objetos del mismo tipo se ubican en la memoria uno tras otro, entonces aumentar el puntero en 1 hará que apunte al siguiente objeto. Por lo tanto, las operaciones aritméticas con punteros se usan con mayor frecuencia cuando se procesan matrices; en cualquier otro caso, difícilmente se justifican.

Aquí hay un ejemplo típico del uso de la aritmética de direcciones al iterar a través de elementos del array usando un iterador:

int ia[10];
int *iter = &ia[0];
int *iter_end = &ia[10];
while (iter != iter_end) {
  do_the event_ with_(*iter);

Artículo relacionado - C++ Pointer

Artículo relacionado - C++ Array