1 Objectivos. 2 Linguagem C. Arquitecturas e Sistemas Operativos (2 a Parte) AT 1: Programação em C. Rever alguns aspectos da programação em C

Documentos relacionados
Algoritmos e Estruturas de Dados I (DCC/003) 2013/1. Estruturas Básicas. Aula Tópico 4

Linguagem C: Introdução

Introdução a Programação de Jogos

INTRODUÇÃO A LINGUAGEM C

Capítulo 2 Operadores. A função scanf()

Programação Básica. Estrutura de um algoritmo

Introdução à Linguagem C Variáveis e Expressões

Computação para Informática - Prof. Adriano Joaquim de Oliveira Cruz Segunda Aula Prática - 3 de setembro de 2010

Funções de Entrada e Saída

Linguagem C: funções e ponteiros. Prof. Críston Algoritmos e Programação

Universidade Estadual de Mato Grosso do Sul Ciência da Computação Algoritmos e Estruturas de Dados I (AED-I) Prof. Nilton

Linguagem e Técnicas de Programação

Linguagem C. Prof.ª Márcia Jani Cícero

Estrutura de Programas e Tipos de Dados Simples

Apresentação da ferramenta de programação. Comandos de entrada e saída. Prof. Alex Camargo

Estrutura do programa

Algoritmos e Programação. Linguagem C Procedimentos e. Eliane Pozzebon

Conceito de procedimentos e funções

Tipos Abstratos de Dados. Estrutura de Dados

Computação L2. Linguagem C++ Observação: Material Baseado na Disciplina Computação Eletrônica.

PROGRAMAÇÃO DE MICROPROCESSADORES 2011 / 2012

Programação Imperativa. Lição n.º 16 A pilha de execução

Ambientação com a Sintaxe de Java: parte 1

Linguagem C: Variáveis e Operadores. Prof. Leonardo Barreto Campos 1

Aula 4. Programa para tabelar potências de base 2 Análise de erros de overflow/underflow

Funções em C. Lucas Ferrari de Oliveira Professor Adjunto. Linguagem de Programação Estruturada I. Universidade Federal do Paraná

Computação para Informática - Prof. Adriano Joaquim de Oliveira Cruz Segunda Aula Prática - 29 de agosto de 2008

Computação I (MAB120) DCC/UFRJ

Linguagem C. Eliane Pozzebon

Operadores. Tipo de operadores. Aritméticos. Relacionais. Lógicos. Bit a bit. Cálculos aritméticos: soma, subtracção, multiplicação, divisão, etc.

Programação Orientada a Objetos. Manipulação de Exceções

(SCC-0120) Linguagem C

Fabiano Moreira.

Variáveis, Tipos de Dados e Operadores

UNIVERSIDADE DO VALE DO RIO DOS SINOS - UNISINOS CENTRO DE CIÊNCIAS EXATAS E TECNOLÓGICAS - CENTRO 06. Funções, variáveis, parâmetros formais

Outline. 33. Manipulação de arquivos DIM

a) Calcule o valor médio de CPI apresentado na execução deste programa P, utilizando-se C1 sem e com otimização.

Linguagem de Programação I. Aula 10 Funções

Linguagem C Controle do Fluxo de Execução. Lógica de Programação

Linguagem C. Programação Estruturada. Modularização (Funções) Prof. Luis Nícolas de Amorim Trigo

Pedro Vasconcelos DCC/FCUP. Programação Funcional 2 a Aula Tipos e classes

cadeia de caracteres (string) INF Programação I Prof. Roberto Azevedo

5 Arrays. 5.1 Criando um array. 5.2 Inicializando arrays. c:\>java Array Janeiro tem 31 dias.

Instituto Superior de Engenharia de Lisboa

MC-102 Aula 10 Vetores

Universidade de São Paulo São Carlos Instituto de Ciências Matemáticas e de Computação. Material preparado pela profa Silvana Maria Affonso de Lara

