Sei sulla pagina 1di 220

iai?

INSTITUTO DE ARTES INTERATIVAS


Apostila Lógica de
Programação
Tópicos
• Introdução a programação

• Arquitetura de computadores

• Números binários e codificação de informação

• Linguagens de programação

• Hello World!

• Estrutura de arquivos: .h .m .xib

• Cocoa - Objective-C

• Exercícios
Tópicos 2
• Variáveis e tipos de dados

• Expressões, operadores e operandos

• Exemplos de straight line programs

• Controle de fluxo: IF/ELSE e SWITCH

• Ciclos: while, do/while, for

• Diagramas de fluxo e simulação manual

• Debugging

• Vetores e C Strings
Tópicos 3
• Alocação dinâmica de memória

• Estruturas

• Funções

• The C Stardard library

• Algoritmos, estruturas de dados e complexidade

• Funções recursivas

• Programas multi-arquivo

• Vetores e C Strings
Tópicos 4

• C++

• Classes

• Construtores e destrutores

• Subclasses

• Polimorfismo
Tópicos 5

• Threads e concorrência

• Callbacks e delegates, event driven programming

• Visual application design

• Bancos de dados
Tópicos 6

• Objective-C

• iPhone
Objetivos do curso

๏ Ajudar a pensar como um cientista da computação


๏ Programar para resolver problemas
๏ Habilidades:
๏ Usar ferramentas básicas de computação para fazer simples
programas
๏ transformar a descrição de um problema em uma solução
computacional
๏ aprender a ler programas escritor por outros
๏ aprender os limites da computação, assim como as capacidades.
Brincadeira do desenho
Programa

๏ Um programa é uma sequência de instruções que serve para fazer


alguma coisa.
๏ Nesse sentido, por exemplo, uma receita de cozinha é um
programa feito em uma linguagem que cozinheiros possam
entender.
๏ Nós teremos que fazer programas em uma linguagem que um
computador entenda.
๏ Mas então, o que um computador entende?
Computadores

๏ Começamos com computadores que faziam sempre a mesma


coisa, por exemplo uma calculadora. Estes são chamados de
“Fixed program computer”.
๏ Suponha agora que exista um circuito que leia como entrada o
diagrama de outro circuito e que possa re-arrumar a si mesmo
para reproduzir tal circuito.
๏ Isso existe e se chama Stored Program Computer, ou seja um
computador para o qual eu forneço instruções sobre como fazer
uma computação.
Arquitetura de um PC

PC (Program Counter)
Personal Computer

๏ Faz operações simples, como pegar dois valores da memória,


passar para a ALU multiplicar e escrever o resultado de volta na
memória.
๏ Algumas instruções envolvem testes, em base aos quais o valor
do PC é alterado.
๏ Existe uma idéia de controle de fluxo, ou seja como se mexer
pela sequência de instruções do programa.
๏ Um programa então é uma receita que usa estas poucas
operações primitivas.
Números binários

๏ A memória de um computador é composta por bits, ou seja


celulas de memória que podem armazenar somente dois valores,
ou 0 ou 1.
๏ Estes bits são organizados em grupos de 8, chamados bytes.
๏ Por exemplo, um byte poderia ser 10010111.
๏ Se um bit pode armazenar dois valores diferentes, quantos
podem ser armazenados em um byte, ou em n bits?
A resposta é 2n, ou 28 no caso de um byte, ou seja 256 números
diferentes, de 0 a 255.
๏ Lembrar que em computação se começa sempre a contar de 0!
Números binários-conversão

๏ Como podemos saber a que número (decimal) corresponde o


byte 10010111? É suficiente alinhar o byte com os potências de 2:
1 0 0 1 0 1 1 1 e depois multiplicar os bits pelas
27 26 25 24 23 222120 correspondentes potências de 2:
๏ 1 * 27 + 0 * 26 + 0 * 25 + 1 * 24 + 0 * 23 + 1 * 22 + 1 * 21 + 1 * 20=
1 * 128 + 1 * 16 + 1 * 4 + 1 * 2 + 1 * 1 =
128 + 16 + 4 + 2 + 1 = 151
๏ Como podemos fazer o contrário, converter de decimal para
binário?
Números binários-conversão

๏ Por exemplo, a qual número binário corresponde o número


decimal 225?
๏ Começamos escrevendo as potências de 2:
128 64 32 16 8 4 2 1
๏ Colocamos um 1 na posição correspondente à maior potência de
2 que seja menor que o número que estamos querendo converter
(224), neste caso o 128.
๏ Subtraímos esta potência de 2 e repetimos para as outras:
๏ 1 -> (225 - 128 = 97)
๏ 11 -> (97 - 64 = 33)
๏ 111 - > (33 - 32 = 1)
๏ 11100001
Codificação

๏ Aprendemos então que a única coisa que computadores sabem


tratar são bits.
๏ Mas então como podem lidar com texto, imagens, sons, etc?
๏ Codificando as informações em bits e bytes.
๏ Por exemplo para testo, existe a tabela ASCII que estabelece
uma correspondência entre todos os caracteres digitáveis e
bytes. Por exemplo ‘A’ corresponde ao byte 65, o carácter 7 ao
byte 55, a letra ‘b’ ao byte 98.
Codificação
Letra Decimal Hexadecimal Binário
B 66 42 0100 0010
r 114 72 0111 0010
u 117 75 0111 0101
n 110 6E 0110 1110
o 111 6F 0110 1111
32 20 0010 0000
E 69 45 0100 0101
i 105 69 0110 1001
t 116 74 0111 0100
i 105 69 0110 1001
Codificação

‣ E para imagens?
๏ No caso de imagens digitais, elas são feitas de pixels, ou seja
pontinhos coloridos que juntos compõe a imagem.
๏ Cada pixel é geralmente representado usando 3 bytes, um para
cada uma das 3 cores básicas, vermelho, verde e azul, onde cada
byte indica a intensidade de cada uma das 3 cores básicas.
๏ Assim, juntanto vários pixels, obtemos uma imagem.
Codificação

๏ E para sons?
๏ Sons digitais tem que ser discretizados, mas sabemos que sons
são ondas de pressão do ar.
๏ O que fazemos é medir esta pressão em vários momentos no
tempo (44100 por segundo, para o CD audio) e armazenamos
estes valores de pressão como grupos de, como no caso do CD
áudio, 2 bytes.
Codigos

๏ Mas o que é um bom código? É um código que nos permita voltar


o mais perto possível da informação original.
๏ Por exemplo, se eu quiser fazer um código para armazenar as
letras a, b, c e d, eu poderia tentar:
๏ a=0
b=1
c = 01
d = 11
๏ Este é um bom código?
Codigos

๏ Não é um bom código.


๏ Se eu escrever por ex 0100, isto pode ser abaa, mas também caa,
ou seja não sei mais voltar ao original.
๏ No caso da tabela ASCII, temos um código de tamanho fixo, ou
seja cada carácter usa o mesmo número de bits, assim, lendo um
byte por vez, não temos dúvidas sobre como recompor o texto
original.
๏ Existem outros bons códigos que não são de tamanho fixo, por
exemplo códigos prefixos.
Instruções primitivas

๏ Afinal, quais instruções podemos usar em nossos programas?


๏ Alan Turing, com seu modelo teórico de computador, provou que
pode-se fazer tudo que qualquer computador hoje faz e sempre
fará com somente 6 instruções primitivas.
๏ Neste sentido, todos os computadores e linguagens de
programação são equivalentes.
๏ Neste curso naturalmente não vamos nos limitar a usar só essas
6 primitivas, mas é interessante saber que tudo pode ser feito
com 6 instruções.
Linguagens de programação

‣ Podem ser:
๏ De alto ou baixo nível
๏ Gerais ou direcionadas
๏ Interpretadas ou compiladas
๏ Uma linguagem de baixo nível é a linguagem máquina, ou
assembly, que depende do modelo de CPU e é entendida e
executada direto pela CPU.
๏ Linguagens de alto nível são linguagens mais naturais, como
C, C++, Java, Objecti-C
Linguagens de programação

๏ Linguagens gerais são linguagens que se adaptam bem a


resolver qualquer problema, como todas as que já
mencionamos.
๏ Linguagens direcionadas servem somente para resolver alguns
tipos de problemas, por exemplo SQL para banco de dados
ou MatLab para calculos matemáticos.
Linguagens de programação

๏ Uma linguagem interpretada precisa de um Interpretador ou


máquina virtual para poder funcionar. O interpretador lê o
programa e o executa na CPU. Isso torna a execução mais
lenta, mas em compensação o mesmo programa roda em
qualquer máquina para a qual exista um interpretador.
Exemplo: Java
๏ Uma linguagem compilada tem que ser compilada ou
“traduzida” para a linguagem máquina antes de ser executada.
O programa que compila se chama COMPILADOR. Exemplo:
C, C++, Objective-C
Componentes de uma linguagem

๏ Sintaxe: “cachorro gato criança”. Quais programas estão


escritos corretamente? (muita ajuda)
๏ Semântica estática: “Esta mesa é Lucas”. Quais programas
tem significado? (alguma ajuda)
๏ Semântica: O que esperamos que um programa faça?
(nenhuma ajuda)

๏ Vamos fazer o nosso primeiro programa em C


O sistema antigo de programar

๏ Abrir um terminal (MAC ou Linux)


๏ Navegar até uma pasta.
๏ Digitar vim helloworld.c
๏ Isto abre um editor de texto e cria o arquivo .c
๏ Digitar ‘i’ para passar para “insert mode”
๏ Escrever o programa:
❖ #include <stdio.h>
int main(int argc, char **argv)
{
printf(“Hello World!\n”);
}
๏ ESC, depois :wq! e ENTER
O sistema antigo de programar

๏ Com isso escrevemos o nosso primeiro programa e salvamos


no arquivo helloworld.c
๏ Agora, para poder rodar, temos que compilar.
๏ Vamos usar um compilador chamado gcc
๏ Digitar:
gcc helloworld.c -o helloworld
๏ Isto vai compilar o programa e gerar um arquivo executável
“helloworld”
๏ Se não tinha nenhum erro no programa, o gcc não diz nada e
simplesmente cria o arquivo executável.
๏ Para executar o programa vamos digitar:
./helloworld
Hello world
๏ Hello world:
❖ #include <stdio.h>
int main(int argc, char **argv)
{
printf(“Hello World!\n”);
}
๏ A primeira linha vamos entender só la para o final do curso o
que significa, por enquanto podemos dizer só que ela me
permite usar a função “printf”.
๏ main: este é o ponto de entrada de todos os programas C.
Podemos pensar em um programa como em uma função, que
recebe informações de entrada (argc e argv) e retorna algum
dado na saída (int), mas mais sobre isso mais para frente.
๏ {} delimitar o corpo da função “main”, que neste caso contem
uma única instrução, “printf”.
Hello world
๏ Hello world:
❖ #include <stdio.h>
int main(int argc, char **argv)
{
printf(“Hello World!\n”);
}
๏ printf é uma função da linguagem C, que nos permite
mostrar texto na tela.
๏ Toda função segue o esquema:
nome_d_
afunção(parâmetros)
๏ Neste caso, printf recebe como único parâmetro um texto a
ser mostrado na tela.
๏ Texto em C é escrito entre “”.
๏ No final de cada instrução da linguagem C devo colocar um ;
๏ C é case sensitive, então printf é diferente de Printf
Hello world
๏ Hello world:
❖ #include <stdio.h>
int main(int argc, char **argv)
{
printf(“Hello World!\n”);
}
๏ Na função printf, a \ tem um significado especial.
๏ \ permite mostrar na tela caracteres não digitáveis
normalmente.
๏ No caso, \n significa quebra de linha.
๏ \\ mostra uma \ na tela
๏ \t insere uma tabulação
๏ Existem outros códigos especiais que vamos ver mais para a
frente.
O jeito fácil

๏ Abrir o XCode
๏ File/New Project
๏ A esquerda escolher Application na seção Mac OS X e
escolher Command Line Tool, com Type: ‘C’
๏ O XCode cria um novo projeto (pasta) que já contem um
programa helloworld escrito no arquivo main.c
๏ Basta clicar Build and Run (Command+Enter) para compilar e
executar o programa.
๏ Para ver a saída dele, tem que abrir o console: Command
+Shift+R
Hello World
๏ Este é um programa que produz uma saída, o texto Hello World,
mas não tem nenhuma entrada.
๏ Para poder fazer um programa que interage com o usuário, ou seja
que tem uma entrada, precisamos de algum jeito para o programa
memorizar dados, para poder em seguida processá-los.
๏ Os dados devem ser armazenados na memoria RAM do
computador.
๏ Devemos pensar na memória de um computador como uma longa
lista numerada de bytes, onde estes números são chamados de
“endereços” das celulas (bytes) de memória.
๏ Portanto, para armazenar dados, por exemplo um número, temos
que reservar um ou mais bytes de memória para guardar estes
dados.
Variáveis
๏ Vamos então tentar fazer um programa que peça para o usuário
digitar um número e imprima este número de volta na tela
(programa “echo”).
๏ Temos então que reservar alguns bytes para guardar o número
digitado.
๏ Para não ter que lidar com endereçoes de memória diretamente,
podemos dar um nome à área de memória que vamos utilizar para
guardar o número digitado.
๏ Esta area de memória reservada à qual damos um nome, se chama
de variável.
๏ A linguagem C fornece vários tipos de variáveis, que
automaticamente reservam memória suficiente para quardar o tipo
de informação desejado.
๏ No caso de números, existem váriáveis de tipo “int”, ou seja
número inteiro.
Variáveis
๏ Antes de poder usar uma variável, temos que criá-la, ou seja
“declarar uma váriável”.
๏ Para declarar uma variável temos de escrever:
<tipo> <nome>;
๏ Por exemplo, para um número inteiro:
int número;
๏ Neste exemplo “int” é o tipo da variável e “número” é o nome
arbitrário que decidimos dar à ela.
๏ Quando o compilador encontra uma declaração de variável, ele
reserva um espaço em algum endereço na memória suficiente para
guardar um “int” e associa o nome à este endereço.
๏ Uma variável de tipo “int” geralmente ocupa 4 bytes.
๏ Um “int” pode guardar um número até 232, ou seja de 0 a
4.294.967.295. Mas como temos também números negativos, um
“int” vai de -2.147.483.647 à +2.147.483.648
Echo

