terça-feira, 23 de outubro de 2012

Seus 3 desejos


A fábula do gênio da lâmpada é conhecida e acredito que todos saberiam o que desejar. Pelo menos o último desejo. Qualquer um que já perdeu a sua inocência da infância sabe que seu último desejo deve ser mais 3 desejos. Ou infinitos. Assim não precisamos nos preocupar com o que desejamos. Podemos sempre desejar mais e mais e sermos felizes realizando nossos desejos para sempre.

Mas será que só 3 desejos não bastam? Claro que isto traz um problema. Pensar direito, desejar direito, ponderar sobre o que realmente vale a pena e pedir que o desejo seja realizado. Deveríamos acreditar que temos apenas 3 desejos. Pensar mais sobre o que realmente queremos. Talvez apenas um desejo pudesse bastar. Talvez seja da natureza do homem não saber o que realmente quer e querer sempre desejar mais.

Pergunto se este desejo não é egoísta. Sim, egoísta. Ora, se seus desejos acabam, a lâmpada apareceria para outra pessoa e esta também teria 3 desejos, certo? Ao desejarmos o gênio para sempre impedimos outras pessoas de também realizarem seus desejos. Esta é a razão de eu achar isto egoísta.

Você deve estar se perguntando se eu endoidei ou se voltei a ler livros da minha infância. Ainda não. Comecei com esta conversa de gênio da lâmpada pensando superficial sobre desejos e mais desejos. Isto não importa. Afinal de contas, isto é só uma fábula, certo?  Onde esta conversa pode nos levar? Bem, voltemos ao mundo real: estou falando de reeleição.

Um candidato pode fazer um ótimo mandato em quatro anos. Não acredito que alguém tenha tanto a desejar e fazer por uma cidade que não consiga fazer em quatro anos. Foi este plano de governo em 4 anos que o elegeu. Se cumprisse seu plano de governo de quatro anos, quatro anos deveriam bastar. Mas não bastam. Sempre há um último desejo que nossos políticos pedem para o genial povo brasileiro: mais 4 anos.

Se ele não pode se reeleger ele coloca a esposa, o irmão, o vice, o filho, o pai, o avô. E tentam passar a lâmpada mágica para alguém muito próximo quando seus desejos acabam. Não querem que alguém de outro partido tenha desejos. Alguém de outro lugar da cidade. Acreditam ou nos fazem acreditar que a felicidade alheia pode atrapalhar a sua própria felicidade. Não querem liberar o gênio, digo, o povo e tentam guardar a lâmpada mágica do poder para si e para os seus.

Acho que você já percebeu que eu sou contra reeleição. Sou contra as capitanias hereditárias na política brasileira. Pense bem. Reeleição não é só reeleger o mesmo candidato. Inclua nisto o irmão, o pai, o neto, o tio, o vice, a esposa e quem mais puder. Todas estas pessoas poderiam ter ajudado nos quatro anos. Eles estavam lá durante o mandato, tiveram a chance de governar com o candidato eleito. Se não fizeram antes, porque farão agora? O que mais este mesmo grupo pode desejar para nossa cidade que não pôde ser feito em quatro anos?

Somos o gênio da lâmpada mas podemos ler a lista de desejos antes de escolher a quem servir. Isto chama-se proposta de campanha. A eleição é um processo democrático que nos permite escolher a melhor proposta. Uma proposta que caiba em quatro anos de mandato. Se na proposta aparecesse: Meu último desejo é mais 3 desejos, eu, como gênio, não serviria a este senhor.

Um outro candidato, nem tão preparado, nem tão conhecido, não tão bem aconselhado talvez tenha alguns desejos bons para a cidade. Talvez este desejo não afete minha vida diária. Com certeza, afetará a de alguém. É justo. É democrático. Outras pessoas deveriam ter o direito de governar. A cidade não é só de seu candidato.

Provavelmente você também desejaria mais 3 desejos se pudesse. Acho que eu faria o mesmo. Só que no caso das eleições, não é uma fábula. E aqui não é você que deseja. Você é o gênio que dá o poder para quem deseja se eleger. Como gênio, por quanto tempo você quer servir ao mesmo senhor?

segunda-feira, 1 de outubro de 2012

Passagem de parâmetros em C


Ao criamos uma função podemos passar parâmetros. Estes parâmetros podem ser passado de algumas formas mas as mais clássicas são por valor ou por referência. Para entender o que é isto teremos de dar uma olhadinha na pilha memória do computador. Começamos definindo funções que recebem inteiros. Serei um tanto simplista no meu exemplo e espero que isto não atrapalhe o entendimento.

