Logo Passei Direto
Buscar
Material
páginas com resultados encontrados.
páginas com resultados encontrados.
left-side-bubbles-backgroundright-side-bubbles-background

Crie sua conta grátis para liberar esse material. 🤩

Já tem uma conta?

Ao continuar, você aceita os Termos de Uso e Política de Privacidade

left-side-bubbles-backgroundright-side-bubbles-background

Crie sua conta grátis para liberar esse material. 🤩

Já tem uma conta?

Ao continuar, você aceita os Termos de Uso e Política de Privacidade

left-side-bubbles-backgroundright-side-bubbles-background

Crie sua conta grátis para liberar esse material. 🤩

Já tem uma conta?

Ao continuar, você aceita os Termos de Uso e Política de Privacidade

left-side-bubbles-backgroundright-side-bubbles-background

Crie sua conta grátis para liberar esse material. 🤩

Já tem uma conta?

Ao continuar, você aceita os Termos de Uso e Política de Privacidade

left-side-bubbles-backgroundright-side-bubbles-background

Crie sua conta grátis para liberar esse material. 🤩

Já tem uma conta?

Ao continuar, você aceita os Termos de Uso e Política de Privacidade

left-side-bubbles-backgroundright-side-bubbles-background

Crie sua conta grátis para liberar esse material. 🤩

Já tem uma conta?

Ao continuar, você aceita os Termos de Uso e Política de Privacidade

left-side-bubbles-backgroundright-side-bubbles-background

Crie sua conta grátis para liberar esse material. 🤩

Já tem uma conta?

Ao continuar, você aceita os Termos de Uso e Política de Privacidade

left-side-bubbles-backgroundright-side-bubbles-background

Crie sua conta grátis para liberar esse material. 🤩

Já tem uma conta?

Ao continuar, você aceita os Termos de Uso e Política de Privacidade

left-side-bubbles-backgroundright-side-bubbles-background

Crie sua conta grátis para liberar esse material. 🤩

Já tem uma conta?

Ao continuar, você aceita os Termos de Uso e Política de Privacidade

left-side-bubbles-backgroundright-side-bubbles-background

Crie sua conta grátis para liberar esse material. 🤩

Já tem uma conta?

Ao continuar, você aceita os Termos de Uso e Política de Privacidade

Prévia do material em texto

Autor: Prof. Olavo Tomohisa Ito
Colaboradoras: Profa. Vanessa Santos Lessa
 Profa. Larissa Rodrigues Damiani
Estruturas de Dados
Professor conteudista: Olavo Tomohisa Ito
Mestre em Engenharia de Produção pela UNIP, bacharel em Física pelo Instituto de Física da USP, 
licenciado em Ciências pela Faculdade de Educação da USP, com o curso completo de Bacharelado 
em Língua e Literatura Japonesa da FFLCH‑USP. Professor dos cursos de graduação tradicional 
de Sistemas de Informação e Ciência da Computação, bem como do curso superior tecnológico de 
Análise e Desenvolvimento de Sistemas. Escreveu alguns livros didáticos como: Linguagem e Técnicas 
de Programação, Programação Orientada a Objetos e Programação para Dispositivos Móveis. 
© Todos os direitos reservados. Nenhuma parte desta obra pode ser reproduzida ou transmitida por qualquer forma e/ou 
quaisquer meios (eletrônico, incluindo fotocópia e gravação) ou arquivada em qualquer sistema ou banco de dados sem 
permissão escrita da Universidade Paulista.
Dados Internacionais de Catalogação na Publicação (CIP)
I89e Ito, Olavo Tomohisa.
Estruturas de Dados / Olavo Tomohisa Ito. – São Paulo: Editora 
Sol, 2022.
348 p., il.
Nota: este volume está publicado nos Cadernos de Estudos e 
Pesquisas da UNIP, Série Didática, ISSN 1517‑9230.
1. Linguagem C. 2. Percurso. 3. Grafo. I. Título.
CDU 681.3.01
U515.97 – 22
Prof. Dr. João Carlos Di Genio
Reitor
Profa. Sandra Miessa
Reitora em Exercício
Profa. Dra. Marilia Ancona Lopez
Vice-Reitora de Graduação
Profa. Dra. Marina Ancona Lopez Soligo
Vice-Reitora de Pós-Graduação e Pesquisa
Profa. Dra. Claudia Meucci Andreatini
Vice-Reitora de Administração
Prof. Dr. Paschoal Laercio Armonia
Vice-Reitor de Extensão
Prof. Fábio Romeu de Carvalho
Vice-Reitor de Planejamento e Finanças
Profa. Melânia Dalla Torre
Vice-Reitora de Unidades do Interior
Unip Interativa
Profa. Elisabete Brihy
Prof. Marcelo Vannini
Prof. Dr. Luiz Felipe Scabar
Prof. Ivan Daliberto Frugoli
 Material Didático
 Comissão editorial: 
 Profa. Dra. Christiane Mazur Doi
 Profa. Dra. Angélica L. Carlini
 Profa. Dra. Ronilda Ribeiro
 Apoio:
 Profa. Cláudia Regina Baptista
 Profa. Deise Alcantara Carreiro
 Projeto gráfico:
 Prof. Alexandre Ponzetto
 Revisão:
 Kleber Souza
 Willians Calazans
Sumário
Estruturas de Dados
APRESENTAÇÃO ......................................................................................................................................................9
INTRODUÇÃO ...........................................................................................................................................................9
Unidade I
1 INTRODUÇÃO À LINGUAGEM C ................................................................................................................. 11
1.1 Histórico ................................................................................................................................................... 12
1.2 IDE C/C++ ................................................................................................................................................ 12
1.3 Regras da linguagem .......................................................................................................................... 15
1.4 Estrutura de um programa em C ................................................................................................... 17
1.5 Funções e procedimentos C ............................................................................................................. 18
1.6 Tipos primitivos ..................................................................................................................................... 19
1.6.1 Operadores ................................................................................................................................................. 20
1.6.2 Entrada e saída ........................................................................................................................................ 21
1.6.3 Comandos da linguagem ..................................................................................................................... 22
1.6.4 Cast ............................................................................................................................................................... 24
2 ALGORITMOS E SOLUÇÃO DE PROBLEMAS .......................................................................................... 25
2.1 Introdução ao nivelamento de algoritmos ................................................................................ 25
2.2 Conceito de análise de algoritmos ................................................................................................ 26
3 REVISÃO DE ARRANJOS ................................................................................................................................ 31
3.1 Representação linear de matrizes ................................................................................................. 35
3.2 Operações com cadeias ...................................................................................................................... 35
3.3 Caracteres em C .................................................................................................................................... 37
3.4 Manipulação de strings ..................................................................................................................... 39
3.5 Modularização ....................................................................................................................................... 40
3.5.1 Procedimentos e funções .................................................................................................................... 44
3.6 Tipos abstratos de dados ................................................................................................................... 44
3.6.1 Tipo estrutura ........................................................................................................................................... 44
3.6.2 Definição de “novos” tipos .................................................................................................................. 45
3.6.3 Conceitos de TAD cadeias .................................................................................................................... 49
4 ALOCAÇÃO DINÂMICA DE MEMÓRIA: PONTEIROS ........................................................................... 52
4.1 O uso da memória ................................................................................................................................ 52
4.2 Variáveis ................................................................................................................................................... 56
4.3 Ponteiro de variáveis ........................................................................................................................... 57
4.4 Passagem de parâmetros por valor e por referência ............................................................. 69
4.5 Passagem de vetores para funções ............................................................................................... 85
4.6 Funções da biblioteca padrão.......................................................................................................... 86
Unidade II
5 ESTRUTURAS UNIDIMENSIONAIS ............................................................................................................. 98
5.1 Lista linear: definição e representação ........................................................................................ 98
5.2 Aplicações: lista sequencial .............................................................................................................. 99
5.2.1 Lista sequencial: inicialização ..........................................................................................................1005.2.2 Lista sequencial: inserção ..................................................................................................................100
5.2.3 Lista sequencial: impressão ..............................................................................................................104
5.2.4 Lista sequencial: busca .......................................................................................................................104
5.2.5 Lista sequencial: remoção .................................................................................................................105
6 LISTA ENCADEADA ........................................................................................................................................107
6.1 Lista encadeada: inicialização .......................................................................................................110
6.1.1 Lista encadeada: inserção ..................................................................................................................111
6.1.2 Lista encadeada: impressão, busca e liberação de memória .............................................. 120
6.1.3 Lista encadeada: remoção ................................................................................................................ 123
6.2 Listas com descritores ......................................................................................................................130
6.2.1 Listas duplamente encadeadas ...................................................................................................... 130
6.3 Listas lineares com disciplina de acesso: pilhas .....................................................................131
6.3.1 Implementação de pilha com lista: representação encadeada ......................................... 132
6.3.2 Criação da pilha com lista ................................................................................................................ 133
6.4 Função de impressão e função de liberação de memória .................................................143
6.5 Listas lineares com disciplina de acesso: filas.........................................................................146
6.5.1 Implementação de fila com lista: representação encadeada ............................................ 147
6.5.2 Criação da fila com lista ................................................................................................................... 148
6.5.3 Inserção na fila com lista .................................................................................................................. 149
6.5.4 Remoção de nó da fila com lista ................................................................................................... 156
6.5.5 Função de impressão e função de liberação de memória com lista ............................... 159
6.6 Implementação de fila com vetor: representação linear ...................................................162
6.6.1 Fila circular ............................................................................................................................................. 168
6.6.2 Filas especiais: deque ......................................................................................................................... 174
Unidade III
7 RECURSIVIDADE E ÁRVORE ......................................................................................................................184
7.1 Recursividade .......................................................................................................................................184
7.2 Árvores ....................................................................................................................................................196
7.2.1 Árvores: definições e representações básicas ........................................................................... 197
7.2.2 Árvores binárias .................................................................................................................................... 198
7.2.3 Percurso em árvores binárias .......................................................................................................... 199
7.2.4 Percurso em pré‑ordem ou prefixo (busca em profundidade) .......................................... 200
7.2.5 Percurso em ordem ou infixo (ordem simétrica)..................................................................... 209
7.2.6 Percurso em pós‑ordem ou pós‑fixo ............................................................................................213
7.3 Estrutura de árvores binárias em C .............................................................................................217
7.3.1 Árvores binárias de busca ................................................................................................................. 224
7.4 Pesquisa de dados: sequencial e binária ...................................................................................237
7.4.1 Busca sequencial .................................................................................................................................. 237
7.4.2 Busca binária ......................................................................................................................................... 238
7.5 Ordenação de dados ..........................................................................................................................242
7.5.1 Ordenação por troca BubbleSort (método da bolha) ............................................................ 243
7.5.2 QuickSort (método da troca e partição) ..................................................................................... 246
7.5.3 Ordenação por inserção InsertionSort (método da inserção direta) .............................. 247
7.5.4 BinaryInsertionSort (método da inserção direta binária) .................................................... 250
7.5.5 Ordenação por seleção SelectionSort (método da seleção direta) .................................. 252
7.5.6 HeapSort (método da seleção em árvore) ................................................................................. 255
7.6 Outros métodos: MergeSort (método da intercalação) e BucketSort 
(método da distribuição de chave) .....................................................................................................277
7.6.1 MergeSort (método da partição e mesclagem) ....................................................................... 277
7.6.2 BucketSort (método do balde) ....................................................................................................... 283
8 GRAFOS: CONCEITOS, REPRESENTAÇÃO E APLICAÇÕES ...............................................................302
8.1 Tipos de grafos ....................................................................................................................................303
8.1.1 Grafo dirigido ........................................................................................................................................ 303
8.1.2 Grafo não dirigido ............................................................................................................................... 304
8.2 Adjacência .............................................................................................................................................304
8.3 Grau .........................................................................................................................................................305
8.4 Caminho .................................................................................................................................................305
8.5 Comprimento de um caminho......................................................................................................306
8.6 Ciclo .........................................................................................................................................................3068.7 Conexão ..................................................................................................................................................306
8.8 Ponderação ...........................................................................................................................................308
8.9 Representação computacional .....................................................................................................308
8.9.1 Matriz de adjacências ........................................................................................................................ 308
8.9.2 Lista de adjacências .............................................................................................................................310
8.10 Tabela hash .........................................................................................................................................331
8.10.1 Inserção e busca .................................................................................................................................331
8.10.2 Tratamento de colisão ..................................................................................................................... 335
9
APRESENTAÇÃO
Caro aluno, a presente disciplina é um passo adiante para aqueles que já estudaram LPA ou disciplinas 
ligadas a algoritmos. Aqui abrangeremos algoritmos de estruturas e técnicas consagradas que o aluno 
poderia até desenvolver por si, mas analisaremos os melhores caminhos para chegarmos à construção 
de uma solução. O paradigma de programação será a estruturada, uma vez que veremos algoritmos 
modulares e técnicas que se tornam fundamentos do paradigma de orientação a objetos. Para tanto, 
utilizaremos a linguagem C++, pois por ser de médio nível nos permitirá o acesso a endereços de memória.
Veremos através das estruturas como as informações estão organizadas no computador, nas pastas 
e nos arquivos. Os dados computacionais, de algum modo, deverão estar dispostos para que seja 
possível realizar a escrita e a sua posterior recuperação de forma íntegra e funcionalmente prática. 
Assim, as estruturas de dados são formas otimizadas de armazenamento e tratamento das informações 
eletronicamente, representando a maturidade na lógica de programação.
INTRODUÇÃO
Na unidade I, veremos uma pequena introdução à linguagem C++. Todavia, este não é um curso de 
tal linguagem, será apenas um exemplo. A estrutura lógica é a mesma das linguagens de programação 
já estudadas no curso. Faremos uma revisão sobre algoritmos, a sua qualidade, e a modularização. 
A conceituação de tipo abstrato de dados (TAD), bem como o estudo técnico da alocação dinâmica de 
memória e os ponteiros como uma maneira dinâmica de manipular valores na memória. Passaremos os 
conceitos iniciais e não os algoritmos em si. Conceitos como tipo abstrato de dados (TAD) e técnicas de 
manipulação de memória através de ponteiros serão fundamentais.
Na unidade II, apresentaremos a primeira estrutura importante, a lista ligada, ou lista encadeada. 
Ela é conhecida por possuir encadeamentos unidirecionais, ou seja, listas organizadas com os dados 
organizados um após o outro. Veremos variantes das listas utilizando a disciplina de acesso, a fila e 
a pilha. A compreensão do conceito de ligação entre as informações servirá como ferramenta para 
diversas técnicas de programação, tornando praticamente ilimitado o armazenamento de informações.
Por fim, haverá uma técnica muito especial não estudada anteriormente, a recursividade. A partir 
dela poderemos estudar estruturas como as árvores, pesquisas, ordenações. O foco da unidade III será 
buscar informação em um conjunto de dados. Para fazê‑lo, utilizaremos algumas técnicas como a 
sequencial, a binária, o uso de grafos e a tabela hash.
11
ESTRUTURAS DE DADOS
Unidade I
Iniciaremos o módulo com um breve resumo da linguagem C. A aplicação dos conceitos será 
através da linguagem C padrão de forma estrutural. Devemos deixar claro que este não é um curso de 
linguagem C ou C++, portanto apenas os comandos e as técnicas aplicáveis à presente disciplina serão 
explicados. Pensando em termos de outras linguagens mais modernas, como Java, PHP ou mesmo o 
Interface Definition Language (IDL) do Common Object Request Broker Architecture (CORBA), usado 
em sistemas distribuídos, têm como base a linguagem C++. Como o aluno neste ponto do curso já deve 
ter certa familiaridade com alguma linguagem de programação, esse início é somente um pequeno 
guia para implementar as estruturas, comandos extras serão explicados durante o transcorrer do curso.
1 INTRODUÇÃO À LINGUAGEM C
A linguagem C pertence a uma família de linguagens cujas características são: portabilidade, 
modularidade, compilação separada, recursos de baixo nível, geração de código eficiente, confiabilidade, 
regularidade, simplicidade e facilidade de uso. Ela é considerada de médio nível. Não é uma linguagem 
de máquina que apenas os sistemas operacionais reconhecem, os chamados de baixo nível, como 
o Assembler, nem de alto nível, em que a própria linguagem fornece todos os recursos para o 
desenvolvimento do programa, como o Visual Basic. Ela faz parte da classe dos programas compilados, 
que são escritos em texto e passam por traduções para adequar‑se ao sistema operacional.
A linguagem C é adequada para a programação estruturada, tendo aplicação, principalmente, no 
desenvolvimento de sistemas operacionais, planilhas eletrônicas e gerenciadores de bancos de dados 
(HICKSON, 2005). Assim, o programa usado para processar este livro‑texto foi escrito em linguagem C.
Segundo Cocian (2004, p. 32), os pontos que tornaram a linguagem C popular foram:
• A portabilidade do compilador, pois existem compiladores para todos os sistemas operacionais.
• O conceito de bibliotecas padronizadas.
• A quantidade e a variedade de operadores poderosos.
• A sintaxe elegante.
• O fácil acesso ao hardware, quando necessário.
• A facilidade com que as aplicações podem ser otimizadas, tanto na codificação quanto na 
depuração, pelo uso de rotinas isoladas e encapsuladas.
12
Unidade I
1.1 Histórico
Em 1966, uma linguagem chamada Basic Combined Programming Language (BCPL) – Linguagem 
de Programação Básica Combinada, desenvolvida por Martin Richards, da Universidade de Cambridge 
(POLLONI; PERES; FEDELI, 2009), serviu de base para a linguagem B, elaborada por Ken Thompson para o 
computador PDP‑7 e que funcionava no sistema operacional Unix. A linguagem B, em razão de problemas 
de velocidade e processamento, viria a ser corrigida na linguagem C (COCIAN, 2004), desenvolvida na 
Bell Laboratories por Dennis Ritchie, em 1972, e liberada para as universidades.
O fato de ser liberada para as universidades tornou‑a popular, havia diversos compiladores para 
os vários sistemas operacionais e computadores. Esse fato mostrou a necessidade de padronização 
da linguagem, o que aconteceu em 1983, quando a American National Standards Institute (ANSI) 
– correspondente à ABNT no Brasil – estabeleceu esse padrão. Com o avanço do paradigma de 
programação orientada a objeto, foi desenvolvido o C++.
Como a linguagem C++ engloba totalmente a linguagem C e os recursos para esse curso são bem 
simples, a validade da referência à linguagem C será válida para a linguagem C++.
 Saiba mais
