Murilo :P

C++, Computação, Programação, Web e afins :)

Estou no Google Summer of Code 2010

with 4 comments

Olá pessoal.

Esse ano fui um dos (2^10 + 4) selecionados para passar alguns meses programando nesse evento.

O que é o Google Summer of Code?

O Google Summer of Code (GS0C) é um programa realizado pela Google que consiste em incentivar o e divulgar o software livre entre os estudantes. A filosofia do GSoC é “flip bits not burgers” que é a de incentivar os estudantes em seu período de férias de verão (as nossas férias são no inverno) a programarem, não trabalhar por exemplo em fast-foods como é comum principalmente nos Estados Unidos.

Neste programa, a Google paga aproximadamente 5000 dólares para cada estudante que ao final do programa tenha completado seu projeto.

Funciona assim: os estudantes escolhem uma ou mais organizações de software livre que foram aceitas pelo programa para trabalhar com elas. Aí o estudante tem que propor um projeto para essa organização que pode ser algo do tipo:

  • Implementar uma nova ferramenta
  • Corrigir bugs
  • Documentar código
  • Melhorar o desempenho de alguma ferramenta
  • Portabilizar para alguma(s) plataforma(s)
  • Adicionar features à alguma ferramenta
  • etc.

Feito isso, há uma seleção interna e logo é divulgado no site do GSoC a lista de aceitos.

Em seguida você tem uma pessoa alocada pela organização para te ajudar que chamamos de mentor.

Ao final do programa, além dos dólares a mais, o participante ganhará uma camiseta do programa e, no meu ver o mais importante, o certificado emitido pela Google atestando a sua participação.

Eu no GSoC 2010

Eu escolhi a Boost que é um conjunto de bibliotecas para C++ (com bindings para Python) amplamente utilizada no mundo inteiro. Algumas bibliotecas Boost já foram inseridas no padrão C++ através do TR1 (e algumas outras serão inseridas no próximo padrão) e por isso as bibliotecas Boost sejam talvez as mais respeitadas, digamos assim, bibliotecas para C++.

Minha proposta é implementar algoritmos rápidos para manipulação de dados binários em inteiros. Se você quiser saber como é a “cara” das coisas que terei que fazer, aqui você encontra alguns dos algoritmos que implementarei.

Meu mentor chama-se Vicente J. Botet Escribá que parece ser uma bastante pró-ativa e que saca bastante e que tem um blog com conteúdo bem interessante.

Ah, e parabéns ao Marcos Roriz meu colega de universidade que também teve seu projeto aprovado.

O abstract do meu projeto e do Marcos podem ser acessados aqui.

Até mais!

Written by Murilo Adriano

3 de May de 2010 at 18:29

Meta-Programação por Templates

with 2 comments

Ultimamente estou me aventurando por essas terras misteriosas e estou fascinado.

Segundo o Wikipedia[1] Meta-programação por templates (TMP ou Template metaprogramming) é:

Template metaprogramming is a metaprogramming technique in which templates are used by a compiler to generate temporary source code, which is merged by the compiler with the rest of the source code and then compiled. The output of these templates include compile-time constants, data structures, and complete function

Ou seja TMP é uma técnica usada para gerar código-fonte em tempo de compilação. Isso quer dizer que, no momento da compilação, as chamadas à esse tipo de código são avaliadas, o código correspondente é gerado e o resultado já é conhecido, antes mesmo do programa ser executado!

Uau!! Isso é perfeito! Resolverá todos meus problemas…

Não é bem assim, como tudo é feito em tempo de compilação, os valores de entrada para esses algoritmos também devem ser conhecidos em tempo de compilação. Isso quer dizer que os valores de entrada devem ser constantes.

Eis um exemplo de código C++ usando essa abordagem:

#include <iostream>

template<int V>
struct Int { enum { value = V }; }; // representa um valor inteiro

template<int V>
struct bin : Int<(V % 10) + 2 * (bin<V / 10>::value)> {}; // algoritmo recursivo

template<> struct bin<0> : Int<0> {}; // base da recursão

int main()
{
	std::cout << "1000b == " << bin<1000>::value << "d\n";
	std::cout << "10001b == " << bin<10001>::value << "d\n";
	std::cout << "111b == " << bin<111>::value << "d\n";
	std::cout << "10b == " << bin<10>::value << "d\n";
	
	return 0;
}

