Baixe o app para aproveitar ainda mais
Prévia do material em texto
APOSTILA : LINGUAGENS DE PROGRAMAÇÃO C E C++ Angelo Passaro Página II.1 Segunda revisão / 2002 IIII.. EESSTTRRUUTTUURRAA DDEE PPRROOGGRRAAMMAASS EEMM CC EE CC++++ ((CCÓÓDDIIGGOO FFOONNTTEE)) Antes de iniciar a apresentação da estrutura de programas em C e C++, é interessante apresentar o conjunto de passos necessários para a geração de um código executável a partir do código fonte. Esses passos são representados, graficamente, na figura 1. fonte compilador pré-compilador fonte pré-compilado código objeto linker executável Compilador, conforme visto pelo usuário Figura 1 : Seqüência de passos para a geração de um programa executável em C e C++, partindo do código fonte O que há a ser notado nesta figura é a execução da compilação em duas etapas, o que, em geral, é transparente para o usuário. Num primeiro passo, é efetuada uma pré-compilação e gerado, internamente, um novo código fonte. Nesta fase de pré-compilação, são feitas as substituições e inclusão de cópias de arquivos indicadas por diretivas de pré-compilação. É este fonte modificado que será submetido ao compilador propriamente dito. De maneira geral, a estrutura de um programa em linguagem C ( e C++) é formada por um conjunto de declarações e definições de objetos (intrínsecos ou derivados), de funções, e de diretivas de pré-compilação, distribuídos em um, ou mais, arquivos fonte. As características de cada um desses itens são apresentadas no restante deste capítulo. II.A. Arquivos fonte Um programa em linguagem C (e C++) pode ser composto por um ou mais arquivos fonte. Um arquivo fonte é um arquivo texto que contém tudo o que é permitido num programa APOSTILA : LINGUAGENS DE PROGRAMAÇÃO C E C++ Angelo Passaro Página II.2 Segunda revisão / 2002 C (ou C++), ou seja, funções, declarações e definições de objetos (intrínsecos ou derivados), macros, definição de constantes, etc. Cada um desses arquivos é compilado em separado e, posteriormente todos são ligados (“linkados”). Um arquivo fonte não precisa conter comandos executáveis. Ele pode ser utilizado por exemplo, para definir variáveis em um arquivo fonte e então declarar referências a essas variáveis em outros arquivos fonte. É isto o que ocorre com os chamados arquivos de cabeçalho (arquivos que apresentam extensão .h em C). Como exemplo, sugere-se a consulta a um arquivo de cabeçalho que é enviado junto com o produto (compilador ), denominado time.h. Utilize para tal o editor de texto de sua preferência. Note-se que nesse arquivo é definido o tipo time_t que é utilizado no padrão ANSI. Cada produtor de compilador implementa este tipo da forma que achar mais conveniente, desde que as funções de informação de tempo definidas pelo padrão recebam e devolvam dados referentes a este tipo de variável de forma consistente. typedef long time_t; // definição do tipo time_t para o compilador C e C++ da // BORLAND II.B. Blocos II.B Bloco é qualquer conjunto de declarações, definições e comandos executáveis delimitados pelos caracteres { e }. Um bloco pode aninhar outros blocos: Exemplo: int nada (void) { // aqui começa o bloco da função nada int a, b = 2; a = 1; if ( a < b ) {// aqui começa o bloco executado se o teste resulta VERDADEIRO printf(“\n a é menor que b””); } // fim do bloco executado se o teste resulta VERDADEIRO else { // aqui começa o bloco executado se o teste resulta FALSO printf(“\n a é maior ou igual a b””); }// fim do bloco executado se o teste resulta FALSO } // fim do bloco de definição da função nada Neste exemplo, temos blocos aninhados, sendo o primeiro, e mais externo, o bloco de definição da função e os blocos delimitam as operações realizadas caso o teste efetuado resulte VERDADEIRO ou caso resulte FALSO. II.C. Definição de Objetos (e variáveis) Um objeto (variável) é definido usando a sintaxe: tipo identificador_variável [ = constante ou variável ]; APOSTILA : LINGUAGENS DE PROGRAMAÇÃO C E C++ Angelo Passaro Página II.3 Segunda revisão / 2002 onde tipo pode ser um tipo intrínseco ou derivado e o conteúdo entre colchetes indica que o objeto (variável) pode ser, opcionalmente, inicializado por meio de uma constante ou atribuição do valor de outro objeto. Exemplos: // note, em todos os exemplos, o uso do caracter de terminação de instrução, ;, o // uso da seqüência de caracteres de comentário e o alinhamento // Observe, também que a sintaxe de definição de um objeto, nestes exemplos, // em nada difere da definição de uma variável qualquer long int var1; // define uma variável com identificador var1 do tipo // intrínseco long int double dvar1 = 10.; // define uma variável com identificador dvar1 do tipo //double // dvar1 é inicializada com o valor 10. Matrix o_mat ; // define um objeto do tipo Matrix com identificador o_mat String o_str1 = “uma string”; // define um objeto do tipo String com identificador o_str1 // inicializada com uma “string literal” II.D. Declaração de funções (protótipos) e definição II.D II.D.1. Declaração A declaração de funções, denominada também de prototipagem, estabelece o nome para uma função (identificador), o tipo de retorno e uma lista de parâmetros formais. Em C, a declaração de funções não é obrigatória. Se o protótipo de uma função não é fornecido, o compilador constrói esse protótipo a partir das informações disponíveis. Contudo, desta forma, aumenta a possibilidade de ocorrência de erros causados por digitação. Desta forma, a declaração explícita de funções é uma prática recomendada por permitir ao compilador uma série de verificações quanto ao tipo de retorno e o número e tipo de parâmetros passados. Em C++ a declaração explícita das funções é obrigatória. A declaração (ou protótipo) de uma função segue a seguinte sintaxe: tipo identificador_função (lista de parâmetros); onde tipo é o tipo de retorno da função (similar às funções de outras linguagens) e pode ser um tipo intrínseco ou derivado. A lista de parâmetros corresponde aos parâmetros passados para a função (similar às subrotinas de outras linguagens). A lista pode ser composta por qualquer número de parâmetros e estes podem ser de tipos intrínsecos ou derivados. A declaração de função deve ser terminada pelo caracter ;. Existem, basicamente, duas maneiras de se passar parâmetros: por valor ou por referência, porém esta discussão será adiada até a apresentação de ponteiros. APOSTILA : LINGUAGENS DE PROGRAMAÇÃO C E C++ Angelo Passaro Página II.4 Segunda revisão / 2002 II.D.2. Definição Definir uma função corresponde a definir o conjunto de operações pelas quais essa função é responsável, assim como a definição das variáveis utilizadas localmente (visibilidade (escopo) de função). Na definição de uma função são repetidos os componentes da declaração, mas o ponto- e-virgula é substituído por um bloco contendo as operações. Todo o programa, nas linguagens C e C++, apresenta, no mínimo, uma função primária que recebe, necessariamente, o identificador main. Um programa tem sua execuçãoiniciada sempre a partir do main que, por sua vez, faz a chamada a outras funções e controla o fluxo de execução. Quando o main, ou qualquer outra função, aciona uma nova função, o controle de execução é passado a essa função. O retorno do controle à função que fez a chamada é feito a partir do comando return. Exemplos: // declaração da função com identificador media (prototipagem) float media (float, float); // o valor de retorno é declarado como sendo do tipo float // a função é declarada com dois parâmetros formais (entre // parênteses) do tipo float, e note-se que não é necessário // declarar o nome das variáveis associadas aos // parâmetros formais (ver seção II.F.2). // Note-se o terminador ; utilizado no final do protótipo. ..... // definição da função media float media (float a, float b) // Note-se que na definição são apresentados os nomes das // variáveis associadas aos parâmetros da função (a e b) { // aqui inicia-se o bloco de definição da função float med; // variável local do tipo float (seção II.F.2) med = (a + b) / 2; // calculo da média aritmética dos parâmetros return med; // retorno da média calculada. Note que med é do tipo // do valor de retorno definido no protótipo } // aqui termina o bloco de definição da função // utilização da função #include <iostream.h> void main(void) { float var1 = 20, var2 = 25, average; average = media(var1, var2); cout << average; } // fim da definição da função main Observação: APOSTILA : LINGUAGENS DE PROGRAMAÇÃO C E C++ Angelo Passaro Página II.5 Segunda revisão / 2002 A definição da função pode ser bem mais simples do que a apresentada no exemplo acima: float media (float a, float b) { return (a + b) / 2; // } II.E. Diretivas Instruem o pré-compilador a executar uma ação específica no texto do programa. Todas as diretivas são precedidas pelo operador de pré-compilação (#). As diretivas não são terminadas com o delimitador ;. II.E.1. #define Sintaxe: #define identificador texto_especificado Exemplos: #define PRETO 0 #define AZUL 1 Definição de macros #define absoluto(x) (x<0)? –x : x // consulte seção III.C sobre operador ternário Com a diretiva define o identificador passa a ser um sinônimo do texto_especificado e toda a ocorrência do identificador é substituída por esse texto na fase de pré-compilação. Este tipo de artifício aumenta a legibilidade do código fonte, sem afetar a eficiência de execução. Observação: Somente o identificador é substituído. Exemplos: #define LOOP_INTEIRO 1 ........... if ( a == LOOP_INTEIRO) // ocorre a substituição if (b == LOOP_INTEIRO_A) // não ocorre a substituição Apesar da utilidade da diretiva define na definição de macros, alguns cuidados devem ser tomados. É comum a ocorrência de SIDE EFFECTS, ou seja, efeitos indesejados de processamento devido à falta de atenção em seu uso. Para exemplificar esta afirmação, tomemos o exemplo da definição da macro absoluto. CÓDIGO FONTE CÓDIGO PRÉ-COMPILADO #define absoluto (x) (x < 0) ? –x : x APOSTILA : LINGUAGENS DE PROGRAMAÇÃO C E C++ Angelo Passaro Página II.6 Segunda revisão / 2002 float y ; ... x = -10; y = absoluto(x); x++; float y ; ... x = -10; y = (x < 0) ? –x : x; x++; O resultado armazenado na variável y é 10 e, em seguida, o conteúdo da variável x é incrementada de 1 (ver seção III.A.7), portanto, resultando x = -9. Até este ponto, o processamento é o esperado. Agora, suponhamos que o programador utilize um dos recursos da linguagem para escrever um código fonte mais compacto, por exemplo, utilizando o operador unário de incremento à direita diretamente no “parâmetro” da macro absoluto (atenção, o operador de incremento à direita somente realiza o incremento de um após o operando ter sido utilizado): CÓDIGO FONTE CÓDIGO PRÉ-COMPILADO #define absoluto (x) (x < 0) ? –x : x float y ; ... x = -10; y = absoluto(x++); float y ; ... x = -10; y = (x++ < 0) ? –x++ : x++; Neste caso, a seqüência de processamento é a seguinte: • o valor atual de x é comparado com zero; • em seguida, é aplicado o operador de incremento , resultando x = -9; • como o teste aplicado a dois passos resultou VERDADEIRO, é atribuído a y o valor absoluto de x atual, portanto, y = +9; • finalmente, após o segundo uso de x, ele sofre uma nova operação de incremento : x = -8. Note-se, portanto, que o resultado final da operação absoluto não é igual ao caso anterior, apesar de que a leitura direta da linha do código fonte não pareça conter nenhum erro. (De fato, não existiria nenhum erro se absoluto fosse uma função e não uma macro). Observação: A compreensão completa, por parte do leitor, deste efeito somente é possível após a compreensão do funcionamento do operador de incremento ++, apresentado na seção seção III.A.7 e após a familiarização do aluno com a compactação de instruções que as linguagens C e C++ permitem. APOSTILA : LINGUAGENS DE PROGRAMAÇÃO C E C++ Angelo Passaro Página II.7 Segunda revisão / 2002 II.E.2. #undef Sintaxe: #undef identificador A diretiva undef remove, para efeito de pré-compilação, a definição de um identificador, a partir do ponto especificado. Exemplo: #define REAL float ....... REAL variavel_real_1; // variável_real_1 é do tipo float, ou seja, o // identificador REAL é substituído por float #undef REAL // a partir deste ponto o identificador REAL não // é mais reconhecido ......... #define REAL double // neste ponto ocorre uma nova definição do // identificador REAL, agora sinônimo de double ..... REAL variavel_real_2; // variável_real_2 é do tipo double, ou seja, o // identificador REAL é substituído por double Observação: undef pode ser utilizado em um identificador não definido anteriormente. II.E.3. #include Sintaxes: #include “path_arquivo” #include <path_arquivo> Com esta diretiva, o conteúdo do arquivo especificado por path_arquivo é incluído no arquivo atual, na posição da diretiva. Sua utilidade pode ser sentida quando da confecção de um programa que envolve um número grande de arquivos fonte, com o objetivo de compartilhar um trecho de código comum a todos esse arquivos fonte. Exemplo: #include <stdio.h> // O conteúdo do arquivo stdio.h é incluído no texto do programa fonte. Normalmente, estes arquivos contêm definições de protótipos de função, diretivas de definição (define), definição de constantes e definição de classes e recebem a denominação de arquivos de cabeçalho.APOSTILA : LINGUAGENS DE PROGRAMAÇÃO C E C++ Angelo Passaro Página II.8 Segunda revisão / 2002 O pré-compilador usa o conceito de diretório, ou diretórios, padrão para a procura dos arquivos de cabeçalho. A localização dos diretórios depende da implementação do pré- compilador. O usuário pode indicar o “path” do diretório padrão por meio de uma chave especial na linha de comando do compilador (/i ou /I). Os arquivos especificados entre os símbolos < e > normalmente fazem parte dos arquivos de cabeçalho fornecidos pelo produtor do compilador, enquanto os colocados entre aspas indicam arquivos de cabeçalho definidos pelo programador. Arquivos de cabeçalho podem ser aninhados até um limite especificado pelo padrão ANSI. II.E.4. Diretivas de compilação condicional #if, #elif, #else, #endif Sintaxes: #if expressão constante bloco #elif expressão constante bloco #else // sem expressão bloco #endif Estas diretivas permitem o controle de compilação. Como exemplo, suponhamos um programa gráfico (lembrando que funções gráficas não estão padronizadas), que, se deseja, possa ser compilado em compiladores C e C++ de vários produtores de software. É possível utilizar estas diretivas e a diretiva define para definir o caminho de compilação, conforme apresentado no exemplo a seguir. Exemplo: #define BORLAND 1 #define MICROSOFT 0 ... void linha (int, int, int,int); // protótipo da função linha .... #if (BORLAND) #include <graphics.h> #elif (MICROSOFT) #include <graph.h> #else // algo mais #endif ... void linha (int x1, int y1, int x2, int y2) // definição da função linha { #if (BORLAND) APOSTILA : LINGUAGENS DE PROGRAMAÇÃO C E C++ Angelo Passaro Página II.9 Segunda revisão / 2002 line (x1,y1,x2,y2); #elif (MICROSOFT) _line (x1,y1,x2,y2); #else //....... #endif } // fim da definição da função linha Note-se que definimos os identificadores BORLAND e MICROSOFT, enquanto o resultado dos testes depende do valor entre parênteses ser VERDADEIRO (diferente de zero) ou FALSO (igual a zero). Uma maneira mais moderna de obter o mesmo resultado é utilizar o operador defined. Com este operador não é necessário definir todas as constantes : BORLAND, MICROSOFT, mas apenas uma delas. Sua aplicação sobre o identificador resulta FALSO se o identificador não foi definido por meio de uma diretiva define, ou VERDADEIRO, caso contrário. Exemplo: #define BORLAND ... void linha (int, int, int,int); // protótipo da função linha .... #if defined (BORLAND) #include <graphics.h> #elif defined(MICROSOFT) #include <graph.h> #else // algo mais #endif ... void linha (int x1, int y1, int x2, int y2) // definição da função linha { #if defined (BORLAND) line (x1,y1,x2,y2); # elif defined (MICROSOFT) _line (x1,y1,x2,y2); #else //....... #endif } // fim da definição da função linha II.F. Tempo de vida, Escopo e Visibilidade APOSTILA : LINGUAGENS DE PROGRAMAÇÃO C E C++ Angelo Passaro Página II.10 Segunda revisão / 2002 São conceitos importantes para compreensão das regras que determinam como objetos e funções são utilizados nos programas. II.F.1. Tempo de Vida É o período durante a execução de um programa no qual a variável ou função existe. Identificadores com uma classe de armazenamento static (também chamados globais) apresentam valor definido durante toda a execução do programa. Um identificador declarado sem o especificador de classe de armazenamento static e declarado no interior de um bloco (identificador local) é armazenado somente enquanto a execução do programa encontra-se no interior do bloco. Esse tipo de identificador é reconhecido somente no período em que o programa está executando o bloco. Toda vez que o bloco é executado é alocado espaço para esse identificador e para o valor associado a ele. Esse espaço é liberado quando a execução abandona o bloco. As seguintes regras são úteis para compreender quando um identificador apresenta tempo de vida global (static) ou local: • Todas as função apresentam tempo de vida static, ou seja, existem durante toda o tempo de execução; • Identificadores declarados no nível externo, ou seja, fora de todos os blocos do programa, sempre apresentam tempos de vida static (globais); • Se uma variável local tem um inicializador, a variável é inicializada toda vez que ela é criada, a menos que esta seja declarada explicitamente com o especificador de classe de armazenamento static. Uma vez declarada static a variável retêm o valor associado a ela entre sucessivas chamadas do bloco; • Parâmetros de funções apresentam tempo de vida local. II.F.2. Escopo e Visibilidade II.F.2 A visibilidade de um identificador determina em quais porções do programa tal identificador pode ser referenciado, ou seja seu escopo. A visibilidade de um identificador pode ser limitada a um arquivo, a uma função, a um bloco ou a um protótipo de função no qual ele aparece. O escopo de um identificador é a parte do programa no qual o nome pode ser usado. O escopo é determinado pelo local no qual a declaração ocorre. As seguintes regras determinam os tipos de escopo dos identificadores no programa: Escopo de arquivo : A declaração de um identificador com escopo de arquivo ocorre fora de qualquer bloco ou lista de parâmetros e é acessível em qualquer ponto do programa após sua declaração. Em geral, identificadores com escopo de arquivo são chamados de identificadores globais ou externos. O escopo de um identificador global começa a partir do ponto em que ele é declarado ou definido. Escopo de função : Um rótulo (label) é o único tipo de identificador que tem escopo de função, sendo declarado implicitamente Quando usado em um comando (tipo goto). Escopo de bloco: A declaração de um identificador com escopo de bloco ocorre no interior APOSTILA : LINGUAGENS DE PROGRAMAÇÃO C E C++ Angelo Passaro Página II.11 Segunda revisão / 2002 de um bloco (seção II.B) ou na lista de declaração de parâmetros na definição de uma função (seção II.D). O identificador é visível somente a partir do ponto em que ele é declarado ou definido até o final do bloco que contém sua declaração ou definição. Seu escopo é limitado a aquele bloco e a quaisquer outros blocos no interior desse bloco. Tais identificadores costumam ser chamados de variáveis locais. Escopo de protótipo : Identificadores com escopo de protótipo ocorrem na declaração da lista de parâmetros em um protótipo de função. Seu escopo termina ao final da declaração da função. Escopo de classe Um nome (identificador) de um membro de classe é local a sua classe e só pode ser utilizado em uma função membro dessa mesma classe ou de uma outra classe derivada dessa primeira. (SOMENTE PARA C++) Observação: Um nome, por exemplo, com visibilidade global, pode ser escondido (“hiding”) se o mesmo identificador for declarado no nivel interno de um bloco. Veja comentários no exemplo abaixo . Exemplo: Neste exemplo são definidos 4 níveis de visibilidade, um externo e 3 níveis de visibilidade de bloco. #include <iostream.h> // cabeçalho com definição de cout e operador << int i = 1; // identificador definido em nível externo (visibilidade global) void main (void) // funçãomain com nível de visibilidade externa (global) { cout << “\n variável i de visibilidade global : ” << i; { // inicio de primeiro bloco aninhado int i = 2; j = 3; // i e j definidos no nível interno cout << “\nvariavel i de visibilidade interna : “<< i; cout << “\nvariavel j de visibilidade interna : “<< j; { // inicio do segundo bloco aninhado // a declaração da variável i com escopo de bloco esconde a variavel i global int i = 0; // identificador i redefinido cou t << “\nvariavel i com visibilidade do segundo bloco aninhado : “ << i; cout << “\n variavel j visivel no segundo bloco aninhado : “<< j; } // fim do segundo bloco aninhado cout << “\nvariavel i de visibilidade interna restaurada : “<< i; } // fim do primeiro bloco aninhado cout << “\nvariavel i de visibilidade global restaurada: ” << i; cout << “\n\n Note-se que a variavel i global apresenta tempo de vida durante toda a execução, \nenquanto que as variaveis definidas dentro dos blocos terminaram seu tempo de vida quando do fechamento dos respectivos blocos.”; APOSTILA : LINGUAGENS DE PROGRAMAÇÃO C E C++ Angelo Passaro Página II.12 Segunda revisão / 2002 } // fim da definição de main
Compartilhar