Entradas e saídas de dados – streaming – C++/Qt – Exemplo 03

Este exemplo tenta abrir o ficheiro “texto.txt“.

Caso tenha sucesso, substitui todas as ocorrências da palavra “ai” pela palavra “ui“.

O texto alterado é gravado no ficheiro “novotexto.txt“.

#include <QTextStream>
#include <QFile>

int main(){
    QString palavra = "ai";                 //palavra a procurar
    QString novapalavra = "ui";             //palavra a substituir
    QFile original;                         //ficheiro original para leitura
    QFile copia;                            //ficheiro destino para escrita
    QString line, new_line;                 //linha retirada do original + linha para escrita no ficheiro alterado

    original.setFileName("texto.txt");
    copia.setFileName("novotexto.txt");
    if (original.open(QFile::ReadOnly)) {
        copia.open(QFile::WriteOnly);
        QTextStream in(&original);
        QTextStream out(&copia);
        out.setCodec("UTF-8");
        in.setCodec("UTF-8");
        do {
            line = in.readLine();
            new_line = line;
            new_line.replace(palavra, novapalavra);
            out << new_line << endl;
        } while (!line.isNull());
        original.close();
        copia.close();
    }else{
        QTextStream monitor(stdout);
        monitor << "Erro ao tentar abrir " << "texto.txt" << endl;
    }
}

artigoprincipalseguinte

Entradas e saídas de dados – streaming – C++/Qt – Exemplo 02

Agora vamos desviar o nosso “stream” para o ficheiro “texto.txt”.

Desta forma o texto não será exibido no ecrã mas armazenado no ficheiro.

#include <QTextStream>
#include <QFile>

int main(){
    QFile nome_do_ficheiro("texto.txt");

    if (nome_do_ficheiro.open(QFile::WriteOnly)) {
        QTextStream ficheiro_de_texto(&nome_do_ficheiro);
        ficheiro_de_texto << "Hoje está um lindo dia!";
    }
}

Note-se que na linha 7 poderíamos ter utilizado QIODevice em vez de QFile, uma vez que a seguinda herdou a primeira e o operador ‘::’ possibilita o acesso aos membros da classe base.

artigoprincipalseguinte

Entradas e saídas de dados – streaming – C++/Qt – Exemplo 01

O típico “Hello World!” escrito em Linguagem C++ poderia ser algo do tipo:

#include <iostream>
int main(){
	std::cout << "Hello World!\n";
}

Se pretendermos utilizar exclusivamente as classes disponibilizadas pelo Qt teremos:

#include <QTextStream>

int main(){
    QTextStream monitor(stdout);
    monitor << "Hello stdout!\n";
}

artigoprincipalseguinte

Entradas e saídas de dados – streaming – C++/Qt

As classes QDataStream e QTextStream permitem manipular sequêcias de bits através de dispositivos representados por um objecto da classe QIODevice.

Quanto ao tipo de dados que permitem manipular temos:

  • QDataStream: Para manipular dados no formato binário;
  • QTextStream: para manipular dados no formato de texto.

Estas duas classes apresentam algumas vantagens relativamente às suportadas pela linguagem C++, entre as quais destaco  a facilidade de utilização, bem como a preocupação pela universalidade da utilização do código, independentemente da plataforma utilizada, país ou idioma, libertando o programador destas preocupações.

Uma vez que as entradas e saídas de dados estão associadas a determinados dispositivos, o Qt também disponibiliza um conjunto de classes para o tratamento de entradas e saídas que as aplicações podem utilizar.

A classe QIODevice é uma classe abstracta onde se centra todo este processo de leitura e escrita em dispositivos capazes de lerem e escreverem blocos de bytes.

As principais subclasses são:

Nota: ATENÇÃO ÀS VERSÕES

Neste artigo pretendo indexar um conjunto de exemplos que vou recolher e construir sobre cada uma destas classes.

Exemplo 01: Escrever um texto no ecrã (stdout).

