Você está na página 1de 27

C para programadores de

Python

por Carl Burch, Hendrix College, agosto de 2011


traduzido para a disciplina de MC202, junho de 2018

C para programadores de Python por Carl Burch   está


licenciado sob uma Licença Creative Commons
Atribuição-Compartilha Igual 3.0 dos Estados Unidos .

Baseado em um trabalho em
www.toves.org/books/cpy/ .

Conteúdo

C para programadores de Python

Conteúdo

1. Construindo um programa simples

1.1. Compiladores versus interpretadores

1.2. Declarações de Variáveis

1.3. Espaço em branco

1.4. A função printf()

1.5. Funções

2. Construções de nível de instrução

2.1. Operadores
2.2. Tipos básicos

2.3. Chaves

2.4. Instruções

2.5. Vetores

2.6. Comentários

3. Bibliotecas

3.1. Protótipos de função

3.2. Arquivos de cabeçalho

3.3. Constantes

Nos anos setenta, nos laboratórios da Bell, Ken


Thompson projetou a linguagem de programação C
para ajudar no desenvolvimento do sistema operacional
UNIX. Seguindo a uma série de eventos históricos,
poucos intencionais, o UNIX cresceu de um pequeno
projeto de pesquisa para um sistema operacional
popular de força industrial. E, junto com o sucesso do
UNIX, veio a linguagem C, já que o sistema operacional
foi projetado para que os programas C pudessem
acessar todos os seus recursos. À medida em que mais
programadores adquiriram experiência com C, eles
começaram a usá-lo também em outras plataformas, de
modo que ele se tornou uma das principais linguagens
para o desenvolvimento de software no final dos anos
80.

Enquanto hoje C não desfruta do amplo domínio que já


teve, sua influência é tão grande que muitas outras
linguagens foram projetadas para se parecer com ele,
incluindo C++, C#, Objective-C, Java, JavaScript, PHP
e Perl. Saber C é em si uma coisa boa — é um excelente
ponto de partida para se relacionar mais diretamente
com o que um computador faz. Mas aprender C
também é um bom ponto de partida para se familiarizar
com todos esses outros idiomas.

Este documento é direcionado   a pessoas que


aprenderam programação em Python e que desejam
aprender sobre C. “A influência de C em Python é
considerável”, nas palavras do inventor de   Python,
Guido van Rossum (“Uma Introdução a Python para
Programadores UNIX/C”). , 1993). Então, aprender
Python é um bom primeiro passo para aprender C.
1. Construindo um programa
simples

Começaremos com vários princípios gerais,


trabalhando para construir um programa em C
completo — mas limitado — até o final da Seção 1.

1.1. Compiladores versus interpretadores

Uma grande diferença entre C e Python é simplesmente


como você executa programas escritos nos dois
idiomas. Com programas em C, você geralmente usa
um compilador   quando está pronto para que um
programa em C execute.   Em contraste, com Python,
você normalmente usa um interpretador   . Um
compilador gera um arquivo contendo a tradução do
programa no código nativo da máquina. O compilador
na verdade não executa o programa; em vez disso, você
primeiro executa o compilador para criar um
executável nativo e, em seguida, executa o executável
gerado. Assim, depois de escrever um programa em C,
executá-lo é um processo de duas etapas.

me@computer:~$ gcc meu_programa.c


me@computer:~$ ./a.out
GCD: 8

No primeiro comando (“gcc meu_programa.c”),


invocamos o compilador, chamado gcc . O compilador lê
o arquivo meu_programa.c , no qual salvamos nosso
código C e gera um novo arquivo chamado
a.out contendo uma tradução desse código no código
binário usado pela máquina. No segundo comando
(“./a.out”), dizemos ao computador para executar este
código binário. Quando está executando o programa, o
computador não tem idéia de que a.out acabou de ser
criado a partir de algum programa em C: ele
simplesmente executa cegamente o código encontrado
no arquivo a.out, da mesma forma que executa
cegamente o código encontrado no arquivo gcc arquivo
em resposta ao primeiro comando.

Em contraste, um interpretador lê o programa escrito


pelo usuário e o executa diretamente. Isso remove uma
etapa do processo de execução, mas um compilador
tem a vantagem de gerar um executável que pode ser
executado da mesma forma que a maioria dos outros
aplicativos da máquina e isso pode levar a tempos de
execução consideravelmente mais rápidos.

Ser compilada ou interpretada tem algumas


consequências importantes no projeto de uma
linguagem de programação. C é projetado de tal forma
que o compilador possa deduzir tudo aquilo de que
precisa para traduzir o programa C sem realmente
executar o programa.

1.2. Declarações de Variáveis

Entre as informações de que C precisa que os


programadores forneçam ao compilador, um dos
exemplos mais importantes   é que C requer
declarações de variáveis, informando para
o   compilador cada uma das variáveis antes que elas
sejam realmente usadas. Isso é típico de muitas
linguagens de programação proeminentes,
particularmente entre aquelas que devem ser
compiladas antes de serem executadas.

Em C, a declaração da variável define o tipo   da