Como ainda estou aprendendo a respeito, vou finalizar esse post nesse código. Caso queiram discutir o tema em questão, sintam-se a vontade!

Links:

[1] Template Metaprogramming no Wikipedia
Outro material sobre TMP
Boost MPL – biblioteca Boost que provê frameworks para meta-programação

Written by Murilo Adriano

19 de April de 2010 at 11:41

Posted in C/C++, Programação

Tagged with ,

A importância de declarar os destrutores como virtual em C++

with 5 comments

Funções Virtuais

Depois de muito tempo sem postar algo aqui vou falar sobre um especificador do C++ que muitas vezes é esquecido: a keyword virtual.

Essa keyword é utilizada para dizer que queremos que faça a avaliação da chamada dessa função em run-time, não em compile-time como é feito por padrão.

Mas no que isso pode ajudar?

#include <iostream>

struct base
{
	void funcao()
	{
		std::cout << "chamada da base\n";
	} 
	
	virtual void vfuncao()
	{
		std::cout << "chamada da base\n";
	}
};

struct derivada: public base
{
	void funcao()
	{
		std::cout << "chamada da derivada\n";
	} 
	
	void vfuncao()
	{
		std::cout << "chamada da derivada\n";
	}
};

int main()
{
	base* pt = new derivada;
	
	//nao virtual
	pt->funcao();
	
	//virtual
	pt->vfuncao();
	
	delete pt;
	
	return 0;
}

Saída:

chamada da base
chamada da derivada

Ou seja, quando uma função-membro (aka. método) é declarada como virtual na classe base, a checagem em run-time é feita e é possível encontrar a vfuncao() na classe

derivada

, assim chamando a função correta, o que não acontece com funcao(): apesar de ter uma função-membro com mesmo nome na classe derivada, é chamada a funcao() da classe base porque o ponteiro que a referencia é do tipo da classe base. Pra isso serve a keyword virtual (êba, vamos usá-la daqui pra frente!).

Mas e os destrutores virtuais?

Segue a mesma idéia e com um agrave de risco eminente de memory leaks!
Se na classe base não temos o destrutor declarado como virtual, temos um grande problema. Vejamos o exemplo:

#include <iostream>

class base 
{
	int* ptr;
public:
	base()
	{
		ptr = new int;
	}
	
	~base() 
	{
		delete ptr;
		std::cout << "'destruindo' base\n";	
	}
};

class derivada: public base
{
	int* dptr;
public:
	derivada() {
		dptr = new int;
	}
	~derivada()
	{
		delete dptr;
		std::cout << "'destruindo' derivada\n";
	}
};

int main()
{
	base* pt = new derivada;
	delete pt;
}

Saída:

'destruindo' base

Como podemos ver, apenas o destrutor da classe base é chamado, deletando o ponteiro ptr. Mas e o dptr declarado em derivada e alocado em seu construtor? Leaked! Se perdeu!

A solução é declarar o destrutor da classe base como virtual e a mágica acontece.

#include <iostream>

class base 
{
	int* ptr;
public:
	base()
	{
		ptr = new int;
	}
	
	virtual ~base() 
	{
		delete ptr;
		std::cout << "'destruindo' base\n";	
	}
};

class derivada: public base
{
	int* dptr;
public:
	derivada() {
		dptr = new int;
	}
	~derivada()
	{
		delete dptr;
		std::cout << "'destruindo' derivada\n";
	}
};

int main()
{
	base* pt = new derivada;
	delete pt;
}

Saída:

'destruindo' derivada
'destruindo' base

Conclusão

É aconselhável que seus destrutores sejam virtuais e que pondere nas outras funções, pois tudo que deixa de ser feito em compile-time e passa a ser feito em run-time, gera overhead na sua aplicação (é… nem tudo é maravilha). Até a próxima!

Written by Murilo Adriano

12 de February de 2010 at 14:50

Posted in C/C++, Programação

Tagged with ,

Static Template Matrix – uma abordagem diferente

with 5 comments

Static Template Matrix?

C++
De fato eu nunca vi esse nome em outro lugar, mas foi o nome que eu pensei que se aproxima mais da implementação que veremos nesse post.

