Programação em C++ Declarações e definições Programação em C++ Pimenta Rodrigues Pedro Pereira Manuela Sousa Declarações vs Definições • • • Declarar uma entidade, consiste em anunciar ao compilador que uma entidade com esse nome foi ou vai ser definida. Definir uma entidade, consiste em reservar espaço de memória para esta residir, especificar o seu comportamento e eventualmente inicia-la com um valor. A maior parte das declarações são também definições. Num programa, deve haver uma única definição para cada nome, mas poderão haver várias declarações de um mesmo nome, desde que todas elas sejam concordantes com o tipo da entidade declarada. • Qualquer declaração que especifica um valor é uma definição. • A declaração de uma variável de um tipo básico, de um tipo derivado, ou de um tipo definido pelo utilizador (Complex, Matrix, BigInt, etc.) só não será simultaneamente uma definição, quando antecedida da palavra chave extern, que informa o compilador que essa variável já se encontra definida algures. Programação em C++ Pimenta Rodrigues Pedro Pereira Manuela Sousa Declarações e definições 4-2 Declarações struct User; Declara User como nome de um novo tipo de objectos definido pelo programador. Complex sqrt(Complex); Declara sqrt() como o nome de uma função que devolve um valor do tipo Complex e recebe um parâmetro também do tipo Complex. extern int errorNumber; Declara errorNumber como o nome de um objecto tipo int, definido algures. Programação em C++ Pimenta Rodrigues Pedro Pereira Manuela Sousa Declarações e definições 4-3 Definições char c; Declara c como nome que designa um char e simultaneamente define c, isto é, reserva espaço em memória onde c passa a residir. int n = 10 Além de declarar n, define a variável e inicia-a com o valor 10, struct Complex { double real; double img; }; Além de declarar a estrutura Complex, define qual a sua composição typedef Complex Point; Além de declarar Point como o nome de um tipo, define que esse tipo é o Complex, pelo que os dois nomes se tornam equivalentes. char* name1 = "Toto"; Declara e define name1 como apontador para char, reserva espaço em memória com a dimensão necessária para conter a string de iniciação, promove a própria iniciação desse string e afecta o apontador nome1 com o valor do primeiro endereço do espaço reservado ao string. char name2[] = "Toto"; Idêntica à instrução anterior, salvo que name2 fica a ser um apontador constante, isto é, name2 não pode ser posto a apontar para outro endereço que não seja aquele com que foi iniciado. char name2[8] = "Toto"; Idêntica à instrução anterior, salvo que o espaço reservado para o string é neste caso 8 bytes,independente da dimensão do string de iniciação. Programação em C++ Pimenta Rodrigues Pedro Pereira Manuela Sousa Declarações e definições 4-4 Alcance de um nome (Scope) Uma declaração introduz um nome com um alcance. Esse nome só tem validade na parte específica do programa em que tenha sido int x; // x global void f() { //Esta declaração ocultou o nome global x. int x=1; // atribuição realizada para a variável global x. ::x=2; } declarada. Isto é um erro, porque estão duas variáveis declaradas com o mesmo nome no mesmo scope. void f( int x ) { //Esta declaração ocultou o nome global x int x=1; // Atribuição realizada para a variável global x ::x=2 } Programação em C++ Pimenta Rodrigues Pedro Pereira Manuela Sousa Declarações e definições 4-5 Tempo de existência de um objecto (1) • Um objecto é uma entidade resultante de uma definição, ao qual está associado um nome, um tipo e para o qual foi reservado espaço adequado em memória. • A menos que o programador especifique em contrário, um objecto é criado e iniciado quando da sua definição e é destruído quando o programa ultrapassa o seu alcance. • Os objectos com nomes globais, são criados e iniciados uma vez, e vivem até que o programa termine. • Objectos declarados antecedidos da palavra chave static, comportam-se da mesma maneira que objectos globais, no que se refere a tempo de existência. • Uma variável estática que não implicitamente iniciada a zeros. • Usando os operadores new e delete, podem criar-se objectos cujo tempo de vida é directamente controlado pelo programa. seja explicitamente iniciada, fica Programação em C++ Pimenta Rodrigues Pedro Pereira Manuela Sousa Declarações e definições 4-6 Tempo de existência de um objecto (2) // x e p criados e iniciados no inicio do programa e destruídos no final // do programa. int x = 10; // Inteiro iniciado com 10. int *p; // Apontador iniciado com 0 (NULL). int *f() { // y criado e iniciado com a valor aleatório quando // função f() é invocada e destruído quando ela termina. long y; // l criado no início do programa, iniciado com 10 na // primeira chamada a f() e destruído no fim do programa. static int l = 10; p = new int(3); // Criar um inteiro com valor 3. Este não é destruído // quando a função termina. } void main() { int *pi = f(); delete pi; } // pi e afectado com o apontador retornado pela função // Destruir o inteiro apontado por pi. Programação em C++ Pimenta Rodrigues Pedro Pereira Manuela Sousa Declarações e definições 4-7 Constantes Em C++ existem 4 formas de definir constantes: Adicionando a keyword const à definição Exº : const pi=3.14159265 •Por enumerados Exº : enum Escapes {BELL=‘\a’, TAB=‘\t’; enum Meses {Jan=1, Fev, Mar, Abr, Mai, Jun, Jul, Ago, Set, Out, Nov, Dez }; •Nomes de arrays Exº : char buf[10]; ++buf; // erro: buf é um apontador constante •Nomes de funções Exº : int funcao(char c); Apontadores constantes Programação em C++ Pimenta Rodrigues Pedro Pereira Manuela Sousa Declarações e definições 4-8 Constantes numéricas O tipo das constantes numéricas pode ser explicitado pelo modo como são escritas: • Para os tipos inteiros, o sufixo U designa unsigned, o sufixo L designa long e o sufixo UL designa unsigned long. if ( value == 30500L ) • Para os tipos reais existem os sufixos F para float e L para long double flag = ( result == 1.5L ); • A representação das constantes reais, envolvem um ponto decimal ou a letra e seguida de um valor inteiro, significando a potência de base 10 pela qual se deve multiplicar o número situado à sua esquerda. Esta última notação designa-se por notação científica. // Representação em ponto decimal. const double pi = 3.14159265; // Representação científica. const double pi = 314159265e-08; Programação em C++ Pimenta Rodrigues Pedro Pereira Manuela Sousa Declarações e definições 4-9 Caracteres especiais Sequências de escape Caracter \a som da campainha (bell ) \b retrocesso (back space) \f inicio de página \n mudança de linha \r inicio de linha \t tabulação horizontal \v tabulação vertical \\ back slash \’ plica \” aspas \000 número em octal \xhh número em hexadecimal Programação em C++ Pimenta Rodrigues Pedro Pereira Manuela Sousa Declarações e definições 4 - 10 Enumerados enum Meses {JAN=1, FEV, MAR, ABR, MAI, JUN, JUL, AGO, SET, OUT, NOV, DEZ }; Meses mes; // ... switch ( mes ) { void f() { Meses m=JAN; int i=FEV; // Conversão implícita. m= i ; case JAN: // … // Erro!..(tem que se explicitar a conversão). m=Meses(i); // A conversão explicita é necessária. I = m; // OK (conversão implícita). m=4; // Erro!(tem que se explicitar a conversão). m = Meses(4);// A conversão explicita é necessária. break; case FEV: // ... break; } } Programação em C++ Pimenta Rodrigues Pedro Pereira Manuela Sousa Declarações e definições 4 - 11 Tipos fundamentais Tipo de valores armazenados Caracteres Inteiros Reais Lógicos char unsigned char signed char int unsigned short unsigned short long unsigned long float double long double bool Programação em C++ Pimenta Rodrigues Pedro Pereira Manuela Sousa Declarações e definições 4 - 12 Conversão implícita de tipos • Quando numa expressão estão envolvidas variáveis de tipos fundamentais diferentes, os tipos das variáveis são automaticamente convertidos para um mesmo tipo, que passa a constituir o tipo do valor da expressão. • Em expressões aritméticas, sempre que a um operador estejam presentes operandos de diferentes tipos, ocorre uma conversão implícita do operando do cuja representação em memória é de menor dimensão, no de representação com maior dimensão. • Em igualdade de espaço de representação prevalece o tipo com maior alcance de representação. Assim, entre float e long prevalece float. E entre qualquer dos tipos a variante unsigned prevalece sobre a variante signed. int n; long int i; char c; double d; float f; n+I // n é convertido em long int e a expressão toma o valor long int. n+c // c é convertido em int e a expressão torna-se do tipo int. c+d // c é convertido em double e a expressão torna-se do tipo double. -1L < 1U // 1U é convertido long -1L > 1UL // -1L é convertido num unsigned long Programação em C++ Pimenta Rodrigues Pedro Pereira Manuela Sousa Declarações e definições 4 - 13 Tipos derivados Operadores: * & [] () Apontador -> operador unário prefixo Referência -> operador unário prefixo Array -> operador unário posfixo função -> operador unário posfixo Definições: estruturas classes int* a; // float v[10]; // char* p[10]; // typedef char* p[10]; // void f(int); // struct Str { // short length; char* p; }; Apontador para um inteiro. Array de 10 elementos tipo float. Array de 10 apontadores para char. Tipo array de 10 apontadores para char. Função com um argumento tipo int. Estrutura com dois campos. Programação em C++ Pimenta Rodrigues Pedro Pereira Manuela Sousa Declarações e definições 4 - 14 Apontadores (1) char** cpp; //Apontador para apontador para carácter. int (*vp)[10]; //Apontador para array de 10 inteiros. int (*fp)(char, char*); /* fp apontador para função que retorna inteiro e que recebe dois parâmetros, um do tipo char e outro do tipo apontador para char */ Ende re ço 0x0000 char c, //c é um carácter. *p; //p é um apontador para carácter. p = &c; *p ='a'; // p agora aponta para 0x03C5 c M e m ória RAM . . . 'a' . . . c. 0x15F2 p // c = 'a' 0xFFFF 0x03C5 . . . Programação em C++ Pimenta Rodrigues Pedro Pereira Manuela Sousa Declarações e definições 4 - 15 Apontadores (2) O que faz esta função ? int strlen(const char* p) { char* q=p; } while (*q++); // Enquanto o caracter apontado por q for // diferente de '\0', incrementa o apontador. return q-p-1; // Retorna número de caracteres do string. void maior(int* pa, int *pb) { int tmp; } if(*pa < *pb) { tmp = *pa; *pa = *pb; *pb = tmp; } void main() { int a = 5, b = 6; } Quais os valores de a e b após a chamada à função maior(…) ?? maior(&a, &b); Programação em C++ Pimenta Rodrigues Pedro Pereira Manuela Sousa Declarações e definições 4 - 16 Desreferência int *p[5]; // Array de 5 apontadores para inteiro ... // afectação do array p com apontadores de inteiros. int a=1; // Inteiro iniciado com o valor 1. *p[a+3]=7;// Afecta com 7 o inteiro apontado por p[a+3]. P+1 P+2 P+3 P+4 P P[0] ou *p *p[0] ou **p P[1] ou *(p+1) P[2] ou *(p+2) P[3] ou *(p+3) *p[2] ou **(p+2) *p[1] ou **(p+1) P[4] ou *(p+4) *p[4] ou **(p+4) *p[3] ou **(p+3) Programação em C++ Pimenta Rodrigues Pedro Pereira Manuela Sousa Declarações e definições 4 - 17 Tipo void e void * void • • //Função que não tem valor de retorno. Aplica-se como o nome de um tipo, apesar de não existir nenhum objecto do tipo void. Serve para referir que uma função não retorna nenhum valor. void f(); //Apontador para um objecto de tipo desconhecido. void* pv; //Função sem argumento equivalente a int g(). int g(void); void * • • • Serve para referir um tipo de apontador que aponta para objectos de tipo desconhecido. Qualquer tipo de apontador pode afectar uma variável do tipo void*. O inverso não é permitido sem fazer cast explicito. Um apontador tipo void* é útil para poder guardar um endereço genérico, podendo ser explicitamente convertido num apontador para um dado tipo de objecto. void* malloc(unsigned size); void free(void *); void f() { //Conversão explicita. int* pi=(int*)malloc(10*sizeof(int)); //Conversão explicita. char* pc=(char *)malloc(10); // ... free(pi); // Conversão implícita. free(pc); // Conversão implícita. } Programação em C++ Pimenta Rodrigues Pedro Pereira Manuela Sousa Declarações e definições 4 - 18 Referências (1) • O C++ permite passagem de parâmetros a funções por referência (&), mais fáceis de usar do que os apontadores. • Tal como um apontador, uma referência corresponde a um local onde se situa outra variável. • Mas tal como uma variável normal, não requer operador especial de desreferênciação. Exº: int x; int& y=x; • Ambas as variáveis referem o conteúdo da mesma localização de memória, e são de facto a mesma variável. Programação em C++ Pimenta Rodrigues Pedro Pereira Manuela Sousa Declarações e definições 4 - 19 Referências (2) • Uma referência é um nome alternativo (alias) para um objecto. • Na definição de referência, esta tem que ser sempre iniciada. • O valor de uma referência não pode mudar após iniciação e refere sempre o objecto para o qual foi iniciado, ou seja uma referência não é um objecto. int i=1; int& r=i; //r e i referem-se ao mesmo int int x=r; //x=1 r=2; //i=2 equivalente int i=1; int* r = &i; int x=*r; //x=1 *r=2; //i=2 Programação em C++ Pimenta Rodrigues Pedro Pereira Manuela Sousa Declarações e definições 4 - 20 Referências (3) • Quando um objecto referenciado não é um lvalue ( não é um objecto do qual se pode tomar o endereço). – É aplicada uma conversão de tipo se necessário. – O valor resultante é colocado numa variável temporária. – O endereço dessa variável é usado como valor do iniciador. A declaração : double& dr=1; Envolve as seguinte acções: const double* drp; double temp; temp=double(1); drp = &temp; Programação em C++ Pimenta Rodrigues Pedro Pereira Manuela Sousa Declarações e definições 4 - 21 Referências vs apontadores Acções inválidas para referências e válidas para apontadores. – – – – – – Apontar para uma referência. Tomar o endereço de uma referência. Comparar referências. Afectar referências. Realizar operações aritméticas sobre referências. Modificar referências. Se qualquer destas operações for efectuada sobre referências, o que acontece na realidade é que iremos actuar sobre o objecto com a qual a referência está relacionada, ou seja, o objecto referenciado. Programação em C++ Pimenta Rodrigues Pedro Pereira Manuela Sousa Declarações e definições 4 - 22 Passagem de parâmetros a funções • Os identificadores dos argumentos que constam na declaração da função, dizem-se parâmetros formais. • Quando se evoca a função, os parâmetros que se lhe associam dizem-se parâmetros actuais, e irão iniciar os parâmetros formais (ou argumentos). • O corpo de uma função nunca afecta os parâmetros actuais com que seja evocada, a menos que o parâmetro formal seja do tipo referência para o parâmetro actual. Qual a diferença entre os seguintes troços de código ??? void f(int val, int &ref) { val++; ref++; } void f(int val, int *pt) { ++val; ++(*pt); } void main() { int n1 = 5, n2 = 5; f(n1, n2); cout << "n1 = " << n1 << " n2 = " << n2 << endl; } void main() { int n1 = 5, n2 = 5; f(n1, &n2); cout << "n1 = "<< n1 << " n2 = " << n2 << endl; } Programação em C++ Pimenta Rodrigues Pedro Pereira Manuela Sousa Declarações e definições 4 - 23 Definição de estruturas Definição de uma estrutura : Acesso aos campos da estrutura. struct Data { int ano; int mes; int dia; }; Data d; … d.ano = 1997; d.mes = 1; d.dia=23; Acesso aos campos da estrutura através de um pointer Data *pd = &d; … pd->ano = 1997; pd->mes = 1; pd->dia=23; Equivalente Data *pd = &d; … (*pd).ano = 1997; (*pd).mes = 1; (*pd).dia=23; Programação em C++ Pimenta Rodrigues Pedro Pereira Manuela Sousa Declarações e definições 4 - 24 Referência como argumento de funções (1) struct Grande { // estrutura de grande dimensão. int item; char text[1000]; // espaço com fartura ... }; Grande bo={123,"isto é uma estrutura de grandes dimensoes..."}; // Três funções que têm a estrutura Grande como argumento. void funcval(Grande v1); // Chamada por valor. void funcptr(const Grande *p1) ;// Chamada por apontador. void funcref( const Grande &r1);// Chamada por referência. void main() { funcval(bo); // Passagem do próprio objecto. funcptr(&bo); // Passagem do endereço do objecto. funcref(bo); // Passagem por referência } Programação em C++ Pimenta Rodrigues Pedro Pereira Manuela Sousa Declarações e definições 4 - 25 Referência como argumento de funções (2) void funcval(Grande v1) { //Notação de valor. cout << '\n' << v1. item; cout << '\n' << v1.text; } void funcptr ( const Grande *p1) { //Notação de apontador. cout << '\n' << p1->item; cout << '\n' << p1->text; } void funcref ( const Grande &r1) { //Notação de referência. cout << '\n' << r1.item; cout << '\n' << r1.text; } Programação em C++ Pimenta Rodrigues Pedro Pereira Manuela Sousa Declarações e definições 4 - 26 Prefixo const • Prefixando a declaração da referência com const torna constante o objecto referenciado, o que significa que o valor do objecto referenciado por esse argumento não pode ser alterado pela função. • Prefixando a declaração de um apontador com const torna constante o objecto apontado, o que significa que o valor do objecto apontado por esse argumento não pode ser alterado pela função. int x=1, y; int next(const int& aa) { return aa++; // ERROR } y = next(x); int x=1, y; int next(const int& aa) { return aa+1; // OK } y = next(x); // y = 2 e x=1 Programação em C++ Pimenta Rodrigues Pedro Pereira Manuela Sousa Declarações e definições 4 - 27 Referência como retorno de funções O retorno de funções por referência, pode ser usado para definir funções que se possam situar do lado esquerdo de uma expressão (lvalue). Verifique a semelhança entre os seguintes troços de programa. int v[20]; int& g(int i) {return v[i];} g(3)=7; Equivalente int v[20]; int* g(int i) {return &v[i];} *g(3)=7; Programação em C++ Pimenta Rodrigues Pedro Pereira Manuela Sousa Declarações e definições 4 - 28 Sobrecarga (overloading) de funções • Em C++, várias funções podem partilhar o mesmo nome (function overloading), desde que cada uma delas se possa distinguir das outras pelo número e tipo dos argumentos com que foi definida. • A sobrecarga do nome de uma função permite em C++ criar uma família de funções diferentes, partilhando o mesmo nome mas com código distinto, exigindo para isso que tenham pelo menos um argumento de tipo diferente. Por exemplo: int f(int); double f(double); // ... f(3); // invoca f(int); f(3.14)//invoca f(double); Programação em C++ Pimenta Rodrigues Pedro Pereira Manuela Sousa Declarações e definições 4 - 29 Funções inline • Quando uma função é definido como inline, o compilador insere o próprio código da função em todos os pontos em que essa função seja invocada pelo programa (se de um ficheiro fonte constam três chamadas a uma função definida como inline, o código dessa função será três vezes repetido no ficheiro objecto). • A vantagem das funções inline reside no facto de terem maior rapidez de execução, dado que ficam libertas dos mecanismos de chamada, de passagem de parâmetros e retorno de valores. Esses mecanismos, no caso da função envolver poucas instruções, constituem um acréscimo significativo em tempo de execução. • O uso criterioso de funções inline implica que sejam simples e curtas, para que se justifique a sua adopção. Programação em C++ Pimenta Rodrigues Pedro Pereira Manuela Sousa 4 - 30 Funções inline Usar o prefixo inline na definição das funções. inline int greate(int a, int b) { return a > b; } inline void swap(int &a, int &b) { int aux = a; a =b; b = aux; } void main() { int x, y; cout << “Escreva dois inteiros ->”; cin >> x >> y; cout << “O maior e´o “ << (greate( y, x ) ? “segundo” : “primeiro”); if ( greate( y , x ) swap( x, y ); cout << x << “ e’ maior que << y << endl; } É equivalente a inserir o código da função em todos os pontos em que é evocada . void main() { int x, y; cout << “Escreva dois inteiros ->”; cin >> x >> y; cout << “O maior e´o “ << y > x ? “segundo” : “primeiro”); if ( y > x ) { int aux = x; x = y; y = aux; } cout << x << “ e’ maior que << y << endl; } Programação em C++ Pimenta Rodrigues Pedro Pereira Manuela Sousa 4 - 31 Algoritmos recursivos • Uma função diz-se recursiva, quando se invoca a si própria na consecução dos objectivos a que se propõe. • Algoritmos recursivos advêm normalmente de definições por relações de recorrência. • Um algoritmo na forma recursiva, traduz-se numa função contendo uma referência explícita a si própria (recursividade directa) ou uma referência a outra função, que no seu corpo a venha a referenciar directa ou indirectamente (recursividade indirecta). Programação em C++ Pimenta Rodrigues Pedro Pereira Manuela Sousa 4 - 32 Algoritmos recursivos (factorial) Um exemplo paradigmático de um algoritmo recursivo é a função para calcular o factorial. Definição: Função factorial em C++ que respeita a definição: int factorial( int n ) { if (n == 0) return 1; else return factorial(n-1)* n; } •O factorial de zero é um. ( 0! = 1 ) Factorial de n é o factorial de (n-1)*n. ( n! = (n -1)! * n ) Esquema de execução da função factorial, quando for pedido o factorial(4). fact(4) 24 = 6 * 4 6=2* 3 fact(3) 2=1* 2 fact(2) 1=1* 1 Chamada fact(1) fact(0) 1 Retorno Programação em C++ Pimenta Rodrigues Pedro Pereira Manuela Sousa 4 - 33 Algoritmo de ordenação-quicksort • É um dos algoritmos de ordenação mais eficientes. • O quicksort pode ser descrito da seguinte forma: 1. O array está ordenado se tiver menos que 2 elementos (condição terminal); 2. Escolher um dos elementos do array para pivot; 3. Decompor o array em duas partes disjuntas. Na parte esquerda colocar elementos menores ou iguais ao pivot, e na parte direita os maiores ou iguais ao pivot; 4. Ordenar a parte esquerda do array. 5. Ordenar a parte direita do array. Programação em C++ Pimenta Rodrigues Pedro Pereira Manuela Sousa 4 - 34 Algoritmo de ordenação-quicksort inline void swap( int &a, int &b ){ int aux= a; a= b; b= aux; } void quickSort( int a[], int min, int max ) { if ( min >= max ) return; // O array está ordenado. int i = min, j = max; int pivot = a[ (i+j)/2 ]; // valor do elemento central. do { // Dividir em duas partes. while( a[i] < pivot ) ++i; while( a[j] > pivot ) --j; if ( i<=j ){ if ( a[i] != a[j] ) swap( a[i], a[j] ); ++i; --j; } } while( i <= j ); quickSort( a, min, j ); // Ordenar parte esquerda. quickSort( a, i, max ); // Ordenar parte direita. } Programação em C++ Pimenta Rodrigues Pedro Pereira Manuela Sousa 4 - 35