

Matrizes
Matrizes de String
Matrizes Multidimensionais
Funções
Arquivos de Cabeçalhos
Definição e Estruturas
Ponteiros
Incremento - Decremento de *p
Ponteiro de Ponteiro
Parametro via valor e referencia
Matrizes
Matrizes são estruturas de dados do tipo vetor com duas ou mais dimensões. Os elementos contidos em uma matriz devem ser todos do mesmo tipo. Pode-se imaginar uma matriz como sendo uma tabela em memória. O exemplo abaixo ilustra a sintaxe de uma matriz.
Sintaxe
tipo_de_dado nome_da_matriz[tamanho_dimensão_1][tamanho_dimensão_2];
Matriz com uma dimensão – Vetor
A matriz de uma única dimensão ou unidimensional é o já conhecido vetor, que já foi abordado anteriormente.
Matrizes com 2 dimensões
Matrizes com 2 dimensões ou bidimensionais possuem a declaração bastante parecida com a de um vetor,
a única diferença é que ao invés de declarar o tamanho apenas de uma dimensão,
é necessário também fazer a declaração do tamanho da segunda dimensão.
Sintaxe
tipo_de_dado nome_da_matriz[tamanho_dimensão_1][tamanho_dimensão_2]
Como já comentado, pode-se pensar na matriz bidimensional como sendo uma tabela,
a primeira dimensão corresponde ao número de linhas
e a segunda dimensão corresponde ao número de colunas.
Muitos também fazem a relação de altura vs. largura da tabela.
Então para se percorrer uma matriz de duas dimensões é necessário percorrer em cada linha (índice da primeira dimensão),
coluna por coluna (índice da segunda dimensão). A figura 79 mostra um exemplo de programa com o uso de matriz bidimensional.
Nele é declarado uma matriz chamada de “matriz” na linha 4, um contador do tipo inteiro inicializado com o valor 1, além de duas variáveis inteiras i e j que serão usadas para percorrer as linhas e as colunas respectivamente da matriz, nas linhas 5 e 6.
Na linha 8, pode-se observar o primeiro comando for cuja iteração é feita através da variável i e percorre as linhas da matriz.
Logo abaixo, na linha 9, é impresso na tela o índice da linha que está sendo percorrida. Para cada iteração do primeiro for, ou seja, para cada linha, existe um segundo comando for, onde dentro dele a iteração ocorre pela variável j na linha 10, usado para percorrer todas as colunas daquela linha.
#include<stdio.h>
int main(){
int matriz[10][10];
int i,j,contador;
contador=1;
for(i=0;i<10;i++){
printf("\nLinha: %d \n", i);
for (j=0;j<10;j++){
matriz[i][j]=contador;
contador++;
printf("Col:%d Num:%d - ",j,matriz[i][j]);
}
}
return(0);
}
Matrizes de String
Matrizes de string são matrizes bidimensionais usadas para guardar uma lista de strings, ou seja, uma lista de vetores, já que uma string é um vetor de char. O exemplo abaixo ilustra a sintaxe de uma matriz de strings, o primeiro índice dirá a quantidade de strings da lista e o segundo índice a quantidade máxima de caracteres de cada string.
Sintaxe
char nome_da_matriz_de_string[numero_de_strings][comprimento_max_das_strings]
Para fazer o acesso de uma string da lista basta variar o primeiro índice da matriz e para percorrer os caracteres de uma determinada string, o segundo índice é usado. A figura 81 mostra um programa que exemplifica o uso de matrizes de string. Nele é declarada uma matriz de string na linha 3 com capacidade para 5 strings de até 20 caracteres. Logo depois, na linha 7, é feita uma iteração com o comando for para que o usuário entre com 5 strings que serão armazenadas em cada uma das posições da matriz. Numa segunda iteração no bloco que se inicia na linha 14, as strings que foram digitadas pelo usuário são impressas na tela.
// Matriz de strings 5 x 20
#include<stdio.h>
int main(){
char matriz_de_strings[5][20];
int i;
for(i=0;i<5;i++){
printf("\n\nDigite uma string para colocar na matriz: ");
gets(matriz_de_strings[i]);
}
printf("\n\nVoce escolheu as seguintes strings para serem colocadas na matriz:\n\n");
for(i=0;i<5;i++)
printf("%s\n", matriz_de_strings[i]);
return(0);
}
Matrizes multidimensionais
Matrizes multidimensionais são matrizes que possuem N dimensões. Elas funcionam basicamente como as outras matrizes, mas vale lembrar que o índice que irá variar mais rapidamente quando se quer percorrê-la, será o índice mais à direita.
Sintaxe
tipo_de_dado nome_da_matriz[tam_dimensão_1][tam_dimensão_2]...[tam_dimensão_N]
inicialização de Matrizes
Assim como já foi visto com outros tipos de variáveis, também é possível inicializar uma matriz no momento de sua declaração,
o exemplo abaixo mostra alguns exemplos. COM COLCHETES { }
Exemplo
float vetor [5] = { 1.3, 4.5, 2.7, 4.1, 0.0 };
int matriz [3][4] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12 };
int matriz [3][4] = { { 1, 2, 3, 4},{ 5, 6, 7, 8},{9, 10, 11, 12} };
char str [10] = { ’T’, ’o’, ’b’, ’y’ }; char str [10] = “Toby”;
char str_vect [3][10] = { “Toby”, “Izabella”, “Boby” };
inicialização sem tamanho específico
É possível inicializar as matrizes sem especificar o tamanho, isso é muito útil quando não se sabe o tamanho de matriz que será necessária no programa. Quando não existe especificação de tamanho na declaração, em tempo de compilação será verificado o que foi inicializado para essa matriz sem tamanho e o programa irá considerar esse valor inicial como sendo o seu tamanho. Após determinado o tamanho, isso não poderá mais ser alterado. Abaixo segue alguns exemplos de matrizes sem especificação de tamanho.
Exemplo
char matriz_de_char [] = “Essa matriz não tem especificação de tamanho”;
int matriz_de_int [][2] = { 1,2,2,4,3,6,4,8,5,10 };
A primeira matriz terá o tamanho de 44 especificado em tempo de compilação,
que corresponde ao tamanho da frase com que ela foi inicializada.
No segundo exemplo, o tamanho do índice não especificado será 5, pois a matriz foi inicializada com 10 elementos, e o índice referente à coluna foi especificado como tamanho de 2. Dessa forma, pode-se calcular 10 elementos divididos em grupos de 2 colunas onde serão necessária a existência de 5 linhas.
#include<stdio.h>
#include<string.h>
#include<conio.h>
int main()
{
int matriz[4][4];
int i, j, maior88, menor8;
maior88=0;
menor8=0;
for (i=0; i<4; i++){
for (j=0; j<4;j++){
printf("linha %d e coluna %d: ", i, j);
scanf("%d", &matriz[i][j]);
if (matriz[i][j] > 88)
maior88++;
if (matriz[i][j] < 8)
menor8++;
}
}
printf("\n");
for (i=0; i<4; i++){
for (j=0;j<4;j++){
printf("[%d][%d]: %d\n", i, j, matriz[i][j]);
}
}
printf("Qualidade de valores maiores que 88: %d\n", maior88);
printf("Qualidade de valores menores que 8: %d\n",
menor8);
return(0);
}
Funções
Funções, definição de Estruturas e Ponteiros
Introdução a FUNÇÕES
Para que servem as funções?
Por motivos de didática e para tornar fácil o entendimento do conteúdo desta apostila, os programas vistos aqui possuem um grau de complexidade baixo, além de serem programas curtos. Porém, na vida real, o mais comum é que os programas sejam muito grandes e na maioria das vezes complexos, podendo conter centenas e até milhares de linhas.
Para melhorar a organização e facilitar a manutenção desses programas muito grandes uma boa técnica é dividi-los em módulos menores.
Essa técnica é conhecida como “dividir e conquistar”.
O que são funções?
Para construir módulos em C, são utilizadas funções, muitos dos comandos vistos nessa apostila são funções prontas que existem nas bibliotecas da linguagem C, como printf, scanf, gets, puts entre outras. Geralmente, programas em C são compostos por funções feitas pelo desenvolvedor combinadas com as funções das bibliotecas do C.
Muitas funções úteis para diversas finalidades, como cálculos aritméticos, input/output, manipulação de strings e caracteres, já estão prontas na biblioteca e podem ser usadas quando necessário, não sendo necessário que desenvolvedores gastem tempo fazendo funções com as mesmas finalidades. Funções devem ser autônomas e são projetadas para cumprir uma tarefa específica. Sendo assim, é importante que elas tenham responsabilidades bem definidas.
Estrutura de uma função
O exemplo abaixo ilustra a estrutura de uma função. Ela é composta, primeiramente, pelo tipo de retorno da função, seguido do nome da função e entre parênteses, os parâmetros ou argumentos que a função deve receber. Logo após, vem as chaves que abrigarão o corpo da função.
Sintaxe
tipo_de_retorno nome_da_função (tipo_param_1 nome_param_1, tipo_param_2 nome_ param_2, ... tipo_param_N nome_param_N)
{
corpo_da_função
}
Os parâmetros da função correspondem à entrada da função, para cada um dos parâmetros deve ser especificado seu tipo.
Assim como o retorno da função corresponde à saída da função.
função sem retorno
É possível que uma função não retorne nenhum tipo de dado.
Nesse caso, o retorno da função é vazio. O exemplo abaixo ilustra a estrutura de uma função de retorno vazio.
Sintaxe
void nome_da_função (tipo_param_1 nome_param_1, tipo_param_2 nome_param_2, ... tipo_param_N nome_param_N)
{
corpo_da_função
}
Comando return
Funções são encerradas com o comando return. Após a execução desse comando, a função é terminada imediatamente, e quando a função possui algum valor de retorno, é através desse comando que ele é retornado.
O exemplo abaixo mostra a sintaxe do comando.
Sintaxe
return valor_de_retorno;
ou return;
Uma função pode ter mais de um comando return, porém, ela sempre será encerrada quando o primeiro return for atingido.
Quando o retorno da função é do tipo void, não há necessidade de colocar o comando return ao término da função.
A figura 85 mostra a declaração de uma função chamada de e_um_numero_par que inicia na linha 3 e vai até a linha 9,
cujo seu objetivo é dizer se um dado número de entrada é par ou ímpar.
No corpo da função existe um comando if que testa se o número passado como parâmetro para a função é ou não divisível por 2,
caso não seja, o número é ímpar e retorna 0,
e se for divisível, é porque se trata de um número par e então a função retorna 1.
Na função main é pedido ao usuário que entre com um número, nas linhas 15 e 16, e logo depois, a função e_um_numero_par é passada dentro de um comando if na linha 18. Caso a função retorne 1, ou seja, o número passado se trata de um par, uma mensagem informando isso é impressa na tela na linha 19, caso contrário, o número passado é um número ímpar e essa informação também é impressa na tela na linha 21.
Como observado no exemplo, o retorno das funções pode ser aproveitado para ser usado em expressões e atribuições. Caso você não use esse retorno, ele será descartado. Por exemplo, a função printf retorna um valor inteiro que quase nunca é usado, dessa forma, na maioria das vezes ele é descartado.
#include<stdio.h>
int e_um_numero_par (int a)
{
if (a%2)
return 0;
else
return 1;
}
int main()
{
int numero;
printf ("Entre com numero: ");
scanf("%d",&numero);
if (e_um_numero_par(numero))
printf("\n\nO numero e par.\n");
else
printf("\n\nO numero e impar.\n");
return 0;
}
Protótipo de função
Nos programas, como por exemplo no exemplo da figura 85, as funções são declaradas antes do método main, pois ao serem usadas no método main, as funções já foram declaradas e o compilador sabe do que se trata quando encontra a chamada de uma função criada pelo desenvolvedor.
Porém, muitas vezes, não é possível fazer dessa forma, principalmente em caso de programas muito grandes, onde muitas vezes o programa pode estar dividido em diversos arquivos. Essa situação pode fazer com que seja necessário chamar funções que foram compiladas em outro arquivo. Para resolver essa situação, são usados os chamados protótipos de funções, que consistem em declarar a estrutura das funções que serão usadas no programa, antes de usá-las, dessa forma o compilador toma conhecimento dos retornos e argumentos daquela função e consegue gerar o código corretamente.
Abaixo segue a sintaxe de um protótipo de função, ela é bem semelhante a declaração de uma função,
porém, logo após a declaração dos parâmetros, segue um ponto e vírgula (;) e o corpo da função é omitido.
Sintaxe
tipo_de_retorno nome_da_função (tipo_param_1 nome_param_1, tipo_param_2 nome_ param_2, ... tipo_param_N nome_param_N);
A figura 88 mostra o mesmo exemplo da figura 85 da seção anterior, porém, usando agora protótipo de função para a função e_um_numero_par. Perceba que o protótipo é declarado antes da função main e o corpo da função só é declarado após a função main.
Arquivos de cabeçalho
Os arquivos de cabeçalhos que já foram vistos na apostila, como por exemplo stdio.h ou string.h, são compostos por protótipos de funções.
Os corpos das funções já estão compilados e são incluídos no programa no momento da linkagem. Você também pode fazer o seu próprio arquivo de cabeçalho para poder usar as suas funções em diversos arquivos diferentes.
Para isso basta criar um arquivo com extensão, por exemplo função.h, e dentro dele escrever o protótipo da função. Em um outro arquivo de extensão .c, por exemplo função.c, deve-se escrever a função por completa. Esse arquivo será incluído dentro do arquivo função.h, depois basta incluí-lo como um arquivo de cabeçalho normalmente, porém, ao invés de usar o sinal de menor e maior <>, ele deve ser incluído usando aspas dupla (“”).
// *******************************************************
// função que receba do usuário dois números
// e retorna a soma de todos os números existentes entre eles.
//
// *******************************************************
#include <stdio.h>
#include<string.h>
#include<conio.h>
// funcao que soma valores entre uma faixa especificada
int somaValoresIntermediarios(int v1, int v2){
int i, soma;
soma = 0;
for (i=v1+1; i<v2; i++){
soma+=i;
}
return soma;
}
// funcao principal do programa, MAIN
int main()
{
int v1, v2;
printf("Digite o primeiro numero: ");
scanf("%d",&v1);
printf("Digite o segundo numero: ");
scanf("%d",&v2);
printf("A soma dos numeros entre %d e %d eh: %d", v1, v2, somaValoresIntermediarios(v1,v2));
return(0);
}
Definição e Estruturas
o que são estruturas definidas?
Estruturas definidas são construídas usando um grupo de variáveis de tipos diversos,
esse agrupamento, por sua vez, resulta em um novo tipo de dado.
Criando uma estrutura
O comando struct é usado para a criação dessas estruturas.
Sintaxe
struct nome_da_estrutura
{
tipo_1 nome_var_1; tipo_2 nome_var_2;
...
tipo_n nome_var_n;
}
variáveis_com_o_tipo_estrutura;
As variáveis_com_o_tipo_estrutura são opcionais
e seriam variáveis do tipo da estrutura que já estariam sendo declaradas no momento da criação da estrutura.
O exemplo abaixo exemplifica uma estrutura criada de disciplina que contém as variáveis nome, professor e uma variável para o número de horas de aula. Para essa estrutura são criadas também duas variáveis do tipo disciplina chamadas matemática e história.
Exemplo
struct disciplina
{
char nome[20];
char professor[20];
int horas_de_aula;
}
matemática, história;
No exemplo abaixo é criado uma segunda estrutura chamada aluno, ela possui uma variável para o nome do aluno, idade, nota e uma variável chamada disciplina do tipo disciplina, que foi a estrutura criada no quadro anterior. Para essa estrutura não foi criada nenhuma variável, pois como já citado, é opcional.
Exemplo
struct aluno
{
char nome[20];
int idade;
float nota;
disciplina disciplina;
}
A figura 94 mostra um programa de exemplo usando as duas estruturas criadas nos quadros anteriores, primeiramente as estruturas são declaradas antes do método main, e dentro dele são preenchidas a variáveis matemática e história criadas já na estrutura, logo após são criados dois alunos e para cada um deles são atribuídos os valores para cada variável da estrutura e uma disciplina também e atribuída a cada um dos alunos.
Atribuição de estruturas do mesmo tipo
Para atribuir uma estrutura para outra do mesmo tipo, basta usar o sinal de igualdade como mostrado abaixo.
Exemplo
estrutura_1 = estrutura_2;
No caso das variáveis alunos criadas no programa de exemplo da figura 76, para que a variável aluno_1, que possui como atributo o nome “Izabela”, ter os mesmos valores que foram atribuídos à variável aluno_2, que possui como atributo o nome “Ale”, basta fazer a atribuição abaixo.
Exemplo
aluno_1 = aluno_2;
Quando essa atribuição é feita, todos os valores dos campos do aluno_2 serão copiadas para os respectivos campos da variável aluno_1,
depois dessa atribuição os dois alunos terão os mesmos valores.
Matrizes de estrutura
Assim como qualquer outro tipo de dado, é possível criar matrizes de estruturas definidas também.
Sintaxe
struct nome_da_estrutura [tamanho_da_matriz];
Por exemplo, para criar uma matriz de alunos, a sintaxe seria a seguinte:
Exemplo
struct aluno [20];
Para acessar o nome da disciplina do décimo aluno da matriz, o código seria o abaixo:
Exemplo
aluno[9].disciplina.name;
Exemplo resolvido
Baseando-se no exemplo aluno disciplina mostrado na seção “Criando uma estrutura”, crie também uma estrutura para professor e altere as estruturas aluno e disciplina para que seja possível cadastrar uma lista de alunos para uma disciplina.
A figura 95 mostra a resolução do exercício proposto acima.
Ponteiros
o que são ponteiros?
Dados do tipo ponteiro guardam endereços de memória, assim como dados do tipo int armazenam inteiros e do tipo float armazenam pontos flutuantes. Ponteiros são usados para guardar o endereço na memória onde está armazenada alguma variável que guarda um dado específico.
Eles sempre apontam para areferência de uma variável de determinado tipo, dessa forma, existem ponteiros de inteiros, ponteiros de ponto flutuante, ponteiros de caracteres e outros tipos. O ponteiro apontará para um dado do tipo que ele foi declarado.
declarando um ponteiro
O exemplo abaixo ilustra a sintaxe para se declarar uma variável do tipo ponteiro, o ponto mais diferente que pode ser percebido,
do que já foi visto na apostila é o asterisco (*) que vem antes do nome da variável.
O restante é igual à declaração dos diversos outros tipos de dados.
Sintaxe
tipo_de_dado_do_ponteiro *nome_da_variável;
O asterisco vai indicar para o compilador que aquela variável se trata de um ponteiro e que ao invés de guardar um valor de um tipo específico, ele armazenará um endereço de memória que aponta para uma variável que contém um dado do tipo especificado.
Exemplo
int *ponteiro_de_inteiro;
char *ponteiro_de_char;
float *ponteiro_de_float;
O exemplo acima ilustra algumas declarações de ponteiros de vários tipos.
inicializando e atribuindo um ponteiro
Quando um ponteiro é declarado como visto na seção, ele ainda não foi inicializado, ou seja, ele aponta para um lugar indefinido na memória. Antes de utilizar um ponteiro, ele precisa ser inicializado, assim como toda variável em C.
Um ponteiro pode ser inicializado com NULL, 0 ou com um endereço de memória. Um ponteiro inicializado aponta para lugar nenhum. Quando um ponteiro é inicializado com 0 é equivalente a inicializá-lo com NULL, porém, apenas ponteiros do tipo int podem ser diretamente inicializados com 0, por isso prefira inicializar com NULL à 0.
Para inicializar um ponteiro com um endereço de memória é necessário utilizar o operador de endereço que é representado pelo caractere &. Você deve lembrar dele da função scanf. O & é um operador unário que retorna o endereço do seu operando.
O exemplo abaixo mostra um ponteiro sendo inicializado com um endereço de memória. Primeiro é declarada uma variável do tipo int chamada i, e atribuído a ela o valor 8. Depois uma variável ponteiro de inteiro é declarada e chamada de ponteiro e logo após o ponteiro recebe o endereço de memória da variável i através do operador de endereço &.
int i = 8;
int *ponteiro;
ponteiro = &i;
Agora pode-se dizer que o ponteiro aponta para a variável i.
Exemplo
A figura 96
ilustra a variável ponteiro apontando para a variável inteira i.
figura 97.
representação do ponteiro e da variável i na memória.
Após a inicialização do ponteiro, ele já pode ser usado em qualquer ponto do seu código.
Dessa forma, o ponteiro já está referenciando indiretamente o valor da variável i.
Sendo assim, é possível alterar o valor dessa variável da seguinte forma:
Exemplo
*ponteiro = 11;
Após essa atribuição, a variável i não possui mais o valor 8 e sim 11. Essa atribuição é chamada de atribuição indireta.
A figura 98 mostra um programa de exemplo com o uso de ponteiros, primeiro são declaradas duas variáveis do tipo inteiro chamadas de numero e valor na linha 5. Logo abaixo, um ponteiro de inteiro também é declarado com o nome de p na linha 6.
Logo depois é atribuído o valor de 11 para a variável numero. Na linha 9 é atribuído ao ponteiro p o endereço da variável numero utilizando o operador de endereço &. Agora pode-se dizer que o ponteiro p aponta para variável numero, ou seja, ele possui o endereço na memória de onde essa variável está armazenada.
Na linha 11, a variável valor é atribuída indiretamente com o valor da variável numero, que é o local para onde ele aponta,
para isso, antes da variável ponteiro deve vir o asterisco (*).
/*
****************************************************************
Programa de exemplo com o uso de ponteiros,
primeiro são declaradas duas variáveis do tipo inteiro
chamadas de numero e valor na linha 5.
Logo abaixo, um ponteiro de inteiro também é declarado
com o nome de p na linha 6.
Logo depois é atribuído o valor de 11 para a variável numero.
Na linha 9 é atribuído ao ponteiro p o endereço da variável
numero utilizando o operador de endereço &.
Agora pode-se dizer que o ponteiro p aponta para variável numero,
ou seja, ele possui o endereço na memória
de onde essa variável está armazenada.
Na linha 11, a variável valor é atribuída indiretamente
com o valor da variável numero,
que é o local para onde ele aponta, para isso,
antes da variável ponteiro deve vir o asterisco (*).
****************************************************************
*/
#include<stdio.h>
int main()
{
int numero, valor;
int *p;
numero=11;
p=№
valor=*p;
printf("A variavel valor foi atribuida com o valor apontado pelo ponteiro p: %d\n\n", valor);
printf("O valor da variavel apontada e: %d\n\n", *p);
return(0);
}
Por último são escritas na tela três strings.
A primeira, na linha 13, escreve na tela o valor da variável valor.
Na linha 15 é impresso o endereço de memória que o ponteiro aponta, para isso é usado o código de formato %p
e a variável ponteiro é passada como parâmetro sem o asterisco (*).
Por fim, na linha 16, é escrito na tela o valor que o ponteiro p está apontando, que no caso é o valor da variável numero.
Para isso, é usado o formatado %d já que a variável numero se trata de um tipo inteiro
e a variável ponteiro p é passada com o asterisco (*), para que o valor apontado por ele seja acessado, ao invés do endereço de memória.
A figura 99 mostra a execução do programa de exemplo da figura 98. Pode-se observar que primeiro é impresso o valor da variável valor, a qual foi atribuída de maneira indireta pelo ponteiro p. Logo depois, é impresso o endereço de memória da variável numero, que é para onde o ponteiro p aponta. E por último, é impresso o valor da variável p, que é acessada indiretamente pelo ponteiro através do operador asterisco (*).


