Introdução à Linguagem C e Estruturas
Introdução à Linguagem C e Estruturas
A Linguagem C
Indice :
1. Introdução à linguagem C
1.1 Introdução
1.2 Características
1.3 Estrutura de um programa
1.4 Instruções
2. Operandos e Operadores
2.1 Introdução
2.2 Nomes
2.3 Tipos de Dados
2.4 Classes de Armazenamento
2.4.1 Tipos de Classes de Armazenamento
2.5 Constantes
2.6 Operadores
2.6.1 Operadores Aritméticos
2.6.2 Operadores Incremento e Decremento
2.6.3 Operadores Relacionais e Operadores Lógicos
2.6.4 Operador Condicional
2.6.5 Operadores Bit a Bit
2.6.6 Operadores de Ponteiros
2.7 Precedências
2.8 Conversão de Tipos
2.9 Typedef
3. Instruções de Controle de Sequência
3.1 Instrução IF
3.2 Instrução SWITCH
3.3 Ciclo WHILE
3.4 Ciclo DO-WHILE
3.5 Ciclo FOR
4. Operações de Entrada / Saida
4.1 Introdução
4.2 Saída Formatada
4.2.1 Especificações da Saída Formatada
4.3 Entrada Formatada
Introdução à Linguagem C
1.1 Introdução
De seguida apareceram os assembladores que mapeavam mnemónicas (mais facilmente utilizadas) para a
linguagem máquina.
Com o passar do tempo, apareceram linguagens de mais alto nível como BASIC e COBOL. Estas
linguagens permitiram que os programadores utilizassem algo parecido com palavras e frases usadas
normalmente como "Let I = 100". Essas instruções eram então traduzidas para linguagem máquina por
interpretadores e compiladores.
Durante alguns anos, o primeiro objectivo de programação foi escrever pequenas porções de código que
eram executadas rapidamente. O programa precisava de ser pequeno, visto que a memória era cara e
precisava de ser executado rapidamente, porque o poder de processamento também era dispendioso. à
medida que os computadores se tornaram mais pequenos, mais baratos, mais rápidos e a memória mais
barata, as prioridades mudaram. Hoje em dia, o tempo dispendido por um programador numa aplicação é
muito mais caro do que o hardware utilizado. Código bem escrito e fácil de actualizar é a prioridade da
maioria das aplicações de hoje.
A linguagem C foi criada e implantada pela primeira vez por Dennis Ritchie num Dec PDP-11, utilizando o
sistema operativo Unix.
A linguagem C foi desenvolvida nos laboratórios Bell na década de 70, tendo surgido a partir da
necessidade de escrever programas, que utilizassem as potencialidades da linguagem máquina, mas de
uma forma mais simples e portátil que esta.
Usada inicialmente para a programação de sistemas, viria pela sua flexibilidade e poder, a tornar-se numa
linguagem de uso geral nos mais diversos contextos, sendo actualmente muito utilizada pelos
programadores profissionais.
A linguagem C pode ser considerada uma linguagem de médio nível, porque para além de possuir
instruções de alto nível e ser tão estruturada como por exemplo o PASCAL, também possui instruções de
baixo nível.
1.2 Características
● Potencialidades e conveniência das linguagens quer de alto nível quer de baixo nível.
● Pequeno conjunto de palavras reservadas.
● Permitir a criação de novos tipos de dados pelo utilizador.
● Portabilidade. Os programas escritos em linguagem C podem ser transportados entre máquinas de
● Permite conversões de quase todos os tipos de dados. (Os tipos caracter e inteiro podem ser
misturados na maioria das expressões).
● Não possui nenhuma verificação de erros em tempo de processamento (fim de vector ou
compatibilidade do tipo de argumento), cabe inteiramente ao programador saber o que o programa
está a fazer.
Uma função contém instruções que especificam as operações de computação a serem executadas,
enquanto as variáveis armazenam os valores.
A função permite encapsular (esconder certa complexidade) alguma computação, podendo depois ser
utilizada sabendo-se apenas o que faz e sem preocupações quanto à forma como o faz.
O cabeçalho é constituído pelo nome da função a que se segue entre parêntesis, uma lista de declarações
de parâmetros. Caso estes não existam, os parêntesis surgem vazios.
{ }
Sempre que se pretenderem incluir comentários num programa, pode-se fazê-lo colocando esses
comentários entre /* e */.
Uma função pode retornar um valor para a função que a chama, o que é conseguido com a instrução
return, sendo o seu aspecto:
return expressão;
em que,
expressão
As funções são chamadas (invocadas para execução) dando-se o nome da função, juntamente com os
argumentos, entre parêntises:
Os valores dos argumentos na chamada da função, são atribuidos aos parâmetros da função pela ordem
em que se encontram.
Exemplo 1 : Programa que imprime no ecrâ : "O meu primeiro programa !"
O resultado da execução do programa será a impressão no ecrâ do texto contido entre aspas na função
printf. A instrução #include <stdio.h> tem por finalidade incluir no programa informação sobre funções de
entrada e saída de dados, tal como a função printf que será explicada no Capítulo 4 .
Exemplo 2 : O programa seguinte chama uma função mul(), que multiplica dois números inteiros e retorna o
resultado.
Os dois parâmetros a e b da função mul(), recebem respectivamente os dois argumentos 5 e 4, com que a
função é chamada.
#include <stdio.h>
main(){
printf( A multiplicação de 5 com 4 é igual a %d,mul(5,4));
}
return (a*b);
}
A impressão no ecrâ será : A multiplicação de 5 com 4 é igual a 20, o que significa que %d vai ser
substituido pelo valor retornado pela função mul().
Notar ainda que as variáveis a e b, definidas na função mul() só são reconhecidas no interior desta.
Uma variável é o nome que é dado a uma zona de memória, cujo tamanho varia conforme o tipo dessa
variável. No exemplo anterior, a e b são variáveis capazes de armazenar inteiros. Este assunto será
abordado detalhadamente na secção 2.3 .
A produção e execução de programas, depende do sistema operativo em que se esteja a trabalhar, bem
como do compilador utilizado.
A função main(), como função que é, também pode retornar um valor. Neste caso, o valor será retornado ao
ambiente exterior, responsável pela execução do programa.
Habitualmente o valor 0 significa um final sem erro, e um valor diferente de 0 um final com erro. No entanto,
e para simplificação não será utilizada a instrução return 0 nas várias funções main() aqui apresentadas.
1.4 Instruções
Expressões são formadas por constantes, variáveis e operadores combinados de uma forma válida.
Simples quando se trata de apenas uma instrução, e compostas quando se trata de um conjunto (bloco) de
instruções.
Estes conceitos, de instrução simples e composta, farão mais sentido quando, no capítulo 3, forem
discutidas as instruções de controle de sequência.
Exemplo :
{
instrução;
instrução;
instrução;
...;
}
Exemplo:
{
X=10;
Y=X+X;
}
O sinal '=' significa atribuição, isto é, para o exemplo acima apresentado, à variável X foi-lhe atribuido o
valor 10.
UP FORWARD
Up Forward
2. Operandos e Operadores
2.1 Introdução
Na base de qualquer programa estão os operandos e operadores, pois é através destes, que
se constroem as expressões que acabarão por formar um programa.
● nome
● tipo
● classe de armazenamento
2.2 Nomes
Nomes com letras maiúsculas são diferentes de nomes com letras minúsculas. Assim sendo,
A e a representarão dois nomes distintos.
Esta utilização de maiúsculas e minúsculas não é uma regra da linguagem, mas ao ser
seguida por quem programa facilita a leitura dos programas.
As palavras reservadas int,if,else, etc. da linguagem C, não são permitidas como nomes.
Essas palavras são reservadas ao compilador para utilização da linguagem. Não podem ser
definidas classes, variáveis ou funções com esses nomes. A lista seguinte é um pouco
arbitrária, visto que algumas palavras reservadas são específicas a um tipo de compilador.
Palavras Reservadas :
auto break case catch char class const continue default delete do double else enum extern
float for friend goto if int long mutable newo perator private protected public register return
short signed size of static structs witch template this throw type def union unsigned virtual
void volatile while
Ao definir-se uma variável, seja qual for o tipo, o compilador reserva o espaço na memória
necessário a essa variável.
Para além dos tipos de dados existem qualificadores que lhes podem ser aplicados, e que
são :
O qualificador short aplica-se a inteiros e tem como objectivo reduzi-los para metade do seu
tamanho (ex: numa máquina em que os inteiros (int) sejam de 4 bytes, o short int será de 2
bytes).
O long aplicado a inteiros tem a função inversa do short. O qualificador long também se
aplica ao tipo double, mas com efeitos que dependem da sua implementação no compilador.
Exemplificando, para o caso do char, uma variável signed char tomaria valores entre -128 e
127, enquanto que se fosse unsigned char, tomaria valores entre 0 e 255. Isto significa que
uma variável char pode conter qualquer caracter ASCII, mais exactamente os respectivos
códigos, pois estes variam de 0 a 127 ( Existem extensões a este código onde se incluem
caracteres de desenho).
Todas as variáveis devem ser declaradas antes de serem utilizadas. Uma declaração
especifica um tipo, e contém uma ou mais variáveis desse tipo.
int ano,mes,dia;
que declara três variáveis do tipo inteiro cujos nomes são ano, mes e dia.
declara uma variável do tipo char sem sinal, e cujo nome é tamanho. Para este caso o
compilador alocaria espaço para um caracter (1 byte).
int x=-691;
double media=0;
sizeof (int);
Exercicio : Escreva um programa que diga qual o espaço reservado em memória para os
seguintes dados: char;short int; long int; float e double.
Resolução :
#include <stdio.h>
#include <conio.h>
main(){
clrscr();
printf (Tamanho dos Short Int =%d\n,sizeof (short int));
printf (Tamanho dos Long Int =%d\n,sizeof (long int));
Saída:
A classe de armazenamento determina para uma variável o seu tempo de vida, e o seu
âmbito (escopo) , isto é :
● auto: Classe de armazenamento interna para variáveis locais que existem e são
reconhecidas somente ao longo da duração da função. Todas as variáveis locais são
implicitamente auto.
● static: O seu significado varia consoante o objecto é definido dentro ou fora de uma
função. Uma variável static quando é inicializada na sua definição só é inicializada na
primeira vez que a função é chamada. Nas chamadas seguintes vai apresentar o valor
da sua última utilização. O interesse desta classe surge quando por exemplo se
pretende que o valor de uma variável de uma função não seja destruído por uma nova
chamada a essa função. O tempo de vida destas variáveis é o da duração do
programa.
● extern:Para variáveis globais a todo o programa. Nesta classe caem por exemplo as
variáveis definidas externamente num ficheiro e que se pretende utilizar noutro ficheiro
do mesmo programa.
● register: O mesmo que auto, excepto que o compilador, se possível, escolhe locais de
memória de acesso mais rápido (registos). É normalmente utilizada para variáveis de
ciclo. Só é aplicável a variáveis do tipo int ou char.
2.5 Constantes
As constantes são, como o seu nome indica, valores fixos que não podem ser alterados
durante o programa e, têm associado a cada uma delas um determinado tipo de dado.
● Decimal
● Octal
● Hexadecimal
Para identificar o tipo de dado como unsigned usa-se o sufixo u ou U, enquanto para
significar long usa-se o sufixo l ou L, e o sufixo ul ou UL para unsigned long. O tipo int é
assumido por defeito, desde que a constante seja inteira e portanto não necessita de sufixo.
Um ponto decimal numa string (cadeia) de dígitos indica que o objecto é do tipo double.
Uma constante de vírgula flutuante com o sufixo l ou L é considerada do tipo long double.
Para significar float usa-se o sufixo f ou F.
Os caracteres são denotados através de plicas, por exemplo, 'q' representa o caracter q.
No código ASCII o caracter q é representado pelo inteiro (em octal ) 0161. Então, c=0161; é
equivalente a c='q';
\n ->newline
\t ->tab
\b ->backspace
\\ ->backslash
\r ->carriage return
Qualquer caracter pode ser especificado pelo seu equivalente numérico usando a notação
\ddd onde ddd é um inteiro octal,ou por \xddd, onde ddd é um inteiro em hexadecimal.
As sequências de escape para caracteres de não impressão são também válidas dentro de
strings de caracteres.
Por exemplo:
main()
{
printf (%s,OLA\n);
}
imprime no ecra a palavra OLA e em seguida há uma mudança de linha provocada pelo
caracter \n.
Uma constante também pode ser uma string, ou melhor, um array (vector) de caracteres em
que o último elemento é o caracter nulo (i.e, caracter cujo código ASCII é 0.
Por Exemplo para a constante ARRAY (string de caracteres), a sua representação interna
seria o conjunto de caracteres, 'A' 'R' 'R' 'A' 'Y' '\0'.
2.6 Operadores.
● Aritméticos.
● Relacionais e Lógicos.
● Bit a Bit (binários).
+ Adição
- Subtracção
* Multiplicação
/ Divisão
O operador % não pode ser aplicado a números de virgula flutuante. Na divisão de inteiros a
parte fraccionária é sempre truncada. Por exemplo de 5/2 resultaria 2.
Na Linguagem C qualquer operador aritmético pode ser combinado com a atribuição, o que
significa que :
No caso de a relação ser verdadeira, resulta o valor 1 (verdade), no caso de a relação ser
falsa resulta o valor 0.
Relacionais:
Lógicos:
|| or (ou)
! not (negação)
expr0 ? expr1:expr2
é o valor de expr1 se expr0 for verdadeira ou o valor de expr2 se expr0 for falsa.
x=a>b?a:b;
if (a>b)
x=a;
else
x=b;
Os operadores bit a bit (binários) permitem, como o nome implica, operações ao nível dos
bits. São seis os operadores bit a bit, os quais só poderão ser aplicados a expressões do tipo
inteiro:
& conjunção
^ ou exclusivo
| disjunção
& 0 1 | 0 1 ^ 0 1
0 0 0 0 0 1 0 0 1
1 0 1 1 1 1 1 1 0
Exemplos :
x=10111011
y=10101010
~x = 01000100
x<<2 = 11101100
y>>2 = 01010100
x&y = 10101010
x|y = 10111011
x^y = 00010001
Atenção : Ter sempre cuidade em não confundir os operadores lógicos && e || com os
operadores binários & e |.
O operador * leva o valor da variável à que precede e usa este valor como endereço da
informação na memória.
O estudo deste tipo de operadores devido à sua dimensão e complexidade, foi deslocado
2.7 Precedências
A tabela seguinte lista as precedências por ordem decrescente bem como o sentido de
avaliação (associatividade). Ter em atenção que alguns dos operadores indicados na tabela
ainda não foram discutidos.
Operadores Associatividade
Por vezes surge a necessidade de trabalhar operandos cujos tipos de dados diferem uns dos
outros.
Nestas situações há que proceder à conversão de tipos, isto é a conversão desses diferentes
tipos num tipo comum, de forma a possibilitar a resolução das expressões. As conversões
caem em três classes : automática, requerida pela expressão e forçada.
Automática
Feita automaticamente pelo compilador sempre que a variável seja do tipo char, short ou
float, não existindo possibilidade de perda de informação. As converssões automáticas são
feitas do seguinte modo :
Exemplo:
int x;
double y; /* conversão num double seguida de */
x=x+y; /* uma conversão num inteiro */
Forçada (cast)
Este tipo de conversão pode ser requerido pelo utilizador sempre que este o considere
necessário, e faz-se, precedendo a variável pelo novo tipo entre parêntises. Ter em atenção
que a conversão para tipos menores pode truncar a variável.
Forma Geral:
Neste exemplo o cast tornou-se necessário para que o resultado da divisão fosse um inteiro,
perdendo-se a parte fraccionária.
2.9 Typedef
/* programa area.c */
#include <stdio.h>
typedef float AREA;
main()
{
AREA x;
float lado1, lado2
Ter em atenção que em última instância a variável x é um float, como se prova pela
formatação (com %f) utilizada no printf().
A utilização de typedef é mais frequente com tipos de dados compostos, como se poderá ver
no capítulo dedicado a esses tipos de dados.
No exemplo anterior foi utilizada a função scanf() com dois ou mais argumentos. O primeiro
argumento, entre aspas (string), indica os tipos de dados a ler para as variáveis que ocupam
os argumentos seguintes.
BACK UP FORWARD
Back Up Forward
Ter em atenção que quando se tem mais do que uma instrução (instrução composta) estas
deverão ser colocadas entre chavetas, como já foi afirmado anteriormente.
Existem duas instruções que permitem alterar a sequência normal de execução dos ciclos: a
instrução break e a instrução continue. A intrução break interrompe definitivamente a
execução de um ciclo , enquanto que a instrução continue interrompe a execução de uma
iteracção saltando para o inicio da iteracção seguinte. A instrucção break também é usada na
instrução switch.
3.1 Instrução IF
Faz-se a avaliação de uma expressão, e caso ela seja verdadeira é executado um primeiro
bloco contendo uma ou mais instruções, caso contrário, será executado se existir, um
segundo bloco iniciado pela palavra else.
if (expressão)
instrução;
ou
if (expressão)
instrução;
else
instrução;
Exemplo : Neste exemplo, a função idade(), verifica em que intervalo se situa uma idade i
dada como argumento. No caso de i ser maior do que 70 imprime Idoso, senão, se for maior
do que 30 imprime Adulto senão imprime Jovem.
void idade(int i) /* 'void' antes do nome da função para significar que esta não retorna
qualquer valor. */
{
if (i>70) printf (Idoso);
else
if (i>30) printf (Adulto);
else
printf (Jovem);
}
Esta instrução avalia uma expressão do tipo inteiro ou caracter, e depois compara-a com
constantes de tipo inteiro ou caracter, permitindo várias decisões(vias). Esta instrução usa-se
normalmente em situações em que existam várias possibilidades de decisão.
Sintaxe :
switch (expressão)
{
case constante1 : Instrução;
break;
case constante2 : Instrução;
break;
...
default : Instrução
break;
}
A instrução break permite sair (quebrar) do switch. Caso esta não seja incluída todas as
instruções abaixo do case seriam executadas até que surgisse um break ou a chaveta que
termina o switch. A instrução default é opcional e é avaliada quando nenhuma das
constantes coincide com o resultado da expressão.
Esta instrução é usada quando se pretende que um bloco de instruções seja executado
enquanto uma determinada condição for verdadeira. O teste da condição é feito no inicio de
cada ciclo, sendo possível que as instruções não sejam executadas uma única vez. O ciclo
while sem nenhuma condição é considerado sempre verdadeiro, dando origem a um ciclo
infinito (do forever).
Sintaxe :
while (expressão)
instrução simples ou composta;
Esta construção é idêntica ao ciclo while. Enquanto que no ciclo while o teste da condição é
feito no fim de cada iteracção, no do-while o teste é feito no fim. Assim, mesmo que a
condição seja sempre falsa, o bloco de instruções é executado pelo menos uma vez (a
primeira).
Sintaxe:
do
instrução simples ou composta
while (expressão);
A instrução for é aquela que habitualmente mais se utiliza na construção de ciclos em C. Isto
deve-se ao seu poder e flexibilidade, como poderá ser observado. Identicamente ao while, a
condição de teste é feita no inicio do ciclo.
Sintaxe:
for (inicio;teste;incremento)
instrução
Em incremento são normalmente efectuados os incrementos das variáveis de ciclo (se forem
mais do que uma as expressões deverão ser separadas por virgula).
Na primeira iteração do ciclo for é executado inicio, depois teste, em seguida se teste resultar
verdadeiro é executado instrução (composto por uma ou mais instruções), e por último
incremento nas restantes iteracções do for, inicio não é executado.
O ciclo for pode ser facilmente convertido num ciclo while com o seguinte aspecto:
inicio;
while (teste)
{
instrução;
incremento;
BACK UP FORWARD
Back Up Forward
4.1 Introdução
Nos capitulos anteriores utilizaram-se algumas funções de entrada e saída de dados que, só
agora serão explicadas com mais pormenor. Estas funções, não são intrínsecas à linguagem,
tendo sendo desenvolvidas à parte e encontram-se em bibliotecas (conjunto de ficheiros
fonte compilados e agrupados num ficheiro). As funções básicas de input e output
disponíveis em todas as implementações de C constituem a biblioteca de rotinas standard
(Standard library routines). Várias declarações e macros necessárias a estas rotinas são
incluidas num programa através da linha
#include <stdio.h>
printf (s_control,arg1,arg2,...);
sprintf (buf,s_control,arg1,arg2,...);
O mesmo que printf() excepto que o output é colocado num buffer (i.e. um array)
especificado por buf.
Qualquer outro caracter é imprimido literalmente; por exemplo, printf (%%) imprime o sinal de
percentagem. Os números são escritos justificados à direita.
● Um sinal menos que indica que o argumento deve ser justificado à esquerda no campo
de output.
● Uma string de dígitos que especifica a largura mínima do campo para este
[Link] o argumento convertido tiver menos caracteres é encostado à esquerda
(ou à direita se o argumento é um ente justificado à esquerda).
● Um ponto que separa a largura do campo de precisão.
● Uma string que especifica o n.º máximo de caracteres a ser imprimidos da string, ou
(se foi imprimido um n.º de virgula flutuante) o n.º de digitos a ser imprimido à direita
do ponto [Link] isto é omitido para o n.º de virgula flutuante, a precisão por
defeito é 6.
● O caracter l indica que o argumento é um long.
A rotina scanf() de input formatado torna o input numa forma que é rigorosamente o inverso
do printf(). A sintaxe é:
scanf(s_control,arg1,arg2,...);
onde s_control é uma string. O input é lido e interpretado de acordo com a string de controle.
Cada um dos restantes argumentos deverá ser um endereço, o input é armazenado nesse
endereço tendo em atenção as especificações de conversão da string de controle.
Se se pretender ler um inteiro, a tendência será para escrever
int i;
scanf (%d,i);
tal, no entanto, não funciona, porque os argumentos da scanf devem ser endereços.
scanf (%d,&i);
Deve-se ter em atenção que a situação é distinta quando se pretende ler uma string, pois o
nome da string é um endereço, como se discutirá no capítulo sobre arrays.
Um dos erros mais comuns com scanf é esquecer isto; fazer sempre com que todos os
argumentos da função scanf sejam endereços.
A função
sscanf (buf,s_control,arg1,arg2,...);
funciona mais ou menos como a scanf excepto que lê dados de uma string buf em vez de os
ler do teclado.
A string de controle das funções scanf, ou sscanf pode conter espaços brancos (os quais são
ignorados), especificações de conversão e caracteres vulgares.
Os caracteres vulgares são esperados para combinar com o próximo caracter não branco do
input. Se a supressão de atribuição é indicada com *, então o próximo campo de input é
A função scanf pára quando atinge o fim da string de controle ou quando o input falha ao
tentar cumprir com a especificação de controle. Retorna como valor, o n.º de campos de
entrada que cumpriram e aos quais tenham sido atribuídos valores.
Para além das funções de entrada e saída formatada já estudadas, existem outras funções
também muito importantes. Estas funções tambem requerem a instrução de inclusão do
ficheiro stdio.h.
int c;
c=getchar();
char string[80]
puts(string);
gets(string);
A string lida é colocada em string. A função retorna o valor da constante NULL quando
encontra o fim do ficheiro ou em caso de erro.
c=getche();
c=getch();
Idêntica à anterior apenas com a diferença de não mostrar o caracter lido no ecrã (stdout). O
facto de estas funções não necessitarem que a tecla [ENTER] seja premida, faz com que
seja de grande interesse a sua utilização.
#include <conio.h>
BACK UP FORWARD
Back Up Forward
5. Funções
5.1 Introdução.
Um programa em linguagem C pode ser composto por uma (função main()) ou mais funções,
distribuídas por um ou mais ficheiros.
A linguagem C está feita de modo a que se construam facilmente funções muito eficientes,
de tal forma que podem ser utilizadas em diferentes programas sem alterações.
5.2 Definição.
Sempre que se pretenda que uma função retorne um valor, então deverá ser incluída a
instrução return.
As variáveis da lista de parâmetros podem ser consideradas como variáveis locais, que são
inicializadas com os valores dos argumentos da chamada da função.
O tipo de dado que precede o nome da função é o tipo do objecto que a função retorna. Por
exemplo, a definição double raiz(double num) significa uma função que retorna um double.
Se o tipo de uma função não é declarado explicitamente, então o tipo retornado por defeito é
int. E se algum dos parâmetros não tiver a declaração explicita do tipo, este é também
assumido como int.
Uma função retorna automaticamente quando atinge o fim do corpo da função, a não ser que
antes encontre a instrução return. A sintaxe da instrução return é :
return(expressão);
/* program raiz.c */
main()
{
float a,r,raiz(); /* Declaração da função raiz() */
printf (\n Introduza um número:);
scanf(%f,&a);
r=raiz(a);
printf (\nA raiz quadrada de %f é %f,a,r);
}
No contexto das funções existem dois conceitos importantes que por vezes dão origem a
alguma confusão:
raiz(a), a é o argumento.
● Parâmetros - Lista de declarações de variáveis aquando da definição da função. No
exemplo anterior a lista de parâmetros da função raiz(float x) é apenas float x.
Muitas funções, não retornam qualquer valor. A forma de o declarar explicitamente é usando
o tipo de dado void. Uma declaração deste tipo, pode ser :
Em C, os argumentos para as funções são passados via chamada por valor, ao passo que
em Pascal e Fortran são chamados via chamada por referência.
A passagem por valor significa que a uma função (mais concretamente aos seus parâmetros
), são apenas passados os valores dos argumentos, enquanto na chamada por referência é
como se fossem passadas as próprias variáveis.
O equivalente em Fortran,seria :
SUBROUTINE TROCA(X,Y)
INTEGER X,Y,TEMP
TEMP=X
X=Y
RETURN
END
troca(x,y)
int x,y
{
int temp;
temp=x;
x=y;
y=temp;
}
Esta rotina troca unicamente os valores das variáveis da função na stack, e deixa os valores
originais da rotina chamadora inalteráveis. Supondo que troca() era invocada do seguinte
modo:
int a=1,b=2;
troca(a,b);
int a=1,b=2;
troca(&a,&b);
e teria o seguinte aspecto, com as variáveis X e Y definidas como apontadores para inteiros
(ou seja, variáveis que guardam endereços de posições de memória onde estão
armazenados inteiros):
*y=temp;
}
O valor de uma expressão numa instrução return é convertido para o tipo da função através
das regras de conversão de tipos de dados. Com efeito, um cast para o tipo apropriado é
inserido automaticamente em qualquer instrução return. Por exemplo, a função atoi() (que
converte uma string num inteiro) pode ser escrita em termos duma rotina de biblioteca atof()
(a qual converte uma string para um número de vírgula flutuante) como se segue :
atoi(char s[])
{
double atof();
return atof(s);
}
O valor produzido pela função atof() é automaticamente convertido para inteiro na instrução
return, em virtude da função atoi() retornar um inteiro.
int n;
double x,log();
x=log((double)n);
parâmetros formais. Esses dois parâmetros formais da main() estão relacionados com os
argumentos de linha de comando que são dados na chamada do programa. A maior parte
dos programadores escolhe os nomes argc e argv para estes dois parâmetros:
Como o nome do próprio programa que é executado está em argv[0], então o argc é sempre
no mínimo 1. O primeiro argumento verdadeiro está em argv[1] e o último está em argv[argc-
1].
5.6 Conclusão
BACK UP FORWARD
Back Up Forward
[Link]
6.1 Introdução
Supor, que se pretendia um programa para ler a média das notas de cada um dos alunos de
uma escola, calcular a média das médias, e depois para cada aluno determinar o desvio da
sua média relativamente à média das médias. Uma solução para guardar cada uma das
médias, seria definir por exemplo 3000 variáveis (número hipotético de alunos), por exemplo
:
float media[3000];
int num;
Deste modo, para a leitura das 3000 variáveis, poder-se-ia utilizar um ciclo, como a seguir se
ilustra:
for (num=0;num<3000;num++)
{
printf (\n Introduza a média do aluno n.º %d,num);
scanf (%f,media[num]);
}
chamados vectores (neste texto é usado também o termo array para significar vector), os
quais se explicam nas próximas secções.
A linguagem C permite definir novos tipos de dados a partir dos existentes. Os arrays, ou
vectores, são um conjunto de elementos do mesmo tipo, agrupados com o mesmo nome, e
diferenciados através de um índice entre parêntises rectos. Antes de mais, convém salientar
que os arrays estão fortemente ligados aos apontadores, os quais não serão aqui abordados.
int a[4];
diz que a é uma variável do tipo array de 4 elementos do tipo int. A sua representação será:
Como se pode ver, os elementos do array ocupam posições de memória contíguas e o seu
índice varia obrigatoriamente de 0 a 3. Ou seja, tem-se, para essas 4 posições os elementos
a[0]=1;
a[1]=2;
a[2]=3;
virá:
Convém ainda saber que o nome array é uma constante e representa o endereço da 1.ª
posição do array. Isto é:
Supondo que o vector se iniciava na posição de memória com o endereço 100, e que cada
inteiro gasta 2 bytes, ter-se-ia:
a==100
&a[0]==100
&a[1]==102
&a[2]==104
&a[3]==106
Em virtude de ser uma constante, não é possível atribuir valores ao nome de array, como se
ilustra no seguinte caso:
int a[4];
a=2
a[0]=2;
a[0]++;
a[1]=2*a[0];
A dimensão de um vector é determinado pelo produto das suas linhas pelas suas colunas. A
seguinte declaração:
int ecra[25][80];
diz que ecra é um array de 25 arrays de 80 elementos inteiros cada. Assim, sizeof(ecra) será
2000 (25*80).
A inicialização de um array pode ser feita no momento da sua definição:
int a[4]={1,2,3};
irá definir (criar) um vector de quatro inteiros e inicializar a[0] a 1, a[1] a 2, e a[2] a 3. No
entanto a definição:
int a[]={1,2,3};
irá definir um vector de apenas três elementos e inicializa-los de forma idêntica ao anterior. O
vector é criado com apenas 3 posições em virtude de não ser explicitado entre parêntises o
número de elementos, e portanto este número será determinado pela lista de inicialização.
No caso de um array ser multidimensional a inicialização obedece às mesmas regras.
Por exemplo :
int m[][3]={1,2,3,11,22,33};
ou
int m[][3]={{1,2,3},{11,22,33}};
m[0][0] é igual a 1
m[0][1] é igual a 2
m[0][2] é igual a 3
m[1][0] é igual a 11
m[1][1] é igual a 22
m[1][2] é igual a 33
1 2 3
11 22 33
Exemplo 1: O programa media.c lê 100 notas para um array, e calcula a média das notas.
/* Programa media.c*/
#include <stdio.h>
main()
{
int i;
float notas[100],soma;
soma=soma+notas[i];
Como cada elemento do vector notas é um inteiro, há necessidade na função scanf de lhe
aplicar o operador & como acontece com qualquer outro inteiro.
O seguinte programa testa uma função que troca os dois primeiros elementos de um vector
dado como argumento.
#include <stdio.h>
void troca(); /*Declara a função como não retornando qualquer valor*/
main()
{
int v[2];
v[0]=2;
v[1]=3;
troca (v);
printf (v[0]=%d,v[1]=%d,v[0],v[1]);
int t;
t=x[0];
x[0]=x[1];
x[1]=t;
O resultado será v[0]=3 e v[1]=2. Neste exemplo é passado como argumento da função o
nome do vector, que é o endereço da 1.ª posição do array. Assim x vai representar também
um endereço, o de v[0] que é o mesmo de x[0], e portanto as alterações feitas na função
terão repercussões nas variáveis da função chamadora.
Na definição da função também seria possível e com os mesmos resultados, ter int x[] em
lugar de int x[2], sendo nesse caso x dimensionado automaticamente a 2.
Supôr que o endereço de v[0] é 200, então é porque v=200 como mostra a figura:
(Supondo uma máquina de inteiros com 2 bytes, v[1] virá 2 bytes à frente (202)).
v=200
em que ao nome do array x corresponderá o valor de 200, ou seja, x será igual a 200
x=200
Conteúdo : 3 2
Endereço : 200 204
6.4 Strings
Em C, não existe o tipo de dado string. Pode no entanto ser simulado através de um array de
caracteres. Por convenção, delimita-se a string com o caracter nulo (\0). Isto é, coloca-se o
caracter nulo na primeira posição não preenchida do array. Assim quando se define o
tamnho do array de caracteres para ser usado como string é necessário contar com mais
uma posição.
char s[]=OLA;
printf (Comprimento da String==%d,strlen(s));
A definição anterior além de inicializar o array com a string, também o dimensiona como um
vector de quatro caracteres, como mostra a figura seguinte:
char destino[]=ADEUS,origem[]=OLA;
strcpy(destino,origem);
printf (%s,destino);
a saida é OLA.
Compara s1 com s2,e devolve 0 se s1 igual a s2, >0 se s1>s2 e <0 se s1<s2.
Uma lista completa destas funções pode ser encontrada nos manuais de qualquer
compilador de C. Para a utilização destas funções, é requerida a linha
#include <string.h>
BACK UP FORWARD
Back Up Forward
7. - Estruturas
7.1. Introdução
Para resolver o problema de ter uma variável constituída por tipos de dados diferentes,
utilizam-se as estruturas que são explicadas em seguida.
7.2. Definição
Uma estrutura é um tipo de dado composto que consiste em uma ou mais variáveis
agrupadas sob um nome. As variáveis na estrutura podem ter tipos diferentes, mas
normalmente existe uma relação entre eles que faz com que seja conveniente tratá-los
conjuntamente como um objecto no programa. Em Pascal a noção análoga é um tipo de
dado, definido pelo utilizador,chamado record. Os componentes individuais de uma estrutura
são chamados membros. Uma declaração de estrutura tem a forma:
struct nome_estrutura
{
Declarações_dos_membros
};
Isto tem o efeito de definição de um novo tipo de dado. Aqui nome_estrutura é um nome
arbitrário e struct nome_estrutura pode ser pensado como o nome de um novo tipo de dado.
As Declarações_dos_membros são como quaisquer outras declarações, excepto que não
definem variáveis mas nomes de componentes de uma instância da estrutura
nome_estrutura.
Alguns exemplos:
struct num_complexo
{
double real;
double imaginário;
};
struct data
{
int ano;
char mes[10];
int dia;
};
struct venda
{
char comprador[SIZE];
double quantidade;
struct data quando;
};
Neste último caso existe uma estrutura encaixada noutra. Estas declarações não definiram
quaisquer variáveis, elas meramente definiram novos tipos de dados e portanto, não lhes foi
reservado espaço de memória. Qualquer definição de variáveis do tipo destas anteriores
utiliza as mesmas regras que para qualquer outro tipo de dado. Como exemplo:
variável_estrutura.membro;
Exemplos :
struct data d;
[Link]=1993
strcpy([Link],Fevereiro);
[Link]=4;
struct venda s;
[Link]=24
[Link]=1994
As variáveis do tipo estrutura podem ser inicializadas na altura da sua definição, colocando-
se a lista de inicializações a seguir à definição. Por exemplo,
struct data d;
imprime_data(d);
Apesar de não existir qualquer conflito entre membros e variáveis com o mesmo nome, é
necessário ter o maior cuidado com esta prática, de forma a que não prejudique a clareza
dos programas.
As estruturas, como qualquer outro tipo de dado podem ser agrupadas em vectores.
Exemplo 1 : Neste exemplo é criado um novo tipo de dado struct data e definida uma variável
feriado do tipo array de 5 estruturas struct data.
Para simplificar as declarações muito extensas, é conveniente atribuir nomes aos tipos de
estruturas. Por exemplo:
agora DATA representa o tipo de dado struct data, passando a poder ser usado em lugar
deste.
7.5 Unions
Uma union (união) permite criar variáveis capazes de suportar diferentes tipos de dados, no
mesmo espaço de memória em momentos diferentes. A declaração de uma union é similar à
declaração de uma estrutura. A diferença é que com uma struct é alocado de uma vez
espaço suficiente para todos os objectos, enquanto que com uma union só é alocado espaço
para o maior dos objectos que a compõem, A declaração,
union Valor{
int ivalor;
double dvalor;
char cvalor;
}val;
O programador é responsável pelo conhecimento de qual dos tipos foi armazenado mais
recentemente na variável val. A sintaxe para aceder ao conteúdos de uma variável union é
exactamente a mesma que para as estruturas. Seguidamente é mostrado o extracto de um
programa em que é utilizada a union acima descrita.
/* Definição de constantes */
#define INT 0
#define DOUBLE 1
#define CHAR 2
int tipo;
...
switch (tipo)
{
case INT:
printf (%d,[Link]);
break;
case DOUBLE:
printf (%lf,[Link]);
break;
case CHAR:
printf (%c,[Link]);
break;
BACK UP FORWARD
Back Up Forward
8. - Ficheiros
8.1. Introdução
Até ao momento, todos os dados têm sido inseridos nos programas através do teclado,
sendo guardados em variáveis, as quais residem em memória central (também chamada de
R.A.M, memória primária ou volátil) . Esta apresenta no entanto os inconvenientes de ser
volátil e cara, pelo que se torna desinteressante para o armazenamento durante periodos
que excedam o de execução de um programa. Para armazenamentos mais demorados é
utilizado outro meio, a que se chama memória secundária ( i.e. o disco rígido, ou a disquete),
em que a informação é armazenada sob a forma de ficheiros. A utilização destas estruturas
(ficheiros) na linguagem C será o assunto deste capítulo. As funções que irão ser
estruturadas fazem parte da biblioteca padrão, e portanto obrigam à inclusão nos programas
da seguinte linha, já conhecida:
#include <stdio.h>
O ficheiro de saída de erro é geralmente usado para escrever mensagens de erro que não
dizem respeito às saidas normais. Por exemplo, se ocorresse um erro grave seria aborrecido
se a mensagem de erro fosse redireccionada para o ficheiro de saída sem que o utilizador se
apercebesse disso. A solução seria escrever a mensagem para o standard error.
A linguagem C permite dois tipos de ficheiros : binário, ou texto. Estes são um conjunto de
linhas, com zero ou mais caracteres cada, sendo cada uma terminada pelo caractere newline
(\n). Estas linhas estão sujeitas a algum processamento feito pelo sistema operativo. Por
exemplo o DOS na escrita, substitui o \n por carriage return e mudança de linha (line feed). A
substituição inversa é feita na leitura.
Um ficheiro binário, é composto por uma sequência de bytes(caracteres) que não são
alterados pelo sistema.
A função fopen() devolve um apontador para um FILE, que passa a ser utilizado em todos os
acessos ao ficheiro. O primeiro argumento é o nome do ficheiro a ser aberto. O segundo
argumento é o modo de abertura. Algumas das possibilidades para o argumento modo são:
A função fopen() retorna NULL se ocorrer um erro na abertura. Após ser realizado o
processo sobre um ficheiro aberto à que fechá-lo. A operação de fecho de um ficheiro é feita
pela função :
Esta função desaloca a estrutura FILE respectiva e fecha o ficheiro apontado por fp. Em caso
de erro (i.e. fechar um ficheiro que ainda não foi aberto) retorna EOF, caso contrário retorna
zero.
#include <stdio.h>
FILE *fp,*fopen();
...
fp=fopen(dados,r);
...
fclose (fp);
Neste exemplo o valor (apontador para um FILE ) retornado por fopen(), é guardado na
variável fp. A partir daqui todas as referências ao ficheiro são feitas usando fp.
Funções Gerais
A função fread lê num objectos de tamanho size de um ficheiro especificado pelo apontador
fp para a posição de memória indicada por buffer. Retorna o n.º dos objectos completamente
lidos, ou retorna 0 se fim de ficheiro. A declaração void * significa apontador para qualquer
tipo de variável, ou seja, um qualquer endereço.
A função de escrita geral da standard library é :
a qual escreve num objectos de tamanho size contidos em buffer, para um ficheiro
especificado por fp. Retorna o número de objectos completamente escritos, o qual será igual
a num a menos que tenha ocorrido um erro. Notar que, programas escritos com fread e fwrite
são portáteirs, mas os ficheiros que foram escritos através destas rotinas poderão não ser,
visto as representações dos tipos de dados serem dependentes da máquina.
Orientada à palavra
● getw(FILE *fp);
● putw(int word,FILE *fp);
int x;
FILE *fp;
...
putw(x,fp);
irá escrever a palavra x no ficheiro especificado por fp. A função retorna a palavra escrita ou
EOF caso ocorra um erro. A linha
x=getw(fp);
Orientada ao Caractere
As rotinas mais primitivas de input e output são getc e [Link] rotinas lêm ou escrevem
um caracter de cada vez, de e para um ficheiro que é especificado pelo apontador FILE
obtido da chamada de fopen(). A chamada
c=getc(fp);
retorna o próximo caracter de um ficheiro especificado por fp ou EOF se foi encontrado o fim
do ficheiro (ou, se ocorreu um erro). A chamada
putc(c,fp);
escreve o caracter c no ficheiro especificado por fp. retorna também o caracter c. A ideia que
getchar e putchar são funções pode agora ser exposto como uma fraude. Elas são definidas
como macros no ficheiro stdio.h da seguinte forma:
De facto, getc e putc são igualmente macros definidas no stdio.h. A entrada e a saída
parecem funcionar caracter a caracter para conveniência do utilizador, mas na realidade eles
já são bufferizados.
Com formatação
Existem rotinas para entrada/saída formatada de dados, definidas na biblioteca padrão. Para
saída tem-se,
que é da família da printf, excepto que a saída é escrita para um ficheiro especificado por fp.
A função fprintf pode ser usada para escrever num canal diferente do standard output. Caso
se especificasse o ficheiro dado pela constante stdout, então fprintf seria o mesmo que printf.
Como exemplos ,
FILE *alunos;
...
alunos=fopen(...);
...
fprintf(alunos,%d, %s\n,numero,nome);
...
fprintf(stderr,Este é o %d-ésimo erro\n,nerro);
As especificações do formato para fprintf são as mesmas das do printf. Ter em atenção, que
o printf não trunca o argumento convertido para o ajustar à largura do campo.
Para a leitura formatada a partir de um ficheiro existe a função fscanf, que possui a seguinte
sintaxe:
fscanf(fp,controle,arg1,arg2,...)
Funciona de forma idêntica ao scanf, excepto que, lê o input de um ficheiro especificado por
fp. A string de controle da função fscanf, é igual à da scanf já estudada. Caso se
especificasse o ficheiro dado pela constante stdin, então fscanf seria o mesmo que scanf.
Orientada à Linha
A orientação de linha na entrada e saída pode ser obtida usando as funções fgets, e fputs. A
função fgets tem a seguinte sintaxe:
Esta função lê a próxima linha de input, incluindo o caracter newline (\n) se existir, de um
ficheiro especificado por fp para o array linha. São lidos todos os caracteres até ao caractere
newline, a não ser que encontre o fim de ficheiro antes, ou atinja o limite n, e então o array
linha é terminado automaticamente com o caracter nulo. Se atingir o fim de ficheiro, ou
houver erro, a função retorna NULL.
Uma chamada a fseek força o próximo input ou output do ficheiro fp a tomar lugar numa
posição deslocada deslocamento bytes relativamente à origem. Os valores possíveis para
origem são:
rewind(fp);
ferror(fp);
Retorna um número não nulo se ocorreu um erro durante a leitura ou escrita do ficheiro
especificado por fp. A função de controle de fim de ficheiro é:
feof(fp);
que retorna um número não nulo se foi atingido o fim de ficheiro senão retorna zero.
BACK UP FORWARD
Back Up Forward
9. O Pré-Processador
9.1 Introdução
9.2 Include
#include FILE
então o pré-processador irá substituir essa linha pelo conteúdo do ficheiro referido por FILE.
Isto é feito antes de qualquer compilação. Isto é por vezes usado por ficheiros header
contendo definições dos dados usados em ficheiros diferentes. FILE pode ter uma das duas
formas:
#include pilha.h
#include <stdio.h>
#include <ctype.h>
#include <math.h>
9.3 Define
então cada ocorrência do identificador nome no resto do ficheiro irá ser substituída por string.
Isto é feito pelo pré-processador antes da compilação.
É um bom estilo de programação usar #define para todas as constantes. É muito mais
simples alterar uma linha da forma,
● A string inclui todos os caracteres (incluindo o espaço branco) até encontrar o caracter
newline.
● o nome segue exactamente as regras dos nomes das variáveis.
● Uma convenção standard é usar nome em maiusculas para se distinguir as macros
das variáveis vulgares.
● Não são possíveis substituições nas strings, isto é, vendo no fragmento,
#define SIM 1
printf (SIM=%d\n,SIM);
o pré-processador só faz uma substituição.
● A string no define pode ser superior a uma linha; para isso o caracter newline terá de
ser precedido pelo caracter \ Barra Invertida (backslash).
Uma das facilidades da linguagem C consiste em invocar #define com argumentos. Por
exemplo,
pode ser usado exactamente como se existisse uma finção que determine quando é que o
caracter c é um digito. Outro exemplo é a definição,
y=ABS(a+b)
Em :
#define nome(arg1,arg2,...)
O uso abusivo do #define com argumentos pode levar a alguns erros aborrecidos. Notar que
a macro anterior ABS tem a vantagem de que trabalhará para variáveis de diferentes tipos.
Se uma macro tem uma string de substituição muito longa ou complicada, talvez seja
preferível implementá-la como uma função.
BACK UP FORWARD
Back Up Forward
Bibliografia
Linguagem C
C Avançado
Sams Publishing
BACK UP
Back Up