Esta é uma pré-visualização de arquivo. Entre para ver o arquivo original
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 protegidas pelo 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!