๏ Voltando ao programa echo, temos então que:


(1) declarar uma variável int
(2) pedir para o usuário digitar um número
(3) ler o número digitado e memorizá-lo na variável
(4) mostrar o número de volta na tela
๏ Fica então:
๏ #include <stdio.h>
int main(int argc, char **argv)
{
! int número; //(1)
! printf(“Digite um número: “); //(2)
! scanf(“%d”, &número); //(3)
! printf(“Você digitou %d.\n”, número); //(4)
}
Comentários
๏ Em programação em geral, é sempre muito importante comentar o
seu código. Isso ajuda você mesmo a entender quando for vê-lo
de novo no futuro e ajuda também outras pessoas que forem usá-
lo a entendê-lo melhor.
๏ Em C, tudo que vem depois de ‘//’ em uma linha é considerado
comentário e é ignorado na hora de compilar
๏ Um comentário de múltiplas linhas pode ser feito incluindo-o
entre /* e */
๏ Tudo que vem depois de /* é considerado comentário, até o
próximo */
Variáveis
๏ Vimos então como declarar variáveis, mas o que constitui um
nome válido de variável?
๏ Nomes válidos de variáveis contêm somente letras, números e _
e começam com letras ou _
๏ A linguagem C é case sensitive, ou seja a variável “Número” é
diferente da variável “número”.
๏ Não usar acentos no código!!!!!
๏ Vamos então entender o programa anterior:
๏ Scanf:
❖ scanf(“%d”, &número);
A funcão “scanf” serve para ler um dado digitado e guardá-lo em uma
variável.
❖ O “%d” indica que esta sendo lido um número inteiro
❖ O & tem que estar antes do nome da variável
Variáveis

๏ Printf: serve para mostrar texto e valor de variáveis na tela.


๏ O caracter % tem um significado especial
๏ %d no printf significa: na hora de imprimir, substitua o %d pelo
valor da variável que vem depois do texto entre “” na função
printf, onde o ‘d’ indica que trata-se de uma variável de tipo ‘int’.
๏ No exemplo do echo, supondo que a variável número tenha valor
8, a linha printf(“Você digitou %d”, número); imprime na tela “Você
digitou 8”.
Programa quadrado

#include <stdio.h>
int main(int argc, char **argv)
{
! int número;
! int resultado;
! printf(“Digite um número: “);
! scanf(“%d”, &número);
! resultado = quadrado * quadrado;
! printf(“Você digitou %d.\n”, número);
}

๏ Desta vez temos duas variáveis de tipo int e temos uma


instrução de assignment:
resultado = número * número;
๏ Esta significica: calcule o valor da expressão a direita do ‘=’ e
guarde o resultado na variável a esquerda do ‘=’
๏ O * indica multiplicação
Expressões

๏ Expressões se compõem de operadores e operandos.


๏ Operandos são números ou variáveis
๏ Operadores são: +, -, *, /, %
๏ Exemplos:
❖ 2+4 --> 6
❖ 2-4 --> -2
❖ 4*2 --> 8
❖ 4/2 --> 2
๏ Estes primeros quatro operadores são soma, subtração,
multiplicação e divisão
๏ Quando operamos com números inteiros, a divisão pode não
produzir o resultado esperado:
5 / 2 ---> 2
A parte decimal é perdida.
% - Resto da divisão inteira

๏ O operador % calcula o resto de uma divisão inteira


๏ 5 % 2 --> 1
๏ 13 % 5 --> 3
๏ 100 % 10 --> 0
Expressões

๏ Expressões podem ser tão compridas quanto necessario:


๏ 10 * 5 - 4
๏ Esta expressão porem é ambigua, porque pode ser:
(10 * 5) + 4 --> 54
ou
10 * (5 + 4) --> 90
๏ Para evitar problemas, é sempre bom usar ().
๏ Posso inclusive ter parenteses umas dentro das outras:
(10 * ((5 + 3) / 4)) --> 20
๏ Sempre que uso números, posso também usar nomes de variáveis.
Exercício

๏ Vamos então fazer um programa “Incrementa”, onde pedimos para


o usuário digitar um número e o programa imprime de volta o
número incrementado de 1.
๏ Por exemplo se eu digitar 10, o programa deve imprimir 11 na tela
๏ Bom trabalho...
Tipos de variáveis

๏ Vimos até agora o tipo ‘int’. Mas quais outros existem?


๏ Em C, o int tem umas variantes. Vimos que int ocupa 4 bytes...
existe também um tipo ‘short’ que ocupa 2 bytes e um tipo ‘long’
que ocupa 8.
๏ Alem de números inteiros, temos números decimais, cujo tipo em
C se chama ‘float’.
๏ O tipo ‘float’ também tem uma variante mais precisa, que se
chama ‘double’
๏ Para letras ou outros caracteres, temos o tipo ‘char’, que ocupa 1
byte
๏ Todos os tipos agora mencionados tem uma variante ‘unsigned’
๏ Por exemplo, se o tipo char vai de -128 a +127, o tipo unsigned
char vai de 0 à 255.
Programa metade

๏ Vamos agora escrever um programa que leia um número decimal


digitado e imprima de volta na tela a sua metade.
๏ #include <stdio.h>
int main(int argc, char **argv)
{
! float número;
! float resultado;
! printf(“Digite um número: “);
! scanf(“%f”, &número);
! resultado = número / 2.0;
! printf(“Você digitou %f.\n”, resultado);
}
๏ Notem que agora, no scanf e printf, em vez de %d, usamos %f
๏ Se fosse um ‘char’, teríamos que usar %c
Char

๏ O tipo ‘char’ tem algumas peculiaridades.


๏ Ele serve para conter qualquer simbolo presente no teclado,
inclusive os que não são visíveis, como quebra de linha, ou
‘control’, ou ‘delete’
๏ Se eu quiser atribuir diretamente uma letra em uma variável de
tipo ‘char’, devo colocar a letra entre aspas simples ‘’.
๏ Exemplo:
char letra;
letra = ‘a’; //certo
letra = a; //errado! a variável a não existe!
๏ Escrever (letra = ‘a’) ou escrever (letra = 97) é exatamente a
mesma coisa!!!
Exercício próxima letra

๏ Para o tipo ‘char’


๏ Tentem então fazer agora um programa que leia uma letra digitada
pelo usuário e imprima de volta a próxima letra em ordem
alfabetica, ou em ordem da tabela ASCII.
๏ ...
Casos especiais

๏ Quando programamos, temos sempre que pensar em todos os


casos possíveis.
๏ Por exemplo, o que acontece no exercício anterior se em vez de
digitar uma letra, o usuário digitar por exemplo um (!) ?
๏ O que acontece se o usuário digitar a letra ‘z’?
Inicialização de variáveis
๏ O que acontece se escrevemos um programa assim:
int a;
printf(“%d”, a);
๏ Qual valor será impresso na tela?
๏ Resposta: impossível saber
๏ Quando alocamos uma variável, é reservada uma área de memória
para ela, então será impresso o valor que por acaso se encontrava
nas celulas de memória reservadas para a variável ‘a’.
๏ Para evitar problemas, é sempre bom atribuir um valor para uma
variável assim que ela for alocada.
๏ Por exemplo:
int a = 0;
๏ Assim temos certeza que ela já é criada com um valor conhecido
๏ Usar uma variável sem antes atribuir um valor gera error difíceis de
achar em seguida. Cuidado!
Casting
๏ O que acontece se escrevermos o seguinte:
int a = 0;
float b = 5.85;
a = b;
printf(%d”, a);
๏ Qual valor será impresso na tela? Ou seja, quanto vale ‘a’?
๏ ‘a’ vale 5, porque, ao fazer a = b, foi feito um ‘cast’ automático, ou
seja uma conversão de tipo de float para int e portanto a parte
decimal foi perdida.
๏ Portanto, cuidado ao usar o = com variáves de tipos diferentes,
porque isso pode produzir resultados diferentes do esperado.
๏ Outro exemplo:
int a = 256; char b = a;
๏ Quanto vale b? b vale 0!
Mostrar o código ASCII de um char
#include <stdio.h>
int main(int argc, char **argv)
{
! char c;
! scanf("%c", &c);
! printf("O código ASCII da letra %c é %d.", c, c);
}
๏ O printf troca o primeiro % com o valor da primeira variável que
segue e o segundo % respectivamente com a segunda variável
que segue.
๏ Mas se imprimo duas vezes a mesma variável, como pode dar
resultados diferentes?
๏ Um char, por traz, é sempre um número.
๏ %c ou %d dizem à função printf como interpretar este número.
Exercício: Média
๏ Faça um programa que peça para o usuário digitar dois números
float e imprima de volta a média deles...
๏ ...

๏ Até agora fizemos “straight line programs”, um pouco como os


