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 .