Trata-se template de uma matriz (matrix) de duas dimensões de tamanho constante (static). Uma das diferenças dessa matriz é que ao invés de se usar o convencional operator[] para acessar os elementos, nessa abordagem iremos utilizar o operator().

Motivações

Na realidade, não tem muito mais utilidades práticas do que matrizes normais. Pode ser que pode servir em algo para você. Então minha motivação é puramente de aprendizagem, principalmente da linguagem.

matrix.h

#ifndef _CLASS_MATRIX_H__
#define _CLASS_MATRIX_H__

/**
 * matrix.h
 * Defines a template type 'matrix' wich is a constant-sized 2-dimensioned
 * 	matrix and generic typed elements.
 *
 * author: Murilo Adriano Vasconcelos
 * website: https://murilo.wordpress.com
 */

/**
 * T = value type
 * R = number of rows
 * C = number of columns
 */
template <typename T, unsigned R, unsigned C>
class matrix
{
    //data matrix
    T data[R][C];

public:
    //type of the current matrix
    typedef matrix<T, R, C> type;
    typedef T            value_type;

    //quantity of rows
    const unsigned rows;
    //quantity of columns
    const unsigned columns;

    //default ctor
    matrix(): rows(R), columns(C) {};

    /*ctor wich initializes the matrix filled
      with 'value' elements */
    matrix(const T& value);

    //copy ctor: copies the matrix 'tocopy'
    matrix(const type& tocopy);

    /*gets and/or set the value of the element
      in the row 'i', column 'j' */
    T& operator()(unsigned i, unsigned j);

    /*gets the value of the element in the
      row 'i', column 'j' */
    const T& operator()(unsigned i, unsigned j) const;

    //copy operator
    type& operator=(const type& tocopy);
};

/* class functions definition */

template <typename T, unsigned R, unsigned C>
matrix<T, R, C>::matrix(const matrix& tocopy): rows(R), columns(C)
{
    for (unsigned i = 0; i < R; i++)
        for (unsigned j = 0; j < C; j++)
            data[i][j] = tocopy(i, j);
}

template <typename T, unsigned R, unsigned C>
matrix<T, R, C>::matrix(const T& value): rows(R), columns(C)
{
    for (unsigned i = 0; i < R; i++)
        for (unsigned j = 0; j < C; j++)
            data[i][j] = value;
}

template <typename T, unsigned R, unsigned C>
matrix<T, R, C>& matrix<T, R, C>::operator=(const matrix& tocopy)
{
    for (unsigned i = 0; i < R; i++)
        for (unsigned j = 0; j < C; j++)
            data[i][j] = tocopy(i, j);

    return *this;
}

template <typename T, unsigned R, unsigned C>
T& matrix<T, R, C>::operator()(unsigned i, unsigned j)
{
    return data[i][j];
}

template <typename T, unsigned R, unsigned C>
const T& matrix<T, R, C>::operator()(unsigned i, unsigned j) const
{
    return data[i][j];
}

#endif

main.cpp

#include "matrix.h" //our matrix
#include <iostream>  //cout'ing
#include <string>   //for strings

int main()
{
        //declares a 3x3 matrix filled with "nil"
        matrix<std::string, 5, 5> mat("nil");

        //modifies the value in the position (1, 1) to C++
        mat(1, 1) = "C++";

        //we can use the const atribbutes rows and columns
        //to check our limits
        for (unsigned i = 0; i < mat.rows; i++) {
                for (unsigned j = 0; j < mat.columns; j++) {
                        //getting, setting the value of matrix elements
                        //is the same way
                        std::cout << mat(i, j) << ' ';
                }

                std::cout << '\n';
        }

        std::cout << '\n';

        matrix<int, 3, 3> ones(1), identity;
        //copy
        identity = ones;

        //so silly
        for (unsigned i = 0; i < identity.rows; i++) {
                for (unsigned j = i; j < identity.columns; j++) {
                        if (i != j) {
                                identity(i, j) = identity(j, i) = 0;
                        }
                }
        }

        for (unsigned i = 0; i < identity.rows; i++) {
                for (unsigned j = 0; j < identity.columns; j++) {
                        std::cout << identity(i, j) << ' ';
                }

                std::cout << '\n';
        }

        return 0;
}

Como vocês puderam notar, a forma de acesso e modificação (get, set) dos elementos da matriz é da mesma forma. Outra coisa que podemos notar é que passamos o tipo e a quantidade de linhas e colunas direto no template. A quantidade de linha e colunas passada deve ser uma constante.