“fixed program computers” que mencionamos no começo.
Programas que fazem sempre a mesma coisa, um pouco como uma
calculadora.
๏ Nos falta a capacidade de fazer testes e decidir fazer coisas
diferentes dependendo do resultado do teste.
๏ Isto se chama de branching.
๏ Podemos pensar nos testes como em nós de uma árvore, a partir
dos quais os ramos se bifurcam.
Branching: if/else
๏ if (teste)
{
//bloco de instruções 1
}
else
{
//bloco de instruções 2
}
//instruções
๏ Isto significa: se o ‘teste’ for verdade, executa o bloco 1 e depois
pula para as ‘instruções’ embaixo. Se o ‘teste’ for falso, executa o
bloco 2 e depois continua com as ‘instruções’ embaixo.
Branching: if/else, nesting
๏ Um bloco de instruções pode conter qualquer coisa, inclusive
outros if/else, por exemplo:
๏ if (teste1)
{
//bloco 1
}
else
{
if (teste2)
{
//bloco 2
}
else
{
//bloco 3
}
}
Branching: if/else, sintaxe
๏ Se um destes blocos de instruções é composto por uma única
instrução, podemos omitir os {}
๏ if (teste)
//instrução
else
{
//bloco
}
๏ Podemos também omitir a parte inteira do ‘else’, caso não seja
necessária:
if (teste)
{
//bloco 1
}
//instruções
๏ Isto significa: se ‘teste’ for verdade, executa o bloco 1 e continua
com as “instruções”. Se ‘teste’ for falso, pula o bloco 1 e vai
testes
๏ Mas o que constitui um teste?
๏ Um ‘teste’ é uma expressão cujo resultado é interpretado
somente como sendo verdadeiro ou falso.
๏ Mas como sabemos que em um computador só existem números,
na verdade o resultado de um teste é um número.
๏ A regra é que 0 é FALSO e 1 ou qualquer outro número é
VERDADE.
๏ Sendo a e b duas variáveis, testes poderiam ser:
๏ if (a > b) --> verdade se a é maior do que b
๏ if (a <= 5) --> verdade se a é menor ou igual que 5
๏ if (b == 7) --> verdade se b vale 7
๏ if (a != b) --> verdade se a é diferente de b
๏ if (1) --> sempre verdade
๏ if (a) --> como dizer if (a != 0), verdade se a não é 0
testes
๏ Tudo que aprendemos para as expressões vale também para os
testes.
๏ if (10 + a - 7) é verdade se o resultado desta expressão é diferente
de 0.
๏ Podemos também combinar vários testes em um único if (). Por
exemplo, como fazemos para saber se o valor de uma variável
esta entre 0 e 10?
if (a >= 0 && a <= 10)
๏ Precisamos então de operadores logicões como ‘e’, ‘ou’ e ‘não’
๏ && --> e lógico
๏ || --> ou lógico
๏ ! --> não lógico
๏ Os testes então, assim como as expressões, podem ser tão
complicados quanto necessário e podem usar ()s.
if - exemplo
๏ Um programa que peça para o usuário digitar um número e que
imprima “sim” na tela se o número for maior que 10 e não no caso
contrário.
๏ #include <stdio.h>
int main(int argc, char **argv)
{
! int num;
! printf("Digite um número: ");
! scanf("%d", &num);
! if (num > 10)
! ! printf("sim\n");
! else
! ! printf("não\n");
}
if - exemplo 2
๏ Um programa que peça para o usuário digitar um número e que
imprima “sim” na tela se o número for entre 10 e 20 ou negativo e
não no caso contrário.
๏ #include <stdio.h>
int main(int argc, char **argv)
{
! int num;
! printf("Digite um número: ");
! scanf("%d", &num);
! if ((num >= 10 && num <= 20) || num < 0)
! ! printf("sim\n");
! else
! ! printf("não\n");
}
if - exercício 1
๏ Faça um programa que peça para o usuário digitar dois números e
que imprima de volta na tela o maior dos dois números:
if - exercício 2
๏ Faça um programa que peça para o usuário digitar um número e
que imprima de volta na tela o valor absoluto deste número
if - exercício 3
๏ Faça um programa que peça para o usuário digitar dois números e
que imprima de volta na tela os dois números em ordem
crescente...
if - exercício 4
๏ Faça um programa que peça para o usuário digitar um caracter. O
seu programa devera dizer como resposta uma entre:
❖ vc digitou uma letra minuscola
❖ vc digitou uma letra maiuscola
❖ vc não digitou uma letra
๏ Dica: consulte a tabela ASCII
if - exercício 5
๏ Altere o programa anterior para que ele faça a conversão de
letras minúsculas para maiúsculas e vice versa, ou diga “vc não
digitou uma letra”...
if - exercício 6
๏ Lembre do programa que impria os dois números de volta em
ordem crescente? Tente agora fazer o mesmo com 3 números...
if - exercício 7
๏ Escreva um programa que diga se um número digitado é par o
impar.
if - exercício 8
๏ Escreva um programa que diga se um número float digitado é
inteiro ou não. (ex. 5.37 não é inteiro, 7.0 sim).
Switch
๏ As vezes precisamos fazer muitos testes sobre o valor de uma
mesma variável, por exemplo queremos fazer uma coisa se ‘a’ vale
1, outra se vale 2, outra se vale 4, etc etc
๏ Podemos escrever
if (a == 1)
{
}
else if (a == 2)
{
}
else if (a == 3) ... etc
๏ Mas tem um jeito mais prático de fazer a mesma coisa...
Switch
๏ switch (a)
{
case 1 :
//bloco de instruções
break;
case 2 :
//bloco ...
break;
case 4 :
//bloco
break;
default: //em todos os outros casos...
//bloco
}
Branching - exercício final
๏ Vamos fazer juntos um exercício que vai ser uma simples
calculadora. O usuário poderá escolher se quer multiplicar,
subtrair, somar ou dividir dois números, aí pedimos os dois números
e fazemos a conta apropriada
Incrementos etc.. - sintaxe
๏ Operações como num = num + 1; ou num = num - 1; são tão
comuns em programação que existe um jeito breve de escrevê-las
๏ num = num + 1; --> num++;
๏ num = num - 1; --> num--;
๏ Operações em geral como num = num * (x) etc também tem uma
forma abreviada:
๏ num = num * x; --> num *= x;
๏ num = num / x; --> num /= x;
๏ num = num + x; --> num += x;
๏ num = num - x; --> num -= x;
Loops
๏ Com o que sabemos até agora, já podemos fazer bastante coisa.
๏ O que não sabemos ainda fazer é repetir a mesma operação várias
vezes...
๏ Por exemplo se quisessemos alterar o programa da calculadora
para que, após fazer uma conta, ele ofereça ao usuário a
possibilidade de fazer outra, tantas vezes quantas o usuário
quiser... teríamos que repetir o mesmo código infinitas vezes
๏ Naturalmente, tem um jeito de automatizar isso...
While
๏ while (teste)
{
//bloco de instruções
}
๏ Isto significa: faça o teste... se for verdade, execute o bloco e
repita o teste. se for falso, pule o bloco e continue
While - Exemplo 1 - Contar
๏ Vamos fazer um programa que conte até 100.
๏ #include <stdio.h>
int main (int argc, const char * argv[])
{
! int num = 0;
! while (num <= 100)
! {
! ! printf("%d\n", num);
! ! num++;
! }
}
๏ Quando a execução chega no teste do while da primeira vez, num
vale 0, portanto o teste da verdade!
๏ Então ele mostra o valor de num (0), aumenta num para 1 e
repete o teste, que ainda vai dar verdade.
๏ Assim vai, até que num vale 100. Ele imprime o 100, aumenta para
101 e repete o teste.
๏ Desta vez o teste da falso, então ele não repete mais e o
programa termina.
While - Exemplo 2 - Contar 2
๏ Vamos alterar o programa anterior para que ele pergunte ao
usuário de quanto ele quer contar e até quanto?
๏ Por exemplo se o usuário escolher contar de 10 e até 15, o seu
programa deverá imprimir na tela 10 11 12 13 14 15
๏ ...

๏ Você pensou em todos os casos?


๏ O que acontece se o usuário pedir para contar de 15 até 10?
While - Exemplo 3 - Letra
๏ Vamos fazer um programa que peça para o usuário digitar um
número. Em quanto ele digitar números entre 0 e 100,
continuamos pedindo para que digite mais números. Assim que ele
digitar um número < 0 ou > 100, o programa termina...
While - Exemplo 4 - Potência
๏ Vamos fazer um programa que calcule uma potência arbitraria.
Vamos acostumar a fazer um esquema do programa antes de
mergulhar no código.
๏ Temos que pensar o que este programa vai precisar fazer para
calcular a potência.
๏ Pedir para o usuário digitar a base
Pedir para o usuário digitar o exponente
Se o exponente for 0, a resposta é 1
Se o exponente for 1, a resposta é base
Nos outros casos, multiplicar base por si mesma exponente vezes
para obter o resultado
Mostrar o resultado
While - Exemplo 4 - Potência
#include <stdio.h>
int main (int argc, const char * argv[])
{
! //declara as variáveis
! int base, exp, resultado;
! //le a base
! printf("Digite a base: ");
! scanf("%d", &base);
! //le o exponente
! printf("Digite o exponente: ");
! scanf("%d", &exp);
! //caso em que o exp é 0
! if (exp == 0)
! {
! ! printf("O resultado é: 1\n");
! ! return 0;
! }
! //caso genérico
! resultado = base;
! while (exp > 1)
! {
! ! resultado = resultado * base;
! ! exp--;
! }
! //mostra o resultado
! printf("O resultado é: %d\n", resultado);
}
Debugging - Potência
Vamos executar o programa anterior um passo por vez, usando um debugger, para
entender o funcionamento.
O debugger nos oferece a possibilidade de executar o programa uma instrução por vez,
clicando no botão “Step Over”.
A cada passo, podemos examinar o valor de todas as variáveis, para entender o
funcionamento.
Para fazer isso, vamos inserir um breakpoint no código, ou seja um ponto onde queremos
que a execução pare para nos dar a chance de examinar o estado do programa.
Para inserir um breakpoint, é só clicar a esquerda de uma instrução.
Apos inserir um breakpoint, é só clicar Build&Run e o programa executa até o breakpoint
e lá para.
Debugging - Potência
Executa a
próxima instrução

Executa até o
próximo breakpoint

Valor das
variáveis

Próxima instrução a
ser executada

Breakpoint: significa execute


o programa e pare quando
chegar no breakpoint
While - Do/While
๏ Note que no caso no while normal, se o teste for falso já da
primeira vez, o bloco de instruções do while pode não ser
executado nem uma vez.
๏ No caso eu queira garantir que o código do while seja executado
pelo menos uma vez, existe uma variante:
๏ do
{
//bloco de instruções
} while (teste);
๏ Neste caso, a diferença é que o teste é executado no final do
bloco, que portanto é executado no mínimo uma vez
๏ De resto é tudo igual, se o teste for verdade, o bloco é
executado de novo. Se for falso, o programa continua.
for
๏ Vamos relembrar o exemplo de contar até 100:
int a = 0;
while (a <= 100)
{
printf(“%d\n”, a);
a++;
}
๏ Tem uma outra variante do ciclo, que permite fazer a mesma coisa
de uma forma mais breve e mais legível
๏ for (int a = 0; a <= 100; a++)
{
printf(“%d\n”, a);
}
for
๏ O for então é útil sempre que queremos contar um número
determinado de vezes
๏ O esquema é:
for (<instrução inicial>; <teste de saída>; <instrução executada a
cada repetição>)
{
//bloco de instruções
}
๏ A instrução inicial é executada uma única vez antes de tudo.
๏ O teste é executado antes de cada repetição. Enquanto ele for
verdade, continua repetindo.
๏ A instrução é executada uma vez no final de cada repetição, logo
antes do teste.
Ciclos: condições de saída
๏ O que faz o seguinte programa:
#include <stdio.h>

int main(int argc, char **argv)


{
! int a = 0;
! while (a < 10)
! {
! ! printf("%d\n", a);
! }
}
๏ O que será impresso na tela?
Ciclos: condições de saída
๏ O que faz o seguinte programa:
#include <stdio.h>

int main(int argc, char **argv)


{
! int a = 0;
! while (a < 10)
! {
! ! printf("%d", a);
! }
}
๏ O que será impresso na tela?
๏ 0000000000000000000000000000...
๏ Este é um programa que contem um ciclo infinito e ele vai imprimir
0 na tela infinitas vezes...
๏ Cuidado com ciclos infinitos quando usarem loops.
๏ Sempre que temos um ciclo, temos que prestar atenção que em
algum momento a condição de saída seja satisfeita.
๏ Neste exemplo, a nunca vai ser >10...
Arrays
๏ O que ainda não sabemos fazer? Tratar grandes listas de
números...
๏ Se eu, por exemplo, quisesse fazer um programa que leia uma lista
de 100 números e ache o mínimo, eu teria que declarar 100
variáveis de tipo int.. se fossem 1000... fica inviável...
๏ Arrays me permitem declarar um monte de variáveis do mesmo
tipo de uma vez só.
๏ Por exemplo, se eu quiser declarar 100 variáveis de tipo int eu
escreveria:
int nums[100];
๏ Isso, de uma vez, aloca 100 variáveis de tipo int em posições
continuas na memória.
Arrays
Endereço Valor
int a; 1 10
int b; 2 20
3 30 c[0]
4 32 c[1]
c[2]
5 0
..
6 0 ...
7 0
int c[10]; 8 0
9 0
10 0
11 0 c[8]
12 0 c[9]
Arrays
๏ Para atribuir um valor por exemplo para a primeira delas, eu
escreveria:
nums[0] = 15;
๏ Lembrem-se que se começa sempre a contar de 0.
๏ Esta linha significa: coloque 15 na primeira destas 100 variáveis.
๏ Se eu quiser mostrar o valor da terceira delas, eu escreveria:
printf(“%d”, nums[2]);
๏ Vamos então fazer um programa que peça para o usuário digitar 10
números e mostre eles de volta na tela.
Arrays - Exemplo 10 números
#include <stdio.h>
int main (int argc, const char * argv[])
{
! int nums[10];
! for (int i = 0; i < 10; i++)
! {
! ! //pede para digitar o i-ésimo número
! ! printf("Digite o número %d: ", i);
! ! //guarda o i-ésimo número na i-ésima posição do array nums
! ! scanf("%d",&nums[i]);
! }
! //mostra eles de volta na tela
! for (int i = 0; i < 10; i++)
! {
! ! printf("O %d-ésimo número é %d\n", i, nums[i]);
! }
}
Arrays - Exemplo 10 números
๏ Altere o exemplo anterior para que ele imprima de volta na tela
os números em ordem invertida, por exemplo, se o usuário digitar
5, 7, 3, 10 o seu programa deverá mostra de volta 10 ,3, 7, 5.
Arrays - Exercio - Mínimo
๏ Tentem fazer um programa que peça para o usuário digitar 10
números e que imprima de volta na tela o mínimo deles...
Arrays - Solução - Mínimo
#include <stdio.h>
int main (int argc, const char * argv[])
{
! int vetor[10];
! int minimo = 0;
! for (int i = 0; i < 10; i++)
!{
! ! printf("Digite o número %d: ", i + 1);
! ! //le o i-esimo número
! ! scanf("%d",&vetor[i]);
!}
! //O mínimo começa sendo o primeiro
! minimo = vetor[0];
! //ai vamos percorrendo a lista
! for (int i = 1; i < 10; i++)
! {//se acharmos um número menor que o nosso mínimo
! ! if (vetor[i] < minimo)
! ! ! minimo = vetor[i]; //o mínimo passa a ser esse número
!}
! //no final temos o mínimo da lista
! printf("O mínimo é: %d\n", minimo);
}
Arrays - Texto
๏ Até agora vimos como tratar letras, mas texto?
๏ Texto em C é um array de char
๏ Exemplo:
๏ #include <stdio.h>
int main (int argc, const char * argv[])
{
! char palavra[100];
! printf("Digite uma palavra: ");
! scanf("%s", palavra);
! printf("Você digitou %s\n", palavra);
}
Arrays - Texto
๏ Para mostrar que texto na verdade é um vetor, podemos também
escrever assim:
๏ #include <stdio.h>
int main (int argc, const char * argv[])
{
! char palavra[100];
! printf("Digite uma palavra: ");
! scanf("%s", palavra);
! printf("Você digitou ");
! int i = 0;
! while (palavra[i] != 0)
! {
! ! printf("%c", palavra[i]);
! ! i++;
! }
! printf("\n");
}
Arrays - Exercício
๏ Repare, do exemplo anterior, que o final de um texto em C é
indicado por um 0 no vetor.
๏ Escreva agora um programa que peça para o usuário escrever uma
palavra e imprima de volta na tela o comprimento dela...
Arrays - Exercício
๏ Escreva agora um programa que leia uma palavra e a imprima de
volta na tela de traz para frente. Por exemplo, se o usuário digitar
“Omar” vcs devem imprimir de volta “ramO”
๏ Repare, do exemplo anterior, que o final de um texto em C é
indicado por um 0 no vetor.
๏ Para fazer este exercício você deverá então procurar a posição
deste 0 e, a partir desta posição, voltar para traz imprimindo as
letras...
๏ Boa sorte...
Arrays - Exercício
๏ Escreva agora um programa que leia duas palavras e as imprima de
volta na tela em ordem alfabética, tratando letras maiúsculas e
minúsculas como iguais.
Ponteiros
๏ Ponteiros são o assunto mais dificil da linguagem C.
๏ Até a gente fazer programas maiores, é dificil entender para o que
servem
๏ Mas servem e muito!
๏ Uma variável de tipo ponteiro é uma variável cujo valor é o
endereço na memória de outra variável
๏ Podemos pensar em ponteiros como um atalho no nosso desktop
para algum arquivo no computador
๏ O atalho contem o endereço do arquivo para onde ele aponta.
๏ Exemplo de declaração de ponteiro:
int *p;
๏ O * indica que a variável p é um ponteiro para uma variável de tipo
int.
Ponteiros
๏ Exemplo:
int a; //declara uma variável int
int *p; //declara um ponteiro para int
p = &a; // p = endereço de a
printf(“%d”, *p); //*p significa o apontado por p, ou seja a
๏ Resumindo:
❖ * na declaração significa que a variável é um ponteiro
❖ * em seguida significa “o apontado por”
❖ & significa “endereço de”
Ponteiros
Endereço Valor
int a; 1 10
int b; 2 20
int * p; 3 1
int * p2; 4 12
5 0
6 0
7 0
8 0
9 0
10 0
11 0
12 0
Ponteiros = Arrays
๏ Arrays na verdade são ponteiros.
๏ Quando escrevo
int a[10];
a na verdade é um ponteiro para o primeiro de 10 int na memória
do computador.
๏ Isso significa, por exemplo, que escrever *a é o mesmo que
escrever a[0]. Ou seja, o “apontado por a” é o primeiro dos 10
inteiros da lista.
๏ Então, a diferença entre escrever
int *a;
ou escrever:
int a[10];
é que no primeiro caso, eu declarei um ponteiro e aloquei memória
para ele; no segundo caso, eu declarei um ponteiro e aloquei
memória para ele, depois aloquei 10 inteiros e fiz o ponteiro
apontar para o primeiro int.
Operações sobre ponteiros
๏ Quais operações são permitidas em ponteiros?
๏ Posso dizer que um ponteiro é igual ao endereço de memória de
outra variável
๏ Posso dizer que um ponteiro é igual a outro, ou seja eles apontam
para o mesmo endereço de memória.
๏ Posso incrementar um ponteiro. Por exemplo: int a[10];
Então vimos que *a é como dizer a[0]. Se eu escreer a++;, agora *a
é como dizer a[1]
๏ Podemos também somar números inteiros à um ponteiro. Por
exemplo: int a[10]; a = a + 5; Agora *a é como dizer a[5].
๏ Mas eu não posso atribuir diretamente um valor. Por exemplo, int
a[10]; a = 30; Isso não é permitido, porque não faz sentido.
๏ Exceção: int *a; a=0; isto é permitido, porque 0 como valor de um
ponteiro é um caso especial, que significa que o ponteiro não
aponta pra nada.
Ponteiros: exemplos
int *p;
char *c;
char d;
float f;
float *fp;
int k[10];
int i;