Caso haja a necessidade de consultar a normatização da linguagem C, 
basta consultar o link a seguir. Apesar de extremamente técnico, poderá 
ajudar na implantação de uma linguagem de programação em novos 
dispositivos físicos.
ISO/IEC. JTC1/SC22/WG21: The C++ Standards Committee – ISOCPP. 
[s.d.]. Disponível em: https://bit.ly/3w2dHEL. Acesso em: 10 maio 2022.
1.2 IDE C/C++
Os programas apresentados neste livro‑texto foram desenvolvidos utilizando o Microsoft Visual 
Studio 2008 (MICROSOFT, s.d.) e a sua versão online. Trata‑se da versão mais leve e com menos problemas 
de incompatibilidade na interface. Uma alternativa para ela é o Code::Blocks(CODE BLOCKS, s.d.), que 
tem instalação simples e debug eficiente. Outra alternativa é usar o Colab do Google, que não tem uma 
compilação automática, fato que permite uma experiência muito boa no processo de passagem do texto 
de programação para o executável no sistema operacional (Linux).
 Observação
Após o processo de compilação algumas mensagens são apresentadas. 
Em caso de erros de sintaxe, nenhum executável é gerado, sendo exibido o 
número da linha da ocorrência. Outra mensagem é o Warning (aviso), neste 
caso o executável é gerado normalmente, ela é apenas uma observação dos 
programas desenvolvidos neste livro‑texto.
13
ESTRUTURAS DE DADOS
A Microsoft oferece outras opções, como o Microsoft Visual Studio, que tem as versões Community 
(gratuita) e a Code. No Visual Studio, nas versões a partir de 2016, é necessária a inclusão na primeira 
linha da seguinte instrução:
Figura 1 
O uso do Visual Studio Code exige algumas personalizações manuais para evitar erros, portanto é 
recomendável apenas para usuários mais experientes.
Existem versões online como a recomendada pelo Code::Block, o OnlineGDB (s.d.). O importante 
nesses casos é selecionar o compilador da linguagem para C ou Turbo C++, conforme a figura a seguir.
Figura 2 – Selecionando o compilador C no OnlineGDB
Adaptada de: OnlineGDB (s.d.).
 Saiba mais
Como visto, o Google tem um ambiente de programação, o Colab, que 
foi projetado para python, mas o seu ambiente já possui instalado um 
compilador C. Seu endereço é o seguinte:
GOOGLE. Conheça o Colab. [s.d.]. Disponível em: https://bit.ly/3lWSgPD. 
Acesso em: 10 maio 2022.
Para fazer a programação, abra um ambiente de trabalho que é tratado, como notebook já 
existente ou novo.
A fim de programar as instruções faça o seguinte procedimento:
• Abra uma janela de código e digite a instrução %%writefile e o nome do programa.
• Digite o seu programa.
• Clique em executar para salvar o arquivo.
14
Unidade I
Figura 3 – Janela de codificação do Colab
Para compilar:
• Abra uma nova janela e digite %%shell, que passará a trabalhar com o ambiente Linux.
• Digite a execução do compilador C (gcc): gcc nome_arquivo_fonte ‑w ‑o nomeexecutável
Em seguida, execute:
• /nomeexecutável (figura a seguir).
• Clique na seta de execução. Em caso de erro, basta corrigir na janela do writefile para salvar o 
código corrigido e executar novamente.
Figura 4 – Compilando e executando um programa
 Saiba mais
A fim de visualizar o notepad mostrado anteriormente, acesse:
COLAB RESEARCH. Linguagem C.ipynb. [s.d.]. Disponível em: 
https://bit.ly/3tdd0XI. Acesso em: 10 maio 2022.
15
ESTRUTURAS DE DADOS
1.3 Regras da linguagem
Como toda linguagem de programação, a linguagem C é muito rígida na sua sintaxe. Sintaxe são 
regras detalhadas para que um programa possa ser executado.
Elas estão relacionadas com os tópicos a seguir:
• Tipos: definem as propriedades dos dados manipulados em um programa.
• Declarações: estabelecem o identificador, para alocar memória, definir conteúdo inicial e 
determinar funções.
• Expressões: fórmulas que atribuem e alteram valores, bem como fazem chamada de funções de 
entrada e saída.
• Funções: subprogramas em C, que, por sua vez, são chamados por outras funções executando 
tarefas específicas. Há funções básicas que estão definidas nas bibliotecas padrão da linguagem, 
e outras que são desenvolvidas por terceiros, com rotinas mais específicas (ITO, 2015). As funções 
printf() e scanf(), por exemplo, que veremos posteriormente, as quais permitem, respectivamente, 
escrever na tela e ler os dados a partir do teclado, fazem parte da biblioteca básica. O programador 
também pode escrever as suas próprias funções.
A primeira função que o programa executa é o main(), portanto é obrigatória a sua declaração no 
programa principal.
Outras regras importantíssimas:
• A linguagem é case sensitive; isso quer dizer que existe diferenciação entre as letras maiúsculas e 
minúsculas, na identificação de comandos, variáveis e funções.
Abacaxi ≠ abacaxi
• Os comandos são separados por ponto e vírgula (“;”), que deve ser usado com muito cuidado, 
principalmente, antes de blocos de comandos.
 Observação
O compilador ignora as quebras de linha, os comandos escritos 
em linhas diferentes, mas separados apenas por esse recurso, para ele 
estão na mesma linha.
16
Unidade I
abcdefgh
ijk
lmn
abc
defgh;ijk;
lmn;
Figura 5 – Para o compilador, a separação dos comandos é feita por “;” e não pela linha
• A linguagem também permite o uso de comentários, que são colocados no texto entre “/*” e “*/”, 
quando queremos escrever mais de uma linha. O uso do “//” ainda serve como comentário, e, neste 
caso, o compilador ignorará tudo o que estiver escrito a partir dele até o fim da linha.
abcdefgh
mn
abc
defgh;/*ijk;
l*/mn; //opqrs
Figura 6 – O compilador ignora os comentários
• Pela definição padronizada pela ANSI (HICKSON, 2005), existem 32 palavras‑chave (ou palavras 
reservadas) que formam a linguagem de programação C. Conforme o compilador, poderão existir 
mais palavras‑chave, todas escritas em letras minúsculas. Nenhuma delas deverá ser utilizada 
para dar nomes a variáveis e funções, pois isso gerará erro ao compilar.
Quadro 1 – Palavras reservadas pelo padrão ANSI
auto double int struct
break else long switch
case enum register typedef
char extern return union
const float short unsigned
continue for signed void
default goto sizeof volatile
do if static while
Fonte: Hickson (2005, p. 28).
17
ESTRUTURAS DE DADOS
• Os identificadores são nomes usados para fazer referência a variáveis, funções, rótulos e vários 
outros objetos definidos pelo programador. Como norma, conforme vimos no pseudocódigo, o 
primeiro caractere deve ser uma letra ou um sublinhado, e o restante podem ser números, letras 
ou sublinhados; apenas os 32 primeiros caracteres de um identificador são significativos. Como já 
dito, a linguagem C é case sensitive, ou seja, as letras maiúsculas diferem das minúsculas.
1.4 Estrutura de um programa em C
Para programar em C partindo do zero, ou seja, de um texto totalmente vazio, é necessário apenas 
o bloco da função main(). Como há a necessidade de interações, devemos colocar as bibliotecas no 
programa, o que é feito em primeiro lugar.
A fim de termos uma ideia mais clara da estrutura, elaboraremos o primeiro programa e 
compreenderemos cada uma das linhas.
Figura 7 
Ao executar o programa, teremos uma mensagem no console do DOS dizendo “Meu primeiro 
programa em C”. Apesar de ser um programa muito simples, existe a estrutura mínima de um programa 
na linguagem. Vamos analisar cada parte dela a seguir:
#include <stdio.h>
O comando include informa ao compilador que ele deve incluir o arquivo‑cabeçalho stdio.h. Nele, 
há um série de funções de entrada e saída úteis, std‑i‑o standard input‑output (biblioteca padrão de 
entrada/saída). Sempre que quisermos usar funções relativas à entrada e saída, devemos verificar se o 
arquivo stdio.h as contém. A linguagem C fornece dezenas de arquivos‑cabeçalhos com milhares de 
funções úteis. Podemos chamar os arquivos‑cabeçalhos de bibliotecas.
 Observação
