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 if
s 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));
}