i = d;
f = p;
f = fp;
f = *fp;
p = i;
*p = i;
p = &i;
i = &p;
p++;
&p = 10;
p = &10;
&i = 10;
p = k;
fp++;
Para o que servem ponteiros?
๏ Um primeiro exemplo. Do jeito que vimos arrays até agora, somos
obrigados a saber antes o tamanho máximo de que vamos precisar.
Não podemos, por exemplo, pedir ao usuário uantos números ele
quer usar e usar a variável que lemos para declarar um array:
int num;
printf(“Quantos números? “);
scanf(“%d”, &num);
int números[num]; //NÃO PERMITIDO!
๏ Para fazer uma coisa deste tipo, temos que explicitamente usar
ponteiros e usar uma função para alocar memória dinamicamente
na hora que for necessário.
Alocação dinâmica de memória
๏ int *números;
int quantos;
printf(“Quantos números? “);
scanf(“%d”, &quantos);
números = malloc(sizeof(int) * quantos);
๏ A função “malloc” quer saber quantos bytes de memória ela deve
alocar e ela me devolve o endereço do primeiro dos bytes
alocados.
๏ No exemplo acima, eu quero alocar memória para “quantos”
variáveis de tipo “int”, mas a função malloc quer saber quantos
bytes ela tem que alocar, não quantos int.
๏ Para isso, nos ajuda a função especial “sizeof”, que me diz quantos
bytes algum tipo de variável ocupa.
๏ No caso acima, (sizeof(int) * quantos) significa aloca tantos bytes
quantos são necessários para um “int” vezes o número de “ints”
que eu preciso.
Alocação dinâmica de memória
๏ Vimos então que utilizando “malloc”, eu consigo alocar memória
na quantidade necessária, na hora que eu precisar, sem ter que
saber antes quanta memória vou ocupar.
๏ Mas quanto eu aloco memória dinamicamente, eu preciso também
liberar a memória alocada quando não precisar mais dela. Se não
liberar, eu corro o risco que em algum momento acabe a memória
do meu computador.
๏ Como eu libero? Existe a função “free”, que faz o oposto da
“malloc”.
๏ int *a = malloc(sizeof(int));
free(a);
Perdas de memória
๏ O que acontece se eu escrever:
int *a = malloc(12);
a = malloc(20); //PERDA DE MEMÓRIA
free(a);
๏ Vamos entender o que acontece... primeiro eu aloco 12 bytes e
faço o ponteiro ‘a’ apontar para o primeiro destes 12 bytes. Logo
depois, eu aloco mais 20 bytes e faço de novo o ponteiro ‘a’
apontar para o primeiro deles. Mas o que aconteceu com os
primeiros 12 bytes? Eles continuam alocados, mas não tenho mais
nenhum ponteiro apontando para eles, portanto não tenho mais
como liberá-los, já que a função “free” quer um ponteiro para a
região de memória à ser liberada.
๏ Importante então nunca perder as referências para memória
alocada, para ter como liberá-la em seguida.
Estruturas
๏ Vamos supor que eu precise, por exemplo, fazer uma lista de
matrículas do iAi.
๏ Para isto, vou ter que armazenar vários dados para cada matrícula,
como nome do aluno, email, telefone, nome do curso, data da
matrícula etc...
๏ Com o que sabemos, vamos ter que fazer um array separado para
nomes, outro para emails, etc...
๏ Isso deixa complicado eu recuperar os dados relativos a uma
matrícula, pois vou ter que buscá-los em vários vetores separados.
๏ Seria melhor se eu pudesse construir um vetor de “matrículas”,
onde cada matrícula contem todos os dados relativos aquela
matrícula.
Estruturas
๏ struct sMatricula
{
char *nome, *email;
char *nomeCurso;
float valorPago;
};
๏ Com isto, declaramos un novo tipo de variável “sMatricula”,
utilizável da mesma forma que os vários “int” ou “float” que já
conhecemos.
๏ Agora posso então usar o meu novo tipo para declarar variáveis,
que automaticamente vão conter dentro delas todos os campos
que caracterizam uma matrícula.
๏ struct sMatricula mat;
๏ Agora posso usar a minha variável “mat”
๏ mat.nome = “Omar”;
mat.valorPago = 10.23;
Estruturas
๏ Vimos então que com esta sintaxe do “.” podemos acessar os
campos de uma estrutura.
๏ Assim, podemos declarar um único array de matriculas, onde cada
matricula contem todos os dados necessarios já agrupados, sem
que eu tenha que caçá-los em vários vetores separados.
๏ struct sMatricula matriculas[10];
matricula[0].nome = “Omar”;
matricula[1].nome = “Lucas”;
etc..
๏ Mas como agora estamos usando um vetor, eu poderia querer
alocá-lo dinâmicamente:
struct sMatricula *matriculas;
matriculas = malloc(sizeof(struct sMatricula) * 10);
matriculas[3].nome = “Carlos”;
etc...
Estruturas
๏ struct sMatricula *matricula = malloc(sizeof(struct sMatricula));
๏ Neste caso, matricula é um apontador para uma estrutura de tipo
sMatricula. Isso significa que escrever
matricula.nome = “Omar”; esta errado! Porque um ponteiro é
diferente de uma estrutura e não tem campos. Eu teria que
escrever “(o apontado por matricula).nome”, ou seja:
(*matricula).nome = “Omar”;
๏ Mas esta sintaxe é pouco prática, então existe outro jeito de
fazer a mesma coisa:
matricula->nome = “Omar”;
๏ Portanto, quando temos uma estrutura, usamos o “.” para acessar
um membro, quando temos um ponteiro para estrutura usamos “-
>”
Ordenar
๏ O problema de ordenar uma lista de itens, sejam números, letras,
palavras, datas etc... é um problema famoso em computação, por
ser relativamente complexo e por ter varias possíveis soluções,
algumas extremamente ineficientes e outras muito eficientes.
๏ A razão para falar deste problema não é que vocês precisam
saber resolver este problema para fazer o curso de iPhone SDK,
mas sim que ele é um bom problema para treinar a nossa lógica de
programação assim como tudo que aprendemos até agora.
๏ Então vamos começar à pensar... vamos supor que eu tenha uma
lista de 100 números e que eu queira ordená-los em ordem
crescente... alguma idéia de como poderíamos fazer?
Ordenar
๏ Uma primeira idéia... se eu souber achar o mínimo de uma lista de
números (e nós sabemos)... eu também sei ordenar.
๏ Eu posso achar o mínimo da lista... este mínimo vai ser o primeiro
número da lista ordenada.
๏ Agora eu removo ele da lista e de novo procuro o mínimo dos que
sobraram. Este vai ser o segundo da minha lista ordenada... e assim
vai.
๏ Falta entender como eu removo um elemento de uma lista...
๏ Na verdade, eu não preciso remover....
๏ Vamos então escrever um pseudo-código deste algoritmo.
Ordenar
๏ Seja l[] uma lista de n números
๏ para i que vai de 0 até n-1
{
acha o mínimo dos elementos entre i e n-1
seja k a posição deste mínimo na lista l[]
agora troca o elemento na posição k com o elemento
na posição i
}
๏ Isto vai ordenar a nossa lista de números ou de qualquer coisa.
๏ Vamos ver no código como fica:
Ordenar
#include <stdio.h>
#include <stdlib.h>
int main (int argc, const char * argv[])
{
! int *nums;
! int quantos;
! int troca, min, posmin;
! printf("Quantos números você quer ordenar? ");
! scanf("%d", &quantos);
! //aloca memoria para quantos inteiros
! nums = malloc(quantos * sizeof(int));
! for (int i = 0; i < quantos; i++)
! {
! ! nums[i] = rand();
! }
! //agora tenho 100 números aleatorios no meu vetor nums
! for (int i = 0; i < quantos; i++)
! {
! ! min = nums[i];
! ! posmin = i;
! ! for (int j = i; j < quantos; j++)
! ! {
! ! ! if (nums[j] < min)
! ! ! {
! ! ! ! min = nums[j];
! ! ! ! posmin = j;
! ! ! }
! ! }
! ! //posmin tem a posição do mínimo da lista que vai de i até 100
! ! //troca o cara que esta na posição i da lista com o que esta na posição posminß
! ! troca = nums[i];
! ! nums[i] = nums[posmin];
! ! nums[posmin] = troca;
! }
! //mostra o resultado
! for (int i = 0; i < quantos; i++)
! {
! ! printf("%d\n", nums[i]);
! }
! free(nums);
return 0;
Introdução à complexidade
๏ Então achamos uma primeira solução para o problema de ordenar.
Mas esta é uma solução eficiente? O que significa eficiente? E
como podemos medir a eficiencia de uma solução?
๏ Existem 2 tipos de complexidade que podemos estar interessados
em medir... complexidade de tempo e de espaço.
๏ Complexidade de tempo significa estimar, em função do tamanho
do problema a ser tratado (no caso, quantos números vamos
ordenar), quantas operações o computador vai ter que executar
para resolver o problema.
๏ Complexidade de espaço significa estimar, em função do tamanho
do problema a ser tratado (no caso, quantos números vamos
ordenar), quanta memória o computador vai precisar para resolver
o problema.
Introdução à complexidade
๏ Vamos então fazer um exemplo com o problema de achar o
mínimo.
๏ Para achar o mínimo de n elementos, temos que percorrer todos
os n uma vez, portanto, se temos n elementos, temos que
executar k*n operações, onde k é uma constante.
๏ Em complexidade, ignoramos constantes, porque assumimos que a
diferença entre fazer n operações ou 2*n ou 10*n é mínima.
๏ Portanto, podemos dizer que, para achar o mínimo de n
elementos, temos que executar O(n) operações, ou seja um
número da mesma ordem de grandeza que n. Portanto o nosso
algoritmo para achar o mínimo tem complexidade O(n), ou seja é
linear. (100 números -> 100 instruções)
๏ A complexidade de espaço também é linear, pois precisamos de n
inteiros para guardar a lista.
Introdução à complexidade
๏ Vamos agora estimar a complexidade do nosso algoritmo para
ordenar.
๏ Olhando com atenção para o código, vemos que temos um for
dentro de outro.
๏ Olhando com mais atenção, temos que achar o mínimo de n
elementos, depois o mínimo de n-1 elementos, depois o mínimo de
n-2 elementos... e sabemos que achar o mínimo de n elementos
custa n.
๏ Portanto, a complexidade será dada por n+n-1+n-2+n-3+...+2+1.. a
matemática nos ensina que isto é da mesma ordem de grandeza
que n2, portanto o nosso algoritmo tem complexidade de tempo
O(n2).
๏ A complexidade de espaço é de O(n), pois temos um único vetor
com n elementos e isso é toda a memória de que precisamos.
Introdução à complexidade
๏ Uma complexidade de O(n2) é ruim, porque se precisamos ordenar
100 números, vamos ter que executar 100 * 100 = 10.000
operações. Se precisarmos ordenar 1.000.000 de números, vamos
ter que executar 1.000 bilhões de operações... e isso vai tomar
muito tempo.
๏ Vamos fazer mais um exemplo, com o nosso algoritmo para
calcular potências arbitrarias.
๏ A complexidade dele, sendo a base b e o exponente n, é O(n).
๏ Ou seja, para calcular basen, temos que multiplicar base por si
mesma n vezes, portanto fazer n multiplicações.
Ordenar
๏ Será que conseguimos achar alguma outra solução para o
problema de ordenar?
๏ E será que conseguimos achar alguma mais eficiente?
๏ Um outro jeito de resolver o problema é o método chamado de
Bubble Sort...
๏ Funciona assim: vamos percorrendo a nossa lista de números, cada
vez comparando um elemento com o seguinte. Se o seguinte for
maior que o anterior, trocamos eles de lugar.
๏ Quanto chegarmos ao final da lista, varremos de novo a lista,
fazendo a mesma coisa.
๏ Quando acontecer de varrermos a lista sem ter mais que fazer
nenhuma troca, a lista estará ordenada.
๏ Vamos ver o código...
Bubble Sort
int números[];
int quantos;
! int trocado = 0;
! int troca;
! do
! {
! ! trocado = 0;
! ! for (int i = 0; i < quantos - 1; i++)
! ! {
! ! ! if (números[i+1] < números[i])
! ! ! {
! ! ! ! troca = números[i];
! ! ! ! números[i] = números[i+1];
! ! ! ! números[i+1] = troca;
! ! ! ! trocado = 1;
! ! ! }
! ! }
! } while (trocado);
Bubble Sort
๏ Qual a complexidade do Bubble Sort?
๏ No pior caso, é de novo O(n2)
๏ Podemos reparar que, se o menor número estiver por último, a
cada repetição do do/while, ele vai subir uma posição; então, para
que ele chegue da última à primeira, o do/while terá que repetir n
vezes.
๏ Para cada uma dessas n vezes, o for interno varre a lista inteira, ou
seja executa n operações, portanto o total da de novo n*n.
๏ Resumindo, conseguimos bolar outro método para ordenar, mas
ele revelou-se tão ruim quanto o outro.
Ordenar
๏ Será que conseguimos bolar mais algum método?
๏ Vamos pensar no seguinte: suponham que eu mantenha duas listas
de números, uma primeira com os números à serem ordenados e
uma segunda inicialmente vazia.
๏ Vamos pegando os números da primeira lista e inserindo um à um
na segunda, já na posição correta.
๏ Para inserir na segunda lista então, preciso varrê-la desde o
começo. Assim que eu achar um número maior do que o que estou
querendo inserir, achei a posição onde inseri-lo.
๏ Mas como eu posso fazer inserção em um vetor? Eu teria que
copiar todo mundo que vem depois uma posição mais abaixo, para
liberar espaço para o número que quero inserir.
๏ Ou seja, com um normal vetor, inserção é uma operação pesada.
Inserção e remoção em listas
๏ Para o que que os vetores que conhecemos são bons?
๏ Elem permitem acésso imediato a qualquer elemento da lista, se
eu souber a sua posição.
๏ Para que vetores são ruins?
๏ Se eu quiser inserir um elemento no meio da lista, eu tenho que
pagar um preço caro, copiando todo mundo que vem depois para
abrir espaço para inserção.
๏ Da mesma forma, remoção de um elemento do meio da lista
implica em copiar todos os que vem depois uma posição pra cima,
para não deixar um espaço vazio.
๏ Outra outra coisa dificil de fazer é mudar o tamanho máximo da
lista. Se eu aloquei 100 elementos, mas alguma hora percebo que
preciso de 150, eu tenho que chamar o malloc de novo e alocar
150 elementos, ai copiar os 100 para a nova area de memória
alocada e liberar a velha memoria
Lista ligada

3 4 7 10 13 16

Agora, para inserir o número 8 é so criar uma nova caixinha e re-ajustar os ponteiros..

3 4 7 10 13 16

8
Lista ligada
๏ Com uma estrutura como essa mostrada no slide anterior, temos
algo que suporta as 3 operações que mencionamos.
๏ Para ler a lista toda, é so percorrer as flechas (ponteiros), para
achar de vez em vez o próximo elemento.
๏ Para inserção, precisamos somente ajustar 2 ponteiros.
๏ Para remoção, precisamos somente ajustar 1 ponteiro
๏ Para mudança de tamanho, é só adiconar elementos no final da
lista.
๏ Mas como podemos construir estas caixinhas, que contenham um
número e um ponteiro para outra caixinha igual?
๏ Usando uma “struct”.
Lista ligada
๏ struct sListNode
{
int num;
struct sListNode *next;
};
๏ Esta estrutura é examente o que precisamos para montar uma
lista ligada.
๏ Então, vamos deixar de lado este assunto por um pouco e falar
antes de... funções.
Funções
๏ Funções são um instrumento extremamente poderoso em
computação.
๏ O uso de funções é o que nos permite passar de programas de 10
ou 20 linhas de código a programas com 100.000 linhas de
código, sem se perder em tanta complexidade.
๏ Funções nos permitem:
❖ quebrar um programa em módulos.
❖ isolar e esconder detalhes.
❖ resolver varias vezes o mesmo problema sem ter que duplicar o código.
❖ Criar novas operações primitivas, por exemplo ordenar uma lista de
números com uma única instrução.
๏ Funcões são usadas para capturar sequências frequentes de
código.
Funções

Entradas Função potência Saída

2
Base 16
4 processamento Resultado
Exponente

๏ Podemos pensar em funções (do ponto de vista de quem as usa),


como uma caixa preta, para quem eu passo informações que são
processadas internamente, sem eu precisar me preocupar com
“como” e que produz algum resultado do outro lado
Funções
๏ Vamos agora pensar em funções do ponto de vista de quem as
cria (ou escreve)
๏ Escrever uma função é como escrever um pequeno programa.
๏ Temos que pensar só no problema que a função tem que resolver
(por ex. calcular uma potência), sem nos preocupar com quando
ou como ela será usada, nem com o que mais o programa que à
usa estara fazendo.
๏ Isso nos permite resolver um programa grande quebrando-o em
pequenos problemas, cada um dos quais se torna uma função.
๏ Então, em vez de resolver um problema grande e dificil,
resolvemos vários pequenos problemas e usamos a solução deles
para resolver o problema maior.
๏ Vamos então escrever uma função que calcula potências.
Funções
Entradas Função potência Saída

2
Base 16
4 processamento Resultado
Exponente

float potência(float base, int exp)


{
! if (exp == 0)
! ! return 1;
! if (exp == 1)
! ! return base;
! float resultado = base;
! while (exp > 1)
! ! resultado *= base;
! return resultado;
}
Funções
๏ Então, a sintaxe de uma função é:
๏ <tipo do resultado> <nome da função>(<tipo e nome do primeiro
parâmetro>, <tipo e nome do segundo parâmetro>, ... ) { <código> }
๏ Se agora quisessemos usar uma função já feita, como a da
potência do exemplo anterior, é só escrever:
int resultado = potência(2,4);
๏ Uma função pode também não retornar nenhum resultado. Por
exemplo, se quisessemos uma função que simplesmente imprima
uma lista de números na tela, não precisaríamos que ela
“retornasse” um valor. Neste caso ela tem um tipo de retorno
especial, “void”, que significa que ela não retorna nada.
๏ Exemplo: void mostraLista(int lista[], int quantos);
Funções
๏ Podemos também pensar em uma função que não precise de
parâmetros de entrada, por exemplo uma função que retorne a
hora atual:
char *hora(void) {...}
ou simplesmente:
char *hora() {...}
Funções - Parâmetros
๏ Considere o seguinte código:
#include <stdio.h>
void func(int num)
{
! num++;
}

int main (int argc, const char * argv[])


{
! int a = 0;
! func(a);
! printf("%d",a);
}
๏ Qual valor será impresso na tela? 0 ou 1?
๏ Em outras palavras, se eu alterar o valor de num, o valor de a
muda?
Funções - Parâmetros
๏ Não, o valor não muda, porque o parâmetro que a função recebe
é uma copia do original. Isto se chama passagem de parâmetro por
valor.
๏ Considere agora este exemplo:
void func(int *num)
{
! (*num)++;
}
int main (int argc, const char * argv[])
{
! int a = 0;
! func(&a);
! printf("%d",a);
}
๏ Qual valor será impresso na tela?
๏ Desta vez, o valor de a mudou!!! A razão é que função recebeu o
endereço de a como parâmetro (e não uma copia de a). Então,
como num aponta para a, incrementar num significa incrementar o
próprio a.
๏ Isto se chama passagem de parâmetro por referência.
Funções - Retorno
๏ Vimos então que uma função pode ter vários parâmetros de
entrada.
๏ Mas vimos também que uma função pode retornar um único valor
de saída.
๏ Como eu faço se precisar de uma função que produza mais que
um valor como resultado?
๏ 3 métodos:
๏ (1) crio uma struct que contenha todos os valores de que vou
precisar e retorno a struct.
๏ (2) passo ponteiros para as variáveis onde eu quero que a função
guarde os resultados.
๏ (3) Variáveis globais
๏ Vamos fazer o exemplo de uma função que receba 3 números de
entrada e calcule mínimo e máximo como resultado, usando os
tres métodos acima...
Funções - Retorno
#include <stdio.h>
struct sMaxMin
{
! int min,max;
};
struct sMaxMin funcMaxMin(int a, int b, int c)
{
! struct sMaxMin ret;
! ret.min = a;
! ret.max = a;
! if (b < a) ret.min = b;
! if (b > a) ret.max = b;
! if (c < ret.min) ret.min = c;
! if (c > ret.max) ret.max = c;
! return ret;
}
int main(int argc, char** argv)
{
! struct sMaxMin res;
! res = funcMaxMin(4, 3, 5);
! printf("Max:%d; min:%d\n", res.max, res.min);
}
Funções - Retorno
#include <stdio.h>
void funcMaxMin(int a, int b, int c, int *min, int *max)
{
! *min = a;
! *max = a;
! if (b < a) *min = b;
! if (b > a) *max = b;
! if (c < *min) *min = c;
! if (c > *max) *max = c;
}
int main(int argc, char** argv)
{
! int rmax, rmin;
! funcMaxMin(4, 3, 5, &rmax, &rmin);
! printf("Max:%d; min:%d\n", rmax, rmin);
}
Variáveis - Scoping
๏ #include <stdio.h>
void f(void)
{
! int num;
}
int main(int argc, char** argv)
{
! int num;
}
๏ Como posso ter duas variáveis com o mesmo nome?
๏ Porque elas tem visibilidade (scoping) diferente. Se eu declarar uma
variável dentro de um bloco de código delimitado por {}, ela vai
existir somente dentro daquele bloco.
๏ Eu posso também declarar variáveis globais, ou seja declaradas
fora de qualquer função, que serão portanto visíveis de qualquer
função.
๏ Exemplo:
Variáveis - Scoping
#include <stdio.h>
int quadrado;
void calculaQuadrado(int num)
{
! quadrado = num * num;
}
int main(int argc, char **argv)
{
! calculaQuadrado(4);
! printf("Quadrado: %d", quadrado);
}
๏ Neste exemplo “quadrado” é uma variável global, livremente
acessível por todas as funções.
๏ Variáveis globais podem ser muito úteis para guardar dados que eu
quero sejam acessíveis facilmente de qualquer lugar.
๏ Também são perigosas, porque tornam o código dificil de
acompanhar; é dificil entender quem muda uma variável global e
quando.
Funções - Potência
๏ Já vimos então como calcular potências, com complexidade O(n),
onde n é o exponente.
๏ Será que conseguimos bolar algo mais eficiente?
๏ Vamos pensar no seguinte:
๏ 28 = 2*2*2*2*2*2*2*2 = 24 * 24
๏ 24 = 2 * 2 * 2 * 2 = 22 * 22
๏ Então, se eu souber calcular 22, só com mais uma multiplicação eu
consigo calcular 24 e em seguida com só mais uma multiplicação,
eu consigo calcular 28.
๏ Ou seja, eu consigo dobrar o exponente e fazer só uma
multiplicação a mais, em vez de o dobro.
๏ Portanto calculo uma potência com complexidade O(lg n), que é
MUITO menos que O(n).
๏ Por exemplo, se n = 1024, lg n = 10
๏ Vamos escrever uma função para calcular potências com esse
novo método super eficiente.
Potência rapida
float potênciaRapida(float base, int exp)
{
! if (exp == 0)
! ! return 1;
! if (exp == 1)
! ! return base;
! float res;
! if (exp % 2 == 0)
! {
! ! res = potênciaRapida(base, exp / 2);
! ! return res * res;
! }
! else
! {
! ! res = potênciaRapida(base, (exp - 1) / 2);
! ! return res * res * base;
! }
}
Complexidade - Potência
๏ Acabamos então de ver uma função recursiva, ou seja uma função
que chama a si mesma.
๏ Vamos simular...

๏ Vamos agora fazer outro exemplo de função recursiva para


calcular o número de Fibonacci de um inteiro n
๏ A definição de número de Fibonacci (F) é:
๏ F(0) = 0
๏ F(1) = 1
๏ F(n) = F(n-1)+F(n-2)
๏ Exemplo:
๏ F(5) = F(4) + F(3) = F(3) + F(2) + F(2) + F(1) = F(2) + F(1) + 2 * (F(1) + F(0))
+ F(1) = 3 * (F(1) + F(0)) + 2 * F(1) = 5
๏ Vamos ver a função:
Fibonacci
int Fibonacci(int num)
{
! if (num == 0) return 0;
! if (num == 1) return 1;
! return Fibonacci(num - 1) + Fibonacci(num - 2);
}

๏ Parece simples demais para ser verdade...


๏ Mas se pararmos para pensar e simular, veremos que é isso
mesmo.
๏ Este é o poder das funções recursivas... para alguns problemas,
elas fornecem soluções extremamente simples!
Ordenar com lista ligada
๏ Voltando agora ao nosso método de ordenar com lista ligada,
vamos ver como fica.
๏ Precisamos de uma função para inserir um novo valor em uma
lista.
๏ Esta função vai separar 3 casos: (1) caso em que o número a ser
inserido vai ser o primero, (2) caso em que ele vai ficar no meio da
lista e (3) caso em que vai ser o último.
๏ Este método também vai ter complexidade O(n2), ou seja também
é um método ruim.
๏ A função ficaria:
Ordenar com lista ligada
struct sListNode
{
! int num;
! struct sListNode *next;
};
//cria um alias: em vez de escrever "struct sListNode"
//escrevo simplesmente "ListNode"
typedef struct sListNode ListNode;
ListNode *insertNum(int num, ListNode *lista)
{
! //cria um novo nó e coloca num nele
! ListNode *newNode = malloc(sizeof(ListNode));
! newNode->num = num;
! newNode->next = 0;
! if (num < lista->num) // (*lista).num
! { //caso em que num vai ser o primeiro elemento da lista
! ! newNode->next = lista;
! ! return newNode;
! }
! ListNode *currentNode = lista;
! //emquanto existe um próximo nó E o número que eu quero inserir é maior que
! //o que esta no próximo nó da lista
! while (currentNode->next != NULL && num > currentNode->next->num)
! { //anda para frente
! ! currentNode = currentNode->next;
! }
! if (currentNode->next != NULL)
! { //caso em que num vai ser inserido no meio da lista
! ! newNode->next = currentNode->next;
! ! currentNode->next = newNode;
! ! return lista;
! }
! //caso em que o num vai ser o último da lista
! currentNode->next = newNode;
! return lista;
}
Ordenar com árvore
๏ Será que conseguimos bolar mais um método de ordenar, desta
vez eficiente?
๏ Vamos ver...
Ordenar - TreeSort
Quero ordenar: 10, 5, 15, 3, 7, 4, 14 Raiz da árvore
10
Ordenar - TreeSort
Quero ordenar: 10, 5, 15, 3, 7, 4, 14 Raiz da árvore
10
5
Ordenar - TreeSort
Quero ordenar: 10, 5, 15, 3, 7, 4, 14 Raiz da árvore
10
5 15
Ordenar - TreeSort
Quero ordenar: 10, 5, 15, 3, 7, 4, 14 Raiz da árvore
10
5 15

3
Ordenar - TreeSort
Quero ordenar: 10, 5, 15, 3, 7, 4, 14 Raiz da árvore
10
5 15

3 7
Ordenar - TreeSort
Quero ordenar: 10, 5, 15, 3, 7, 4, 14 Raiz da árvore
10
5 15

3 7

4
Ordenar - TreeSort
Quero ordenar: 10, 5, 15, 3, 7, 4, 14 Raiz da árvore
10
5 15

3 7 14

4
๏ struct sTreeNode
{ int num;
struct sTreeNode *left, *right;
}
Ordenar com árvore
๏ Se conseguirmos montar uma árvore como essa mostrada na
figura, já temos a lista ordenada; é só visitar os nos da árvore na
ordem correta, imprimindo seus valores.
๏ Para obter essa ordem correta, a logica é: cada vez que encontro
un novo nó, imprimo primeiro o valor dos nós que estão a sua
esquerda, depois ele mesmo, depois o valor dos nós que estão a
direita.
๏ Para obter isto, basta uma simplissima função recursiva:
๏ void treeWalk(TreeNode *root)
{ //mostra primeiro quem esta a esquerda
! if (root->left)
! ! treeWalk(root->left)
! //depois eu
! printf("%d\n", root->num);
! //mostra quem esta a direita
! if (root->right)
! ! treeWalk(root->right)
}
Ordenar com árvore
๏ Só nos falta então uma função para inserir novos números em
uma árvore:
void treeInsert(int num, TreeNode *root) else
{ ! { //vai para a direita
! if (num < root->num)
! {//vai pra esquerda
! ! if (root->right != NULL)
! ! if (root->left != NULL) ! ! {
! ! {//se tem alguém pra esquerda ! ! ! treeInsert(num, root-
! ! ! //insere na sub-árvore esquerda >right);
! ! ! treeInsert(num, root->left); ! ! }
! ! }
! ! else
! ! else
! ! { //se não, achei onde inserir ! ! {
! ! ! //cria um novo nó e coloca ele à ! ! ! TreeNode *newNode = malloc
minha esquerda (sizeof(TreeNode));
! ! ! TreeNode *newNode = malloc(sizeof ! ! ! newNode->left = NULL;
(TreeNode)); ! ! ! newNode->right = NULL;
! ! ! newNode->left = NULL;
! ! ! newNode->right = NULL; ! ! ! newNode->num = num;
! ! ! newNode->num = num; ! ! ! root->right = newNode;
! ! ! root->left = newNode; ! ! }
! ! } ! }
! } }
Ordenar com árvore
๏ Com as funções que escrevemos, fica fácil ordenar:
int quantos;
! printf("Quantos números vc quer ordenar? ");
! scanf("%d", &quantos);
! //inicializa o gerador de números aleatórios
! srand(clock());
! TreeNode *tree = malloc(sizeof(TreeNode));
! tree->num = rand();
! tree->left = NULL;
! tree->right = NULL;
! //gera "quantos" números aleatórios e insere eles na
lista
! for (int i = 1; i < quantos; i++)
! {
! ! treeInsert(rand(), tree);
! }
! treeWalk(tree);
Funções
๏ Agora que sabemos o que são funções e como escrevelas,
podemos livremente usar funções escritas por outros.
๏ De agora em diante, boa parte do trabalho de programar é
aprender a usar funções e descobrir quais existem.
๏ Por exemplo, como faço para baixar um XML da web? Alguém
escreveu uma função para isso que eu posso usar.
๏ Como façõ para tocar um mp3 no iPhone? Alguém escreveu uma
função para nos usarmos.
๏ E assim vai...
๏ Aprender iPhone SDK significa aprender quais funções existem para
programação no iPhone e como usá-las.
The C Standard Library
๏ A linguagem C vem com uma biblioteca padrão de funções, das
quais a maioria é util também no iPhone.
๏ Estas funções são agrupadas em “header files”, os arquivos .h que
em parte vimos no começo de nossos programas, como “#include
<stdio.h>”
๏ Este arquivo stdio.h por exemplo inclue as funções que vimos até
agora, como printf e scanf e outras como as de acesso a files, que
já vamos ver.
๏ Outros headers importantes são math.h, que contem funções
matematicas como seno, coseno e outras.
๏ time.h, que contem funções relativas a tempo e horas.
๏ string.h, para manipulação de strings em C
๏ stdlib.h, que contem por exemplo a função rand() para gerar
números aleatórios.
The C Standard Library
๏ Para ver a lista completa, é só digitar no Google “C standard
library”.
๏ Alem disso, se quisermos saber mais sobre uma função da C
standard library, em MAC e Linux, é só digitar no terminal “man
printf” por exemplo.
๏ O comando man (de manual) nos permite ver a documentação de
qualquer função da biblioteca C.
๏ Vamos agora usar as funções desta biblioteca para aprender a ler
e escrever arquivos (FILEs).
FILEs
๏ Do ponto de vista do programador, um FILE é uma longa sequência
de bytes.
๏ Para poder ler ou escrever, um file precisa primeiro ser aberto da
forma correta.
๏ Uma vez aberto, recebo um ponteiro para uma estrutura FILE, que
posso usar para ler e escrever.
๏ Um file aberto tem uma posição atual e quando eu ler ou
escrever, vou fazê-lo a partir desta posição atual.
๏ Para escrever, a função fwrite precisa saber onde pegar os bytes
que vão ser escritos, quantos bytes vão ser escritor e um ponteiro
para o file aberto.
๏ A função que le precisa de um endereço de memória onde
escrever os bytes lidos do file, quantos bytes ler e o ponteiro para
o file.
๏ Vamos fazer um exemplo que escreva um int em um file.
FILEs - Escrever
int num;
! ! FILE *fp = fopen("/Users/omar/teste.txt", "w");
! ! if (fp) // if (fp != NULL)
! ! {
! ! ! //abriu! vamos escrever
! ! ! printf("Qual número vc quer escrever? ");
! ! ! scanf("%d", &num);
! ! ! if (fwrite(&num, sizeof(int), 1, fp) == 1)
! ! ! {//sucesso!
! ! ! ! printf("Escrevi %d\n", num);
! ! ! }
! ! ! else
! ! ! {
! ! ! ! printf("Erro ao escrever no arquivo!\n");
! ! ! }
! ! ! fclose(fp);
! ! }
! ! else
! ! {
! ! ! printf("Erro ao abrir o arquivo!\n");
! ! }
FILEs - Ler
int num;
! ! FILE *fp = fopen("/Users/omar/teste.txt", "r");
! ! if (fp)
! ! {//funcionou
! ! ! if (fread(&num, sizeof(int), 1, fp) == 1)
! ! ! {
! ! ! ! printf("Li %d\n", num);
! ! ! }
! ! ! else
! ! ! {
! ! ! ! printf("Erro ao ler do arquivo!\n");
! ! ! }
! ! ! fclose(fp);
! ! }
! ! else
! ! {
! ! ! printf("O arquivo não existe!\n");
! ! }
FILEs - Detalhes
๏ Vamos entender mais uns detalhes.
๏ Tentem usar o código anterior para escrever um número, por
exemplo 10000.
๏ Agora tentem abrir com o TextEdit o arquivo que foi criado... o
que vocês esperariam encontrar la? Encontraram o que
esperavam?
๏ O problema é que um int ocupa 4 bytes, mas o textedit não
sabe que estes 4 bytes são um int, então ele interpreta cada
byte como um char.
๏ Para ver realmente o que esta escrito lá, precisamos de um Hex
Editor, ou seja um editor que nos mostre o valor dos bytes, sem
interpretar nada.
๏ Vamos agora fazer outro teste e escrever la o número 1;
Endianness
๏ Em hexadecimal, a gente esperaria encontrar 00 00 00 01
๏ Mas na verdade achamos la 01 00 00 00. Porque esta ao
contrário?
๏ A razão é o jeito como as CPUs da Intel guardam números
inteiros. CPUs da Intel são de tipo “Little Endian”.
๏ Isso significa que estas CPUs memorizam primeiro o byte menos
significativo, ou seja invertem a ordem dos bytes.
๏ Outras CPUs como o ARM que tem no iPhone são de tipo “Big
Endian” e nestas sim os números são armazenados como
esperaríamos.
๏ Endianness é uma coisa que é importante considerar na hora por
exemplo de fazer um aplicativo que inclua comunicação de rede
entre máquinas com arquiteturas diferentes.
Multi-file Programs
๏ Até agora vimos somente simples programas feitos de um único
arquivo, onde escrevemos todo o nosso código no arquivo main.c
๏ Isto torna-se inviável na hora de escrever programas grandes, no
qual caso é bom separar o código em arquivos diferentes.
๏ Tem também outras razões para separar o código em mais
arquivos. Por exemplo, se eu desenvolver um conjunto de
funções para comunicação em rede, eu posso querer vendê-las,
mas sem que o comprador tenha acesso ao meu código fonte
descobrindo todos os segredos da minha super implementação.
๏ Para isso o que eu gostaria de fazer é mandar o meu código já
compilado, de forma que o comprador não possa ver o código
fonte.
Multi-file Programs
๏ Ou seja, eu tenho que mandar uma “library” já compilada e um
associado “header file”.
๏ Quando separamos funções em outros arquivos, em geral
criamos dois arquivos para cada grupo de funções, um .h que
contem somente declarações de funções e variáveis e
um .c/.cpp (código fonte) ou um .lib (já compilado).
๏ Desta forma, quem for usar a minha classe precisa só se
interessar com o .h e nem ver o que tem no .c (ou tanto menos
no .lib).
๏ Vamos fazer um exemplo, com a nossa função para potências,
supondo que eu quera separá-la em outro arquivo.
Multi-file Programs
File potência.h:

float potência(float base, int exp);

File potência.c:
float potência(float base, int exp)
{
! if (exp == 0)
! ! return 1;
! if (exp == 1)
! ! return base;
! float resultado = base;
! while (exp > 1)
! ! resultado *= base;
! return resultado;
}
Multi-file Programs

Entre <> quando é um header padrão que veio com o ambiente

File main.c:

#include <stdio.h> Entre “” quando é um header que esta no meu projeto


#include "Potência.h"

int main (int argc, const char * argv[])


{
! printf("%d", potência(2, 3));
}
Multi-file Programs
๏ Outra vantagem é que agora quem for usar a minha função
potência, sabe tudo que precisa olhando o .h e não precisa se
preocupar com como a função foi implementada, mas somente
com quais parâmetros ela quer de entrada e qual resultado
produz na saída.
๏ Desta forma, podemos dividir nossos projetos grandes em vários
pares .h/.c, cada um com grupos de funções relacionadas ao
mesmo assunto e portanto modularizar o nosso software.
๏ Modularizar permite gerenciar complexidade e torna factíveis
programas de 200.000 linhas de código.
C++: Classes e Objetos
๏ Falando em modularizar nossas aplicações, Classes, que são a
principal mudança do C para o C++, são outro instrumento que
nos ajuda a quebrar um problema grande em módulos (ou
classes) menores.
๏ Inicialmente, podemos pensar em classes como “struct”s que
contem como membros, alem de variáveis, também funções (ou
métodos).
๏ Assim podemos usar classes para modelar objetos complexos
com comportamentos.
๏ Por exemplo poderíamos pensar em uma classe “Carrinho”, com
a qual simular um carrinho de compras. Podemos adicionar ou
remover itens neste carrinho, obter o valor total etc.
C++: Classes e Objetos
class Carrinho
{
! int numItens;
! char **nomes;
! float *precos;
! void adicionarItem(char *nome, float preco)
! {...}
! void removerItem(char *nome)
! {...}
};

