Escribe el prototipo de una función que toma una matriz de exactamente 16 enteros

12 minutos de lectura

avatar de usuario
sriks

Una de las preguntas de la entrevista me pedía que “escribiera el prototipo de una función C que toma una matriz de exactamente 16 enteros” y me preguntaba qué podría ser. Tal vez una declaración de función como esta:

void foo(int a[], int len);

¿O algo mas?

¿Y si el lenguaje fuera C++ en su lugar?

avatar de usuario
jonathan leffler

En C, esto requiere un puntero a una matriz de 16 enteros:

void special_case(int (*array)[16]);

Se llamaría con:

int array[16];
special_case(&array);

En C++, también puede usar una referencia a una matriz, como se muestra en la respuesta de Nawaz. (La pregunta pide C en el título y originalmente solo mencionaba C++ en las etiquetas).


Cualquier versión que utilice alguna variante de:

void alternative(int array[16]);

termina siendo equivalente a:

void alternative(int *array);

que aceptará cualquier tamaño de matriz, en la práctica.


Se hace la pregunta – ¿ special_case() realmente evita que se pase un tamaño diferente de matriz. La respuesta es sí’.

void special_case(int (*array)[16]);

void anon(void)
{

    int array16[16];
    int array18[18];
    special_case(&array16);
    special_case(&array18);
}

El compilador (GCC 4.5.2 en MacOS X 10.6.6, por casualidad) se queja (advierte):

$ gcc -c xx.c
xx.c: In function ‘anon’:
xx.c:9:5: warning: passing argument 1 of ‘special_case’ from incompatible pointer type
xx.c:1:6: note: expected ‘int (*)[16]’ but argument is of type ‘int (*)[18]’
$

Cambie a GCC 4.2.1, según lo proporcionado por Apple, y la advertencia es:

$ /usr/bin/gcc -c xx.c
xx.c: In function ‘anon’:
xx.c:9: warning: passing argument 1 of ‘special_case’ from incompatible pointer type
$

La advertencia en 4.5.2 es mejor, pero la esencia es la misma.

  • ¿Evitaría realmente que el usuario hiciera una matriz?[17] o matriz[15]? cuando pienso qué es exactamente int [], en el fondo es un puntero int a una matriz no dinámica. Entonces, ¿el programa/compilador rechazaría automáticamente una longitud diferente, o la aceptaría e intentaría manejarla lo mejor que pudiera?

    – Jason Rogers

    17 de enero de 2011 a las 6:25

  • Esto es exactamente correcto, eliminé mi respuesta después de la prueba. Pensé que podría complicarme con otro uso para estático (AUFS) e ir con C99 void foo(int a[static 16]); pero me acabo de dar cuenta de que garantiza al menos 16 miembros no exactamente como lo quería el OP

    – SiegeX

    17 de enero de 2011 a las 6:42

  • Entonces, si paso una matriz que no es de tamaño 16, ¿no se compilará? digamos que tengo int intArray[20]; caso_especial(&intArray). ¿Eso compilará?

    – aalimian

    15/04/2014 a las 20:13

  • @armanali: depende de su compilador y de las opciones del compilador. Debería recibir una advertencia sobre la falta de coincidencia de tipos; es posible que no obtenga un error a menos que convierta todas las advertencias en errores. Como se muestra en la pregunta, GCC advierte pero compila el código a menos que incluya -Werror.

    –Jonathan Leffler

    15/04/2014 a las 20:16


  • ¿Es esta construcción void f(int (*array)[16]) válido con C90, o requiere C99 o más allá?

    – Cian

    29 de junio de 2015 a las 7:19

Hay varias formas de declarar parámetros de matriz de tamaño fijo:

void foo(int values[16]);

acepta cualquier puntero aintpero el tamaño de la matriz sirve como documentación

void foo(int (*values)[16]);

acepta un puntero a una matriz con exactamente 16 elementos

void foo(int values[static 16]);

acepta un puntero al primer elemento de una matriz con al menos 16 elementos

struct bar { int values[16]; };
void foo(struct bar bar);

acepta una estructura que encuadra una matriz con exactamente 16 elementos, pasándolos por valor.

  • En el void foo(int values[static 16]); caso, el estándar C no requiere que el compilador verifique que el argumento dado es correcto. Y, AFAIK, ningún compilador intenta siquiera hacer esto. Entonces, si bien es bueno en teoría, en el momento actual no le brinda ninguna seguridad: no se emitirá una advertencia si lo llama con una matriz más pequeña. Esperemos que los compiladores mejoren.

    –MM

    29 de junio de 2015 a las 7:11


  • En realidad, clang da una advertencia si pasa una matriz que es demasiado corta. No da ningún aviso si pasas &local_intsin embargo, a pesar de que esto significa que, en efecto, está dando una matriz de longitud 1.

    – Armín Rigo

    29 de junio de 2015 a las 8:53

avatar de usuario
Nawaz

& es necesario en C++:

void foo(int (&a)[16]); // & is necessary. (in C++)

Nota: & es necesario, de lo contrario, puede pasar una matriz de cualquier tamaño.


