punteros lenguaje funciones estructuras estructura entre ejemplo diferencia dev con arreglo anidadas c++ arrays data-structures parameters

c++ - lenguaje - punteros en c



Pasar una matriz de estructuras para funcionar c++ (5)

Perdón por la pregunta de novato, estoy un poco confundido.
Si tengo una matriz de estructuras en main que quiero pasar a una función:

struct MyStruct{ int a; int b; char c; mayarray[5]; }; MyStruct StructArray[10]; myFunction(StructArray[])

Pase a esto a una función:

void myFunction(struct MyStruct PassedStruct[]) { PassedStruct[0].a = 1; PassedStruct[0].b = 2; // ... etc }

Mi pregunta es: ¿llamar a la función de esta manera modificará los datos en StructArray ? Lo necesito para. ¿Sería eso llamar por referencia? Estoy un poco confundido. ¿Cómo lo cambiaría para que cuando pase la matriz de estructuras a la función, la función modifique la matriz StructArray ? Estoy usando visual studio por cierto.
Gracias.


Aunque las matrices y punteros son conceptualmente cosas diferentes, las aguas están muy oscurecidas por los parámetros de función. No puede pasar directamente una matriz a una función; solo puedes pasar un puntero. Como resultado, el lenguaje "útilmente" convierte un prototipo como este:

void foo (char arr[])

dentro de esto:

void foo (char *arr)

Y cuando llama a la función, no le pasa una matriz completa, pasa un puntero al primer elemento. Como resultado, dentro de la función foo , tendrá un puntero a la matriz original , y la asignación a los elementos de arr también cambiará la matriz en la persona que llama.

En todas las demás situaciones fuera de los parámetros de la función, la matriz y la sintaxis del puntero en las declaraciones no son equivalentes, y se refieren a aspectos conceptualmente diferentes. Pero dentro de las listas de parámetros de función, la sintaxis de matriz crea tipos de puntero.


Creo que x [] significa lo mismo que x * que significa "puntero al tipo". Como este es el caso, modificará el objeto que pase (deberá llamarlo usando el operador &, o ''dirección de''), y puede considerarlo como una referencia.


Cuando pasa una matriz como argumento a una función, la matriz se desintegra a un puntero al primer elemento de la matriz.

Entonces, cuando dentro de su función usa [] para acceder a los elementos de la matriz, realmente solo está haciendo aritmética de puntero con su puntero inicial para obtener los elementos de la matriz ORIGINAL.

Entonces, sí, estás modificando la matriz original. Y esta respuesta es bastante independiente de qué compilador está utilizando (aunque es una buena práctica, en mi humilde opinión, indicar el compilador en la pregunta como lo hizo)


Podría usar un std :: vector en su lugar. Las matrices son construcciones en C, C ++ tiene clases de envoltura para evitar exactamente este tipo de ambigüedades


struct MyStruct PassedStruct[]

es principalmente una sintaxis alternativa para:

struct MyStruct * PassedStruct

Entonces sí, accederá y modificará la estructura original.

Solo un detalle para cambiar, la llamada correcta a la función no es

myFunction(StructArray[]);

pero:

myFunction(StructArray);

Ahora intentaré explicar por qué utilicé la palabra principalmente en la oración anterior:

Daré alguna pista sobre la diferencia entre matrices y punteros, por qué no debes confundirlos (incluso si no diría que no están relacionados, todo lo contrario), y el problema con el MyStruct PassedStruct[] anterior de paso de MyStruct PassedStruct[] .

Esto no es para principiantes, y los expertos estándar de C ++ también deben evitar leer esto (porque no quiero entrar en una guerra ISO Standard_ ya que entro territorio de comportamiento indefinido de ISO, también conocido como territorio prohibido).

Comencemos con las matrices:

Imagine una estructura simple:

struct MyStruct{ int a; int b; char c; };

MyStruct a1[3]; es la declaración de una matriz cuyos elementos son del tipo de estructura anterior. Lo más importante que hace el compilador al definir una matriz es asignarle espacio. En nuestro ejemplo, reservó espacio para 3 estructuras. Este espacio reservado puede estar en la pila o en recursos de la memoria global, dependiendo de dónde se encuentre la declaración.

También puedes inicializar la estructura cuando la declaras como en:

struct MyStruct a1[3] = {{1, 2}, {3, 4}, {5, 6}};

Tenga en cuenta que en este ejemplo, no inicialicé el campo c , sino solo a y b . Esto está permitido. También podría usar la sintaxis del designador si mi compilador lo admite como en:

struct MyStruct a1[3] = {{a:1, b:2}, {a:3, b:4}, {a:5, b:6}};

Ahora, hay otra sintaxis para definir una matriz usando conjuntos cuadrados vacíos como en:

struct MyStruct a2[] = {{1, 2}, {3, 4}, {5, 6}};

El punto aquí es que a2 es una matriz perfectamente normal como a1 . El tamaño de la matriz no está implícito, se da a través del inicializador: tengo tres inicializadores, por lo tanto, obtengo una matriz de tres estructuras.

Podría definir una matriz no inicializada de tamaño conocido con esta sintaxis. Para una matriz no inicializada de tamaño 3 tendría:

struct MyStruct a2[] = {{},{},{}};

El espacio se asigna, exactamente como con la sintaxis anterior, sin puntero involucrado aquí.

Vamos a introducir un puntero:

MyStruct * p1;

Este es un simple puntero a una estructura de tipo MyStruct. Puedo acceder a los campos a través de la sintaxis del puntero habitual p1->a (*p1).a . También hay una matriz de sintaxis con sabor para hacer lo mismo que arriba p1[0].a . Todavía lo mismo que arriba. Solo tienes que recordar que p1 [0] es una abreviatura de (*(p1+0)) .

También recuerde la regla para la aritmética del puntero: agregar 1 a un puntero significa agregar el sizeof del objeto puntiagudo a la dirección de memoria subyacente (lo que obtiene cuando usa el parámetro de formato% p printf). La aritmética del puntero permite el acceso a sucesivas estructuras idénticas. Eso significa que puede acceder a las estructuras por índice con p1[0] , p1[2] , etc.

Los límites no están controlados. Lo que se señala en la memoria es la responsabilidad del programador. Sí, sé que ISO dice de manera diferente, pero eso es lo que todos los compiladores que he intentado hacer, así que si conoces uno que no lo haga, por favor dímelo.

Para hacer algo útil con p1, debes hacer que apunte a alguna estructura de tipo MyStruct . Si tiene una matriz de tales estructuras disponibles como nuestra a1 , puede hacer p1=a1 y p1 apuntará al comienzo de la matriz. En otras palabras, también podría haber hecho p1=&a1[0] . Es natural tener una sintaxis simple disponible, ya que es exactamente para lo que está diseñada la aritmética de punteros: acceder a matrices de objetos similares.

La belleza de esa convención es que permite unificar completamente la sintaxis de acceso a punteros y arreglos. La diferencia solo la ve el compilador:

  • cuando ve p1[0] , sabe que tiene que buscar el contenido de una variable cuyo nombre es p1 y que contendrá la dirección de alguna estructura de memoria.

  • cuando ve a1[0] , sabe que a1 es una constante que debe entenderse como una dirección (no algo que recuperar en la memoria).

Pero una vez que la dirección de p1 o a1 está disponible, el tratamiento es idéntico.

Un error común es escribir p1 = &a1 . Si lo hace, el compilador le dará unas palabras de cuatro letras. Ok, &a1 también es un puntero, pero lo que obtienes al tomar la dirección de a1 es un puntero a toda la matriz. Eso significa que si agrega 1 a un puntero de este tipo, la dirección real se moverá por pasos de 3 estructuras a la vez.

El tipo real de un puntero de ese tipo (llamémoslo p2 ) sería MyStruct (*p2)[3]; . Ahora puedes escribir p2 = &a1 . Si quiere acceder a la primera struct MyStruct al comienzo del bloque de memoria apuntado por p2 , tendrá que escribir MyStruct como p2[0][0].a o (*p2)[0].a o (*(*p2)).a o (*p2)->a o p2[0]->a .

Gracias al sistema de tipos y a la aritmética de punteros, todos hacen exactamente lo mismo: recuperar la dirección contenida en p2, usar esa dirección como una matriz (una dirección constante conocida) como se explicó anteriormente.

Ahora puede entender por qué los punteros y matrices son tipos totalmente diferentes que no deben confundirse, como algunos pueden decir. En palabras simples, los punteros son variables que contienen una dirección, las matrices son direcciones constantes. Por favor, no me disparen C ++ Gurus, sí, sé que no es la historia completa y que los compiladores guardan muchas otras informaciones junto con la dirección, el tamaño del objeto apuntado (¿direccionado?) Por ejemplo.

Ahora podría preguntarse por qué en el contexto de paso de parámetros puede usar corchetes vacíos y realmente significa puntero . ? Ni idea. Alguien probablemente pensó que se veía bien.

Por cierto, al menos con gcc, también puede poner algún valor entre corchetes en lugar de mantenerlos vacíos. No hará la diferencia, igual obtendrás un puntero, no una matriz, y los límites o la verificación de tipos no se realizarán. No revisé el estándar ISO que se debe hacer y si es requerido por el estándar o si es un comportamiento específico.

Si desea que el tipo revise los límites, simplemente use una referencia. Eso puede ser sorprendente, pero este es un área donde si utiliza una referencia, el tipo real del parámetro cambia de puntero a matriz (y no de puntero a referencia al puntero como se puede esperar).

MyStruct StructArray[10];

  • encabezado: void myFunction(struct MyStruct * PassedStruct)
  • llamador: myFunction(StructArray)
  • estado: funciona, trabajas con un puntero en PassedStruct
  • encabezado: void myFunction(struct MyStruct PassedStruct[])
  • llamador: myFunction(StructArray)
  • estado: funciona, trabajas con un puntero en PassedStruct
  • encabezado: void myFunction(struct MyStruct (& PassedStruct)[10])
  • llamador: myFunction(StructArray)
  • estado: funciona, trabajas con una referencia a una matriz de tamaño 10
  • encabezado: void myFunction(struct MyStruct (& PassedStruct)[11])
  • llamador: myFunction(StructArray)
  • estado: no compila, tipo de desajuste de matriz entre prototipo y parámetro real
  • encabezado: void myFunction(struct MyStruct PassedStruct[10])
  • llamador: myFunction(StructArray)
  • estado: funciona, PassedStruct es un puntero, el tamaño proporcionado se ignora
  • encabezado: void myFunction(struct MyStruct PassedStruct[11])
  • llamador: myFunction(StructArray)
  • estado: funciona, PassedStruct es un puntero, el tamaño proporcionado se ignora