Baixe o app para aproveitar ainda mais
Prévia do material em texto
Bruno Jurkovski Fábio da Fontoura Beltrão Felipe Augusto Chies Kauê Soares da Silveira Lucas Fialho Zawacki Marcos Vinicius Cavinato Revisão da Aula 3 � Na última aula aprendemos o que são funções. Estas são uma característica muito importante da linguagem C. � Lembrando que funções recebem parâmetros, e que estes podem ser passados de duas maneiras: Revisão da Aula 3 � Por valor: /* Função que retorna o resultado em float da divisao de a por b */ float divide (float a, float b) { return a / (float) b; } int main() { float x = 2, y = 3; printf( “%f” , divide (x , y) ); } Revisão da Aula 3 � Por referência: /* Função que atribui à variavel resultado o valor da divisão de a por b */ void divide_ref (float a, float b, float *resultado) { *resultado = a / (float) b; } int main() { float x = 2, y = 3, res; divide_ref (x , y, &res); // passamos o endereço de res printf( “%f” , resultado); } Revisão da Aula 3 � Outro aspecto importante sobre funções é que elas possuem o seu próprio escopo. void perde_tempo () { int n = 0; //n só existe nesse escopo while (n < 100000) n++; } int main() { int n; // não é o mesmo n que declaramos acima perde_tempo(); } Ponteiros Na aula de hoje vamos entender os ponteiros. Inicialmente, podem causar dúvidas quanto a sua real importância, mas com exemplos, ficará fácil de compreender seu funcionamento e utilidade. � Toda programação com uma linguagem imperativa como o C envolve o manuseio de dados através de variáveis � Uma variável, na verdade, é uma região de memória específica para guardar um determinado dado Introdução ... ... 501 502 503 504 505 506 507 ... ... #include <stdio.h> int a; int b[5]; main(){ a = 6; b[0] = 10; b[4] = 25; ... � Toda programação com uma linguagem imperativa como o C envolve o manuseio de dados através de variáveis � Uma variável, na verdade, é uma região de memória específica para guardar um determinado dado Introdução ... ... 501 502 503 504 505 506 507 ... ... #include <stdio.h> int a; int b[5]; main(){ a = 6; b[0] = 10; b[4] = 25; ... � Toda programação com uma linguagem imperativa como o C envolve o manuseio de dados através de variáveis � Uma variável, na verdade, é uma região de memória específica para guardar um determinado dado Introdução #include <stdio.h> int a; int b[5]; main(){ a = 6; b[0] = 10; b[4] = 25; ... ... ... 501 6 502 503 504 505 506 507 ... ... � Toda programação com uma linguagem imperativa como o C envolve o manuseio de dados através de variáveis � Uma variável, na verdade, é uma região de memória específica para guardar um determinado dado Introdução #include <stdio.h> int a; int b[5]; main(){ a = 6; b[0] = 10; b[4] = 25; ... ... ... 501 6 502 10 503 504 505 506 507 ... ... � Toda programação com uma linguagem imperativa como o C envolve o manuseio de dados através de variáveis � Uma variável, na verdade, é uma região de memória específica para guardar um determinado dado Introdução #include <stdio.h> int a; int b[5]; main(){ a = 6; b[0] = 10; b[4] = 25; ... ... ... 501 6 502 10 503 504 505 506 25 507 ... ... Introdução � Toda variável possui um endereço de memória associado. Esse endereço é o local onde a variável está armazenada em memória. ... ... 501 6 502 10 503 504 505 506 25 507 ... ... Endereço da variável a: 501 Endereço da variável b: 502 Endereço da variável b[0] = 502 Endereço da variável b[3] = 505 Endereço da variável b[4] = 506 Ponteiros � Variáveis em geral guardam determinados tipos de conteúdo específicos (inteiro, ponto flutuante, caracter…) � Um ponteiro é uma variável que contém um endereço de memória. � Esse endereço de memória é geralmente a posição de uma outra variável na memória. � Se uma variável contém o endereço de uma outra, então diz-se que a primeira variável aponta para a segunda (por isso o nome Ponteiro!) Tipo de base * Nome da variável ; O tipo de base é importante para a Aritmética de Ponteiros Ponteiros - Declaração Declaração de variável: Declaração de ponteiro: int x; float j; char v; ... int * x; float * j; char * v; ... Ponteiros ... ... 501 506 502 503 504 505 506 12 507 ... ... Neste caso: Se a variável da posição 501 não for ponteiro, então possui valor 506. Se for ponteiro, então é do tipo inteiro e aponta para a posição 506(que contém o valor 12). Ponteiros - Operadores Existem 2 operadores especiais para ponteiros: � & � * O ‘&’ é um operador unário(ou seja, requer apenas um operando), que devolve o endereço na memória de seu operando. O ‘*’ é o complemento do ‘&’: É um operador unário que devolve o valor da variável localizada no endereço que o segue. Ponteiros - Operadores Um exemplo do uso do operador & para extrair o endereço de uma variável é a função scanf int numero; scanf(“%d”, &numero); /* Isso quer dizer: armazene o valor lido no teclado na variável cujo endereço eu estou lhe fornecendo */ Ponteiros - Operadores ... ... 501 502 503 504 505 506 507 ... ... #include <stdio.h> int var1; int *pont1; main(){ var1 = 6; pont1 = &var1; *pont1 = 30; ... Exemplo: Ponteiros - Operadores ... ... 501 6 502 503 504 505 506 507 ... ... #include <stdio.h> int var1; int *pont1; main(){ var1 = 6; pont1 = &var1; *pont1 = 30; ... Exemplo: Ponteiros - Operadores ... ... 501 6 502 501 503 504 505 506 507 ... ... #include <stdio.h> int var1; int *pont1; main(){ var1 = 6; pont1 = &var1; *pont1 = 30; ... Exemplo: Ponteiros - Operadores ... ... 501 30 502 501 503 504 505 506 507 ... ... #include <stdio.h> int var1; int *pont1; main(){ var1 = 6; pont1 = &var1; *pont1 = 30; ... Exemplo: Ponteiros - Operadores #include <stdio.h> main() { float var0; int var1; int *pont1, *pont2; pont1 = & var1; pont2 = & var0; ... Exemplo de erro: pont2 é um ponteiro para inteiro e var0 é float - ERRO DE COMPILAÇÃO Ponteiros - Atribuição Ponteiros podem ser atribuídos como uma variável qualquer: ... ... 501 502 503 504 505 506 507 ... ... Exemplo: main(){ int a = 8, b = 98; int *p1, *p2; p1 = &a; p2 = &b; p1 = p2; ... Ponteiros - Atribuição Ponteiros podem ser atribuídos como uma variável qualquer: ... ... 501 8 502 503 504 505 506 507 ... ... int a = 8, b = 98; int *p1, *p2; main(){ p1 = &a; p2 = &b; p1 = p2; ... Ponteiros - Atribuição Ponteiros podem ser atribuídos como uma variável qualquer: ... ... 501 8 502 98 503 504 505 506 507 ... ... int a = 8, b = 98; int *p1, *p2; main(){ p1 = &a; p2 = &b; p1 = p2; ... Ponteiros - Atribuição Ponteiros podem ser atribuídos como uma variável qualquer: ... ... 501 8 502 98 503 504 505 506 507 ... ... int a = 8, b = 98; int *p1, *p2; main(){ p1 = &a; p2 = &b; p1 = p2; ... Ponteiros - Atribuição Ponteiros podem ser atribuídos como uma variável qualquer: ... ... 501 8 502 98 503 501 504 505 506 507 ... ... int a = 8, b = 98; int *p1, *p2; main(){ p1 = &a; p2 = &b; p1 = p2; ... Ponteiros - Atribuição Ponteiros podem ser atribuídos como uma variável qualquer: int a = 8, b = 98; int *p1, *p2; main(){ p1 = &a; p2 = &b; p1 = p2; ... ... ... 501 8 502 98 503 501 504 502 505 506 507 ... ... Ponteiros - Atribuição Ponteiros podem ser atribuídos como uma variável qualquer: int a = 8, b = 98; int *p1, *p2; main(){ p1 = &a; p2 = &b; p1 = p2; ... ... ... 501 8 502 98 503 502 504 502 505 506 507 ... ... ... ... 501 8 502 98 503 502 504 502 505 506 507 ... ... É importante notar que mesmo que declaremos uma variável após a outra, não existe garantia da posição em que elas estarão na memória. Isto está a cargo do compilador. Ponteiros - Atribuição Ponteiros - Atribuição � Outra coisa importantíssima de se lembrar quando usamos ponteiros é que sempre devemos inicializá-los de antemão. Se usarmos ponteiros não inicializados, podemos acabar acessando posições de memória que já estão sendo usadas por nosso programa ou que estão protegidaspelo sistema operacional. � Isso pode resultar em telas como essa: Ponteiros - Atribuição Aritmética de Ponteiros • Existem apenas duas operações aritméticas que podem ser usadas com ponteiros: adição e subtração. • A aritmética de ponteiros é relativa ao seu tipo base, ou seja, depende de quantos bytes o tipo ocupa na memória. Exemplo: Suponhamos que pont1 esteja apontando para a posição 1000. Após realizar um pont1++ ele passa a apontar para a posição 1004* * Obs.: Atualmente inteiros ocupam 4 bytes de memória! Aritmética de Ponteiros • Manipular ponteiros dessa forma pode ser útil, principalmente quando estamos tratando de arrays. • É importante notar que o nome de um array (sem um índice) já representa o endereço do primeiro elemento do array. int array_int[5] = {1,2,3,4,5}; float array_float[3] = {1.0,2.3,5.1}; int *p_int = array_int; float *p_float = array_float; ... Aritmética de Ponteiros • Os exemplos a seguir demonstram como é possível manipular arrays apenas usando ponteiros. printf (“%d” , *p_int); // imprime 1, o primeiro elemento do array printf (“%d” , *(p_int + 1) ); // imprime o 2 o segundo elemento printf(“%d” , p_int[0] ); // equivalente a *p_int printf(“%d” , p_int[1] ); // equivalente a *(p_int + 1) printf(“%f” , *(p_float + 2); // imprime 5.1 *(p_int + 1) // e agora prestem atenção printf(“%f” , *(p_float + 12) ); /* note que como nos arrays, podemos “desrespeitar” o tamanho usando essa construção * / ... O ponteiro NULL • Como já foi dito antes, quando declaramos uma variável em C ela começa com valores aleatórios (lixo). Para tentar impedir que nossos ponteiros sejam usados de maneira danosa para o nosso programa, é uma convenção comum em C usar o identificador NULL, definido na biblioteca stdlib.h . • Na realidade o NULL é equivalente a 0. Isso facilita testar nossos ponteiros, visto que se ele for NULL ele será avaliado como falso em um teste. O ponteiro NULL /* Prestem atenção no próximo exemplo, primeiro declaramos dois ponteiros para char */ char *pont = NULL,*pont2 = NULL; char string[100], string2[100]; int escolha = 0; scanf(“%1d %s”,&escolha,string); //lemos um dígito e uma string if (escolha != 0) //se o dígito for diferente de 0... pont = string; // apontamos para a string scanf(“%1d %s”,&escolha,string2); if (escolha != 0) pont2= string2; O ponteiro NULL if (pont != NULL) //testa se já atribuímos pont printf(“Primeira string: %s” , pont); /* observem que isso irá imprimir a variável string, já que pont contém o endereço dela */ if (pont2) // esse teste equivale a pont2 != NULL printf(“Segunda string: %s” , pont2); • Como podemos ver, o ponteiro NULL é muito útil para evitar erros comuns, decorrentes da não inicialização de ponteiros e afins. Exercícios • Faça uma função que recebe uma string e um caractere, e retorna um ponteiro para a primeira ocorrência do caractere na string, ou NULL caso não o encontre. DICA 1: Se um função deve retornar um ponteiro, ela tem que ser declarada da seguinte forma: tipo* funcao(parametros) DICA 2: Não se esqueçam que o ‘\0’ é o caractere terminador da string. char * acha_char(char *string, char a) { char *pont = NULL; int achou = 0; while(*string != '\0' && achou == 0) { if (*string == a) //testa o caractere atual { pont = string; achou = 1; } else string++; } return pont; } Resposta Resposta int main() { char *string = "Lucas Fialho Zawacki"; printf("%s\n", acha_char(string, 'F')); /* imprime “Fialho Zawacki” */ } Indireção Múltipla • Variáveis do tipo ponteiro, também podem apontar para outros ponteiros. • Para cada novo * na declaração da variável, estamos adicionando mais um nível de “indireção”. • Um exemplo bem simples seria: int num = 30, *p, **q; p = # q = &p; ... ... 501 30 502 503 504 505 506 507 ... ... Indireção Múltipla int num = 30, *p, **q; p = # q = &p; ... ... 501 30 502 503 504 505 506 507 ... ... Indireção Múltipla int num = 30, *p, **q; p = # q = &p; ... ... 501 30 502 501 503 504 505 506 507 ... ... Indireção Múltipla int num = 30, *p, **q; p = # q = &p; ... ... 501 30 502 501 503 502 504 505 506 507 ... ... Indireção Múltipla Indireção Múltipla • Usar mais do que um nível de ponteiros pode ser útil para manipular estruturas mais complexas como matrizes multidimensionais, embora seja mais recomendado utilizar a notação de array. Ponteiros void • Qualquer tipo de ponteiro pode ser convertido para o tipo (void*) e depois convertido de volta para o seu tipo, sem que haja perda de dados. • Esse tipo de operação será usada, principalmente para alocar memória dinamicamente. Alocação Dinâmica de Memória • Existem momentos em nossos programas, em que precisaremos requisitar memória em tempo de execução, por exemplo: • A criação de uma matriz cujo tamanho é fornecido pelo usuário. • A criação de listas com tamanho indefinido. • A biblioteca stdlib.h nos oferece algumas funções para nos facilitar o gerenciamento dessas necessidades. Alocação Dinâmica de Memória • A função malloc será usada para alocar uma quantidade ‘n’ de variáveis de algum tipo. • Seu protótipo é: void * malloc (unsigned int num); float *pont_f; pont_f = (float*) malloc(sizeof(float) * 30) ; // alocamos um array de 30 floats Alocação Dinâmica de Memória • Aspectos importantes do exemplo anterior: • A função sizeof(tipo) nos retorna o tamanho em bytes do tipo usado como argumento. Ela nos será útil para que não precisemos saber o tamanho de cada tipo (até porque eles podem variar de um computador para outro). • O ponteiro retornado é do tipo void*, então é necessário fazer uma conversão para o tipo que nós iremos usar. Alocação Dinâmica de Memória • Podemos manipular o ponteiro resultante como preferirmos, ou seja, usando a notação de arrays ou a de ponteiros. int i; for (i = 0; i < 30; i++) printf(“%f - ” , pont_f[i]); // imprimimos todos elementos do array Alocação Dinâmica de Memória • A função free é usada para liberar a mémoria que nós alocamos previamente em nosso programa. • O protótipo da função é: free (void* ptr); free(pont_f); // qual a importância dessa operação? Alocação Dinâmica de Memória • É importante liberar a memória que alocamos dinamicamente porque, caso não o façamos, ela ficará indisponível para nós até o fim do programa. Exercício Crie um programa com a seguinte definição: O programa deve ter, além da main, uma outra função void chamada DEVOLVE_PONTEIRO que recebe um array de inteiros e o tamanho do array e escreve na tela o maior valor contido nesse array. Ex.: Temos o array de tamanho 3 com os valores: 2, 4 e 5; DEVOLVE_PONTEIRO deve imprimir 5 na tela! Ex: void devolve_ponteiro(int *vet, int tam); Ex. de chamada na main: int vetor[3] = {1,2,3} , tam = 3; devolve_ponteiro(vetor, 3); USAR FOR PARA ACHAR O MAIOR VALOR!
Compartilhar