Bom, até mais.

obs.: Eu odeio profundamente o source highlight bugado do WordPress que converte todos os meus < e > e etc. para &lt; &gt;. Por isso os fontes acima não estão com hl.

Fixed!

Agora sim, até mais.

Written by Murilo Adriano

12 de October de 2009 at 21:08

Regional da Maratona de Programação 2009

with 2 comments

Considerações

Bem, eu ainda não tinha escrito nada sobre a fase regional da XIV Maratona de Programação que ocorreu dia 19 de setembro por falta de tempo e porque não havia recebido as fotos do evento.

O primeiro ponto que quero destacar é a estrutura da regional aqui de Goiânia. Não deixou nada a desejar! Ambiente ótimo, infra-estrutura boa, máquinas, espaço, equipamento, organização. Tenho que parabenizar os professores que organizaram o evento, principalmente os professores Humberto Longo e Cláudio Nogueira do INF-UFG.

Esse ano (2º da regional de Goiânia) participaram 11 equipes de 5 instituições: ALFA (1), IFG-Morrinhos (2), UEG (2), PUC-GO (3) e INF-UFG (3).

Local da competição

Local da competição

Antes…

Na chegada das equipes, pude reencontrar vários amigos, principalmente da PUC-GO e aí os assuntos ficaram em dia (conversamos sobre computação, lógico). O clima estava típico de uma decisão mesmo: em um segunto estávamos conversando e rindo, no outro estávamos tensos e pensativos.

Galera no corredor

Galera no corredor

Antes de começar o warm-up, tivemos uma “mini-palestra” sobre as regras da maratona e de como usar o sistema BOCA e entregas de credenciais, logins/senhas e das plaquinhas que identificam as equipes.

Antes do warm-up

Pré-warm-up

O warm-up foi tranquilo, só pra testes mesmo. Parece que houve um problema no julgamento do problema “Bolhas e Baldes” que posteriormente foi solucionado.

Fim de warm-up, fomos almoçar. Como o campus é uma cidade a parte, quase todo mundo almoçou no mesmo lugar, tendo calculado o menor caminho ou não :).

Começa a maratona e aí vira tensão…

Começa a competição e com 6 minutos, minha equipe (UFG – Monkeys) recebe o primeiro AC da competição: problema B – “Alarme”, Carlitos na implementação. Não sei dizer mas acho que esse foi um dos balões mais rápidos do Brasil.

Não demorou e nossa principal rival, a PUC-GO – Mother Focas, e a UFG – Sobrevientes do RU conseguiram também seus balões amarelos.

Os Focas

Os Focas: Jackie, Divinera e Marciano

Até os 3 primeiros balões nós lideramos com uma certa vantagem no tempo. O problema foi quando os Focas conseguiram o quarto balão e nós não. Aí bateu aquele “caramba, agora fodeo!”. Aí focamos no problema (que por sinal era muito simples) e mandamos… Recebemos um NO bem grande e aí bateu o desespero. Consegui achar a lógica, conferi com meus colegas a corretude e, finalmente YES! Na hora olhamos para o Score que estava num telão na nossa frente e vimos que tinhamos passado os Focas (na verdade olhei o score mesmo assim deu um branco não saquei nada e tive que perguntar a nossa posição pra minha equipe [sic]).

Alguns balões

Alguns balões

Ficamos focados no problema G – “Registrador” e perdemos tempo, muito tempo [sic²]. Quando vimos, tinham 6 equipes com 4 balões, aí de novo bateu o pensamento “fodeo”…

No freeze, conseguimos formular um algoritmo pro E – “Dragster” mas aí já era tarde… Estávamos exaustos e não conseguimos codificá-lo. Enquanto isso, eu estava tentando ter a sacada do F – “Torres de Celular” (pô, tava na cara e eu não enxerguei). E o freeze-time foi isso: Diego e Carlos no Dragster (esse dava hein) e eu tentando algo no Torres de Celular.

Nosso quarto balão

Nosso quarto balão. Monkeys: Murilo, Carlos e Diego

A cada vez que nós víamos alguem teclando algo no freeze, dava um frio na barriga. Tentamos fazer uns “cortes” no registrador e mandamos várias outras vezes.

