Documenti di Didattica
Documenti di Professioni
Documenti di Cultura
• Arquitetura de computadores
• Linguagens de programação
• Hello World!
• Cocoa - Objective-C
• Exercícios
Tópicos 2
• Variáveis e tipos de dados
• Debugging
• Vetores e C Strings
Tópicos 3
• Alocação dinâmica de memória
• Estruturas
• Funções
• 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
• Bancos de dados
Tópicos 6
• Objective-C
• iPhone
Objetivos do curso
PC (Program Counter)
Personal Computer
‣ 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
‣ 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
๏ 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
#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);
}
Executa até o
próximo breakpoint
Valor das
variáveis
Próxima instrução a
ser executada
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
2
Base 16
4 processamento Resultado
Exponente
2
Base 16
4 processamento Resultado
Exponente
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:
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
File main.c:
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::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:
@end
Objective-C - classes - .
#import "UmaClasse.h"
@implementation UmaClasse
@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:
@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