Carrinho car1;
car1.adicionarItem("Harry Potter", 14.95);
C++: Classes e Objetos
๏ Como vimos no exemplo anterior, podemos usar uma classe da
mesma forma que uma struct.
๏ Mas ainda temos vários detalhes que nos separam de uma
struct.
๏ No exemplo anterior, o que acontece se o usuário alterar
diretamente a variável numItens?
๏ Provavelmente, vai quebrar o sistema, pois vou alterar de forma
errada uma variável que na verdade é usada só internamente
pela classe.
๏ Para evitar estes problemas, posso querer limitar o acesso direto
à alguns membros da classe, de forma que só as funções
membros da classe possam acessar a variável e não quem for
usar a minha classe.
๏ Esta forma de limitação naturalmente existe. Vamos ver como
funciona...
C++: Classes e Objetos
๏ Posso limitar o acesso seja a membros (variáveis) que a métodos
(funções), separando-as em “private” e “public”.
๏ Desta forma, os membros “private” podem ser acessados
somente pelas funções internas da classe. Os membros “public”
podem ser acessados por qualquer um, de fora ou de dentro da
classe.
๏ class Carrinho
{ private:
! int numItens;
! char **nomes;
! float *precos;
public:
! void adicionarItem(char *nome, float preco)
! {...}
! void removerItem(char *nome)
! {...}
};
C++: Classes e Objetos
๏ Vimos então como limitar o acesso à alguns membros de uma
classe.
๏ Uma outra capacidade desejável seria, na hora de declarar um
objeto do tipo carrinho, fazer automaticamente alguma
inicialização, por exemplo setar a variável numItens para 0.
๏ Para isto, classes tem duas funções especiais, chamadas de
“construtor” e “destrutor”, que servem para isso.
๏ O construtor é chamado automaticamente na hora que um
objeto da classe é alocado.
๏ O destrutor é chamado automaticamente quando um objeto é
de-alocado, permitindo liberar recursos segurados pela classe,
como arquivos abertos ou outra memória alocada.
๏ Construtor e destrutor tem que ter nomes específicos.
C++: Construtores e Destrutores
๏ class Carrinho
{
Carrinho(void) //construtor 1
{...}
Carrinho(char *nome, float valor) //construtor 2
{...}
~Carrinho(void) //destrutor
{...}
}
๏ O construtor deve ter o mesmo nome da classe.
๏ Podemos ter mais que um construtor, mas cada um deve aceitar
parâmetros diferentes.
๏ O destrutor tem que ter o mesmo nome da classe, precedido
por um ~ e não pode aceitar parâmetros.
๏ Mas como são usados?
C++: new e delete
๏ Quando trata-se de classes, as usamos sempre em forma de
ponteiros; desta forma podemos automaticamente chamar o
construtor.
๏ Vamos usar esta chance para aprender também como se faz
alocação de memória em C++ ( malloc ainda pode ser usada, mas
é aconselhado, sobretudo para classes, usar os métodos próprios
do C++)
๏ Carrinho *c = new Carrinho();
๏ O * indica um ponteiro.
๏ O new aloca memória em C++.
๏ Carrinho() é a chamada ao construtor.
๏ delete c;
๏ O delete de-aloca a memória alocada com “new” e
automaticamente chama o destrutor.
C++: .h e .cpp
๏ Geralmente separamos a declaração da classe (.h) da
implementação dos métodos (.cpp), usando o nome da classe
como nome do arquivo.
๏ Por exemplo, em um .h poríamos:
class a
{
public: :: indica que a função c()
é membro da classe a
int b;
int c(void);
}
๏ E no .cpp poríamos:
int a::c(void)
{
return b;
}
C++: classe Stack
๏ Um Stack é uma estrutura de dados muito comum e conhecida
em computação e significa pilha.
๏ As funções suportadas por uma pilha são:
๏ top: diz quem esta em cima
๏ pop: remove o item de cima da pilha
๏ push: empilha um item
๏ isEmpty: diz se a pilha esta vazia
๏ Os itens à serem empilhados podem ser qualquer coisa... simples
números, structs, outrois objetos... enfim qualquer tipo de
variável de C ou C++
๏ Para simplificar o exemplo, vamos fazer uma classe que empilhe
números inteiros positivos
C++: classe Stack: header

