todas - tipos de funciones en c++
Escriba el prototipo para una funciĆ³n que toma una matriz de exactamente 16 enteros (5)
Creo que la forma más sencilla de estar seguro de tipos sería declarar una estructura que contenga la matriz y pasarla:
struct Array16 {
int elt[16];
};
void Foo(struct Array16* matrix);
Una de las preguntas de la entrevista me pidió que "escribiera el prototipo para una función de C que tome 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?
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 pregunta por C en el título, y originalmente solo mencionaba C ++ en las etiquetas).
Cualquier versión que use 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 special_case()
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, según ocurre) 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, tal como lo proporciona 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 sustancia es la misma.
Hay varias formas de declarar parámetros de matriz de tamaño fijo:
void foo(int values[16]);
acepta cualquier puntero a int
, pero el tamaño de 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 de boxeo una matriz con exactamente 16 elementos, pasándolos por valor.
Ya tienes 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, puedes 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 utilizar el contenedor std :: array, que se puede pasar con una sintaxis más natural (suponiendo cierta familiaridad con la sintaxis de la plantilla).
#include <array>
const size_t N = 16;
void bar(std::array<int, N> arr) {
// Do something with arr.
}
Como contenedor, std :: array permite en su mayoría la misma funcionalidad que una matriz de estilo C normal, mientras que también 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, por lo que sé (que es bastante limitado en ese momento), la aritmética de punteros no es segura con std :: array a menos que use los datos de la función miembro () para obtener primero la dirección de la matriz real. Esto es para evitar que futuras modificaciones a la clase std :: array no 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 nuevo código, o si convierte su código preexistente para usar arrays std :: en lugar de arrays de estilo C. Como std :: arrays son tipos agregados, carecen de constructores personalizados y, por lo tanto, no puede cambiar directamente de array de estilo C a std :: array (aparte de usar un cast, pero eso es feo y puede causar problemas en el futuro) ). Para convertirlos, deberí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 utiliza matrices de estilo C y no sería factible convertirlo para usar matrices std :: en su lugar, sería mejor que se quede con las matrices de estilo C.
(Nota: especifiqué los tamaños como N para que pueda reutilizar más fácilmente el código donde lo necesite).
Edit: 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 agnósticas en la implementación; En lugar de ser diseñados para contenedores específicos, operan en rangos, utilizando iteradores. (Esto también significa que funcionan para std::basic_string
y sus instancias, 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 es necesario que especifique los parámetros de la plantilla, y puede dejar que el compilador se encargue de eso 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 dependen de los iteradores, estas funciones también son compatibles con matrices de estilo C (ya que los iteradores son una generalización de los punteros, todos los punteros son iteradores de definición (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 pueden usar indistintamente con std::array
. Esto se debe a que std::begin()
y std::end()
se implementan de tal manera que para cualquier contenedor, tienen el mismo tipo de retorno y devuelven el mismo valor, al llamar a las funciones miembro begin()
y end()
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...
Los arreglos 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 están sobrecargadas 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 algún segmento de código en particular tendrá que usar matrices de estilo C, es más seguro usar std::begin()
y std::end()
.
[Tenga en cuenta que si bien usé std::copy()
como ejemplo, el uso de rangos e iteradores es muy común en la biblioteca estándar. La mayoría, si no todas, las funciones diseñadas para operar en contenedores (o más específicamente, cualquier implementación del concepto de Container , como std::array
, std::vector
, y std::string
) usan rangos, haciéndolos compatibles con cualquier Contenedores actuales y futuros, así como con matrices de estilo C. Sin embargo, puede que haya excepciones a esta compatibilidad generalizada de las que no tengo conocimiento.]
2) Cuando se pasa un std :: array por valor, puede haber una sobrecarga considerable, dependiendo del tamaño del array. 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 serán del 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 centrado en matrices, use funciones de plantilla. [Basándose en esta respuesta a otra pregunta .]
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 está haciendo esto, incluso puede ir tan lejos como para la 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, vea here .
Si alguien ve algún error que pueda haber omitido, siéntase libre de señalarlo para que lo arregle, o de arreglarlo usted mismo. Creo que los atrapé a todos, pero no estoy 100% seguro.
& es necesario en C ++:
void foo(int (&a)[16]); // & is necessary. (in C++)
Nota: y 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