Score Final

Por um problema na rede (ou de juízes engraçadinhos), o score final demorou uma eternidade após a competição a ser apresentado a nós (aproximadamente 4min e 30seg). Anunciado o vencedor, só alegria.

Comemoração

Comemoração

Mais algumas fotos

PUC-GO Bit a bit

PUC-GO Bit a bit: Douttor, Mauricinho e Loos

UFG - Sons of a Bit

UFG - Sons of a Bit: Nemo, Will e PC

UFG - Sobreviventes do RU

UFG - Sobreviventes do RU: Everton, Will e Lup

dsc08344

A única competidora

A única competidora, o japa e o barba

Os melança

Os melança

Premiação

É… teve premiação com direito a reitor e tudo mais. Auditório cheio e prêmios para os vencedores 🙂

As três primeiras equipes ganharam camisetas geeks, medalhas e trófeus além de um livro (alguns dos prêmios concedidos pela SistemasAbertos).

Nós da Monkeys ganhamos uma camiseta personalizada da Maratona de Programação, como essa abaixo do Longo branca.

3º Lugar - UFG - Sons of a Bit

3º Lugar - UFG - Sons of a Bit e o coach Humberto Longo

2º Lugar - PUC-GO - MotherFocas

2º Lugar - PUC-GO - MotherFocas, pró-reitora de graduação e o coach Alexandre

1º Lugar - UFG - Monkeys

1º Lugar - UFG - Monkeys, coach: Humberto Longo e o reitor

MONKEYS

MONKEYS

E agora…

Final Nacional em Campinas

Final Nacional em Campinas

Rumo a Tóquio, ops, Harbin na China!! 🙂

Ballon Face

O scoreboard, clarifications e etc podem ser visualizados aqui.

Written by Murilo Adriano

9 de October de 2009 at 22:31

Trabalho Em Equipe

with one comment

Pessoal não ando postando algo aqui porque estou muito sem tempo mesmo. Meu time (UFG – Monkeys) passou para a final nacional da maratona de programação, as matérias do período estão bastante árduas e comecei a estagiar na universidade por isso a falta de tempo.

Encerro esse post com uma imagem que achei na grande rede sobre trabalho em equipe:
Trabalho em Equipe

Esse é o caminho 🙂

Written by Murilo Adriano

9 de October de 2009 at 13:58

Posted in Cotidiano

Tagged with ,

C++: Uma introdução a Threads com boost::thread

with 10 comments

boostPara quem ainda não conhece, Boost é um conjunto de bibliotecas C++ multiplataforma que trabalha bem com a STL e é muito bem aceita na comunidade. Já existem dez bibliotecas Boost incluídas no C++ TR1 e que estarão no C++0x além de muitas outras que estarão no C++0x e que atualmente não estão no TR1.

Para instalar o boost siga esse Getting Started.

Fazer threads com boost::thread é muito simples. Basta criar uma função ou um functor e passar pro construtor da classe boost::thread:

class functor
{
public: void operator()() {};
};

functor::operator()()
{
	/* content */
}

void function()
{
	/* content */
}

functor obj;
boost::thread tObj(obj); //passa uma cópia de obj
boost::thread tObjRef(boost::ref(obj)); //passa uma referência para obj
boost::thread tFunc(&funcao);

// aguarda a finalização das threads
tObj.join();
tObjRef.join();
tFunc.join();

//ou pode-se criar grupos de threads

boost::thread_group my_threads;
my_threads.create_thread(obj);
my_threads.create_thread(boost::ref(obj));
my_threads.create_thread(&function);

my_threads.join_all(); // aguarda o grupo de threads retornar

Para ilustrar como funciona, fiz dois programinhas simples:

cin.cpp

#include <iostream>
#include <boost/thread/thread.hpp>
#include <boost/thread/xtime.hpp>

//determina se o usuário já entrou com o número
bool flag = false;

struct in
{
	int n;

	in() : n(0) {};

	void operator()()
	{
		//try to read the number
		while (!(std::cin >> n)) {
			std::cin.clear();
			std::cin.ignore(10000, '\n');

			std::cout << "Enter a valid number" << std::endl;
		}
		
		flag = true;
	}

};