class Stack
{
private:
! int pilha[100];
! int topElement;
public:
! Stack(); //construtor
! Stack(int num); //construtor
! ~Stack(); //destrutor
! bool isEmpty(); //fala se a pilha esta vazia
! int top(); //devolve o elemento de cima da pilha
! bool push(int num); //empilha num
! int pop(); //desempilha
};
C++: tipo bool
๏ Este tipo, bool, é um tipo introduzido no C++, que pode ter
somente os valores “true” ou “false”.
๏ Exemplo:
bool b;
b = true;
b = false;
๏ “true” e “false” são constantes que valem 1 e 0.
๏ Voltando à nossa classe Stack
C++: classe Stack: implementação
bool Stack::push(int num) //empilha num
{
! if (topElement == 99)
!{
! ! return false;
!}
! topElement++;
! pilha[topElement] = num;
! return true;
}

int Stack::pop() //desempilha


{
! if (topElement == -1)
!{
! ! return -1;
!}
! int n = pilha[topElement];
! topElement--;
! return n;
}
C++: classe Stack: implementação
Stack::Stack() //construtor
{
! topElement = -1; //pilha vazia
}
Stack::Stack(int num)
{
! Stack();
! push(num);
}
Stack::~Stack() //destrutor
{
}
bool Stack::isEmpty() //fala se a pilha esta vazia
{
! if (topElement == -1)
! ! return true;
! else
! ! return false;

}
int Stack::top() //devolve o elemento de cima da pilha
{
! return pilha[topElement];
}
C++: static members
๏ Para poder usar a função push(), por exemplo, vimos que temos
primeiro que alocar um objeto de tipo Stack e só depois
podemos usar suas funções membros.
๏ Estas funções são chamadas funções de instância, porque preciso
de uma instância da minha classe para poder chamá-las. Da
mesma forma, as variáveis que vimos também são de instância,
pelo mesmo motivo.
๏ Existem funções e variáveis membros de classe que podem ser
usadas sem alocar primeiro um objeto daquela classe. Estas são
chamads de funções (ou variáveis) de classe. Em C++, se chamam
de estaticas. (static em ingles).
๏ Por serem de classe, elas são únicas para todos os objetos da
classe e nos permitem fazer coisas como por exemplo contar
quantos objetos daquela classe foram criados.
C++: static members
๏ class a
{
public:
static int b;
static void func(void);
}
a::b = 10;
a::func();
C++: subclasses
๏ Vamos agora supor que alguém tenha escrito uma classe Pessoa,
que guarde todos os dados de uma pessoa, como por exemplo
nome, idade, rg, endereço.
๏ Vamos supor que a gente queira criar uma classe Empregado, que
faça tudo que a classe pessoa já faz, mas alem disso tenha
também um campo “cargo”, dizendo o trabalho da pessoa.
๏ Em vez de reescrever tudo, podemos estender a classe Pessoa:
class Empregado : Pessoa
{
char *cargo;
}
C++: subclasses
๏ Com isto, criamos uma sub-classe da classe Pessoa, que contem
automaticamente tudo que a classe Pessoa tem e alem disso o
campo cargo.
๏ Podemos agora escrever coisas como
Empregado e;
e.nome = “Omar”;
e.cargo = “Programador”;
๏ Alem disso, vamos supor que temos alguma função que receba
como parâmetro um objeto de tipo pessoa. Podemos passar um
objeto de tipo empregado para essa função, que nunca vai
perceber a diferença, já que a nossa classe Empregado faz tudo
que a classe Pessoa já faz.
๏ Esta possibilidade de trocar uma classe por qualquer sua
subclasse se chama polimorfismo.
Objective-C
๏ Objective-C é uma mistura de duas linguagens diferentes, C e
Smalltalk.
๏ Em Obj-C, posso literalmente escrever Obj-C e C tudo
misturado, portanto tudo que aprendemos até agora pode ser
usado também em objetive C.
๏ As principais diferenças são na sixtaxe e no comportamento das
classes.
๏ Vamos tentar listar todas as diferenças no comportamento..
๏ Em Obj-C, todos os membros são “private” e isso não pode ser
mudado.
๏ O conceito de variável estática não existe em Obj-C. Para fazer
a mesma coisa temos que usar uma variável global.
๏ Em Obj-C, todas as funções são “public”.
๏ Em Obj-C, tudo é uma classe. Não é possível escrever funções
“soltas”, não membros de alguma classe!
Objective-C - classes - .h
๏ Como no C++, no .h vai a declaração de uma classe, no .m
(equivalente ao .cpp), vai a implementação dos métodos.
๏ Exemplo de declaração de classe:

@interface UmaClasse : NSObject


{
! int umInteiro;
}

- (void) umaFuncaoDeInstancia: (int) a;


+ (void) umaFuncaoDeClasse;

@end
Objective-C - classes - .

#import "UmaClasse.h"

@implementation UmaClasse

- (void) umaFuncaoDeInstancia: (int) a


{
! //...
}
+ (void) umaFuncaoDeClasse
{
! //...
}

@end
Objective-C vs. outras

C/Java/Pascal... Objective-C
Declaração: Declaração:
void meuMetodo(void) {...} -(void)meuMetodo {...}

Chamada: Chamada:
meuMetodo(); [self meuMetodo];
Declaração: Declaração:
void meuMetodo (int x) {...} -(void)meuMetodo:(int)x {...}

Chamada: Chamada:
meuMetodo(4); [self meuMetodo:4];
Declaração: Declaração:
void potência (int base, int exp) -(void)potênciaBase:(int)base exponente:(int)exp {...}
{...}
Chamada: Chamada:
meuMetodo(4,5); [self potênciaBase:4 exponente:5];

Declaração: Declaração:
int potência (int base, int exp) { -(int) potênciaBase:(int)base exponente:(int)exp {
return ...; return ...;
} }

