Arrays em funções
Quando sua função main está enorme com inúmeras linhas de código e você tem a brilhante ideia de separar em funções menores, infelizmente essa árdua tarefa pode ter um comportamento inesperado ao passar um array para uma função.
Por exemplo, o código a seguir funciona perfeitamente na main:
int main() {
int array[] = {5, 1, 2, 4, 3};
std::size_t length = sizeof(array) / sizeof(array[0]);
for (int i = 0; i < length; i++) {
std::println("{}° elemento: {}", i+1, array[i]);
}
return 0;
}
/*
resultado:
1° elemento: 5
2° elemento: 1
3° elemento: 2
4° elemento: 4
5° elemento: 3 */
Mas se você abstrair para um função print_array o resultado pode ser inesperado ao passar seu array como parametro da função:
void print_array(int array[]) {
std::size_t length = sizeof(array) / sizeof(array[0]);
for (int i = 0; i < length; i++) {
std::println("{}° elemento: {}", i+1, array[i]);
}
}
int main() {
int array[] = {5, 1, 2, 4, 3};
print_array(array);
return 0;
}
/*
resultado:
1° elemento: 5
2° elemento: 1 */
Bem… Por que isso acontece?
Vamos comparar os tamanhos nas funções main e print_array:
void print_array(int array[]) {
std::println("\nPRINT_ARRAY");
std::println("tamanho da array: {} bytes", sizeof(array));
std::println("tamanho do elemento: {} bytes", sizeof(array[0]));
return;
//
}
int main() {
int array[] = {5, 1, 2, 4, 3};
std::println("MAIN");
std::println("tamanho da array: {} bytes", sizeof(array));
std::println("tamanho do elemento: {} bytes", sizeof(array[0]));
print_array(array);
return 0;
}
/*
resultado:
MAIN
tamanho da array: 20 bytes
tamanho do elemento: 4 bytes
PRINT_ARRAY
tamanho da array: 8 bytes
tamanho do elemento: 4 bytes */
Ao passar meu array para uma função, o array decai para um ponteiro.
Ou seja, ambas as assinaturas são equivalentes:
void print_array(int array[])
void print_array(int* array)
Na minha máquina, 64 bits, um ponteiro tem 8 bytes, por isso ainda mostra 2 elementos da array.
Em uma máquina de 32 bits talvez mostrasse menos elementos.
Esse comportamento é inconsistente, se meu array tiver 2 elementos eu vou achar que o código está correto, quando não está.
Algumas maneiras de passar arrays em c++
Passe o tamanho para a função
Passe a array em si e o tamanho que ela tem para sua função. Por exemplo:
void print_array(int array[], int size) {
for (int i = 0; i < size; i++) {
std::println("{}° elemento: {}", i+1, array[i]);
}
}
int main() {
int array[] = {5, 1, 2, 4, 3};
int size = sizeof(array) / sizeof(array[0]);
print_array(array, size);
return 0;
}
/*
resultado:
1° elemento: 5
2° elemento: 1
3° elemento: 2
4° elemento: 4
5° elemento: 3 */
Referências
Referências não decaem para ponteiros (embora a sintaxe seja um pouco estranha a principio: type (&name)[size]
), então é possível receber uma referência de um array como abaixo:
void print_array(int (&array)[5]) {
for (int i = 0; i < 5; i++) {
std::println("{}° elemento: {}", i+1, array[i]);
}
}
int main() {
int array[] = {5, 1, 2, 4, 3};
print_array(array);
return 0;
}
/*
resultado:
1° elemento: 5
... */
Infelizmente nessa abordagem é preciso saber o tipo e tamanho do array que a função deve receber e isso nem sempre é prático.
Templates e referências
É aí que entram Templates e Referências, ferramentas poderosas que consegue resolver alguns dos “problemas” citados acima. Templates permitem que o compilador deduza o tipo do parâmetro e o tamanho recebido.
template<typename T, std::size_t N>
void print_array(T (&array)[N]) {
for (int i = 0; i < N; i++) {
std::println("{}° elemento: {}", i+1, array[i]);
}
}
int main() {
int array[] = {5, 1, 2, 4, 3};
print_array(array);
return 0;
}
/*
resultado:
1° elemento: 5
...*/
E por ser uma referência, não decai para ponteiros e mantêm a informação do tamanho ao usar sizeof
template<typename T, std::size_t N>
void print_array(T (&array)[N]) {
std::println("\nPRINT_ARRAY");
std::println("tamanho da array: {} bytes", sizeof(array));
std::println("tamanho do elemento: {} bytes", sizeof(array[0]));
...
}
/*
resultado:
PRINT_ARRAY
tamanho da array: 20 bytes // mesmo que na main!
tamanho do elemento: 4 bytes
1° elemento: 5
... */
Outra vantagem dessa abordagem é que a função é genérica o suficiente, aceita diversos tipos e tamanhos diferentes, sem precisar alterar seu código.
Por exemplo, agora passarei um int[5]
e double[3]
normalmente:
template<typename T, std::size_t N>
void print_array(T (&array)[N]) {
for (int i = 0; i < N; i++) {
std::println("{}° elemento: {}", i+1, array[i]);
}
}
int main() {
int array[] = {5, 1, 2, 4, 3};
double double_array[] = {3.14, 1.6, 1.33};
print_array(array);
print_array(double_array);
return 0;
}
/*
1° elemento: 5
2° elemento: 1
3° elemento: 2
4° elemento: 4
5° elemento: 3
1° elemento: 3.14
2° elemento: 1.6
3° elemento: 1.33*/
C++ tem std::array e std::vector!!
Em C++ moderno é preferível usar std::array se você não precisar alocar dinamicamente o array ou std::vector se o tamanho precisar ser dinâmico. Entretanto isso é assunto para outra postagem :)