Linguagem C
Curso de Nivelamento LCG - 2004
AVISO• Estas transparências contêm apenas
o que eu me lembro da linguagem• Ultimamente, minha memória tem
falhado bastante • Detalhes devem ser procurados nos
manuais
Linguagem C • Proposta por Kernighan e Ritchie (1970)• Linguagem para desenvolvimento de sistemas
▪ UNIX supostamente deveria ser todo escrito em “C”▪ Permite construções pouco seguras
• Ponteiros podem ser usados para endereçar toda a memória
• Muito popular e usada até hoje• Variantes mais atualizadas e +seguras foram
sendo propostas▪ ANSI C▪ Objective C (NextStep)▪ C++▪ C# (Microsoft .NET)
• 90% do software básico de hoje é escrito em C ou C++
Programa em C• Organizado em “unidades de
compilação” ▪ Cada unidade é um arquivo com
extensão .c podendo conter diversas diretivas de inclusão de outros arquivos-fonte:#include <xpto.h>
#include ”foobar.h”
▪ arquivo.h “include”▪ Nome entre < e >: include em diretórios-
padrão ▪ Nome entre “aspas”: include no mesmo
diretório que o arquivo .c
Programa em C• Um programa simples tem apenas
uma unidade• Unidade principal é a que contém a
função main
Declarações e Definições• Arquivos-fonte contêm
▪ Declarações• Informam nomes e tipos de variáveis e funções
▪ Diretivas para o pré-compilador• #include • #define • #if / #ifdef
▪ Definição de funções• Onde os comandos são efetivamente escritos
▪ Comentários• /* Comentário */• // Também aceito em alguns compiladores (não é
padrão)
Programa Exemplo#include <stdio.h>
char *titulo = "Programa Exemplo\n";/* * Programa para imprimir argumentos dados */int main (int argc, char * argv []) { int i; printf (titulo); printf ("Argumentos:\n"); for (i = 0; i < argc; ++i) { printf ("%d: %s\n", i, argv[i]); } return 0;}
Diretiva de inclusão
Declaração de variável global
Função main(Programa principal)
uso da função printf definida em
stdio.h
parâmetros da função mainargc: número de argsargv: array de strings
Variável local
função main retorna 0 para indicar sucesso
Pré-processador• Processa as diretivas #...• Compilador na verdade vê o
programa fonte pré-processado• Pré-processador não entende “C”,
apenas processa textos• #include inclui o arquivo indicado
como se tivesse sido digitado naquela linha
#define• Também chamado de macro• #define nome texto
▪ sempre que nome for citado no programa, será substituído por texto
▪ Exemplo:#define X a , b , c
f (X);
▪ ... é equivalente af (a , b, c);
#define• É possível ter macros parametrizadas• #define nome(arg1,...argN) texto
▪ texto contém os símbolos arg que são substituídos pelos valores passados quando do uso
▪ Exemplo:#define MAX(a,b) a > b ? a : b
x = MAX(b,c);
▪ ... é equivalente ax = b > c ? b : c;
#define• Cuidado! substituição é literal:
#define MAX(a,b) a > b ? a : b
x = MAX(x & 2, 1);
▪ ... é equivalente ax = x & 2 > 1 ? x & 2 : 1;
▪ ... que é equivalente ax = (x & (2 > 1)) ? (x & 2) : 1;
• Nesses casos, é melhor usar parênteses
#define MAX(a,b) ((a)>(b) ? (a) : (b))
Compilação condicional• Expediente para obter variações do
mesmo código adaptado a diversos ambientes
• Diretivas #if , #ifdef, #else, #endif• Exemplo:
#ifdef WIN... código que contorna bug do Windows
#else... código normal
#endif
Dados em C• Tipos nativos
▪ int, float, double, char▪ modificadores : unsigned, signed, long, short
• Ponteiros: tipo *• Arrays: tipo [tamanho]• Enumerações:
▪ enum { nomes }• Estruturas:
▪ struct { campos }▪ union { campos }
Declaração de variáveis em C?qualif??alocação? tipo var ? = valor?;
• qualif▪ volatile pode mudar inesperadamente
(multithreading)▪ const valor constante
• alocação ▪ extern definido em outra unidade de
compilação▪ static tempo de vida estático ▪ auto alocada na pilha de execução▪ register alocada em registradores
Declaração de variáveis em C• tipo é um nome de tipo nativo ou
previamente declarado • var é
▪ identificador▪ *var▪ var [tamanho]
• = valor▪ inicialização da variável▪ valor tem que ser uma expressão
constante • uma expressão constante é aquela que pode
ser avaliada pelo compilador
Exemplosregister int i, j, k;
auto unsigned char a[10];
static double *p[10];
float **f;
const double pi = 3.1416;
extern unsigned long int param;
struct { int s1; char c; } s;
enum { pera, limao, maca } fruta;
Declaração de tipostypedef decl;• decl tem o mesmo formato de uma
declaração de variáveis, mas sem qualificadores
• identificadores que aparecem em decl vão ser associados a nomes de tipos, e não variáveis
• nomes de tipos podem ser usados em declarações posteriormente
• Exemplo:typedef int * ponteiro;
ponteiro p, *pp;
Arrays• Coleção de valores do mesmo tipo
alocados em seqüência na memóriaint a [10];▪ 10 variáveis inteiras referenciadas como a[0],
a[1], ... a[9]
• Arrays multidimensionais são alocados com os índices mais à esquerda variando mais rápidoint b[2][2][2] = {5,2,3,9,1,2,3,4};
5 2 3 9 1 2 3 4
b[0][0][0] b[0][0][1] b[0][1][0] b[0][1][1] b[1][0][0] b[1][0][1] b[1][1][0] b[1][1][1]
Endereços crescentes de memória
Inicialização de arrays• Um array pode ser inicializado assim que
declarado• Lista de valores é posta entre chaves• Para arrays multidimensionais, a
inicialização é por linha• Exemplo
int i [10] = { 1, 2, 3, 4, 5, 5, 4, 3, 2, 1 };
int m [3][2] = { { 1, 2 }, { 3, 4 }, { 5, 6 } };
Inicialização de arrays• Arrays de caracteres podem ser
inicializados com uma cadeia de caracteres entre aspas (string)▪ É assumido um caractere nulo no fim da
string• Exemplo
char s [10] = ”alo”;
• É o mesmo quechar s [10] = {’a’,’l’,’o’,′\0’};
• Observe que posições não inicializadas têm valor indefinido
Ponteiros• Variáveis que contêm o endereço de
outra variável• Recurso poderoso mas perigoso!int i = 5, *p = &i;
5 2124
i
2124
p
2126
Identificadores
Endereços
5i
p
Endereço de i
Ponteiros e Arrays• Um ponteiro e um array muitas vezes
se confundem uma vez que o nome de um array é interpretado como o endereço do primeiro elemento do array
• Por exemplo:int a [3] = {1,2,3};int *p, *q;p = a;q = p;
• Após este código, a, p e q referem-se todos ao mesmo endereço
Ponteiros e Arrays• De forma semelhante, ponteiros
podem ser indexados como arrays:p [1] = 7;
*(p+1) = 7;
• Entretanto, arrays e ponteiros não são a mesma coisa▪ Um ponteiro ocupa uma palavra de
memória e um array várias▪ Um array não pode mudar de endereço
• Qual o significado de a = p ?
Estruturas• Tipo agregado heterogêneo• Conjunto de campos com nomes• Imprescindível para construção de
estruturas de dados mais complexas• Base para o conceito de classe em
C++
Estruturas• Forma geral:
struct ?nome? {decl;...decl;
} var;onde▪ nome é o nome da estrutura▪ decl é uma declaração de campo (semelhante
às declarações de variáveis;▪ var é uma ou mais variáveis que se quer
declarar• podem ser simples, arrays ou ponteiros
Estruturas• Exemplo: a declaração
struct S {int a;
char b, *s, n[10];
struct S * ptr;
} v1, v2 [10], *q;▪ ... declara uma estrutura v1, um array
de 10 estruturas v2 e um ponteiro para estrutura q
Estruturas• Os campos da estrutura são acessados
escrevendo o nome da variável e o nome do campo separados por um ponto:v1.a = 1;
*v2[4].s = ’a’;
(*q).n[2] = ’b’;
• Se p é um ponteiro para uma estrutura, pode-se substituir a construção (*p).campo por p->campoq->n[2] = ’b’;
Uniões• Semelhantes a struct, mas todos os
campos ocupam a mesma área de memória
• Usar com muito cuidado!• Sintaxe: trocar struct por union• Exemplo:
union {int a; double b; char c;
} x;x.a = 10;/* Quanto vale x.b ??? */
Expressões• Compostas de operandos,
operadores e parênteses• Operandos
▪ variáveis: a, b, c, *p, t[b], t[a+5] ▪ constantes: 1, 1L, 2.4, ’a’, ”xxxxx” ▪ chamadas de função: f(), g(2, 4)
Expressões• Operadores
▪ Aritméticos: +, -, /,*▪ Comparação: ==, !=, >, <, >=, <=▪ Lógicos: &&, ||, !▪ Bit a Bit: &, |, ~, ^▪ Condicional: expr ? expr : expr▪ Referência e Dereferência: & , *▪ Cast: (tipo) expr
• Parênteses: servem para alterar a ordem de precedência▪ Precedência funciona quase sempre como se
espera:• a * b + c == d && *x = 1
▪ Na dúvida: olhar o livro ou usar parênteses
Expressões com efeito colateral• Ao avaliar a expressão, alguma variável
pode mudar de valor. Por exemplo: a = b = 4 ;▪ a recebe o valor da expressão b = 4▪ expressão vale 4, mas tem como efeito
colateral atribuir 4 a b• Chamar funções pode ter efeito colateral:
▪ Ex.: f(&a) pode alterar o valor de a (ou de qualquer variável global)
• Outros operadores com efeito colateral: ▪ Misturas de atribuição com operadores
binários: +=, *=, /=, &=, |= etc▪ Pré e Pós incremento/decremento: ++ e ––
• a = x++; a = x; x = x+1;• a = ++x; x = x+1; a = x;
Funções• Encapsulam os algoritmos• Forma geral:
tipo nomefunção (arg, ... arg) {declaração;...declaração;comando;...comando;
}
tipo do valor de retorno Lista de
parâmentros
Valor de retorno de Funções• O tipo de retorno pode ser
▪ um tipo simples: int, double, char, etc;▪ um ponteiro (endereço)
• Se a função é avaliada e não passa por nenhum comando return, então o valor retornado é indefinido
• Se a função não retorna valores, pode-se usar void como tipo de retorno
• Mesmo que uma função retorne um valor, este não necessariamente é usado por quem chama:i = g(5);g(9);
Argumentos de funções• Lista de argumentos é semelhante à
declaração de variáveis• Parâmetros são passados por valor, à
exceção de arrays e structs▪ Um parâmetro passado por valor é
equivalente a uma variável local inicializada com o valor passado
▪ Para alterar uma variável passada como argumento, usa-se ponteiros
Passagem de Parâmetrosint j = 4;
void f (int i) {i = 5;
}
int main () {f (j);
printf (”%d\n”, j);
}• Resultado: 4
Passagem de Parâmetrosint j[1] = { 4 };
void f (int i[]) {i[0] = 5;
}
int main () {f (j);
printf (”%d\n”, j[0]);
}• Resultado: 5
Passagem de Parâmetrosint j = 4;
void f (int *i) {*i = 5;
}
int main () {f (&j);
printf (”%d\n”, j);
}• Resultado: 5
if• Comando condicional fundamental• Formato1:
▪ if (expr) comando1▪ Se expr avaliar como qualquer valor
diferente de 0, comando1 é executado.
• Formato2: ▪ if (expr) comando1 else comando2 ▪ Se expr avaliar como qualquer valor
diferente de 0, comando1 é executado, senão, comando2 é executado
Comando composto• Vários comandos sintaticamente equivalentes
a um só• Formato: { decl; ... decl; comando; ....
comando; }• OBS.:
▪ Todo comando é terminado por um ponto-e-vírgula▪ Comandos compostos não precisam de ponto-e-
vírgula
• Ex.: if (a == b) {
c = d; printf (”OK”);
} else { c = b; printf (”Não OK”);
}
switch• Permite selecionar um entre vários
caminhos• Formato:
switch { expr } {case val : comando; ... comando; ...case val : comando; ... comando; default: comando; ... comando;
}• Avalia expr , procura o val igual e começa a
executar os comandos subseqüentes• Se não encontrar, executa os comandos
após a cláusula default (se existir)• OBS.: o switch não pula os demais case’s.
Para encerrar o case , usar o comando break
break• Interrompe o comando composto
atual e retoma a execução no próximo comando
• Muito usado em switches e laços de repetição
• Ex.:switch (i) {
case 1: case 2: i = 3;case 3: case 4: k = 4; break;case 5: k = 9; break;default: k = 10;
}
while ... do e do ... while• while (expr) comando
▪ enquanto expr é diferente de 0, comando é repetido
▪ pode não executar comando nenhuma vez
• do comando while (expr)▪ repete comando enquanto expr é
diferente de 0▪ sempre executa comando pelo menos
uma vez
for• for (expr1; expr2; expr3) comando
▪ equivale aexpr1;while (expr2) {
comando;expr3;
}▪ 90% das vezes, alguma coisa como
for (i = 0; i < n; i++) {a [i] = ...;
}
bibliotecas• fornecem o restante da funcionalidade da
linguagem▪ entrada e saída▪ controle de processos e multiprogramação▪ comunicação c/ sistema operacional▪ matemática▪ gráficos e multimídia▪ etc
• usar uma biblioteca requer:▪ inclusão dos headers da biblioteca no
programa cliente▪ “link-edição” da biblioteca com o executável
Entrada e Saída (I/O)• Duas bibliotecas padrão
▪ I/O de “baixo nível”• Usa chamadas diretas do Sistema
Operacional• entrada e saída sem “buferização”•#include <io.h>
▪ I/O de “alto nível”• Usa I/O de baixo nível• entrada e saída buferizadas• suporta formatação de dados binários em
texto e vice-versa: scanf, printf•#include <stdio.h>
Entrada e Saída c/ stdio• Dados são lidos/escritos em arquivos
▪ Estado de um arquivo é guardado em memória por um tipo struct chamado FILE
▪ RFunções que lidam com arquivos têm parâmetros do tipo FILE*
• Existem 3 arquivos de entrada/e saída default:▪ stdin arquivo de entrada default▪ stdout arquivo de saída default▪ stderr arquivo de saída de mensagens de erro▪ normalmente associados ao terminal
• (ver o tópico redirecionamento na documentação do seu Sistema Operacional)
printf• saída formatada• uso: printf (“formato”, expr1, ...
exprN);• igual a fprintf (stdout,
“formato”, ...);• expr são os valores que se quer
imprimir• formato é uma string comum, mas
intercalada com especificadores de formato
printf• Alguns especificadores de formato:
▪ %c escreve um caractere▪ %d escreve um inteiro em decimal▪ %x escreve um ▪ %ld escreve um inteiro longo em decimal▪ %g escreve um float ou double▪ %s escreve uma cadeia (string)
• Exemplo:int i = 10; char c = ‘a’; double d = 0.315155;
printf (”i vale %d, c vale %c e d vale %g”, i, c, d);
printf• Opcionalmente, especificadores
podem incluir modificadores entre o % e a letra. Ex.:▪ %10d escreve usando 10 caracteres
(preenchido com brancos)▪ %010d escreve usando 10 caracteres
(preenchido com zeros)▪ %10.3g escreve em 10 caracteres,
mantendo 3 casas decmais
scanf• análogo do printf para leitura• uso: scanf (“formato”, addr1, ... addr2);• retorna o número de valores lidos• addr são endereços de variáveis• Exemplo:
int i; char c; float f;if (scanf (”%d %c %f”, &i, &c, &f) != 3)
fprintf (stderr, ”falha ao converter algum dado\n”);
• Importante: como sempre ao manipular endereços, é preciso cuidado redobrado:scanf (”%d %c %g”, &i, &c, &f);
▪ scanf espera um double e não um float!
Alocação de Memória• variáveis estáticas (static)
alocadas de uma vez só quando da carga programa
• variáveis automáticas (auto) alocadas quando o fluxo de execução entra num bloco e desalocadas quando sai
• Para ter estruturas de dados de tamanho variável é necessário alocar memória dinâmicamente
malloc• #include <stdlib.h>• void * malloc (nbytes)
▪ retorna um ponteiro para uma área livre de nbytes bytes
▪ área não é inicializada (pode conter lixo)▪ para saber quantos bytes são
necessários para guardar um dado, usar a função sizeof(tipo)
▪ Exemplo: queremos alocar dinâmicamente um array de n doubles
double *a = (double*) malloc(sizeof(double)*n);
free• desfaz o trabalho do malloc• retorna a memória alocada para o
sistema• uso: free (p); onde p é um ponteiro
alocado por malloc, calloc ou realloc
Programa exemplo: listas#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
typedef struct NoStruct {
int val;
struct NoStruct * prox;
} NoLista, * Lista;
Programa exemplo: listasvoid push (Lista* pLista, int n) { Lista tmp = (Lista)
malloc (sizeof (NoLista));assert (pLista != NULL);tmp->val = n;tmp->prox = *pLista;*pLista = tmp;
}
int top (Lista lista) {assert (lista != NULL);return lista->val;
}
Programa exemplo: listasvoid pop (Lista* pLista) {
Lista tmp;assert (pLista != NULL);assert (*pLista != NULL);tmp = *pLista;*pLista = tmp->prox;free (tmp);
}
void print (Lista lista) {if (lista == NULL) {
printf ("\n");} else {
printf ("%d ", top(lista));print (lista->prox);
}}
Programa exemplo: listas
int main () {Lista l = NULL;push (&l, 1); push (&l, 2); push (&l, 3);print (l);pop (&l->prox);print (l);return 0;
}
Top Related