variável, especificando se ela é um número inteiro
(int), um número de ponto flutuante (double), um
caractere (char) ou algum outro tipo que estudaremos
mais tarde. Uma vez que você declara que uma variável
é de um tipo particular, você não pode mudar seu tipo:
se a variável x é declarada como do tipo double, e você
realiza a atribuição “x = 3”, então x irá guardar o valor
de ponto flutuante 3,0 ao invés do inteiro 3. Você pode,
se desejar, imaginar que a variável x é uma caixa que é
capaz de guardar apenas valores double; se você tentar
colocar algo diferente (como o valor inteiro 3), o
compilador converterá esse valor em um valor
double de forma com que ele caiba na caixa.

Declarar uma variável é bastante simples: você insere o


tipo da variável, alguns espaços em branco, o nome da
variável e um ponto e vírgula:
double x;

Em C, as declarações de variáveis devem ficar no topo


da função em que são usadas.

Se você se esquecer de declarar uma variável, o


compilador se recusará a compilar o programa, i.e., ele
não vai compilar um programa em que uma variável é
usada, mas não é declarada. A mensagem de erro
indicará a linha dentro do programa, o nome da
variável e uma mensagem como “símbolo não
declarado”.

Para um programador Python, parece difícil incluir


essas declarações de variáveis em um programa,
embora isso fique   mais fácil com mais prática. Os
programadores de C tendem a sentir que as
declarações de variáveis compensam esse pequeno
trabalho extra. A grande vantagem é que o compilador
irá identificar automaticamente sempre que um nome
de variável for digitado incorretamente e apontar
diretamente para a linha onde o nome está escrito
incorretamente. Isso é muito mais conveniente do que
executar um programa e descobrir que ele está errado
porque em algum lugar o nome da variável contém
erros ortográficos.

1.3. Espaço em branco

Em Python, os espaços em branco como tabs e quebras


de linhas são importantes: você separa suas instruções
colocando-as em linhas separadas e indica a extensão
de um bloco (como o corpo de uma instrução while ou
if) usando o recuo. Esses usos do espaço em branco são
idiossincrasias de Python. (Na verdade, não são muitas
as linguagens que usam espaços para separar blocos.)

Como a maioria das linguagens de programação, C não


usa espaço em branco, exceto para separar palavras. A
maioria das instruções   é terminada com um ponto e
vírgula ';' e blocos de instruções são indicados usando
um par de chaves, '{ ' e '}'. Aqui está um fragmento de
exemplo de um programa em C, com seu equivalente
em Python.
Figura 1: fragmento C e equivalente em Python
Fragmento em C Equivalente em Python

disc = b * b - 4 * a * disc = b * b - 4 * a *
c; c
if (disc < 0) { if disc < 0:
    num_sol = 0;     num_sol = 0
} else { else:
t0 = -b / a; t0 = -b / a
    if (disc == 0) {     if disc == 0:
        num_sol = 1;         num_sol = 1
        sol0 = t0 / 2;         sol0 = t0 / 2
    } else {     else:
num_sol = 2; num_sol = 2
        t1   =         t1 = disc **
sqrt(disc) / a; 0.5 / a
        sol0 = (t0 +         sol0 = (t0 +
t1) / 2; t1) / 2
sol1 = (t0 - sol1 = (t0 -
t1) / 2; t1) / 2
    }
}

O programa C à esquerda é como eu o escreveria. No


entanto, o espaço em branco é insignificante, então o
computador ficaria tão feliz quanto se eu tivesse escrito
o seguinte.