O vetor é provavelmente um dos mais simples e importantes tipos agregados. Através do seu uso, podemos

Linguagem C: variáveis, operadores, entrada/saída. Prof. Críston Algoritmos e Programação

Algoritmos e Estruturas de Dados I. Funções. Pedro Olmo Stancioli Vaz de Melo

Introdução a classes e objetos. Prof. Marcelo Roberto Zorzan Prof a. Rachel Reis

Introdução à Programação Orientada a Objetos em C++

5. Vetores e alocação dinâmica

1) Operadores de auto incremento ++ e auto decremento --

Unidade 5: Introdução à Programação com C/C++

1ª Lista de Exercícios

Introdução à Programação em C (I)

INF 1005 Programação I

Programando em Assembly

As funções são blocos de instruções que facilitam o entendimento do código. Sua sintaxe é:

MC102 Algoritmos e Programação de Computadores

Programação II. Introdução à Linguagem C

Linguagem C Tipos de Dados. void; escalares; sizeof Vectores; strings em C Estruturas Introdução ao pré-processador

Modularização: Funções em C

Recursividade UFOP 1/48

Linguagem de Programação

Conteúdo programático

Matrizes. DCC 119 Algoritmos

Programas Interativos

Tipos de Dados Simples

Dicas para implementação do Trabalho 6

Programação 2009/2010 MEEC - MEAer Laboratório 5 Semana de 26 de outubro de 2009

Introdução à Linguagem C++

Laboratório de Programação. Prof. Oscar Luiz Monteiro de Farias

Variáveis e constantes; Operadores e Tipos de Dados

Introdução à Programação

Professora Jeane Melo

Objectivos. Programação I (2010/2011) 2 o Trabalho de Programação

Linguagens de Programação I

Elementos de Linguagem C

Linguagem C. Programação Estruturada. Fundamentos da Linguagem. Prof. Luis Nícolas de Amorim Trigo

Estruturas de Dados Aulas 3 e 4: Uso da. 14/03/2011 e 16/03/2011

Classes e Objetos. Prof. Leonardo Barreto Campos 1

É usual respeitarem-se as seguintes convenções: Nome de uma classe começa por maiúscula (e.g. Solido)

Arrays. 6.2 Arrays. 6.1 Introdução. Elementos de um array são como as variáveis normais que vocês já conhecem

Estrutura de um Algoritmo, Variáveis, Comandos de Entrada e Saída e Expressões Aritméticas

INTRODUÇÃO À LINGUAGEM PASCAL PREFÁCIO

Introdução à Linguagem C. Adaptado de slides das Profas. Patrícia Jaques, Mônica Py, Deise Saccol e Vania Bogorny

Conceitos Básicos da Linguagem C++ Prof. Leonardo Barreto Campos 1

2. OPERADORES ALGORITMOS, FLUXOGRAMAS E PROGRAMAS FUNÇÕES... 10

Linguagens de Programação Conceitos e Técnicas. Amarrações

9 Classes Abstractas e Interfaces

Curso C: Ponteiros e Arrays

Controle de Fluxo. Laços e Desvios incondicionais

Capítulo 2: Introdução à Linguagem C

UNIVERSIDADE FEDERAL DO RIO GRANDE DO NORTE DEPARTAMENTO DE INFORMÁTICA E MATEMÁTICA APLICADA. DIM0320 Algoritmos e Programação de Computadores

Introdução. 17-out-08 Leandro Tonietto 2

Introdução a POO. Introdução a Linguagem C++ e POO

Para cada programa, por mais simples que seja, comece sempre por esboçar a solução desenhando um fluxograma.

Transcrição:

Arquitecturas e Sistemas Operativos (2 a Parte) AT 1: Programação em C 3 o Ano MIEEC 1 Objectivos Rever alguns aspectos da programação em C 2 Linguagem C A linguagem C é uma linguagem imperativa. Todos os programas têm que ter uma e uma só função de nome main. De facto, o programa mais simples em C (main.c) é: Para o compilar em Linux basta executar o comando: gcc -Wall main.c -o main Por momentos vamos ignorar o aviso: main.c: In function main : main.c:2: warning: control reaches end of non-void function e executá-lo:./main Nota: A substring./ que precede o nome do programa indica ao interpretador de comandos, vulgo shell, onde pode encontrar o programa main. E o resultado desta execução é... nada visível terminando o programa de imediato. De facto, outra coisa não seria de esperar porque a função main() não inclui qualquer instrução. Antes de avançar para um programa que faz alguma coisa, conviria eliminar o aviso do compilador. Para isso basta acrescentar a linha à função main():

O programa mais famoso O programa C mais famoso é o Hello, World! : printf("hello, World!\n"); O qual invoca a função printf() para imprimir a string Hello, World! na consola (mais correctamente, na saída standard). A primeira linha é uma directiva para incluir um ficheiro do tipo header com a declaração da função printf() (entre outras): Em C todas as funções têm que ser declaradas (ou definidas) antes de serem invocadas. (Ver mais à frente o que são a declaração, a definição e a invocação duma função.) Para saber que header file deverá incluir quando usa uma função da biblioteca C poderá executar o utilitário man, como se segue; man 3 printf Obviamente, se estiver interessado noutra função deverá substituir a string printf pelo nome dessa função. O número 3 especifica que se pretende a página do manual de programação, o qual é, por assim dizer, o 3 o volume do manual disponível através de man. Mais sobre printf() É claro que a função printf() da biblioteca C permite imprimir muito mais do que uma constante do tipo string, i.e. uma string cujo valor não varia, como é o caso de Hello, World!\n. (A propósito, o último carácter desta string, o carácter \n, representa o carácter nova linha e faz com que o cursor se desloque para o início da linha seguinte. Outro carácter especial de interesse é o carácter \t, o qual representa um tab, i.e. um número pré-definido de espaços em branco.) De facto, printf() permite imprimir um n o variável de argumentos. O primeiro é, digamos, a string principal, a qual especifica não só a parte fixa do que se pretende imprimir como o formato da parte variável, se alguma, sendo por isso designada por string de formatação. O formato da parte variável é descrito por uma string que consiste no carácter % seguido por um conversion specifier, o qual especifica o formato de representação da parte variável da string a imprimir. Em princípio, deverá haver um conversion specifier, e correspondente carácter %, por cada argumento de printf() que segue a string de formatação. O ficheiro digits.c ilustra o uso de printf() para imprimir os 10 algarismos um por linha. void main() { int i;

for(i = 0; i < 10; i++) { printf("%d\n", i); Neste caso como pretendemos imprimir apenas números inteiros usamos a string %d. Para uma especificação do formato e do significado dos conversion specifiers pode consultar a man page de printf() retornada por man 3 printf. A http://en.wikipedia.org/wiki/printf# printf_format_placeholderspginadawikipediasobre\texttt{printf apresenta um sumário dessa informação duma forma mais legível recorrendo a tabelas. Após compilar: gcc -Wall digits.c -o digits A execução de digits (dando o comando./digits) produz o resultado esperado: 0 1 2 [...] 8 9 Onde [...] indica que omiti algumas linhas. Tipos Na linguagem C, variáveis e constantes podem ser dum conjunto de tipos básicos, ou primitivos, tais como: char, para um carácter ou um inteiro de 8 bits com sinal; short int, int e long int para inteiros com sinal usando um n o diferente de bytes (a linguagem C não define o tamanho de cada um destes tipos, o que obriga a algum cuidado para garantir portabilidade do código). Para permitir uma escrita mais eficiente, C permite que se omita a string int em short int e em long int. float e double para números de vírgula flutuante de precisão simples e dupla, respectivamente. Além dos tipos inteiros com sinal descritos acima, C suporta ainda tipos inteiros sem sinal que se especificam precedendo as key words correspondentes com a string unsigned. Por exemplo: unsigned char ou unsigned long. (Note-se que neste último caso, estamos a usar apenas long em vez de long int.) Essencialmente, um tipo determina: 1. O conjunto de valores que uma constante ou variável pode assumir;

2. O conjunto de operações que se pode aplicar a uma constante ou variável desse tipo. Além disso, um tipo determina a forma como uma constante ou variável é representada internamente, i.e. ao nível do processador e da memória. O programa div em div.c ilustra o efeito do tipo duma variável no resultado da operação aritmética divisão: int m = 10; int n = 3; int q; double x = 10.0; double y = 3.0; double z; q = m/n; z = x/y; printf(" %d / %d = %d \n %f / %f = %f \n", m, n, q, x, y, z); Compilando-o e executando-o, obtém-se: 10 / 3 = 3 10.000000 / 3.000000 = 3.333333 Um dos aspectos mais subtis da linguagem C é a conversão entre tipos. De facto, C realiza conversão entre tipos quando numa expressão se usa constantes/variáveis de tipos diferentes. Contudo, os resultados nem sempre são os que se esperam, como ilustra o seguinte programa que se encontra no ficheiro conv.c: int m = 10; int n = 3; int q; double z; q = m/n; z = m/n; printf(" %d / %d = %d \n %d / %d = %f \n", m, n, q, m, n, z);

O qual produz: 10 / 3 = 3 10 / 3 = 3.000000 Tente alterar este programa para que a segunda expressão produza de facto o resultado da divisão não inteira. (Pode encontrar uma solução no ficheiro casts.c) Além dos tipos básicos, C permite ainda especificar tipos compostos tais como vectores (ou arrays) e estruturas de dados: int a[10]; // a is an array of 10 integers (not initialized) struct { double re, im; z; // z is a struct composed of two doubles Funções Uma função em C é muito semelhante a uma função matemática: tal como esta última, pode ser usada para mapear os valores dum determinado domínio, especificados no seu argument, em valores dum contradomínio, o valor retornado pela função. Em C, as funções aparecem no contexto de 3 construções, ou usos: Definição especifica as instruções que deverão ser executadas, sempre que a função é invocada, para produzir o resultado desejado a partir dos argumentos. Invocação determina a execução das instruções que constam na sua definição para um determinado valor de cada um dos seus argumentos. Declaração declara o nome da função bem como os tipos dos seus argumentos e dos valores de retorno. A declaração é usada essencialmente para simplificar a compilação. De notar que a declaração duma função nem sempre é necessária, nomeadamente se a invocação da função for precedida pela sua definição. O programa fdiv em fdiv.c ilustra precisamente este facto: double div(double x, double y) { double z; // Definition of div() z = x/y; return z; double u = 10.0; double v = 3.0;

printf("%f / %f = %f \n", u, v, div(u,v)); // Invocation of div() A compilação e a execução deste programa produzem os resultados esperados. Se definirmos a função div() depois da função main() (ver ficheiro no_decl.c) de forma a que a função div() é invocada antes de ser definida e sem que tenha sido previamente declarada obteremos o seguinte erro de compilação: gcc -Wall no_decl.c -o no_decl decl.c: In function main : decl.c:7: warning: implicit declaration of function div decl.c:7: warning: format %f expects type double, but argument 4 has type i decl.c: At top level: decl.c:12: error: conflicting types for div O que acontece aqui é que o compilador ao encontrar a invocação de div assume que retorna um inteiro e infere que os seus argumentos são do tipo double. Contudo, mais à frente, quando encontra a definição de div(), detecta que há um conflito, pois o valor de retorno é do tipo double e assinala um erro. Neste caso, tudo o que temos que fazer é declarar a função div() antes da primeira invocação, como ilustrado em decl.c: double div(double, double); // declaration of div() double u = 10.0; double v = 3.0; printf("%f / %f = %f \n", u, v, div(u,v)); // invocation of div() double div(double x, double y) { // definition of div() double z; z = x/y; return z; IMP.- Nem sempre a invocação duma função antes da sua definição e sem a sua declaração prévia dá origem a erros de compilação. Contudo, mesmo nestes casos o comportamento do programa pode ser diferente do desejado. Assim, é importante que sempre que usa uma função antes de a definir (ou se ela estiver definida noutro ficheiro, como é o caso de printf()) a declare previamente, ou de forma explícita ou incluindo um header file com essa declaração.

Passagem de argumentos duma função Um aspecto essencial da linguagem C é que os argumentos são passados por valor. I.e., quando da invocação duma função com argumentos, os valores dos argumentos especificados nessa invocação são copiados para uma região de memória apropriada e que será usada pela função invocada sempre que as suas instruções acederem (i.e. lerem ou escreverem) aos seus argumentos. Este aspecto da linguagem C pode ser determinante na correcção dum programa. O programa seguinte fun_args.c ilustra uma implementação errada duma função que troca o valor das variáveis que lhe são passadas como argumento: void swap(int a, int b) { int t; printf("in swap, before swap: a = %d b = %d\n", a, b); t = a; a = b; b = t; printf("in swap, after swap: a = %d b = %d\n", a, b); int n = 5; int m = 1; printf("before calling swap: n = %d m = %d\n", n, m); swap(n,m); printf("after calling swap: n = %d m = %d\n", n, m); De facto, o problema é que o que é passado à função swap() são os valores das variáveis m e n, os quais são atribuídos aos argumentos a e b. Depois, a função swap() opera apenas sobre estas variáveis e não sobre as variáveis m e n. Para conseguir o efeito desejado temos que usar apontadores. Apontadores Um apontador é uma variável que toma como valor um endereço. De facto, em C, apontadores não são apenas endereços, são endereços de variáveis/constantes dum determinado tipo, ou de forma mais breve endereços dum determinado tipo, o que tem algumas vantagens como veremos. Para suportar a manipulação de endereços, a linguagem C oferece 2 operadores: & o qual retorna o endereço da variável que é o seu operando; * o qual retorna o valor na posição de memória cujo endereço é o seu operando; O programa ptr em ptr.c ilustra o uso destes 2 operadores:

0: 1: 2: 3: int m = 10; 4: int *ptr; // pointer to integer 5: 6: ptr = &m; // initialize ptr with the address of m 7: printf("ptr = %p \t *ptr = %d \t m = %d \n", ptr, *ptr, m); 8: return 0 9: De notar, a linha 4 é a declaração da variável ptr como um apontador para inteiro. Esta declaração pode ser lida como: ptr é uma variável cujo valor apontado é um inteiro. Compilando-o e executando-o, obtém-se: ptr = 0xbfccc73c *ptr = 10 m = 10 Ou seja, à variável inteira m foi atribuída uma região de memória começando na posição de endereço 0xbfccc73c. E o valor dessa região de memória obtido usando a expressão *ptr está de acordo com o valor da variável m. Agora que sabemos como funcionam os apontadores poderemos corrigir a função swap() apresentada acima (ver ficheiro swap.c): void swap(int* a, int* b) { int t; printf("in swap, before swap: *a = %d *b = %d\n", *a, *b); t = *a; *a = *b; *b = t; printf("in swap, after swap: *a = %d *b = %d\n", *a, *b); int n = 5; int m = 1; printf("before calling swap: n = %d m = %d\n", n, m); swap(&n,&m); printf("after calling swap: n = %d m = %d\n", n, m); As alterações são menores, mas a diferença no resultado é imensurável: entre um resultado correcto e outro que não o é. De facto, a solução consiste em passar como argumentos a swap() não o valor

das variáveis cujos valores pretendemos trocar, mas os endereços dessas variáveis. Estes endereços são usados então dentro da função swap() para alterar os valores das posições de memória correspondentes. IMP.- Note-se que C continua a passar os argumentos por valor, apenas estes argumentos são endereços, o que permite produzir um efeito semelhante ao modo de passagem de argumentos por referência. O programa swap ilustra um dos principais usos de apontadores na linguagem C: a possibilidade de alterar o valor duma variável de forma visível fora da função que faz essa alteração. Um dos exemplos mais comuns do uso desta funcionalidade é a função scanf() da biblioteca C. A função scanf() é dual da função printf() no sentido de que enquanto esta última permite escrever na saída standard (normalmente a consola), scanf() permite ler da entrada standard (normalmente o teclado). O programa input em input.c ilustra o uso de scanf() para ler um inteiro do teclado: int n; printf("enter an integer... "); scanf("%d", &n); printf("\nread %d \n", n); Ou seja, neste caso, scanf() inicializa a região de memória apontada pelo endereço que lhe é passado como argumento, neste caso o endereço da variável n, com o inteiro introduzido através do teclado, inicializando desta forma a variável n com esse valor. Note-se que scanf() usa strings de formatação com um formato semelhante ao destas strings em printf(). É assim possível ler não só inteiros mas também números de vírgula flutuante bem como valores de outros tipos. Para uma explicação detalhada do uso desta função pode consultar a sua man page dando o comando man 3 scanf. Para um sumário mais legível pode consultar a página correspondente da Wikipedia http://en.wikipedia.org/wiki/scanf#format_string_specifications. Strings Em C, há uma relação estreita entre strings e endereços/apontadores. De facto, C não tem um tipo string, mas apenas um tipo char. Contudo, atendendo a que uma string é tipicamente guardada em memória com cada um dos seus carácteres em posições consecutivas, é possível referir uma string pelo endereço da sua primeira posição de memória. Contudo tal não basta. Como é que sabe qual é o último carácter duma string? A solução adoptada em C, é usar um carácter especial, conhecido por end of string character, o carácter de código 0, para marcar o fim duma string. O programa string em string.c ilustra este aspecto da linguagem C: // Strings are represented as pointers to their first character // The end of string is marked by the \0 character

char *str, *p; printf("string @ %p\n", "Hello World!\n"); str = "Hello World!\n"; for( p = str; *p!= \0 ; p++) printf("char @ %p: %c\n", p, *p); O que é que este programa faz? Verifique a sua resposta compilando-o e executando-o. Arrays A linguagem C suporta também arrays/vectores. O programa array em array.c ilustra alguns dos aspectos relacionados com a sintaxe de manipulação de vectores em C: 0: 1: 2: 3: int a[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9; 4: int i; 5: 6: for( i = 0; i < 10; i++ ) 7: printf("a[%d] = %d\n", i, a[i]); 8: 9: Em particular note o uso de [] na declaração dum vector (na linha 3), bem como na referência a um elemento particular desse vector na linha 7. Note-se que os indíces dum vector começam em 0. De notar ainda, a inicialização dos elementos do vector na linha 3 usando-se para o efeito {,. Os elementos dum vector não inicializado terão um valor arbitrário. Quando não se faz a inicialização dos elementos do vector na sua declaração, é necessário especificar o n o de elementos do vector. Quando se faz a inicialização, o compilador assume que o vector tem um n o de elementos iguais ao n o de valores usados na inicialização. Tal como uma string consiste numa sequência de carácteres, também um array/vector consiste numa sequência de elementos dum determinado tipo. Assim, em C há uma relação estreita entre arrays e endereços/apontadores. Nomeadamente, o nome dum vector é de facto o endereço da primeira posição de memória desse vector. Como ilustra o programa array ptr em array ptr.c: int a[] = {0, 1, 2; int i; int *p;

printf("array a[] @ %p\n", a); for( i = 0, p = a; i < 3; i++, p++) { printf("a[%d] (@ %p) = %d\n", i, &a[i], a[i]); printf("a[%d] (@ %p) = %d\n", i, p, *p);