Skip to content

Instantly share code, notes, and snippets.

@vmattos
Last active June 9, 2017 14:13
Show Gist options
  • Save vmattos/0d9f716c25ec6509b70de0aa4311940f to your computer and use it in GitHub Desktop.
Save vmattos/0d9f716c25ec6509b70de0aa4311940f to your computer and use it in GitHub Desktop.

Funções

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:

Não precisam de retorno

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
 *
 */

Podem não receber parametro algum

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
 *
 */

Podem receber mais de um parametro

int calcula_media(int n1, int n2) {
  return (n1 + n2) / 2  
}

Podem produzir efeitos colaterais

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
}

real shit

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:

  1. Exibe a mensagem pro usuario
  2. Captura o input
  3. 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 :godmode:

Bonus round

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

Tipos

Para de declarar uma variável em C, você precisa dizer o tipo o nome da variável:

int main() {
  int numero;
  char letra;
}

Ok, mas pra que caralhos servem esses tipos?

Pra dizer ao computador computador quantos bytes na memória aquela variavel precisa Exemplo:

int: 4 bytes
char: 1 byte
double: 8 bytes

Tá, mas como eu poderia verificar quantos bytes são resevados por cada tipo?

Verifique por você mesmo, jovem, com o comando sizeof:

printf("Quantidade de bytes em 1 char: %lu", sizeof(char));

/* 
 * Output:
 * Quantidade de bytes em 1 char: 1
 *
 */

Porra. O tipo serve -SÓ- pra isso?

SIM :D

Mas ele não guarda na memória o valor certinho que eu coloquei, baseado no tipo?

NÃO :D Ele sempre guarda um número

Saca esse exemplo:

int numero = 122;
printf("Numero: %d", numero);
printf("Letra: %c", numero);

/*
 * Output:
 * Numero: 122
 * Letra: z
 *
 */

Que porras! De onde veio esse z??

O programa guardou o valor 122 na variavel numero. Quando pedimos para imprimir o número usando a formatação %d, ele imprimiu o número 122 Quando pedimos para imprimir o número com a formatação %c (caractere), ele imprimiu o caractere de posição 122 na tabela ASCII

(http://www.asciitable.com/index/asciifull.gif)

PUTA QUEO PARIU, então se eu fizer o contrário, daria no mesmo?

SIM :D

char letra = 'z';
printf("Letra: %c", letra);
printf("Numero: %d", letra);

/*
 * Output:
 * Letra: z
 * Numero: 122
 *
 */

Viado, tu pode até imprimir o valor hexadecimal dessa porra

printf("Hexadecimal: %x", letra);

/*
 * Output:
 * Hexadecimal: 7a
 *
 */

Pode verificar a conversão aqui: http://www.binaryhexconverter.com/hex-to-decimal-converter

Isso significa que C tem tipagem fraca. O tipo não significa praticamente nada na execução do programa, mas isso o torna FODIDAMENTE rápido

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment