Você está na página 1de 106

FACULDADE ÚNICA

DE IPATINGA

1
SUMÁRIO

UNIDADE OPERADORES E EXPRESSÕES .................................................................... 7

01
1.1 DEFINIÇÃO DE OPERADOR .................................................................................... 7
1.2 CLASSES E TIPOS DE OPERADORES ........................................................................ 8
1.3 DEFINIÇÃO E RESOLUÇÃO DE EXPRESSÕES ........................................................ 16
1.4 TIPOS DE EXPRESSÕES........................................................................................... 20
FIXANDO O CONTEÚDO ...................................................................................... 24

UNIDADE TIPOS DE DADOS ESPECIAIS ................................................................... 29

02
2.1 ESTRUTURAS ........................................................................................................... 29
2.2 ENUMERAÇÕES ..................................................................................................... 33
2.3 PONTEIROS ............................................................................................................ 37
FIXANDO O CONTEÚDO ...................................................................................... 44

UNIDADE ESTRUTURAS DE SELEÇÃO........................................................................ 50

03
3.1 SELEÇÃO SIMPLES ................................................................................................. 50
3.2 SELEÇÃO COMPOSTA .......................................................................................... 51
3.3 SELEÇÃO MÚLTIPLA............................................................................................... 56
FIXANDO O CONTEÚDO ...................................................................................... 59

UNIDADE ESTRUTURAS DE REPETIÇÃO..................................................................... 65

04
4.1 REPETIÇÃO COM TESTE NO INÍCIO...................................................................... 65
4.2 REPETIÇÃO COM TESTE NO FINAL ....................................................................... 67
4.3 REPETIÇÃO COM VARIÁVEL DE CONTROLE........................................................ 69
FIXANDO O CONTEÚDO ...................................................................................... 73

UNIDADE MODULARIZAÇÃO .................................................................................. 78

05
5.1 MODULARIZAÇÃO DE CÓDIGO .......................................................................... 78
5.2 FUNÇÕES............................................................................................................... 79
5.3 TIPOS DE PASSAGEM DE PARÂMETRO................................................................. 84
FIXANDO O CONTEÚDO ...................................................................................... 88

UNIDADE MANIPULAÇÃO DE ARQUIVOS............................................................... 94

06
6.1 ARMANEZAMENTO DE DADOS ............................................................................ 94
6.2 ABERTURA E FECHAMENTO DE ARQUIVOS.......................................................... 95
6.3 LEITURA E ESCRITA EM ARQUIVOS ....................................................................... 99
FIXANDO O CONTEÚDO .................................................................................... 104

RESPOSTAS DO FIXANDO O CONTEÚDO ............................................. 109

REFERÊNCIAS ......................................................................................... 110

5
CONFIRA NO LIVRO

A Unidade I apresenta os conceitos fundamentais que envolvem o


uso de operadores e também a avaliação de expressões lógicas,
aritméticas e relacionais em linguagens de programação.

A Unidade II descreve tipos especiais de dados que são utilizados


para facilitar a manipulação de informações. Além disso, essa
unidade também apresenta o ponteiro, um dos conceitos mais
fundamentais da área de programação de computadores.

A Unidade III apresenta as estruturas de seleção que são estruturas


de controle utilizadas para alterar o fluxo de execução de um
programa mediante uma tomada de decisão.

A Unidade IV descreve um outro tipo de estrutura de controle: as


estruturas de repetição. Essas estruturas são parte fundamental da
lógica de programação e são usadas quando um programa
precisa repetir uma dada tarefa até que uma certa condição seja
satisfeita.

A Unidade V trata da modularização de código, destacando as


principais vantagens em se adotar essa prática, além de mostrar
como utilizar funções e diferentes tipos de passagem de
parâmetros de entrada.

A Unidade VI mostra como as linguagens de programação podem


ser usadas para realizar a leitura e gravação de dados em
arquivos.

6
OPERADORES E EXPRESSÕES UNIDADE

1.1 DEFINIÇÃO DE OPERADOR


01
As linguagens de programação fornecem um conjunto básico de recursos
que permitem ao programador escrever um código-fonte funcional, isto é, um
código que possa ser usado para resolver os mais variados tipos de problemas.
Dentre os recursos disponíveis nas linguagens de programação, os operadores se
destacam como sendo um dos mais importantes e essenciais elementos para
construir qualquer programa.
De fato, os operadores são tão imprescindíveis em uma linguagem de
programação que até os códigos mais simples possivelmente precisam utilizar
algum operador para executar a função desejada. Por exemplo, para executar
uma tarefa trivial como calcular a soma de dois números inteiros, o código escrito
na linguagem de programação C, ilustrado na Figura 1, necessita utilizar o operador
aritmético de adição (“+”) e o operador de atribuição (“=”) para realizar essa
tarefa.

Figura 1: Código em C para calcular a soma de 2 números

int main()
{
int a, b, c;
a = 10;
b = 20;
c = a + b;
}
Fonte: Elaborado pelo Autor (2021)

Quanto à definição de operador, pode-se dizer que um operador é utilizado


para executar uma determinada ação a partir de um conjunto de elementos.
Portanto, para implementar qualquer tarefa que envolva cálculos, atribuições ou
comparações de valores é muito provável que o programador precise empregar
algum operador disponível na linguagem para alcançar o objetivo desejado.
O conhecimento sobre os operadores disponíveis em uma linguagem é tão
importante que qualquer livro ou manual da linguagem de programação, de forma

7
geral, possui alguma seção dedicada a apresentar e detalhar os operadores
disponíveis naquela linguagem.
É interessante dizer que os operadores das linguagens de programação são
muito semelhantes aos operadores usados na matemática. Sendo assim, uma
operação sempre envolve um operador e um conjunto de operandos, como
ilustrado na Figura 2.

Figura 2: Operação, operador e operandos

Operador
Operando Operando

a+ b
Operação
Fonte: Elaborado pelo Autor (2021)

No exemplo descrito na Figura 1, a operação sendo executada é a soma


entre dois números, isto é, a operação matemática de adição. O operador utilizado
para realizar essa operação é o operador aritmético de adição (“+”) e os
operandos dessa operação são os elementos a e b.
À primeira vista, pode parecer trivial, ou até mesmo desnecessário, abordar
essa separação entre os elementos que compõem uma operação. Entretanto, esse
conhecimento é especialmente útil para compreender e avaliar corretamente
expressões lógicas e aritméticas que estão comumente presentes nos códigos-
fonte.

1.2 CLASSES E TIPOS DE OPERADORES

Ao pensar nos operadores que são utilizados para realizar as quatro


operações matemáticas básicas (adição, multiplicação, subtração e divisão),
certamente fica difícil imaginar que existam operadores que requeiram um número
diferente de 2 operandos. Contudo, há sim operadores que requerem 1 ou 3
operandos, como é o caso do operador de negação lógica que requer apenas 1
operando para executar a sua operação.

8
Como o número de operandos pode variar, os operadores podem ser
classificados de acordo com essa quantidade de operandos necessária para
executar a sua operação. A Tabela 1 lista as classes de operadores de acordo com
a quantidade de operandos requeridos.

Tabela 1: Classes de operadores


Classe de operador Quantidade de operandos
Unário 1
Binário 2
Ternário 3
Fonte: Elaborado pelo Autor (2021)

Por mais incomum que possa parecer, há linguagens de programação que


suportam operadores ternários, ou seja, que realizam suas operações a partir de 3
operandos. Esses operadores geralmente são usados para realizar operações
especiais que tem a finalidade de facilitar a escrita do código. A próxima seção
apresenta alguns operadores dessa natureza.
Além dos operadores estarem categorizados em relação ao número de
operandos, os operadores também podem ser agrupados pelo tipo de operação
que executam. Os tipos mais comuns de operadores encontrados em grande parte
das linguagens estão descritos a seguir:
● Aritméticos: são usados para realizar operações básicas da matemática
como adição, subtração, multiplicação, divisão, exponenciação e resto
inteiro da divisão (também chamado de módulo). Nesse grupo de
operadores também estão inseridos aqueles que permitem realizar o
incremento ou decremento de um dado valor, isto é, somar ou subtrair o
valor de uma unidade, respectivamente.

● Lógicos: representam os conectivos de conjunção (“e lógico”), disjunção


(“ou lógico”) e negação (“não lógico”) que são empregados para construir
e avaliar expressões lógicas booleanas. Há linguagens que também
disponibilizam um operador para o “ou exclusivo”.

● Relacionais: são usados para comparar elementos que podem ser desde
números inteiros e reais, até caracteres ou tipos de dados especiais. As
opções de comparação incluem igualdade, diferença, maior, maior ou
igual, menor e menor ou igual.

9
● Atribuição: constituem o operador simples de atribuição de valor à variável e
algumas variações interessantes como as atribuições seguidas de alguma
operação aritmética.

● Bit a bit: são um conjunto de operadores que, ao realizar suas operações,


avaliam os operandos considerando a sua representação binária. Além de
operações voltadas para comparação, há também operadores a nível de
bit que são usados para alterar a cadeia de bits dos operandos.
Complemento, deslocamento a esquerda e a direita, são exemplos de
operações dessa natureza.

Há também um conjunto de operadores que não se encaixam


perfeitamente em nenhuma dessas categorias. São operadores especiais que
permitem executar tarefas diversas como a identificação e manipulação do
endereço de memória ocupado por uma variável, por exemplo. Ao contrário de
operadores lógicos e aritméticos, que comumente estão disponíveis na grande
maioria das linguagens, os operadores especiais podem variar muito de uma
linguagem de programação para outra, podendo até mesmo não existir em uma
dada linguagem de programação.
Outro ponto a ser ressaltado é o tipo de dado resultante de uma operação.
Para operações aritméticas, o resultado é expresso em formato de um número
inteiro ou real, como era de se esperar. Mas, para operações lógicas e relacionais,
ou que envolvam ambos os tipos, o resultado é expresso como verdadeiro ou falso.
A representação de valores lógicos é muito particular em cada linguagem,
com algumas utilizando as palavras reservadas false e true , e outras utilizando os
números 0 e 1 para representar, respectivamente, falso e verdadeiro. Na linguagem
C, por exemplo, o valor para falso é representado com o número 0 e, para
verdadeiro, é considerado qualquer valor que seja diferente de 0.

10
Para se ter uma ideia mais prática da quantidade, tipos e classes de
operadores que são suportados por uma linguagem de programação real, o
Quadro 2 lista todos os 36 operadores disponíveis na linguagem C.

Quadro 1: Operadores suportados pela linguagem C


Operador Operação Tipo Classe
+ Adição Aritmético Binário
- Subtração Aritmético Binário
* Multiplicação Aritmético Binário
/ Divisão Aritmético Binário
% Módulo Aritmético Binário
++ Incremento Aritmético Unário
– Decremento Aritmético Unário
&& Conectivo de conjunção (“E lógico”) Lógico Binário
|| Conectivo de disjunção (“OU lógico”) Lógico Binário
! Conectivo de negação (“NÃO lógico”) Lógico Unário
== Igualdade Relacional Binário
!= Diferença Relacional Binário
> Maior Relacional Binário
>= Maior ou igual Relacional Binário
< Menor Relacional Binário
<= Menor ou igual Relacional Binário
= Atribuição simples Atribuição Binário
+= Atribuição com adição Atribuição Binário
-= Atribuição com subtração Atribuição Binário
*= Atribuição com multiplicação Atribuição Binário
/= Atribuição com divisão Atribuição Binário
%= Atribuição com resto da divisão Atribuição Binário
<<= Atribuição com deslocamento à Atribuição Binário
esquerda
>>= Atribuição com deslocamento à Atribuição Binário
direita
&= Atribuição com “E” binário Atribuição Binário
|= Atribuição com “OU” binário Atribuição Binário
^= Atribuição com “OU EXCLUSIVO” Atribuição Binário
binário
& E lógico bit a bit Bit a bit Binário
| OU lógico bit a bit Bit a bit Binário
^ OU EXCLUSIVO lógico bit a bit Bit a bit Binário
~ Negação ou complemento de 1 Bit a bit Unário
<< Deslocamento binário à esquerda Bit a bit Binário
>> Deslocamento binário à direita Bit a bit Binário
* Operação com ponteiros Especial Unário
& Endereço de memória Especial Unário
?: Seleção simples Especial Ternário
Fonte: Elaborado pelo Autor (2021)

11
Avaliando essa lista de operadores, pode-se identificar algumas questões
interessantes. Em primeiro lugar, a linguagem C oferece operadores de todas as 3
classes, isto é, operadores unários e binários, e também um operador ternário que é
usado para escrever uma estrutura de seleção simples. É preciso ficar atento à
classe do operador para que se possa empregá-lo da forma correta.
Além disso, não é difícil notar que muitos operadores têm a mera finalidade
de simplificar a escrita do código ao oferecer uma espécie de atalho para se
executar uma determinada tarefa. Esse é o caso do operador de atribuição com
adição (“+=”) que atualiza uma variável com a soma entre seu valor atual e o
operando utilizado na operação, como exemplificado no código ilustrado na Figura
3.

Figura 1: Utilização de operadores abreviados em C

int main()
{
int a, b;
a = 10;
b = 10;
a = a + 20;
b += 20;
}
Fonte: Elaborado pelo Autor (2021)

No código ilustrado na Figura 3, as linhas destacadas em verde e vermelho


executam a mesmíssima ação, embora a linha destacada em verde esteja
utilizando o operador de atribuição com adição, enquanto que a linha destacada
em vermelho está usando os operadores de atribuição e adição. Mas, apesar de
produzir o mesmo efeito, vale ressaltar que os programadores preferem utilizar essas

12
versões abreviadas dos operadores a fim de tornar o código mais limpo, legível e
elegante.

Por último, as informações contidas no Quadro 1 mostram que, apesar do


grande número de operadores disponíveis, há apenas um pequeno grupo de
operadores que serão, de fato, utilizados pela ampla maioria dos programadores. E
não é pra menos. Há operadores que são tão específicos que são usados apenas
em situações muito particulares.
Entretanto, isso não significa, sobremaneira, que esses operadores não
possuam alguma utilidade prática. Por exemplo, os operadores a nível de bit são
essenciais quando se está implementando sistemas embarcados para serem
executados em microcontroladores. Por se tratar de dispositivos que dispõem de
recursos limitados, os sistemas embarcados precisam ser construídos de tal forma
que os recursos de hardware sejam aproveitados da melhor maneira possível. E é
justamente por isso que os programadores utilizam operadores bit a bit para realizar
tarefas que estejam no nível de hardware do dispositivo.

Por último, é interessante observar que a linguagem C possui dois operadores


que servem a propósitos distintos, embora sejam representados pelo mesmo

13
símbolo. O símbolo “*” (asterisco) é utilizado para representar, ao mesmo tempo, a
operação aritmética de multiplicação e a operação especial de manipulação de
ponteiros. A diferença entre uma situação e outra é que o operador aritmético é
binário e o especial, unário.
Se essa situação pode ser observada dentro de uma mesma linguagem de
programação, não é de se espantar que linguagens diferentes possam usar
símbolos completamente distintos para representar uma mesma operação. De fato,
o Quadro 2 apresenta os símbolos usados para representar algumas operações em
8 linguagens diferentes, evidenciando o quão diferente pode ser a simbologia
utilizada pelas linguagens.
Por exemplo, o operador de atribuição simples é representado pelo símbolo
“=” nas linguagens C, Python, Java, Ruby e Lua, enquanto que a linguagem R
adota um símbolo completamente diferente das demais linguagens para
representar essa operação (“<-”). Nesse mesmo sentido, a linguagem Lua utiliza o
símbolo “~=” para representar a operação de diferença que, como pode ser visto
no Quadro 2, não é o mesmo símbolo usado para representar essa operação em
nenhuma das linguagens listadas.

Quadro 2: Simbologia utilizada em diferentes linguagens para um mesmo conjunto de


operações
Operação C Pascal Python Java Go Ruby Lua R
Atribuição = := = = := = = <-
simples
Igualdade == = == == == == == ==
Diferença != <> != != != != ~= !=
Potência Não Não ** Não Não ** ^ ^
possui possui possui possui
Módulo % mod % % % % % %%
Fonte: Elaborado pelo Autor (2021)

Outra questão que pode ser observada no Quadro 2 é que nem todas as
operações suportadas por uma linguagem estão disponíveis em uma outra. Das
linguagens listadas no quadro, apenas Python, Ruby, Lua e R possuem um operador
aritmético para cálculo da potência. Sendo assim, para calcular a potência nas
outras linguagens, o programador precisaria recorrer a alguma função provida por
uma biblioteca específica, como pode ser visto no exemplo de código em C
ilustrado na Figura 4.

14
Figura 4: Cálculo de potência em C

#include <math.h>

int main()
{
int numero;
numero = pow(2,4);
printf(“%d\n”, numero);
}
Fonte: Elaborado pelo Autor (2021)

Uma das formas de calcular a potência em C é fazer uso da função


pow()que é fornecida pela biblioteca math.h. Naturalmente, o programador
poderia criar sua própria função para executar essa tarefa ou então usar uma outra
biblioteca que tenha preferência. Esse raciocínio é o mesmo para qualquer outra
operação que não esteja disponível em uma linguagem de programação no
formato de um operador.
Antes de concluir essa seção, vale a pena mencionar uma funcionalidade
relacionada à operadores que, embora não esteja no escopo desse material, é
interessante de ser conhecida. Trata-se da sobrecarga de operadores que é
suportada por linguagens como C++ e Python. Sucintamente, essa funcionalidade
permite que alguns operadores sejam reprogramados de tal forma que exerçam
funções específicas de acordo com o tipo de operando que estão tratando.
Em geral, esse recurso é utilizado em linguagens orientadas a objeto para
alterar o comportamento dos operadores ao tratar desses elementos. Assim, pode-
se, por exemplo, fazer com que uma operação de “adição” entre objetos resulte
em um terceiro objeto contendo as características dos outros dois que estão
envolvidos na operação. Algo que funcionaria como uma concatenação desses
objetos. Portanto, ao invés de escrever um método ou função para realizar uma
dada operação, pode-se realizar uma sobrecarga de operadores para que um
dado operador seja utilizado para executar tal ação.
Esse é um assunto avançado e sua apresentação neste material tem a
intenção de mostrar o quão flexível pode ser uma linguagem de programação no
contexto dos operadores.

15
1.3 DEFINIÇÃO E RESOLUÇÃO DE EXPRESSÕES

Em linguagens de programação, uma expressão é um conjunto de uma ou


mais operações que envolvam números, constantes ou variáveis. Portanto, todos os
itens listados na Figura 5 podem ser considerados como expressões.

Figura 5: Exemplos de expressões


1 + 5
+ +
3 ∗ 7 + 5 −2
( + ) − ( / )
Fonte: Elaborado pelo Autor (2021)

Para poder avaliar corretamente uma expressão, é fundamental que se


identifique precisamente quais são as operações que a compõem. Por exemplo, a
expressão ilustrada na Figura 6 é composta por 2 operações (soma e subtração) e 3
operandos.

16
Figura 6: Operações contidas em uma expressão

Operação Operação
1 2

1+2-1
Fonte: Elaborado pelo Autor (2021)

No caso desse exemplo, é necessário resolver primeiro uma das duas


operações para que o resultado dessa primeira operação seja utilizado como o
operando da operação remanescente, como pode ser visto na Figura 7.

Figura 7: Resolução de expressão

Fonte: Elaborado pelo Autor (2021)

No exemplo anterior, a operação de soma foi resolvida primeiro do que a


operação de subtração. Em uma expressão, a definição de qual operação deve
ser resolvida antes de uma outra é chamada de ordem de precedência de
operadores. Dentre as quatro operações aritméticas básicas, a ordem de
precedência diz que as operações de divisão e multiplicação devem ser resolvidas
antes de qualquer operação que envolva adição ou subtração. Para exemplificar,
a Figura 8 apresenta a resolução, por etapas, de algumas expressões em que a
ordem de precedência foi respeitada.

17
Figura 8: Resolução de expressões observando a ordem de precedência dos operadores

( ) 1 + 5 + 3 ∗ 2 = 1 + 5 + 6 = 1 + 11 = 12
( )8/4 + 1 − 2 = 2 + 1−2 = 3 − 2 = 1
( ) 5 ∗ 3 + 4 / 2 = 15 + 4 / 2 = 15 + 2 = 17
Fonte: Elaborado pelo Autor (2021)

Pode-se notar que, se a ordem de precedência não fosse considerada, os


resultados seriam completamente diferentes. Na expressão (a), se as operações
fossem resolvidas na ordem em que aparecem (da esquerda para direita), o
resultado final seria 18 e não 12, como apontado na figura.
A ordem de precedência, na aritmética, considera não apenas as quatro
operações básicas, mas também a exponenciação e a utilização de separadores
como parênteses, colchetes e chaves, de acordo com a ordem indicada no
Quadro 3.

Quadro 3: Ordem de precedência de operadores na matemática

Ordem Operação ou elemento

1 Parêntesis

2 Exponenciação

3 Multiplicação e divisão (da esquerda para a direita)

4 Soma e subtração (da esquerda para a direita)


Fonte: Elaborado pelo Autor (2021)

Sendo assim, as operações que estão delimitadas entre parêntesis são as que
possuem a maior prioridade de resolução em uma expressão, seguidas pelas
operações de exponenciação. Já as operações de multiplicação e divisão estão
no mesmo patamar de prioridade e, por isso, não possuem prioridade uma sobre a
outra. Esse é o mesmo caso das operações de soma e subtração, sendo que essas
duas ocupam a última posição na ordem de prioridade.

18
A fim de tornar essa explicação menos abstrata, a Figura 9 ilustra como a
ordem de precedência é utilizada para resolver uma expressão composta por
variados tipos de operação e também por operações delimitadas entre parêntesis.

Figura 9: Resolução de expressões observando a ordem de precedência dos operadores

((4 ∗ 5) − (4 + 1) + 3) / 9 ⇒
(20 − (4 + 1) + 3) / 9 ⇒
(20 − 5 + 3) / 9 ⇒
(15 + 3) / 9 ⇒
18 / 9 =
Fonte: Elaborado pelo Autor (2021)

Na expressão apresentada na Figura 9, deve-se resolver primeiramente as


operações que estão delimitadas pelos parênteses mais internos como é o caso das
operações “(4 ∗ 5)” e “(4 + 1)”. Embora a operação “(4 ∗ 5)” tenha sido resolvida
primeiro, não haveria nenhum problema em resolver inicialmente a operação “(4 +
1)”, visto que ambas estão no mesmo patamar de prioridade. Após a resolução
dessas duas operações, é a vez de resolver a sub-expressão “(20 − 5 + 3)” que, por
estar entre parêntesis, tem prioridade sob a operação de divisão com o operando
“9”.
Já a resolução das operações de subtração e adição, dentro da sub-
expressão “(20 − 5 + 3)”, devem ser resolvidas na ordem que aparecem, isto é, da
esquerda para a direita. Por fim, toda a sub-expressão “((4 ∗ 5) − (4 + 1) + 3)” é
igual ao número 18 que, ao ser dividido pelo 9, resulta em 2, o valor final da
expressão.
As linguagens de programação tendem, em sua maioria, a seguir a ordem

19
de precedência definida na matemática quando se trata das operações
aritméticas elementares e do uso dos parêntesis. Contudo, como discutido na
próxima seção, cada linguagem pode apresentar especificidades em relação à
ordem de precedência quando se trata de expressões que não contenham
somente operações aritméticas básicas.

1.4 TIPOS DE EXPRESSÕES

Quando se fala em expressões, logo vem à mente aquelas que foram


abordadas na seção anterior, isto é, as expressões aritméticas. Porém, no contexto
de linguagens de programação, as expressões, a exemplo das operações, também
podem ser de outros tipos. Sim, além das expressões aritméticas, as linguagens de
programação também suportam expressões de outros tipos como as relacionais e
lógicas.
Na verdade, o uso das expressões relacionais e lógicas são parte
fundamental da grande maioria dos programas. Sem adentrar ainda no conceito
de estruturas de controle em programação, é possível adiantar que as tomadas de
decisão em um programa são feitas a partir da avaliação de expressões relacionais
e lógicas.
Para exemplificar, pode-se imaginar um programa bem simples que tenha o
seguinte objetivo: determinar se um aluno foi ou não aprovado com base em sua
média final e assiduidade às aulas. O aluno é considerado aprovado caso tenha
obtido uma nota maior ou igual a 7, e uma assiduidade maior ou igual a 75%. Esse
programa, em C, poderia ser escrito da forma ilustrada na Figura 10.

Figura 10: Código para determinar a aprovação de um aluno

#include <stdio.h>

int main()
{
int nota, assiduidade;
if ((nota >= 7) && (assiduidade >= 75))
printf(“Aluno aprovado\n”);
else
printf(“Aluno reprovado\n”);
}
Fonte: Elaborado pelo Autor (2021)

20
Como pode ser visto na Figura 10, o programa determina se o aluno foi ou
não aprovado ao avaliar o resultado da expressão ((nota >= 7) && (assiduidade >=
75)). Essa expressão combina operadores relacionais, lógicos e também o uso de
parêntesis para determinar a ordem em que as operações devem ser avaliadas.
Portanto, primeiro deve-se apurar se a nota é maior ou igual a 7. Após, verifica-se se
a assiduidade do aluno foi maior ou igual a 75 (representando 75%). Por fim, os
resultados das duas operações relacionais se tornam os operandos da operação
lógica de conjunção (E lógico) que, em C, é representada pelos símbolos “&&”.
Ao avaliar cuidadosamente a expressão usada para determinar se o aluno
foi ou não aprovado, é possível notar que os parênteses foram utilizados para
garantir que as condições de média e assiduidade fossem avaliadas
individualmente de tal modo que o aluno pudesse ser considerado aprovado
somente se ambas as condições fossem satisfeitas. A expressão, portanto, modela a
regra que é usada para resolver o referido problema de aprovação do aluno.
Mas, nesse caso em particular, cabe uma ressalva quanto à obrigatoriedade
do uso dos parênteses para segregar as operações. Nessa situação específica, não
haveria necessidade de se utilizar os parênteses para determinar a ordem de
precedência naquela expressão. Isso acontece, pois as linguagens de
programação possuem uma ordem de precedência própria para todos os seus
operadores, e não somente para os aritméticos. Em C, a ordem de precedência
preconiza que as operações envolvendo o operador “>=” têm prioridade sobre
aquelas que estão utilizando o operador “&&”.
Sendo assim, o código descrito na Figura 11, que não utiliza parênteses para
separar as operações relacionais, teria a mesma funcionalidade do código original,
já que as operações relacionais seriam resolvidas antes de se considerar a
operação lógica.

21
Figura 11: Código para determinar a aprovação de um aluno com expressão sem
parênteses

#include <stdio.h>

int main()
{
int nota, assiduidade;
if (nota >= 7 && assiduidade >= 75)
printf(“Aluno aprovado\n”);
else
printf(“Aluno reprovado\n”);
}
Fonte: Elaborado pelo Autor (2021)

Essa ordem de precedência pode variar de uma linguagem para outra.


Assim, não se pode assumir previamente que uma expressão, escrita para uma
dada linguagem, será avaliada exatamente da mesma forma em uma outra
linguagem de programação. A Figura 12 resume a ordem de precedência em C.

Figura 12: Ordem de precedência de operadores na linguagem C

Fonte: Disponível em https://bit.ly/2UFWMIr. Acesso em: 16 maio. 2021.

Portanto, para evitar problemas e erros difíceis de se identificar, é


recomendável que o programador se familiarize com a ordem de precedência
adotada pela linguagem que esteja utilizando.

22
23
FIXANDO O CONTEÚDO

1. Os operadores são recursos disponibilizados pelas linguagens de programação


para executar as mais variadas ações. O código a seguir, em Fortran, utiliza um
operador para realizar o cálculo de uma exponenciação.

program exponenciacao

implicit none

! Declarando variaveis
integer :: a, b, c

! Atribuindo valores
a = 5
b = 3

! Calculando a exponenciacao
c = a ** b

end program exponenciacao

De acordo com o código acima e considerando o número requerido de


operandos, o operador “**” pode ser classificado como

a) unário.
b) binário.
c) ternário.
d) lógico.
e) relacional.

2. A linguagem de programação PETEQS foi criada por brasileiros com o intuito de


facilitar o aprendizado de lógica de programação nas escolas e universidades. É
uma linguagem de programação simples, eminentemente didática. De acordo
com seu manual, essa linguagem suporta os seguintes operadores:

Operador Operação
+ Adição entre dois elementos
- Subtração entre dois elementos
* Multiplicação entre dois elementos
/ Divisão entre dois elementos
mod Obtém o resto de uma divisão inteira

24
- Sinaliza que um número é negativo
+ Sinaliza que um número é positivo
ou Conectivo lógico de conjunção
e Conectivo lógico de disjunção
não Conectivo lógico de negação
<- Atribuição de valores
== Igualdade
!= Diferença
> Maior
< Menor
>= Maior ou igual
<= Menor ou igual

Avaliando o conjunto de operadores disponíveis na linguagem PETEQS, pode-se


afirmar que essa linguagem possui:

a) Apenas operadores relacionais e lógicos, e nem todos os operadores são unários.


b) Operadores especiais para manipulação de endereços de memória principal e
registradores.
c) Operadores abreviados para realizar operações lógicas e de atribuição.
d) Operadores aritméticos, relacionais, lógicos e de atribuição, mas nenhum
operador bit a bit.
e) Apenas operadores aritméticos binários e operadores lógicos unários.

3. A linguagem C possui diferentes tipos de operadores como os relacionais,


aritméticos e lógicos. O código em C abaixo realiza uma série de cálculos
envolvendo 2 variáveis.

int main()
{
int a, b;
a = 15;
b = 8;
b++;
a = a + 5;
a *= 5;
}

A respeito do código acima e levando em conta os tipos de operadores disponíveis


em C, é correto afirmar que:
a) estão sendo utilizados apenas operadores aritméticos e relacionais.

25
b) o operador de incremento, representado pelo símbolo “++”, é categorizado
como um operador aritmético.
c) o único operador de atribuição utilizado no código é o de atribuição simples,
representado pelo símbolo “=”.
d) o símbolo “;”, utilizado no final de cada linha, é considerado como um dos
operadores especiais da linguagem.
e) as chaves utilizadas no código são classificadas como operadores relacionais
ternários.

4. A linguagem Julia é utilizada, principalmente, para escrever programas científicos


que demandam uma grande quantidade de cálculos. Nessa linguagem, o símbolo
“-” é utilizado para representar tanto uma operação de subtração quanto a
sinalização de que um número é negativo.

Considerando as informações contidas no texto acima, é correto afirmar que:

a) nenhuma linguagem de programação deve utilizar o mesmo símbolo para


representar operações diferentes, pois trataria-se de uma ambiguação. Cada
operação só pode ser associada a um único símbolo.
b) embora tenham nomes diferentes, as operações, que estão representadas pelo
mesmo símbolo, executam exatamente a mesma função. Portanto, no final das
contas, o símbolo representa apenas uma operação.
c) as linguagens podem ter símbolos que representem operações diferentes, desde
que o número de operandos seja distinto em cada operação. A operação de
sinalização de número negativo é unária, enquanto que a subtração, binária.
d) em nenhuma outra linguagem de programação se observa uma situação em
que o mesmo símbolo seja usado para representar diferentes operações.
e) por se tratar de duas operações lógicas, não há problema em se ter um mesmo
símbolo representando ambas as operações.

5. Uma expressão é um conjunto de operações que envolvem números, constantes


e variáveis. Uma das premissas para se avaliar uma expressão é identificar
corretamente as operações que a compõem. Dito isso, a expressão (1 + 3 + 4) ∗
2 + 7 possui quantas operações?

26
a) 1.
b) 2.
c) 3.
d) 4.
e) 5.

6. Considerando a ordem de precedência adotada na matemática, a expressão


(1 + 3 + 4) ∗ 2 + 7 tem como resultado final o número

a) 18.
b) 72.
c) 23.
d) 30.
e) 51.

7. As linguagens de programação costumam adotar, para os operadores das


quatro operações aritméticas elementares, a mesma ordem de precedência da
matemática. Porém, para os demais operadores, cada linguagem pode apresentar
uma ordem de precedência diferente. Suponha que uma linguagem de
programação adote a ordem de precedência indicada na tabela abaixo e que o
resultado para verdadeiro e falso sejam representados, respectivamente, pelos
números inteiros 0 e 1.

Ordem Operador Descrição

1 () Delimitação de operações

3 * / Multiplicação e divisão

4 + - Adição e subtração

6 < <= >= > Relacionais

11 && Conectivo de conjunção (E lógico)

12 || Conectivo de disjunção (OU lógico)

A partir dessas informações, qual seria o resultado final da expressão abaixo?


3 + ((1 >= 2 + 1) && (1 || (3 < 5)))

27
a) 0.
b) 1.
c) Verdadeiro.
d) 3.
e) 4.

8. As linguagens de programação geralmente fornecem operadores relacionais


para maior e menor, e também para suas variantes com igualdade. Mas, para
efeito desta questão, suponha que uma linguagem de programação fictícia não
possua os operadores “<=” e “>=”. Nesse contexto, a expressão abaixo poderia ser
reescrita como:
( <= ) || ( >= )

a) (( < ) || ( == )) || (( > ) || ( == ))
b) (( < ) && ( == )) && (( > ) || ( == ))
c) (( < ) || ( == )) || (( > ) && ( == ))
d) (( < ) || ( == )) && (( > ) || ( == ))
e) ( < ) || ( > )

28
TIPOS DE DADOS ESPECIAIS UNIDADE

2.1 ESTRUTURAS
02
As linguagens de programação fornecem tipos pré-definidos de dados que
permitem armazenar e manipular valores lógicos, números, caracteres, entre outros
tipos de valores. Esses tipos de dados pré-definidos são conhecidos como tipos
primitivos ou básicos, e são parte integrante da especificação da própria
linguagem.
Os tipos primitivos podem variar bastante de uma linguagem para outra. Em
C, por exemplo, há tipos primitivos para tratar números inteiros, números reais e
caracteres, mas não há um tipo básico para manipular valores lógicos e cadeias de
caracteres. Já em Pascal, por exemplo, há sim tipos primitivos para manipular
valores lógicos (booleanos) e cadeias de caracteres. As linguagens apresentam
diferentes tipos primitivos, pois essa questão está intimamente ligada à essência e às
características predominantes de cada linguagem.
Embora o uso dos tipos primitivos seja suficiente e adequado para uma
ampla gama de situações, há casos em que pode ser mais interessante agrupar
diferentes tipos primitivos em um único novo tipo de dado a fim de facilitar a
manipulação da informação. Uma das formas de se fazer isso é por meio da
definição de uma estrutura ou registro.
Uma estrutura é composta por um grupo de membros ou campos, onde
cada um desses elementos pode ser do mesmo tipo primitivo ou de tipos
completamente diferentes. A Figura 13 ilustra um exemplo de definição de uma
estrutura em C que é usada para representar um produto.

Figura 13: Exemplo de estrutura em C

typedef struct
{
int codigo;
int quantidade;
float valor;
}produto;
Fonte: Elaborado pelo Autor (2021)

29
A estrutura produto é composta pelos membros codigo, quantidade e valor,
e poderia ser usada, por exemplo, para manipular produtos em um programa de
controle de estoque. Os membros codigo e quantidade são do mesmo tipo primitivo
(int), enquanto que o elemento valor é do tipo float que é usado em C para
representar números reais. Portanto, essa estrutura agrupa três elementos que,
semanticamente, estão conectados entre si de acordo com um determinado
contexto.
Após a definição da estrutura, pode-se utilizar esse novo tipo para declarar
novas variáveis, exatamente da mesma forma que se faz com os tipos primitivos. No
exemplo ilustrado na Figura 14 foram criadas duas variáveis - ventilador e
geladeira - que receberam diferentes valores para cada um dos membros ou
campos da estrutura.

Figura 14: Declaração de uma variável usando uma estrutura

typedef struct
{
int codigo;
int quantidade;
float valor;
}produto;

int main()
{
produto ventilador, geladeira;

ventilador.codigo = 1;
ventilador.quantidade = 10;
ventilador.valor = 10.59;

geladeira.codigo = 2;
geladeira.quantidade = 7;
geladeira.valor = 602.45;
}
Fonte: Elaborado pelo Autor (2021)

Dentre outras vantagens, é notório que uma estrutura facilita e simplifica o


acesso a uma informação, já que os dados ficam concentrados e envoltos por um
único identificador. Para acessar um determinado membro, basta utilizar o símbolo
ponto (“.”) seguido pelo nome do membro que foi indicado no momento da
definição da estrutura. Essa sintaxe é utilizada tanto para recuperar a informação
quanto para atualizar os dados na variável.

30
Além disso, é possível criar uma nova estrutura em que os seus membros
sejam do novo tipo criado a partir de outra estrutura. Em outras palavras, pode-se
fazer um encadeamento de estruturas de forma a tornar o código ainda mais
legível, organizado e elegante. Para exemplificar essa possibilidade, a Figura 15
apresenta uma alteração na definição da estrutura produto.

Figura 15: Estrutura composta por membro definido a partir de outra estrutura

typedef struct
{
int largura;
int comprimento;
int altura;
}medidas;

typedef struct
{
int codigo;
int quantidade;
float valor;
medidas dimensoes;
}produto;

int main()
{
produto ventilador;

ventilador.codigo = 1;
ventilador.quantidade = 10;
ventilador.valor = 1.59;
ventilador.dimensoes.largura = 35;
ventilador.dimensoes.altura = 25;
ventilador.dimensoes.comprimento = 15;
}
Fonte: Elaborado pelo Autor (2021)

31
Além de contar com os membros originais, a estrutura produto agora possui
um novo membro chamado dimensoes. Mas, em vez de utilizar um tipo primitivo
para declarar esse novo campo, foi utilizado o tipo medidas, definido a partir de
uma estrutura. A estrutura medidas, por sua vez, é composta pelos membros altura,
comprimento e largura. Dessa forma, o tipo produto passa a ter dois níveis de
informação, onde o segundo nível diz respeito às medidas ou dimensões do
produto. O acesso a esse novo nível também é feito por meio do símbolo ponto
(“.”).
Outra possibilidade interessante é combinar um tipo definido a partir de uma
estrutura com vetores unidimensionais. Dessa forma, em vez de criar uma variável
para cada item que se deseja manipular, pode-se criar vetor de elementos em que
cada posição do vetor armazene um desses itens. A Figura 16 exemplifica esse
cenário.
Figura 16: Combinação entre estrutura e vetor

typedef struct
{
int largura;
int comprimento;
int altura;
}medidas;

typedef struct
{
int codigo;
int quantidade;
float valor;
medidas dimensoes;
}produto;

int main()
{
produto estoque[100];

estoque[0].codigo = 1;
estoque[0].quantidade = 10;
estoque[0].valor = 1.59;
estoque[0].dimensoes.largura = 35;
estoque[0].dimensoes.altura = 25;
estoque[0].dimensoes.comprimento = 15;
}
Fonte: Elaborado pelo Autor (2021)

Como pode ser visto no código da Figura 15, foi criado um vetor de 100
elementos chamado estoque. Por estar sendo declarado com o tipo produto, então

32
cada um dos 100 elementos deste vetor pode ser usado para armazenar um item
que, por sua vez, possui todos os campos da estrutura produto. De fato, o exemplo
mostra que os dados de um produto foram atribuídos ao primeiro elemento do vetor
estoque que, na linguagem C, é referenciado pelo índice 0.
Utilizando essa combinação de vetor e tipos definidos a partir de estruturas,
não há necessidade de criar uma variável diferente para cada um dos itens que se
deseja manipular. Dessa forma, o uso de estruturas confere muito mais organização
e clareza ao código, facilitando tanto o armazenamento quanto a recuperação da
informação.

2.2 ENUMERAÇÕES

Além de variáveis, as linguagens de programação também oferecem a


possibilidade de se criar constantes. Como o valor de uma constante não pode ser
alterado após a sua criação, os programadores comumente utilizam esse recurso
para definir rótulos que são usados para representar alguma informação. A
utilização desses rótulos facilita a leitura e a documentação do código, além de
tornar o processo de manutenção muito mais ágil e simplificado.

33
Tomando como exemplo o código apresentado na Figura 16, poderia-se
expandir um pouco mais o tipo produto para incluir um novo membro a fim de
indicar a categoria ou classe do produto. Isso pode ser feito a partir de constantes,
como mostra o código ilustrado na Figura 17.

Figura 17: Uso de constantes para indicar a classe do produto

#define ELETRODOMESTICO 10
#define ALIMENTO 20
#define ROUPA 30
#define INFORMATICA 40

typedef struct
{
int largura;
int comprimento;
int altura;
}medidas;

typedef struct
{
int codigo;
int quantidade;
int categoria;
float valor;
medidas dimensoes;
}produto;

int main()
{
produto estoque[100];

estoque[0].codigo = 1;
estoque[0].quantidade = 10;
estoque[0].categoria = ELETRODOMESTICO
estoque[0].valor = 1.59;
estoque[0].dimensoes.largura = 35;
estoque[0].dimensoes.altura = 25;
estoque[0].dimensoes.comprimento = 15;
}
Fonte: Elaborado pelo Autor (2021)

Em C, as constantes são definidas por meio da diretiva define. Nesse


exemplo, foram criadas 4 constantes para representar as possíveis classes de um
determinado produto. Assim, um produto, no contexto deste exemplo, pode ser um
eletrodoméstico, alimento, roupa ou algum item relacionado à informática. Após a
definição dessas constantes, o programador pode utilizá-las em todo o código,

34
como foi feito na atribuição da informação ELETRODOMESTICO ao campo categoria
do primeiro produto do vetor estoque. Sendo assim, a constante ELETRODOMESTICO
passou a ser utilizada como um rótulo para denotar que um dado produto é um
eletrodoméstico.
Essa identificação da categoria do produto poderia ser feita de outras
formas. Uma opção seria utilizar diretamente o número inteiro que foi atribuído no
momento de criação das constantes. Dessa forma, em vez de atribuir a constante
ELETRODOMESTICO ao membro categoria, poderia-se atribuir diretamente o seu valor,
isto é, o número 10. Contudo, essa solução certamente dificultaria o entendimento
do código, já que seria preciso lembrar que o número 10 estaria associado à
categoria de eletrodoméstico. Para não precisar lembrar dessa associação, haveria
a possibilidade de manter a informação sobre essa relação em um arquivo
separado ou dentro do próprio código, mas em formato de comentários. De
qualquer forma, essa solução está bem longe de ser eficiente, visto que a leitura do
código precisaria ser interrompida a todo momento para que essa informação fosse
decifrada.
Outra opção seria utilizar um texto para representar essa informação, algo
como atribuir o valor “eletrodomestico” ao membro categoria da estrutura produto.
Mesmo parecendo uma boa solução, essa alternativa traz alguns problemas. Em
primeiro lugar, há linguagens que fazem diferenciação entre letras maiúsculas e
minúsculas (também conhecido pelo termo em Inglês case sensitive). Dessa forma,
mesmo que as palavras sejam ortograficamente idênticas, o valor
“eletrodomestico”, para essas linguagens, não é igual à “Eletrodomestico”, visto que
a segunda palavra está escrita com um “E” maiúsculo. Além disso, ao adotar essa
abordagem, o programador também pode cometer o erro de escrever a palavra
com alguma pequena variação, como utilizar o plural, por exemplo. Assim, mesmo
que a linguagem de programação não seja case sensitive, os valores
“eletrodomestico” e “eletrodomesticos” são inegavelmente diferentes.
Por conta de todos esses aspectos, a utilização de constantes é uma boa
prática quando se precisa representar informações por meio de rótulos. Mas, em
vez de definir individualmente essas constantes, pode-se fazer uso de um recurso
chamado enumeração em que as constantes são definidas em conjunto e ficam
associadas a um novo tipo de dado.
O código da Figura 18 mostra como uma enumeração pode ser usada para

35
substituir as definições individuais de constantes para representação de rótulos. Em
C, uma enumeração é criada por meio da palavra reservada enum.

Figura 18: Exemplo de código com uma enumeração

typedef enum
{
alimento,
roupa,
eletrodomestico,
informatica
}departamentos;

typedef struct
{
int largura;
int comprimento;
int altura;
}medidas;

typedef struct
{
int codigo;
int quantidade;
departamentos categoria;
float valor;
medidas dimensoes;
}produto;

int main()
{
produto estoque[100];

estoque[0].codigo = 1;
estoque[0].quantidade = 10;
estoque[0].categoria = eletrodomestico
estoque[0].valor = 1.59;
estoque[0].dimensoes.largura = 35;
estoque[0].dimensoes.altura = 25;
estoque[0].dimensoes.comprimento = 15;
}
Fonte: Elaborado pelo Autor (2021)

A enumeração departamentos é composta pelas constantes alimento, roupa,


eletrodomestico e informatica, ou seja, os mesmos itens que foram definidos
individualmente na versão anterior código deste código. Após a sua definição, a
enumeração passa a ser um tipo de dado e, como no caso das estruturas e tipos
primitivos, pode ser usada para definir variáveis que sejam desse novo tipo. De fato,

36
o membro categoria, da estrutura produto, não é mais do tipo int, mas sim do tipo
departamentos. Diferentemente da versão anterior deste código, a variável
categoria, do primeiro elemento do vetor estoque, recebeu a constante
eletrodomestico que é um dos itens definidos na enumeração departamentos.
Uma das grandes vantagens de se empregar enumerações diz respeito à
característica de restrição dos valores que podem ser atribuídos a uma variável que
seja declarada com um tipo criado a partir de uma enumeração. Por exemplo,
uma variável declarada com o tipo departamentos apenas aceitará os itens que
estejam definidos nesta enumeração. Sendo assim, caso o programador decida
atribuir o valor livros a variável categoria, o código não será compilado, já que
esse item, essa constante, não é parte integrante da enumeração departamentos.
Essa propriedade evita que ocorra o problema de se atribuir valores que não
estejam incluídos na enumeração, tornando o código mais confiável e menos
suscetível a erros.

2.3 PONTEIROS

Em um programa, as variáveis e constantes são utilizadas para manipular


dados que, por sua vez, são armazenados na memória principal do computador.
Quando um programa é executado, o sistema operacional reserva posições de
memória para alocar o conteúdo das variáveis definidas no código. Enquanto o
programa estiver em execução, essas variáveis estarão sempre associadas a essa
mesma posição de memória.
Portanto, a memória principal é dividida em várias partes menores chamadas
de células e, a cada uma dessas partes, é atribuído um endereço. Como o próprio
nome já indica, os endereços de memória são usados para identificar o local em
que uma informação está armazenada. A partir deste endereço, o sistema
operacional, em conjunto com o hardware do computador, é capaz de processar

37
e manipular corretamente os dados dos programas.
A Figura 19 apresenta uma ilustração sobre a divisão da memória principal
em células. Esse esquema é meramente didático, já que há muito mais detalhes
técnicos que envolvem a divisão, alocação e acesso aos dados em uma memória
real. Mas, para o contexto dessa disciplina, basta entender que a memória é
dividida em células que armazenam o conteúdo das variáveis e que cada uma
dessas células é referenciada por meio de um endereço de memória. Sendo assim,
de acordo com este exemplo, a memória estaria dividida em 8 células e, na célula
endereçada pelo número 5, estaria armazenado o conteúdo “Maicon Melo”.

Figura 19: Esquema de divisão da memória principal

Conteúdo
Endereço

1 252 Célula

2 15
3 Verdadeiro

4 8.25

5 Maicon Melo

6 1589

7 369

8 Produto

Memória principal

Fonte: Elaborado pelo Autor (2021)

Dessa forma, o sistema operacional não conhece os nomes das variáveis ou


constantes utilizados em um código-fonte, já que toda a manipulação de dados na
memória principal acontece, de fato, a partir dos endereços de memória. Então, no
final das contas, o nome de uma variável pode ser entendido como um
identificador para aquela posição de memória. Por conta dessa abstração, os

38
programadores podem escrever códigos muito mais simples, visto que não precisam
se preocupar com questões tão relacionadas ao hardware do computador.

Por exemplo, o código descrito na Figura 20 define uma variável chamada


numero que recebe o valor 10. Na prática, esse identificador número está apontando
ou referenciando a algum endereço de memória na qual as informações dessa
variável estão armazenadas. A linguagem C oferece um operador que permite
obter o endereço de memória atrelado a uma determinada variável. Assim, ao
utilizar o operador especial “&”, o programa é capaz de obter e imprimir na tela o
endereço de memória na qual o conteúdo da variável numero está alocado.

Figura 20: Impressão do endereço de memória alocado a uma variável

#include <stdio.h>

int main()
{
int numero;
numero = 10;
printf("%p\n", &numero);
}
Fonte: Elaborado pelo Autor

Ao ser executado, o programa gerado a partir desse código deve imprimir


algo semelhante a 0x7fffd9871784 que representa o endereço de memória
referenciado ou apontado pela variável numero. Por ser um número muito extenso,
os endereços de memória comumente são expressos no formato hexadecimal
como o endereço deste exemplo. Além disso, os endereços de memória, em C,
iniciam com prefixo “0x” a fim de indicar que o valor em questão é um endereço
de memória.
Algo interessante a ser notado é que esse programa vai imprimir diferentes

39
valores a cada execução, pois cada uma dessas execuções resultará em
diferentes solicitações de alocação de memória realizadas ao sistema operacional.
Embora as linguagens de programação e o sistema operacional ofereçam
essa abstração em que identificadores são usados para referenciar endereços de
memória, há uma série de tarefas em que o programador precisa lidar diretamente
com esses endereços. É por conta dessa necessidade que grande parte das
linguagens de programação oferecem um tipo de dado chamado ponteiro. Uma
variável do tipo ponteiro é utilizada para armazenar o endereço de memória que
está sendo referenciado por uma outra variável. O código da Figura 21 mostra
como essa operação pode ser feita em C.
Figura 21: Utilização de variável do tipo ponteiro

#include <stdio.h>

int main()
{
int numero = 10;
int *ptro;

ptro = &numero;
printf("%p\n", ptro);
}
Fonte: Elaborado pelo Autor

Em C, a criação de um ponteiro é realizada com o operador especial “*”.


Assim, o comando “int *ptro;” diz ao compilador que deve ser criada uma
variável com nome ptro para manipular endereços de memória que estejam
armazenando dados do tipo inteiro, ou seja, do tipo primitivo int. Após a criação
do ponteiro, o programador pode utilizar o operador “&”, como mostrado
anteriormente, para obter o endereço de memória de uma variável. No exemplo, o
ponteiro ptro recebe o endereço de memória referenciado pela variável numero.
De posse de um endereço, o ponteiro pode ser usado para realizar
operações diretamente nessa posição de memória, ou seja, sem envolver o
identificador original dessa posição. O código da Figura 22 apresenta um exemplo
de como o conteúdo de uma posição de memória pode ser alterado a partir de
um ponteiro.

Figura 22: Acesso indireto à memória por meio de um ponteiro

#include <stdio.h>

40
int main()
{
int numero = 10;
int *ptro;

ptro = &numero;

printf("%d\n", numero);

*ptro = 20;

printf("%d\n", numero);
}
Fonte: Elaborado pelo Autor

Ao ser executado, este código deve imprimir os valores 10 e 20, nessa ordem.
A variável numero recebeu o valor 10 durante sua criação e, em seguida, o
endereço de memória referenciado por numero foi atribuído à variável ptro que é
um ponteiro para variáveis do tipo int.
Nesse ponto, como ilustrado na Figura 23, tanto a variável numero quanto
ptro estão apontando para o mesmo endereço de memória. A execução da
instrução “*ptro = 20;” atribui o valor 20 ao endereço de memória apontado pela
variável ptro o que, na prática, acaba resultando também na alteração do valor
da variável numero.

Figura 23: Alocação de variável na memória principal

numero

10 Célula

0x7fffd9871784
*ptro Endereço

Fonte: Elaborado pelo Autor

Como pode ser visto no código da Figura 22, o operador “*” foi utilizado em
duas ocasiões diferentes. Na primeira, o operador foi utilizado para declarar a
variável do tipo ponteiro chamada ptro. Já na outra ocasião, o operador foi
empregado para acessar o endereço de memória contido na variável ptro. Trata-
se, portanto, de um acesso indireto feito a um endereço de memória a partir de um

41
ponteiro.
Algo importante a ser compreendido é que o próprio ponteiro também é
uma variável e, mesmo sendo de um tipo especial que armazena endereços,
também está alocado em uma determinada posição de memória. Sendo assim, o
próprio ponteiro também possui o seu endereço de memória e esse endereço não
tem qualquer relação com o endereço que está armazenado no conteúdo
ponteiro.
Para ficar mais claro, a Figura 24 apresenta um esquema onde é
evidenciada a diferença entre o conteúdo de um ponteiro e o endereço da
posição de memória que essa variável está alocada. Nesse exemplo, a variável
ponteiro ptro está armazenada no endereço 0x7ffc3aeb2c70, enquanto que o seu
conteúdo é, na verdade, o endereço 0x7fffd9871784, isto é, o endereço da
variável numero.

Figura 24: Diferença entre o conteúdo de um ponteiro e a sua posição alocada na memória

numero
*ptro

0x7fffd987178 10
Endereços
Conteúdo
0x7fffd9871
0x7ffc3aeb2c7

ptro
Fonte: Elaborado pelo Autor (2021)

O conceito de ponteiros é algo que precisa de um certo tempo para ser


completamente assimilado. De fato, não é algo trivial. Contudo, esse conhecimento
é necessário para realizar vários tipos de tarefas na área de programação como,
por exemplo, implementar a passagem de parâmetros por referência em funções e
procedimentos.

42
43
FIXANDO O CONTEÚDO

1. Considere o seguinte fragmento de código em C na qual duas estruturas são


definidas:

typedef struct
{
int x;
}estruturaA;

typedef struct
{
int x;
estruturaA a;
}estruturaB;

int main()
{
estruturaB b;
}

Levando em conta o código acima, qual seria a forma correta de se acessar o


conteúdo do membro x da estruturaA?

a) x
b) b.x
c) a.x.b
d) b.a.x
e) b.b.x

2. Considerando o código em C a seguir, qual será o valor da variável z ao final de


sua execução?

int main()
{
int x, *y, *z;
x = 10;
y = &x;
z = y;
}

44
a) O número 10.
b) Não possuirá nenhum valor.
c) O endereço de memória da variável x.
d) O valor da variável x.
e) O endereço de memória da variável y.

3. O fragmento de código em C abaixo define uma estrutura denominada carro


que é composta apenas pelo membro marca.

typedef struct
{
montadoras marca;
}carro;

Mesmo sem ter mais detalhes sobre o tipo montadoras, é correto afirmar que:

a) essa definição de estrutura só estaria correta no caso em que o tipo montadoras


fosse um ponteiro para variáveis que armazenam números reais.
b) caso o tipo montadoras tenha sido definido a partir de uma estrutura composta
exclusivamente por apenas 1 membro do tipo inteiro, então o membro marca
apenas poderá receber valores que sejam negativos.
c) não é possível definir uma estrutura com um tipo de dado que não seja um dos
tipos primitivos da linguagem, já que isso acarretaria em falhas de identificação do
membro da estrutura.
d) o membro marca pode receber qualquer número inteiro, independentemente da
forma como o tipo montadoras foi definido.
e) se o tipo montadoras foi definido a partir de uma enumeração, então apenas as
constantes listadas nessa enumeração poderão ser atribuídas ao membro marca.

4. Em C, o operador “*” é usado para acessar o conteúdo armazenado em uma


determinada posição de memória. Já o operador “&” permite obter o endereço de
memória no qual uma variável está alocada.

Levando em conta essas informações e o código abaixo, é correto afirmar que a


variável z receberia:

45
int main()
{
int x, y, z;
x = 10;
y = 20;
z = ((*&x) + (*&y));
}

a) um valor nulo.
b) o endereço de memória da variável x.
c) o resultado entre a soma dos valores contidos nos endereços de memória
apontados pelas variáveis x e y.
d) o resultado entre a soma dos endereços de memória da variáveis x e y.
e) o endereço de memória da variável x somado ao valor 20.

5. A linguagem C é considerada como sendo case sensitive, já que faz distinção


entre letras maiúsculas e minúsculas. Portanto, duas palavras só são consideradas
iguais quando as letras utilizadas para escrever essas palavras são exatamente as
mesmas, isto é, não pode haver diferença entre maiúsculas e minúsculas, ou uso de
acentos, por exemplo. Dito isso, avalie o seguinte trecho de código em C:

typedef enum
{
campos,
Campos,
caMpos,
}cidades;

A respeito dessa enumeração, avalie as afirmações abaixo:

I. essa não é uma enumeração válida, pois todos os seus elementos são iguais.
II. embora válida, essa enumeração não pode ser utilizada para definir um membro
em uma estrutura.
III. embora os nomes dos elementos sejam ortograficamente iguais, cada um dos
elementos é uma constante completamente diferente.

As afirmações corretas são:

a) apenas I.

46
b) apenas II.
c) apenas III.
d) I e II.
e) I, II e III.

6. Na linguagem C, o operador “*” é utilizado para acessar o valor contido em um


determinado endereço de memória. Considerando o código abaixo, é correto
afirmar que o valor armazenado na variável z seria:

int main()
{
int x, *y, z;
x = 10;
y = &x;
x = x + 20;
z = *y;
}

a) 0.
b) 10.
c) 20.
d) 30.
e) 40.

7. Uma variável do tipo ponteiro é, antes de mais nada, uma variável. Por essa
razão, uma variável ponteiro também ocupa uma posição de memória,
identificada por um endereço único, em que uma informação é armazenada. No
caso dos ponteiros, essa informação é o endereço de memória ocupado por uma
outra variável. Em C, os endereços de memória são iniciados com o prefixo “0x” e
são exibidos no formato hexadecimal.

Levando em conta essas informações e considerando os dados exibidos no


esquema abaixo, é correto afirmar que:

47
a) o conteúdo armazenado na posição de memória referenciada pelo endereço
0x7fffd9871784 é um outro endereço cuja posição em memória armazena um
número real.
b) o conteúdo armazenado na posição de memória referenciada pelo endereço
0x7fffd9871784 é o número inteiro 658.
c) o conteúdo armazenado na posição de memória referenciada pelo endereço
0x7fffd98711281 é o endereço 0x7adfd98711348 que, por sua vez, faz referência
a uma posição de memória em que um terceiro endereço está armazenado.
d) o conteúdo armazenado na posição de memória referenciada pelo endereço
0x7adfd98711348 é o endereço 0x7ffc3aeb2c70 que, por sua vez, faz referência
a uma posição de memória em que um número inteiro está armazenado.
e) o conteúdo armazenado na posição de memória referenciada pelo endereço
0x7fffd9871784 é um texto.

8. Para manipular as informações em um programa que trata da venda de carros


em uma agência de veículos, um programador decidiu utilizar o recurso de
estrutura ou registro para criar um novo tipo de dado chamado carro. Esse novo
tipo de dado foi definido a partir do seguinte trecho de código escrito em C:

typedef enum
{
usado,
seminovo,
novo
}tipos_situacao;

typedef struct
{
tipos_situacao situacao;

48
}detalhes;

typedef struct
{
int codigo;
float valor;
detalhes informacoes;
}carro;

De acordo com a definição do tipo carro, é correto afirmar que:


a) a estrutura carro é composta por apenas dois membros.
b) o membro situacao, da estrutura detalhes, só poderá receber os valores usado,
novo e seminovo.
c) a estrutura carro possui, ao todo, 7 membros.
d) a estrutura carro possui apenas membros definidos a partir de tipos primitivos.
e) o acesso ao membro novo pode ser acessado diretamente a partir da estrutura
carro.

49
ESTRUTURAS DE SELEÇÃO UNIDADE

3.1 SELEÇÃO SIMPLES


03
Em seu cotidiano, as pessoas precisam avaliar situações para decidir o que
deve ser feito caso uma dada condição seja satisfeita. Esse tipo de avaliação
condicional é realizada intuitivamente pelas pessoas ao executar toda sorte de
atividades e tarefas.
Por exemplo, quando uma pessoa condiciona sua ida à praia apenas se
houver sol, está, a rigor, tomando uma decisão com base em um determinado
critério. Esse tipo de avaliação envolve, em geral, uma condição e um conjunto de
ações a serem executadas caso a condição seja verdadeira, como ilustrado na
Figura 25.

Figura 25: Exemplo de avaliação condicional

Se estiver com sol, então irei à praia.


Condição Ação, caso a
condição seja
verdadeira

Fonte: Elaborado pelo Autor (2021)

Nesse exemplo, a condição que está sendo avaliada é se o dia está


ensolarado e, caso isso se mostre verdadeiro, então a ação será “ir à praia”. É
interessante também notar o uso das palavras “se” e “então” para compor essa
expressão.
Assim como acontece no mundo real, um programa também pode precisar
tomar certas decisões que alteram, mesmo que momentaneamente, o seu fluxo de
execução. Para isso, as linguagens de programação fornecem um recurso
chamado de estrutura de seleção que permite avaliar uma dada condição e,
dependendo do resultado desse teste, executar um determinado conjunto de
instruções. Uma estrutura de seleção pode ser simples, composta, encadeada ou

50
múltipla.
A estrutura de seleção simples é composta por uma condição e por um
único conjunto ou bloco de instruções que será executado caso a condição seja
verdadeira. O código em C ilustrado na Figura 26 mostra o uso dessa estrutura de
seleção simples para verificar se um número é negativo.

Figura 26: Exemplo de estrutura de seleção simples

#include <stdio.h>

int main()
{
int numero;

if (numero < 0)
{
printf("Número é negativo!");
}
}
Fonte: Elaborado pelo Autor (2021)

Para escrever uma estrutura de seleção simples em C utiliza-se a palavra


reservada if que, na língua portuguesa, pode ser traduzida para “se”, justamente a
primeira palavra usada na expressão ilustrada na Figura 25. O bloco de instruções
abaixo da palavra reservada if será executado caso a condição se mostre
verdadeira.
Vale ressaltar que a condição a ser avaliada na estrutura de seleção simples
precisa retornar um valor lógico, ou seja, verdadeiro ou falso. Sendo assim, essa
condição pode ser formada por expressões lógicas, relacionais, ou qualquer outro
tipo de expressão desde que, necessariamente, o resultado do teste seja expresso
em um valor lógico.

3.2 SELEÇÃO COMPOSTA

A estrutura de seleção simples é um recurso que já confere uma boa dose de


flexibilidade e funcionalidade aos programas. Contudo, em uma tomada de
decisão, é muito comum considerar uma opção alternativa caso a condição que
está sendo avaliada seja falsa. No caso do exemplo em que uma pessoa está
decidindo se deve ou não ir à praia, pode-se pensar em uma outra ação caso o
dia não esteja ensolarado, como mostrado na Figura 27.

51
Figura 27: Exemplo de avaliação condicional

Se estiver com sol, então irei à praia, senão, irei ao cinema.

Condição Ação, caso a Ação, caso a


condição seja condição seja
verdadeira falsa

Fonte: Elaborado pelo Autor (2021)

Então, nesse exemplo, a pessoa irá ao cinema caso não o dia não esteja
com sol. Em linguagem natural, costuma-se utilizar a palavra “senão” que tem o
mesmo significado de “do contrário”. Portanto, em suma, se a condição for
verdadeira, então uma determinada ação será realizada, senão uma outra ação
alternativa deverá ser executada.
Essa estrutura em que é utilizada uma alternativa a ser executada caso a
condição seja falsa é denominada como estrutura de seleção composta. Para
ilustrar a aplicação de uma estrutura de seleção composta, o código apresentado
na Figura 28 mostra como determinar se um número é par ou ímpar.

Figura 28: Exemplo de estrutura de seleção composta

#include <stdio.h>

int main()
{
int numero = 10;
int resto;

resto = numero % 2;

if (resto == 0)
{
printf("Número é par");
}
else
{
printf("Número é ímpar");
}

52
}
Fonte: Elaborado pelo Autor (2021)

Em C, a estrutura de seleção composta faz uso da palavra reservada else


para indicar que, se a condição não for satisfeita, o bloco de instruções aninhado a
essa palavra chave deverá ser executado. O termo “else” pode ser traduzido para
a língua portuguesa como “senão”. Essa palavra é, de fato, o termo que se
costuma adotar para indicar as ações que serão executadas quando a condição
que está sendo avaliada não é verdadeira.

3.3 SELEÇÃO ENCADEADA

Em alguns casos, o programador pode precisar testar uma condição que só


pode ser avaliada de acordo com o resultado de uma outra condição. Pode-se
dizer, portanto, que essas condições estariam encadeadas, já que a avaliação de
uma delas depende do resultado obtido no teste da outra condição. Para
exemplificar essa ideia, a Figura 29 traz uma adaptação do já famigerado exemplo
de decisão sobre ir ou não à praia.

53
Figura 29: Encadeamento de avaliações condicionais

Se estiver com sol, então irei à praia,


Ação, caso a
condição #1

Se tiver companhia, então irei ao cinema, senão,


Ação, caso a Ação, caso a
condição #2 (e condição #2 (e

Fonte: Elaborado pelo Autor (2021)

Nesse novo cenário, há uma segunda condição que só será avaliada se a


primeira condição não for satisfeita. De fato, se o dia estiver ensolarado, então a
decisão final é ir à praia e não há mais nada para ser avaliado nesse caso. Mas, se
o dia não estiver com sol, então a segunda condição precisará ser testada para
decidir qual ação será realizada. Se a pessoa tiver companhia, então ela irá ao
cinema. Do contrário, ficará em casa.
Portanto, para que a ação “ir ao cinema” seja realizada, não só é preciso
que a condição “ter companhia” seja verdadeira, mas que, concomitantemente, a
condição “estar com sol” seja falsa. Sendo assim, as ações “ir ao cinema” e “ficar
em casa” dependem do resultado de duas condições que estão ligadas,
encadeadas.
Para executar esse tipo de avaliação, as linguagens de programação
oferecem a possibilidade de executar uma seleção encadeada. O código
apresentado na Figura 30 mostra como implementar essa funcionalidade na
linguagem C.

Figura 30: Exemplo de estrutura de seleção encadeada

#include <stdio.h>

int main()
{
int nota;
int frequencia;

if (nota < 7)
{
printf("Aluno reprovado por média!");

54
}
else
{
if (frequencia < 75)
printf("Aluno reprovado por falta!");
else
printf("Aluno aprovado!");
}
}
Fonte: Elaborado pelo Autor (2021)

Nesse exemplo, o programa recebe informações de nota e frequência de


um aluno para determinar se ele foi aprovado, reprovado por nota ou reprovado
por frequência. A primeira condição a ser testada é se a nota é menor do que 7.
Em caso positivo, o aluno já está reprovado por média. Porém, se a nota for maior
ou igual a 7, há duas possibilidades. Se a frequência for inferior a 75%, o aluno está
reprovado por falta e, caso contrário, está aprovado. É interessante notar que, para
estar aprovado, o aluno precisa que as duas condições avaliadas sejam falsas, ou
seja, que a sua nota não seja inferior a 7 e que sua frequência não seja menor do
que 75.
Esse mesmo código poderia ser reescrito de várias outras formas, bastando
inverter a ordem em que as condições são avaliadas ou o sentido em que as
comparações foram feitas. Além disso, a sintaxe da linguagem C também suporta o
uso da palavra reservada if logo após uma cláusula else. Porém, essa estrutura
pode dificultar um pouco o entendimento do código, já que as estruturas de
seleção não ficam posicionadas de tal forma que facilite a identificação dos
diferentes níveis da avaliação condicional.

55
3.3 SELEÇÃO MÚLTIPLA

As estruturas de seleção simples, composta e encadeada permitem avaliar


todo tipo de condição formada a partir de expressões lógicas e relacionais. Mas,
para uma situação em particular, o uso dessas estruturas, embora viável, pode
trazer uma certa complexidade ao código.
Esse é o caso em que o programa precisa comparar uma variável a um
conjunto de valores. Um exemplo clássico desse tipo de necessidade é quando se
deseja imprimir o dia da semana de acordo com um número inteiro informado ao
programa. Então, se o programa receber o número 1, deve imprimir “domingo”,
número 2, “segunda”, e assim sucessivamente. Nesse caso, uma variável deve ser
comparada com uma série de valores (7, nesse exemplo) para determinar qual
ação deverá ser executada.
Sem dúvida, esse problema pode ser resolvido com uma estrutura de seleção
encadeada. Contudo, pode ser que o código fique extenso e confuso, já que será
preciso utilizar várias condições aninhadas.
Para esses casos, algumas linguagens suportam a estrutura de seleção
múltipla que é um recurso bem mais adequado para tratar esse tipo de situação.
Em linguagem natural, essa estrutura de seleção poderia ser entendida como o ato
de selecionar o conjunto de instruções a ser executado de acordo com o valor
contido na variável de decisão.
Em C, essa estrutura é implementada com as palavras reservadas switch e
case, como mostrado no código da Figura 31.

Figura 31: Exemplo de estrutura de seleção múltipla

#include <stdio.h>

int main()
{
int dia_da_semana;

switch (dia_da_semana)
{
case 1: printf("domingo");
break;
case 2: printf("segunda");
break;
case 3: printf("terça");
break;

56
case 4: printf("quarta");
break;
case 5: printf("quinta");
break;
case 6: printf("sexta");
break;
case 7: printf("sábado");
break;
default: printf("número incorreto");
break;
}
}
Fonte: Elaborado pelo Autor (2021)

A palavra reservada switch é usada para iniciar o bloco de seleção múltipla,


também conhecido em C como estrutura switch-case. Entre parênteses, após o
switch, deve ser inserida a variável de decisão, ou seja, a variável que será
comparada ao conjunto de valores. Nesse exemplo, essa variável é denominada
dia_da_semana e é usada para representar o dia da semana de acordo com os
números entre 1 a 7. Então, para cada valor esperado, deve-se utilizar uma
instrução iniciada com a palavra reservada case, seguida pelo valor que se deseja
testar. À frente desta instrução vem o bloco de código que será executado caso
esse valor esteja atribuído a variável de decisão. Por fim, a palavra reservada break
é usada para indicar o final do bloco de instruções corrente.
É preciso ressaltar que essa estrutura não está disponível em todas as
linguagens de programação mais conhecidas e que, mesmo em C, esse recurso
possui algumas limitações. A variável de decisão só pode ser dos tipos primitivos int
e char, ou seja, só pode armazenar números inteiros ou um caractere. Isso significa
que a cláusula switch-case não suporta a avaliação de números reais ou cadeias
de caracteres, por exemplo.
Mas, mesmo com essas limitações, essa estrutura continua sendo uma
ferramenta interessante para escrever um código simples e organizado para tratar
esses casos em que uma variável deve ser comparada a um conjunto de valores.

57
58
FIXANDO O CONTEÚDO

1. As estruturas de seleção encadeadas são compostas por grupos ou conjuntos de


condições aninhadas que apresentam algum grau de dependência. Considerando
o seguinte fragmento de código em C, é correto afirmar que essa estrutura de
seleção encadeada é composta, em sua totalidade, por

if (x > 0)
{
if (z == 10)
a = 20;
else
b = 30;
}
else
{
a = 0;
}

a) nenhuma avaliação condicional.


b) 3 avaliações condicionais.
c) 1 avaliação condicional e uma seleção múltipla.
d) 2 avaliações condicionais.
e) 1 avaliação condicional formada por uma expressão lógica.

2. Considerando o fragmento de código abaixo e que a variável x seja igual a 10, é


correto afirmar que o valor da variável y que será impresso na tela é igual a:

if (x > 0)
{
if (x < 10)
{
y = 0;
}
else
{
if (x > 11)
{
y = -1;
}
else
{
y = 1;

59
}
}
}
printf(“%d”, y);

a) 0.
b) 1.
c) -1.
d) Nulo.
e) 20.

3. Uma estrutura de seleção pode ser reescrita de várias formas diferentes ao se


modificar a ordem das avaliações condicionais ou apenas empregando outros
tipos de estrutura de seleção. Considerando o trecho de código a seguir, que
implementa uma estrutura de seleção encadeada em C para determinar a
estação do ano de acordo com o valor da variável x, é correto afirmar que essa
mesma estrutura poderia ser reescrita utilizando:

if (x == 1)
{
printf(“verão”);
}
else
{
if (x == 2)
{
printf(“outono”);
}
else
{
if (x == 3)
{
printf(“inverno”);
}
else
{
printf(“primavera”);
}
}

a) apenas 1 estrutura de seleção múltipla.


b) apenas 1 estrutura de seleção simples.
c) apenas 1 estrutura de seleção composta.

60
d) apenas 1 estrutura de seleção encadeada contendo apenas 1 avaliação
condicional.
e) apenas 1 estrutura de seleção encadeada sem conter nenhuma avaliação
condicional.

4. A estrutura de seleção múltipla disponível na linguagem C possui uma palavra


reservada denominada default que é acionada caso o valor contido na variável
de decisão não seja igual a nenhuma das cláusulas case presentes na estrutura de
seleção.

Considerando o trecho de código a seguir, que implementa uma estrutura de


seleção múltipla para determinar se uma letra é vogal ou consoante, avalie as
seguintes afirmações:

I. Essa estrutura de seleção múltipla poderia ser reescrita utilizando-se apenas 1


estrutura de seleção composta.

II. Essa estrutura de seleção múltipla poderia ser reescrita utilizando-se apenas 1
estrutura de seleção simples.

III. Essa estrutura de seleção múltipla não poderia ser reescrita.

char letra;

switch (letra)
{
case ‘a’: printf("vogal");
break;
case ‘e’: printf("vogal");
break;
case ‘i’: printf("vogal");
break;
case ‘o’: printf("vogal");
break;
case ‘u’: printf("vogal");
break;
default: printf("consoante");
break;
}

Indique a opção que corresponde às afirmações corretas.


a) apenas I .

61
b) apenas II.
c) apenas III.
d) I e II.
e) I e III.

5. As estruturas de seleção avaliam condições formadas por expressões lógicas e


relacionais. Em muitas ocasiões, para facilitar o entendimento do código, é
desejável agrupar operações relacionais individuais em uma mesma expressão,
usando conectivos lógicos. Ao fazer isso, o código torna-se mais simples e intuitivo
de ser interpretado e modificado.

O trecho de código em C abaixo apresenta duas estruturas de seleção simples.

if (x == 10)
{
z = 0;
}

if (y == 10)
{
z = 0;
}

Tomando como base as afirmações acima e considerando a lógica de


programação implementada neste código, é correto afirmar que essas duas
estruturas de seleção poderiam ser reescritas em apenas uma estrutura de seleção
simples em que a condição fosse igual a seguinte expressão:

a) ((x == 10) && (y == 10))


b) !(x == 10)
c) !(y == 10)
d) !((x == 10) || (y == 10))
e) ((x == 10) || (y == 10))

6. Avaliando o código a seguir, indique qual seria o valor que estaria atribuído à
variável z no final da execução desse programa:

int main()
{

62
int x, z;
x = 0;

if (x > 0)
z = 0;
else
z = 1;

if (x < 0)
z = 1;
else
z = 0;
}

a) 1.
b) -1.
c) 0.
d) -2.
e) 2.

7. Observe os dois fragmentos de código abaixo e responda em seguida.

Código A:
if (x > 0)
z = 0;
else
z = 1;

Código B:
if (x < 0)
z = 1;
else
z = 0;

A respeito dos códigos A e B, é correto afirmar que:


a) ambos os códigos utilizam uma estrutura de seleção encadeada.
b) o código A utiliza uma estrutura de seleção simples, enquanto que o B,
composta.
c) o código A utiliza uma estrutura de seleção composta, enquanto que o B,
simples.

63
d) ambos os códigos utilizam uma estrutura de seleção composta e são
equivalentes em relação a lógica de programação.
e) ambos os códigos utilizam uma estrutura de seleção composta, mas não são
equivalentes em relação a lógica de programação.

8. As condições avaliadas em estruturas de seleção são formadas por expressões


lógicas e relacionais. Em C, o conectivo lógico de conjunção é representado pelo
símbolo “&&”. Dito isso, a respeito do código abaixo, é correto afirmar que:

int main()
{
int x, z;

if ((x > 0) && (x == 0))


z = 0;
else
z = 1;
}

a) independentemente do valor atribuído a x, a variável z sempre receberá o


número 1.
b) a variável z receberá o valor 0, caso o número -1 seja atribuído à variável x.
c) a estrutura de seleção utilizada no código é inválida e, por este motivo, o código
não será compilado.
d) independentemente do valor atribuído a x, a variável z sempre receberá o
número 0.
e) a variável z receberá o valor 0, caso o número 0 seja atribuído à variável x.

64
ESTRUTURAS DE REPETIÇÃO UNIDADE

4.1 REPETIÇÃO COM TESTE NO INÍCIO


04
Em inúmeras situações da vida real, as pessoas precisam realizar,
repetidamente, uma mesma tarefa. Na computação, isso não é diferente. Por
exemplo, um programa que precise imprimir na tela uma contagem regressiva entre
dois números está, no final das contas, repetindo essa mesma tarefa para cada um
dos números que está sendo impresso. Embora a mensagem não seja exatamente
igual por conta do número impresso na tela, a natureza da tarefa, em si, é
praticamente a mesma. O código ilustrado na Figura 32 apresenta uma versão
desse programa de contagem regressiva.

Figura 32: Contagem regressiva

#include <stdio.h>

int main()
{
printf("Contagem regressiva: 5\n");
printf("Contagem regressiva: 4\n");
printf("Contagem regressiva: 3\n");
printf("Contagem regressiva: 2\n");
printf("Contagem regressiva: 1\n");
}
Fonte: Elaborado pelo Autor (2021)

O programa gerado a partir deste código-fonte irá imprimir 5 linhas, onde


cada uma dessas linhas corresponde a um número da contagem regressiva entre 5
e 1. Para esse caso específico em que a contagem regressiva possui apenas 5
números, a solução adotada no código-fonte parece totalmente razoável. Mas, se
fosse necessário fazer uma contagem regressiva com 1000, 10000 ou 1 milhão de
números, certamente ficaria inviável escrever uma linha para cada um dos números
compreendidos na sequência decrescente.
Para esse tipo de situação, as linguagens de programação provêem um
recurso chamado de estruturas de repetição que são parte fundamental da
programação de computadores. Juntamente com as estruturas de seleção, as

65
estruturas de repetição também fazem parte do conjunto de estruturas de controle
que estão presentes em grande parte das linguagens de programação. Enquanto
as estruturas de seleção são usadas para alterar o fluxo de execução de um
programa, as estruturas de repetição são empregadas para executar um conjunto
de instruções enquanto uma dada condição for verdadeira.
As estruturas de repetição podem ser divididas entre estruturas com teste no
início, teste no final e com variável de controle. Da mesma forma que acontece
com as estruturas de seleção, cada tipo de estrutura de repetição é mais
adequada para resolver um determinado tipo de problema.

A estrutura de repetição com teste no início tem a forma geral descrita na


Figura 33. Em linguagem natural, essa estrutura pode ser interpretada como
“enquanto a condição for verdadeira, repita as instruções contidas neste bloco”.
Portanto, o bloco de instruções dentro da estrutura vai ser continuamente repetido
enquanto a condição for verdadeira. Se a condição for falsa antes da primeira
iteração, então o bloco de instruções não será executado nenhuma vez.

Figura 33: Forma geral da estrutura de repetição com teste no início

Enquanto
<condição> faça
Bloco de
Fonte: Elaborado pelo Autor (2021)

Em C, essa estrutura é implementada usando-se a palavra reservada while


que, em língua portuguesa, pode ser traduzida como “enquanto”. O código em C,
descrito na Figura 34, exemplifica como utilizar esse tipo de estrutura de repetição

66
para implementar aquele mesmo exemplo de contagem regressiva.

Figura 34: Exemplo de estrutura de repetição com teste no início

#include <stdio.h>

int main()
{
int x = 10;
while (x != 0)
{
printf("Contagem regressiva: %d\n", x);
x--;
}
}
Fonte: Elaborado pelo Autor (2021)

Entre parênteses, logo após a palavra reservada while, vem a condição que
está sendo testada no início da estrutura de repetição. Portanto, enquanto o valor
da variável x for diferente de 0, as instruções contidas dentro do bloco de código
serão executadas repetidamente. Como a variável x é iniciada com o valor 10 e, a
cada execução da repetição, está sendo decrementada de 1 (operador “--”),
então, após 10 execuções, a variável possuirá o valor 0. Quando isso ocorrer, a
condição que está sendo testada pela estrutura de repetição não será mais
verdadeira, ocasionando o término da repetição.
Vale notar que a estrutura de repetição com teste no início é mais adequada
para as situações em que, caso a condição avaliada seja falsa, o bloco de
instruções não deve ser executado nenhuma vez.

4.2 REPETIÇÃO COM TESTE NO FINAL

Diferentemente dos casos indicados para o uso da estrutura de repetição


com teste no início, há situações em que o programa precisa executar o bloco de
instruções ao menos uma vez, independentemente do resultado da condição que
está sendo avaliada na estrutura de repetição.
Para este tipo de situação, é indicado o uso da estrutura de repetição com
teste no final. Nesse tipo de estrutura de repetição, o bloco de instruções é sempre
executado pelo menos uma vez, já que a condição é avaliada somente no final da
estrutura. A Figura 35 apresenta a forma geral dessa estrutura. Em linguagem

67
natural, essa estrutura poderia ser interpretada como “faça as instruções contidas
neste bloco, enquanto a condição for verdadeira”, indicando que a lógica
adotada nessa estrutura vai de encontro àquela empregada na estrutura de
repetição com teste no início.

Figura 35: Forma geral da estrutura de repetição com teste no final

Faça
Bloco de instruções
Enquanto <condição>

Fonte: Elaborado pelo Autor (2021

Na linguagem C, essa estrutura é implementada usando-se as palavras


reservadas do e while, como pode ser visto no código apresentado na Figura 36.

Figura 36: Exemplo de estrutura de repetição com teste no final

#include <stdio.h>

int main()
{
int operacoes = 5;
int operando = 2;
int cont = 1;
do
{
printf("Tabuada: %d x %d = %d\n", operando, cont, operando *
cont);
cont++;
}while (cont < operacoes);
}
Fonte: Elaborado pelo Autor (2021)

Esse código implementa uma tabuada de multiplicação, onde é possível


configurar o número de operações impressas, ao ajustar a variável operacoes, e
também o operando usado no cálculo das operações de acordo com o valor
informado na variável operando. Por exemplo, ao atribuir os valores 2 e 5 às variáveis
operando e operacoes, respectivamente, o programa imprimiria na tela um total de 5
operações de multiplicação envolvendo o número 2.
É interessante perceber que, mesmo se a variável operacoes fosse iniciada

68
com 1 ou, até mesmo com 0, o programa iria imprimir ao menos a primeira
operação da tabuada, ou seja, a operação “2 x 1” caso o operando fosse o
número 2. Isso acontece, pois o bloco de instruções seria executado no mínimo uma
vez, mesmo que a condição do laço já fosse falsa desde sua primeira iteração.
Talvez possa parecer que essa estrutura não tenha muita utilidade prática,
principalmente quando comparada com a estrutura de repetição com teste no
início. Ledo engano. Em certos casos, uma estrutura de repetição com teste no final
se encaixa perfeitamente na maneira como o programador implementou a sua
lógica de programação, evitando, assim, que ele faça alterações desnecessárias
no código para garantir que o bloco de instruções seja executado pelo menos uma
vez.

4.3 REPETIÇÃO COM VARIÁVEL DE CONTROLE

As estruturas de repetição com testes no início e no final possuem


características muito similares. Ambas são controladas por uma condição que é
avaliada a cada iteração do laço de repetição e, quando a condição não é mais
verdadeira, o laço é interrompido.
Além dessas duas estruturas, as linguagens de programação também
oferecem um terceiro tipo, a estrutura de repetição com variável de controle. Nessa
estrutura, uma variável auxiliar é usada para controlar a quantidade de iterações a
serem executadas pelo laço. A forma mais geral dessa estrutura está ilustrada na

69
Figura 37.

Figura 37: Forma geral da estrutura de repetição com variável de controle

Para <variavel> de <valor inicial> até <valor final> no passo


<passo> faça
Fonte: Elaborado pelo Autor (2021)

Essa estrutura difere bastante das estruturas vistas nas últimas duas seções. Em
linguagem natural, essa estrutura poderia ser lida como “Para o valor inicial de uma
variável de controle até um valor final, repita as instruções contidas neste bloco,
observando um determinado passo de iteração”. Essa estrutura é composta,
portanto, por 4 elementos, descritos a seguir:

● Variável: variável auxiliar usada para controlar o número de iterações a


serem executadas pelo laço de repetição.
● Valor inicial: valor que será inicialmente atribuído à variável de controle no
início da execução do laço de repetição.
● Valor final: quando a variável de controle atingir esse valor, o laço de
repetição se encerra.
● Passo: determina o passo da iteração realizada pelo laço de repetição.
Embora a possibilidade de alterar o passo de iteração seja necessário em
certas situações, pode-se afirmar que, na maioria das vezes, o passo de
iteração será igual 1.

Na linguagem C, a estrutura de repetição com variável de controle é um


pouco diferente da forma geral ilustrada na Figura 37. Na verdade, a versão
disponível em C é até mais flexível e poderosa. Para exemplificar, o código descrito
na Figura 38 utiliza essa estrutura para implementar o mesmo programa de tabuada
apresentado na seção anterior.

Figura 38: Exemplo de estrutura de repetição com variável de controle

#include <stdio.h>

int main()
{

70
int operacoes = 5;
int operando = 2;
int cont;
for (cont=1; cont<=operacoes; cont++)
{
printf("Tabuada: %d x %d = %d\n", operando, cont, operando *
cont);
}

}
Fonte: Elaborado pelo Autor (2021)

Como pode ser conferido no exemplo, esta estrutura é implementada em C


por meio da palavra reservada for, que poderia ser traduzida como “para” na
língua portuguesa. Essa cláusula é composta por 3 parâmetros que, de certa forma,
abrangem todos os elementos presentes na forma padrão dessa estrutura de
repetição.
No primeiro parâmetro, deve-se informar a variável que será usada para
controlar o laço e também o valor inicial que essa variável deverá assumir.
Já o segundo parâmetro é utilizado para indicar a condição de parada da
repetição. Em outras palavras, quando essa condição for alcançada, o laço é
interrompido. Essa condição de parada também pode ser interpretada como o
valor final que a variável de controle deve assumir para que o laço seja concluído.
De fato, nesse exemplo a repetição será encerrada quando a variável cont atingir o
valor de operacoes. Mas, por avaliar uma condição, essa estrutura em C permite
realizar outros tipos de repetição que vão além da funcionalidade original dessa
estrutura.
Por fim, no terceiro parâmetro, deve ser inserida a instrução que vai
determinar o passo de iteração do laço. Em geral, utiliza-se a variável de controle
com um operador de incremento, indicando que cada iteração será realizada em
1 passo. Em outros termos, isso significa que a variável de controle será
incrementada de 1 em 1. O passo pode ser alterado utilizando-se qualquer outro
operador abreviado de atribuição com operação aritmética como os operadores
“=+” e “=*”. Por exemplo, caso o laço precisasse avançar de 2 em 2, poderia-se
utilizar a instrução “cont+=2”.
Essa estrutura de repetição é mais adequada para as situações em que as
interações devem ocorrer de acordo com um determinado número de vezes. Esse é

71
o caso em que o programa precisa percorrer um vetor de certo tamanho, por
exemplo. Essa finalidade difere, dessa forma, das estruturas de repetição com testes
no início e no final, em que o objetivo é continuar repetindo as instruções.

72
FIXANDO O CONTEÚDO

1. Na linguagem C, a estrutura de repetição com teste no início é implementada


utilizando-se a palavra reservada while. Nesta estrutura, enquanto a condição for
verdadeira, o laço de repetição será continuamente executado. Sobre o
fragmento de código abaixo, é correto afirmar que o bloco de instruções dentro do
laço será executado

x = 1;
while (x != 0)
{
x--;
}

a) nenhuma vez.
b) três vezes.
c) apenas 1 vez.
d) duas vezes.
e) nunca.

2. Em uma estrutura de repetição com teste no início, a condição é sempre


avaliada antes da execução do bloco de instruções. Portanto, o bloco de
instruções só será executado enquanto essa condição for verdadeira.

Sobre o fragmento de código abaixo, escrito na linguagem C, é correto afirmar que


a condição do laço de repetição será avaliada

x = 1;
while (x != 0)
{
x--;
}

a) nenhuma vez.
b) três vezes.
c) apenas 1 vez.
d) duas vezes.
e) nunca.

73
3. Na linguagem C, a estrutura de repetição com teste no final é implementada
utilizando-se as palavras reservadas do e while. Nesta estrutura, o bloco de
instruções dentro do laço é executado enquanto a condição for verdadeira.
Portanto, mesmo que a condição seja falsa ainda na primeira iteração, o bloco de
instruções é sempre executado, no mínimo, uma vez.

Sobre o fragmento de código abaixo, é correto afirmar que o bloco de instruções


dentro do laço de repetição será executado

x = 1;
{
x--;
} while (x != 0)

a) nenhuma vez.
b) três vezes.
c) apenas 1 vez
d) duas vezes.
e) nunca.

4. Na estrutura de repetição com teste no final, a condição de repetição do laço é


testada após a execução do bloco de instruções. Dessa forma, mesmo que a
condição seja falsa, o bloco é executado, no mínimo, uma vez.

Sobre o fragmento de código abaixo, escrito na linguagem C, é correto afirmar que


a condição do laço de repetição será avaliada

x = 1;
{
x--;
} while (x != 0)

a) nenhuma vez.
b) três vezes.
c) apenas 1 vez.
d) duas vezes.
e) nunca.

74
5. Em C, a estrutura de repetição com variável de controle é implementada
utilizando-se a palavra reservada for. Nesta estrutura, o bloco de instruções dentro
do laço é executado até que uma determinada condição seja satisfeita. Quando
a condição é alcançada, o laço é interrompido.

A respeito do fragmento de código abaixo, é correto afirmar que:

for (x=1; x<10; x++)


{
printf(“teste\n”);
}

a) a condição será avaliada 10 vezes, enquanto que o bloco de instruções será


executado 9 vezes.
b) a condição será avaliada 9 vezes, enquanto que o bloco de instruções será
executado 9 vezes.
c) a condição será avaliada 9 vezes, enquanto que o bloco de instruções será
executado 10 vezes.
d) a condição será avaliada 10 vezes, enquanto que o bloco de instruções será
executado 10 vezes.
e) nem a condição será avaliada e nem o bloco de instrução será executado
nenhuma vez.

6. Em Python, a estrutura de repetição com teste no início é implementada


utilizando-se a palavra reservada while. A cada iteração, a estrutura avalia uma
condição para determinar se uma nova iteração deverá ser executada.
Diferentemente de C, em que o bloco de instruções a ser repetido é delimitado por
chaves, em Python essa delimitação é feita a partir de indentação. Portanto, todo o
código aninhado a cláusula while é entendido como parte do bloco de instruções
contido no laço de repetição.

Considerando essas informações e a respeito do fragmento de código abaixo, é


correto afirmar que:

cont = 0
while (cont < 5):
print(“teste”)
cont = 0

75
a) esse laço de repetição será executado uma única vez.
b) a mensagem “teste” será impressa apenas 4 vezes.
c) a mensagem “teste” será impressa apenas 5 vezes.
d) esse laço de repetição nem será executado, visto que a condição já é falsa
desde a primeira iteração.
e) esse laço de repetição será executado continuamente, já que a condição de
parada nunca se tornará falsa.

7. A linguagem C oferece três estruturas de repetição que, assim como as estruturas


de seleção, podem ser aninhadas, isto é, podem ser utilizadas de forma
encadeada uma dentro da outra.

Considerando essa informação e a respeito do fragmento de código abaixo, é


correto afirmar que a palavra “teste” será impressa

cont = 0;
while(cont < 5)
{
for(x=cont; x<5; x++)
{
printf(“teste”);
}
cont += 2;
}

a) nenhuma vez.
b) mais de 5 vezes.
c) apenas 1 vez.
d) mais de 20 vezes.
e) até 3 vezes.

8. A linguagem Object Pascal suporta uma estrutura de repetição com teste no final
conhecida como “Repita até que”. Essa estrutura repete uma sequência de
comandos até que uma dada condição seja verdadeira. Portanto, o laço segue
repetindo um bloco de instruções enquanto a condição for falsa. Esta estrutura é
implementada por meio das palavras reservadas repeat e until que significam,
respectivamente, “repita” e “até que” na língua portuguesa.

76
O trecho de código abaixo, escrito em Object Pascal, utiliza um laço “Repita até
que” para imprimir a palavra “teste” na tela.

cont := 2;
repeat
write('Teste');
cont := cont - 1;
until cont = 0;

Sabendo que os símbolos “:=” e “=” são, respectivamente, operadores de


atribuição e igualdade, é correto afirmar que a palavra teste será impressa na tela

a) nenhuma vez.
b) uma única vez.
c) menos do que 2 vezes.
d) mais do que 2 vezes.
e) exatamente 2 vezes.

77
MODULARIZAÇÃO UNIDADE

5.1 MODULARIZAÇÃO DE CÓDIGO


05
Os primeiros programas feitos por um estudante de programação, quando
muito, não ultrapassam nem 100 linhas de código-fonte. Afinal, são programas
básicos criados para resolver problemas bem simples. Em programas reais, contudo,
a situação é bem diferente. Não é difícil encontrar um programa real que tenha
centenas ou até milhares de linhas de código. O grande problema é que, quanto
maior for esse código-fonte, mais complexa se torna a tarefa de realizar alguma
modificação no programa, seja para corrigir algum erro, seja para adicionar novas
funcionalidades.
Para amenizar esse problema, os programadores utilizam o conceito de
modularização de código-fonte que nada mais é do que dividir o código em
diferentes partes, módulos. Sendo assim, a interpretação e alteração do código
pode ser feita de uma forma muito mais simples, já que o código-fonte está
organizado e estruturado em porções funcionais e autocontidas de código.
Portanto, caso um programa de vendas esteja apresentando um erro no cálculo de
desconto, por exemplo, o programador poderá começar a investigar o problema
pelo módulo responsável por calcular o preço final de venda. Do contrário, em um
código não modularizado, certamente o programador precisaria percorrer grande
parte do código-fonte a fim de encontrar o trecho utilizado para executar tal
funcionalidade.
Como ilustrado na Figura 39, um código que não é construído a partir de
módulos é conhecido como código-fonte monolítico, em referência a palavra
monólito que faz menção à algo formado a partir de uma única rocha, estrutura ou
bloco. Por outro lado, um código modularizado, ou simplesmente código modular, é
formado a partir da junção de vários módulos ou partes distintas.

78
Figura 39: Códigos monolítico e modular

Fonte: Elaborado pelo Autor (2021)

Como citado anteriormente, um dos benefícios de se modularizar um código


é a facilidade de se encontrar uma determinada funcionalidade, visto que o
código está melhor organizado e estruturado. Mas, além disso, os programadores
modularizam seus códigos também por uma outra razão: reuso. Sim, um programa
geralmente precisa executar uma mesma funcionalidade em diferentes partes do
código. Então, em vez de escrever vários códigos distintos para realizar a mesma
tarefa, é muito melhor criar um módulo capaz de executar tal atividade, permitindo
que esse artefato de software seja reutilizado ao longo de todo o código-fonte.
Além dessa vantagem de não desperdiçar energia ao escrever vários trechos
de código que executam a mesma atividade, a modularização também facilita a
manutenção do código-fonte. De fato, se for necessário realizar algum ajuste em
alguma funcionalidade, basta alterar apenas o módulo que implementa essa
funcionalidade para que essa modificação seja replicada a todos os pontos do
código que estejam usando esse módulo.
Em resumo, a modularização confere organização, elegância, simplicidade e
manutenibilidade ao código, facilitando, assim, as tarefas de modificação e
implementação de novas funcionalidades.

5.2 FUNÇÕES

79
A seção anterior mostrou algumas das vantagens de se aplicar o conceito de
modularização em um código-fonte. Todos aqueles benefícios são praticamente os
mesmos, a despeito da linguagem de programação utilizada para criar o
programa. Por outro lado, a maneira como a modularização é feita vai depender
do tipo e também das características da linguagem de programação.
Em linguagens orientadas a objeto, por exemplo, uma forma de modularizar
o código é por meio da definição de objetos que são constituídos por métodos e
propriedades. Dessa forma, os objetos podem ser usados para representar
diferentes módulos responsáveis por executar determinadas tarefas ou prover certas
funcionalidades. Já em outras linguagens, como em NesC - linguagem usada para
programar Redes de Sensores sem Fio (RSSF), a modularização pode ser feita por
meio de componentes.

Mas, em grande parte das linguagens de programação, a modularização é


implementada utilizando-se funções. Fundamentalmente, uma função é um
artefato de software que produz uma saída de acordo com uma determinada
entrada. Em outras palavras, a função processa os dados de entrada e, como
saída, retorna o resultado desse processamento.

80
A Figura 40 apresenta um exemplo bem simples de função em que o seu
processamento consiste em calcular o dobro de um número inteiro. Portanto, ao
receber o número 15 como entrada, a função retorna o dobro desse valor, isto é, o
número 30. Os dados de entrada recebidos por uma função costumam ser
referenciados como parâmetros de entrada, enquanto que o valor de saída é mais
frequentemente conhecido pelos termos retorno ou resultado da função.

Figura 40: Exemplo de função

Fonte: Elaborado pelo Autor (2021)

A Figura 40 apresenta a sintaxe geral para criação de uma função em C.


Como destacado na figura, a definição de uma função nessa linguagem pode
envolver cinco fatores principais, descritos a seguir:
● Nome da função (A). Da mesma forma que acontece com variáveis e
constantes, a função também precisa ser identificada por um nome. O ideal
é que esse nome remeta à tarefa ou tipo de atividade que é executada pela
função.

81
● Tipo do valor de retorno (B). Na definição da função, é preciso indicar o tipo
do valor de retorno. No exemplo, a função dobro irá retornar um valor que é
do tipo primitivo int.
● Parâmetros de entrada (C). Entre parênteses, logo após o nome da função,
deve-se indicar os parâmetros de entrada que serão recebidos pela função
quando esta for executada.
● Corpo da função (D). Bloco de código que será executado quando a função
for chamada ou evocada.
● Retorno da função (E). O retorno da função é realizado pela palavra
reservada return.
Nesse exemplo, a função dobro recebe apenas um número inteiro como
parâmetro de entrada, identificado como numero. Após calcular o dobro desse
número, o resultado desse processamento é armazenado na variável valor. Por fim,
a palavra reservada return é usada para indicar o valor que será retornado pela
função.

Figura 41: Forma geral para criação de função em C

C
A

B
int dobro(int numero)
{
int valor;
valor = numero * 2; D
return valor;
}

Fonte: Elaborado pelo Autor (2021)

A Figura 42 mostra como essa função pode ser chamada ou evocada no


código principal de um programa. Como pode ser visto na linha destacada em
verde, a função dobro é chamada tendo a variável parametro como parâmetro de
entrada. Já que essa variável está armazenando o número 8 no momento da

82
chamada da função, então a variável resultado será preenchida com o dobro
desse valor, isto é, com o número 16.

Figura 42: Chamada de função

int dobro(int numero)


{
int valor;
valor = numero * 2;
return valor;
}

int main()
{
int resultado;
int parametro = 8;
resultado = dobro(parametro);
}
Fonte: Elaborado pelo Autor (2021)

É interessante lembrar que essa função pode ser evocada quantas vezes
forem necessárias. Afinal, o reuso é justamente um dos principais motivos para se
criar uma função. Além disso, caso fosse preciso realizar algum ajuste no cálculo
dessa função, bastaria alterar o seu bloco de código para que essa modificação
fosse aplicada a todas as partes do programa que evocam essa função.

83
5.3 TIPOS DE PASSAGEM DE PARÂMETRO

Em grande parte das linguagens de programação, a passagem de


parâmetros para uma função pode ser feita de duas formas: por valor ou
referência.
Na passagem de parâmetro por valor, a função recebe, de fato, apenas o
valor daquele parâmetro que está sendo indicado em sua chamada. Dessa forma,
qualquer alteração realizada no parâmetro de entrada não terá nenhum efeito ao
término da execução da função. Em outras palavras, as modificações realizadas
nos parâmetros de entrada, não alteram o conteúdo da variável que foi passada
por parâmetro na chamada da função no programa principal.
Isso acontece, pois o parâmetro de entrada da função é uma variável
independente, isto é, não possui o mesmo endereço de memória da variável
passada como parâmetro no momento em que a função é chamada. São
elementos completamente distintos.
Para exemplificar, a Figura 43 traz um trecho de código que implementa uma
função chamada teste. Essa função recebe como parâmetro de entrada um
número inteiro identificado como x. Após incrementar o valor de x em 1, a função
retorna esse valor atualizado como resultado do seu processamento. Deve-se notar
que, para realizar esse incremento, a função não utiliza nenhuma variável auxiliar, já
que o próprio parâmetro de entrada é utilizado para efetuar esse cômputo.

Figura 43: Exemplo de função escrita em C

int teste(int x)
{
x = x + 1;
return x;
}

84
int main()
{
int parametro = 5;
int resultado;
resultado = teste(parametro);
printf("variável parametro: %d\n", parametro);
printf("variável resultado: %d\n", resultado);
}
Fonte: Elaborado pelo Autor (2021)

Então, mesmo que o parâmetro x esteja sendo alterado dentro da função,


essa modificação não será refletida na variável parametro, visto que a função teste
está implementando uma passagem de parâmetro por valor. De fato, após a
execução da função, a variável parametro continuará contendo o valor 5, mesmo
que, dentro de teste, o parâmetro x tenha sido alterado para 6.
Por outro lado, caso a função teste tivesse sido implementada utilizando
uma passagem de parâmetro por referência, o seu comportamento seria
completamente diferente. Neste caso, qualquer alteração realizada no parâmetro
de entrada seria refletida quando a função fosse concluída. Esse comportamento
ocorre devido a forma como os parâmetros de entrada são passados para a
função.
Na passagem de parâmetro por referência, a função não recebe o valor da
variável, mas sim o seu endereço de memória. Então, como tanto a variável
passada por parâmetro quanto o próprio identificador do parâmetro de entrada
estão apontando para a mesma posição de memória, qualquer modificação
realizada no parâmetro afeta diretamente a variável no programa principal. Afinal,
trata-se exatamente do mesmo endereço de memória.
O código ilustrado na Figura 44 mostra a implementação da função teste
utilizando, nesta versão, o conceito de passagem de parâmetro por referência. O
oprador “*” é utilizado na declaração do parâmetro de entrada x para indicar que
o valor a ser recebido pela função não é um número inteiro, mas sim um endereço
de memória.
Como visto na unidade de ponteiros, esse mesmo operador é também
utilizado para acessar o conteúdo armazenado em uma dada posição indicada
por um endereço de memória. É por essa razão que o incremento (e retorno) é feito
utilizando-se esse operador unitário à frente da variável x.

85
Figura 44: Exemplo de passagem de parâmetro por referência

int teste(int *x)


{
*x = *x + 1;
return *x;
}

int main()
{
int parametro = 5;
int resultado;
resultado = teste(&parametro);
printf("variável parametro: %d\n", parametro);
printf("variável resultado: %d\n", resultado);
}

Fonte: Elaborado pelo Autor (2021)

Ao final da execução desse programa, não só a variável resultado possuirá


o número 6, mas também a variável parametro que teve o seu conteúdo
indiretamente alterado dentro da função teste.
Neste ponto, cabe uma reflexão. A passagem de parâmetro por referência
também pode ser usada como um recurso adicional para retornar outros valores de
uma função, além do seu próprio retorno.

86
87
FIXANDO O CONTEÚDO

1. A modularização é uma prática em que o programador pode estruturar melhor o


seu código-fonte a fim de se beneficiar de vantagens como organização e
possibilidade de reuso. Considerando o trecho de código em C abaixo, é correto
afirmar que a função soma

int soma(int a, int b)


{
return a + b;
}

a) possui apenas um parâmetro de entrada.


b) implementa a passagem de parâmetro por referência.
c) retorna um vetor de caracteres.
d) implementa a passagem de parâmetro por valor.
e) possui três parâmetros no total.

2. Em programas reais, é comum implementar funções que evocam outras funções.


O mesmo pode ser feito diretamente em apenas uma linha de comando, em que o
resultado de uma função torna-se o parâmetro de entrada para uma outra. Dito
isso, qual seria o valor variável resultado ao final da execução desse código:

int soma(int n1, int n2)


{
return n1 + n2;
}

int main()
{
int a = 5;
int b = 2;
int resultado;
resultado = soma(soma(a,b),soma(a,b));
}

a) 7.
b) 10.
c) 8.
d) 5.

88
e) 14.

3. Na linguagem C, os parâmetros de entrada em uma função são considerados


posicionais. Isso quer dizer que o valor indicado no primeiro argumento da função
será associado ao primeiro parâmetro de entrada, o segundo argumento,
associado ao segundo parâmetro de entrada, e assim sucessivamente.
Considerando essa afirmação e de acordo com o código fonte abaixo, é correto
afirmar que a seguinte mensagem será impressa na tela

int imprimir(int a, int b, int c)


{
printf(“%d %d %d\n”, a, b, c);
}

int main()
{
int a = 3;
int b = 5;
int c = 0;
imprimir(b,c,a);
}

a) 0 3 5.
b) 5 0 3.
c) 0 5 3.
d) 3 5 0.
e) 5 3 0.

4. Em algumas linguagens de programação, como em Pascal, as funções que não


devolvem nenhum valor de saída são chamadas de procedimentos. Já em outras
linguagens como C ou Python, não há nenhuma diferenciação formal na
linguagem para distinguir a definição de procedimentos e funções. Considerando
essas informações e de acordo com o código-fonte em C abaixo, é correto dizer
que:

int decremento(int a)
{
return a - 1;
}

a) a função decremento, se fosse implementada em Python, precisaria utilizar uma

89
sintaxe própria para ser definida, pois, na terminologia dessa linguagem, esse
módulo é um procedimento.
b) a função decremento, se fosse implementada em Pascal, precisaria utilizar uma
sintaxe própria para ser definida, pois, na terminologia dessa linguagem, esse
módulo é um procedimento.
c) a função decremento, caso fosse implementada em Python, poderia usar a
sintaxe para definição de procedimento, desde que permaneça recebendo
apenas um parâmetro de entrada.
d) a função decremento, caso fosse implementada em Pascal, poderia usar tanto a
sintaxe para função quanto para procedimento.
e) a função decremento, caso fosse implementada em Pascal, não poderia usar a
sintaxe para definição de procedimento, pois há retorno de valor.

5. O código abaixo implementa uma função em C que utiliza ambos os tipos de


passagem de parâmetro suportados pela linguagem. A respeito desse trecho de
código, é correto afirmar que, ao término da execução, os valores das variáveis z e
y serão, respectivamente

int decremento(int a, int *b)


{
*b = a - 1;
return *b;
}

int main()
{
int x = 10;
int y = 15;
int z;
z = decremento(x,&y);
}

a) 15 e 15.
b) 10 e 10.
c) 9 e 9.
d) 9 e 15.
e) 10 e 9.

6. Em geral, uma função é utilizada para realizar um certo processamento em um

90
conjunto de dados de entrada a fim de produzir um determinado valor de saída.
Na prática, porém, as linguagens de programação permitem definir funções que
não recebem nenhum parâmetro de entrada e tampouco fornecem um valor de
retorno. Em C, por exemplo, a definição dessa função poderia ser feita como
mostrado abaixo. O tipo de dado void é utilizado em C para denotar que uma
variável ou função é vazia, ou seja, não armazena (ou retorna - no caso das
funções) nenhuma informação.

void funcao()
{
/*bloco de código*/
}

Levando em conta as afirmações acima, é correto afirmar que:

a) uma função desse tipo, embora não retorne nenhum valor, pode ser usada para
modularizar trechos de código extensos e frequentemente executados a fim de
tornar o código-fonte mais legível e organizado.
b) uma função desse tipo não tem nenhuma aplicabilidade prática, nem sequer
para organizar ou estruturar melhor o código-fonte.
c) não há nenhuma linguagem de programação que permita criar uma função
que não retorne nenhum valor e que não receba nenhum parâmetro de entrada.
d) há linguagens de programação que até permitem criar uma função que não
retorne nenhum valor, desde que recebam ao menos um parâmetro de entrada.
e) no contexto de linguagens de programação, uma função que não retorna
nenhum valor e que não recebe nenhum parâmetro de entrada, não é, de fato,
uma função.

7. Por meio da passagem de parâmetro por referência, uma função torna-se capaz
de retornar valores além do próprio retorno padrão da função. O trecho de código
abaixo implementa a função soma_ao_dobro que retorna em sua saída padrão a
soma entre dois números inteiros. Mas, adicionalmente, a função também utiliza a
passagem de parâmetro por referência para retornar o dobro dessa soma.

int soma_ao_dobro(int a, int b, _______)


{
int soma = a + b;

91
int dobro = soma * 2;
*c = dobro;
return a + b;
}

int main()
{
int x = 10;
int y = 15;
int w;
int z;
z = soma_ao_dobro(x,y,&w);
}

Dito isso e considerando o código acima, o que deve ser preenchido na lacuna dos
parâmetros de entrada da função soma_ao_dobro para que a variável w, no
programa principal, receba o dobro da soma entre x e y:

a) int w
b) int *w
c) int c
d) int *c
e) int *b

8. Indique a opção abaixo que apresenta a implementação de uma função em C


que retorna, via passagem de parâmetro por referência, o quadrado de um
número inteiro passado para o parâmetro de entrada valor:

92
93
MANIPULAÇÃO DE ARQUIVOS UNIDADE

6.1 ARMANEZAMENTO DE DADOS


06
Essencialmente, os programas são criados para resolver problemas ou realizar
tarefas. Para isso, os programas precisam ser capazes de obter dados de entrada,
além de armazenar os dados de saída. No final das contas, a própria computação
em si se resume a esse constante movimento de ler, processar e armazenar
informações.
Uma das formas de se armazenar os dados gerados por um programa é por
meio de um banco de dados. Os bancos de dados podem ser definidos como
softwares que têm a função de persistir dados de forma que sua recuperação,
atualização e inserção sejam efetuadas de maneira segura, confiável e facilitada.
Por ser um assunto extenso, há toda uma área da computação inteiramente
dedicada a estudar e aprimorar os métodos e técnicas utilizados nos sistemas
gerenciadores de banco de dados.
Além dos bancos de dados, os programas também podem persistir suas
informações em arquivos que são armazenados em discos magnéticos, ópticos ou
de estado sólido. O gerenciamento dos arquivos é realizado pelo sistema
operacional que trata as requisições enviadas pelos programas que estão em
execução.
Cada arquivo possui um conjunto de propriedades como data e hora da sua
criação, permissões de acesso e quantidade de bytes armazenados. Essas
informações são utilizadas pelo sistema operacional para gerenciar toda sorte de
ações que envolvam os arquivos como, por exemplo, determinar se um arquivo
pode ou não ser acessado por um dado programa ou usuário.
Além disso, o sistema operacional também utiliza essas informações para
gerenciar o acesso concorrente aos arquivos, isto é, quando mais de um programa
ou usuário tentam acessar, ao mesmo tempo, um arquivo. O acesso simultâneo
para realizar leitura de dados não incorre em nenhum problema. A questão se
complica quando há duas ou mais solicitações simultâneas para editar o arquivo,
ou seja, para escrever dados em seu conteúdo. Para que os dados do arquivo não

94
sejam corrompidos, o sistema operacional libera o acesso para edição a apenas
um programa por vez. Do contrário, caso não houvesse esse controle, as
informações gravadas por um programa seriam sobrescritas por um outro,
causando perda de dados.
Em resumo, os arquivos são parte fundamental dos sistemas computacionais
e, além de serem usados para gravar as informações geradas pelos programas, são
também usados para armazenar suas informações de configuração e controle,
dados que são necessários para o correto funcionamento do software. Sendo assim,
é imprescindível que um programador saiba como escrever programas capazes de
manipular dados em arquivos.

6.2 ABERTURA E FECHAMENTO DE ARQUIVOS

Como descrito na seção anterior, o gerenciamento de arquivos é realizado


pelo sistema operacional. É por essa razão que, antes de ler ou gravar dados em
um arquivo, um programa deve solicitar ao sistema operacional a permissão para
acessá-lo. Isso é realizado por meio de um processo denominado como abertura de
arquivo.
Analogamente, esse processo pode ser comparado à abertura daquelas
gavetas físicas de metal que guardam várias fichas, pastas ou arquivos. Para se ter
acesso aos documentos dentro dessas gavetas é necessário, primeiro, abri-las. Em
suma, o processo de abertura permite que o sistema operacional exerça o controle
e gerenciamento adequado dos arquivos.
Sendo assim, antes de realizar a leitura ou gravação de dados, é
necessário abrir o arquivo. É o primeiro passo. Cada linguagem de programação

95
possui uma forma muito particular e específica para realizar esse processo. Na
linguagem C, a abertura de um arquivo pode ser feita como ilustrado na Figura 45.

Figura 44: Abertura de arquivo

#include <stdio.h>
#include <stdlib.h>

int main()
{
FILE *arquivo;

arquivo = fopen("teste.txt", "r");

}
Fonte: Elaborado pelo Autor (2021)

Para abrir um arquivo em C deve-se, primeiramente, declarar uma variável


ponteiro do tipo especial FILE que é usada para armazenar o descritor de arquivo.
Em linhas gerais, o descritor de arquivo é um número inteiro usado para identificar o
arquivo que está sendo manipulado. Após, basta chamar a função fopen que irá
retornar o descritor do arquivo, caso a abertura tenha sido bem sucedida, ou então
o valor NULL, em caso contrário.
É de praxe utilizar essas informações de retorno da função fopen para
verificar se a abertura do arquivo foi realmente feita com sucesso antes de
prosseguir para as etapas de leitura e gravação de dados. De fato, se o arquivo
não foi aberto com sucesso, todas as operações de acesso ao seu conteúdo
resultarão em um erro fatal, podendo causar o encerramento abrupto do
programa.
A função fopen deve ser chamada com dois parâmetros de entrada. No
primeiro, deve-se informar o nome do arquivo a ser aberto, enquanto que, no
segundo, deve ser indicado o modo de abertura do arquivo. O modo de abertura é
utilizado para informar ao sistema operacional o tipo de operação que o programa
deseja realizar e também para indicar a posição em que o cursor deverá estar
quando o arquivo for aberto. O Quadro 4 lista algumas das opções de modo de
abertura disponíveis em C.

96
Quadro 4: Exemplo de código com uma enumeração

Modo Descrição Posição do Cursor

“r” Abre um arquivo texto para leitura. Início do arquivo.

“r+” Abre um arquivo texto para leitura e gravação. Início do arquivo.

Abre um arquivo para gravação. Caso o arquivo