Exemplo 02: Escreve um texto num ficheiro.

Exemplo 03: Substitui todas as ocorrências de uma palavra no ficheiro texto.txt por outra palavra dada. O resultado é guardano um novo ficheiro.

Exemplo 04: Implementação gráfica do Exemplo 03.

Exemplo 05: Upgrade do Exemplo 04 para utilização da classe QFileDialog de forma a permitir seleccionar a directoria de trabalho.

Exemplo 06: Permite abrir ficheiro de texto ( txt ou ficheiro de programação *.h, *.cpp …), procurar uma palavra e substituíla por outra, podendo no final o resultado ser guardado no disco.

Exemplo 07: Um exemplo sobre QDataStream. Em vez de uma coisa chata fiz uma aplicação que permite controlar um semáforo que está a ser executado nos vários postos da rede. O protocolo UDP é utilizado para “fugir” às aplicações do tipo cliente/servidor.

Polimorfismo

polimorfismo3

Pressupostos

Para compreender o texto que preparei são necessário conhecimentos elementares sobre : classes, estruturas de dados e herança.

Apontadores para a classe base

Um dos aspectos chave da derivação de classes resulta do facto de um apontador para uma classe derivada ser de tipo compatível com o apontador da sua classe base. O polimorfismo é a arte de tirar proveito desta característica, que sendo simples é bastante poderosa, o que conduz as metodologias Orientadas a Objectos ao máximo do seu potencial.
Vamos começar por reescrever o nosso programa acerca do triâgulo e do rectângulo utilizado na secção anterior mas tendo em consideração esta propriedade de compatibilidade entre apontadores:

// pointers to base class
#include <iostream>
using namespace std;

class CPolygon {
public:
	void set_values (int a, int b){
		width=a;
		height=b;
	}
protected:
	int width, height;
};

class CRectangle: public CPolygon{
public:
	int area (){
		return (width * height);
	}
};

class CTriangle: public CPolygon{
public:
	int area (){
		return (width * height / 2);
	}
};

int main (){
	CRectangle rect;
	CTriangle trgl;
	CPolygon * ppoly1 = &rect;
	CPolygon * ppoly2 = &trgl;
	ppoly1->set_values (4,5);
	ppoly2->set_values (4,5);
	cout << rect.area() << endl;
	cout << trgl.area() << endl;
	return 0;
}

Na função main, criamos dois apontadores que apontam para objectos da classe CPoligon (ppoly1 e ppoly2). De seguida atribuímos as referências a rect e trgl para estes apontadores, e como ambos são objectos da classe derivada de CPolygon, ambas são operações de atribuição válidas.
A única limitação de utilizar ppoly1 e ppoly2 em vez de rect e trgl é que ambos ppoly1 e ppoly2 são do tipo CPolygon* e, portanto, apenas podemos utilizar estes apontadores para nos referirmos aos membros que as classes CRectangle e CTriangle herdam de CPoligon. É por esta razão que quando chamamos os membros area() no final do programa tivemos que utilizar directamente os objectos rect e trgl em vez dos apontadores *ppoly1 e *ppoly2.
De forma a podermos utilizar a função area() com os apontadores para a classe CPoligon, este membro também deveria ter sido declarado na classe CPolygon, e não exclusivamente na sua classe derivada. O problema é que CRectangle e CTriangle implementam diferentes versões da função area(), por isso não podemos implementa-las na classe base. É aqui que os membros virtuais vão tornar-se úteis.

Membros virtuais
Um membro de uma classe que pode ser redefinido na sua classe derivada é designado de membro virtual. Para declararmos um membro de uma classe como virtual devemos preceder a sua declaração com a palavra virtual:

// virtual members
#include <iostream>
using namespace std;

class CPolygon{
public:
	void set_values (int a, int b){
		width=a;
		height=b;
	}
	virtual int area (){
		return (0);
	}
protected:
	int width, height;
};

class CRectangle: public CPolygon {
public:
	int area (){
		return (width * height);
	}
};