Definiremos 3 funções: uma recebe o parâmetro por valor e retorna o resultado e outra por referência, ou seja, ponteiros, e retorna o valor e outra recebe por referência e não retorna nada.

int soma_valor(int x, int y){
   return x + y;
}

int soma_referência1(int * x, int * y){
   return *x + *y;
}

void soma_referência2(int * x, int * y){
   *x = *x + *y;
}

main(){
   int a = 3;
   int b = 5;

   soma_valor(a,b);
   soma_referência1(&a, &b);
   soma_referência2(&a, &b);
}

Bem, vamos ver o que acontece na nossa pilha quando executamos o programa main.

O Sistema operacional (SO) irá alocar espaço para a variável a e vai guardar o valor 3. Depois ele vai guardar espaço para a variável b e vai guardar o valor 5.

Ao chamarmos a função soma_valor, ele vai alocar outros espaços para outras variáveis x e y (que também poderiam chamar a e b ou qualquer outro nome. Mudei o nome só para facilitar a explicação) e vai copiar o valor de a para x e de b para y. O sistema vai alocar também uma variável sem nome para o retorno da função. Temos neste ponto alocados na memória 5 inteiros: a, b, x, y e retorno. A função executa, libera a memória das variáveis x, y e retorno e retorna o valor. Não estamos fazendo nada com o retorno dela mas ela retorna.
Custo total: alocação de 5 variáveis, calcular o resultado da soma e cópia de memória de a para x, b para y e resultado da soma para o retorno.

Na próxima função, alocamos apenas o inteiro para o retorno. A chamada da função irá receber um ponteiro, ou seja, o endereço alocado previamente (indicado pelo &) das variáveis a e b e não precisará alocar espaço para seus valores e nem copiar os valores de uma região da memória para outro. Internamente ela pega o valor que está neste endereço (indicado pelo *), soma, copia o valor da soma para o retorno e retorna. Com certeza isto é mais barato pois no final teremos alocado apenas o valor de um inteiro para o retorno.
Custo total: Alocação de 1 variável retorno, calcular o resultado da soma e cópia do resultado da soma para o retorno.

Na terceira e última função, não alocamos valor nenhum da memória. A função receberá o endereço das variáveis previamente alocadas, irá copiar o valor da soma para o endereço de memória da variável a (que internamente na função é chamado de x) e não precisa retornar. Neste caso, no término da função o valor da variável a será 8 e não mais 3. Isto evita a alocação da variável de retorno mas irá perder para sempre o valor inicial de a. Isto pode ser bom ou ruim, dependendo do objetivo do programa.
Custo total: Calcular o resultado da soma.

Agora, imagine que este programa seja o processamento de um vídeo e vc esteja somando 2 vídeos de 2 GB cada. Na primeira função, temos a alocação de 5 variáveis que somadas ocuparão 10GB da memória. No segundo exemplo ocuparemos apenas 6GB de memória e no terceiro apenas 4GB de memória, o que é o custo mínimo já que os mesmos precisarão ser carregados. Acho que dá para começar a sentir a diferença de desempenho destas funções que fazem a mesma coisa mas que fazem de forma diferente...

No primeiro exemplo temos a cópia da memória de 4GB das variáveis a e b para x e y. Quanto tempo isto leva? E se esta função for chamada 10 vezes em um segundo? Dá para fazer isto em "tempo real"? No segundo exemplo não perdemos tempo copiando os valores de entrada mas precisamos copiar o valor para a variável de retorno. Já é mais eficiente. No terceiro podemos fazer a soma diretamente no endereço de a (que internamente na função é chamado de x) e esta soma pode ser feita "in loco". Com certeza mais eficiente.

Alguns textos que tem exemplos mais bonitos que o meu:



Agradeço ao Rael que me mandou a dúvida e que rendeu este texto.

Entendendo orientação a objetos


Praticamente toda linguagem de programação estruturada possui a capacidade de definirmos estruturas de dados. Uma estrutura de dados é um tipo de dado composto definido pelo usuário. Assim, podemos definir um tipo Pessoa que possui um nome e uma idade. Sei que este exemplo não é nada útil mas vou usá-lo mesmo assim. Isto é uma estrutura de dados. Em C definimos como struct e em pascal como um record. Em C fica assim:

struct {
     char * nome;
     int idade;
} pessoa;