Chamada: Chamada:
int z = meuMetodo(4,5); int z = [self potênciaBase:5 exponente:6];
Objective-C - propriedades
๏ Como em Obj-C todas as variáveis membros são “private”, o
único jeito de acessá-las é atraves de funções.
๏ Para isto, teríamos que criar uma função “setter”, para atribuir um
valor para a variável e uma função “getter”, para ler o valor da
variável. Exemplo:

@interface UmaClasse : NSObject getter: mesmo nome da variável


{
! int umInteiro;
}
- (int) umInteiro;
- (void) setUmInteiro: (int) a;
@end
- (int) umInteiro setter: mesmo nome da variável
{
! return umInteiro; com a primeira letra maiuscola e
} get na frente
- (void) setUmInteiro: (int) a
{
! umInteiro = a;
}
Objective-C - propriedades
๏ Para agilizar um pouco este processo, Objective-C tem o
conceito de “propriedade”, que tem uma vaga semelhança com o
conceito de variável “public”.
๏ Eu posso declarar uma variável como sendo uma propriedade.
๏ Tenho que criar getter e setter para ela (tem um jeito
automático de fazer isso).
๏ A partir deste momento, posso acessar a variável com a sintaxe
do “.” como se fosse uma variável public, mas o que acontece de
verdade por traz é que o setter e getter são usados para alterar
o valor dela. Então a variável não é mesmo public, mas é quase
como se fosse.
๏ Com o exemplo anterior ficaria:
Objective-C - propriedades
@interface UmaClasse : NSObject
{
! int umInteiro;
}

@property (nonatomic, assign) int umInteiro;

@end
Diz que a variável umInteiro é uma
propriedade (pode ser acessada com o .)

@implementation UmaClasse

@synthesize umInteiro;

@end;
Cria automaticamente getter e setter
para a variável umInteiro
Threads
๏ Uma thread é um processo paralelo no nosso programa,
geralmente uma função.
๏ Com threads, posso executar duas funções em paralelo, ao
mesmo tempo.
๏ No caso de uma CPU só, a realidade é que o OS alterna entre as
duas (ou mais) threads, mas faz isso tão rapidamente que a
impressão é de execução simultanea.
๏ No caso de CPU multi-core, a execução é realmente simultanea.
๏ Threads criam várias dificuldades para os programadores, devidas
ao fato de que não sabemos bem em que ordem as coisas
acontecem e não sabemos quando o OS vai suspender uma
thread para iniciar a execução de outra.
๏ Para evitar problemas, precisamos de alguma forma de
sincronizar as threads.
Threads
๏ Vamos fazer um exemplo de código que pode criar problemas. O
exemplo é o de dois clientes operando na mesma conta
bancaria. Ambos querem fazer um depósito de R$ 10, em uma
conta que contem R$ 100.
๏ Então temos uma variável, float conta = 100; e ambas as threads
querem fazer a mesma operação, conta = conta + 10;
๏ É importante entender que o que acontece em conta = conta +
10; são na verdade 3 operações: primeiro pega o valor de conta
na memoria, depois soma 10 e por fim salva o resultado de volta
em conta.
๏ Vamos supor então a seguinte ordem de operações:
Threads
๏ Thread 1 ๏ Thread 2

le o valor de conta leu 100

soma 10 deu 110

le o valor de conta leu 100

soma 10 deu 110

salva o resultado salvou 110

salva o resultado salvou 110

๏ Mas era para o resultado ser 120, se os dois somaram 10.


Threads
๏ No exemplo anterior vimos então que quando mais que uma
thread escrevem na mesma variável, podemos ter problemas.
๏ Ou seja, esta operação de incremento em um dado
compartilhado é uma operação crítica, que não pode ser
interrompida no meio.
๏ Para isso o OS fornece um mecanismo de sincronização,
chamado de mutex.
๏ Podemos pensar em mutex como em uma bandeirinha. Somente
a thread que esta com a bandeirinha pode executar uma
operação crítica.
๏ Quando uma thread vai fazer uma operação critica, ela pede o
mutex para o OS.
๏ Se o mutex esta livre, ela consegue pegar, executa a operação e
devolve o mutex ao SO.
๏ Se o mutex ja esta ocupado por outra thread, a thread espera
até ele se liberar.
Event driven programming
๏ Em iPhone e em geral em programas com interface gráfica, o
sistema de programação é diferente.
๏ Nos acostumamos em fazer programas em que começamos
executando o nosso código, fazemos em seguida tudo que
precisamos, até o programa terminar.
๏ Em iPhone OS, ou Windows ou em geral com interfaces gráficas,
o modelo de programação não é esse, de começar e fazer tudo
seguido até acabar.
๏ Em iPhone, fazemos programação baseada em eventos.
๏ O iPhone OS nos avisa que aconteceu alguma coisa, por exemplo
foi clicado um botão e nos da a chance de rodar uma função
para responder a este click.
๏ Ou o OS nos avisa que a aplicação foi inicializada e a janela dela
esta visível na tela e temos a chance de fazer alguma coisa.
Event driven programming
๏ Em geral o programa fica simplesmente esperando acontecer
alguma coisa e reage a eventos.
๏ A forma como isso funciona é que associamos uma função a cada
evento que queremos tratar.
๏ Quando o evento acontece, o OS chama a função associada
aquele evento para nos.
๏ Estas funções se chamam de callbacks, ou seja funções que não
chamamos diretamente, mas que o OS chama para nos na hora
certa.
๏ Um conceito parecido é o de delegate. O delegate de algum
objeto é uma instancia de uma classe, responsável por
implementar funções que tratam os eventos gerados pelo objeto.
๏ Por exemplo no iPhone, uma tabela gera uma serie de eventos ou
pedidos para o seu delegate, que precisam ser respondidos. Cabe a
nos escrever as funções que respondem a estes eventos.
Event driven programming
๏ No exemplo da tabela, ela pede para o seu delegate (uma classe
que nos implementamos), atraves de funções com um nome
determinado, quantas linhas a tabela vai ter. Então temos que
escrever uma função que a tabela chama quando precisa e que
responda quantas linhas queremos na tabela.
๏ Em seguida, a tabela pergunta para o seu delegate: me da a
célula para a linha 1 e temos que escrever uma função que
forneca uma célula para uma determinada linha.
๏ Quando o usuário clicar em uma linha da tabela, de novo a
tabela chama uma função do seu delegate (que nos devemos
escrever) dizendo que foi clicada a linha tal e nos dando a
chance de fazer alguma coisa.
Design visual de aplicações
๏ No iPhone OS e não só, alem de escrever código, temos uma
ferramenta visual para fazer as nossas interfaces. Desta forma
precisamos somente escrever os métodos que tratam os
eventos, sem nos preocupar em mostrar, via código, os
elementos na tela, tendo que advinhar a posição correta em
termos de pixels etc...
๏ Em alguns ambientes, estas ferramentas de design visual já criam
parte do código para nos e temos somente que preencher os
“brancos”.
Banco de dados
๏ Falamos um poquinho de banco de dados neste curso, para
deixar os alunos preparados também para o módulo 3 de iPhone
SDK, onde se assume que o aluno já saiba o que é um banco de
dados e como usar SQL. Isso também nos da a chance de
mostrar uma linguagem direcionada (SQL).
๏ Um banco de dados relacional, como são a quase totalidade dos
bancos de dados de hoje, é feito por um conjunto de tabelas.
๏ Cada tabela tem um nome e uma serie de colunas nomeadas.
๏ SQL (Structured Query Language) é uma linguagem padrão para
consultar um banco de dados.
๏ Vamos ver, muito brevemente, as principais operações do SQL,
que são:
SQL
๏ INSERT: serve para inserir uma nova linha em uma tabela.
๏ UPDATE: atualiza informações de linhas já presentes na tabela.
๏ DELETE: remove uma ou mais linhas de uma tabela.
๏ SELECT: serve para ler uma ou mais linhas de uma ou mais
tabelas.
๏ Sql tem muitas mais operações, que não teremos tempo de ver
aqui. Quem quiser, pode consultar o guida de SQL no site
www.w3schools.com
SQL: SELECT
SQL: SELECT + WHERE
SQL: INSERT
SQL: INSERT
iPhone
๏ Vamos finalmente fazer nosso primeiro programa de iPhone, para
começar a nos ambientar nessa nova forma de programar.
๏ Abrir o XCode, escolher File/New Projecto, na barra esquerda
escolher iPhoneOS/Application e depois Window Based
Application, que é o projeto mais simples possível.
๏ Vamos chamar projeto de, advinhem, Hello World.
๏ Na barra esquerda do XCode, se expandirmos a pasta Source,
vamos ver que o projeto já tem uma classe, que se chama
HelloWorldAppDelegate.
๏ Esta classe é o delegate da aplicação, ou seja ela tem que tratar
os principais eventos que acontecem em uma aplicação de
iPhone.
๏ O primeiro que nos interessa, é o da função application:
didFinishLaunching:
iPhone
๏ Esta função vai ser o nosso main(). Ela é a primeira chance que
temos de fazer alguma coisa em um programa de iPhone.
๏ Vamos então, como primeirissimo programa, escrever Hello
World no console.
๏ O console não é visível para o usuário, mas é util para nos
desenvolvedores.
๏ Vamos escrever então:
NSLog(@”Hello World!”);
๏ Reparem que em Objective-C, strings são escritas usando @”” e
não simplesmente “” como em C/C++.
๏ Agora é só clicar Build&Run para rodar o simulador de iPhone.
๏ A tela fica branca, pois não colocamos nada la, mas no console
podemos ver o nosso HelloWorld.
๏ Nota: printf também funciona para escrever no console.
iPhone
๏ Vamos agora fazer a mesma coisa, mas mostrando o hello world
na tela.
๏ A tela do iPhone tem 320px de largura e 480px de altura, com a
origem no alto a esquerda.
๏ A barra de status do iPhone, onde tem o nível de bateria etc,
ocupa 20px, então, quando colocamos componentes direto na
janela do iPhone, temos que começar da coordenada y=20, para
evitar que nossos componentes fiquem escondidos atras da
status bar.
๏ Todo componente visível na tela do iPhone, é uma subclasse da
classe UIView
๏ Existe o conceito de views e subviews. Posso colocar uma view
dentro (por cima) de outra view e ela passa a ser uma subview da
view que esta por traz.
iPhone
๏ O componente então que nos permite mostrar texto na tela é a
classe UILabel, que naturalmente é uma subclasse de UIView.
๏ Priemira coisa então temos que alocar uma instância da classe
UILabel:
UILabel *myLabel = [[UILabel alloc] init];
๏ O método de classe “alloc” aloca um objeto do tipo (no caso)
UILabel e retorna ele. Em seguida chamamos o método “init” do
objeto alocado. Esta é a forma normal de alocar e inicializar
objetos em Objective-C.
๏ O próximo passo é colocar algum texto na label. Ela tem uma
propriedade chamada text que serve para isso:
myLabel.text = @”Hello World!”;
iPhone
๏ Agora temos que determinar posição e tamanho da nossa label
na tela. Isso é feito especificando um retângulo que à contem.
Esse retângulo é especificado dizendo o ponto no alto a
esquerda dele, sua largura e altura.
๏ Esse retângulo é a propriedade “frame” de qualquer UIView. Esta
propriedade é uma struct CGRect, que contem dois membros,
um CGPoint e um CGSize.
๏ Estes por sua vez são structs, CGPoint tem dois membros float
x e y. CGSize tem dois membros float width e height.
๏ Existe uma simples funçãozinha de C para criar um novo frame:
myLabel.frame = CGRectMake(20, 40, 200, 50);
๏ Criamos então uma label com origem no ponto 20,40, 200px
de largura e 50 de altura.
iPhone
๏ O último passo é colocar essa label na window, uma variável
que já vem pronta para nosso uso e que representa a janela do
iPhone:
[window addSubview: myLabel];
๏ Agora é só dar Build&Run para testar.
๏ Outra coisa com que podemos brincar um pouco é cor. Podemos
escrever:
myLabel.textColor = [UIColor redColor];
Isso deixa o texto vermelho.
๏ Também posso especificar uma cor usando os valores exatos de
RGB, que variam entre 0 e 1. Por exemplo:
myLabel.textColor = [UIColor colorWithRed: 0.5 green: 0.0 blue:
1.0 alpha: 1.0];
๏ Podem testar...
iPhone
๏ Vamos agora usar diretamente um UIView. A única propriedade
interessante de uma UIView é o background color. Vamos então
escrever um pouco de código para encher a tela de quadradinhos
coloridos. Para gerar cores aleatórias precisamos conseguir gerar
números aleatórios entre 0 e 1. Vamos escrever uma função para
isso.
๏ float rand01()
{
return (float) rand() / (float) RAND
MAX;
_
}
๏ Agora vamos escrever o seguinte código na função
application:didFinishLaunching:
iPhone

for (int x = 0; x < 320 / 20; x++)


! for (int y = 0; y < 460 / 20; y++)
! {
!! UIView *v = [[UIView alloc] initWithFrame:
CGRectMake(x*20,y*20+20,20,20)];
v.backgroundColor = [UIColor colorWithRed:rand01() green:rand01
() blue:rand01() alpha:1];
! ! [window addSubview:v];
! }
iPhone
๏ Tentem agora alterar o código visto para criar um xadrez na tela,
assim:

Potrebbero piacerti anche