class CTriangle: public CPolygon {
public:
	int area (){
		return (width * height / 2);
	}
};

int main () {
	CRectangle rect;
	CTriangle trgl;
	CPolygon poly;
	CPolygon * ppoly1 = &rect;
	CPolygon * ppoly2 = &trgl;
	CPolygon * ppoly3 = &poly;
	ppoly1->set_values (4,5);
	ppoly2->set_values (4,5);
	ppoly3->set_values (4,5);
	cout << ppoly1->area() << endl;
	cout << ppoly2->area() << endl;
	cout << ppoly3->area() << endl;
	return 0;
}

Agora as três classes (CPolygon, CRectangle e CTriangle) possuem todas os mesmos membros: width, height, set_values() e area().
A função membro area() foi declarada como virtual na classe-base pois será redefinida mais tarde na classe derivada. Isto pode ser verificado removendo a palavra reservada virtual da declaração de area() na classe CPoligon, e de seguida executando o programa e verificando que o resultado é 0 para os três polígonos, em vez de 20, 10 e 0. Isto acontece porque em vez de  chamarmos a função area() correspondente para cada objecto (CRectangle::area(), CTriangle::area() e CPolygon::area() respectivamente), CPolygon::area() será chamado em todos os casos,  uma vez que as invocações são feitas através dos apontadores do tipo CPolygon *.
Por isso o que a palavra reservada virtual faz, é permitir que um membro de uma classe derivada com o mesmo nome de uma pertencente à classe-base seja invocada de forma apropriada a partir do apontador, e mais precisamente quando o tipo do apontador é um apontador para a classe base mas que aponta para um objecto da classe derivada, tal como mostrado no exemplo acima.
Uma classe onde é declarada ou herdada uma função virtual é designada de classe polimórfica.
Note que apesar da sua natureza virtual, também nos foi possível declarar um objecto do tipo CPoligon e invocar a sua função area(), que devolve sempre 0.

Classes base abstractas

As classe base abstractas são muito semelhantes à nossa classe CPoligon do exemplo anterior. A única diferença reside no facto de  no exemplo anterior termos definido uma função area() válida, com o mínimo de funcionalidades para os objectos que eram da classe CPoligon ( como o objecto poly), enquanto que numa classe base abstracta podíamos deixar essa função membro area() sem qualquer implementação. Isto é feito adicionando =0 (igual a zero) à declaração da função.
Uma classe base abstracta CPolygon poderia ter este aspecto:

// abstract class CPolygon
class CPolygon {
public:
	void set_values (int a, int b) {
		width=a; height=b;
	}
    virtual int area () =0;
protected:
	int width, height;
};

Note como adicionamos =0 a virtual int area () em vez de especificar uma implementação para a função. Este tipo de função é designado de função virtual pura, e todas as classes que contenham pelo menos uma função deste tipo são consideradas como classe base abstracta.
A principal diferença entre classes base abstractas e classes polomórficas comuns resulta do facto de nas classes base abstractas não podermos criar instâncias (objectos) a partir delas, pois pelo menos uma das suas funções membro não possui implementação.
De qualquer das formas uma classe a partir da qual não é possível instanciar objectos não é totalmente inútil. Podemos criar apontadores para ela e tirar vantagens das suas potencialidades polimórficas. Por esta razão uma declaração como:
CPolygon poly;
Não seria válida para a classe base abstracta que acabamos de criar, porque tenta instanciar um objecto. No entanto, os seguintes apontadores:
CPolygon * ppoly1;
CPolygon * ppoly2;
Seriam perfeitamente válidos.
Isto só acontece enquanto CPolygon contiver uma função virtual pura e no entanto trata-se de uma classe base abstracta. De qualquer das formas, os apontadores para esta classe base abstracta podem apontar para objectos na classe derivada.
Segue-se um exemplo completo:

// abstract base class
#include <iostream>
using namespace std;

