Revista Do Linux
EDIÇÃO DO MÊS
 CD do Mês

 Capa
 Entrevista
 Corporativo
 Editoração
 Segurança
 Hardware
 Produtos
 Programação
 Sistema
 

Curso de Linguagem C

Continuando o estudo dos ponteiros, vamos conhecer mais algumas características importantes desse poderoso recurso da linguagem C

Bem-vindos! Nesta sexta parte de nosso curso, vamos continuar o estudo dos ponteiros, iniciando com uma rápida revisão e apresentando algumas características dos vetores de ponteiros. A seguir, veremos as formas de passagem de argumentos para funções e de que modo o conhecimento que adquirimos com os ponteiros irá nos ajudar nesse assunto. Como sempre, todos os pontos apresentados têm exemplos para sua melhor compreensão. Mãos à obra!

Revisão

Vimos na edição passada que um ponteiro é uma variável cujo conteúdo é o endereço de uma outra variável, ou seja, ele "aponta" para a localização de uma variável. Por exemplo, para declararmos uma variável como sendo um ponteiro para uma variável inteira, ela é declarada da seguinte forma:

int *pont;

Se desejarmos armazenar em pont o endereço de uma variável, devemos utilizar o operador de obtenção de endereço, representado pelo caractere &

pont = №

E, caso desejarmos saber o conteúdo da variável apontada por pont, utilizamos o operador de obtenção do conteúdo do endereço, representado pelo caractere *:

int numero1, numero2;
int *pont;

pont = &numero1;
numero2 = *pont;

Vimos ainda que os nomes de vetores também são ponteiros para a primeira posição desse vetor:

int vetor[10];
int *pont;
pont = vetor; 
/* comando válido, pois vetor também é um ponteiro */
pont = vetor[0]; 
/* comando inválido, vetor[0] é inteiro */

Sendo que a mesma característica se aplica aos vetores de caracteres (strings).

Vetores de Ponteiros

Como um ponteiro é um tipo de dado da linguagem C, também podemos ter vetores de ponteiros. Vejamos o exemplo a seguir:

main()
{
int *pont[5];
int numero1, numero2, numero3, numero4, numero5;
int i;

numero1 = 10; numero2 = 14; numero3 = 23; numero4 = 34; numero5 = 52;

pont[0] = &numero1;
pont[1] = &numero2;
pont[2] = &numero3;
pont[3] = &numero4;
pont[4] = &numero5;

for(i=0; i<5; i++)
printf("O valor da variável apontada por pont[%d] eh: %d\n", i, *pont[i]);
}

Compile:

# gcc exemplo1.c -o exemplo1

No programa, definimos um vetor de ponteiros para variáveis do tipo inteiro, com cinco posições. Repare que essas cinco posições não serão ocupadas por números inteiros, e sim por endereços de variáveis que contêm números inteiros. Declaramos a seguir cinco variáveis do tipo inteiro, com nomes numero1 até numero5 e mais uma variável inteira chamada i.

A seguir, atribuímos a cada uma das posições do vetor o endereço de cada uma das variáveis inteiras que criamos, em ordem crescente (numero1, numero2 etc.).

Por fim, imprimimos, através de um laço de repetição, o conteúdo das variáveis apontadas pelas posições no vetor pont.

Para executar o programa, digite:

# ./programa1
O valor da variável apontada por pont[0] eh: 10
O valor da variável apontada por pont[1] eh: 14
O valor da variável apontada por pont[2] eh: 23
O valor da variável apontada por pont[3] eh: 34
O valor da variável apontada por pont[4] eh: 52

Ponteiros de Ponteiros

No exemplo anterior, definimos um vetor de ponteiros. Na revisão feita na primeira seção deste artigo, lembramos que o nome de um vetor é considerado pela linguagem C como um ponteiro para a primeira posição desse vetor.

Assim, quando declaramos:

int *pont[5];

