Obra: Programar em C/Ponteiros
poderíamos escrever um livro inteiro sobre ponteiros pois conteúdo demasiadamente extenso por esse motivo este assunto foi dividido em básico intermediário avançado assim leitor poderá fazer seus estudos conforme suas necessidades recomendável para quem está vendo programação pela primeira vez aqui que não se preocupe com avançado sobre ponteiros por enquanto básico que um ponteiro um ponteiro simplesmente uma variável que armazena endereço de outra variável um exemplo que ponteiro de um relógio que aponta para as horas minutos ou segundos um ponteiro aponta para algo em programação temos as variáveis armazenadas na memória um ponteiro aponta para um endereço de memória imagine as variáveis como documentos memória do computador como pastas para guardar os documentos ponteiro como atalhos para as pastas não se desespere caso não consiga entender num primeiro momento conceito fica mais claro com prática declarando acessando ponteiros um ponteiro como qualquer variável deve ter um tipo que tipo da variável para qual ele aponta para declarar um ponteiro especificamos tipo da variável para qual ele aponta seu nome precedido por asterisco int ponteiro declara uma variável comum do tipo inteiro int ponteiro declara um ponteiro para um inteiro tome cuidado ao declarar vários ponteiros em uma linha pois asterisco deve vir antes de cada nome de variável note os três exemplos int estamos declarar três variáveis comuns int cuidado apenas será um ponteiro int agora sim temos três ponteiros esquema de um ponteiro para acessar endereço de uma variável utilizamos operador comercial chamado operador de referência ou operador de endereço como nome sugere ele retorna endereço na memória de seu operando ele unário deve ser escrito antes do seu operando por exemplo se uma variável nome foi guardada no endereço de memória expressão nome valerá com isso fica claro esquema ao lado variável contém valor ponteiro contem endereço de para atribuir um valor ao ponteiro usamos apenas seu nome de variável esse valor deve ser um endereço de memória portanto obtido com operador int int claro que também podemos inicializar um ponteiro int nos dois casos ponteiro irá apontar para variável mas como ponteiro contém um endereço podemos também atribuir um valor variável guardada nesse endereço ou seja variável apontada pelo ponteiro para isso usamos operador asterisco que basicamente significa valor apontado por ex int int para ver resultado printf printf cuidado você nunca deve usar um ponteiro sem antes inicializá lo esse um erro comum inicialmente um ponteiro pode apontar para qualquer lugar da memória do computador ou seja ao tentar ler ou gravar valor apontado por ele você estará manipulando um lugar desconhecido na memória int nesse exemplo estamos manipular um lugar desconhecido da memória se você tentar compilar esse código compilador deverá dar uma mensagem de aviso durante execução provavelmente ocorrerá uma falha de segmentação erro que ocorre quando um programa tenta acess
cução provavelmente ocorrerá uma falha de segmentação erro que ocorre quando um programa tenta acessar memória alheia um exemplo mais elaborado include int main int int printf return primeiramente declaramos variável com valor ponteiro que apontará para endereço de depois guardamos valor no endereço apontado por se você executar esse exemplo verá algo parecido com ff claro que os valores de de serão iguais já que aponta para terceiro valor endereço de memória onde está próprio valor de será diferente em cada sistema cuidado os operadores unários não podem ser confundidos com os operadores binários and bit bit multiplicação respectivamente ponteiro null uma falha de segmentação ou em inglês segmentation fault ocorre quando um programa tenta acessar um endereço na memória que está reservado ou que não existe nos sistemas unix quando acontece este tipo de erro sinal sigsegv enviado ao programa indicando uma falha de segmentação aqui ponteiro contem null definido com endereço que causa uma falha de segmentação endereço invalido define null char int main void int int null esse programa termina anormalmente você esta tentando colocar valor em um endereço inválido para que isso não aconteça ponteiro deve ser inicializado com um endereço valido exemplo include include include int main void int int null operação não permitida if null return eperm else printf endereço disposição pode colocar null está definido dentro do cabeçalho stddef aqui você não espera que programa acabe com algum tipo de mágica se null igual ao valor do ponteiro isso significa que não foi encontrado nem um endereço acessível então você para caso contrario você estará executando uma operação que não permitida ou colocar em mais operações com ponteiros suponhamos dois ponteiros inicializados podemos fazer dois tipos de atribuição entre eles esse primeiro exemplo fará com que aponte para mesmo lugar que ou seja usar será equivalente usar após essa atribuição nesse segundo caso estamos igualar os valores apontados pelos dois ponteiros alteraremos valor apontado por para valor apontado por agora vamos dar mais alguns exemplos com ponteiro aqui estamos incrementar ponteiro quando incrementamos um ponteiro ele passa apontar para próximo valor do mesmo tipo em relação ao valor para qual ponteiro aponta isto se temos um ponteiro para um inteiro incrementamos ele passa apontar para próximo inteiro note que incremento não ocorre byte byte aqui colocamos entre parênteses para especificar que queremos alterar valor apontado por ou seja aqui iremos incrementar conteúdo da variável apontada pelo ponteiro neste caso efeito não tão claro quanto nos outros exemplos precedência do operador sobre operador faz com que expressão seja equivalente valor atual de retornado ao operador valor de incrementado ou seja obtemos valor atual do ponteiro já fazemos apontar para próximo valor esta linha atribui uma variável conteúdo do décimo quinto inteiro adiante daquele apontado por por exemplo suponhamos que tivés
ável conteúdo do décimo quinto inteiro adiante daquele apontado por por exemplo suponhamos que tivéssemos uma série de variáveis que apontasse para nossa variável receberia valor de tente acompanhar este exemplo dos dois tipos de atribuição de ponteiros int apontará para apontará para altero valor existente na variavel copio valor de apontado por para apontado por altero valor da variável aponta para mesmo lugar que ou seja para altero valor de intermediário ponteiro de estrutura para começar deixar mais claro definimos uma estrutura simples com dois campos struct int double minha_estrutura passo seguinte definir um ponteiro para essa estrutura struct minha_estrutura partir do ponteiro podemos ter acesso um campo da estrutura usando um seletor uma flecha mesmo resultado pode ser obtido da seguinte forma operador cast também bastante utilizado para estruturar áreas de estoque temporários buffer os tipos dentro da estrutura devem ser mesmo do arranjo para evitar problemas de alinhamento seguir um pequeno exemplo include typedef struct estruturar char char estruturar int main char buffer estruturar struct estruturar buffer printf getchar se ambiente for windows descomente começo da linha return ponteiros como parâmetros de funções comecemos por uma situação problema eu tenho variáveis quero trocar valor delas vamos começar com um algoritmo simples dentro da função main include int main int temp printf temp temp printf return esse exemplo funcionará exatamente como esperado primeiramente ele imprimirá depois ele imprimirá mas se quisermos trocar várias vezes valor de duas variáveis muito mais conveniente criar uma função que faça isso vamos fazer uma tentativa de implementação da função swap troca em inglês include void swap int int int temp temp temp int main int printf swap printf return no entanto que queremos não irá acontecer você verá que programa imprime duas vezes por que isso acontece lembre se do escopo das variáveis as variáveis são locais função main quando as passamos como argumentos para swap seus valores são copiados passam ser chamados de troca ocorre entre de modo que quando voltamos função main nada mudou então como poderíamos fazer isso como são retornados dois valores não podemos usar valor de retorno de uma função mas existe uma alternativa os ponteiros include void swap int int int temp temp temp int main int printf neles valem swap printf neles agora valem return neste exemplo definimos função swap como uma função que toma como argumentos dois ponteiros para inteiros função faz troca entre os valores apontados pelos ponteiros já na função main passamos os endereços das variáveis para função swap de modo que função swap possa modificar variáveis locais de outra função único possível inconveniente que quando usarmos função teremos de lembrar de colocar um na frente das variáveis que estivermos passando para função se você pensar bem já vimos uma função em que passamos os argumentos precedidos de função scanf por que fazemos isso
m já vimos uma função em que passamos os argumentos precedidos de função scanf por que fazemos isso simples chamamos função scanf para que ela ponha nas nossas variáveis valores digitados pelo usuário ora essas variáveis são locais portanto só podem ser alteradas por outras funções através de ponteiros quando uma função recebe como parâmetros os endereços não os valores das variáveis dizemos que estamos fazer uma chamada por referência caso desse último exemplo quando passamos diretamente os valores das variáveis para uma função dizemos que uma chamada por valor foi caso do segundo exemplo veja mais um exemplo abaixo include int cubo_valor int int cubo_referencia int int main int number printf no valor original eh number number cubo_valor number printf no novo valor de number eh number printf number printf no valor original eh number cubo_referencia number printf no novo valor de number eh number return int cubo_valor int return int cubo_referencia int aptr aptr aptr aptr aptr return aptr ponteiros vetores em os elementos de um vetor são sempre guardados sequencialmente uma distância fixa um do outro com isso possível facilmente passar de um elemento outro percorrendo sempre uma mesma distância para frente ou para trás na memória dessa maneira podemos usar ponteiros aritmética de ponteiros para percorrer vetores na verdade vetores são ponteiros um uso particular dos ponteiros acompanhe exemplo seguir include int main int int vetorteste int ptr vetorteste printf vetorteste printf ptr printf ptr for começamos declarando um vetor com três elementos depois criamos um ponteiro para esse vetor mas repare que não colocamos operador de endereço em vetorteste fazemos isso porque um vetor já representa um endereço como você pode verificar pelo resultado da primeira chamada printf como você já viu anteriormente neste capítulo podemos usar sintaxe ptr para acessar inteiro seguinte ao apontado pelo ponteiro ptr mas se ponteiro aponta para vetor próximo inteiro na memória será próximo elemento do vetor de fato em as duas formas ptr ptr são equivalentes não necessário criar um ponteiro para usar essa sintaxe como já vimos vetor em si já um ponteiro de modo que qualquer operação com ptr será feita igualmente com vetorteste todas as formas abaixo de acessar segundo elemento do vetor são equivalentes vetorteste vetorteste ptr ptr veja mais este exemplo include int main int numbers int int numbers numbers numbers numbers for ele resume as várias formas de acessar elementos de um vetor usando ponteiros indexação estranha de ponteiros permite fazer um tipo indexação de um vetor quando uma variável controla seu índice seguinte código válido funciona observe indexação vetori include int main int int vetor for essa indexação apesar de estranha funciona corretamente sem aviso na compilação ela prática mas para os iniciantes pode parecer complicada só treinar para entender comparando endereços como os endereços são números eles também podem ser comparados entre si veja ex
comparando endereços como os endereços são números eles também podem ser comparados entre si veja exemplo seguir com efeito equivalente ao primeiro exemplo da seção anterior include int main int vetorteste int ptr vetorteste int while ptr esse programa incrementa ponteiro enquanto esse endereço for igual ou menor ao endereço do último elemento do vetor lembre se que os índices do vetor são avançado ponteiros para ponteiros note que um ponteiro uma variável como outra qualquer por isso também ocupa espaço em memória para obtermos endereço que um ponteiro ocupa em memória usamos operador assim como fazemos nas variáveis comuns mas se estivéssemos interessados em guardar endereço de um ponteiro que tipo de váriavel deveria recebe lo resposta um ponteiro isto um ponteiro para outro ponteiro considere seguinte declaração int declaramos uma variável chamada com valor como já sabemos para declarar um ponteiro deve se verificar tipo da variável que ele irá apontar neste caso int colocar um asterisco entre tipo da variável nome do ponteiro int p_x declaramos um ponteiro apontado para agora para se guardar endereço de um ponteiro os mesmos passos devem ser seguidos primeiramente verificamos os tipo da variável que será apontada int colocamos um asterisco entre tipo nome do ponteiro int p_p_x p_x declaramos um ponteiro que irá apontar para ponteiro p_x ou seja um ponteiro para ponteiro note que não impõe limites para número de asteriscos em uma variável no exemplo seguir todos os printf irão escrever mesma coisa na tela include int main void int int p_x p_x aponta para int p_p_x p_x p_p_x aponta para ponteiro p_x printf valor da variável printf p_x valor da variável apontada por p_x printf p_p_x valor da variável apontada pelo endereço apontado por p_p_x return percebe que p_p_x consiste no valor da variável apontada pelo endereço apontado por p_p_x uma aplicação de ponteiros para ponteiros está nas strings já que strings são vetores que por sua vez são ponteiros um vetor de strings seria justamente um ponteiro para um ponteiro passando vetores como argumentos de funções os ponteiros podem ser passados como argumentos de funções parâmetro ponteiro passando um array include void atribuivalores int int void mostravalores int int int main int vetorteste crio um vetor sem atribuir valores atribuivalores vetorteste mostravalores vetorteste return void atribuivalores int valores int num for int repare que passamos dois parâmetros para as funções nome do vetor que representa seu endereço na memória temos maneiras para passar endereço do vetor diretamente pelo seu nome via um ponteiro ou pelo endereço do primeiro elemento uma constante que representa número de elementos do vetor isso importante pois não guarda informações sobre tamanho dos vetores você não deve tentar alterar ou acessar valores que não pertencem ao vetor claro que devemos passar endereço do vetor por referência pois os seus valores são alterados pela função atribuivalores de nada adiantaria passar
referência pois os seus valores são alterados pela função atribuivalores de nada adiantaria passar vetor por valor pois valor só seria alterado localmente na função como já vimos no caso de troca do valor de duas variáveis por causa dessa equivalência entre vetores ponteiros podemos fazer uma pequena alteração no protótipo tanto na declaração quanto na definição das funções atribuivalores mostravalores sem precisar alterar código interno dessas funções ou chamada elas dentro da função main trocando void atribuivalores int int void mostravalores int int por void atribuivalores int int void mostravalores int int para compilador você não fez mudança alguma justamente por conta dessa equivalência em ambos os casos foi passado endereço do vetor para as funções ponteiros para funções os ponteiros para funções servem geralmente para passar uma função como argumento de uma outra função neste exemplo include int soma int int return int operacao int int int func int int int func return int main int operacao soma printf return veja que criamos uma função que retorna soma dos dois inteiros ela fornecidos no entanto ela não chamada diretamente ela chamada pela função operacao através de um ponteiro função main passa função soma como argumento para operacao função operacao chama essa função que lhe foi dada como argumento note bem terceiro argumento da função operacao ele um ponteiro para uma função nesse caso ele foi declarado como um ponteiro para uma função que toma dois inteiros como argumentos retorna outro inteiro indica que estamos declarando um ponteiro não uma função os parênteses em torno de func são essenciais pois sem eles compilador entenderia argumento como uma função que retorna um ponteiro para um inteiro forma geral para declarar um ponteiro para uma função tipo_retorno lista de argumentos para chamar função apontada pelo ponteiro há duas sintaxes sintaxe original argumentos se ptr um ponteiro para uma função faz bastante sentido que função em si seja chamada por ptr no entanto sintaxe mais moderna permite que ponteiros para funções sejam chamados exatamente da mesma maneira que funções argumentos por fim para inicializar um ponteiro para função não precisamos usar operador de endereço ele já está implícito por isso quando chamamos função operacao não precisamos escrever soma veja mais um exemplo na verdade uma extensão do exemplo anterior include int soma int int return int subtracao int int return int menos int int subtracao int operacao int int int func int int int func return int main int operacao soma operacao menos printf return aqui criamos mais uma função subtracao além de criar um outro ponteiro para ela uma espécie de atalho menos na função main referimo nos função de subtração através desse atalho veja também que aqui usamos sintaxe moderna para chamada de ponteiros de funções ao contrário do exemplo anterior