disc=b*b-4*a*c;if(disc<0){
num_sol=0;}else{t0=-b/a;if(
disc==0){num_sol=1;sol0=t0/2
;}else{num_sol=2;t1=sqrt(disc/a;
sol0=(t0+t1)/2;sol1=(t0-t1)/2;}}

Enquanto o computador pode estar feliz com isso,


nenhum humano sensato preferiria essa versão.
Portanto, um programador competente tende a ser
muito cuidadoso com os espaços em branco para
indicar a estrutura do programa.

(Há algumas exceções à regra de ignorar o espaço em


branco: o espaço é frequentemente importante para
separar palavras de símbolos. O fragmento “intmain” é
diferente do fragmento “int main”; da mesma forma, o
fragmento “a++ + 1” é diferente do fragmento “a+ +
+1”.)
1.4. A função printf()

Um ingrediente importante para escrever programas C


úteis é exibir os resultados para o usuário ver, o que
você realizaria usando print em Python. Em C, você
usa em printf(). Essa é na verdade uma função —
que uma das funções mais úteis da biblioteca padrão de
funções da linguagem C.

A maneira com   que   os parâmetros de printf()


funcionam é um pouco complicada, mas é também
bastante conveniente: o   primeiro parâmetro é uma
string que especifica o formato do texto que se deseja
imprimir e os seguintes parâmetros indicam os valores
a serem impressos. A maneira mais fácil de entender
isso é olhar para um exemplo.

printf("# solns: %d\n", num_sol);

Esta linha diz para imprimir usando "#solns:%


d\n" como a string de formatação. A função printf()
percorre essa string de formato, imprimindo os
caracteres "# solns: " antes de chegar ao caractere
de porcentagem '%'. O caractere de porcentagem é
considerado especial para a função printf(): ele diz
 para imprimir um valor especificado em um parâmetro
subsequente. Nesse caso, uma letra minúscula
d acompanha   o caractere de porcentagem, indicando
para exibir o parâmetro do tipo int na forma decimal.
(O d   representa decimal.) Então, quando
printf() atinge "%d", ele olha para o valor do seguinte
parâmetro (vamos imaginar num_sol vale   2 neste
exemplo) e exibe esse valor. Em seguida, continua
pela string de formatação, neste caso, exibindo um
caractere de quebra de linha. Portanto, o usuário vê a
seguinte linha de saída:

# solns: 2

Como Python, C permite incluir caracteres de escape


em uma string usando uma barra invertida. A sequência
'\n' representa o caractere de quebra de linha — isto
é, o caractere que representa uma quebra de linha. Da
mesma forma, '\t'   representa o caractere de
tabulação, '\"' representa o caractere de aspas duplas
e '\\' representa o caractere de barra invertida. Esses
caracteres de escape fazem parte da sintaxe C e não
fazem parte da função printf(). (Isso é, a string que a
printf()   função recebe na verdade contém uma
quebra de linha, não uma barra invertida seguida por
um n. Assim, a natureza da barra invertida é
fundamentalmente diferente do caractere de
porcentagem, que printf()   veria e interpretaria em
tempo de execução.)

Vamos ver outro exemplo.

printf("# solns: %d", num_sol);


printf("solns: %f, %f", sol0, sol1);

Vamos supor que num_sol tenha valor 2, sol0 tenha


valor 4 e sol1 tenha valor 1. Quando o computador
executar essas duas chamadas à função printf(), ele
primeiro executará a primeira linha, que exibe “#
solns: 2” e, em seguida, a segunda, que exibe “solns:
4.0, 1.0”, conforme ilustrado abaixo.

# solns: 2solns: 4,0, 1,0

Observe que printf() exibe apenas o que é dito a ele,


sem adicionar espaços extras ou quebras de linha. Se
quisermos que uma quebra de linha seja inserida entre
as duas partes da saída, precisaríamos incluir “\n” no
final da primeira string de formatação.

A segunda chamada printf()   neste exemplo ilustra


como a função pode imprimir vários valores de
parâmetro. Na verdade, neste caso não há nenhuma
razão para não termos combinado as duas chamadas a
printf() em uma.

printf("# solns: %dsolns: %f, %f",


    num_sol, sol0, sol1);

Aliás, a função printf()   exibe “4.0” ao invés de


simplesmente “4”, porque a string de formato usa “%f”,
que diz para interpretar os parâmetros como números
de ponto flutuante. Se você quiser exibir apenas “4”,
você pode se sentir tentado a usar "%d”. Mas isso não
funcionaria, porque printf() iria interpretar a
representação binária usada para um número de ponto
flutuante como uma representação binária para um
número inteiro, e esses tipos são armazenados de
maneiras completamente diferentes!   No meu
computador, substituir cada “%f” por “%d” leva à
seguinte saída:

# solns: 2solns: 0, 1074790400

Há diversos caracteres que podem seguir o caractere


de porcentagem na string de formatação.

● %d, como já vimos, diz para imprimir um valor


do tipo int na forma decimal.

● %x diz para imprimir um valor do tipo int em


formato hexadecimal.

● %f   diz para imprimir um valor do tipo


double em forma de ponto decimal.

● %e   diz para imprimir um valor do tipo


double em notação científica (por exemplo,
3.000000e8 ).

● %c diz para imprimir um valor do tipo char.

● %s diz para imprimir uma string. Não há


nenhum tipo de variável para representar uma
string, mas C suporta alguns recursos para string
usando vetores de caracteres. Vamos adiar a
discussão dessas propriedades   para depois de
discutirmos ponteiros, pois entender strings
depende de alguns conceitos mais complexos que
ainda não vimos.

Você também pode incluir um número entre o caractere


de porcentagem e o descritor de formato como em “
%10d”, que informa printf()   para justificar à direita
um número inteiro decimal em dez colunas.

1.5. Funções

Ao contrário de Python, todo o código em C deve estar


aninhado dentro das funções e as funções não podem
ser aninhadas umas nas outras. Assim, a estrutura
geral de um programa em C é tipicamente muito direta:
é uma lista de definições de funções, uma após a outra,
cada uma contendo uma lista de instruções a serem
executadas quando a função é chamada.

Aqui está um exemplo simples de uma definição de


função:

double expon(double b, int e) {


    if (e == 0) {
        return 1.0;
    } else {
return b * expon(b, e - 1);
    }
}

Uma função em C é definida nomeando o tipo de


retorno (double neste exemplo, porque a função
devolve um resultado de tipo ponto flutuante), seguido
pelo nome da função (expon), seguido por um par de
parênteses listando os parâmetros. Cada parâmetro é
descrito incluindo o tipo do parâmetro e o nome do
parâmetro. Depois da lista de parâmetros entre
parênteses, deve vir um par de chaves, no qual você
aninha o corpo da função.

Se você tiver uma função que não tenha nenhum valor


de retorno, você deve escrever void no lugar do tipo de
retorno.

Programas têm uma função especial chamada main,


cujo tipo de retorno é um inteiro. Esta função é o
“ponto de partida” do programa: o computador chama
essencialmente a função main   do programa quando
quiser executar o programa. O valor de retorno inteiro
é pode ser ignorado em grande parte das vezes; assim,
sempre retornaremos 0 ao invés de nos preocuparmos
sobre como o valor de retorno pode ser usado.

Estamos agora em posição de apresentar um programa


C completo, juntamente com seu equivalente em
Python.

Figura 2:   Um programa C completo e um


equivalente em Python

Programa em C Programa em Python


int gcd(int a, int b) def gcd(a, b):
{ if b == 0:
  if (b == 0) {     return a
    return a;   else:
  } else { return gcd(b, a   %
    return gcd(b, a % b)
b);
  } print("GCD: " +
} str(gcd(24, 40)))

int main() {
  printf("GCD: %d\n",
    gcd(24, 40));
  return 0;
}

Como você pode ver, o programa C consiste em duas


definições de função. Em contraste com o programa
Python, onde a linha que contém print existe fora de
qualquer definição de função, o programa C requer
printf() esteja na função main do programa, já que é
onde colocamos todo o código de nível superior que
deve ser concluído quando o programa é executado.

2. Construções de nível de
instrução

Agora que vimos como construir um programa


completo, vamos aprender o universo do que podemos
fazer dentro de uma função C, através das diversas
formas de escrever instruções na linguagem de
programação C.

2.1. Operadores

Um operador é algo que podemos usar em expressões


aritméticas, como um sinal de mais '+' ou um teste de
igualdade '=='. Os operadores em C parecerão
familiares, uma vez que o criador de Python, Guido van
Rossum, escolheu começar a partir da lista de
operadores de C; mas existem algumas diferenças
importantes.
Figura 3. Principais operadores em C e Python

Precedência de Precedência de operador


operador em C em Python
++ --(postfix) **
+ - ! (operadores + - (operadores unários)
unários) * / % //
* / % + - (operadores binários)
+ -(operadores unários) < > <= >= == !=
< > <= >= not
== != and
&& or
||
= += -= *= /= %=

Algumas distinções importantes:

● C não possui um operador de exponenciação


como o operador **   de   Python . Para realizar
exponenciação em C, você precisará usar a
função de biblioteca pow(). Por exemplo, a
expressão pow(1.1, 2.0) calcula 1,1².

● C usa símbolos em vez de palavras para as


operações booleanas E (&&), OR (||) e NÃO (!).

● O nível de precedência de NOT (o operador !) é


muito alto em C. Isso quase nunca é desejado,
então você acaba precisando de parênteses na
maioria das vezes que deseja usar o operador !.

● Em C, a atribuição de variável é feita com um


operador, enquanto em Python a atribuição como
uma instrução. O valor devolvido pelo operador
de atribuição é o valor atribuído. Uma
consequência do projeto de C é que uma
atribuição pode legalmente ser parte de outra
instrução.

while ((a = getchar()) != EOF)

Aqui, atribuímos o valor devolvido


por getchar()   à variável a e, em seguida,
testamos se o valor atribuído a a corresponde à
constante EOF, que é usada para decidir se se
deve repetir o laço novamente. Muitas pessoas
afirmam que esse estilo de programação é
extraordinariamente ruim; outros acham que ele
é conveniente. Python, é claro, foi projetado para
que uma atribuição ocorra em uma instrução
separada, portanto, as atribuições de
aninhamento dentro while   da condição de uma
instrução são ilegais em Python.

● Operadores de C ++   e -- servem para


incrementar e decrementar uma variável. Assim,
a instrução   “i++” é uma forma mais curta de
escrever “i = i + 1”  (ou “i += 1”).

● O operador de divisão / de C faz divisão inteira


se ambos os lados do operador tiverem um tipo
int; isto é, qualquer resto da divisão é ignorado.
Assim, em C, a expressão “13   / 5”   é avaliada
como 2, enquanto “13 / 5.0” é 2.6: a primeira
tem valores inteiros em cada lado, enquanto a
segunda tem um número de ponto flutuante à
direita.
Observe que,   com as versões mais recentes
de Python (3.0 e posterior), o operador de barra
única sempre faz a divisão de ponto flutuante.

2.2. Tipos básicos

A lista de tipos básicos de C é bastante restrita.

int para um inteiro

char para um único caractere

float para um número de ponto flutuante de


precisão simples
double para um número de ponto flutuante de
precisão dupla

O tipo int é direto. Você também pode criar outros


tipos de inteiros, usando os tipos long e short. Um
variável long reserva pelo menos tantos bits quanto um
tipo int, enquanto uma variável short reserva menos
bits do que um int (ou o mesmo número). A linguagem
não garante o número de bits para cada um, mas a
maioria dos compiladores atuais usa 32 bits para um
int, o que permite números de até 2,15 × 109  . Isso é
suficiente para a maioria dos propósitos e, de todo
modo, muitos compiladores também usam 32 bits para
long. Assim, tipicamente as pessoas usam int em seus
programas.

O tipo char também é simples: representa um único


caractere, como um símbolo de letra ou pontuação.
Você pode representar um caractere individual em um
programa colocando o caractere entre aspas simples:
“digit0 = '0';” guardaria o caractere de dígito zero
na variável variável digit0 de tipo char.

Entre os dois tipos de ponto flutuante existentes,


float e double, a maioria dos programadores de hoje
utilizam quase que exclusivamente o tipo double. Esses
tipos são para números que podem ter valores decimais
neles, como 2,5, ou para números maiores que um
int pode conter, como 6,02 × 1023   . Os dois tipos
diferem no fato de que um float normalmente utiliza
apenas 32 bits de armazenamento, enquanto
double normalmente utiliza 64 bits. A técnica de
armazenamento de 32 bits permite um intervalo de
números mais estreito (de −3,4 × 1038 a 3,4 × 1038 ) e
— mais problemático numericamente — com cerca de 7
dígitos significativos. Assim, uma variável do tipo
float não pode guardar precisamente o número
281.421.906 (que foi a população dos EUA em 2000
segundo o censo), porque representar esse número
requer nove dígitos significativos; se atribuíssemos esse
número a uma variável float, obteríamos apenas uma
aproximação, como 281,421,920. Por outro lado, a
técnica de armazenamento de 64 bits permite uma
faixa mais ampla de números (−1,7 × 10308   a 1,7 ×
10308   ) e aproximadamente 15 dígitos significativos.
Isso é mais adequado para situações diversas e o gasto
extra de 32 bits de armazenamento raramente é
relevante, portanto utilizar double é quase sempre
preferível.

C não possui um tipo booleano para representar valores


verdadeiro/falso. Isso tem implicações importantes para
uma instrução como if, onde você precisa de um teste
para determinar se deve executar um grupo de
instruções. A abordagem utilizada em C é tratar o
inteiro 0 como falso e todos os outros valores inteiros
como verdadeiros. O seguinte seria um programa C
válido.

int main() {
    int i = 5;
    if (i) {
        printf("in if\n");
    } else {
        printf("in else\n");
    }
    return 0;
}

Este programa compilaria e imprimiria “in if” quando


executado, já que o valor da expressão i vale 5, o que é
diferente de 0   e, portanto, o teste da instrução if   é
bem-sucedida.

Operadores em C cujos resultados devem ser valores


booleanos (como ==, &&   e   ||), na verdade, calculam
valores int. Em particular, esses operadores devolvem
1   para representar verdadeiro   e 0   para representar
falso.

Esta peculiaridade — de que C considera todos os


números inteiros diferentes de zero como valor
verdadeiro — é geralmente considerada um erro.
Assim, a maioria dos programadores experientes
nunca   realizam operações aritméticas (soma,
subtração, etc.) com o resultado de expressões
condicionais (operações booleanas, comparações, etc.)

2.3. Chaves

Várias instruções, como a instrução if, incluem um


corpo que pode conter várias outras instruções.
Normalmente, o corpo é cercado por chaves ('{' e '}')
para indicar sua extensão. Mas quando o corpo contém
apenas uma instrução, as chaves são opcionais. Assim,
poderíamos encontrar o máximo de dois números sem
usar chaves, desde que o corpos tanto de if quanto de
else contenham apenas uma única instrução, cada.
if (first > second)
    max = first;
else
    max = second;

(Também poderíamos incluir chaves em apenas um dos


dois corpos, contanto que esse corpo contenha apenas
uma instrução.)

Os programadores C usam isso com bastante


frequência quando querem que um de vários teste de
if   seja executado. Um exemplo disso foi dado no
código da fórmula quadrática acima. Podemos calcular
o número de soluções da seguinte forma:

disc = b * b - 4 * a * c;


if (disc < 0) {
    num_sol = 0;
} else {
    if (disc == 0) {
num_sol = 1;
    } else {
        num_sol = 2;
    }
}

Observe que a cláusula else aqui contém apenas uma


instrução (uma instrução if…else), então podemos
omitir as chaves ao redor dela. Podemos então
escrever:

disc = b * b - 4 * a * c;


if (disc < 0) {
    num_sol = 0;
} else
    if (disc == 0) {
        num_sol = 1;
    } else {
        num_sol = 2;
    }

Mas essa situação surge com freqüência suficiente para


que os programadores C sigam uma regra especial para
recuar nesse caso — uma regra que permite que todos
os casos sejam escritos no mesmo nível de recuo.
disc = b * b - 4 * a * c;
if (disc < 0) {
num_sol = 0;
} else if (disc == 0) {
    num_sol = 1;
} else {
    num_sol = 2;
}

Como isso é viável usando as regras de chaveamento de


C, C não inclui um paralelo da cláusula elif que você
encontra em   Python. Você pode juntar quantas
combinações else if desejar.

Além desta situação em particular, eu recomendo que


você sempre inclua as chaves, mesmo que só haja uma
instrução. À medida em que você continua trabalhando
em um programa, geralmente descobre que deseja
incluir instruções adicionais no corpo de um if e ter as
chaves lá já lhe poupa o trabalho de adicioná-las mais
tarde. E torna mais fácil manter o controle das chaves,
já que cada nível de indentação requer uma chave
direita de fechamento.

2.4. Instruções

Vimos quatro tipos de instruções, três das quais


correspondem de perto com as de Python.

1. int x;

Nós já discutimos declarações de variáveis na


Seção 1.2 . Elas não têm paralelo em Python.

2. x = y + z; ou printf("%d", x);

Você pode ter uma expressão como uma


instrução. Tecnicamente, a expressão poderia ser
“x + 3”, mas tal instrução não serve para nada:
pedimos ao computador para adicionar x e 3, mas
não pedimos que nada aconteça com esse
resultado. Quase sempre, as expressões têm uma
das duas formas acima: uma forma é um operador
que altera o valor de uma variável, como o
operador de atribuição (“x = 3;”), o operador de
atribuição de adição +=   ou o operador de
incremento ++. A outra forma de expressão que
você vê como uma instrução é uma chamada de
função, como uma instrução que simplesmente
chama a função printf().

3. if (x < 0) { printf("negative"); }

Você pode ter uma instrução if, que funciona de


maneira muito semelhante à
instrução if de Python. A única grande diferença
é a sintaxe: em C, a condição if de uma instrução
deve estar entre parênteses, não há dois pontos
após a condição e o corpo tem um par de chaves
que a encerra.

Como já vimos, C não possui uma cláusula


elif como em Python; em vez disso, os
programadores C usam a regra de chave opcional
e escrevem “else if”.

4. return 0;

Você pode ter uma instrução return para sair de


uma função com um determinado valor de
retorno. Ou, para uma função sem valor de
retorno (a que tem tipo de retorno void), você
escreveria simplesmente “return;”.

Existem mais três tipos de instruções que se


correlacionam de perto com os equivalentes de Python.

5. while (i >= 0) { i--; }

A instrução while funciona de maneira idêntica à


de Python, embora a sintaxe seja diferente da
mesma maneira que a sintaxe if é diferente.

while (i >= 0) {
    printf("%d\n", i);
    i--;
}

Novamente, a expressão de teste requer um


par   de parênteses em torno dela, não há dois
pontos e usamos chaves para cercar o corpo do
laço.
6. break;

Como em Python, a instrução break sai


imediatamente do laço mais interno em que ela é
encontrada. Claro, a instrução   tem um ponto e
vírgula a seguir.

7. continue;

Também como em   Python, a instrução


continue pula para o final do laço   mais interno
em que é encontrado e testa se deve repetir o
laço novamente. Ele tem um ponto-e-vírgula
também.

E existem dois tipos de instruções que não possuem um


bom paralelo em Python.

8. for (i = 0; i < 10; i++) {…

Embora Python também tenha uma instrução for,


seu propósito e sua ; sintaxe têm pouca
semelhança com a instrução for de C. Em C, a
palavra-chave for é seguida por um par de
parênteses que contém três partes separadas por
ponto e vírgula.

for (inicialização; teste; atualização)

A intenção do laço for de C é permitir passar


uma variável por uma série de números, como
contar de 0 a 9. A parte antes do primeiro ponto e
vírgula (inicialização) é executada assim que a
instrução for   é atingida; é para inicializar a
variável que contará. A parte entre os dois pontos
e vírgulas (teste) é avaliada antes de cada
iteração para determinar se a iteração deve ser
repetida. E a parte seguinte ao último ponto e
vírgula (atualização) é avaliada no final de cada
iteração para atualizar a variável de contagem
para a iteração a seguir.

Na prática, os laços for são usados com mais


freqüência para contar n   iterações. O idioma
padrão para isso é o seguinte.
for (i = 0; i < n; i++) {
    corpo
}

Aqui temos uma variável de contador i cujo valor


começa em 0. Com cada iteração, testamos se
i alcança n   ou não; e, se não, então nós
executamos o corpo for da instrução   e então
executamos a atualização  i++ de forma que i vá
para o inteiro seguinte. O resultado é que o corpo
é executado para cada valor de i, começando em
0 até n - 1.

Mas você também pode usar um laço for para


outros propósitos. No exemplo a seguir, exibimos
as potências de 2 até 512. Observe como a parte
de atualização da instrução for  foi alterada para
“p *= 2”.

for (p = 1; p <= 512; p *= 2) {


    printf("%d\n", p);
}

9. switch (grade) { case 'A':…

A instrução switch não tem nenhum equivalente


em Python, mas é essencialmente equivalente a
uma forma particular de uma instrução da forma
if... elif... elif... else onde cada um dos
testes são feitos para diferentes valores de uma
mesma variável.

Uma instrução switch é útil quando você tem


vários blocos de código possíveis, um dos quais
deve ser executado com base no valor de uma
expressão específica. Aqui está um exemplo
clássico de instrução switch:

switch (letter_grade) {
case 'A':
    gpa += 4;
credits += 1;
    break;
case 'B':
    gpa += 3;
    credits += 1;
break;
case 'C':
    gpa += 2;
credits += 1;
    break;
case 'D':
    gpa += 1;
    credits += 1;
break;
case 'W':
    break;
default:
    credits += 1;
}

Dentro dos parênteses após a palavra-chave


switch, temos uma expressão cujo valor deve ser
um caractere ou inteiro. O computador avalia
essa expressão e desce para uma das palavras-
chaves case dependente do valor dessa
expressão. Se o valor é o caractere A, então o
primeiro bloco é executado (gpa   += 4;
credits += 1;); se for B , então o segundo bloco
é executado; etc. Se o valor não for nenhum dos
caracteres (como um F), então o bloco após a
palavra-chave default é executado.

A instrução break no final de cada bloco é um


detalhe crucial: se a instrução break for omitida,
o computador continuará no seguinte bloco. Em
nosso exemplo acima, se omitimos todas as
instruções break, uma nota A   levaria o
computador a executar não apenas o caso A, mas
também o B, C, D, W e caso default. O resultado
seria que gpa aumentaria em 4 + 3 + 2 + 1 =
10, enquanto credits aumentaria em 5.
Ocasionalmente, você realmente deseja que o
computador continue para o próximo caso
(chamado de “fall-through”) e assim você omite
uma instrução break; mas na prática você quase
sempre quer uma instrução break no final de
cada caso.

Há uma exceção importante em que fall-through é


bastante comum: às vezes, você deseja que o
mesmo código seja aplicado a dois valores
diferentes. Por exemplo, se quiséssemos que nada
acontecesse se a nota fosse P   ou W   , então
poderíamos incluir “case 'P':” um pouco antes
de “case 'W':”, sem nenhum código
intermediário.

2.5. Vetores

Python suporta muitos tipos que combinam os tipos


atômicos básicos em um grupo: tuplas, listas,
cadeias de caracteres, dicionários, conjuntos.

O suporte de C é muito mais rudimentar: O único tipo


composto é o vetor, que é semelhante à lista de Python,
exceto que um vetor em C não pode crescer ou
encolher —   seu tamanho é fixado   no momento da
criação. Você pode declarar e acessar um vetor da
seguinte maneira.

double pops[50];
pops[0] = 897934;
pops[1] = pops[0] + 11804445;

Neste exemplo, criamos um vetor   contendo 50


variáveis do tipo double. As variáveis são indexados de
0 a 49.

C não tem suporte para acessar o tamanho de um vetor


depois de criado; ou seja, não há nada análogo ao
len(pops) de Python ou ao pops.length de Java.

Um ponto importante em relação aos vetores: o que


acontece se você acessar um índice de vetor   fora do
vetor, como acessar pops[50] ou pops[-100]? Com
Python ou Java, isso terminará o programa com uma
mensagem amigável apontando para a linha com a
falha e dizendo que o programa foi além dos limites do
vetor. C não é tão amigável. Quando você acessa além
dos limites de um vetor, ele o faz cegamente.

Isso pode levar a um comportamento peculiar. Por


exemplo, considere o programa a seguir.

int main() {
    int i;
    int vals[5];

    for (i = 0; i <= 5; i++) {


        vals[i] = 0;
    }
    printf("%d\n", i);
    return 0;
}

Alguns sistemas (incluindo uma instalação do Linux que


encontrei) guardariam i na memória logo após
o vetor vals; assim, quando i atinge 5 e o computador
executa “vals[i] = 0”, ele de fato redefine a memória
correspondente a i   para 0. Como resultado, o
laço for foi reinicializado e o programa passa pelo
laço   novamente, e novamente, repetidamente. O
programa nunca alcança a chamada de função printf e
o programa nunca termina.

Em programas mais complicados, a falta de checagem


de limites de vetor   pode levar a bugs muito difíceis,
onde o valor de uma variável muda misteriosamente em
centenas de funções e você como programador deve
determinar onde um índice do vetor foi acessado fora
dos limites. Este é o tipo de bug que leva muito tempo
para descobrir e reparar.

É por isso que você deve considerar as mensagens de


erro fornecidas por Python (ou Java) como
extraordinariamente amigáveis: não apenas informa a
causa de um problema, mas também informa
exatamente qual linha do programa estava com defeito.
Isso economiza muito tempo de depuração.

De vez em quando, você verá uma falha no programa


em C, com uma mensagem como “falha de
segmentação” ou “erro de barramento”. Não será útil
incluir qualquer indicação de qual parte do programa é
a culpa: tudo que você consegue são aquelas essas duas
palavras. Esses erros geralmente significam que o
programa tentou acessar um local de memória inválido.
Isso pode indicar uma tentativa de acessar um índice
de vetor inválido, mas normalmente o índice precisa
estar bem fora dos limites para que isso ocorra. (Em
geral, isso indica uma tentativa de fazer referência a
um ponteiro não inicializado ou a um ponteiro NULL,
que discutiremos mais adiante.)

2.6. Comentários

No projeto original de C, todos os comentários


começam com uma barra seguida por um asterisco
(“/*”) e terminam com um asterisco seguido por uma
barra (“*/”). O comentário pode abranger várias linhas.

/* gcd - returns the greatest common


 * divisor of its two parameters */
int gcd(int a, int b) {

(O asterisco na segunda linha é ignorado pelo


compilador. No entanto, a maioria dos programadores o
incluiria, porque parece mais bonito e também porque
indica a um leitor humano que o comentário está sendo
continuado a partir da linha anterior.)

Embora este comentário em múltiplas linhas tenha sido


o único comentário incluído originalmente em C, o C++
introduziu um comentário em uma única linha que
provou ser tão útil que a maioria dos compiladores C
atuais também o suportam. Começa com dois
caracteres de barra (“//”) e vai para o final da linha.

int gcd(int a, int b) {


  if (b == 0) {
    return a;
  } else {
    // recurse if b != 0
return gcd(b, a % b);
  }
}

3. Bibliotecas

Tendo discutido as funções internas, agora nos


voltamos para discutir questões relacionadas a funções
e separar um programa em vários arquivos.

3.1. Protótipos de função

Em C, uma função deve ser declarada acima do local


onde você a usa. No programa C da Figura 2, definimos
a função gcd() primeiro e depois a função main(). Isso
é significativo: se trocássemos as funções gcd()   e
main(), o compilador iria reclamar em main() que a
função gcd() não está declarada. Isso é porque C
presume que um compilador lê um programa de cima
para baixo: no momento em que chega a main(), ele
não foi informado sobre uma função gcd() e, portanto,
acredita que não existe tal função.

Isso gera um problema, especialmente em programas


maiores que abrangem vários arquivos, em que as
funções em um arquivo precisarão chamar funções em
outro. Para contornar esse problema, C fornece a noção
de um protótipo de função, onde escrevemos o
cabeçalho da função mas omitimos a definição do
corpo.

Por exemplo, digamos que queremos quebrar nosso


programa C em dois arquivos: o   primeiro arquivo,
math.c, conterá a função gcd() e o segundo arquivo,
main.c, conterá a função main(). O problema com isso
é que, quando compilarmos main.c, o compilador não
saberá sobre a função gcd() que está tentando chamar.

Uma solução é incluir um protótipo de função em


main.c.

int gcd(int a, int b);

int main() {
printf("GCD: %d\n",
        gcd(24, 40));
    return 0;
}

A linha “int gcd…” é o protótipo da função. Você pode


ver que ela começa da mesma forma que uma definição
de função começa, mas nós simplesmente colocamos
um ponto-e-vírgula onde o corpo da função
normalmente estaria. Ao fazer isso, estamos declarando
que a função será eventualmente definida, mas ainda
não a definimos. O compilador aceita isso e
obedientemente compila o programa sem reclamações.

3.2. Arquivos de cabeçalho

Programas maiores que abrangem vários arquivos


freqüentemente contêm muitas funções que são usadas
muitas vezes em muitos arquivos diferentes. Seria
doloroso repetir cada protótipo de função em todos os
arquivos que usam a função. Então, criamos um arquivo
— chamado de arquivo de cabeçalho — que contém
cada protótipo escrito apenas uma vez (e possivelmente
algumas informações compartilhadas adicionais) e
então podemos nos referir a esse arquivo de cabeçalho
em cada código-fonte que deseja os protótipos. O
arquivo de protótipos é chamado de arquivo de
cabeçalho, pois contém as “cabeças” de várias funções.
Convencionalmente, os arquivos de cabeçalho usam o
prefixo .h, ao invés do prefixo .c   usado para os
códigos-fontes C.

Por exemplo, poderíamos colocar o protótipo para a


nossa função gcd()   em um arquivo de cabeçalho
chamado math.h .

int gcd(int a, int b);

Podemos usar um tipo especial de linha começando


com #include para   incorporar esse arquivo de
cabeçalho no topo da main.c.

#include <stdio.h>
#include "math.h"

int main() {
printf("GCD: %d\n",
        gcd(24, 40));
    return 0;
}

Este exemplo em particular não é muito convincente,


mas imagine uma biblioteca que consiste em dezenas
de funções, que são usadas em dezenas de arquivos: de
repente, a economia de tempo de ter apenas um único
protótipo para cada função em um arquivo de
cabeçalho começa a fazer sentido.

A linha #include é um exemplo de uma diretiva para o


pré-processador   de C,   para o qual o compilador C
envia cada programa antes de compilá-lo. Um
programa pode conter comandos (diretivas)
informando ao pré-processador para manipular o texto
do programa que o compilador realmente processa. A
diretiva #include informa ao pré-processador para
substituir a linha #include pelo conteúdo do arquivo
especificado.
Você deve ter notado que colocamos stdio.h   entre
colchetes angulares, enquanto math.h está entre aspas
duplas. Os colchetes angulares são para arquivos de
cabeçalho padrão — arquivos especificados junto com a
especificação da linguagem C. As aspas são para
arquivos de cabeçalho personalizados que podem ser
encontrados no mesmo diretório dos códigos-fontes.

3.3. Constantes

Outra diretiva de pré-processador particularmente útil


é a diretiva #define. Ela diz ao pré-processador para
substituir todas as ocorrências futuras de algum
palavra por outra.

#define PI 3.14159

Neste fragmento, dizemos ao pré-processador que, para


o resto do programa, ele deveria substituir cada
ocorrência de “PI” por “ 3.14159”. Suponha que mais
tarde no programa exista a seguinte linha:

printf("area: %f\n", PI * r * r);

Vendo isso, o pré-processador iria traduzi-lo no


seguinte texto para o compilador C processar:

printf("area: %f\n", 3.14159 * r * r);

Essa substituição acontece nos bastidores, para que o


programador não veja a substituição.

Estes são os princípios básicos de escrever programas


em C, dando-lhe o suficiente para poder escrever
programas razoavelmente úteis. Mas, para ser um
programador proficiente em C, você precisaria saber
sobre ponteiros —   um tópico que adiaremos a   outro
momento.

Você também pode gostar