class CPolygon {
public:
	void set_values (int a, int b){
		width=a;
		height=b;
	}
    virtual int area (void) =0;
protected:
	int width, height;
};

class CRectangle: public CPolygon {
public:
	int area (void) {
		return (width * height);
	}
};

class CTriangle: public CPolygon {
public:
	int area (void) {
		return (width * height / 2);
	}
};

int main () {
	CRectangle rect;
	CTriangle trgl;
	CPolygon * ppoly1 = &rect;
	CPolygon * ppoly2 = &trgl;
	ppoly1->set_values (4,5);
	ppoly2->set_values (4,5);
	cout << ppoly1->area() << endl;
	cout << ppoly2->area() << endl;
	return 0;
}

Se analisarmos o programa atentamente iremos certamente notar que é feita referência a objectos diferentes mas de classes relacionadas, recorrendo apenas a um único tipo de apontador( CPoligon*). Isto pode extraordinariamente útil. Por exemplo, agora podemos criar uma função membro na classe base abstracta CPoligon que escreva no ecrã o resultado da função area() mesmo apesar da própria CPoligon não possuir uma implementação para esta função:

// pure virtual members can be called
// from the abstract base class
#include <iostream>
using namespace std;

class CPolygon {
public:
	void set_values (int a, int b) {
		width=a;
		height=b;
	}
    virtual int area (void) =0;
    void printarea (void) {
		cout << this->area() << endl; }
protected:
	int width, height;
};

class CRectangle: public CPolygon {
public:
	int area (void) {
		return (width * height);
	}
};

class CTriangle: public CPolygon {
public:
	int area (void) {
		return (width * height / 2);
	}
};

int main () {
	CRectangle rect;
	CTriangle trgl;
	CPolygon * ppoly1 = &rect;
	CPolygon * ppoly2 = &trgl;
	ppoly1->set_values (4,5);
	ppoly2->set_values (4,5);
	ppoly1->printarea();
	ppoly2->printarea();
	return 0;
}

Os membros virtuais e as classes abstractas concedem à linguagem C++ as características polimórficas que tornam a programação orientada a objectos um instrumento de enorme utilidade em projectos de grandes dimensões. Claro que os exemplos apresentados são aplicações muito simples destas características, que têm aplicação em arrays de objectos ou objectos alocados dinamicamente.
Vamos terminar com o mesmo exemplo, mas desta vez com alocação dinâmica de memória:

// dynamic allocation and polymorphism
#include <iostream>
using namespace std;

class CPolygon {
public:
	void set_values (int a, int b) {
		width=a;
		height=b;
	}
    virtual int area (void) =0;
    void printarea (void) {
		cout << this->area() << endl;
	}
protected:
	int width, height;
};

class CRectangle: public CPolygon {
public:
	int area (void) {
		return (width * height);
	}
};

class CTriangle: public CPolygon {
public:
	int area (void) {
		return (width * height / 2);
	}
};

int main () {
	CPolygon * ppoly1 = new CRectangle;
	CPolygon * ppoly2 = new CTriangle;
	ppoly1->set_values (4,5);
	ppoly2->set_values (4,5);
	ppoly1->printarea();
	ppoly2->printarea();
	delete ppoly1;
	delete ppoly2;
	return 0;
}