Ao criarmos uma estrutura de dados é como se criássemos um novo tipo de variável que pode ser usada em nosso programa. Bem, podemos então instanciar uma variável do tipo pessoa da mesma forma que instanciamos uma variável do tipo inteiro ou float. Assim:

int a;
float b;
pessoa c;

Ao usarmos esta variável é possível acessar os dados encapsulados pelo tipo. Desta forma é possível acessar o nome e a idade do tipo pessoa diretamente:

pessoa c;
c.idade = 20;
c.nome = "Antonio";

Além da possibilidade de criarmos estruturas de dados, as linguagens costumam permitir que criemos fuincionalidades. Assim é possível definirmos uma função gravar que irá gravar uma pessoa. O tipo de dado pessoa pode ser passado diretamente para a função que irá conhecer este tipo de dado da mesma forma que conhece um inteiro, float ou char:

/* Exemplo que recebe dois inteiros */
int somar(int a, int b){
     return a + b;
}

/* Exemplo que recebe uma pessoa */
int gravar(pessoa p){
     /* Grava de alguma forma em arquivo ou banco de dados
     p.nome;
     p.idade;
     */
     return 1;
}


Bem, o que a orientação a objetos traz de diferente é a possibilidade de definirmos as funcionalidades junto com a estrutura de dados. A este conjunto de estrutura de dados (que chamamos de propriedades) e funcionalidades (que chamamos de métodos) chamamos classe. Seria o mesmo que definir em uma struct algo assim:

struct {
     char * nome;
     int idade;
     int gravar_pessoa(){
     /* Grava de alguma forma */
     /* this.nome; */
     /* this.idade */
     return 1;
     }
} pessoa;

NOTE QUE ESTE EXEMPLO NÃO FUNCIONA!

Ao adicionarmos a funcionalidade a estrutura de dados, podemos acessar o método (função) da mesma forma que acessamos os atributos ou propriedades.

pessoa p;
p.nome = "Maria";
p.idade = 23;
p.gravar();


Veja que o método gravar pertence a instância da variável e por isto ele consegue acessar internamente suas propriedades. E isto é orientação a obejtos. Costumo dizer que OO é uma questão de escopo. De maneira muito simplista é fechar a chave que define a estrutura de dados depois de definirmos as funcionalidades. :-) Só que em vez de definirmos esta estrutura como uma struct (que o próprio nome indica ser APENAS estrutura de dados) definimos como uma classe.

Se Orientação a objetos fosse apenas isto, já seria muito legal. Mas OO é bem mais que isto.

Polimorfismo


Na maioria das linguagens, não podemos definir duas funções com o mesmo nome. Se tivermos um sistema que possui as estruturas de dados pessoa, usuário, aluno, livro e empréstimo teremos de ter funções com nomes diferentes para gravar pessoa, usuário, aluno, livro e empréstimo. Cada uma delas receberá um tipo de dado definido anteriormente e que a função sabe como tratar.

int gravar_pessoa(pessoa p){ /* ... */}
int gravar_usuário(usuário u){/* ... */}
int gravar_aluno(aluno a){/* ... */}
int gravar empréstimo(empréstimo e){ /* ... */}


Agora é possível termos vários métodos gravar, um para pessoa, outro para livro e outro para aluno. E isto tem nome, chama-se polimorfismo. Polimorfismo nada mais é que métodos com o mesmo nome em classes diferentes, no caso gravar, que fazem coisas diferentes, no caso gravar coisas diferentes.

Encapsulamento

Orientação a objetos também permite acessos distintos a estrutura de dados. Podemos ter dados privados, que apenas a própria classe pode acessar ou dados públicos que qualquer dado pode acessar. Isto não existe na programação estruturada e é bem útil para dados internos e de controle.

Assim, sua estrutura de dados não fica "exposta" indevidamente e você pode definir diferentes níveis de acesso para as propriedades e também para os métodos. Diria que com isto a programação em equipe fica mais organizada.

Herança

OO também permite que uma classe complemente os dados de outra. Assim, temos uma estrutura de dados chamada usuário que possui apenas login e senha e temos uma estrutura de dados chamada aluno que irá estender usuário e só por estender já ganha os atributos login e senha.

O método logar também será herdado e este método usará apenas os dados do usuário, que o aluno já herdou.


class Usuário {
   char * login;
   char * senha;
public:
   int logar() { /* ... */  }
};

class Aluno : public Usuário { /* ... */ };

 int main() {
   Aluno a;
   a.logar();
}

Conclusão

Orientação a objetos é bem legal!