O compilador ao encontrar o comando #include, antes de iniciar o 
processo de compilação, adiciona a listagem da biblioteca à listagem 
escrita pelo programador, formando apenas uma listagem. Somente depois 
o programa é compilado.
18
Unidade I
/*
Meu primeiro programa em C
*/
No código a seguir, temos uma estrutura básica que possui o conjunto:
Figura 8 
1.5 Funções e procedimentos C
Os programas em C são montados com a reunião de várias pequenas funções, em vez de poucas de 
maior tamanho. Na linguagem C, tudo é feito por meio de funções e de suas bibliotecas. Já havíamos 
utilizado algumas, como as da biblioteca stdlib.h, para entrada e saída de dados.
Sintaxe da função em C:
<tipo de retorno> nome( <tipo1> var1, <tipo2> var2){
 Corpo da função
 return retorno; 
}
19
ESTRUTURAS DE DADOS
O <tipode retorno> pode ser qualquer um, até os criados por meio do struct. Um caso particular 
é o tipo void (nulo). Isso significa que a função não retornará nenhum valor, ou seja, trata‑se de 
um procedimento.
Durante a programação, a execução sempre começa no main(). Na verdade, o próprio main() é 
uma função. Aqui, usualmente utilizamos no main() o tipo void, e o seu parâmetro é vazio. Sendo uma 
função main(), ele pode ser definido como qualquer tipo e podemos vê‑lo como int, mas deverá retornar 
um valor inteiro que poderá ser utilizado pelo sistema operacional. Os parâmetros da função main são 
aqueles passados quando um programa é executado na linha de comando e alguns valores são digitados 
após o nome. Dessa maneira, é feita a interação do ambiente operacional e do programa.
1.6 Tipos primitivos
Na linguagem C, além dos tipos primitivos int, float, char e double, temos as suas modificações, e a 
faixa de números utilizáveis é determinada pelo número de bits de cada tipo modificado. Podemos ver 
essa precisão e a faixa numérica a seguir.
Quadro 2 – Tipos primitivos com os modificadores
Tipo N. de bits
Intervalo
Valor inicial Valor final
Char 8 ‑128 127
Unsigned char 8 0 255
Signed char 8 ‑128 127
Int 16 ‑32,768 32,767
Unsigned int 16 0 65,535
Signed int 16 ‑32,768 32,767
Short int 16 ‑32,768 32,767
Unsigned short int 16 0 65,535
Signed short int 16 ‑32,768 32,767
Long int 32 ‑2.147.483.648 2.147.483.647
Signed long int 32 ‑2.147.483.648 2.147.483.647
Unsigned long int 32 0 4.294.967.295
Float 32 3,4E‑38 3,4E+38
Double 64 1.7 E‑308 1.7 E308
Long Double 80 3.4E‑4932 3.4E+4932
Fonte: Hickson (2005, p. 30).
Conforme vimos, os tipos lógicos e as cadeias de caracteres não constam no quadro. Para esses tipos, 
a linguagem C tem um tratamento específico.
20
Unidade I
• Tipo lógico: a linguagem C trata como verdadeiro (true) o valor inteiro 1 e como falso (false) o 
valor inteiro 0; assim, a ausência do tipo lógico é compensada pelo correspondente numérico. Na 
versão C++, a linguagem passa a incorporar o tipo bool.
1.6.1 Operadores
Conforme veremos a seguir, existem os operadores unários e binários. No quadro, temos 
os operadores de referência que observaremos posteriormente em Alocação dinâmica de 
memória: ponteiros.
Quadro 3 – Relação de operadores
Tipo Operador Propósito Exemplo
Aritméticos
+ Adição a = 4 + 1; // 5
‑ Subtração a = 4 – 1; // 3
* Multiplicação a = 2 * 4; // 8
/ Divisão a = 8 / 2; // 4
% Módulo (resto da divisão) a = 5 % 2; // 1
Atribuição = Atribuição simples a = 50;
Lógicos
&& “e” lógico (a > 1) && (b < 1)
|| “ou” lógico (a > 1) || (b < 1)
! não (inversão) !(a > 2)
Relacionais (Comparação)
== igual a (a == 0)
!= diferente de (a != 0)
< menor que (a < 0)
> maior que (a > 0)
<= menor ou igual a (a <= 0)
>= maior ou igual a (a >= 0)
Incremento e Decremento
++ Incremento a++;
‑‑ Decremento a‑‑;
Referência (Apontadores) 
Operadores utilizados antes 
do nome de variáveis. Será 
visto com detalhes em 
Alocação dinâmica de 
memória: ponteiros.
& Retorna o “endereço de” int a; //variável inteira
int *p; //declaração de ponteiro p = &a; 
//atribui o endereço de a
*p = 2; //atribui ao conteúdo
//apontado por p o valor 2;
//como p aponta para o endereço
//de a, então a recebe 2.
* Retorna o “conteúdo de”
21
ESTRUTURAS DE DADOS
1.6.2 Entrada e saída
No item Estrutura de um programa em C, vimos que, no código, devemos incluir o arquivo‑cabeçalho 
stdio.h, que contém uma série de funções de entrada e saída úteis. Nos nossos programas, utilizaremos 
duas funções dessa biblioteca: o printf e o scanf.
• printf(formato, argumentos): função para saída de valores segundo um determinado formato. 
As máscaras de formatação podem ser vistas no quadro 4 – Especificadores de formato.
Exemplo:
Figura 9 
Figura 10 
• scanf(formato, lista de endereços): função para capturar e armazenar valores fornecidos 
via teclado.
No comando scanf, a lista de endereços pode ser escrita com o nome da variável em que se deseja 
atribuir um valor precedido pelo caractere &.
Exemplo:
Figura 11 
Figura 12 
22
Unidade I
Quadro 4 – Especificadores de formato
%c char
%d int
%u unsigned int
%f double ou float
%e double ou float (científico)
%s cadeia de caracteres
\n quebra de linha
\t tabulação
\” caractere ”
\\ caractere \
1.6.3 Comandos da linguagem
No padrão da linguagem C, como vimos, existem 32 palavras‑chave, e entre elas temos os comandos 
para que todas as bibliotecas possam ser montadas.
Quadro 5 – Relação dos comandos da linguagem C
Comando Propósito Sintaxe
Declaração de variável Declaração de variável tipo nome_variavel = valor_inicial;
Declaração de constante Declaração de constante #define NOME_CONSTANTE valor
Bloco Marcar um bloco de cód. { } //Abre e fecha chaves “{}“
if Comando condicional
if (a > b) {
 printf(“%s”, “A é maior que B”);
} else {
 printf(“%s”, “A é igual ou menor que B”);
}
switch Comando condicional
switch (i) {
case 0 :
 printf(“%s”, “ZERO”); 
 break;
case 1:
 printf(“%s”, “UM”); 
 break;
case 2:
 printf(“%s”, “DOIS”);
 break;
}
23
ESTRUTURAS DE DADOS
Comando Propósito Sintaxe
while Laço com pré‑validação
int i = 1;
while (i <= 10) { 
 printf(“%d”, i++);
}
do Laço com pós‑validação
int i = 1;
do {
 printf(“%d”, i++);
} while (i <= 10);
for Laço simplificado
for (i=1;i<=10;i++){
 printf(“\n%d”, i);
}
break Saída de bloco break;
continue Reinício de bloco continue;
Sub‑rotinas
Funções
float area(float altura, float base) { 
 return altura * base;
}
Procedimentos
void area(float altura, float base) { 
{ printf(“A area é: %f”, altura * base);
}
Vetores Variáveis unidimensionais
int v[10]; //Vetor de inteiros
//v[0] é o primeiro elemento e v[9] o último
Matrizes Variáveis multidimensionais
float mat[4][3]; //Tabela com 4 linhas
 //e 3 colunas
struct Tipos compostos de dados
struct ponto { 
 int x;
 int y;
}
struct ponto p;
p.x = 10;
p.y = 20;
typedef Definição de novos tipos de dados
typedef struct ponto {
 int x;
 int y;
} Ponto;
Ponto p;
p.x = 10;
24
Unidade I
1.6.4 Cast
A conversão de um tipo de dados em outro é conhecida como cast de tipo, um molde, ou coerção, para 
fazer conversão. Por exemplo, para armazenar um valor long em um inteiro simples (int), é necessário 
fazer do long para int. É possível converter os valores de um tipo para outro explicitamente usando o 
operador de conversão da seguinte maneira:
Figura 13 
Figura 14 
Observamos que o resultado é 3, mas o resultado da divisão da linha 8 deveria ser 3,4. Mesmo a 
variável média sendo do tipo double o cálculo resulta em um número inteiro, assim sendo as casas após 
a vírgula são desprezadas.
Para corrigir o problema, a divisão será formatada com um cast para double.
Figura 15 
Figura 16 
25
ESTRUTURAS DE DADOS
2 ALGORITMOS E SOLUÇÃO DE PROBLEMAS
Todas as ações no nosso dia a dia seguem um algoritmo, seja um ato intencional, seja um ato 
instintivo. Com o decorrer da vida, conforme vamos crescendo e aprendendo, novas instruções se 
incorporam ao nosso repertório. A experiência humana é um repertório de algoritmos que foram 
se acumulando durante a vida. O algoritmo é um conjunto finito de instruções, de comandos, de ações 
que tem como objetivo a resolução de uma tarefa, ou a resolução de um problema.
Constam na sequência outras definições sobre algoritmos:
Para Forbellone (1993, p. 3),
 
Algoritmo é uma sequência finita de instruções ou operações cuja execução, 
em tempo finito, resolve um problema computacional, qualquer que seja 
sua instância. [...] Algoritmo é uma sequência de passos que visam atingir 
um objetivo bem‑definido.
Já Manzano (1996, p. 6) considera que “algoritmo são regras formais para a obtenção de um resultado 
ou da solução de um problema, englobando fórmulas de expressões aritméticas”. Temos na sequência a 
descrição dada por Ascencio e Campos (2003, p. 1): “algoritmo é a descrição de uma sequênciade passos 
que deve ser seguida para a realização de uma tarefa”.
Por fim, consta a definição de Farrer et al. (1999, p. 14):
 
Ação é um acontecimento que, a partir de um estado inicial, após um período 
de tempo finito, produz um estado final previsível e bem‑definido. Portanto 
um algoritmo é a descrição de um conjunto de comandos que, obedecidos, 
resultam numa sucessão finita de ações.
2.1 Introdução ao nivelamento de algoritmos
Diferentes algoritmos podem realizar a mesma tarefa usando um conjunto distinto de instruções em 
mais ou menos tempo, espaço ou esforço do que outros. Tal diferença pode ser reflexo da complexidade 
computacional aplicada, que depende de estruturas de dados adequadas ao algoritmo. A existência de 
um algoritmo para resolver um problema não implica necessariamente que ele possa de fato fazê‑lo na 
prática. Há restrições de tempo e espaço de memória a serem consideradas.
Se até agora os algoritmos foram vistos como um conjunto de instruções computacionais para 
resolver problemas gerais, a partir de agora serão entendidos como uma sequência de técnicas lógicas 
para a solução de problemas fundamentais de tratamento a dados como ordenações de busca, acesso 
a informações.
26
Unidade I
Sendo assim, nivelamento é desenvolver uma métrica, ou um modelo de avaliar para analisar a 
eficiência de algoritmos com a mesma finalidade, com lógicas de programação diversas, sem levar em 
consideração todos os detalhes do dispositivo no qual o algoritmo está executando.
2.2 Conceito de análise de algoritmos
Nesta disciplina veremos apenas os conceitos da análise de algoritmos, não faremos uma abordagem 
mais profunda.
Conforme Cormen (2012, p. 14),
 
Algoritmos diferentes criados para resolver o mesmo problema muitas 
vezes são muito diferentes em termos de eficiência. Essas diferenças 
podem ser muito mais significativas que as diferenças relativas a 
hardware e software.
Analisar um algoritmo significa prever os recursos de que ele necessita. Verificando‑se algoritmos 
candidatos para a solução de um problema, pode‑se identificar um que seja mais eficiente, ou então 
apontar também um descartável devido à qualidade inferior no processo.
Na análise do desempenho de um algoritmo, precisam ser observados os seguintes parâmetros:
• Tempo de execução: quanto tempo um código leva para ser executado.
• Uso de memória volátil: quantidade de espaço ocupada na memória principal do computador.
O tempo de execução de um algoritmo será representado por uma função de custo T, onde T(n) é a 
medida do tempo total necessário para executar um algoritmo para um problema de tamanho n. Esse 
custo em tempo de execução é o quanto um programa demora para encerrar a sua execução. Define‑se 
então que T é chamada de função complexidade de tempo do algoritmo. Se T(n) é a medida de memória 
necessária para a execução do algoritmo de tamanho n, então T é chamada função de complexidade de 
espaço. Observa‑se que, como há a dependência do hardware, T(n) não representa diretamente o tempo 
de execução, mas o número de vezes que certa operação relevante é executada. Conforme Borin (2020), 
a função custo T(n) de um algoritmo qualquer pode ser dada como:
T(n) = Ttempo+Tespaço 
Sendo T tempo o custo em tempo de execução e Tespaço o custo de ocupação de memória.
Para entender o custo ao executar um programa simples, limitando‑se a um processador único, 
considerando‑se o processamento simples e cada operação com o peso, temos o exemplo simples. Em 
um programa que executa o laço com a quantidade de iterações determinada pelo valor atribuído na 
variável n, o tempo será maior quanto maior a grandeza do número.
27
ESTRUTURAS DE DADOS
Figura 17 
Para analisar executando passo a passo, podemos contar a quantidade de instruções realizadas no 
processamento do programa.
Quadro 6 
Passo a passo
Linha Instruções Operações Quantidade
5 n=5 n=5 1
6 for (i=0;i<=n;i++){ i=0 i<=0 2
7 //Laço 0
6 for (i=0;i<=n;i++){ i++ i<=0 2
n vezes
7 //Laço 
6 for (i=0;i<=n;i++){ i++ i<=0 2
7 //Laço 
6 for (i=0;i<=n;i++){ i++ i<=0 2
7 //Laço 
6 for (i=0;i<=n;i++){ i++ i<=0 2
7 //Laço 
6 for (i=0;i<=n;i++){ i++ i<=0 2
Ao processar a linha 5, a atribuição na variável n, bem como a inicialização da variável i e a primeira 
verificação do laço na linha 6 acontecem apenas uma vez.
Quadro 7 
Linha Instruções Operações Quantidade
5 n=5 n=5 1
6 for (i=0;i<=n;i++){ i=0 i<=0 2
6 for (i=0;i<=n;i++){ i++ i<=0 2n
28
Unidade I
O laço da linha 6 é executado n vezes, assim sendo o custo do tempo de execução do programa é 
dado por:
T(n) = 2n + 3
O primeiro exemplo é simples apenas para entender o funcionamento do custo de tempo. Ao inserir 
condicionais no programa, conforme os dados de entrada, os custos podem ter resultados distintos, pois 
os caminhos tomados no processamento conseguem executar quantidades diferentes de instruções.
Ao montar um programa para apresentar o maior valor contido em um vetor, temos o programa 
a seguir:
Figura 18 
Neste caso, o custo é calculado da seguinte forma:
Quadro 8 
Passo a passo
Linha Instruções Operações Quantidade
5 maior = v[0]; maior = v[0] 1
6 i=0; i=0 1
7 while (i<4){ (i<4) 1
8 if (v[i]>=maior) (v[i]>=maior) 1
n vezes
9 maior=v[i]; maior=v[i] 1
10 i++; i++ 1
7 while (i<4){ (i<4) Laço 1
Desta forma, o custo do programa para a entrada v={1,2,3,4} é:
T(n) = 4n + 3
29
ESTRUTURAS DE DADOS
O vetor v está em ordem crescente e considera‑se que em todos os casos a linha 9 será executada, 
ou seja, é o pior caso. Alterando a entrada em ordem decrescente, fica da seguinte forma:
Figura 19 
O custo para a entrada está em ordem decrescente v={4,3,2,1}, ao ser processado o seu resultado é:
Quadro 9 
Passo a passo
Linha Instruções Operações Quantidade
5 maior = v[0]; maior = v[0] 1
6 i=0; i=0 1
7 while (i<4){ (i<4) 1
8 if (v[i]>=maior) (v[i]>=maior) 1
n vezes10 i++; i++ 1
7 while (i<4){ (i<4) Laço 1
O custo do programa para a entrada v={4,3,2,1} é:
T(n) = 3n + 3
Logo, o resultado do custo de tempo é menor. De acordo com a figura a seguir, observa‑se que 
conforme a quantidade de dados n aumenta, a diferença entre o melhor e o pior caso também o faz. 
Ainda é importante observar que independentemente dos dados de entrada no programa apresentado, 
o resultado estará entre o melhor e o pior caso.
30
Unidade I
100
200
300
400
500
600
700
800
900
1000
Custo de tempo
100
0
20 30 40 50
n
pior caso melhor caso
60 70 80 90 100
Figura 20 – Gráfico do custo de tempo em função da quantidade de dados
No caso, o programa é bastante simples, mas a grande maioria deles é mais complexa e seria muito 
trabalhosa uma contagem linha a linha. Para contornar essa dificuldade, é possível outra abordagem, a 
análise assintótica.
A análise assintótica é feita tentando extrapolar o conjunto de dados de entrada, tendendo‑os 
ao infinito e tomando a liberdade de desprezar os outros. Desta forma, se considerarmos o melhor 
caso, temos:
T(n) = 4n + 3 → T(n) = n 
Na hipótese de laços encadeados, um laço dentro do outro, teremos n x n, ou seja, n2. Por exemplo:
31
ESTRUTURAS DE DADOS
Figura 21 
O importante são os 3 laços intercalados (linhas 6, 7, 8), o que resultará pela análise assintótica um 
custo com o comportamento n3. Desta forma, conforme laços são colocados dentro de outros, o custo 
do tempo aumenta exponencialmente. O objetivo ideal é construir um algoritmo em que o custo seja 
menor com uma grande quantidade de dados.
Na literatura temos notações utilizadas para comparar algoritmos. De acordo com Ascencio (2002), 
essas notações determinam a complexidade assintótica de um código para diferentes situações e 
dependentes de um conjunto de dados.
• Grande-O (Big-O): define o comportamento assintótico superior, possui mais instruções sendo 
executadas e é o pior caso de um algoritmo.
• Grande-ômega (Big-Ω): define o comportamento assintótico inferior, possui menos instruçõessendo executadas e é o melhor caso do algoritmo (caso ótimo).
• Grande-teta (BIG-Θ): define o comportamento assintótico firme, trata‑se do comportamento 
considerado na grande maioria dos casos, é o caso médio de um algoritmo.
3 REVISÃO DE ARRANJOS
Uma estrutura de dados que utiliza somente um tipo de dado é conhecida como dados homogêneos. 
Variáveis compostas homogêneas são aquelas em que as posições de memória são identificadas por um 
mesmo nome e individualizadas por índices, possuindo todos os conteúdos compostos do mesmo tipo. Assim, 
como visto anteriormente, os vetores (também conhecidos como estruturas de dados unidimensionais) e 
as matrizes (estruturas de dados bidimensionais) seguem a estrutura dos dados homogêneos.
32
Unidade I
A forma mais simples de estruturar um conjunto de dados é por meio de vetores. Os vetores são utilizados 
quando há muitas variáveis do mesmo tipo em um programa. É possível imaginá‑los como uma “fila” de 
variáveis do mesmo tipo e com mesmo nome que é identificada por um número sequencial. Portanto, um 
vetor pode ser definido como um conjunto de variáveis exatamente do mesmo tipo que armazena, cada 
um associado a um número, que se refere à posição de armazenamento e é conhecido como índice.
Cada posição sua é acessada individualmente de forma direta, podendo ser lida ou escrita diretamente, 
como uma variável, conforme vimos, e sem obedecer a nenhuma regra ou ordem preestabelecida. Logo, 
os vetores podem ter acesso aleatório.
Segundo Laureano (2008, p. 2),
 
O vetor é uma estrutura de dados linear que necessita de somente um 
índice para que seus elementos sejam endereçados. É utilizado para 
armazenar uma lista de valores do mesmo tipo, ou seja, o tipo vetor 
permite armazenar mais de um valor em uma mesma variável. Um dado 
vetor é definido como tendo um número fixo de células idênticas (seu 
conteúdo é dividido em posições). Cada célula armazena um e somente um 
dos valores de dados do vetor. Cada uma das células de um vetor possui seu 
próprio endereço, ou índice, através do qual pode ser referenciada. Nessa 
estrutura todos os elementos são do mesmo tipo, e cada um pode receber 
um valor diferente. Algumas características do tipo vetor:
• Alocação estática (devem‑se conhecer as dimensões da estrutura no 
momento da declaração);
• Estrutura homogênea;
• Alocação sequencial (bytes contíguos);
• Inserção/exclusão;
• Realocação dos elementos;
• Posição de memória não liberada.
6,0
1
7,0
2
2,5
3
10,0
4
9,5
5
8,0
6
elementos
índices
5,5
0
Figura 22 – Vetor contendo notas
Essa figura ilustra um vetor de sete posições, preenchido com notas. Assim, por exemplo, no elemento 
de índice 3, o conteúdo corresponde à nota 2,5; em outro caso, no elemento de índice 5, temos a nota 9,5. 
Logo, na linguagem C, a declaração do vetor é:
float v[7];
33
ESTRUTURAS DE DADOS
 Observação
Devemos tomar cuidado, pois o índice do vetor na linguagem C sempre 
se inicia em zero. Portanto, na declaração anterior, os elementos vão 
de v[0] até v[6].
Uma característica na linguagem C é que os vetores também podem ser inicializados na declaração:
float v[7]={ 5.5, 6.0, 7.0, 2.5, 10.0, 9.5, 8.0};
Ou simplesmente:
float v[ ]={ 5.5, 6.0, 7.0, 2.5, 10.0, 9.5, 8.0};
O programa aloca, automaticamente, sete espaços de inteiros na memória.
A atribuição de um elemento do vetor a uma variável é feita utilizando a seguinte sintaxe:
variável=v[índice];
Assim, na operação notaPedro<‑notas[2], a variável NotaPedro passará a armazenar o valor 7,0.
float notaPedro=v[2];
A atribuição de um valor a um elemento do vetor é feita utilizando a seguinte sintaxe:
v[índice]=valor;
Para a atribuição do valor 8,0 na posição 2, o comando será:
v[3]=7.0;
6,0
1
7,0
2
8,0
3
10,0
4
9,5
5
8,0
6
elementos
índices
5,5
0
Vetor: v
Figura 23 – Nova situação do vetor
Quando os vetores são multidimensionais eles recebem o nome de matrizes; os mais comuns são os 
bidimensionais. Assim, a matriz bidimensional é uma estrutura de dados que necessita de um índice para 
referenciar a linha e outro para referenciar a coluna e localizar o elemento da informação.
34
Unidade I
7,0
1
1
8,5
2
2
10
3
elementos
índices
8,5
0
0
4,0 6,0 5,53,0
7,5 6,0 5,07,0
Figura 24 – Representação de uma matriz bidimensional
Na linguagem C podem ser criadas matrizes bidimensionais. Portanto, para declararmos uma matriz 
de valores reais com três linhas e quatro colunas, fazemos:
float classe[3][4];
Ao ser declarada a matriz, o C reserva um espaço de memória para armazenar os 12 elementos de 
maneira contínua, organizada linha a linha. Então, esse espaço é preenchido com a seguinte declaração:
float classe[3][4] = {{ 8.5, 7.0, 8.5, 10.0},{ 3.0, 4.0, 6.0, 5.5},{ 7.0, 7.5, 
6.0, 5.0}};
Como a linguagem C ignora os caracteres de quebra de linha, para ficar mais fácil de visualizar 
durante a programação, é possível declarar a matriz da seguinte forma:
float classe[3][4] = {{ 8.5, 7.0, 8.5, 10.0}
 ,{ 3.0, 4.0, 6.0, 5.5}
 ,{ 7.0, 7.5, 6.0, 5.0}};
 Lembrete
Como as matrizes são um caso particular de vetores, os índices também 
iniciam em zero.
 Saiba mais
O uso típico de matrizes é o jogo da batalha naval. Consta a seguir 
exemplo simples de jogo em uma matriz 5 x 5.
C PROGRESSIVO. Batalha naval em C. [s.d.]. Disponível em: 
https://bit.ly/39HP0oq. Acesso em: 10 maio 2022.
35
ESTRUTURAS DE DADOS
3.1 Representação linear de matrizes
Como vimos, ao inicializar um vetor com valores, é desnecessário declarar a sua dimensão. Trata‑se de 
uma característica muito importante para começar a entender como a linguagem C manipula a memória.
A fim de ilustrar o funcionamento, consta a linguagem ao executar a linha a seguir:
float v[ ]={ 5.5, 6.0, 7.0, 2.5, 10.0, 9.5, 8.0};
A execução ao encontrar float v[ ] entende que receberá a informação { 5.5, 6.0, 7.0, 2.5, 10.0, 9.5, 
8.0}; ou seja, sete números do tipo float declarados à esquerda, irá procurar na memória RAM um 
espaço livre contíguo que o vetor caiba. Após encontrá‑lo, uma tabela das variáveis é preenchida com 
o nome, o endereço e o tipo da variável. No exemplo, supondo que o tipo float ocupe cada 4 bytes, o 
sistema reservará 28 bytes.
As matrizes trabalham da mesma forma, alocando os dados na memória de modo contíguo. Assim, 
uma matriz pode ser declarada de várias maneiras.
Utilizando o exemplo já visto:
float classe[3][4] = {{ 8.5, 7.0, 8.5, 10.0}
 ,{ 3.0, 4.0, 6.0, 5.5}
 ,{ 7.0, 7.5, 6.0, 5.0}};
Outra maneira que a linguagem C permite é declararmos sequencialmente:
float classe[3][4] = { 8.5, 7.0, 8.5, 10.0, 3.0, 4.0, 6.0, 5.5, 7.0, 7.5, 6.0, 
5.0};
Em razão da particularidade da administração da memória também podemos escrever da seguinte 
forma:
float classe[ ][4] = { 8.5, 7.0, 8.5, 10.0, 3.0, 4.0, 6.0, 5.5, 7.0, 7.5, 6.0, 
5.0};
O administrador da memória que possui apenas o segundo elemento é capaz de montar as linhas da 
matriz. Sempre é necessário tomar cuidado com a relação linha/coluna, haverá erro se a declaração for:
float classe[3][4] = {{ 8.5, 7.0, 8.5},
 {10.0, 3.0, 4.0},
 { 6.0, 5.5, 7.0},
 { 7.5, 6.0, 5.0}};
Não há conformidade entre linhas e colunas e a matriz inicializada.
3.2 Operações com cadeias
As cadeias de caracteres em C (Strings) são representadas por vetores do tipo char terminadas, 
obrigatoriamente, pelo caractere nulo (‘\0’). Sempre que ocorre o armazenamento em cadeia é necessário 
36
Unidade I
reservar um elemento adicional para o caractere de fim da cadeia. Todas as funções que manipulam 
Strings (dos quais alguns serão estudados) recebem como parâmetro um vetor de char e processam 
caractere por caractere, até encontrarem o caractere nulo, que sinaliza o final da cadeia.
Para imprimir uma cadeia utilizando a função printf, é necessário o especificador de formato %s. 
A função, na verdade, recebe um vetor de char e imprime elemento por elemento, até encontrar o 
caractere nulo (‘\0’).
O exemplo a seguir ilustra a representaçãode uma string. Como queremos representar a palavra 
UNIP, composta de quatro caracteres, declaramos um vetor com dimensão 5 (um elemento adicional 
para armazenarmos o caractere nulo no final da cadeia). O código preenche os elementos do vetor, 
incluindo o caractere ‘\0’, e imprime a palavra na tela.
Figura 25 
Ao executarmos, obtemos o seguinte resultado:
Figura 26 – Saída do programa com uma string montada em um vetor
Se o ‘\0’ não fosse colocado, a função printf não encontraria o fim da cadeia, e o programa mostraria 
caracteres que eventualmente restam na memória até encontrar um ‘\0’.
Figura 27 – A função de impressão não reconhece o fim de caractere
37
ESTRUTURAS DE DADOS
3.3 Caracteres em C
A linguagem C não oferece um tipo de caractere em especial. Os caracteres são representados 
por números inteiros convertidos no momento da exibição. O tipo char armazena valores inteiros do 
tamanho de 1 byte, 8 bits, podendo então representar valores que variam de ‑128 a 127.
 Observação
A correspondência entre os caracteres e seus códigos numéricos é 
feita por uma tabela de códigos, a tabela ASCII, os códigos de 0 até 31 são 
de uso do sistema, ou layout. Alguns de interesse são 0 que corresponde 
ao NULL ou \0, 9 que corresponde ao TAB ou \t e a combinação de 10 
(pulo de linha) com 13 (Volta ao início da linha) corresponde ao \n. Fora 
desta faixa outros importantes são o 32 que é o espaço e o 127 que 
aciona o botão delete.
A tabela completa pode ser obtida executando o programa:
int main (){ 
 for (int i=0;i<32;i++){
 printf(“%5d %5x %5c “,32+i,32+i,32+i);
 printf(“| %5d %5x %5c “,64+i,64+i,64+i);
 printf(“| %5d %5x %5c \n”,96+i,96+i,96+i);
 }
}
Executando a saída é:
32 20 64 40 @ 96 60 `
33 21 ! 65 41 A 97 61 a
34 22 “ 66 42 B 98 62 b
35 23 # 67 43 C 99 63 c
36 24 $ 68 44 D 100 64 d
37 25 % 69 45 E 101 65 e
38 26 & 70 46 F 102 66 f
39 27 ‘ 71 47 G 103 67 g
40 28 ( 72 48 H 104 68 h
41 29 ) 73 49 I 105 69 i
42 2a * 74 4a J 106 6a j
43 2b + 75 4b K 107 6b k
44 2c , 76 4c L 108 6c l
45 2d ‑ 77 4d M 109 6d m
38
Unidade I
46 2e . 78 4e N 110 6e n
47 2f / 79 4f O 111 6f o
48 30 0 80 50 P 112 70 p
49 31 1 81 51 Q 113 71 q
50 32 2 82 52 R 114 72 r
51 33 3 83 53 S 115 73 s
52 34 4 84 54 T 116 74 t
53 35 5 85 55 U 117 75 u
54 36 6 86 56 V 118 76 v
55 37 7 87 57 W 119 77 w
56 38 8 88 58 X 120 78 x
57 39 9 89 59 Y 121 79 y
58 3a : 90 5a Z 122 7a z
59 3b ; 91 5b [ 123 7b {
60 3c < 92 5c \ 124 7c |
61 3d = 93 5d ] 125 7d }
62 3e > 94 5e ^ 126 7e ~
63 3f ? 95 5f _ 127 7f
A diferença entre caracteres e inteiros em C acontece apenas nos tratamentos deles. Assim, a 
impressão de um mesmo valor pode ser feita de duas formas desde que os formatos sejam distintos. 
Por exemplo:
Figura 28 
Resulta em:
Figura 29 – Diferença entre %c e %d na saída
Como o número 97 corresponde ao caractere a, o printf ao mostrar na tela a variável c no formato %d, 
exibe o conteúdo numérico, pois o formato é de número inteiro, e para a letra a, quando encontra o 
formato %c, mostra o caractere correspondente ao número 97.
39
ESTRUTURAS DE DADOS
3.4 Manipulação de strings
A linguagem C, para manipular strings e caracteres, fornece algumas bibliotecas. Essas bibliotecas 
dão algumas funções extras, além do scanf e do printf, para entrada e saída de caracteres e cadeias.
• Função putchar(): a função putchar() (put character) da biblioteca stdio.h imprime um caractere 
na saída‑padrão (em geral, o monitor de vídeo).
• Função getchar(): a função getchar() (get character) da biblioteca stdio.h lê um caractere do 
teclado, ou arquivo. Se ocorrer um erro ou uma condição de “fim de arquivo” durante a leitura, 
a função retornará o valor da constante simbólica end of file (EOF) definida na biblioteca stdio.h, 
permitindo facilmente a detecção de finais de arquivos. Essa função não retorna valores até 
que o caractere de controle enter (\n) seja lido. Exemplo: consta a seguir o uso das funções 
getchar() e putchar() em um programa que lê caracteres do teclado e os reimprime convertidos 
para maiúsculos.
Figura 30 
Figura 31 – Uso do getchar e do putchar
• Função puts(): a função puts() (put string) da biblioteca stdio.h mostra uma string na tela 
acrescentando um enter (\n) ao final.
• Função gets(): a função gets() (get string) da biblioteca stdio.h lê uma string do teclado até o 
operador digitar o enter. Essa instrução é útil e foi substituída pela fgets (variável, tamanho, stdin).
40
Unidade I
Exemplo:
Figura 32 
Testando com o exemplo Joaquim Santos, temos:
Figura 33 – Saída usando fgets e puts
Veremos posteriormente mais funções de manipulações de cadeias em Conceitos de TAD cadeias.
3.5 Modularização
Quando um programa é muito grande, por diversas vezes, torna‑se difícil acompanhar a lógica 
ali empregada. Com o uso das funções, pode‑se dividir tarefas volumosas de computação em tarefas 
menores. A criação de funções evita a repetição de código. Quando um trecho do programa é bastante 
repetido, deve ser transformado em uma função, que será chamada diversas vezes daqueles lugares 
onde aconteciam a repetição do código. Um programa bem‑estruturado deve ser pensado utilizando 
funções externas de seu corpo principal. Tal processo é denominado modularização.
Os programas em C usualmente são montados com uma coleção de várias pequenas funções em 
vez de utilizar poucas de maior tamanho. Na linguagem C, tudo é feito por meio de funções e de suas 
bibliotecas. Algumas já foram utilizadas, como as da biblioteca stdlib.h para entrada e saída de dados.
Sintaxe da função em C:
<tipo de retorno> nome( <tipo1> var1, <tipo2> var2){
 Corpo da função
 return retorno; 
}
O <tipo de retorno> pode ser qualquer um, até os criados por meio do struct. Um caso particular é 
o tipo void (nulo). Isso significa que a função não retornará nenhum valor, ou seja, é um procedimento.
41
ESTRUTURAS DE DADOS
Vejamos na sequência um exemplo de declaração da função fatorial:
Figura 34 
Exemplo de aplicação
Vamos fazer um programa para calcular a quantidade de combinações simples. A fórmula matemática 
da combinação é:
Ca,b= b!(a‑b)!
a!
Assim, tomando a combinação de três elementos dois a dois, temos:
C3,2= = 32!(3‑2)!
3!
Se considerarmos o conjunto de combinações de três letras (a, b, c) tomando‑as duas a duas, 
independentemente da ordem, teremos (a, b), (a, c) e (b, c), ou seja, três.
Para montarmos o programa, como já vimos em laços, podemos montar os três fatoriais a!, b! e (a‑b)! 
e fazer as operações entre eles.
O programa montado da forma tradicional ficará:
Figura 35 
42
Unidade I
Figura 36 – Saída do programa de combinação simples com o programa sem modularização
Para modularizar, analisaremos o programa procurando por repetições de código.
Figura 37 
Podemos observar que o seguinte trecho se repete durante o programa:
 
Figura 38 
É interessante, portanto, transformá‑lo em uma função.
Como o return será fat, um número inteiro, e onde está a interrogação, um número que será o 
fatorial a ser calculado, podemos montar a seguinte função:
43
ESTRUTURAS DE DADOS
Figura 39 
Assim sendo, remontamos o programa agora modularizando a função.
Figura 40 
Figura 41 – Saída do programa de combinação simples com o programa modularizado
 Lembrete
Com o uso de diversas funções em um único programa, muitas vezes entre 
os programadores inexperientes, pode acontecer de programar duas vezes a 
função main(), causando um erro na compilação. Devemos tomar cuidado.
44
Unidade I
3.5.1 Procedimentos e funções
A diferença entre uma função e um procedimento é o fato de a função retornar um valor, mas 
o procedimento não. Formalmente não existem distinções estruturais entre ambos na linguagem C. 
A diferença é que o procedimento é um tipo particular de função cujo retorno é void (nulo), sendo assim 
não é necessário o return no bloco da função.
Durante a programação, a execução sempre começa no main(). Na verdade, ele próprioé uma função. 
O tipo, até agora, é void, e o parâmetro é vazio. Os parâmetros da função main são aqueles passados 
quando um programa é executado na linha de comando e alguns valores são digitados após o nome. 
Dessa maneira, é feita a interação entre o ambiente operacional e o programa.
Observaremos a seguir um programa com uma função e dois procedimentos, lembrando que o main 
no caso, pela declaração void, é um procedimento.
Figura 42 
3.6 Tipos abstratos de dados
Antes de entrarmos em tipos abstratos de dados, devemos estudar como fazer tipos de dados próprios.
3.6.1 Tipo estrutura
Em C, é possível definir um tipo de dado cujos campos são compostos de vários valores de tipos mais 
simples. Uma estrutura, em C, serve basicamente para agrupar diversas variáveis em um único contexto. 
A sintaxe para a definição de uma estrutura será mostrada na sequência:
struct ponto {
 float x;
 float y;
};
45
ESTRUTURAS DE DADOS
Desta forma, a estrutura ponto passa a ser um tipo e então é possível declarar suas variáveis, como 
em qualquer tipo normal visto até agora.
struct ponto p;
A linha de código declara p como sendo uma variável do tipo struct ponto. Os elementos de uma 
estrutura podem ser acessados através do operador de acesso “ponto” (.). Assim, é válido escrever:
p.x=10.0;
p.y=5.0;
A variável p, definida dentro de main, é local como outra qualquer. Quando a declaração é encontrada, 
aloca‑se, na pilha de execução, um espaço para seu armazenamento, isto é, um local suficiente para 
armazenar todos os campos da estrutura (no caso, dois números reais).
3.6.2 Definição de “novos” tipos
A linguagem C permite criar nomes de tipos. Em geral, definimos nomes de tipos para as estruturas 
com as quais nossos programas trabalham. Por exemplo, podemos escrever:
struct ponto {
 float x;
 float y;
};
typedef struct ponto Ponto;
Neste caso, Ponto passa a representar nossa estrutura de ponto, podendo ser utilizado como um tipo 
conforme o programa a seguir:
Figura 43 
46
Unidade I
Por fim, vale salientarmos que é possível definir a estrutura e associar mnemônicos para ela em um 
mesmo comando:
typedef struct ponto {
 float x;
 float y;
};Ponto;
Voltando ao tipo abstrato de dados (TAD), trata‑se da especificação matemática de um conjunto de 
dados e das operações que podem ser executadas sobre eles. Lembrando que:
• O conceito de TAD é dissociado do hardware.
• TAD define o que cada operação faz, mas não como faz.
Assim, um mesmo TAD pode ser concretizado (ou implementado) de diversas formas.
TADs são materializados pelas seguintes estruturas de dados:
• Lista encadeada: implementação com referências.
• Lista com alocação estática: implementação usando array.
Um programa geralmente agrupa muitos tipos e funções com funcionalidades relacionadas, com 
uma finalidade bem definida em um mesmo propósito. Por exemplo, se analisarmos o stdio.h, ele tem 
um conjunto de funções para a manipulação de entradas e saídas. Nos casos em que um módulo 
define um novo tipo de dado e o conjunto de operações para manipular dados desse tipo, dizemos que 
o módulo representa um tipo abstrato de dados (TAD). Nesse contexto, abstrato significa “abstraída a 
forma de implementação”, ou seja, um TAD é descrito pela finalidade do tipo e de suas operações, e não 
pela forma como está implementado.
Por exemplo, será criado um TAD para manipular uma conta bancária definida pela seguinte estrutura:
Figura 44 
Onde o número é o número de uma conta bancária, e o seu saldo atual. Para manipular essa 
estrutura, inicializando, depositando, sacando e mostrando o saldo, são criadas funções, bem como o 
47
ESTRUTURAS DE DADOS
conjunto estrutura e as funções que formam o TAD. Os asteriscos (*), seta (‑>) e o “e” comercial (&) serão 
explicados no item Alocação dinâmica de memória: ponteiros.
Figura 45 
Desta forma, uma função main pode manipular as operações utilizando o TAD:
Figura 46 
Uma boa técnica de programação é implementar os TADs em arquivos separados do programa 
principal. Para isso, geralmente separa‑se a declaração e a implementação em dois arquivos:
• NomeDoTAD.h: com a declaração.
• NomeDoTAD.c: com a implementação.
O programa ou outros TADs que utilizam o seu TAD devem dar um #include no arquivo.h. Assim 
sendo, se quisermos podemos separar o programa anterior nos seguintes arquivos:
48
Unidade I
Figura 47 – Arquivo ContaBancaria.h na pasta \include
Figura 48 – Arquivo ContaBancaria.c na pasta \include
Figura 49 – Arquivo <NomedoArquivoPrincipal>.c na pasta do projeto
49
ESTRUTURAS DE DADOS
Consta a seguir o código completo:
#include <stdio.h>
typedef struct {
 int numero;
 double saldo;
}contabancaria;
void inicia(contabancaria* conta, int numero, double saldo) {
 conta‑>numero = numero;
 conta‑>saldo = saldo;
}
void deposito(contabancaria* conta, double valor) {
 conta‑>saldo += valor;
}
void saque(contabancaria* conta, double valor) {
 conta‑>saldo ‑= valor;
}
void imprime(contabancaria conta) {
 printf(“Numero: %d\n”, conta.numero);
 printf(“Saldo: %f\n”, conta.saldo);
}
int main() {
 contabancaria conta1;
 inicia(&conta1, 1, 0.00);
 printf(“\n Antes da movimentação: \n”);
 imprime(conta1);
 deposito(&conta1, 50.00);
 saque(&conta1, 70.00);
 printf(“\n Depois da movimentação: \n”);
 imprime(conta1);
}
3.6.3 Conceitos de TAD cadeias
A linguagem C possui uma biblioteca reunindo funções que trabalham com a estrutura de cadeias 
(Strings). A seguir serão apresentadas as mais usuais delas.
• Função strlen(): a função strlen() (string length), da biblioteca string.h, retorna o tamanho de 
uma cadeia, conta a quantidade de caracteres até encontrar um nulo (\0) e não o conta. Exemplo: 
o programa retorna o tamanho da cadeia digitada.
50
Unidade I
Figura 50 
A biblioteca usada é a string.h. O resultado é:
Figura 51 – Contando a quantidade de caracteres
• Função strcat(): a função strcat() (string concatenate), da biblioteca string.h, tem duas cadeias 
concatenadas (unidas sequencialmente). Ela recebe duas strings como argumento e copia a 
segunda delas no final da primeira. Exemplo: fazer um programa que junte duas cadeias.
Figura 52
Figura 53 – Fazendo a concatenação de caracteres
• Função strcpy(): o strcpy (string copy) é uma função da biblioteca string.h que copia uma string 
em uma variável. Ao contrário da função strcat, que faz a concatenação, esta sobrescreve a cadeia 
caso ela já tenha um valor armazenado. Exemplo:
51
ESTRUTURAS DE DADOS
Figura 54
Neste programa, a saída é dada por duas variáveis, sendo que nenhuma delas é aquela em que foi 
dada a entrada. O resultado do programa é:
Figura 55 – Saída da função strcpy
• Função strcmp(): na função strcmp() (string compare), da biblioteca string.h, duas cadeias são 
comparadas. A comparação é feita caractere a caractere, até encontrar a primeira diferença entre 
eles; conforme a diferença, a função devolve um valor distinto, usando o seguinte critério:
— < 0, se cadeia1 < cadeia2;
— = 0, se cadeia1 = cadeia2;
— > 0, se cadeia1 > cadeia2. 
Executando o programa a seguir:
Figura 56
52
Unidade I
Cujo resultado será:
Figura 57 – Comparações entre caracteres
 Saiba mais
Para obter acesso a outras funções da biblioteca strings, consulte o 
seguinte livro:
MANZANO, J. A. Linguagem C: acompanhada de uma xícara de café. São 
Paulo: Érica/Saraiva, 2015.
4 ALOCAÇÃO DINÂMICA DE MEMÓRIA: PONTEIROS
Para declarar o vetor de um certo tamanho, é necessário saber de antemão quantos elementos 
iriam compô‑lo. Devemos prever o número de elementos no vetor durante o processo da codificação. 
Seu pré‑dimensionamento é um fator que limita a programação. Uma solução seria superdimensionar 
o vetor para tentar contornar essa limitação, acarretando um desperdício de memória. Esse desperdício 
é inaceitável em diversas situações, como em aplicativos portáteis os quais estão sempre sujeitos a ter 
falta de memória.
A linguagem C oferece meios de utilizar e racionalizar o uso da memóriadurante a execução do 
programa, ou seja, podemos alocar memória dinamicamente. Esse recurso permite ao programa reservar 
elementos ou registros de modo dinâmico, sem desperdício de memória.
4.1 O uso da memória
Basicamente existem três maneiras de reservarmos espaço de memória para o armazenamento de 
informações. São eles:
• Por meio de variáveis globais (e estáticas): o espaço reservado para uma variável existirá 
enquanto o programa estiver sendo executado.
• Por meio de variáveis locais: o espaço na memória existe apenas no período em que a função 
que declarou a variável está sendo executada, sendo liberado assim que a execução terminar. 
Como consequência, a função pai, a que chama, não pode fazer referência ao espaço local da 
função chamada (filho). As variáveis globais ou as locais podem ser simples ou vetores. Para 
53
ESTRUTURAS DE DADOS
os vetores, é necessário informar o número de elementos que ele comporta, caso contrário o 
compilador não terá a informação sobre o tamanho do espaço a ser reservado.
• Por meio de solicitação ao programa que aloque dinamicamente um espaço na memória 
durante sua execução: o espaço alocado permanece reservado até que seja liberado explicitamente 
pelo programa, através de comando específico.
Para o entendimento do processo de uso da memória, executaremos a simulação da execução de 
um programa. Inicialmente observa‑se a memória RAM de um computador sem nenhum programa 
sendo executado.
RAM
Figura 58
Ao executar um programa, o sistema operacional carrega o código compilado na memória, o 
programa é carregado na memória RAM a partir de um HD, ou de um servidor de rede, ou de um SSD e 
outros dispositivos de armazenamento de memória não volátil, byte a byte.
54
Unidade I
RAM
Figura 59 
Uma vez carregado com o código na memória do computador, o sistema passa a ser executado pelos 
comandos carregados na memória.
RAM
Programa
Figura 60 
Na linguagem C, e em outras linguagens fortemente tipadas, a primeira etapa é reservar o 
espaço para as constantes e variáveis globais, pois essas são estáticas, ou seja, não serão criadas 
constantes nem variáveis globais durante a execução do resto do programa, e terão endereço fixo 
facilitando o acesso.
55
ESTRUTURAS DE DADOS
RAM
Variáveis globais e 
constantes
Programa
Figura 61 
Cada vez que uma determinada função é chamada, o sistema reserva o espaço necessário para 
as variáveis locais dela. Simultaneamente, a pilha do controle da chamada das funções armazena a 
sequência de chamada e o estado geral das memórias operacionais. Conforme elas são chamadas, 
a memória livre cresce, e a pilha é atualizada. Quando as funções são encerradas, o sistema consulta 
na pilha o ponto em que a função foi chamada, retornando o programa nele e liberando o espaço das 
variáveis e da pilha criados dinamicamente. O sistema administra de tal forma que as variáveis locais 
sejam criadas e destruídas de modo dinâmico no espaço livre de memória, enquanto a pilha de controle 
utiliza a memória RAM do final para o início.
RAM
Variáveis globais e constantes
Variáveis locais
Memória livre
Pilha
Programa
Figura 62 – As variáveis locais são alocadas e desalocadas no início da 
memória livre, enquanto a pilha de controle das funções ocorre no fim
56
Unidade I
4.2 Variáveis
Antes de falarmos de ponteiro de variáveis, é necessário entendermos o processo envolvido 
quando uma variável é declarada. Para tanto, faremos um ambiente virtual simulando as operações. 
Nele, possuímos a memória RAM, uma vez que cada byte tem um endereço numérico, conforme 
representação a seguir.
Memória RAM
endereço 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120
conteúdo
nome
endereço 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140
conteúdo
nome
endereço 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160
conteúdo
nome
Figura 63 – Simulação da memória RAM
Os dados ocuparão uma quantidade de bytes conforme o tipo da variável armazenada. Para o 
sistema saber onde uma variável está, é feita uma tabela associando o nome ao endereço e o tipo que 
será encontrado naquele lugar.
Tabela das variáveis
nome endereço tipo
Figura 64 – Tabela de variáveis que relaciona o nome ao endereço
Ao fazer a declaração:
int idade;
O administrador de memória localiza um espaço em que é possível ocupar a quantidade de bytes 
necessária para armazenar um número inteiro.
Supondo que uma variável do tipo inteiro ocupe quatro bytes. Na memória RAM a seguir, o espaço 
é localizado no endereço 125.
57
ESTRUTURAS DE DADOS
Memória RAM
endereço 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120
conteúdo
nome
endereço 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140
conteúdo
nome
endereço 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160
conteúdo
nome
espaço livre para ocupar 4 bytes
Figura 65 – Memória RAM já com informações que 
estão ocupando os bytes pintados em preto
Uma vez localizado um espaço livre, a tabela é preenchida com as informações associando o nome, 
o endereço e o tipo da variável.
Memória RAM
endereço 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120
conteúdo
nome
endereço 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140
conteúdo
nome idade
endereço 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160
conteúdo
nome
Tabela das variáveis
nome endereço tipo
idade 125 int
Figura 66 – Situação após a declaração da variável
Assim, sempre que necessário o programa manipular uma variável, o administrador de memória 
consulta a tabela de variáveis para localizar o conteúdo.
4.3 Ponteiro de variáveis
Para entendermos ponteiros, utilizaremos um exemplo no mundo real.
Na cidade de São Paulo existe um edifício famoso, diante do qual acontecem as comemorações 
dos campeonatos conquistados pelos times paulistas, o prédio da Gazeta. Supondo que você não seja 
paulistano, mas deseje comemorar o campeonato do seu time, e precise chegar ao referido local a partir 
58
Unidade I
de um terminal de ônibus, de uma estação de trem ou de um aeroporto, indo de táxi ou Uber, pode pedir 
para que o levem ao prédio da Gazeta, ou então para a Avenida Paulista, n. 900.
Nas diversas disciplinas sempre foi dito em princípio que cada vez que uma variável é declarada, um 
espaço é alocado na memória para armazenar valores. Tecnicamente, é feita mais do que uma simples 
alocação de espaço. Como vimos, o sistema tem uma tabela, em que armazena o nome da variável, o 
endereço e o tipo de valor que será armazenado.
Em muitas ocasiões, seria interessante ter a possibilidade de acessar o conteúdo de um local da 
memória de outras maneiras que não apenas pelo nome, mas pelo endereço de memória, como no exemplo 
do prédio da Gazeta em que é possível ser localizado tanto pelo seu endereço quanto pelo seu nome.
A linguagem C tem uma maneira especial de uma variável armazenar endereços. Ela se chama 
variável ponteiro ou simplesmente ponteiro. A declaração de uma variável é feita da seguinte forma:
<tipo> *nome;
Por exemplo:
int *p;
O programa reserva um espaço na memória para uma variável chamada p, que irá guardar um 
endereço, e esse endereço armazenado conterá uma informação do tipo inteiro. O símbolo * identifica 
que uma variável é do tipo ponteiro. A variável ponteiro sempre irá armazenar um endereço de memória, 
independentemente do seu tipo.
Para acessar os endereços de memória, a linguagem oferece dois operadores unários:
• & (“endereço de”): aplicado a variáveis, resulta no endereço da posição da memória reservada 
para a variável.
• * (“conteúdo de”): aplicado a variáveis do tipo ponteiro, acessa o conteúdo do endereço de 
memória armazenado pela variável ponteiro.
Vamos analisar o seguinte trechode programa:
59
ESTRUTURAS DE DADOS
Figura 67 
Analisando linha a linha, temos:
Ao executar a linha 4, a variável a, como vimos, é criada em um espaço disponível para o tamanho 
de um número inteiro na memória RAM. No exemplo, encontra‑se no endereço 117 e está registrado na 
tabela de variáveis.
Figura 68 
60
Unidade I
Memória RAM
endereço 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120
conteúdo
nome
endereço 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140
conteúdo
nome
endereço 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160
conteúdo
nome
Tabela das variáveis
nome endereço tipo
a 117 int
a
Figura 69 – Criação da variável a na memória RAM
Executando a linha seguinte é criado o ponteiro p, considerando que particularmente neste exemplo 
uma variável que armazena um endereço de memória ocupe três bytes, o administrador de memória 
encontra um espaço no endereço 125. Ele é alocado e a tabela atualizada.
Figura 70 
61
ESTRUTURAS DE DADOS
Memória RAM
endereço 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120
conteúdo
nome
endereço 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140
conteúdo
nome
endereço 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160
conteúdo
nome
Tabela das variáveis
nome endereço tipo
a
*p
117
125
int
int
p
a
Figura 71 – Criando a variável ponteiro p
Na linha 6, para armazenar o valor 5 na variável a, o sistema recorre à tabela e localiza o endereço 
da variável para gravar o valor na memória.
Figura 72 
62
Unidade I
Memória RAM
endereço 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120
conteúdo 5
nome
endereço 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140
conteúdo
nome
endereço 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160
conteúdo
nome
Tabela das variáveis
nome endereço tipo
a
*p
117
125
int
int
p
a
Figura 73 – O valor 5 é armazenado na variável a, localizado no endereço 117
Na variável ponteiro p é armazenado o endereço da variável a. O endereço da variável está na 
própria tabela na coluna endereço. Desta forma, o conteúdo é armazenado no local reservado para o 
ponteiro p na memória RAM.
Figura 74 
63
ESTRUTURAS DE DADOS
Memória RAM
endereço 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120
conteúdo 5
nome
endereço 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140
conteúdo &117
nome
endereço 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160
conteúdo
nome
Tabela das variáveis
nome endereço tipo
a
*p
117
125
int
int
p
a
Figura 75 – Armazenando o endereço de a no ponteiro p
Na linha 8 observa‑se a principal característica do uso de ponteiro. A variável p serve para indicar 
o local a ser alterado. No caso, temos o valor 6 atribuído no local apontado por p, ou seja, no endereço 
que é o conteúdo da variável p.
Figura 76 
64
Unidade I
Memória RAM
endereço 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120
conteúdo 5 6
nome
endereço 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140
conteúdo &117
nome
endereço 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160
conteúdo
nome
Tabela das variáveis
nome endereço tipo
a
*p
117
125
int
int
p
a
Figura 77 – O valor é armazenado no endereço contido na variável ponteiro p
O valor contido na variável a será mostrado já com o conteúdo alterado pela operação *p.
Figura 78 
65
ESTRUTURAS DE DADOS
Memória RAM
endereço 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120
conteúdo 6
nome
endereço 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140
conteúdo &117
nome
endereço 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160
conteúdo
nome
Tabela das variáveis
nome endereço tipo
a
*p
117
125
int
int
p
a
Figura 79 – Localizando o conteúdo da variável a
Na linha 10 é criada uma nova variável inteira. O administrador de memória procura um espaço 
livre (&136), reserva a quantidade de bytes necessária para armazenar um número inteiro (no exemplo, 
4 bytes) e atualiza a tabela.
Figura 80 
66
Unidade I
Memória RAM
endereço 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120
conteúdo 6
nome
endereço 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140
conteúdo &117
nome
endereço 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160
conteúdo
nome
Tabela das variáveis
nome endereço tipo
a
*p
b
117
125
136
int
int
int
p
a
b
Figura 81 – Criando uma nova variável inteira b
Agora o endereço de b é armazenado na variável ponteiro p.
Figura 82 
67
ESTRUTURAS DE DADOS
Memória RAM
endereço 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120
conteúdo 6
nome
endereço 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140
conteúdo &117 &136
nome
endereço 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160
conteúdo
nome
Tabela das variáveis
nome endereço tipo
a
*p
b
117
125
136
int
int
int
p
a
b
Figura 83 – Armazenando o endereço da variável b na memória RAM no ponteiro p
Na linha 12 o valor que está contido na variável a é atribuído no endereço apontado pelo ponteiro 
p (&136), ou seja, o mesmo local da variável b.
Figura 84 
68
Unidade I
Memória RAM
endereço 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120
conteúdo 6
nome
endereço 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140
conteúdo &136 6
nome
endereço 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160
conteúdo
nome
Tabela das variáveis
nome endereço tipo
a
*p
b
117
125
136
int
int
int
p
a
b
Figura 85 – Conteúdo da variável a no endereço %136 apontado por p
Como a atribuição aconteceu no endereço da variável b, podemos sempre utilizar diretamente o 
nome da variável.
Figura 86 
Memória RAM
endereço 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120
conteúdo 6
nome
endereço 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140
conteúdo &136 6
nome
endereço 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160
conteúdo
nome
Tabela das variáveis
nome endereço tipo
a
*p
b
117
125
136
int
int
int
p
a
b
Figura 87 – Mostrando diretamente a variável b
69
ESTRUTURAS DE DADOS
A flexibilidade do uso do ponteiro pôde ser observada, vimos que dá para manipular várias variáveis 
utilizando apenas um ponteiro, dispensando o nome dela e muitas vezes precisamos editar e recompilar 
o programa. Assim sendo, existem duas maneiras através das quais conseguimos trabalhar com memória 
em C: por meio do nome da variável e pelo endereço dela.
4.4 Passagem de parâmetros por valor e por referência
Ao estudarmos funções, vimos que quando passamos informações para dentro delas, fazemos isso 
por meio de parâmetros. Também aprendemos que a função devolve apenas um valor. O uso de ponteiros 
amplia a possibilidade de utilização dos parâmetros e a solução de problemas de retornos múltiplos.
• Quando há passagem de parâmetros por valor, já que conteúdos (e não endereços) são passados 
para as funções.
No exemplo a seguir, os valores de a e b são passados para a função troca.
Figura 88 – Saída mostrando a passagem de parâmetros por valor
O resultado de a é 7 e o de b é 5, pois efetivamente as variáveis a e b são locais da função main().
• Quando os parâmetros passados são endereços, dizemos que foram transmitidos por referência,como no exemplo a seguir.
70
Unidade I
Figura 89 – Saída do programa passando parâmetros por referência
Apesar de os programas serem praticamente idênticos, o resultado é bem diferente, pois o mecanismo 
de trabalho foi bem diverso.
Exemplo de aplicação
Para entender o que acontece em cada um dos casos, faremos o teste de mesa nos próprios códigos.
Passagem por valor
Figura 90 
71
ESTRUTURAS DE DADOS
Memória RAM
endereço 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120
conteúdo
nome
endereço 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140
conteúdo
nome
endereço 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160
conteúdo
nome
Tabela das variáveis
nome endereço tipo
Figura 91 – Início do programa
O programa começa com a memória vazia, executando a função main().
Figura 92 
72
Unidade I
Memória RAM
endereço 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120
conteúdo 7 5
nome
endereço 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140
conteúdo
nome
endereço 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160
conteúdo
nome
Função: main
Tabela das variáveis
nome endereço tipo
a
b
101
105
int
int
a b
Figura 93 – Criação das variáveis a e b do programa
As variáveis a e b da função são criadas e inicializadas. Nesse momento, o programa reserva os 
espaços na memória RAM e atualiza a tabela das variáveis da função main().
Ao encontrar a chamada para a função troca, o programa continua reservando espaço da memória 
para as variáveis que forem aparecendo e cria uma nova tabela para as suas variáveis locais, armazenando 
os parâmetros de entrada. Em seguida, passa a executar a função troca.
Figura 94 
73
ESTRUTURAS DE DADOS
Memória RAM
endereço 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120
conteúdo 7 5 7 5
nome
endereço 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140
conteúdo
nome
endereço 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160
conteúdo
nome
Tabela das variáveis
nome endereço tipo
a
b
101
105
int
int
Tabela das variáveis
nome endereço tipo
a
b
109
113
int
int
Função: main Função: troca
a ab b
Figura 95 – Reserva de memória para as próximas variáveis e criação de tabela para as variáveis locais
Na execução das figuras 97, 99 e 103 é efetuado swap de valores entre as variáveis a e b.
Figura 96 
74
Unidade I
Memória RAM
endereço 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120
conteúdo 7 5 7 5
nome a b a b temp
endereço 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140
conteúdo
nome
endereço 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160
conteúdo
nome
Tabela das variáveis
nome endereço tipo
a
b
101
105
int
int
Tabela das variáveis
nome endereço tipo
a
b
temp
109
113
117
int
int
int
Função: main Função: troca
Figura 97 – O programa cria a variável temp do programa
Figura 98 
75
ESTRUTURAS DE DADOS
Memória RAM
endereço 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120
conteúdo 7 5 7 5 7
nome a b a b temp
endereço 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140
conteúdo
nome
endereço 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160
conteúdo
nome
Tabela das variáveis
nome endereço tipo
a
b
101
105
int
int
Tabela das variáveis
nome endereço tipo
a
b
temp
109
113
117
int
int
int
Função: main Função: troca
Figura 99 – Temp recebe o conteúdo da variável a da função troca
Figura 100 
76
Unidade I
Memória RAM
endereço 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120
conteúdo 7 5 5 5 7
nome a b a b temp
endereço 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140
conteúdo
nome
endereço 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160
conteúdo
nome
Tabela das variáveis
nome endereço tipo
a
b
101
105
int
int
Tabela das variáveis
nome endereço tipo
a
b
temp
109
113
117
int
int
int
Função: main Função: troca
Figura 101 – A variável a recebe o conteúdo da variável b da função troca
Figura 102 
77
ESTRUTURAS DE DADOS
Memória RAM
endereço 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120
conteúdo 7 5 5 7 7
nome a b a b temp
endereço 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140
conteúdo
nome
endereço 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160
conteúdo
nome
Tabela das variáveis
nome endereço tipo
a
b
101
105
int
int
Tabela das variáveis
nome endereço tipo
a
b
temp
109
113
117
int
int
int
Função: main Função: troca
Figura 103 – A variável b recebe conteúdo de temp troca
A variável b recebe o conteúdo da variável temp da função troca, e o programa retorna para o ponto 
de chamada da função main().
Memória RAM
endereço 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120
conteúdo 7 5 5 5 7
nome
endereço 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140
conteúdo
nome
endereço 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160
conteúdo
nome
Tabela das variáveis
nome endereço tipo
a
b
101
105
int
int
Tabela das variáveis
nome endereço tipo
a
b
temp
109
113
117
int
int
int
Função: main Função: troca
a b
Figura 104 – Exibição dos conteúdos de a e b da função main() troca
78
Unidade I
A função main() exibe os conteúdos das variáveis a e b da própria função main(), os valores que 
estão nos endereços da tabela de variáveis: main.
Figura 105 
Passagem por referência
Figura 106 – Saída do programa passando parâmetros por referência
Apesar de os programas serem praticamente idênticos, o resultado é diferente, pois o mecanismo de 
trabalho ocorre de modo diverso.
O programa começa com a memória vazia, executando a função main().
79
ESTRUTURAS DE DADOS
Figura 107 
Memória RAM
endereço 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120
conteúdo 7 5
nome
endereço 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140
conteúdo
nome
endereço 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160
conteúdo
nome
Tabela das variáveis
nome endereço tipo
a
b
101
105
int
int
Função: main
a b
Figura 108 – Inicialização, criação e inicialização das variáveis a e b
As variáveis a e b da função são criadas e inicializadas. Nesse momento, o programa reserva os 
espaços na memória RAM e atualiza a tabela das variáveis da função main(). Até esse ponto, os dois 
programas são iguais.
80
Unidade I
Figura 109 
Na chamada da função começam as diferenças. A função troca recebe dois ponteiros do tipo 
inteiro, lembrando sempre que ponteiros armazenam endereços e não valores absolutos. Na passagem 
de parâmetro, são endereços de memória, em virtude do operador unário & aplicado em a e em b. 
Assim, os parâmetros *a e *b da função troca são os endereços das variáveis a e b do programa pai.
Memória RAM
endereço 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120
conteúdo 7 5 &101 &105
nome
endereço 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140
conteúdo
nome
endereço 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160
conteúdo
nome
Tabela das variáveis
nome endereço tipo
a
b
101
105
int
int
Tabela das variáveis
nome endereço tipo
a
b
109111
int
int
Função: main Função: troca
a *ab *b
Figura 110 – Passagem da referência como parâmetro
Na função troca, a variável temporária temp é criada, depois ela receberá o valor contido no 
endereço apontado pelo parâmetro *a. Já se observa a diferença em relação à passagem por valor.
81
ESTRUTURAS DE DADOS
Figura 111 
Memória RAM
endereço 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120
conteúdo 7 5 &101 &105 7
nome
endereço 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140
conteúdo
nome
endereço 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160
conteúdo
nome
Tabela das variáveis
nome endereço tipo
a
b
101
105
int
int
Tabela das variáveis
nome endereço tipo
a
b
temp
109
111
113
int
int
int
Função: main Função: troca
a b *b temp*a
1
2
Figura 112 – *a aponta para o endereço &105 e o seu conteúdo é atribuído à variável temp
Na linha 4, o conteúdo do endereço apontado por *b da função troca é armazenado no local 
apontado por *a, também da função troca, ou seja, no endereço &101, e receberá o valor que está no 
endereço &105.
82
Unidade I
Figura 113 
Memória RAM
endereço 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120
conteúdo 5 5 &101 &105 7
nome
endereço 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140
conteúdo
nome
endereço 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160
conteúdo
nome
Tabela das variáveis
nome endereço tipo
a
b
101
105
int
int
Tabela das variáveis
nome endereço tipo
a
b
temp
109
111
113
int
int
int
Função: main Função: troca
a b *b temp*a
Figura 114 – O conteúdo do endereço apontado por *b é armazenado no endereço apontado por *a
A seguir, o conteúdo da variável auxiliar temp é atribuído no local apontado pela variável *b da 
função troca. O programa retorna para o ponto em que foi chamado.
83
ESTRUTURAS DE DADOS
Figura 115 
Memória RAM
endereço 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120
conteúdo 5 7 &101 &105 7
nome
endereço 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140
conteúdo
nome
endereço 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160
conteúdo
nome
Tabela das variáveis
nome endereço tipo
a
b
101
105
int
int
Tabela das variáveis
nome endereço tipo
a
b
temp
109
111
113
int
int
int
Função: main Função: troca
a b *b temp*a
Figura 116 – O conteúdo de temp é copiado para o endereço apontado por *b
No retorno da função as variáveis estão trocadas, pois as operações são realizadas entre os locais 
referenciados pelos parâmetros passados para a função.
84
Unidade I
Figura 117 
Memória RAM
endereço 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120
conteúdo 5 7 &101 &105 7
nome
endereço 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140
conteúdo
nome
endereço 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160
conteúdo
nome
Tabela das variáveis
nome endereço tipo
a
b
101
105
int
int
Tabela das variáveis
nome endereço tipo
a
b
temp
109
111
113
int
int
int
Função: main Função: troca
a b *b temp*a
Figura 118 – As variáveis estão com os valores trocados, pois foram manipuladas por referência
Observamos que a passagem de valor por referência permite que variáveis criadas em uma função 
possam ser alteradas fora dela. Essa técnica se torna importante, principalmente, pelo fato de a 
linguagem C não passar vetores e matrizes como parâmetros de uma função.
85
ESTRUTURAS DE DADOS
4.5 Passagem de vetores para funções
Como dito, a linguagem C não passa vetores como parâmetros de função; assim, a maneira para fazê‑lo 
é passando o endereço da primeira posição do vetor. Dessa forma, para receber um vetor, é necessário 
colocar uma declaração do mesmo tipo do vetor com a indicação de que é um ponteiro (usando o *).
Exemplo de aplicação
Dado o programa a seguir:
Figura 119 
Na função main() é criado o vetor inteiro a[], que será passado como parâmetro para a função 
incrvetor(). Assim, ela recebe o endereço do vetor a pela declaração int * v. Nesse caso, o vetor é chamado 
de a na função main() e, dentro da função incrvetor(), é tratado como v. Como ambos têm o mesmo 
endereço, eles se referem ao mesmo espaço de memória.
O resultado desse programa será mostrado na sequência:
Figura 120 – Saída do programa com passagem de vetor na função
O programa passa como parâmetros um número e um vetor; ele tem como elementos os valores 
{1, 3, 5}. A função recebe o endereço do vetor, refere‑se a ele como v e acrescenta 1 a cada elemento; 
assim, o vetor é alterado para {2, 4, 6}.
 Lembrete
As bibliotecas de funções são anexadas ao programa principal pela 
diretiva de pré‑compilação #include. Vimos até agora as bibliotecas 
stdio.h e string.h.
86
Unidade I
4.6 Funções da biblioteca padrão
Muitas vezes o uso de vetores e matrizes fica limitado pelo fato de sabermos antecipadamente 
a quantidade de elementos que serão necessários. O interessante seria ter condições de criar e 
destruir elementos sem a limitação (apenas da máquina, e não da lógica), conforme forem surgindo 
as necessidades.
A biblioteca stdlib.h possui algumas funções que permitem criar e trabalhar dinamicamente, ou seja, 
durante a execução de um certo trecho do programa. Para isso, é preciso entender certas funções e 
utilizar os conhecimentos do uso da memória.
A melhor forma de usar todas as funções necessárias é mostrando‑as manualmente, como no 
seguinte trecho:
int *v;
v = (int*) malloc(10*sizeof(int));
Que é equivalente a simplesmente declararmos:
int v[10];
Para estudarmos esse trecho com detalhes, analisaremos minuciosamente como é escrita a 
segunda linha.
Começaremos com a memória fictícia vazia, iniciada pelo endereço &101 e, logo na sequência, 
veremos a tabela de variáveis sem nenhuma declaração.
Memória RAM
endereço 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120
conteúdo
nome
endereço 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140
conteúdo
nome
endereço 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160
conteúdo
nome
Tabela das variáveis
nome endereço tipo
Figura 121 – Memória fictícia vazia, começando do endereço &101
87
ESTRUTURAS DE DADOS
Para termos uma referência, a variável ponteiro é criada. Ela apontará o vetor originado.
int *v;
Memória RAM
endereço 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120
conteúdo
nome
endereço 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140
conteúdo
nome
endereço 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160
conteúdo
nome
Tabela das variáveis
nome endereço tipo
*v 101 int
Figura 122 – O ponteiro *v alocado na posição &101
A primeira função é malloc(int), memory allocation ou alocação de memória. Ela reserva a quantidade 
de bytes que é passada como parâmetro e retorna o endereço em que esse espaço de memória foi 
reservado. Assim, para reservar o espaço de dez elementos inteiros a um vetor, supondo que cada valor 
inteiro use 2 bytes (esse é um número puramente fictício, pois cada compilador pode ter um tamanho 
diferente), será necessário reservar 20 bytes de memória.
malloc(20)
Considerando que 20 é 10* 2, dez elementos de 2 bytes. Ao utilizar a função, o administrador de 
memória reserva a quantidade de bytes livres contíguos passados pelo parâmetro. No exemplo, malloc 
encontra um endereço livre no endereço &103, a função devolverá o valor correspondente ao local em 
que o espaço foi reservado.
Ao tentar alocar o valor retornado pela função em uma variável ponteiro, temos:
V=malloc(20);
O programa irá acusar erro, pois existe um conflitode informações: a variável v é um ponteiro 
apontando para inteiro, e o espaço de memória não corresponde à informação de um valor inteiro. 
Na prática, a função malloc retorna um número inteiro (int) correspondente ao endereço, enquanto a 
variável v foi declarada com int *.
Voltando ao número 20, não é sabido ao certo se a quantidade de bytes que uma variável do tipo 
inteiro ocupa é realmente 2 bytes. Para descobrir quantos bytes um tipo ocupa, a biblioteca nos fornece 
a função sizeof(tipo), tamanho de.
88
Unidade I
sizeof(<tipo>)
Essa função retorna um número inteiro que um tipo ocupa, seja ele preexistente, seja criado por nós 
com o typedef. Assim, por exemplo, ao fazer:
sizeof(int)
A função devolverá o número de bytes utilizados por uma variável do tipo int. Conforme o compilador, 
o sistema operacional de um mesmo tipo pode ter tamanhos diferentes. Portanto, para montar um vetor 
de 10 elementos inteiros, bastará fazer:
10 * sizeof(int)
Assim, para reservar o espaço, utilizamos a expressão como parâmetro da função malloc().
malloc(10 * sizeof(int))
Obter automaticamente o tamanho correto para alocarmos 10 números inteiros independe 
do compilador.
Memória RAM
endereço 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120
conteúdo
nome
endereço 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140
conteúdo
nome
endereço 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160
conteúdo
nome
Tabela das variáveis
nome endereço tipo
*v 101 int
Figura 123 – Alocado espaço para 10 números inteiros
O espaço alocado está de acordo com o espaço independente do compilador (no exemplo, 2 bytes 
cada), mas voltando ao problema de armazenarmos o endereço do local reservado, temos:
v=malloc(10 * sizeof(int))
O erro persiste, pois v é um ponteiro inteiro, e o endereço devolvido pelo malloc é um valor absoluto.
89
ESTRUTURAS DE DADOS
Anteriormente, vimos a conversão de tipos, o chamado cast. Ela irá compatibilizar o valor absoluto 
retornado pelo malloc no tipo correspondente do ponteiro. Nesse caso, é possível afirmar que é feita a 
formatação ou a adequação do espaço alocado. Como o tipo de v é um ponteiro inteiro, aplicaremos 
a conversão do tipo int *.
Assim, a expressão fica:
int *v;
v=(int *)malloc(10*sizeof(int));
A informação devolvida pelo malloc fica completa: com um endereço e o tipo correspondente. Ao 
aplicarmos o cast, a memória ficará assim:
Memória RAM
endereço 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120
conteúdo &103
nome
endereço 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140
conteúdo
nome
endereço 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160
conteúdo
nome
Tabela das variáveis
nome endereço tipo
*v 101 int
Figura 124 – Cast aplicado dividindo o espaço reservado pelo malloc em inteiros
O programa a seguir monta manualmente o vetor v. Nele, é criado um vetor sem a necessidade de 
declaração int v[10].
Figura 125 
90
Unidade I
Memória RAM
endereço 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120
conteúdo &103 13 23
nome
endereço 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140
conteúdo
nome
endereço 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160
conteúdo
nome
Tabela das variáveis
nome endereço tipo
*v 101 int
*v v[0] v[1] v[2] v[3] v[4]
v[8]v[6] v[7] v[9]v[5]
Figura 126 – Situação após a execução do programa, criando o vetor manualmente
Utilizando a criação manual, o valor 10 no exemplo pode ser substituído por uma variável. Conforme 
o valor dela, é possível um vetor em um mesmo código fonte ter tamanhos diferentes.
Para liberar um espaço de memória alocado dinamicamente, utiliza‑se a função free da biblioteca 
stdlib.h. Ela recebe como parâmetro o ponteiro da memória a ser liberada e o espaço alocado é liberado 
para outros usos futuros. Assim, a fim de liberar o vetor v, é colocado no programa:
free(v);
Memória RAM
endereço 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120
conteúdo &103
nome *v
endereço 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140
conteúdo
nome
endereço 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160
conteúdo
nome
Tabela das variáveis
nome endereço tipo
*v 101 int
Figura 127 – Espaço liberado pelo free()
91
ESTRUTURAS DE DADOS
Uma vez definida a estrutura do vetor e seu espaço na memória RAM, os valores são armazenados 
nos espaços múltiplos a partir do endereço inicial. Observe que supondo o tamanho do tipo float como 
4 bytes (sizeof(float)), se o endereço inicial for 121 (121 + 0 × 4), o próximo será 125 (121 + 1 × 4), o 
terceiro elemento 129 (121 + 2 × 4), e assim por diante.
Memória RAM
endereço 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120
conteúdo &121
nome *v
endereço 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140
conteúdo 5.5 6.0 7.0 2.5 10.0
nome v+0 x sizeof(float) v+1 x sizeof(float) v+2 x sizeof(float) v+3 x sizeof(float) v+4 x sizeof(float)
endereço 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160
conteúdo 9.5 8.0
nome v+5 x sizeof(float) v+6 x sizeof(float)
Tabela das variáveis
nome endereço tipo
*v 103 int
δr u Ц միꝘ Щ ملسو هيلع هللا ىلص
Figura 128 – Valores armazenados no vetor
Pode‑se notar que o valor multiplicando o sizeof() corresponde ao índice de um vetor. A partir desta 
operação, podemos chegar ao conceito de aritmética de ponteiros.
 Observação
O ponteiro também tem uma aritmética própria. Ao fazermos a soma 
de um número inteiro a um ponteiro, ele apontará para o endereço 
com o avanço de múltiplos correspondentes ao tamanho do tipo 
definido para ele.
Assim, no exemplo anterior podemos ter as seguintes equivalências:
Quadro 10 – Relação entre o ponteiro e o índice de um vetor
Expressão Ao fazer a expressão Equivale ao fazer a expressão
int *x = v+0 printf(“%d”,*x) printf(“%d”,v[0])
int *x = v+1 printf(“%d”,*x) printf(“%d”,v[1])
int *x = v+2 printf(“%d”,*x) printf(“%d”,v[2])
int *x = v+3 printf(“%d”,*x) printf(“%d”,v[3])
int *x = v+4 printf(“%d”,*x) printf(“%d”,v[4])
92
Unidade I
 Lembrete
O compilador ignora os comentários.
 Resumo
Nesta unidade tivemos como objetivo uma breve introdução à 
linguagem C, e por meio dela estudamos os conceitos fundamentais tanto 
na programação quanto nos dados. Vimos alguns conceitos da análise dos 
algoritmos e as técnicas para o uso da memória RAM.
Na análise de um algoritmo, focamos no cálculo do custo de execução, 
revimos certos conceitos de modularização. Da modularização evoluímos 
para o uso de várias funções que manipularão um conjunto de dados, 
conhecido como tipo abstrato de dados (TAD).
No item TAD também foi estudada a criação para agrupar e modelar 
novos tipos de dados por meio de estruturas.
Além das estruturas, em relação aos dados, foi feita a revisão das 
matrizes e vetores, bem como o seu uso como cadeia de caracteres. Uma 
nova técnica de armazenamento de dados foi apresentada, o ponteiro de 
variáveis, que vai muito além da declaração de variáveis. Para entendê‑la, 
foi vista a administração dinâmica da memória RAM. O ponteiro é um tipo 
especial de variável que armazena o endereço de memória. Trata‑se de 
uma ferramenta que permite a manipulação de variáveis e que servirá para 
novas técnicas, ampliando o horizonte da manipulação de dados.
93
ESTRUTURAS DE DADOS
 Exercícios
Questão 1. Leia o texto a seguir, a respeito da linguagem de programação C.
A linguagem C é uma das mais bem‑sucedidas linguagens de alto nível já criadas, sendo considerada 
uma das mais utilizadas de todos os tempos. Define‑se como linguagem de alto nível aquela que 
apresenta um nível deabstração relativamente elevado, que está mais próximo da linguagem humana 
do que do código de máquina. Ela foi criada em 1972, nos laboratórios Bell, por Dennis Ritchie, sendo 
revisada e padronizada pelo ANSI (American National Standards Institute), em 1989.
Trata‑se de uma linguagem estruturalmente simples e de grande portabilidade. Poucas são as 
arquiteturas de computadores para as quais não exista um compilador C. Além disso, o compilador da 
linguagem gera códigos mais enxutos e velozes do que muitas outras linguagens.
Trata‑se de uma linguagem procedural, ou seja, permite que um problema complexo seja facilmente 
decomposto em módulos, sendo cada módulo um problema mais simples. Além disso, ela fornece 
acesso de baixo nível à memória, o que possibilita o acesso e a programação direta do microprocessador. 
Ela também permite a implantação de programas utilizando instruções em Assembly, o que viabiliza 
programar problemas em que a dependência do tempo é crítica.
Por fim, a linguagem C foi criada para incentivar a programação multiplataforma, ou seja, programas 
escritos em C podem ser compilados para uma grande variedade de plataformas e sistemas operacionais 
com apenas pequenas alterações no seu código‑fonte.
Todo programa escrito em linguagem C que vier a ser desenvolvido deve ter o esqueleto mostrado 
no código‑fonte da figura a seguir.
Figura 129 
Adaptada de: BACKES, A. Linguagem C: completa e descomplicada. Rio de Janeiro: LTC, 2021.
94
Unidade I
Com base na leitura e em seus conhecimentos, avalie as afirmativas a seguir.
I – O código de um programa em C precisa, necessariamente, conter um bloco da função main().
II – O tipo de dado char é um tipo primitivo da linguagem C.
III – A scanf() é uma função, declarada no arquivo de cabeçalho stdio.h, que serve para imprimir 
valores em tela.
É correto o que se afirma em:
A) I, apenas.
B) II, apenas.
C) I e II, apenas.
D) II e III, apenas.
E) I, II e III.
Resposta correta: alternativa C.
Análise das afirmativas
I – Afirmativa correta.
Justificativa: ao programarmos em C, precisamos inserir, de forma obrigatória, uma função main(). 
Outras funções podem ser inseridas no código, mas a main() é a única obrigatória. Considerando um 
código com diversas funções, a main() servirá como o ponto de partida para a execução do programa. 
Em geral, ela coordena a execução, direcionando as chamadas para outras funções ao longo da execução 
das instruções do programa.
II – Afirmativa correta.
Justificativa: a linguagem C traz definidos os seguintes tipos de dados primitivos: char, int, float e 
double. No código, eles podem vir acompanhados dos modificadores signed, unsigned, short e long.
III – Afirmativa incorreta.
Justificativa: a função scanf() é encontrada no arquivo de cabeçalho stdio.h, mas atua como função 
de entrada de dados. Ela captura e armazena valores fornecidos via teclado. A função descrita no texto 
da afirmativa é a printf(), que é uma função de saída de dados.
95
ESTRUTURAS DE DADOS
Questão 2. Leia o texto a seguir, que demonstra a importância de uma estrutura de dados, 
implementada em linguagem C.
Imagine o seguinte problema: leia as notas de uma turma de cinco estudantes e depois imprima 
aquelas que forem maiores do que a média da turma. Um programa simples para resolver esse problema 
poderia ser o apresentado na figura a seguir.
Figura 130 
O programa anterior representa, de fato, uma solução possível para o problema. Porém, os 
inconvenientes dessa solução são a grande quantidade de variáveis para gerenciar e o uso repetitivo de 
comandos praticamente idênticos, como é o caso dos comandos if().
Expandir o programa anterior para trabalhar com uma turma de 100 alunos significaria, basicamente, 
aumentar o número de variáveis para guardar as notas de cada aluno e repetir, ainda mais, o conjunto 
de comandos praticamente idênticos. Surge, então, a necessidade de usar uma estrutura de dados.
Pensando no exemplo anterior, poderíamos usar uma estrutura contendo 100 elementos para 
guardar as notas dos 100 alunos. Ela seria declarada conforme mostrado a seguir.
float notas[100];
Adaptado de: BACKES, A. Linguagem C: completa e descomplicada. Rio de Janeiro: LTC, 2021.
A respeito da estrutura de dados apresentada no texto como solução para o problema, avalie as 
afirmativas a seguir.
96
Unidade I
I – A estrutura é um array, traduzido para o português como vetor.
II – O comando apresentado ao final do texto define um array de nome notas, contendo 
100 elementos adjacentes na memória. Cada elemento do array é do tipo float.
III – O vetor é uma estrutura de dados linear que necessita de somente um índice para que seus 
elementos sejam endereçados.
É correto o que se afirma em:
A) I, apenas.
B) II, apenas.
C) I e II, apenas.
D) II e III, apenas.
E) I, II e III.
Resposta correta: alternativa E.
Análise das afirmativas
I – Afirmativa correta.
Justificativa: trata‑se de um array (ou vetor), uma estrutura que cria um conjunto de variáveis do 
mesmo tipo utilizando apenas um nome. No exemplo do código da figura, as variáveis que guardam as 
notas dos alunos são todas do mesmo tipo. Usar um array permitiria utilizar apenas um nome (notas, por 
exemplo) de variável para representar todas as notas dos alunos, em vez de um nome para cada variável.
Em linguagem C, a declaração de um array possui a seguinte forma geral:
tipo_dado nome_array[tamanho];
Este comando define um array de nome nome_array contendo tamanho e elementos adjacentes na 
memória. Cada elemento do array é do modelo tipo_dado.
II – Afirmativa correta.
Justificativa: o comando apresentado ao final do texto é replicado a seguir:
float notas[100];
97
ESTRUTURAS DE DADOS
Ao compararmos o comando do texto com a forma geral, apresentada na justificativa da afirmativa I, 
temos uma troca de tipo_dado por float, nome_array por notas e [tamanho] por [100]. Isso significa que 
foi declarado um vetor de nome notas, que abriga 100 elementos do tipo float.
III – Afirmativa correta.
Justificativa: um array de n elementos endereça cada um de seus elementos por um índice i, que 
começa em 0 e termina em n-1. Desse modo, um vetor de 5 elementos terá endereços que vão de 0 a 4, 
cada um deles apontando para um item.

Mais conteúdos dessa disciplina