Notar que os apontadores ppoly1 e ppoly2:
CPolygon * ppoly1 = new CRectangle;
CPolygon * ppoly2 = new CTriangle;
são declarados como sendo do tipo apontadore para Cpolygon mas os objectos alocados dinamicamente foram declarados directamente como sendo do tipo da classe derivada.
Nota final:
Este artigo resultou da necessidade de utilizar um texto que resumisse o essencial sobre esta questão do polimorfismo, e que estivesse escrito em Português.
Depois de alguma pesquisa seleccionei um artigo de Juan Soulie que encontrei em  http://www.cplusplus.com/doc/tutorial/polymorphism.html .
Não sou nenhum especialista em tradução mas julgo que dá para entender a mensagem, e este artigo está de facto muito interessante.
A imagem inicial foi inspirada nouto pequeno artigo ( http://www.vivaolinux.com.br/artigo/Polimorfismo-Uma-visao-em-C++-e-Java ) que também considero muito engraçada para introduzir esta questão do polimorfismo. Achei importante alterar as setas devido às regras de representação de Diagramas de Classes e aproveitei para mudar a bicharada para algo mais Lusitano.
O meu muito obrigado a todos os que partilharam o conhecimento que eu utilizei neste trabalho.

Debian 5.0 “Lenny”

debian5

Tratando-se de um blog que foi inspirado neste Sistema Operativo, não podia deixar de assinalar a data do lançamento da nova versão estável (2009/02/14).

“The King is dead. Long live the King!

Publicado em debian. Leave a Comment »

Utilização do operador de resolução de “escopo” ( :: ) nas classes em C++

O :: (scope resolution operator) possibilita o acesso a nomes ocultos que de outra forma seriam inacessíveis.

Nos casos em que é utilizada herança privada ou protegida, é possível tornar acessíveis os membros da classe-base a partir da respectiva classe derivada, bastando para o efeito utilizar uma declaração de acesso.

O exemplo seguinte ilustra esta situação.

#include <iostream>
using namespace std;

class Base{
public:
	int x;
protected:
	int y;
private:
	int z;
};

class Derivada1:private Base{
public:
	Derivada1(){
		w = 33;
	}
	Base::x;
	Base::y;
	void f1(){
		x = 0;
		y = 0;
	}
protected:
	void f2(){
		w = 2 * x;
	}
private:
	int w;
};

int main(){
	Derivada1 B;
	B.x = 3;	//só é possível em consequência da linha 18
	B.y = 4;	//só é possível em consequência da linha 19
}

Observações:

  • o tipo de herança utilizado é “private”, pelo que seria de esperar que o utilizador da classe derivada não tivesse acesso à classe base;

tiposdeheranca04

  • no entando, tal como se pode constatar no exemplo, é possível aceder aos membros x e y da classe-base (ver linhas 34 e 35);
  • tal só é possível utilizando as declarações de acesso (ver linhas 18 2 19).

Para visualizar as mensagens de erro recomendo comentar as linhas 18 e 19 e depois tentar compilar o programa.

Nota: Tenho imensas dificuldades em traduzir algumas palavras para Português. Não é pelo facto da tradução directa não ser possível, mas por vezes perde-se algo nessa transposição. É o caso da palavra “scope” :)

Lançamento do Debian “Lenny” 5.0

diad

Está previsto para o “Dia dos Namorados”!

Ver aqui: link

Publicado em debian. Leave a Comment »

Tipos de herança

Uma classe pode conter até três secções:

tiposdeheranca01

  • Pública (public)
  • Protegida (protected)
  • Privada (private)

Do ponto de vista do utilizador da classe, que podemos considerar a título de exemplo a função main( ), apenas os membros da secções pública lhe são acessíveis. É por esta razão que a secção pública também é designada de “interface da classe“.

Os membros privados e protegidos só podem ser acedidos pela própria classe.

Os membros protegidos poderão ser acedidos pelas classes derivadas, dependendo do tipo de herança estabelecido entre estas.

Quando abordei aqui este assunto pela primeira vez ignorei propositadamente a secção protegida, e o tipo de esquema utilizado na altura foi o seguinte.

class2

É como se agora este núcleo fosse separado em duas secções distintas. A privada e a protegida.

Um exemplo …

#include <iostream>
using namespace std;

class Base{
public:
	int x;
protected:
	int y;
private:
	int z;
};

int main(){
	Base A;
	A.x = 1;	//Correcto
	//A.y = 2;	//Erro - y é membro protegido
	//A.z = 3;	//Erro - x é membro privado
}

Note que as lihnas 16 e 17, se descomentadas, produziriam os seguintes erros:

main.cpp: In function ‘int main()’:
main.cpp:8: error: ‘int Base::y’ is protected
main.cpp:16: error: within this context
main.cpp:10: error: ‘int Base::z’ is private
main.cpp:17: error: within this context

Assim sendo, vamos agora enumerar os tipos de herança que podemos definir entre classes.

Herança pública

tiposdeheranca021

Um exemplo fictício …

#include <iostream>
using namespace std;

class Base{
public:
	int x;
protected:
	int y;
private:
	int z;
};

class Derivada1:public Base{
public:
	Derivada1(){
		w = 33;
	}
	void f1(){
		x = 0;	// A classe Derivada tem acesso aos membros públicos da Base
		y = 0;	// A classe Derivada tem acesso aos membros protegidos da Base
		//z = 0;// A classe Derivada não tem acesso aos membros privados da Base
	}
protected:
	void f2(){
		w = 2 * x;
	}
private:
	int w;
};

int main(){
	Derivada1 B;
	//Testar o acesso à classe derivada
	B.f1();		//Correcto
	//B.f2();	//Erro - f1() é membro protegido
	//B.w = 5;	//Erro - f1() é membro privado

	//Testar o acesso aos membros da classe base
	B.x = 7;	//Correcto
	//B.y = 6;	//Erro - y é membro protegido da classe base
	//B.z = 5;	//Erro - z é membro privado da classe base
}
  • Este é o tipo de herança utilizado na maioria das vezes.
  • Tal como representado na imagem, o utilizador continua a ter acesso às interfaces das duas classes, ou seja, aos membros das suas secções públicas (ver linhas 34 e 39). Todos os membros públicos da classe Base  são considerados membros públicos da classe Derivada.
  • Cada classe continua a ter acesso às suas secções pública, protegida e privada, como aliás seria de esperar.
  • A classe derivada ganha acesso à secção protegida da classe base (ver linha 20). Herda os seus membros protegidos e mantém-nos como tal.
  • Os membros públicos são herdados como públicos.
  • Os membros protegidos são herdados como protegidos.

Recomendo compilar o exemplo descomentando as linhas comentadas. As mensagens de erro dizem tudo!

Herança protegida

tiposdeheranca03

Se utilizarmos herança protegida teremos:

  • Os membros públicos e protegidos passam a ser membros protegidos da classe derivada, ou seja, o utilizador deixa de ter acesso à interface da classe base.
  • No exemplo anterior, alterando o tipo de herança na linha 13 de public para protected, passamos a obter um erro.
  • Para todos os efeitos o membros públicos e protegidos da classe basse passam agora a ser considerados membros protegidos da classe derivada, e portanto acessíveis apenas pelas classes que voltarem a herdar a classe derivada.

Herança privada

tiposdeheranca04

Relativamente à herança privada:

  • Na herança privada, o utilizador da classe Derivada não tem acesso a nenhum dos membros da classe Base, o que aliás já acontecia na situação anterior.
  • Estes tornam-se todos privados, pelo que o utilizador da classe derivada deixa de poder utilizar as funcionalidades da classe Base.
  • Esta fica  fica totalmente oculta ao utilizador. Deste facto resulta a possibilidade se se proceder a alterações na classe Base sem necessidade alteração à interface proporcionada ao utilizadorm pela classe Derivada.
  • Caso a classe Derivada venha a ser herdada por outra classe, todos os membros da classe Base ficam com acesso restrido à sua super-classe, e não aos outros níveis da hierarquia de herança.

Considero este assunto simultaneamente simples e complexo.

Simples porque as regras são claras quando analisadas separadamente.

Complexo porque se imaginarmos um hierarquia de classes com alguma complexidade pode tornar-se complicada a sua interpretação.

De qualquer das formas espero ter conseguido com estes esquemas e pequenos exemplos fazer passar as ideias mais importantes.

Para terminar, deixo o quadro resumo.

cartazheranca

Bom proveito e qualquer chamada de atenção é bem vinda!