O valor representado pela variável pont, sem o índice, é um ponteiro para a primeira posição de um vetor de ponteiros para variáves do tipo inteiro. Se for necessário atribuir o valor de pont a uma outra variável, essa outra variável deverá ser declarada da seguinte forma:

int **pontpont;
pontpont = pont;

A declaração da variável pontpont, que a princípio parece erro de digitação, significa que pontpont é um ponteiro (contém o endereço) de uma variável do tipo ponteiro de uma variável do tipo inteiro.

A partir das definições anteriores, podemos reescrever o programa exemplo1 da seguinte forma:

main()
{
int *pont[5];
int numero1, numero2, numero3, numero4, numero5;
int i;
int **pontpont;

numero1 = 10;
numero2 = 14;
numero3 = 23;
numero4 = 34;
numero5 = 52;
pont[0] = &numero1;
pont[1] = &numero2;
pont[2] = &numero3;
pont[3] = &numero4;
pont[4] = &numero5;
for(i=0, pontpont = pont; i<5; i++, pontpont++)
printf("O valor da variável apontada por pont[%d] eh: %d\n", i, **pontpont);
}

Compile:

# gcc exemplo2.c -o exemplo2

Execute:

# ./exemplo2

O valor da variável apontada por pont[0] eh: 10
O valor da variável apontada por pont[1] eh: 14
O valor da variável apontada por pont[2] eh: 23
O valor da variável apontada por pont[3] eh: 34
O valor da variável apontada por pont[4] eh: 52

Repare que estamos utilizando mais uma variável, pontpont, para indicar a posição dentro do vetor pont. Como já foi mostrado, a forma de exibição do conteúdo de um endereço apontado é feita utilizando-se o operador "*". Sendo pontpont um "ponteiro de ponteiro", é necessária uma dupla operação para obtermos o conteúdo desejado.

Argumentos de Funções

Quando apresentamos o conceito de funções e modularização de programas, na parte 4 de nosso curso, vimos também a forma de passagem de valores para essas funções através dos argumentos:

int funcao1(int a, int b)	
{
... comandos da função ...
}

Ao executarmos o código de uma função dentro de um programa, esse armazena uma cópia dos valores dos argumentos em uma área de memória específica. Essa área é liberada somente no término da execução da função. Outra característica importante é que, como é feita uma cópia do conteúdo dos argumentos, esses não são alterados na função chamadora, mesmo que tenha havido modificação da função chamada. Veja o exemplo:

int funcao1(int parm1)
{
parm1 = 40;
return(0);
}
main()
{
	int numero1;
numero1 = 10;
printf("O valor de numero1 antes de chamar a funcao1 eh: %d\n", numero1);
funcao1(numero1);
printf("O valor de numero1 apos chamar a funcao1 eh: %d\n", numero1);
}

Compile:

# gcc exemplo3.c -o exemplo3

Execute:

# ./exemplo3
O valor de numero1 antes de chamar a funcao1 eh: 10
O valor de numero1 apos chamar a funcao1 eh: 10

Imagine agora que temos uma função que precisa dos valores armazenados em um vetor de 100 posições. Podemos declarar a função da seguinte forma:

int funcao2 (int a, int b[100])
{
... comandos ...
}

Se o programa utilizasse a mesma lógica citada, seria copiado para a pilha o conteúdo de 100 posições, que já está armazenado em uma outra área de memória do programa. Ou seja, estamos duplicando nosso consumo de memória de forma desnecessária.

Para permitir a alteração de valores dos argumentos e também otimizar o espaço ocupado em memória, a linguagem C utiliza duas formas de passagem de parâmetro: por valor e por referência. Como já vimos, na passagem por valor copiamos o valor dos argumentos em uma área temporária. Já na passagem por referência indicamos (ou referenciamos) onde está o valor da variável. E como fazemos essa referência? Obviamente, através de ponteiros.

Utilizando a passagem por referência, vamos reescrever o exemplo anterior:

int funcao1(int *parm1)
{
  	*parm1 = 40;
    	return(0);
    }
    main()
    {
      int numero1;
        numero1 = 10;
          printf("O valor de numero1 antes de chamar a funcao1 eh: %d\n", numero1);
            funcao1(&numero1);
              printf("O valor de numero1 apos chamar a funcao1 eh: %d\n", numero1);
              }
              

Compile:

              # gcc exemplo4.c -o exemplo4
              

Execute:

              # ./exemplo4
              O valor de numero1 antes de chamar a funcao1 eh: 10
              O valor de numero1 apos chamar a funcao1 eh: 40
              

Para permitir a passagem do conteúdo de vetores, também utilizamos um ponteiro. Lembre que a linguagem considera que o nome de um ponteiro também é o nome de um vetor. Veja o exemplo a seguir:

              int funcao1(int *numeros)
              {
                int i;
                  for( i = 0; i < 5; i++)
                      printf("O valor do elemento %d eh: %d\n", i, numeros[i]);
                        return(0);
                        }
                        main()
                        {
                          int vetor[5];
                            vetor[0] = 10;
                              vetor[1] = 22;
                                vetor[2] = 35;
                                  vetor[3] = 48;
                                    vetor[4] = 57;
                                      funcao1(vetor);
                                      }
                                      

Compile:

                                      # gcc exemplo5.c -o exemplo5
                                      

Execute:

                                      # ./exemplo5
                                      O valor do elemento 0 eh: 10
                                      O valor do elemento 1 eh: 22
                                      O valor do elemento 2 eh: 35
                                      O valor do elemento 3 eh: 48
                                      O valor do elemento 4 eh: 57
                                      

Argumentos de Programa

Já vimos como fornecer valores para funções por meio de argumentos. E se desejarmos passar valores para um programa através de um comando shell? Por exemplo, como o comando copy consegue obter os nomes do arquivo origem e destino?

Lembre que a parte principal do programa, main, também é uma função. Nos exemplos vistos até agora, não utilizamos nenhum parâmetro nessa função. Mas a linguagem C estipula que ela tem dois parâmetros, comumente referenciados por argc e argv.

                                      main(int argc, char *argv[])
                                      {
                                      ...comandos
                                      }
                                      

O parâmetro argc é do tipo inteiro, e indica o número de argumentos do programa. Uma característica dos programas C no ambiente Unix é que o nome do programa também é contado.

O parâmetro argv é um vetor de ponteiros para vetores de caracteres (strings). O número de ponteiros é, conseqüentemente, o valor indicado pelo parâmetro argc.

Como exemplo, vamos desenvolver um programa que imprime os parâmetros passados pela linha de comando:

                                      main(int argc, char *argv[])
                                      {
                                        int i;
                                          for( i = 0; i < argc; i++)
                                              printf("O %d.o argumento eh: %s\n", i+1, argv[i]);
                                              }

                                              

Compile:

                                              # gcc exemplo6.c -o exemplo6
                                              

Execute:

                                              # ./exemplo6 segundo terceiro quarto
                                              O 1.o argumento eh: ./exemplo6
                                              O 2.o argumento eh: segundo
                                              O 3.o argumento eh: terceiro
                                              O 4.o argumento eh: quarto
                                              

Lembre que, quando passamos o formato s para o comando printf, devemos passar como argumento respectivo um ponteiro para um vetor de caracteres.

Conclusão

Neste artigo pudemos conhecer mais alguns recursos importantes disponibilizados pela linguagem C com o uso de ponteiros. A partir dos conceitos de argumentos de funções e do entendimento da relação existente entre ponteiros e conteúdos de variáveis, torna-se possível o desenvolvimento de programas modulares e poderosos.

Na próxima parte do curso, iremos apresentar as formas de estruturação de dados que permitem à linguagem criar e gerenciar tipos mais complexos de dados por meio de instruções simples. A continuação desse curso de C será feita no site da RdL, em www.RevistaDoLinux.com.br .


A Revista do Linux é editada pela Conectiva S/A
Todos os Direitos Reservados.

Política de Privacidade
Anuncie na Revista do Linux