Pense em funções como pequenas máquinas. Elas recebem uma entrada, executam alguma lógica, e produzem uma saída
Entrada
+ +
| |
+-+ +--------+
| |
| Lógica |
| |
+--------+ +-+
| |
+ +
Saída
As funções executam sempre a mesma coisa Você sempre recebe a mesma saída, se as entradas forem as mesmas
E você receberá apenas um resultado
Isso, pelo menos é como funções em matemática funcionam:
f(x) = 2 * x + 1
f(1) = 2 * 1 + 1 = 3
f(2) = 2 * 2 + 1 = 5
.
.
.
Se fossemos representar essa função em C, bastaríamos declarar uma função
antes da função main
#include <stdio.h>
int f(int x) {
return x * 2 + 1;
}
int main() {
int n1 = f(1);
int n2 = f(2);
printf("f(1) = %d \n", n1);
printf("f(2) = %d \n", n2);
}
/*
* Output:
* f(1) = 3
* f(2) = 5
*
*/Ok, mas o que significa cada parte dessa declaração?
A primeira coisa a fazer é declarar o tipo do dado que essa função retorna.
Como, no caso, ela retorna um número, podemos usar int
A segunda parte é o nome da função. Escolhi o nome f porque -eu quis-.
Podemos usar qualquer nome. Se quisesse, poderia chamar de batata
A terceira parte, entre parentes, indica os parâmetros que essa função recebe.
No caso, recebe um número. Então escolhemos receber um int. Precisamos também
de um nom para esse parâmetro dentro da função. Resolvemos chamar de x. De novo,
poderia ser qualquer nome.
Esse parâmetro cria uma variável x do tipo int que existe somente no escopo da
função (ou seja, entre os {} da função)
Mas eu ja vi funções com mais parâmetros, ou que não retornam nada
O que eu citei ali em cima é como funcionam funções na matemática, ou funções puras (recebem 1 input -> trabalham somente com ele -> devolvem 1 output)
Funções em C, e na maioria das outras linguagens não-funcionais, podem se comportar de forma diferente:
- Não precisam necessariamente retornar alguma coisa
- Podem não receber parâmetro algum
- Podem receber mais de um parâmetro
- Podem produzir efeitos colaterais (wat? mais pra frente eu explico)
Mais fácil com exemplos:
Podemos declarar uma função que não retorna nada!
int faz_alguma_coisa(int x) {
x + 10;
}
int main() {
int n = faz_alguma_coisa(19);
printf("%d", n);
}
/*
* Output:
* ????
*
*/Estamos dizendo que a função retorna um int, mas não retornamos nada
(não usamos o return)
O output, no caso, vai variar de acordo com o compilador.
No GNU/gcc com as flags -Wno-return-type e -Wno-unused-value, por exemplo,
obtive a saída 0. Isso porque o GNU/gcc inicializa as variáveis automaticamente,
então o valor inicial de n é 0
Se quisermos não retornar nada, o correto é usar o type void, que não guarda
valor algum
void faz_alguma_coisa(int x) {
printf("kkk teu cu");
}
int main() {
faz_alguma_coisa(19);
// A expressão abaixo produz um erro, já que void não retorna nada :-)
// int n = faz_alguma_coisa(19);
}
/*
* Output:
* kkk teu cu
*
*/Funcões em C não precisam receber parâmetros:
void f() {
printf("Função void sem parametros \n");
}
int h() {
return 19;
}
int main() {
int numero = h();
f();
printf("Numero: %d \n", numero);
}
/*
* Output:
* Função void sem parametros
* Numero: 19
*
*/int calcula_media(int n1, int n2) {
return (n1 + n2) / 2
}Efeitos colateirais acontecem quando uma função faz ou modifica algo que está fora do escopo dela
wat
Digamos que você agora trabalha pro Trump, e quer escrever uma simples função que calcula a soma de 3 números:
int sum(int a, int b, int c) {
int s = a + b + c;
nuke_russia(); // Aaaa não. Você causou a terceira guerra mundial ¯\_(ツ)_/¯
return s;
}
int main() {
printf("%d", sum(10,20,30));
}Em C, não somos limitados a trabalhar apenas com o escopo da função
No exemplo bem exagerado ali, fizemos uma função de soma, que chama uma outra função, que ocasiona o fim do mundo
Um exemplo real de efeito colateral é a função scanf()
int main() {
int numero = 10;
printf("numero: %d \n", numero); // Exibe "10"
scanf("%d", &numero); // Passa o endereço de memória da variável numero para a
// função scanf, que modifica seu valor.
// Essa alteração pode ser observada fora da função
printf("%d", numero); // Valor digitado pelo usuário
}Ok, na teoria funções são bem simples. Na pratica, podem ser bem complicadas
Pensa nesse problema classico do Fico
Faça um programa para calcular a média simples de 2 números. Os números não podem ser negativos, iguais a 0, ou ímpares. Use funções blablabla
porra, show. sem funções:
#include <stdio.h>
int main() {
int n1 = 0;
int n2 = 0;
l_n1: // brotip: labels não são indentados, de acordo o padrão NetBSD
printf("Digite um número par: ");
scanf("%d", &n1);
if(n1 <= 0 || n1 % 2 != 0) {
printf("Entrada inválida. Digite novamente \n");
goto l_n1;
}
l_n2:
printf("Digite um número par: ");
scanf("%d", &n2);
if(n2 <= 0 || n2 % 2 != 0) {
printf("Entrada inválida. Digite novamente \n");
goto l_n2;
}
printf("Média: %d", (n1 + n2) / 2);
}Primeira coisa que a gente pode extraír daí: O cálculo da média ser feito em uma função
#include <stdio.h>
int calc_media(int a, int b) {
return (a + b) / 2;
}
int main() {
int n1 = 0;
int n2 = 0;
l_n1:
printf("Digite um número par: ");
scanf("%d", &n1);
if(n1 <= 0 || n1 % 2 != 0) {
printf("Entrada inválida. Digite novamente \n");
goto l_n1;
}
l_n2:
printf("Digite um número par: ");
scanf("%d", &n2);
if(n2 <= 0 || n2 % 2 != 0) {
printf("Entrada inválida. Digite novamente \n");
goto l_n2;
}
printf("Média: %d", calc_media(n1, n2));
}Sabe o que me incomoda? Repetição de código me incomoda. A gente ta repetindo basicamente o mesmo código 2 vezes! A gente:
- Exibe a mensagem pro usuario
- Captura o input
- Faz a validação
Isso 2 vezes. Se quisessemos alterar esse programa pra fazer a média de 3 números, o ctrl+c ctrl+v iria chorar.
AÍ, se quisermos alterar a logica de validação, teríamos que alterar em TRÊS lugares
PUTA QUEO PARIU!
É pedir pra ter bug e entrar na fila 2 vezes
Vamos extraír essa lógica pra uma função
#include <stdio.h>
int valida(int n) {
if(n <= 0 || n % 2 != 0) {
return 0;
} else {
return 1;
}
}
int calc_media(int a, int b) { /* ... */ }
int main() {
/* ... */
l_n1:
printf("Digite um número par: ");
scanf("%d", &n1);
if(valida(n1) == 0) {
printf("Entrada inválida. Digite novamente \n");
goto l_n1;
}
l_n2:
printf("Digite um número par: ");
scanf("%d", &n2);
if(valida(n2) == 0) {
printf("Entrada inválida. Digite novamente \n");
goto l_n2;
}
printf("Média: %d", calc_media(n1, n2));Lembra que C tem tipagem fraca e não guarda valores, então tudo são números!
Não existe verdadeiro ou falso em C, apenas 1 e 0
Agora podemos validar inifnitos números. E quando quisermos alterar a lógica, basta alterar em 1 lugar 👌
Sabe outra parada que eu não gosto? goto
goto é tipo uva passa. Estraga tudo
Vamos tirar essa confusão ai, e colocar um while no lugar
while(n1 == 0){
printf("Digite um número par: ");
scanf("%d", &n1);
if(valida(n1) == 0) {
printf("Entrada inválida. Digite novamente \n");
n1 = 0;
}
}
while(n2 == 0) {
printf("Digite um número par: ");
scanf("%d", &n2);
if(valida(n2) == 0) {
printf("Entrada inválida. Digite novamente \n");
n2 = 0;
}
}nice
Agora a gente pode olhar pro códig e não ficar enjoado. Tudo ao mesmo tempo
Sabe o que é tosco?
Aqueles printf e scanf são toscos
#include <stdio.h>
int valida(int n) { /* ... */ }
int calc_media(int a, int b) { /* ... */ }
int captura_num() {
int n;
printf("Digite um número par: ");
scanf("%d", &n);
return n;
}
int main() {
int n1 = 0;
int n2 = 0;
while(n1 == 0){
n1 = captura_num();
if(valida(n1) == 0) { /* ... */ }
}
while(n2 == 0) {
n2 = captura_num();
if(valida(n2) == 0) { /* ... */ }
}
printf("Média: %d", calc_media(n1, n2));
}Uuuu agora tempos um código com pouca repetição. Irado
Outra forma de fazer isso seria passando os ponteiros de n1 e n2 pra
captura_num, mas não estamos interessados em ponteiros
A gente ainda repete o while, a chamda de valida(), e o bloco do if depois
da validação
A gente pode incluir isso tudo dentro de captura_num(), já que só nos interessa
os números já validados
#include <stdio.h>
int valida(int n) { /* ... */ }
int calc_media(int a, int b) { /* ... */ }
int captura_num() {
int n = 0;
while(n == 0){
printf("Digite um número par: ");
scanf("%d", &n);
if(valida(n) == 0) {
printf("Entrada inválida. Digite novamente \n");
n = 0;
}
}
return n;
}
int main() {
int n1 = 0;
int n2 = 0;
n1 = captura_num();
n2 = captura_num();
printf("Média: %d", calc_media(n1, n2));
}Agora nosso código: praticamente não tem repetição de lógica, tem um main bem
enxuto e fácil de ler, é fácil de debuggar, é fácil de extender e modificar ![]()
C, como você já sabe, não tem os conceitos de true ou false, mas sim 1 e 0
O que significa que operaçòes Booleanas (É. Álgebra de Boole mesmo),
como (n <= 0 || n % 2 != 0), simplesmente retornam 1 ou 0!
E isso significa que os ifs da vida esperam 1 ou 0
A gente pode modificar nosso código e deixa-lo ainda mais enxuto
#include <stdio.h>
int valida(int n) { return (n > 0 && n % 2 == 0); } // Agora testa se é válido em vez de inválido
int calc_media(int a, int b) { return (a + b) / 2; }
int captura_num() {
int n = 0;
while(n == 0){
printf("Digite um número par: ");
scanf("%d", &n);
if(!valida(n)) { // não precisa verificar se é igual a 0. O retorno vai ser 0 ou 1
printf("Entrada inválida. Digite novamente \n");
n = 0;
}
}
return n;
}
int main() {
int n1 = 0;
int n2 = 0;
n1 = captura_num();
n2 = captura_num();
printf("Média: %d", calc_media(n1, n2));
}