incremento e decremento de ponteiros
Assim como os outros tipos de variáveis, também é possível incrementar ou decrementar um ponteiro.
Quando um ponteiro é incrementado, ele passa a apontar para o próximo valor na memória que seja do mesmo tipo que o dele.
Então, se um ponteiro de inteiro é incrementado, ele passará a apontar para o próximo valor inteiro na memória.
O decremento é o contrário, ele passará a apontar para o valor anterior do mesmo tipo.
O exemplo abaixo ilustra o incremento de um ponteiro.
Lembrando que dessa forma ele incrementa o endereço de memória, passando a apontar para o próximo endereço.
Exemplo
ponteiro++;
ponteiro--;
Para incrementar o valor da variável que o ponteiro está apontando // uso de parênteses no nome do ponteiro
o incremento ou decremento deve ser feito da forma mostrada abaixo.
Exemplo
(*ponteiro)++;
(*ponteiro)--;
O que será impresso pelo comando printf no programa abaixo?
Depois de analisado e descoberto o resultado, copie o código no Dev C++,
compile e execute para comparar e comprovar o seu resultado.
Comente cada linha do código, explicando sua função.
// ****************************************************************
//
// incremento e decremento de ponteiros
//
// ****************************************************************
#include<stdio.h>
int main()
{
int a, *ponteiro, b; // declaracao das variaveis a e b, e do ponteiro
a = 0; // inicializacao da variavel a, a=0
ponteiro = &a; // atribuicao de ponteiro para a variavel a, ponteiro=0
b = *ponteiro; // atribuicao do valor atual do ponteiro a variavel b, b=0
b = 4; // atribuicao de novo valor a variavel b, b=4
(*ponteiro)++; // incremento no valor da vaiavel do ponteiro, variavel do ponteiro a, a=0+1=1
b--; // decremento a variavel b, b=4-1=3
(*ponteiro) += b; // incremento de b ao valor da variavel do ponteiro a, a=1+3
printf ("a = %d\n", a); // impressao na tela do valor de a, a=4
return(0);
}
Ponteiro de Ponteiro
É possível fazer com que um ponteiro aponte para outro ponteiro que por sua vez aponte para uma variável. Esse primeiro ponteiro que aponta para outro ponteiro é chamado ponteiro de ponteiro. Ficou um pouco confuso né!?
O exemplo abaixo ilustra a sintaxe de um ponteiro para ponteiro, porém, é possível ter um ponteiro de ponteiro de ponteiro ou ainda um ponteiro de ponteiro de ponteiro de ponteiro e assim por diante, a quantidade de asteriscos determina essa relação. No caso de um ponteiro para ponteiro, serão apenas dois asteriscos (**).
Sintaxe
tipo_de_dado_do_ponteiro **nome_da_variável;
Para acessar o conteúdo da variável final que um ponteiro de ponteiro aponta é preciso usar dois asteriscos (**),
pois com apenas um asterisco (*) se obtém apenas o endereço da variável que é apontada pelo segundo ponteiro.
A figura 100 mostra um programa de exemplo usando ponteiro de ponteiro.
Na linha 5 são declaradas três variáveis, um inteiro, um ponteiro e um ponteiro de ponteiro.
Logo abaixo, na linha 6, é atribuído o valor 11 para a variável denominada inteiro.
Na linha 9 o ponteiro recebe o endereço da variável inteiro e começa então a apontar para ela
e na linha 12 o ponteiro_de_ponteiro recebe o endereço do ponteiro e começa a apontar para ele.
O primeiro printf na linha 15 imprime o conteúdo que o ponteiro de ponteiro aponta,
pois, como dito, o asterisco duplo (**) acessa o conteúdo da variável final que é apontada.
O segundo printf na linha 18 imprime o conteúdo apontado pela variável ponteiro, que também é o valor da variável inteiro.
Dessa forma os valores impressos nos dois comandos printf são iguais.
A figura 101 ilustra a execução do exemplo com uso de ponteiro de ponteiro.
figura 100. programa de exemplo com uso de ponteiro para ponteiro.
// ****************************************************************
//
// ponteiro de ponteiro
//
// ****************************************************************
/*
programa de exemplo usando ponteiro de ponteiro.
Na linha 5 são declaradas três variáveis, um inteiro, um ponteiro e um ponteiro de ponteiro.
Logo abaixo, na linha 6, é atribuído o valor 11 para a variável denominada inteiro.
Na linha 9 o ponteiro recebe o endereço da variável inteiro e começa então a apontar para ela
e na linha 12 o ponteiro_de_ponteiro recebe o endereço do ponteiro e começa a apontar para ele.
O primeiro printf na linha 15 imprime o conteúdo que o ponteiro de ponteiro aponta,
pois, como dito, o asterisco duplo (**) acessa o conteúdo da variável final que é apontada.
O segundo printf na linha 18 imprime o conteúdo apontado pela variável ponteiro,
que também é o valor da variável inteiro.
Dessa forma os valores impressos nos dois comandos printf são iguais.
A figura 101 ilustra a execução do exemplo com uso de ponteiro de ponteiro
*/
#include<stdio.h>
int main()
{
int inteiro, *ponteiro, **ponteiro_de_ponteiro;
inteiro = 11;
ponteiro = &inteiro;
ponteiro_de_ponteiro = &ponteiro;
printf("Ponteiro de ponteiro: %d \n", **ponteiro_de_ponteiro);
printf("Ponteiro: %d", *ponteiro);
return(0);
}
Parâmetro passado por valor ou referência
Já foi visto que a entrada de uma função são os parâmetros ou argumentos passados para ela, esses chamados também de parâmetros formais. Quando uma função com parâmetros é chamada de dentro do método main, por exemplo, os valores que são passados na chamada da função são copiados para os parâmetros formais da função. Dessa forma, os valores que foram passados não sofrem alterações dentro da função,
pois foram passados para a função apenas os valores dos parâmetros e não os parâmetros em si. Essa forma de chamada de função é conhecida como chamada por valor.
Quando é necessário alterar as variáveis que foram passadas como parâmetros na chamada dentro da função,
é necessário que os parâmetros sejam passados como ponteiros, essa forma de chamada de função é denominada de chamada por referência.
O quadro abaixo ilustra a sintaxe de uma função que recebe parâmetros por referência.
Observe que os nomes dos parâmetros precedem de asterisco (*),
pois como já mencionado, eles devem ser ponteiros.
Sintaxe
tipo_de_retorno nome_da_função (tipo_param_1 *nome_param_1, tipo_param_2 *tipo_ param_2, ... tipo_param_N *nome_param_N)
{
corpo_da_função
}
O quadro abaixo mostra como seria a sintaxe para a chamada de uma função com parâmetros passados por referência, note que o operador de memória foi usado nas variáveis, isso porque ele retornará o endereço de memória das variáveis, que são ponteiros.
tipo_de_retorno nome_da_função (&var_param_1, &var_param_2, ...&var_param_N);