void my_alarm()
{
	while (!flag) {
		//shows the message
		std::cout << "Enter a number" << std::endl;
		
		boost::xtime time;
		boost::xtime_get(&time, boost::TIME_UTC); //current time

		time.sec += 5; //adds 5 secs to time
		//sleep 5 secs
		boost::thread::sleep(time);
	}

	//exiting function
	std::cout << "Alarm finished" << std::endl;
}

int main()
{
	in test;
	
	//create the two threads
	boost::thread tin(boost::ref(test));
	boost::thread tal(&my_alarm);

	// wait
	tin.join();
	tal.join();

	//show the user's number
	std::cout << "Ok " << test.n << std::endl;
	
	return 0;
}

Esse programa é composto por duas threads (fora o fluxo principal) uma que é responsável por fazer a leitura de um número e a outra que fica a cada 5 segundos mostrando uma mensagem “Enter a number” enquanto o usuário não digitar um número válido. Quando o usuário digita um número válido, a flag é setada e os loops das duas threads são finalizados. Finalmente quando as duas threads são finalizadas, o fluxo normal de main() é reestabelecido e é mostrado o número digitado pelo usuário.

Lembrando que para usar boost::thread é necessário “linkar” a biblioteca ao programa. No caso do Linux é necessário linkar também a biblietca pthread em Windows “linkar” a win32api. No meu caso preferi usar linkagem estática.

murilo@blacksheep:~/programacao/boost$ g++ cin.cpp libboost_thread-gcc43-mt-1_39.a -o cin -lpthread
murilo@blacksheep:~/programacao/boost$ ./cin
Enter a number
Enter a number
naoehumnumero
Enter a valid number
Enter a number
4
Alarm finished
Ok 4

O outro exempo que fiz é um programinha que faz a multiplicação de duas matrizes e “simultaneamente” (depende de processador e SO) mostra uma barra de progresso com o progresso da operação.

multiplica.cpp

#include <iostream>
#include <boost/thread/thread.hpp>
#include <boost/thread/xtime.hpp>

const int MAX = 1000;

int A[MAX][MAX];
int B[MAX][MAX];
int C[MAX][MAX];

int porcentagem = 0;

void multiplica()
{
	int soma;	
	
	for (int i = 0; i < MAX; i++) {
		for (int j = 0; j < MAX; j++) {
			soma = 0;
			for (int k = 0; k < MAX; k++) {
				soma += A[i][k] * B[k][j];
			}
			
			C[i][j] = soma;			
		}
		if (i % 10 == 0) porcentagem++;
	}
}

void barra_de_progresso()
{
	int atual = 0;
	while (porcentagem != 100) {
		if (atual != porcentagem) {
			if (porcentagem % 10 == 0) {
				std::cout << "\b\b" << porcentagem << '%';
			}
			else std::cout << '=' << std::flush;
			
			atual++;
		}
	}
}

int main()
{
	boost::thread tMultiplica(&multiplica);
	boost::thread tProgresso(&barra_de_progresso);
	
	boost::xtime inicio, fim;
	boost::xtime_get(&inicio, boost::TIME_UTC);
	
	std::cout << "Iniciando a multiplicação de matrizes" << std::endl;
	
	tMultiplica.join();
	tProgresso.join();
	
	boost::xtime_get(&fim, boost::TIME_UTC);
	
	std::cout << "\n\nProcesso terminado em " << 
		(fim.sec - inicio.sec) << " segundos" << std::endl;
	
	return 0;	
}

Saída:

murilo@blacksheep:~/programacao/boost/thr$ g++ multiplica.cpp libboost_thread-gcc43-mt-1_39.a -lpthread -o multiplica
murilo@blacksheep:~/programacao/boost/thr$ ./multiplica
Iniciando a multiplicação de matrizes
=======10%=======20%=======30%=======40%=======50%=======60%=======70%=======80%=======90%=======100%

Processo terminado em 43 segundos

Claro que o que menos nos importa nesse momento é o valor das matrizes e a multiplicação em si, por isso nem me dei ao trabalho de inicializar as matrizes nem de fazer nada com a matriz resultante.

Acredito que para uma introdução está bom e deu pra mostrar como é fazer programas com boost::thread com esses simples exemplos. Mais exemplos você pode encontrar na pasta libs do boost.

Links:

Written by Murilo Adriano

6 de August de 2009 at 18:06

Posted in C/C++, Programação

Tagged with , , ,