“w” exista, apaga todos os dados e, se não existir, Início do arquivo.
cria um novo.

Abre um arquivo para leitura e gravação. Caso


“w+” o arquivo exista, apaga todos os dados e, se Início do arquivo.
não existir, cria um novo.

Abre um arquivo para adição (escrita no final do


“a” Final do arquivo.
arquivo).

Para leitura, o cursor é


Abre um arquivo para leitura e adição (escrita
“a+” posicionado no início. Para
no final do arquivo).
gravação, no final.
Fonte: Elaborado pelo Autor (2021)

Para realizar apenas a leitura de um arquivo, o modo mais indicado é o “r”.


Mas, se o programa precisa realizar a leitura e gravação de dados, então a função
fopen deve ser chamada com o modo de abertura “r+”. Os modos “w” e “w+”
também permitem abrir um arquivo no modo de edição. Contudo, ao utilizar essas
opções, o conteúdo do arquivo é completamente apagado no momento em que
a função fopen é executada.
Em todos esses modos descritos até esse ponto, o cursor é sempre
posicionado no início do arquivo. Para posicionar o cursor no final do arquivo, deve-
se utilizar os modos “a” e “a+”, onde o primeiro abre um arquivo apenas para
adição (escrita no final do arquivo) e o segundo permite que o programa realize
tanto a leitura quanto a adição no arquivo. Um detalhe interessante é que, no
modo “a+”, o cursor é posicionado sempre no início para operações de leitura e no
final para operações de gravação.

97
Após a abertura bem-sucedida do arquivo, o programa já pode ler e
escrever dados em seu conteúdo. Mas, para que o sistema operacional possa
executar adequadamente seu trabalho de gerenciamento, o programa deve
realizar uma operação para indicar que não está mais acessando aquele arquivo.
Trata-se do processo de fechamento de arquivo. Enquanto a abertura é o primeiro
passo a ser realizado na manipulação de arquivos, o fechamento é o último.
O código ilustrado na figura abaixo mostra como realizar esse processo na
linguagem C. Sem dúvida, fechar um arquivo é bem mais simples do que efetuar a
sua abertura.

Figura 45: Fechamento de arquivo

#include <stdio.h>
#include <stdlib.h>

int main()
{
FILE *arquivo;

98
arquivo = fopen("teste.txt", "r");

fclose(arquivo);
}
Fonte: Elaborado pelo Autor (2021)

Para fechar um arquivo em C, basta chamar a função fclose, passando


como parâmetro de entrada o descritor de arquivo. Assim como no caso da
abertura de arquivo, é interessante também avaliar o retorno da função fclose a
fim de verificar se o fechamento foi feito com sucesso. Dessa forma, pode-se evitar
vários problemas inesperados e de difícil depuração.
Em linguagens como Python, há um recurso que garante o fechamento
implícito do arquivo após o programa realizar a leitura ou escrita em seu conteúdo.
Mas, em grande parte das linguagens usadas atualmente, é necessário que o
programador se atente sempre ao fechamento do arquivo.

6.3 LEITURA E ESCRITA EM ARQUIVOS

Após abrir o arquivo, o programa já pode realizar operações de leitura e


escrita em seu conteúdo. Em C, há mais de uma forma de ler os dados de um
arquivo. Por exemplo, para ler apenas 1 caractere de um arquivo texto, pode-se
utilizar a função fgetc como destacado em verde no código ilustrado na Figura 47.

Figura 47: Leitura de um caractere de um arquivo texto

#include <stdio.h>
#include <stdlib.h>

int main()

99
{
FILE *arquivo;
char caractere;

arquivo = fopen("teste.txt", "r");

if (arquivo == NULL)
exit(1);

caractere = fgetc(arquivo);

fclose(arquivo);
}
Fonte: Elaborado pelo Autor (2021)

A função fgetc recebe como parâmetro o descritor do arquivo na qual a


operação deve ser realizada e retorna como resultado o primeiro caractere
presente nesse arquivo.
Para ler todos os caracteres de um arquivo texto, a função fgetc também
pode ser usada, mas, para isso, deve ser executada continuamente até que o
programa alcance o final do arquivo. Para determinar se não há mais nenhuma
informação a ser lida no arquivo, a linguagem C emprega uma constante
denominada EOF (abreviação de End of File - Fim de Arquivo) que é retornada pela
função fgetc para indicar que todos os dados já foram lidos. A Figura 48 mostra
como realizar a leitura e impressão de todos os caracteres contidos em um arquivo
texto.

Figura 48: Leitura de vários caracteres de um arquivo texto

#include <stdio.h>
#include <stdlib.h>

int main()
{
FILE *arquivo;
char caractere;

arquivo = fopen("teste.txt", "r");

if (arquivo == NULL)
exit(1);

while ((caractere=fgetc(arquivo))!=EOF)
putchar(caractere);

fclose(arquivo);

100
}
Fonte: Elaborado pelo Autor (2021)

As linhas destacadas em verde mostram como utilizar um laço de repetição


com teste no início para iterar sobre todos os caracteres do arquivo. A repetição é
encerrada quando a função fgetc retorna um valor igual a EOF, situação que
ocorre quando não há mais nenhum dado a ser consumido do arquivo.

Uma outra forma de ler dados de um arquivo texto consiste em obter, de


uma só vez, uma determinada quantidade de caracteres. Esse processo é
realizado por meio da função fgets como pode ser visto na Figura 49.

Figura 49: Leitura de um conjunto de caracteres de um arquivo texto

#include <stdio.h>
#include <stdlib.h>

int main()
{
FILE *arquivo;
char string[30];

arquivo = fopen("teste.txt", "r");

if (arquivo == NULL)
exit(1);

while (fgets(string, 30, arquivo))!=EOF)


puts(string);

fclose(arquivo);
}
Fonte: Elaborado pelo Autor (2021)

A função fgets recebe três parâmetros de entrada, a saber: vetor de

101
caracteres que irá receber os dados lidos, a quantidade de caracteres que serão
lidos por vez e o descritor do arquivo na qual a operação será realizada.
Diferentemente de fgetc, a função retorna NULL quando não há mais nenhum
dado a ser lido no arquivo. A função realiza a leitura do arquivo obtendo a
quantidade de caracteres indicada no seu segundo parâmetro e armazenando
esses dados no vetor indicado no primeiro parâmetro de entrada.
Há um porém. Caso a função encontre um indicador de final de linha, os
caracteres dessa linha serão imediatamente retornados, a despeito da quantidade
de caracteres que foi solicitada à função. Por exemplo, se a função está realizando
a leitura de 10 caracteres por vez e, na primeira linha do arquivo há apenas 2
caracteres, então a função fgets vai retornar somente esses 2 caracteres. Por outro
lado, se uma linha possui 40 caracteres, então a função, nesse exemplo, iria retornar
somente os 10 primeiros caracteres dessa linha.
No exemplo descrito na Figura 49, a função fgets está sendo usada
para obter 30 caracteres por vez. Como dito anteriormente, essa função retorna
NULL quando não há mais nenhum dado a ser lido. Por essa razão, o laço de
repetição continua sua iteração enquanto o valor retornado pela função for
diferente de NULL. A cada iteração o programa utiliza a função puts para imprimir
na tela os caracteres lidos do arquivo.
A escrita de 1 caractere por vez segue a mesma lógica da leitura. Para
escrever 1 caractere por vez, pode-se utilizar a função fputc que é a contraparte
de fgetc. O funcionamento é praticamente o mesmo, com exceção do retorno da
função. Quando a operação de escrita é realizada com sucesso, a função fputc
retorna o próprio caractere que foi escrito. Mas, em caso de falha, essa função
retorna a constante EOF.
Já o processo de escrita de uma cadeia de caracteres muda um pouco em
relação à leitura. A Figura 50 ilustra como utilizar a função fputs para escrever um
vetor de caracteres em um arquivo texto.

Figura 50: Escrita de um conjunto de caracteres em um arquivo texto

#include <stdio.h>
#include <stdlib.h>

int main()
{
FILE *arquivo;

102
char string[30] = “escrevendo algo no arquivo”;

arquivo = fopen("teste.txt", "r+");

if (arquivo == NULL)
exit(1);

fputs(string, arquivo);

fclose(arquivo);
}
Fonte: Elaborado pelo Autor (2021)

A função fputs recebe dois parâmetros de entrada. No primeiro parâmetro


deve-se passar o vetor de caracteres a ser escrito no arquivo e, no segundo, o
descritor de arquivo na qual a operação será realizada. Essa função retorna um
número não negativo em caso de sucesso e, do contrário, retorna a constante EOF.
Uma questão a ser observada no código da Figura 49 é a utilização do modo
de abertura “r+” na função fopen a fim de indicar que o arquivo deve ser aberto
para leitura e gravação. Se a abertura tivesse sido feita com o parâmetro “r”, a
função fputs teria retornado um erro, já que não teria permissão para realizar
alterações no arquivo.

103
FIXANDO O CONTEÚDO

1. Suponha que um programa codificado na linguagem C precise escrever, no final


de um arquivo texto, a letra “x”. Considerando que os dados atuais do arquivo
devem ser mantidos, é correto afirmar que o seguinte modo de abertura seria o
mais adequado para ser utilizado nesse caso:

a) “a”
b) “x”
c) “w”
d) “w+”
e) “r+”

2. Suponha que um programa codificado na linguagem C precise escrever, no


início de um arquivo texto, a letra “z”. Considerando que os dados atuais do arquivo
não precisam ser mantidos, é correto afirmar que o seguinte modo de abertura seria
o mais adequado para ser utilizado nesse caso:

a) “r”
b) “x”
c) “w”
d) “z+”
e) “r+”

3. A função fgets obtém, de uma única vez, um determinado número de caracteres


contidos dentro de um arquivo texto. No entanto, caso a função encontre um
indicador de final de linha, todos os caracteres dessa linha são imediatamente
retornados, mesmo que essa quantidade de caracteres seja menor do que a
solicitada inicialmente. Lembrando que, no contexto de linguagens de
programação, os espaços entre as letras também são considerados como sendo
caracteres.

104
Considerando as informações acima e tomando como exemplo o seguinte arquivo
texto contendo 3 linhas, é correto afirmar que a função fgets, ao ser chamada com
o parâmetro de 20 caracteres e logo após a abertura do arquivo, retornaria:

Este é um teste de leitura de arquivo


A linguagem C pode ler e escrever em arquivos
Todo programador deve saber manipular arquivos

a) este é um teste de leitura de arquivo.


b) este é um teste de l.
c) todo programador deve saber manipular arquivos.
d) todo programador deve saber manipulador.
e) a linguagem C.

4. Em C, a função fgetc é utilizada para ler apenas 1 caractere por vez. Quando
não há mais nenhum dado a ser lido, a função retorna a constante EOF. Supondo
que seja necessário ler um arquivo contendo 100 caracteres, é correto afirmar que:

a) não há nenhuma forma de utilizar a função fgetc para realizar essa leitura.
b) a função fgetc pode até ser utilizada nesse tipo de operação, mas desde que o
arquivo não contenha mais do que 200 caracteres.
c) a função fgetc pode até ser utilizada nesse tipo de operação, mas desde que o
arquivo não contenha mais do que 250 caracteres.
d) a função fgetc pode ser utilizada para realizar essa leitura ao ser inserida em um
laço de repetição que executa continuamente a leitura de caracteres até que essa
função retorne a constante NULL.
e) a função fgetc pode ser utilizada para realizar essa leitura ao ser inserida em um
laço de repetição que executa continuamente a leitura de caracteres até que essa
função retorne a constante EOF.

5. O programa abaixo está apresentando um erro durante a execução da função


fputs. Considerando os diferentes modos de abertura de arquivos disponíveis em C,
é correto afirmar que esse erro está sendo causado porque

105
#include <stdio.h>
#include <stdlib.h>

int main()
{
FILE *arquivo;
char string[30] = “escrevendo algo no arquivo”;

arquivo = fopen("teste.txt", "r");

if (arquivo == NULL)
exit(1);

fputs(string, arquivo);

fclose(arquivo);
}

a) a quantidade de caracteres a serem escritos no arquivo é maior do que o


permitido pelo sistema operacional.
b) antes de utilizar a função fputs, o cursor deve ser movido para o início do
arquivo.
c) antes de utilizar a função fputs, o cursor deve ser movido para o final do arquivo.
d) a função fputs está tentando realizar a gravação de dados em um arquivo que
foi aberto tanto para leitura quanto para gravação.
e) a função fputs está tentando realizar a gravação de dados em um arquivo que
foi aberto apenas para leitura.

6. Suponha que um programa codificado na linguagem C precise escrever, no final


de um arquivo texto, a seguinte cadeia de caracteres: “teste”. Mas, para isso, o
programa deve executar apenas 1 instrução de escrita.
Considerando as características das funções fputc e fputs, e também os modos de
abertura de arquivos em C, é correto afirmar que, nesse caso, poderia ser utilizado,
respectivamente, a seguinte função e modo de abertura de arquivo:

a) fputc e “a+”
b) fputs e “a+”
c) fputs e “r”
d) fputc e “a”
e) fputc e “r+”

106
7. A função fgets obtém, de uma única vez, um determinado número de caracteres
contidos dentro de um arquivo texto. No entanto, caso a função encontre um
indicador de final de linha, todos os caracteres dessa linha são imediatamente
retornados, mesmo que essa quantidade de caracteres seja menor do que a
solicitada inicialmente. Lembrando que, no contexto de linguagens de
programação, os espaços entre as letras também são considerados como sendo
caracteres.

Considerando as informações acima e tomando como exemplo o seguinte arquivo


texto contendo 3 linhas, é correto afirmar que, para obter o conteúdo integral da
segunda linha do arquivo, deveria se

Este é um teste de leitura de arquivo


A linguagem C pode ler e escrever em arquivos
Todo programador deve saber manipular arquivos

a) chamar a função fgets duas vezes, passando o valor 30 para a quantidade de


caracteres a ser obtida.
b) chamar a função fgets apenas uma vez, passando o valor 30 para a
quantidade de caracteres a ser obtida.
c) chamar a função fgets duas vezes, passando o valor 15 para a quantidade de
caracteres a ser obtida.
d) chamar a função fgets duas vezes, passando o valor 45 para a quantidade de
caracteres a ser obtida.
e) chamar a função fgets apenas uma vez, passando o valor 45 para a
quantidade de caracteres a ser obtida.

8. O programa abaixo está apresentando um erro durante a execução da função


fgetc. Considerando as etapas que devem ser realizadas ao manipular arquivos em
C, é correto afirmar que esse erro está sendo causado porque

#include <stdio.h>
#include <stdlib.h>

107
int main()
{
FILE *arquivo;
char caractere;

if (arquivo == NULL)
exit(1);

fgetc(caractere, arquivo);

fclose(arquivo);
}

a) o arquivo não foi fechado.


b) antes de utilizar a função fgetc, o cursor deve ser movido para o início do
arquivo.
c) o arquivo não foi aberto.
d) antes de utilizar a função fgetc, o cursor deve ser movido para o meio do
arquivo.
e) antes de utilizar a função fgetc, o cursor deve ser movido para o final do arquivo.

108
RESPOSTAS DO FIXANDO O CONTEÚDO

UNIDADE 01 UNIDADE 02

QUESTÃO 1 B QUESTÃO 1 D
QUESTÃO 2 D QUESTÃO 2 C
QUESTÃO 3 B QUESTÃO 3 E
QUESTÃO 4 C QUESTÃO 4 C
QUESTÃO 5 D QUESTÃO 5 C
QUESTÃO 6 C QUESTÃO 6 D
QUESTÃO 7 D QUESTÃO 7 D
QUESTÃO 8 A QUESTÃO 8 B

UNIDADE 03 UNIDADE 04

QUESTÃO 1 D QUESTÃO 1 C
QUESTÃO 2 B QUESTÃO 2 D
QUESTÃO 3 A QUESTÃO 3 C
QUESTÃO 4 A QUESTÃO 4 C
QUESTÃO 5 E QUESTÃO 5 A
QUESTÃO 6 C QUESTÃO 6 E
QUESTÃO 7 D QUESTÃO 7 B
QUESTÃO 8 A QUESTÃO 8 E

UNIDADE 05 UNIDADE 06

QUESTÃO 1 D QUESTÃO 1 A
QUESTÃO 2 E QUESTÃO 2 C
QUESTÃO 3 B QUESTÃO 3 B
QUESTÃO 4 E QUESTÃO 4 E
QUESTÃO 5 C QUESTÃO 5 E
QUESTÃO 6 A QUESTÃO 6 B
QUESTÃO 7 D QUESTÃO 7 D
QUESTÃO 8 B QUESTÃO 8 C

109

Você também pode gostar