Já usaste e escreveste funções C++ em alguns conceitos. Está na hora de entrar na parte técnica. O excerto de código abaixo mostra os termos mais comuns, para consulta rápida. Como o C++ ignora os espaços em branco, a formatação foi alterada para colocar cada elemento numa única linha.
// Function declaration:
bool // Return type
admin_detected(string user, string password) // Type signature
; // Don't forget the ';' for the declaration
// Function definition:
bool // Return type
admin_detected // Function name
(string user, string password) // Parameter list
{ return user == "admin" && password == "1234"; } // Function body
A declaração funciona como uma nota para o compilador, a dizer que existe uma função com esse nome, esse tipo de retorno e essa lista de parâmetros. O código não vai funcionar se faltar a definição. As declarações são opcionais; são necessárias se usares a função antes da sua definição. As declarações podem resolver problemas como referências cíclicas e podem ser usadas para separar a interface da implementação.
const
Por vezes queres garantir que os valores não podem ser alterados depois de terem sido inicializados.
O C++ usa a palavra-chave const como qualificador para constantes.
const int number_of_dragon_balls{7};
number_of_dragon_balls--; // compilation error
Vais ver muitas vezes constantes escritas em UPPER_SNAKE_CASE. Recomenda-se reservar esta convenção de escrita para macros, caso não exista outra.
Se tentares alterar uma variável constante depois de esta ter sido definida, o teu código não vai compilar. Isto ajuda a evitar alterações involuntárias, mas também abre possibilidades de otimização para o compilador. Enquanto pessoa, também é mais fácil raciocinar sobre o código quando sabes que certas partes não vão ser afetadas.
Também podes usar const como qualificador nos parâmetros de uma função.
string guess_number(const int& secret, const int& guess) {
if (secret < guess) return "lower.";
if (secret > guess) return "higher.";
return "exact!";
}
Quando passas uma referência const à função, podes ter a certeza de que ela vai ficar inalterada.
Vais ver muitas vezes referências const para objetos que podem ser caros de copiar, como strings mais longas.
Um terceiro caso de uso do qualificador const são as funções membro que não alteram a instância de uma classe.
class Stubborn {
public:
Stubborn(string reply) {
response = reply;
}
string answer(const string& question) const {
if (question.length() == 0) { return ""; }
return response;
}
private:
string response{};
};
A função membro answer da classe Stubborn usa uma referência const string& como parâmetro.
Isto evita uma operação de cópia do objeto original que foi passado à função.
Várias funções podem ter o mesmo nome se a lista de parâmetros for diferente. A isso chama-se sobrecarga de funções e faz-se normalmente quando essas funções desempenham tarefas muito semelhantes.
O cabeçalho da função sem o tipo de retorno é a assinatura de tipo da função. Uma alteração na assinatura de tipo dá origem a uma nova função.
O exemplo play_sound tem seis sobrecargas diferentes para acomodar cenários distintos:
// different argument types:
void play_sound(char note); // C, D, E, ..., B
void play_sound(string solfege); // do, re, mi, ..., ti
void play_sound(int jianpu); // 1, 2, 3, ..., 7
// different number of arguments:
void play_sound(string solfege, double duration);
// different qualifiers:
void play_sound(vector<string>& solfege);
void play_sound(const vector<string>& solfege);
A assinatura de tipo é definida pelo nome da função, pelo número de parâmetros, pelos seus tipos e pelos seus qualificadores (mas não pelos seus nomes). O tipo de retorno explicitamente não faz parte da assinatura de tipo e vais ter erros de compilação se tiveres duas funções que só diferem no tipo de retorno. O compilador vai queixar-se porque não é claro qual das duas deve ser usada.
Algumas funções podem ficar muito extensas e muitas das suas chamadas podem usar os mesmos valores para a maioria dos parâmetros. A repetição nessas chamadas pode ser evitada com argumentos por omissão.
void record_new_horse_birth(string name, int weight, string color="brown-ish", string dam="Alruccaba", string sire="Poseidon");
record_new_horse_birth("Urban Sea", 130); // color will be brown, dam "Alruccabam", sire "Poseidon"
record_new_horse_birth("Highclere", 175, "off-white", "Fall Aspen"); // sire will be "Poseidon"
Como a declaração da função é muitas vezes lida antes da definição, é o melhor local para definir os argumentos por omissão. Se um parâmetro tiver uma declaração por omissão, todos os parâmetros à sua direita também precisam de uma declaração por omissão. Por vezes, sobrecargas de funções complicadas podem ser refatoradas em menos funções com argumentos por omissão, para melhorar a manutenção.
Neste exercício vais escrever mais algum código relacionado com a preparação e a confeção da tua fantástica lasanha do teu livro de receitas preferido.
Tens cinco tarefas. A primeira está relacionada com a própria confeção, as outras quatro são sobre a preparação perfeita.
Para a próxima lasanha que vais preparar, queres garantir que reservas tempo suficiente para poderes desfrutar da confeção. Já planeaste que camadas vai ter a tua lasanha. Agora queres estimar quanto tempo vai demorar a preparação com base nisso.
Implementa uma função preparationTime que aceita um vector de camadas como string e o tempo médio de preparação por camada, em minutos, como um int.
A função deve devolver a estimativa do tempo total de preparação, com base no número de camadas, como um int.
Se o tempo médio de preparação não for passado, deve ser usado o valor predefinido 2.
std::vector<std::string> layers{"sauce", "noodles", "sauce", "meat", "mozzarella", "noodles"};
preparationTime(layers, 3)
// => 18
preparationTime(layers)
// => 12
Além de reservares o tempo, também queres garantir que tens molho e noodles suficientes para cozinhar a lasanha dos teus sonhos. Por cada camada de noodles na tua lasanha, vais precisar de 50 gramas de noodles. Por cada camada de molho na tua lasanha, vais precisar de 0,2 litros de molho.
Define a função quantities, que recebe um vector de camadas como parâmetro, do tipo string.
A função determina depois a quantidade de noodles e molho necessária para preparares a tua refeição.
O resultado deve ser devolvido como uma struct amount, que já está definida para ti.
A amount tem duas variáveis membro: uma para o peso dos noodles como um int e a segunda para o volume do sauce como um double.
struct amount { int noodles; double sauce; };
amount needed{quantities({"sauce", "noodles", "sauce", "meat", "mozzarella", "noodles"})};
// needed.noodles => 100
// needed.sauce => 0.4
Há algum tempo, visitaste um amigo e comeste lasanha em casa dele. Estava fantástica e tinha algo de especial. O amigo enviou-te a lista de ingredientes e disse-te que o último item da lista era o "ingrediente secreto" que tornava aquela refeição tão especial. Agora também queres adicionar esse ingrediente secreto à tua receita.
Escreve uma função addSecretIngredient que aceita dois vector of strings como parâmetros.
O primeiro parâmetro é uma referência para os ingredientes da tua própria receita, e o segundo vector de strings contém os ingredientes que o teu amigo te enviou.
O último elemento da tua lista de ingredientes é sempre "?".
A função deve substituí-lo pelo último item da lista do teu amigo.
Nota: a addSecretIngredient não devolve nada: deves modificar diretamente a lista dos teus ingredientes.
A lista com os ingredientes do teu amigo também pode ser uma referência (para melhorar o desempenho), mas não deve ser modificada.
Certifica-te de que a lista não é modificada, usando uma referência const.
std::vector<std::string> friendsList{"noodles", "sauce", "mozzarella", "kampot pepper"};
std::vector<std::string> myList{"noodles", "meat", "sauce", "mozzarella", "?"};
addSecretIngredient(myList, friendsList);
// myList => {"noodles", "meat", "sauce", "mozzarella", "kampot pepper"}
As quantidades indicadas no teu livro de receitas dão apenas para uma lasanha de duas porções. Como da próxima vez queres cozinhar para mais pessoas, queres calcular as quantidades para diferentes números de porções.
Implementa uma função scaleRecipe que recebe dois parâmetros.
double necessárias para 2 porções.A função deve devolver um novo vector com as quantidades necessárias para o número de porções desejado.
Queres, no entanto, manter a receita original.
Isto significa que o argumento quantities não deve ser modificado nesta função.
std::vector<double> quantities{1.2, 3.6, 10.5};
scaleRecipe(quantities, 4);
// => { 2.4, 7.2, 21 }
Lembras-te de histórias da tua infância, contos sobre a tua tia-bisavó, a guardiã dos segredos culinários mais profundos da família. Reza a lenda que ela tem a chave para desvendar sabores além da imaginação, mas os métodos dela são pouco convencionais. Ela não lida com listas nem com medidas; fala em sussurros, revelando apenas um ingrediente de cada vez.
Com uma mistura de entusiasmo e receio, decides contactar a tua tia-bisavó. Como esperado, ela responde da sua forma enigmática, oferecendo um único ingrediente secreto, envolto em mistério.
A contribuição da tua tia-bisavó acrescenta uma camada de complexidade que queres usar na tua receita.
Implementa outra versão da função addSecretIngredient que aceita dois argumentos: uma referência para um vector que representa a tua lista de ingredientes e uma única string que representa a oferta enigmática da tua tia-bisavó.
std::vector<std::string> myList{"noodles", "meat", "sauce", "mozzarella", "?"};
std::string secretIngredient{"spice melange"};
addSecretIngredient(myList, secretIngredient);
// myList => {"noodles", "meat", "sauce", "mozzarella", "spice melange"}
Boa sorte, corajoso chef!
Inscreve-te no Exercism para aprenderes e dominares C++ com 19 conceitos100 exercícios, e mentoria humana real, tudo grátis.