Entiendo que no se admite la asignación de matrices por miembros, por lo que lo siguiente no funcionará:
int num1[3] = {1,2,3};
int num2[3];
num2 = num1; // "error: invalid array assignment"
Simplemente acepté esto como un hecho, pensando que el objetivo del lenguaje es proporcionar un marco de trabajo abierto y dejar que el usuario decida cómo implementar algo como la copia de una matriz.
Sin embargo, lo siguiente funciona:
struct myStruct { int num[3]; };
struct myStruct struct1 = {{1,2,3}};
struct myStruct struct2;
struct2 = struct1;
La matriz num[3]
se asigna por miembros desde su instancia enstruct1
, a su instancia en struct2
.
¿Por qué se admite la asignación de matrices por miembros para estructuras, pero no en general?
editar : Comentario de Roger Pate en el hilo std :: string in struct - ¿Problemas de copia / asignación? parece apuntar en la dirección general de la respuesta, pero no sé lo suficiente para confirmarlo yo mismo.
edición 2 : Muchas respuestas excelentes. Elegí el de Luther Blissett porque principalmente me preguntaba acerca de la razón filosófica o histórica detrás del comportamiento, pero la referencia de James McNellis a la documentación de especificaciones relacionada también fue útil.
memcpy()
o similar.boost::array
( boost.org/doc/libs/release/doc/html/array.html ) y ahorastd::array
( en.cppreference.com/w/cpp/container/array ) son alternativas compatibles con STL al matrices C antiguas desordenadas. Admiten la asignación de copias.Respuestas:
Esta es mi opinión al respecto:
El desarrollo del lenguaje C ofrece una idea de la evolución del tipo de matriz en C:
Intentaré delinear lo de la matriz:
Los precursores B y BCPL de C no tenían un tipo de matriz distinto, una declaración como:
auto V[10] (B) or let V = vec 10 (BCPL)
declararía que V es un puntero (sin tipo) que se inicializa para apuntar a una región no utilizada de 10 "palabras" de memoria. B ya se usaba
*
para desreferenciar punteros y tenía la[]
notación abreviada, es*(V+i)
decirV[i]
, al igual que en C / C ++ hoy. Sin embargo,V
no es una matriz, sigue siendo un puntero que tiene que apuntar a algo de memoria. Esto causó problemas cuando Dennis Ritchie intentó extender B con tipos de estructura. Quería que las matrices fueran parte de las estructuras, como en C hoy:struct { int inumber; char name[14]; };
Pero con el concepto B, BCPL de matrices como punteros, esto habría requerido que el
name
campo contuviera un puntero que tenía que inicializarse en tiempo de ejecución a una región de memoria de 14 bytes dentro de la estructura. El problema de inicialización / diseño finalmente se resolvió dándole a las matrices un tratamiento especial: el compilador rastrearía la ubicación de las matrices en estructuras, en la pila, etc.sin requerir realmente que el puntero a los datos se materialice, excepto en expresiones que involucren las matrices. Este tratamiento permitió que casi todo el código B aún se ejecutara y es la fuente de la regla "las matrices se convierten en puntero si las observa" . Es un truco de compatibilidad, que resultó ser muy útil, porque permitía matrices de tamaño abierto, etc.Y aquí está mi suposición de por qué no se puede asignar una matriz: dado que las matrices eran punteros en B, simplemente podría escribir:
auto V[10]; V=V+5;
para reajustar una "matriz". Esto ahora no tenía sentido, porque la base de una variable de matriz ya no era un lvalue. Así que esta asignación fue rechazada, lo que ayudó a capturar los pocos programas que hicieron este rebase en matrices declaradas.. Y luego esta noción se atascó: como las matrices nunca fueron diseñadas para ser citadas de primera clase del sistema de tipo C, en su mayoría fueron tratadas como bestias especiales que se convierten en punteros si las usa. Y desde cierto punto de vista (que ignora que las matrices C son un truco fallido), rechazar la asignación de matrices todavía tiene algún sentido: una matriz abierta o un parámetro de función de matriz se trata como un puntero sin información de tamaño. El compilador no tiene la información para generar una asignación de matriz para ellos y la asignación de puntero fue requerida por razones de compatibilidad.
/* Example how array assignment void make things even weirder in C/C++, if we don't want to break existing code. It's actually better to leave things as they are... */ typedef int vec[3]; void f(vec a, vec b) { vec x,y; a=b; // pointer assignment x=y; // NEW! element-wise assignment a=x; // pointer assignment x=a; // NEW! element-wise assignment }
Esto no cambió cuando una revisión de C en 1978 agregó la asignación de estructuras ( http://cm.bell-labs.com/cm/cs/who/dmr/cchanges.pdf ). Aunque los registros fueron tipos distintos en C, no era posible asignarlos en K&R C. Tenías que copiarlos por miembros con memcpy y solo podías pasarles punteros como parámetros de función. La asignación (y el paso de parámetros) ahora se definió simplemente como la memoria en bruto de la estructura de la estructura y, dado que esto no podía romper el código existente, se adaptó fácilmente. Como efecto secundario involuntario, esto introdujo implícitamente algún tipo de asignación de matriz, pero esto sucedió en algún lugar dentro de una estructura, por lo que esto realmente no podría presentar problemas con la forma en que se usaban las matrices.
fuente
int[10] c;
para hacer que lvalue sec
comporte como una matriz de diez elementos, en lugar de como un puntero al primer elemento de una matriz de diez elementos. Hay algunas situaciones en las que es útil poder crear una typedef que asigna espacio cuando se usa para una variable, pero pasa un puntero cuando se usa como un argumento de función, pero la incapacidad de tener un valor de tipo de matriz es una debilidad semántica significativa en el idioma.Con respecto a los operadores de asignación, el estándar C ++ dice lo siguiente (C ++ 03 §5.17 / 1):
Una matriz no es un lvalor modificable.
Sin embargo, la asignación a un objeto de tipo de clase se define especialmente (§5.17 / 4):
Entonces, miramos para ver qué hace el operador de asignación de copia declarado implícitamente para una clase (§12.8 / 13):
Entonces, para un objeto de tipo de clase, las matrices se copian correctamente. Tenga en cuenta que si proporciona un operador de asignación de copia declarado por el usuario, no puede aprovechar esto y tendrá que copiar la matriz elemento por elemento.
El razonamiento es similar en C (C99 §6.5.16 / 2):
Y §6.3.2.1 / 1:
En C, la asignación es mucho más simple que en C ++ (§6.5.16.1 / 2):
Para la asignación de objetos de tipo estructura, los operandos izquierdo y derecho deben tener el mismo tipo, por lo que el valor del operando derecho simplemente se copia en el operando izquierdo.
fuente
=
requiere un valor r en el RHS y una matriz no puede ser un valor r . La conversión de lvalue a rvalue está prohibida para matrices, reemplazada por lvalue-to-pointer.static_cast
no es mejor para hacer un rvalue porque está definido en los mismos términos.En este enlace: http://www2.research.att.com/~bs/bs_faq2.html hay una sección sobre asignación de matrices:
Los dos problemas fundamentales con las matrices son que
Y creo que esta es la diferencia fundamental entre matrices y estructuras. Una variable de matriz es un elemento de datos de bajo nivel con un autoconocimiento limitado. Básicamente, es un trozo de memoria y una forma de indexarlo.
Por tanto, el compilador no puede diferenciar entre int a [10] e int b [20].
Las estructuras, sin embargo, no tienen la misma ambigüedad.
fuente
sizeof(a)
vs.sizeof(b)
o pasara
avoid f(int (&)[20]);
.Lo sé, todos los que respondieron son expertos en C / C ++. Pero pensé, esta es la razón principal.
num2 = num1;
Aquí está intentando cambiar la dirección base de la matriz, lo cual no está permitido.
y por supuesto, struct2 = struct1;
Aquí, el objeto struct1 se asigna a otro objeto.
fuente
num2 = num1
se comportarían perfectamente. Los elementos denum2
tendrían el mismo valor que el elemento correspondiente denum1
.Otra razón por la que no se hicieron más esfuerzos para reforzar las matrices en C es probablemente que la asignación de matrices no sería tan útil. Aunque se puede lograr fácilmente en C envolviéndolo en una estructura (y la dirección de la estructura se puede convertir simplemente a la dirección de la matriz o incluso a la dirección del primer elemento de la matriz para su procesamiento posterior), esta característica rara vez se usa. Una razón es que las matrices de diferentes tamaños son incompatibles, lo que limita los beneficios de la asignación o, relacionado, el paso a funciones por valor.
La mayoría de las funciones con parámetros de matriz en lenguajes donde las matrices son tipos de primera clase se escriben para matrices de tamaño arbitrario. Luego, la función generalmente itera sobre el número dado de elementos, una información que proporciona la matriz. (En C, el modismo es, por supuesto, pasar un puntero y un recuento de elementos separados.) Una función que acepta una matriz de un solo tamaño específico no es necesaria con tanta frecuencia, por lo que no se pierde mucho. (Esto cambia cuando puede dejar que el compilador genere una función separada para cualquier tamaño de matriz que ocurra, como con las plantillas de C ++; esta es la razón por la que
std::array
es útil).fuente