Muitas linguagens foram desenvolvidas e por
muitos anos utilizadas com diferentes objetivos e características, tais como:
Fortran, Cobol, Basic, Algols, Pascal e etc. Mas o que é C? C é o nome
de uma linguagem atualmente utilizada em diferentes áreas e propósitos. Faz
parte hoje de uma linguagem considerada avançada, desenvolvida nos laboratórios
Bell nos anos 70.
A definição formal da linguagem pode ser
encontrada no livro “The C Programming Language” de Brian W. Kernighan e Dennis
M. Ritchie (os pais da linguagem). Nos anos 80 iniciou-se um trabalho de
criação de um padrão chamado C ANSI (American
National Standardization Institute).
É uma linguagem de nível médio, pois
pode-se trabalhar em um nível próximo ao da máquina ou como uma linguagem de
alto nível como outras existentes.
Com o C podemos escrever programas concisos,
organizados e de fácil entendimento, mas infelizmente a falta de disciplina
pode gerar programas mal escritos, difíceis de serem lidos e compreendidos. Não
se deve esquecer que C é uma linguagem para programadores, pois impõe poucas
restrições ao que pode ser feito. O C é amigável e estruturado para encorajar
bons hábitos de programação; cabe ao programador exercitar esses hábitos.
A necessidade de escrever programas, que façam
uso de recursos da linguagem de máquina de uma forma mais simples e portátil,
fez com que a principal utilização do C fosse a reescrita do sistemas
operacional UNIX. Sua indicação é principalmente no desenvolvimento de
programas, tais como: compiladores, interpretadores, editores de texto;
banco de dados. Computação gráfica, manipulação e processamento de imagens,
controle de processos, …
Principais características da linguagem C a
serem ponderadas:
-
Portabilidade
-
Geração
de códigos executáveis compactos e rápidos
-
Interação
com o sistema operacional
-
Facilidade
de uso (através de ambientes como o Borland C++ 5.0)
-
Linguagem
estruturada
-
Confiabilidade
-
Simplicidade
São utilizados para dar nomes a constantes,
variáveis, funções e vários objetos definidos pelo usuário. As regras para
formação desses nomes são:
1) Todo identificador deve iniciar por uma letra (a..z ou A..Z) ou um
sublinhado
2) Não pode conter símbolos especiais. Após o primeiro caracter pode ser
utilizado: letras, sublinhados e/ou dígitos.
3) Utiliza-se identificadores de, no máximo, 32 caracteres por estes serem
significativos.
4) Não pode ser palavra reservada e nem nome de funções de bibliotecas.
Obs:
letras maiúsculas e minúsculas são tratadas de forma diferente.
|
Tipo |
Número de bytes |
Escala |
char
|
1 |
-128
a 127 |
|
Int |
2[1] |
-32768
a 327671 |
|
float |
4 |
3.4E-38
a 3.4E+38 (+-) |
|
double |
8 |
1.7E-308
a 1.7E+308 (+-) |
|
void |
0 |
sem
valor |
Tipo
|
Número de bytes |
Escala |
|
Unsigned char |
1 |
0
a 255 |
|
unsigned int |
21 |
0
a 655351 |
|
Short int |
2 |
-32768
a 32767 |
|
unsigned short int |
2 |
0
a 65535 |
|
long int |
4 |
-2147483648
a 2147483647 |
|
unsigned long int |
4 |
0
a 4294967295 |
|
long double |
10 |
3.4E-4932 a 1.1E+4932 |
Observações:
1) O modificador signed eventualmente
pode ser utilizado, porém o seu uso equivale a utilizar um tipo sem qualquer
modificador.
2)
A palavra int pode
ser omitida. Ex: unsigned
long int Û unsigned long
A forma geral
para declaração de uma variável é:
tipo_da_variável
lista_de_variáveis;
onde
tipo_da_variável é um tipo válido em C (Seções 1.2 e 1.3)
e lista_de_variáveis pode ser um ou
mais nomes de identificadores separados por virgula.
Exemplos:
int f, i, k; /* todas variáveis do tipo int */[2]
float a, A, b; /* todas variáveis
do tipo float */
Em C,
constantes são valores fixos que não podem ser alterados por um programa.
1)
Constantes numéricas inteiras: podem ser atribuídas
a variáveis dos tipos char e int, modificados ou não, dependendo do
valor da constante e da faixa de valores aceita pela variável.
Exemplos: 345 10 0 5000000
2)
Constantes numéricas não inteiras: podem ser
atribuídas a variáveis dos tipos float
e double, modificados ou não,
dependendo do valor da constante e da faixa de valores aceita pela variável.
Exemplos: -56.897 1.2E+5
3)
Constantes em forma de caracter: podem ser
atribuídas a variáveis do tipo char,
modificadas ou não. O valor da constante é igual ao valor numérico da tabela
ASCII[3]
do caracter representado entre ‘ ‘ (comumente chamados de “plicas”)
Constantes
em base hexadecimal iniciam com 0x, ao passo que constantes em base octal iniciam
com um 0.
Exemplos: 0xAB (hexadecimal) 016 (octal)
São
representadas entre aspas.
Exemplo:
“Esta é uma constante em forma de
string”.
Estes
caracteres devem ser representados como caracteres alfanuméricos (entre ‘ ‘) ou
como conteúdo de uma string
|
Código |
Significado |
|
\a |
sinal audível |
|
\b |
retrocesso do cursor |
|
\f |
alimentação de formulário |
|
\n |
nova linha |
|
\r |
retorno de carro |
|
\t |
tabulação horizontal |
|
\’ |
aspas |
|
\’ |
apóstrofo |
|
\0 |
nulo (zero) |
|
\\ |
barra invertida |
|
\v |
tabulação vertical |
|
\a |
sinal sonoro |
|
\N |
constante octal (onde N é um octal) |
|
\xN |
constante hexadecimal (onde N é um hexadecimal) |
Uma instrução em linguagem C é uma expressão
seguida de um ponto e vírgula. Pode ser uma atribuição, uma chamada de função,
um teste de desvio ou um teste de laço.
Exemplo de instrução de atribuição: x = 12;
onde
o sinal de igual (=) é o operador de
atribuição. Note-se que o operando do lado esquerdo do operador de atribuição é
sempre uma variável, e que o operando do lado direito deve ser de um tipo de
dado compatível com o tipo da variável.
Adição
|
+ |
|
Subtração |
- |
|
Divisão |
/ |
|
Multiplicação |
* |
|
Resto |
% |
Observações:
1)
Todos os operadores são definidos para os tipos
inteiros e não inteiros, exceto o operador resto (%) que não é definido para
variáveis dos tipos não inteiros.
2) Para qualquer tipo inteiro, a adiç ão
de um ao maior número da faixa daquele tipo produz o menor número da faixa. Os erros de estouro
nem sempre são detectados, cabendo ao programador tomar cuidado ao dimensionar
as variáveis do programa para que eles não ocorram.
Exemplo:
unsigned char x;
x = 255;
x = x + 1; /* x deveria assumir 256, no entanto
estoura a faixa e retorna para o menor
valor que é 0 */
|
Menor que |
< |
|
Maior que |
> |
Menor
ou igual
|
< = |
|
Maior ou igual |
> = |
|
Igualdade |
= = |
|
Desigualdade |
! = |
Observações:
1) Todas as operações relacionais tem como
resultado um inteiro representando um valor lógico (1 = true e 0 = false).
2) Não confundir o operador de atribuição (= ) com o operador de igualdade
( = = ).
|
e (conjunção) |
&& |
|
ou (disjunção) |
| | |
|
não (negação) |
! |
Os operadores
lógicos podem receber qualquer valor de operando, porém os valores diferentes
de zero são sempre interpretados como “true” (verdadeiro) e os iguais a zero
são interpretados como “false” (falso). O resultado de uma operação lógica é sempre
um valor lógico.
Tabela da verdade
|
p |
Q |
p && q |
p || q |
!p |
|
0 |
0 |
0 |
0 |
1 |
|
0 |
1 |
0 |
1 |
1 |
|
1 |
0 |
0 |
1 |
0 |
|
1 |
1 |
1 |
1 |
0 |
+= -= *= /=
Exemplos:
a += b; /* a = a + b; */
a -= b; /* a = a – b; */
a *= b; /* a = a * b; */
a /= b; /* a = a / b; */
Observações:
1) Todos os operadores
de atribuição atribuem o resultado de uma expressão a uma variável
Se o tipo do
lado esquerdo não for o mesmo do lado direito, o tipo do lado direito será
convertido para o tipo do lado esquerdo. Isto pode causar a perda de precisão
em alguns tipos de dados e deve ser levado a sério pelo programador.
|
Operador |
Significado |
|
++variavel |
incrementa a variável
antes de usar o seu valor |
|
Variável++ |
incrementa a variável
depois de usar o seu valor |
|
--variavel |
decrementa a variável
antes de usar o seu valor |
|
variavel-- |
decrementa a variável
depois de usar o seu valor |
Exemplos:
int a, b, c;
a = 6;
b = ++a; /* a recebe 7 e depois b também recebe 7*/
c = a++; /* c recebe 7 e depois a recebe 8 */
|
Deslocamento à
esquerda (shift left) |
<< |
|
Deslocamento à
direita (shift right) |
>> |
|
e (and) |
& |
|
ou (or) |
| |
|
ou exclusivo
(xor) |
^ |
|
não (not) |
~ |
Para uma discussão
mais aprofundada sobre o uso de bits e números binários, consultar o texto
sobre Bases Numéricas
& -
retorna o endereço da variável
* - retorna
o conteúdo do endereço armazenado em uma variável do tipo ponteiro
Para saber mais
sobre operadores de endereço, consultar o texto sobre Ponteiros.
sizeof(operando) – fornece o tamanho em bytes
do seu operando
Ex:
int x;
float y;
char c;
x= sizeof(int); /* fornece o tamanho do tipo int (2
bytes) */
x= sizeof(y); /* fornece o tamanho da variável y (4
bytes) */
x= sizeof(c); /* fornece o tamanho da variável c (1
byte) */
Operadores,
constantes, variáveis e funções constituem expressões. As principais regras
algébricas são consideradas nas expressões. Alguns aspectos das expressões são
específicos da linguagem C e são explicados a seguir.
Quando constantes,
variáveis e funções de tipos diferentes são misturados em uma expressão, elas
são todas convertidas para o tipo do operando
maior. Isto é feito operação a operação, de acordo com as seguintes regras:
1)
Todos
os operandos dos tipos char e short int são convertidos para int. Todos os operandos do tipo float são convertidos para double.
2)
Para
todos os pares de operandos envolvidos em uma operação, se um deles é long double o outro operando é convertido para um long double. Se um é double, o outro é convertido para double. Se um é long, o outro é convertido para long.
Se um é unsigned, o outro é convertido para unsigned.
Ex:
float x, res;
char c;
...
res = x/c; /* o valor de x/c é convertido para um float, embora c seja
originalmente um char */
É possível
forçar uma expressão a ser de um tipo específico, sem no entanto mudar os tipos
das variáveis envolvidas nesta expressão. A esse tipo de operação chama-se
conversão explícita de tipo, ou type cast.
A forma geral de
um type cast é:
(tipo) expressão;
onde tipo é um dos tipos de dado padrão da
linguagem C.
As operações
de type cast são muito úteis em
expressões nas quais alguma operação resulta em perda de precisão devido ao
tipo das variáveis ou constantes envolvidas. Por exemplo:
float res;
int op1,op2;
op1 = 3;
op2 = 2;
res = op1 / op2; /* res recebe 1, já que op1 e op2 são
ambos
números do tipo int e o resultado da sua
divisão também é int */
res = (float)op1 / op2; /* res recebe 1.5, já
que o type cast
forçou o operando
op1 a ser um float
nesta operação. O resultado da
divisão,
por consequência, também
é float */
Podemos
colocar espaços em uma expressão para torná-la mais legível. O uso de parênteses redundantes ou
adicionais não causará erros ou diminuirá a velocidade de execução da expressão.
Ex:
a=b/9.67-56.89*x-34.7;
a = (b / 9.67) – (56.89 * x) – 34.7; /* equivalente */
Uma
particularidade interessante no programa C é seu aspecto modular e funcional,
em que o próprio programa principal é uma função. Esta forma de apresentação da
linguagem facilita o desenvolvimento de programas, pois permite o emprego de
formas estruturadas e modulares encontradas em outras linguagens.
A estrutura
de um programa em C possui os seguintes elementos, sendo que aqueles delimitados
por colchetes são opcionais:
[ definições
de pré-processamento ]
[ definições
de tipo ]
[ declarações
de variáveis globais ]
[ protótipos
de funções ]
[ funções ]
main ( )
{
/* definições de variáveis
*/
/*
corpo da função principal, com declarações de suas variáveis,
seus
comandos e funções */
}
Definições de pré-processamento são comandos interpretados pelo
compilador, em tempo de compilação, que dizem respeito a operações realizadas
pelo compilador para geração de código. Geralmente iniciam com uma cerquilha
(#) e não são comandos da linguagem C, por isso não serão tratados aqui com
maiores detalhes.
Ex:
#include <stdio.h> /* comando de
pré-processador, utilizado para indicar ao compilador que ele deve ´colar´ as
definições do arquivo stdio.h neste
arquivo antes de compilá-lo */
Definições de tipos são definições de estruturas ou
tipos de dados especiais, introduzidos pelo usuário para facilitar a
manipulação de dados pelo programa. Também não serão tratados aqui em maiores
detalhes.
Declarações de variáveis globais são feitas quando é necessário
utilizar variáveis globais no programa. O conceito de variável global e as
vantagens e desvantagens do seu uso dizem respeito à modularização de um
programa em C (consultar o material sobre modularização e funções).
Protótipos de funções e funções também dizem respeito a questões de modularização.
main() é a função principal de um programa em C,
contendo o código que será inicialmente executado quando o programa em si for
executado. Todo programa em C deve
conter a função main(), do contrário
será gerado um erro durante o processo de geração do programa (mais
especificamente, na etapa de ligação).
Esta seção
descreve algumas das funções básicas de E/S, que serão utilizadas inicialmente
para prover o programador de um canal de entrada de dados via teclado e um
canal de saída de dados via monitor.
A função printf( ) é basicamente utilizada para
enviar informações ao monitor, ou seja, imprimir informações. O seu protótipo é o seguinte:
printf( string de
dados e formato, var1, var2,..., varN);
onde string de dados e formato é formada por
dados literais a serem exibidos no monitor (por exemplo, um texto qualquer) mais
um conjunto opcional de especificadores
de formato (indicados pelo símbolo % e um conjunto de caracteres). Estes especificadores determinarão de
que forma o conteúdo dos argumentos var1 a
varN será exibido.
var1 a varN indicam,
por sua vez, os argumentos (variáveis ou constantes) cujos valores serão
exibidos no local e no formato determinado pelos especificadores de formato,
dentro da string de dados e formato. O número N deve ser igual ao número de especificadores de formato
fornecidos.
Especificadores de
formato mais utilizados:
|
%c |
caracteres simples (tipo char) |
|
%d |
inteiro (tipo int ) |
|
%e |
notação científica |
|
%f |
ponto flutuante (tipo float) |
|
%g |
%e ou %f (mais curto) |
|
%o |
octal |
|
%s |
string |
|
%u |
inteiro sem sinal |
|
%x |
hexadecimal |
|
%lf |
tipo double |
|
%u |
inteiro não sinalizado (tipo unsigned int) |
|
%ld |
tipo long int |
Exemplos:
1)
int n = 15;
printf(“O valor de n eh %d”, n);
/* exibe ´O valor de n eh 15´. Note-se que todo o
conteúdo da string de dados e formato é exibido literalmente, com exceção do
especificador %d, que é substituído pelo valor em formato inteiro da variável
n */
2)
char carac = ´A´;
float num = 3.16;
printf(“A letra eh %c e o numero eh %f”,
carac, num);
/* exibe ´A letra eh A e o numero eh 3.16´. Neste
caso, o
especificador %c (primeiro da string) é
substituído pelo valor
da variável carac e o especificador %f é
substituído pelo valor
da variável num. Note-se que os tipos dos
especificadores e das
variáveis são compatíveis */
A função scanf
é utilizada para receber dados de uma entrada de dados padrão.
Consideraremos, para fins de simplificação, que essa entrada padrão é sempre o
teclado. O protótipo de scanf
é o seguinte:
scanf (string de formato, &var1, &var2,
…, &varN);
onde a string
de formato contém os especificadores de formato na sequência e relativos a
cada um dos dados que se pretende receber. Para uma lista dos especificadores
de formato mais utilizados, ver seção 3.1.
var1 a varN identificam as
variáveis nas quais serão armazenados os valores recebidos por scanf, na
mesma ordem determinada pelos especificadores de formato. O número N deve ser igual ao número de
especificadores de formato fornecidos.
IMPORTANTE:
o operador de endereço (&) DEVE obrigatoriamente ser utilizado diante dos
identificadores das variáveis, do contrário ocorre um erro. Para maiores
detalhes, consultar a teoria sobre ponteiros.
Exemplos:
1)
int t;
printf(“Digite um inteiro: “);
scanf(“%d”, &t); /* aguarda a
digitação de um número do tipo
int. O número digitado é armazenado na
variável t quando o
usuário digita ENTER */
2)
char carac1;
int i;
printf(“Digite um caracter e um int,
separados por vírgula: “);
scanf(“%c, %d”, &carac1, &i);
/* neste caso, os especificadores de
formato %c e %d estão
separados por vírgula, o que significa que
o usuário deve digitar os valores também separados por vírgula e na ordem
correta */
A função getch
é utilizada, basicamente, para esperar o pressionamento de uma tecla pelo
usuário. A tecla pressionada pode ser capturada através do valor de retorno da
função (para maiores detalhes sobre valor de retorno, consultar a teoria sobre
funções).
Pelo fato de
interromper a execução até o pressionamento de uma tecla, a função getch pode ser utilizada no
final de um programa de console para permitir que o usuário visualize o
resultado do programa antes que a sua janela se feche.
Exemplo:
printf(“Estou mostrando uma frase\n”);
printf(“Digite qualquer tecla para sair do
programa”);
getch(); /*
aguarda aqui até uma tecla ser pressionada */
/* fim do
programa */
Observação:
a função getche funciona de forma semelhante, porém exibe na tela o
caracter digitado (o nome significa “get char with echo”).
A função clrscr
é utilizada para limpar a tela (o nome significa “clear screen”).
Uma linha de
comando em C sempre termina com um ponto e vírgula (;)
Exemplos:
x = 443.7;
a = b + c;
printf(“Exemplo”);
Utiliza-se
chaves ( { } ) para delimitar blocos de comando em um programa em C. Estes são
mais utilizados no agrupamento de instruções para execução pelas cláusulas das
estruturas condicionais e de repetição.
Formato:
if ( condição )
{
bloco de comandos 1
}
else
{
bloco de comandos 2
}
condição é qualquer expressão que possa ser
avaliada com o valor verdadeiro (“true”)
ou falso (“false”). No caso de expressões que possuam um valor númerico ao
invés de um valor lógico, se o valor é diferente de zero a expressão é avaliada
com valor lógico “true”, do contrário é avaliada com o valor lógico “false”.
Caso a
condição possua um valor lógico “true”, bloco
de comandos 1 é executado. Se o valor lógico da condição for “false”, bloco
de comandos 2 é executado. Para qualquer um dos blocos, se este for formado
por um único comando as chaves são opcionais.
A estrutura if-else
é semelhante a uma estrutura condicional composta, em que um ou outro bloco
de comandos é executado; a cláusula else, no entanto, é opcional, e se
for omitida a estrutura passa a funcionar como uma estrutura condicional
simples, em que um bloco de comandos (no caso, o bloco 1) somente é executado se a condição for verdadeira.
Exemplos:
1)
int num;
printf(“Digite um numero: “);
scanf(“%d”, &num);
if (num < 0) /*testa se num é menor que zero */
{
/* bloco de comandos executado se a condição é
verdadeira. Neste caso, como printf é um
único comando as chaves poderiam ser omitidas
*/
printf(“\nO número é menor que
zero”);
}
else
{
/* bloco de
comandos executado se a condição é falsa.
Neste caso, como printf é um único
comando as chaves
poderiam ser omitidas */
printf(“\nO número é maior que
zero”);
}
2)
if ((a == 2) && (b == 5)) /* condição com
operação lógica */
printf(“\nCondição
satisfeita”); /*
bloco de comandos */
getch(); /* esta instrução não faz parte da estrutura
condicional, logo é sempre
executada */
3)
if (m == 3)
{
if ((a >=1) && (a <= 31)) /* este if faz
parte do bloco de comandos do if anterior
*/
{
printf(“Data OK”);
}
else /* este else é do if mais proximo (que faz
parte do bloco de
comandos) */
{
printf(“Data inválida”);
}
}
Formato:
switch (expressão)
{
case valor1:
seq. de comandos 1
break;
case valor2:
seq. de comandos 2
break;
. . .
case
valorN:
seq. de comandos N
break;
default:
seq. padrão
}
O comando switch
avalia expressão e compara sucessivamente com uma lista de
constantes valor1 a valorN (menos constantes strings). Quando
encontra uma correspondência entre o valor da expressão e o valor da constante,
salta para a cláusula case correspondente e executa a sequência de
comandos associada até encontrar um comando break, saindo em seguida da estrutura.
A cláusula default
é executada se nenhuma correspondência for encontrada. Esta cláusula é
opcional e, se não estiver presente, nenhuma ação será realizada se todas as
correspondências falharem. Ë usada normalmente para direcionar qualquer final
livre que possa ficar pendente na declaração switch.
OBSERVAÇÕES:
-
se o
comando break for esquecido ao final de uma sequência de comandos, a
execução continuará pela próxima declaração case até que um break ou
o final do switch seja encontrado, o que normalmente é indesejado.
-
nunca
duas constantes case no mesmo switch podem ter valores iguais.
-
uma
declaração switch é mais eficiente do que um encadeamento de if-else,
além do que pode ser escrito de forma muito mais “elegante”.
-
valor1
a valorN DEVEM ser
valores constantes.
Exemplos:
1)
int dia;
printf(“Digite um dia da semana, de 1 a
7”);
scanf(“%d”, &dia);
switch(dia) /* testa o valor da variável dia */
{
case 1:
printf(“Domingo”);
break;
case 2:
printf(“Segunda”);
break;
case 3:
printf(“Terça”);
break;
case 4:
printf(“Quarta”);
break;
case 5:
printf(“Quinta”);
break;
case 6:
printf(“Sexta”);
break;
case 7:
printf(“Sábado”);
break;
default:
printf(“Este dia não existe”); /* só entra aqui se o usuário não digitar um dia entre 1 e
7 */
break;
}
Formato:
while (condição)
{
sequência de comandos
}
O comando while
avalia o valor lógico de condição; se o valor lógico for verdadeiro
(true) a sequência de comandos é executada, caso contrário a
execução do programa continua após a estrutura while. Caso a sequência
de comandos seja formada por um único comando, o uso das chaves é opcional.
Após a execução da
sequência de comandos, o valor lógico de condição é reavaliado, e se
continuar sendo verdadeiro (true) a sequência de comandos é executada
novamente. Este comportamento se repete até que o valor lógico da condição seja
falso (false), quando a execução da estrutura while é
interrompida e continua na instrução seguinte.
Cada uma das
execuções da sequência de comandos é chamada de iteração do laço. No
caso da estrutura while o número de iterações pode variar de 0 até N,
sendo N o número da iteração após a qual o teste da condição resulta em um
valor lógico falso.
OBSERVAÇÃO: caso a
condição seja verdadeira no primeiro teste e a sequência de comandos seja
executada, é necessário que esta torne a condição falsa em algum momento; do
contrário, a condição sempre será reavaliada como verdadeira e a sequência de
comandos será executada em um número infinito de iterações[5].
Exemplo:
int x = 0;
/* imprime os
valores de x de 0 até 9
o valor 10 não é impresso porque, ao
testar a condição para
x igual a 10, o valor lógico é falso e a
execução do while
é interrompida */
while (x < 10)
{
printf(“\nx = %d”, x);
x++; /* faz a condição tornar-se falsa
em algum momento */
}
Formato:
do {
sequência de comandos
} while (condição);
A sequência de
comandos sempre é executada inicialmente em uma estrutura do-while.
Após a sua execução, o valor lógico da condição é avaliado, e se for
verdadeiro (true) a sequência de comandos é executada novamente. O ciclo
se repete até que o valor lógico da condição seja falso (false), quando
a execução continua na instrução seguinte à estrutura do-while. Caso a
sequência de comandos seja formada por um único comando, o uso das chaves é
opcional.
Diferentemente do
que ocorre na estrutura while, na estrutura do-while o número de
iterações varia entre 1 e N, onde N é o número da iteração após a qual o teste
da condição resulta em um valor lógico falso.
OBSERVAÇÃO: assim
como na estrutura while, caso a condição seja verdadeira no primeiro
teste é necessário que a sequência de comandos torne a condição falsa em algum
momento.
Exemplo:
int num;
do {
printf(“Digite um número de 1 a 9:
“)
scanf(“%d”, &num);
} while (!((num >=1) && (num
<=9))); /*
nesse caso, a obtenção
do valor
de num via
scanf pode tornar a
condição falsa */
Formato:
for (inicialização; condição;
incremento)
{
sequência de comandos
}
A inicialização
é executada uma única vez, no início da execução da estrutura for, e
normalmente é uma atribuição utilizada para inicializar alguma variável de
controle do laço.
Após a
inicialização, o valor lógico da condição é testado. Se for verdadeiro (true),
a sequência de comandos é executada, do contrário a execução continua
após a estrutura for. Ao final da execução da sequência de comandos, o
comando correspondente ao incremento é executado, e a condição volta a
ser testada. O ciclo se repete até que o teste da condição resulte em um valor
lógico falso (false), quando então a execução prossegue após a estrutura
for. Caso a sequência de comandos seja formada por um único comando, o
uso das chaves é opcional.
A estrutura for
é equivalente a uma estrutura while com o seguinte formato:
inicialização
while (condição)
{
sequência de comandos
incremento
}
OBSERVAÇÕES:
-
Qualquer
uma das cláusulas do cabeçalho (inicialização, condição ou incremento) pode ser
omitida; no caso da omissão da inicialização ou do incremento considera-se que
estes são comandos nulos (ou seja, não executam nada), já na omissão da
condição considera-se que o seu valor lógico é sempre verdadeiro. Os sinais de
ponto-e-vírgula que separam cada uma das cláusulas não podem ser omitidos.
-
As
cláusulas de inicialização e incremento podem se constituir de vários comandos
cada uma; nesse caso, os comandos devem ser separados entre si por vírgulas.
Exemplos:
1)
/* neste caso, x
é usado como variável de controle do laço
(controla a execução entre 1 e 100) e
também tem o seu valor
impresso pela função printf */
for (x = 1; x <= 100; x++)
{
printf(“%d”, x);
}
2)
/* neste caso a
sequência de comandos é nula, e o laço é
utilizado somente para “gastar tempo”
contando de 0 a 999 */
for (x = 0; x< 1000; x++);
3)
/* não há incremento,
e o laço é executado até que o valor
digitado pelo usuário seja 10 */
for (x = 0; x != 10;)
scanf(“%d”, &x);
4)
/* duas variáveis
são inicializadas, testadas e incrementadas */
for (x = 0, y = 0; x + y < 100; x++, y++)
printf(“%d”, x + y);
O comando break
pode ser utilizado para interromper a execução de um laço a qualquer
momento. Somente o laço mais interno é interrompido, e a execução continua no
comando seguinte a esse laço.
Exemplo:
#include <stdlib.h> /* requerida para
usar rand() */
#include <stdio.h>
void main(void)
{
int sorteio = rand(); /* gera um
número aleatório
entre 0
e 32767 */
int num, x;
for (x = 0; x<10; x++)
{
printf(“Tente acertar o número
(entre 0 e 32767).”);
printf(“Vc tem %d tentativas.”, 10 – x);
scanf(“%d”, &num);
if (num == sorteio) /* se acertou o
número */
{
break; /* interrompe o
laço (não são
necessárias
mais tentativas) */
}
}
/* se x
igual a 10, o usuário esgotou suas tentativas
sem obter sucesso */
if (x < 10)
{
printf(“Muito bem!”);
}
else
{
printf(“Lamentável!”);
}
}
O comando continue
tem funcionamento semelhante ao break, com a diferença de que
somente a iteração corrente é interrompida; ou seja, a execução do laço
continua a partir do início da próxima iteração.
Exemplo:
/* imprime os
números pares
for (x = 0; x < 100; x++)
{
/* se o
número não é par, passa para a próxima iteração
sem imprimir */
if (x % 2 != 0)
continue;
printf(“%d, “, x);
}
Em C não
existe uma distinção entre funções e subrotinas. Ou seja, todas as subrotinas,
do ponto de vista de algoritmos, podem ser tratadas como funções que não
retornam nenhum valor.
Formato de
declaração de funções :
Tipo de retorno identificador_da_função (tipo1 param1, tipo2 param2,...,
tipoN paramN)
{
/* corpo da
função */
return
valor de retorno;
} /* fim da função
*/
Tipo de retorno especifica o tipo do valor que será retornado
para quem chamou a função. Quando o tipo de retorno for void isto significa que se trata de uma função que se comporta como
uma subrotina; ou seja, a função não necessita retornar nenhum valor, apenas
ser chamada.
Exemplos de
tipos de retorno nos cabeçalhos das funções:
int func1(...) /* retorna um valor inteiro */
void func2(...) /* não retorna nenhum valor.
Comporta-se como subrotina */
O comando return é utilizado para realizar o
retorno da função; este pode ser utilizado em qualquer ponto da função que se
deseje finalizar a sua execução e retornar o valor (se a função retornar algum
valor) para quem a chamou.
Valor de retorno é o valor a ser efetivamente retornado e pode
ser tanto uma variável como uma constante; nos casos em que a função não
retorna nenhum valor o comando return
deve ser utilizado sozinho ou pode-se simplesmente omití-lo, o que fará com que
a função retorne automaticamente ao seu final.
Exemplos de
uso de return:
return 0; /* retorna o valor constante 0 */
return var; /* retorna o valor da variável ‘var’
*/
return; /* não retorna valor. É usado para funções com retorno do
tipo void */
Os
parâmetros param1 a paramN identificam os parâmetros que se
deseja passar para a função. Cada um destes parâmetros passa a ser uma variável
local da função de tipo tipo1 a tipoN e é inicializado com o valor que foi passado
para si no momento da chamada da função. Funções que não recebem nenhum valor
como parâmetro devem ser declaradas com a palavra void entre os parênteses.
Exemplos de
declarações de parâmetros no cabeçalho
das funções:
/* dois
parâmetros, um int e um char. O ... se refere a um tipo de retorno qualquer */
... Func1(int var, char var2)
{
}
... Func2 (void) /* não recebe nenhum parâmetro
*/
{
}
Exemplo de
função e programa em C que a chama:
int func1
(char carac, int inteiro, float flutuante)
/* declaracão da função */
{
/* pode-se
declarar outras variáveis aqui dentro, como em um trecho normal de programa
estas variáveis são locais da função */
int outra;
/* uso das variáveis recebidas como parâmetro */
printf(“%c”, carac); printf(“%f”, flutuante);
scanf(“%d”, &outra);
printf(“%d”, inteiro + outra);
return outra; /* retorna o valor da variável ‘outra’ */
} /* fim da função */
void main
(void) /*
programa principal */
{
char c1;
float f;
int resultado;
int inteiro;
/*esta variável
‘inteiro’ existe no escopo da função ‘main’, logo não tem nada a ver com a
variável ‘inteiro’ que é criada na função ‘func1’ no momento da passagem dos
parâmetros */
/* lê um número inteiro, um caracter e um float */
scanf(“%d, %c, %f”, &inteiro, &c1,
&f);
/* chama a função
‘func1’ com os parâmetros na ordem correta */
resultado = func1(c1, inteiro, f);
printf(“%d”, resultado); /* imprime
resultado da função */
}
Observações:
-
main ()
também é uma função, porém especial já que ela representa o ponto de partida
para qualquer programa em C;
-
O
resultado da função ‘func1’, no exemplo acima, não precisa necessariamente ser
atribuído a uma variável (no caso, ‘resultado’); se isto não acontecer o valor
de retorno da função simplesmente será perdido. Porém, como a função foi feita
para retornar um valor inteiro isto deve ser evitado, porque constitui-se em uma
má estruturação e uso da função;
-
Todas
as variáveis declaradas dentro do corpo de uma função são locais a ela, ou seja, só
existem enquanto a função está sendo executada.
Todas as
funções devem ser “conhecidas” no local onde forem utilizadas, ou seja, a sua
declaração deve vir antes do uso. Caso não se deseje implementar a função antes
do local onde ela vai ser utilizada pode-se escrever um protótipo da seguinte forma:
Tipo de retorno identificador_da_função (tipo1 param1, tipo2 param2,...,
tipoN paramN);
O protótipo
deve ser colocado antes da chamada da função, sinalizando então ao compilador
que aquela função existe e vai ser implementada adiante. No nosso exemplo, se
quiséssemos escrever a função ‘func1’ depois da função ‘main’ deveríamos
incluir um protótipo de ‘func1’ antes dela.
CUIDADO!!
O protótipo não é
exatamente igual ao cabeçalho da função, ele possui um ponto-e-vírgula a mais
no final!
Em C considera-se como variável global todas aquelas variáveis declaradas fora do escopo
de qualquer função (inclusive da função ‘main’). Qualquer variável só é
conhecida após a sua declaração, logo costuma-se declarar todas as variáveis
globais no início do programa, antes da implementação das funções que a
utilizam.
Exemplo de declaração e uso de variáveis
globais:
int c;
char t;
/*
função que retorna um valor inteiro e não recebe parâmetro */
int func1
(void)
{
/* existe uma variável t que é global, porém esta
funciona como uma variável local */
int t;
/* c é global, logo pode ser utilizada dentro da
função ‘func1’ */
if (c!=0)
{
c++;
t = c*2;
/* neste caso o valor de t retornado
é o da variável local, já que definições locais sobrepõem-se a definições
globais nos escopos onde existem */
return t;
}
else return 0;
}
void main(void)
{
int retorno;
printf(“Entre com um caracter:”);
scanf(“%c”, &t);
printf(“Entre com um inteiro:”);
scanf(“%d”, &c); /*as variáveis
t e c podem ser usadas aqui porque são globais */
retorno = func1(); /* chama a função func1 e retorna o
valor na variável ‘retorno’ */
printf(“\nResultado: %d”, retorno);
}
Na passagem por valor, uma cópia do valor do
argumento é armazenado no parâmetro da função chamada. Qualquer alteração deste
parâmetro não se reflete no valor original do argumento.
Uma alternativa para a passagem de parâmetro
de valor, que é a passagem de parâmetros por
referência utilizando ponteiros,
permitiria que a função alterasse o valor do parâmetro de forma que esta
alteração se refletisse no valor original do argumento. Este tipo de passagem
de parâmetros será melhor estudado no capítulo sobre ‘Ponteiros’.
Em
C é possível declarar funções cuja quantidade de parâmetros não é definida.
Cabe então à função, por meio do uso de funções específicas de biblioteca de C,
obter cada um dos parâmetros recebidos e convertê-lo para o tipo desejado.
A
biblioteca cstdarg provê alguns
tipos de dados e funções utilizadas para a obtenção dos parâmetros de uma lista:
va_list –
tipo de lista de parâmetros variáveis, utilizado para declarar uma estrutura
(ver o capítulo sobre “Estruturas de dados”) que contém os parâmetros variáveis
recebidos.
void va_start(va_list lista, ultimo) – macro utilizada para inicializar a lista de parâmetros do tipo va_list. Ultimo é o identificador do
último parâmetro à direita que não pertence à lista variável de parâmetros.
tipo va_arg(va_list lista, tipo) – permite, a partir da lista do
tipo va_list, obter o valor de tipo tipo do próximo argumento da lista.
void va_end(va_list lista) – finaliza a obtenção dos parâmetros da lista.
Para
declarar uma função com lista variável de parâmetros:
Tipo de retorno identificador_da_função (tipo1 param1, tipo2 param2,
...);
Onde
a elipse (. . .) denota o início da lista variável de parâmetros.
Um
exemplo: função que recebe n valores e retorna a sua média:
/*
n é a quantidade de valores, que vêm em
seguida na lista de parâmetros */
float media
(int n, ...)
{
float soma = 0;
int i;
va_list valores; /* lista de parâmetros */
va_start(valores, n); /* ‘n’ é o último
parâmetro fixo antes da lista de parâmetros variáveis */
for (i = 0; i < n; i++)
{
/* aqui
o valor do próximo parâmetro, de tipo float é adicionado a ‘soma’*/
soma += va_arg(valores, float);
}
va_end(valores); /* finaliza a obtenção dos parâmetros */
return soma/n;
}
Vetor em C é
uma variável composta por um conjunto de dados com um mesmo nome
(identificador) e individualizadas por um índice.
O vetor é
declarado da seguinte maneira:
tipo
nome [tamanho];
Onde tipo
é o tipo de cada um dos elementos do vetor e tamanho é o número de
elementos do vetor.
Para acessar
um elemento do vetor a sintaxe é:
nome [índice];
IMPORTANTE! O índice do primeiro elemento de um
vetor é SEMPRE ZERO! Assim, indice pode
variar entre 0 e o valor de tamanho –
1.
Por exemplo,
para a declaração de um vetor chamado teste cujo tipo dos dados é char
e que tenha 4 posições declara-se:
char teste [4];
O índice do
último elemento indexável do vetor é 3, pois em C a primeira posição utilizada
é a posição 0. Neste caso as posições disponíveis no vetor são as seguintes:
|
teste[0] |
|
teste[1] |
|
teste[2] |
|
teste[3] |
Existem três
maneiras possíveis:
tipo_retorno nome (tipo v[tam],
...);
tipo_retorno nome (tipo v[], ...);
tipo_retorno nome (tipo * v, ...);
Em todos os
casos a função recebe uma referência (endereço). Note que na última maneira é
utilizado um ponteiro, que será explicado mais adiante.
Por ser
passada uma referência, as alterações feitas nos elementos do vetor dentro da
função serão refletidos nos valores
originais do vetor (já que se utilizará sua posição real na memória).
Por exemplo:
void troca
(int v[])
{
int aux;
v[0] = v[1];
v[1] = aux;
}
void main(void)
{
int nums[2];
nums[0] = 3;
nums[1] = 5;
troca (nums); /*
O argumento é o nome do vetor */
/* imprime ‘5, 3’, já que os valores do
vetor nums foram trocados dentro da função ‘troca’ */
printf(“%d, %d”, nums[0], nums[1]);
}
A declaração
de matrizes se dá da seguinte maneira:
tipo nome[dim1][dim2];
Onde dim1 e dim2 são as duas dimensões da matriz (no caso de uma matriz bi-dimensional).
Para se acessar um elemento da matriz a sintaxe é:
nome[ind1][ind2];
Onde ind1 e ind2 seguem as mesmas regras dos índices de vetores unidimensionais
(ou seja, podem assumir valores entre 0 e a dimensão – 1), sendo ind1 o índice da linha e ind2 o índice da coluna.
As
representação gráfica de uma matriz M 3x2 se dá da seguinte maneira:
|
M[0][0] |
M[0][1] |
|
M[1][0] |
M[1][1] |
|
M[2][0] |
M[2][1] |
Na memória
ela pode ser vista da seguinte forma (obs: os valores da esquerda
representam endereços arbitrários de
memória, considerando uma matriz de elementos char de um byte):
|
0100 |
M[0][0] |
|
0101 |
M[0][1] |
|
0102 |
M[1][0] |
|
0103 |
M[1][1] |
|
0104 |
M[2][0] |
|
0105 |
M[2][1] |
As
possibilidades são as seguintes:
tipo retorno nome(tipo m[][dim2],...)
tipo retorno nome(tipo *m,...)
No primeiro
caso, dim2 deve ser fornecido para que o compilador possa calcular o
deslocamento em bytes em relação ao endereço do primeiro elemento para uma
determinada posição.
No segundo caso, m só pode ser utilizado através da aritmética de
ponteiros (explicada adiante).
Exemplo:
void
inverte_linha(int m[][2])
{
int aux1, aux2;
aux1 = m[0][0];
aux2 = m[0][1];
m[0][0] = m[1][0];
m[0][1] = m[1][1];
m[1][0] = aux1;
m[1][1] = aux2;
}
void main(void)
{
int m[2][2];
.
.
.
inverte_linha(m);
.
.
.
}
Para vetores: valores entre chaves, separados por vírgulas. Por exemplo:
int primos [7] = {2, 3, 5, 7, 11, 13, 17};
Caso o
número de valores de inicialização seja menor que o tamanho do vetor, as
posições restantes serão preenchidas com zeros. Por exemplo:
int teste[5] = {1, 2, 3}; /* teste[3] e teste[4] recebem 0 */
Para
matrizes cada linha é preenchida entre chaves, com valores separados por
vírgulas. Todas as linhas ficam entre chaves.
Ex:
int m[5][3] = {{1, 2, 3,}, {3, 2, 1}, {3,
3, 2}, {1, 2, 1} {3, 2, 0}};
Caso algum
elemento não seja explicitado, ele será preenchido com zero.
Ex:
int m2[3][4] = {{3, 2, 5}, {4, 6}, {1, 2, 3, 4}};
|
3 |
2 |
5 |
0 |
|
4 |
6 |
0 |
0 |
|
1 |
2 |
3 |
4 |
Ponteiro em
C é uma variável que, ao invés de armazenar um dado de um determinado tipo,
armazena o endereço de um dado de um determinado tipo:
Ponteiros
são usados freqüentemente para:
§
Acesso
a E/S mapeada em memória
§
Uso
de alocação dinâmica de memória.
§
Alternativa
para passagem de parâmetros por referência (em C++)
Os ponteiros
são declarados da seguinte maneira:
tipo *nome;
Onde nome
é o identificador do ponteiro e tipo é o tipo de dado para o qual ele
pode apontar.
Ex:
int *d;
short int *ptr;
float *ptr2;
Operador
&: Operador de
referenciação. Retorna o endereço de uma variável. Pode ser utilizado para
inicializar um ponteiro.
Operador
*: Operador de
derreferenciação. Retorna o conteúdo do endereço apontado por um ponteiro.
Ex:
int x,a;
int *ptr;
x = 30;
ptr = &x; /* ptr <- endereço
de x */
.
.
.
a = *ptr; /* a recebe o conteúdo do endereço apontado*/
Um modo
didático para o entendimento de ponteiros é “ler” o significado de * e &
como “conteúdo do endereço apontado por” e “endereço de”, respectivamente. Por
exemplo no seguinte código:
int *ptr;
int x;
x = 10;
*ptr = 3; /* O CONTEÚDO DO ENDEREÇO APONTADO POR ptr recebe 3 */
ptr = &x; /* ptr recebe o ENDEREÇO DE x */
- Ponteiros
sempre devem apontar para endereços correspondentes a variáveis que tenham sido
declaradas ou a regiões de memória nas quais não existam dados ou código de
outros programas. Por exemplo, o seguinte código armazena o conteúdo da
variável x em um endereço qualquer, que pode ser um endereço inválido.
int *ptr;
int x = 3;
*ptr = x; /* ERRO! Para onde o ptr aponta??? */
- Ponteiros
devem apontar para dados do mesmo tipo de sua declaração, do contrário podem
ocorrer interpretações erradas na operação de derrenferenciação. Por exemplo, o
seguinte código não armazena o valor 56 na variável f, já que o ponteiro para float
tentará ler o tamanho de um dado float a partir do endereço de
memória da variável x e não o tamanho de um int, que é o tipo declarado
da variável x.
int x = 56;
float *ptr;
float f;
ptr = &x; /*
Ponteiro para float aponta para int */
.
.
.
f = *ptr; /*
ERRO! Valor de F não é 56 */
Valores
numéricos inteiros podem ser adicionados ou subtraídos de um ponteiro. O
resultado é um endereço que segue as regras da aritmética de ponteiro, ou seja:
Para um
ponteiro declarado da seguinte maneira:
tipo* ptr;
e
inicializado com um endereço end1:
ptr = end1;
a operação ptr + N, onde N é um número
inteiro, resulta um endereço que é igual a end1 mais N vezes o
tamanho do tipo de dado apontado (ou seja, o tamanho em bytes de tipo).
Por exemplo, considerando que a variável x foi alocada no endereço 120:
int x, y;
int * ptr;
ptr = &x; /* ptr recebe
o endereço 120 */
y = *(ptr +
4); /* y recebe
o conteúdo do endereço 120 + 4*(tamanho do int) == endereço 128 */
Outro
exemplo:
float *ptr;
ptr = (float*)100; /* ponteiro é ‘forçado’ para o end. 100 */
.
.
.
*(ptr + 3) = 15; /* Número 15 é armazenado no
endereço 100 + 3x4 = 112 */
Quando um
vetor é declarado, o identificador deste vetor marca o endereço de início da
área de memória alocada por ele. Assim, o nome do vetor pode ser usado como
referência de endereço com os mesmos operadores utilizandos para ponteiros.
Portanto:
int vetor[10], b; /* vetor alocado no end. 100 (p. ex.) */
.
.
.
b = vetor[3]; /* posição 3 == end. 106 de memória */
Equivale a:
int vetor[10], b; /* vetor alocado no end. 100 (p. ex.) */
/* Na memória isso será guardado na posição 106 -> 100
+ 3 x Tamanho do tipo de dado apontado (int = 2 bytes) */
b = *(vetor + 3);
Em muitos
casos é interessante que uma função forneça mais do que um valor de saída como
seu resultado. Porém a sintaxe de linguagem C permite somente um valor de
retorno direto (através do comando return).
O uso de
ponteiros cria uma alternativa para que uma função forneça mais do que um valor
de saída, baseado no fato de que o conceito de endereço em um programa é
independente de escopo. Ou seja, se uma função chamadora fornecer para a função
chamada os endereços de suas variáveis, a função chamada poderá recebê-los em
ponteiros e preencher valores nestes endereços, que por sua vez estarão
disponíveis para acesso direto pela função chamadora.
Exemplo:
/* função recebe
dois endereços de ‘int’ como parâmetros
*/
void troca (int *a, int *b)
{
int aux;
aux = *a; /* conteúdo do endereço recebido como parâmetro */
*a = *b
*b = aux;
}
void main(void)
{
int n1 = 8, n2 = 5;
/*
endereços de n1 e n2 são passados para a função troca() */
troca (&n1, &n2);
printf(“%d, %d”, n1, n2);
}
Strings são
seqüências de caracteres diversos. São conhecidos por “literais” na teoria de
algoritmos estruturados, sendo representados entre aspas. Alguns exemplos de
strings:
“Fulano da Silva”,
“? Interrogação? “,
“1,234”,
“0”.
Em C,
strings são representadas através de vetores de caracteres, terminados com o
caractere de fim de string cujo valor na tabela ASCII é zero (0 ou \0).
Um vetor em
C que pretenda armazenar uma string n caracteres deve ser alocado com n+1
posições do tipo char para conter o terminador de string. A
inicialização de uma string pode ser efetuada com o uso de uma sequência de
caracteres entre aspas.
Exemplos de
declarações de string:
char frase[] =
“Primeira string”; /*Inicialização sem a dimensão */
char frase[16]
= “Primeira string”;
char
frase[6] = {‘T’, ‘e’, ‘s’, ‘t’, ‘e’, 0);
/* inicializado como um vetor de caracteres
comum, ‘forçando’ o caracter terminador */
No caso do
primeiro e do segundo exemplo, a representação do vetor da string frase
é:
|
‘P’ |
‘r’ |
‘i’ |
‘m’ |
‘e’ |
‘i’ |
‘r’ |
‘a’ |
‘’ |
‘f‘ |
‘r’ |
‘a’ |
‘s’ |
‘e’ |
0 |
Onde cada
quadrado representa um byte de memória (tamanho de um char).
String não é um
tipo primitivo da linguagem C, por isso as seguintes operações NÃO são
válidas:
char str1[10];
char str2[] = “Palavra 2”;
str1 = str2 /* ERRO! Não copia str2 em str1 */
if (str1 == str2) /* ERRO! Não compara str1 com str2 */
{
.
.
.
}
Para operar
sobre strings são utilizadas funções da biblioteca string.h. Esta
biblioteca possui algumas dezenas de funções com diversas variações e por
questões de simplificação apenas algumas das principais serão explicadas neste
material. Para maiores detalhes sobre as demais funções, consultar documentação
sobre a biblioteca (geralmente disponível nos arquivos de ajuda dos ambientes de
desenvolvimento).
Protótipo:
int strlen (char *string)
Descrição:
Retorna o número de caracteres de uma string (exceto o caractere de fim de
string).
Exemplo:
char nome[] = “Fulano”;
printf (“O nome possui %d letras”, strlen (nome));
Protótipo:
char *strcpy (char *string1, char *string2)
Descrição:
Copia o conteúdo de string2 em string1 e retorna o endereço de
string.
Exemplo:
char str1[10];
char str2[] = “Palavra”;
strcpy (str1, str2); /* Agora str1 também
contém “Palavra” */
Protótipo:
int strcmp (char *string1, char *string2)
Descrição:
Compara os conteúdos de string1 e string2 caracter a caracter e retorna
§ 0 se string1 = string2
§ <0 se string1 < string2
§ >0 se string1 > string2
Exemplo:
char nome1[] = “Fulano”
char nome2[] = “Beltrano”;
if (strcmp (nome1, nome2) == 0)
{
printf (“Nomes são iguais”);
}
else
{
printf (“Nomes são diferentes);
}
Protótipo:
void gets (char *string1)
Descrição:
Recebe uma string via teclado e armazena em string1. Os caracteres são
armazenados até que o enter seja pressionado.
Exemplo:
char nome[10];
gets (nome);
Observações:
§
a
função gets() permite que o usuário forneça mais caracteres do que os que podem
ser armazenados no vetor, o que pode causar um erro. Para evitar este problema,
pode-se utilizar a função fgets:
char nome[10];
fgets(nome, 10, stdin); /* ‘stdin’ é um
arquivo aberto por padrão, relacionado aos dados digitados via teclado */
No exemplo mostrado, fgets receberia 9
caracteres (ou até que o usuário teclasse enter) e armazenaria os dados
digitados na string nome, adicionando o caracter terminador de string. É
importante observar que, caso o usuário digitasse enter antes de 9 caracteres,
o caracter de nova linha (‘\n’) também seria armazenado no vetor.
§
gets()
termina quando o usuário digita um espaço, impedindo a digitação de frases com
mais de uma palavra. Para contornar este problema pode-se utilizar opções de
recebimento de dados do scanf:
scanf (“%s”, str); /* Recebe uma string até que
o primeiro espaço seja inserido */
scanf (“%[\n]s”, str) /* Recebe uma string até
que seja enviado o caractere ASCII \n, que corresponde a enter */
É possível
fazer uma entrada de dados controlada (ou seja, os caracteres são checados
assim que são recebidos) recebendo os mesmos um a um. No exemplo a seguir
implementaremos uma entrada de senha que mostra os caracteres * na tela ao
invés das letras correspondentes utilizando a função getch (que não ecoa o caracter digitado para o monitor). Note que
só serão aceitos letras e não números e símbolos.
int i = 0; char str[9];
printf (“Digite uma senha de oito letras”);
while (i < 8)
{
str[i] = getch();
if (((str[i] >= ‘a’) && (str[i] <= ‘z’)) || ((str[i] >= ‘A’) && (str[i] <= ‘Z’)))
{
printf (“*”);
i++;
}
}
Alocação
dinâmica de memória consiste em reservar espaço para o armazenamento de dados
sob demanda, liberando este espaço quando não for mais necessário. Para fazer
alocação dinâmica são utilizadas funções da biblioteca alloc.h, das
quais as principais serão apresentadas aqui.
A função malloc
é usada para tentar alocar um espaço contíguo de n bytes de memória.
Caso consiga ela retorna o endereço de início da área de memória, caso
contrário retorna zero. O protótipo da função é:
void* malloc
(int n);
Exemplo:
int *v;
int n;
printf (“Quantos elementos no vetor?”);
scanf (“%d”, &n);
v = (int*) malloc(n * sizeof(int)); /* Alocar n
vezes o tamanho de um ‘int’ */
if (v == 0)
{
printf (“Erro”);
}
else
{
/* aqui poderia vir o código para manipulação do vetor
.
.
.
*/
free (v);
}
A função free() é chamada ao final da utilização do espaço de
memória dinamicamente alocado para liberar este espaço, permitindo que seja
utilizado por outras operações de alocação dinâmica. O protótipo de free é
o seguinte:
void
free(void* ptr)
onde ptr contém
o endereço inicial da área de memória a ser desalocada.
[1] O tamanho do tipo int é dependente da plataforma sobre a qual o programa é compilado.
Para a compilação na plataforma DOS padrão (16 bits), um dado do tipo int ocupa dois bytes de tamanho
[2] /* e */ delimitam um comentário textual,
que não é compilado mas que auxilia o programador na documentação do seu
código. Note-se que existe uma versão alternativa para delimitar um comentário
até o final da linha, usando //, porém esta versão é padrão C++ e não deve ser
utilizada para compilação de código ANSI C (C padrão)
[3] A
tabela ASCII é uma tabela padronizada que relaciona um conjunto de caracteres a
valores numéricos entre 0 e 255. Por exemplo, o caracter correspondente ao
dígito ‘9’ corresponde ao código ASCII 57.
[4] Para maiores informações sobre números
octais e hexadecimais, consultar o texto sobre Bases Numéricas
[5] Estamos considerando programas executados
“linearmente”, ou seja, sem a ocorrência de eventos assíncronos (p. ex.,
interrupções).