Aprenda C

  • Página Inicial
  • Contato!
  • Tudo sobre C Parte 1!
  • Tudo sobre C Parte 2!
  • Tudo sobre C Parte 3!
  • Tudo sobre C Parte 4!
  • Tudo sobre C Parte 5!
  • Tudo sobre C Parte 6!
  • Tudo sobre C Parte 7!
  • Tudo sobre C Parte 8!
  • Tudo sobre C Parte 9!
  • Tudo sobre C Parte 5

    Argumentos para main, argc e argv

    Quando usamos passagem de parâmetros para a função main, podemos chamar um programa através do terminal pelo nome, podendo enviar um valor, que pode ser tratado dentro do programa através de dois parâmetros, argc e argv, na função main.

    A int argc guarda a quantidade de parâmetros informados (1, 2, 3, etc), e a char *argv[] é um array que serve como ponteiro para a matriz de caracteres que armazena os argumentos.

    Veja um exemplo de programa que receberá um parâmetro (rodando pelo terminal):

    
    #include <stdio.h>
    #include <stdlib.h>
    #include <locale.h>
    
    int main(int argc, char *argv[]) {
        setlocale(LC_ALL,"portuguese");
    
        int cont = 1;
    
        printf("Argumentos: %d\n", argc);
    
        while(cont < argc) {
            printf("\nParâmetro Nº%d: %s", cont, argv[cont]);
            cont++;
        }
    
        return 0;
    }
    
    

    PS: O primeiro argumento sempre é o nome do programa, que é exibido quando não é passado nenhum outro argumento.

    Assim podemos chamar o programa pelo terminal passando um parâmetro com o nome do programa, sem precisar o ".exe" no final (por exemplo aplicacaoc ao invés de aplicacaoc.exe). E podemos passar outros parâmetros junto com o programa. Isso é útil para programas como navegadores.

    Manipulando Datas

    Para manipular datas em C, podemos fazer assim (importe time.h):

    
    char data[9];
    
    printf("\n%s", _strdate(data));
    
    

    Caso deseje formatar datas e horas, faça isso:

    
    char* semana[7] = {"Domingo", "Segunda-feira", "Terça-feira", "Quarta-feira", "Quinta-feira", "Sexta-feira", "Sábado"};
    char sem[2];
    char data[20];
    char hora[20];
    
    struct tm *timeinfo;
    time_t hoje;
    
    time(&hoje);
    timeinfo = localtime(&hoje);
    
    strftime(sem, 2, "%w", timeinfo);
    
    strftime(data, 20, "%d/%m/%Y", timeinfo);
    
    strftime(hora, 20, "%H:%M:%S", timeinfo);
    
    int ds = strtol(sem, NULL, 10);
    
    printf("%s\n", semana[ds]);
    printf("%s\n", data);
    printf("%s\n", hora);
    
    

    Enums em C

    As enumerations definem um novo tipo de variável e limita desde logo os valores.

    Basicamente, um Enum se define assim:

    
    typedef enum {dom, seg, ter, qua, qui, sex, sab} semana;
    
    

    E podemos inicializar os valores, assim:

    
    typedef enum {dom = 1, seg, ter, qua, qui, sex, sab} semana;
    
    

    Da forma acima, os outros valores automaticamente serão atribuídos a 2, 3, 4, 5, 6 e 7. Se inicializar de outro número (como 10, ele irá pro 11, 12, etc.).

    Dessa forma, podemos verificar assim:

    
    typedef enum {dom = 1, seg, ter, qua, qui, sex, sab} semana;
    
    semana dias;
    
    printf("Digite o número do dia da semana: ");
    scanf("%d", &dias);
    
    printf("\n");
    
    if(dias >= dom && dias <= sab) {
        switch(dias) {
            case dom:
                printf("%d - Domingo\n", dias);
                break;
            case seg:
                printf("%d - Segunda\n", dias);
                break;
            case ter:
                printf("%d - Terça\n", dias);
                break;
            case qua:
                printf("%d - Quarta\n", dias);
                break;
            case qui:
                printf("%d - Quinta\n", dias);
                break;
            case sex:
                printf("%d - Sexta\n", dias);
                break;
            case sab:
                printf("%d - Sábado\n", dias);
                break;
        }
    }
    else {
        printf("Dia Inválido!\n");
    }
    
    

    Podemos também correr os dados de um enum num loop, assim:

    
    semana dias = dom;
    
    for(int i = dias; i <= sab; i++) {
        printf("%d\n", dias);
        dias++;
    }
    
    

    Como Criar Arquivos de Cabeçalho em C

    Quando trabalhamos com projetos grandes, costumamos criar muitas funções para fazer inúmeras tarefas, o que deixa o código muito confuso, para isso podemos criar nosso próprios arquivos de cabeçalho. Assim como existem arquivos padrões como o stdio.h, podemos fazer nosso próprios para simplificar programas.

    Pra entendermos, abra um novo projeto em C primeiro. E em File, selecione File de novo pra esolher o arquivo header (H) e salve na pasta do projeto atual. O header no exemplo terá o nome calculos.h.

    Coloque esse código no calculos.h:

    
    #ifndef CALCULOS_H_INCLUDED
    #define CALCULOS_H_INCLUDED
    
    #define _PI 3.14159
    int quadrado(int x);
    int cubo(int x);
    
    #endif // CALCULOS_H_INCLUDED
    
    

    Agora criaremos um novo File da mesma forma, mas um C source com o nome calculos.c, nele será escrito esses códigos:

    
    #include "calculos.h"
    
    int quadrado(int x) {
        return x * x;
    }
    
    int cubo(int x) {
        return x * x * x;
    }
    
    

    E no main.c, colocamos o calculos.h incluído entre aspas, por estar na mesma pasta, dessa forma, veja o código completo abaixo:

    
    #include <stdio.h>
    #include <stdlib.h>
    #include "calculos.h"
    
    int main() {
        printf("Usando headers:\n");
        int y = 5;
        int quadr = quadrado(y);
        int cub = cubo(y);
    
        printf("Quadrado de %d: %d\n", y, quadr);
        printf("Cubo de %d: %d\n", y, cub);
        printf("Valor da constante PI: %f\n", _PI);
    
        return 0;
    }
    
    

    PS: Em todos os arquivos, adicione o Debug e o Release.

    Ao incluir o include com aspas (tipo #include "funcoes.h"), como feito com os arquivos de cabeçalho, o compilador apenas procurará na mesma pasta onde está o código-fonte a ser compilado. Quando são usadas tags (como #include <stdlib.h>) ele procurará na pasta padrão do compilador (no caso do Linux, poderia ser em /usr/include, por exemplo).

    Criar Makefile em Linux

    Basicamente, podemos pegar os códigos-fontes anteriores e colocá-los numa mesma pasta. Nessa pasta crie também um arquivo com o nome makefile, sem extensão, e coloque esse código:

    
    contas: main.o calculos.o
        gcc main.o calculos.o -o contas -no-pie
    
    main.o: main.c calculos.h
        gcc -c main.c -o main.o
    
    calculos.o: calculos.c calculos.h
        gcc -c calculos.c -o calculos.o
    
    clean:
        rm -f *.o
    
    

    No caso ele executará de baixo pra cima, e não esqueça da tabulação. Para compilar basta digitar no terminal make e executar o arquivo digitando ./contas.

    Ponteiros - Introdução

    Um ponteiro é uma variável especial que contém um endreço de memória armazenado, em vez de dados comuns. Esse endereço é, no geral, a posição na memória de uma outra variável. Dessa forma, a variável que contém esse endereço aponta para a outra variável. Daí o nome "ponteiro".

    O domínio e o emprego dos ponteiros é extremamente importante para que seja possível programar com eficiência e desenvoltura em C. Eles são úteis em inúmeros contextos como por exemplo, no suporte à rotinas de alocação dinâmica de memória e na construção ADTs (Abstract Data Types), como por exemplo, pilhas, listas e filas.

    A figura a seguir ilustra a ideia geral de funcionamento de um ponteiro:

    Ponteiros

    Na figura, temos representados endereços na memória RAM do computador, e um conjunto de variáveis criadas nesses endereços, podemos ver que no endereço 5000 existe uma variável ponteiro, cujo conteúdo é o endereço de memória 5004, ou seja, esse ponteiro aponta para a variável que está localizada no endereço 5004 da memória RAM.

    Uma declaração de ponteiro consiste em um tipo, seguido do caracter * (operador de indireção ou de referência) e do nome que se deseja dar à variável. A sintaxe a seguir mostra a forma de se declarar um ponteiro:

    
    char *nome;
    
    

    O tipo pode ser qualquer um válido em C, define o tipo da variável que o ponteiro pode apontar. Só lembrando que os ponteiros e as variáveis que eles apontam devem ser do mesmo tipo.

    Veja o código de exemplo do uso deles:

    
    int main() {
        int *x, valor, y;
        valor = 35;
        x = &valor;
        y = *x;
    
        printf("Endereço da variável comum valor: %p\n", &valor);
        printf("Lendo o conteúdo do ponteiro x: %p\n", x);
        printf("Endereço da variável ponteiro x: %p\n", &x);
        printf("Conteúdo da variável apontada por x: %d\n", *x);
        printf("Conteúdo da variável comum y: %d\n", y);
    }
    
    

    No código acima, o *x é um ponteiro, as outras variáveis declaradas na mesma linha não são ponteiros, pra elas serem teriam que também ter o asterisco.

    Como o ponteiro armazena o endereço de memória (apenas o endereço, não armazena o conteúdo da variável apontada), ao colocarmos o & em &valor, o x pegará o endereço da memória, e não o conteúdo da variável valor. O & indica o endereço da variável na memória.

    E o y pegará o valor que está sendo apontado para *x (o conteúdo, não o endereço).

    PS: Observe que no print formatado, usamos %p por se tratar de ponteiros. Observe também que os endereços de memória são exibidos em hexadecimal.

    Resumindo, o & pega o endereço de memória, ou envia conteúdos para ele. Para exibição usa a máscara %p.

    O * pega o conteúdo que está no endereço apontado, ou envia para o mesmo. Usa as máscaras do tipo do valor.

    Com isso, podemos manipular variáveis independente do escopo das mesmas, podendo fazer isso dentro ou fora da função e tudo mais, por mexer diretamente no endereço de memória, que sempre será o mesmo durante a execução do programa. Isso cria várias possibilidades de manipulação de variáveis nos programas futuros.

    Podemos fazer algo assim, por exemplo:

    
    *x = 20;
    
    

    PS: Devemos sempre colocar o endereço (&) em variáveis criadas sem ponteiro para pegar o endereço da mesma. Em variáveis criadas com ponteiros só colocamos ele para pegar o endereço de memória do ponteiro (ele sem nada tem o endereço da outra variável armazenada). Veja o exemplo abaixo:

    
    int num = 50;
    int *pont;
    
    pont = &num;
    
    printf("Endereço de num: %p\n", &num);
    printf("Conteúdo de num: %d\n", num);
    printf("Endereço de pont: %p\n", &pont);
    printf("Endereço apontado por pont: %p\n", pont);
    printf("Conteúdo apontado por pont: %d\n", *pont);
    
    

    PS: No final do programa, é recomendarmos usarmos o free para excluir o ponteiro, liberando memória, assim:

    
    free(pont);
    
    

    Ah, um ponteiro sempre ocupará a quantidade fixa de 8 bytes, isso porque ele ocupa apenas o endereço de memória que aponta para outra variável, que pode ter capacidade de bytes variada, como por exemplo:

    
    printf("%ld\n", sizeof(num));
    printf("%ld\n", sizeof(pont));
    
    

    Ponteiros - Indireção Múltipla

    Podemos ter um ponteiro que aponte para um outro ponteiro, o qual por sua vez aponta para uma variável comum com um valor armazenado na memória.

    Chamamos a essa técnica de Indireção Múltipla, ou ainda Ponteiros para Ponteiros, em contraste com a Indireção Simples, que ocorre quando um ponteiro aponta para uma variável diretamente.

    Um ponteiro comum pode ser representado da seguinte forma:

    Ponteiro Comum

    Já um ponteiro para ponteiro pode ser representado assim:

    Ponteiro de Ponteiros

    Um ponteiro para ponteiro deve ser declarado de forma especial. Para isso, adicionamos mais um caractere * na frente do nome da variável ponteiro em sua declaração. Por exemplo int **valor;.

    Essa declaração cria um ponteiro de nome valor, o qual irá apontar para outro ponteiro, o qual por sua vez aponta para uma variável do tipo int.

    PS: Neste exemplo, valor não é um ponteiro para um número inteiro, e sim um ponteiro para um ponteiro de inteiro.

    Veja um exemplo de uso abaixo:

    
    int main() {
        int a = 40;
        int *pont1; // Ponteiro normal.
        int **pont2; // Ponteiro de ponteiros
    
        pont1 = &a; // Aponta para a.
        pont2 = &pont1; // Aponta para pont1, na prática aponta também pra a, mas ele não pode apontar diretamente pra a.
    
        printf("O endereço da variável é: %p\n", &a);
        printf("O endereço do ponteiro pont1 é: %p\n", &pont1);
        printf("O endereço do ponteiro pont2 é: %p\n", &pont2);
        printf("O endereço apontado por pont1 é: %p\n", pont1);
        printf("O endereço apontado por pont2 é: %p\n", pont2);
        printf("E o valor armazenado em a é: %d\n", **pont2);
    }
    
    

    Caso precise declarar um ponteiro como nulo, use NULL e inclua a bibioteca <stddef.h>.