para C:

void foo(int (*a)[16]) //one way
{
}

typedef int (*IntArr16)[16]; //other way
void bar(IntArr16 a)
{
}

int main(void) 
{
        int a[16];
        foo(&a); //call like this - otherwise you'll get warning!
        bar(&a); //call like this - otherwise you'll get warning!
        return 0;
}

demostración: http://www.ideone.com/fWva6

  • Las referencias no son parte del lenguaje C.

    – Esteban Canon

    17 de enero de 2011 a las 6:20

  • @Stephen: tienes razón, pero aunque el título pide C, las etiquetas incluyen C++.

    –Jonathan Leffler

    17 de enero de 2011 a las 6:24

  • @Jonathan: eres un rito, pero cuando hablamos de conceptos como estos c o c ++ es lo mismo … ¿no es así … ahora agregué c ++ en Que

    – sriks

    18 de enero de 2011 a las 2:02


  • @Srikanth: No, para conceptos como este, C y C++ no son idénticos porque C++ tiene referencias y C no tiene referencias. Y la respuesta que usa referencias, por lo tanto, no se aplica a C. Son dos lenguajes distintos, aunque relacionados.

    –Jonathan Leffler

    18 de enero de 2011 a las 3:04

  • Haciendo una operación en a desde dentro foo() o bar() no es tan sencillo como si fuera un simple puntero de tabla, debido al nivel adicional de direccionamiento indirecto. no es posible hacer a[5]=1;pero el equivalente de trabajo parece ser a[0][5]=1;. No es un gran problema, pero aún así es un poco menos claro de leer. Más importante aún, no estoy seguro de entender cómo es posible hacer cumplir const propiedad. Parece que algo como esto no funcionaría: ` void foo(const int (*a)[16]) { (…) } barra vacía (int (*a)[16]) { foo(a); }` Los punteros se consideran de diferente tipo.

    – Cian

    29 de junio de 2015 a las 8:06

Creo que la forma más sencilla de tener seguridad de tipos sería declarar una estructura que contenga la matriz y pasar eso:

struct Array16 {
  int elt[16];
};


void Foo(struct Array16* matrix);

avatar de usuario
Justin Time – Reincorporar a Monica

Ya obtuvo algunas respuestas para C y una respuesta para C++, pero hay otra forma de hacerlo en C++.

Como dijo Nawaz, para pasar una matriz de tamaño N, puede hacer esto en C++:

const size_t N = 16; // For your question.

void foo(int (&arr)[N]) {
    // Do something with arr.
}

Sin embargo, a partir de C++ 11, también puede usar el contenedor std::array, que se puede pasar con una sintaxis más natural (suponiendo cierta familiaridad con la sintaxis de plantilla).

#include <array>

const size_t N = 16;

void bar(std::array<int, N> arr) {
    // Do something with arr.
}

Como contenedor, std::array permite prácticamente la misma funcionalidad que una matriz de estilo C normal, al tiempo que agrega funcionalidad adicional.

std::array<int, 5> arr1 = { 1, 2, 3, 4, 5 };
int arr2[5] = { 1, 2, 3, 4, 5 };

// Operator[]:
for (int i = 0; i < 5; i++) {
    assert(arr1[i] == arr2[i]);
}

// Fill:
arr1.fill(0);
for (int i = 0; i < 5; i++) {
    arr2[i] = 0;
}

// Check size:
size_t arr1Size = arr1.size();
size_t arr2Size = sizeof(arr2) / sizeof(arr2[0]);

// Foreach (C++11 syntax):
for (int &i : arr1) {
    // Use i.
}
for (int &i : arr2) {
    // Use i.
}

Sin embargo, que yo sepa (que es ciertamente limitado en ese momento), la aritmética de punteros no es segura con std::array a menos que use la función miembro data() para obtener primero la dirección real de la matriz. Esto es para evitar que futuras modificaciones a la clase std::array rompan su código y porque algunas implementaciones de STL pueden almacenar datos adicionales además de la matriz real.


Tenga en cuenta que esto sería más útil para el código nuevo, o si convierte su código preexistente para usar std::arrays en lugar de matrices de estilo C. Como std::arrays son tipos agregados, carecen de constructores personalizados y, por lo tanto, no puede cambiar directamente de una matriz de estilo C a std::array (sin usar un molde, pero eso es feo y puede causar problemas en el futuro ). Para convertirlos, necesitarías usar algo como esto:

#include <array>
#include <algorithm>

const size_t N = 16;

std::array<int, N> cArrayConverter(int (&arr)[N]) {
    std::array<int, N> ret;

    std::copy(std::begin(arr), std::end(arr), std::begin(ret));

    return ret;
}

Por lo tanto, si su código usa matrices de estilo C y no sería factible convertirlo para usar std::arrays en su lugar, sería mejor que se quedara con las matrices de estilo C.

(Nota: especifiqué tamaños como N para que pueda reutilizar el código más fácilmente donde lo necesite).


Editar: Hay algunas cosas que olvidé mencionar:

1) La mayoría de las funciones de la biblioteca estándar de C++ diseñadas para operar en contenedores son independientes de la implementación; en lugar de estar diseñados para contenedores específicos, operan en rangos, usando iteradores. (Esto también significa que trabajan para std::basic_string y sus instancias, tales como std::string.) Por ejemplo, std::copy tiene el siguiente prototipo:

template <class InputIterator, class OutputIterator>
OutputIterator copy(InputIterator first, InputIterator last,
                    OutputIterator result);
// first is the beginning of the first range.
// last is the end of the first range.
// result is the beginning of the second range.

Si bien esto puede parecer imponente, generalmente no necesita especificar los parámetros de la plantilla y puede dejar que el compilador lo maneje por usted.

std::array<int, 5> arr1 = { 1, 2, 3, 4, 5 };
std::array<int, 5> arr2 = { 6, 7, 8, 9, 0 };
std::string str1 = ".dlrow ,olleH";
std::string str2 = "Overwrite me!";

std::copy(arr1.begin(), arr1.end(), arr2.begin());
// arr2 now stores { 1, 2, 3, 4, 5 }.

std::copy(str1.begin(), str1.end(), str2.begin());
// str2 now stores ".dlrow ,olleH".
// Not really necessary for full string copying, due to std::string.operator=(), but possible nonetheless.

Debido a que se basan en iteradores, estas funciones también son compatibles con matrices de estilo C (ya que los iteradores son una generalización de punteros, todos los punteros son, por definición, iteradores (pero no todos los iteradores son necesariamente punteros)). Esto puede ser útil cuando se trabaja con código heredado, ya que significa que tiene acceso completo a las funciones de rango en la biblioteca estándar.

int arr1[5] = { 4, 3, 2, 1, 0 };
std::array<int, 5> arr2;

std::copy(std::begin(arr1), std::end(arr1), std::begin(arr2));

Es posible que haya notado en este ejemplo y en el último que std::array.begin() y std::begin() se puede usar indistintamente con std::array. Esto es porque std::begin() y std::end() se implementan de tal manera que para cualquier contenedor, tienen el mismo tipo de devolución y devuelven el mismo valor, como llamar al begin() y end() funciones miembro de una instancia de ese contenedor.

// Prototype:
template <class Container>
auto begin (Container& cont) -> decltype (cont.begin());

// Examples:
std::array<int, 5> arr;
std::vector<char> vec;

std::begin(arr) == arr.begin();
std::end(arr) == arr.end();

std::begin(vec) == vec.begin();
std::end(vec) == vec.end();

// And so on...

Las matrices de estilo C no tienen funciones miembro, lo que requiere el uso de std::begin() y std::end() para ellos. En este caso, las dos funciones se sobrecargan para proporcionar punteros aplicables, según el tipo de matriz.

// Prototype:
template <class T, size_t N>
T* begin (T(&arr)[N]);

// Examples:
int arr[5];

std::begin(arr) == &arr[0];
std::end(arr) == &arr[4];

Como regla general, si no está seguro de si un segmento de código en particular tendrá que usar matrices de estilo C, es más seguro usar std::begin() y std::end().

[Note that while I used std::copy() as an example, the use of ranges and iterators is very common in the standard library. Most, if not all, functions designed to operate on containers (or more specifically, any implementation of the Container concept, such as std::array, std::vector, and std::string) use ranges, making them compatible with any current and future containers, as well as with C-style arrays. There may be exceptions to this widespread compatibility that I’m not aware of, however.]

2) Al pasar un std::array por valor, puede haber una sobrecarga considerable, según el tamaño del arreglo. Como tal, generalmente es mejor pasarlo por referencia o usar iteradores (como la biblioteca estándar).

// Pass by reference.
const size_t N = 16;

void foo(std::array<int, N>& arr);

3) Todos estos ejemplos asumen que todas las matrices en su código tendrán el mismo tamaño, como lo especifica la constante N. Para hacer que su código sea más independiente de la implementación, puede usar rangos e iteradores usted mismo, o si desea mantener su código enfocado en matrices, use funciones con plantilla. [Building on this answer to another question.]

template<size_t SZ> void foo(std::array<int, SZ>& arr);

...

std::array<int, 5> arr1;
std::array<int, 10> arr2;

foo(arr1); // Calls foo<5>(arr1).
foo(arr2); // Calls foo<10>(arr2).

Si hace esto, puede incluso ir tan lejos como para crear una plantilla del tipo de miembro de la matriz, siempre que su código pueda operar en tipos distintos a int.

template<typename T, size_t SZ>
void foo(std::array<T, SZ>& arr);

...

std::array<int, 5> arr1;
std::array<float, 7> arr2;

foo(arr1); // Calls foo<int, 5>(arr1).
foo(arr2); // Calls foo<float, 7>(arr2).

Para ver un ejemplo de esto en acción, consulte aquí.


Si alguien ve algún error que pueda haber pasado por alto, no dude en señalarlo para que yo lo corrija o lo corrija usted mismo. Creo que los atrapé a todos, pero no estoy 100% seguro.

¿Ha sido útil esta solución?