Ir para conteúdo
Fórum Script Brasil
  • 0

Estruturas De Dados


bonoddr

Pergunta

Olá pessoal. Resolvi abrir este tópico pelo motivo que vou passar a postar nele um estudo dirigido em Estruturas de Dados. Semanalmente atualizarei (terça ou quarta-feira), acho interessante abordar este assunto para aprendermos a gerenciar memória com muito mais eficiência através de ponteiros. O curso de C/C++ básico está sendo planejado que seja criado. smile.gif

Link para o comentário
Compartilhar em outros sites

12 respostass a esta questão

Posts Recomendados

  • 0

Conceitos iniciais

Conceito de variável: é a composição de 3 conceitos: identificador, alocação e tipo.

- O indentificador especifica o próprio nome que lhe foi atribuída.

- A alocação determina onde a variável será armazenada ou, melhor dizendo, alocada.

- Finalmente o tipo, como o nome já diz, determina o tipo de variável que será utilizada (inteiro, decimal, caractere etc.)

Quando um programa é compilado, são 4 as "áreas" responsáveis para a execução completa do mesmo (sem a necessidade da utilização de todos, dependendo do caso):

- Código: aqui é armazenado todo o código para a análise do programa

- Dados: aqui todas as variáveis são armazenadas (desde que não foram alocadas dinamicamente)

- Heap: aqui sim são armazenadas todas as variáveis que são alocadas dinamicamente (new em C++ e malloc em C)

- Pilha: Implementa o controle da execução do programa.

Mas... Como saber qual é o endereço para a próxima instrução em um código? A resposta é simples: existe um registrador que se encarrega de tal tarefa, conhecido como PC (Program Counter). Já pararam para pensar como o compilador sabe que a linha de baixo é a próxima a ser executada?

Agora... Se vocês chamam algum procedimento no meio de duas instruções por exemplo? Como o registrador vai interpretar o desvio do endereço para a próxima instrução?

Na verdade, quando isso ocorre por exemplo, a pilha se encarrega do controle através do registrador. Supondo:

MAIN||||||||||||||||||||||P1

10 instrução 1||||||||||25 if i=1 then {

11 instrução 2||||||||||26 cout<<"valor1"}

12 CALL P1||||||||||||||27 else cout<<"outro valor"

13 instrução 3||||||||||28 return 0

a pilha então ficaria na ordem: 28 -> 13, onde a área de memória correspondente a 28 será avaliada anteriormente a área 13.

Mais uma última abordagem: análise da complexidade pessimista.

O critério de avaliação da complexidade do pior caso é o mais utilizado e, para essa avaliação, os critérios são dependentes somente da estrutura do algoritmo.

Traduzindo biggrin.gif , quer dizer que, sempre que se for tentado construir um algoritmo, deve-se levar em conta o pior caso possível de erro. Quer dizer que não podemos deixar escapar nenhuma exceção que torne o algoritmo, nesse caso específico, ineficaz.

Bom gente, até a próxima. Postem se acharam o conteúdo útil ou um monte de blablabla inútil. Preciso da opinião de vocês quanto à qualidade do conteúdo. Já aviso de antemão que não vou ficar sempre na teoria, em breve começarei a abordar códigos.

Link para o comentário
Compartilhar em outros sites

  • 0

Entendendo ponteiros

Resolvi tentar explicar o conceito e as aplicações deste assunto que tanto apavora muitos programadores.

Uma variável de ponteiro é uma variável contendo um endereço, geralmente o endereço de outra variável. Sua declaração é feita como a de uma variável normal, exceto pela adição do caractere unário "*". Em uma expressão, o caractere "&" significa "o endereço de", e o unário "*" significa "apontado por". Abaixo, um exemplo simples para entendimento:

void fn(){
  int i;
  int *pI;
  pI = &i;
  *pI=10;
}
Na 1ª linha, apenas estamos declarando uma variável i do tipo número inteiro. Na 2ª linha, estamos declarando uma variável-ponteiro que aponta um valor inteiro. Na 3ª linha, podemos observar que o ponteiro pI está apontando o endereço da variável i, até então de valor desconhecido (variável i). Finalmente, a 4ª linha nos informa que o ponteiro aponta para o valor 10. Então, concluímos que *pI aponta para um endereço e pI aponta para um valor. Pode-se entender melhor por "armazene 10 na localização apontada por pI". Então, conclui-se ainda que o valor 10 está localizado no endereço de i. [ ]0X300 i [ ]0X302 pI [0X302]0X304 //Na 1ª atribuição, pI armazena o endereço de i [ ]0X306 [ ]0X300 i [ 10 ]0X302 //Na 2ª atribuição, diz-se que pI aponta para o valor 10 pI [0X302]0X304 [ ]0X306 Melhor ainda: 0X304 -> 0X302 e 0X302 contém o valor 10. Incrementando Ponteiros
void fn(char *pI){
  while (*pI != NULL){ // ou while (*pI != '0')
    cout<<*pI;
    pI++;
  }
}

int main(void){
  fn("Testando o incremento de ponteiros");
  return 0;
}
Neste exemplo, estou fazendo o uso de uma função dentro do main. O programa usa um ponteiro dentro da função fn() para exibir, um caractere de cada vez, o conteúdo da string de caracteres. A função fn() termina quando o ponteiro aponta para um endereço de memória que nada contém, ou seja, no caso de caracteres, o delimitador '\0'. Simplificando os comandos Como o caractere NULL ('\0') é o valor ASC II de 0 em C++ e retorna um valor para representar falso, seus programas podem escrever o laço:
while(*pI != '0')
como simplesmente:
while(*pI)
Também se aplica a:
cout<<*pI;
pI++;
por simplesmente:
cout<<*pI++;
Então, finalmente, pode-se substituir:
while (*pI != '0')
    cout<<*pI;
    pI++;
por:
while (*pI){
  cout<<*pI++;
}
Mais simples, não? Agora lanço um desafio: quero ver quem descobre a funcionalidade deste programa abaixo.
char *func(char *string){
  char *ender_inicial = string; // endereço de string [0]
  while (*string){
    if ((*string >= 'a') && (*string <= 'z'))
      *string = *string - 'a' + 'A';
  string++;
  }
return (ender_inicial);
}

int main(void){
  cout<<func("Ola pessoal")<<endl;
  return 0;
}

Bom, acho que já posso encerrar por aqui. Espero que tenham entendido e, qualquer coisa, PM! cool.gif

Link para o comentário
Compartilhar em outros sites

  • 0

Bom pessoal, para quem não descobriu, a funcionalidade do programa anterior é para substituir todos os caracteres minúsculos por maiúsculos. A diferença na tabela ASC II é constante para quaisquer caracteres (ex.: c para C, f para F, j para J, ...)

Não tenho tido muito tempo para postar, mas quando puder vou atualizar. Até mais! smile.gif

OBS.: postar no sentido de colocar um material consistente, pois tenho frequentado um pouco o fórum de VB/VB.NET também.

Link para o comentário
Compartilhar em outros sites

  • 0

Entendendo a programação Orientada à Objetos

Aqui neste exemplo vou usar somente a classe account pra vocês entenderem como fazer uso de objetos, chamada de métodos e até mesmo como organizar todo o código na prática.

criem o arquivo account.h e insiram o código abaixo:

Código:

#ifndef ACCOUNTH 
#define ACCOUNTH 

//const int MAXACCOUNTS=10; //isto na verdade não é necessário agora 

class account 
{ 
private: 
  float Balance; 
  int AccountNumber; 
public: 
  bool Withdraw(float Amount); 
  bool Deposit(float Amount); 
  float CheckBalance(); 
  int CheckAccountNumber(); 
  void verConta(); //criei mais um método para exibir o saldo atual 
}; 
#endif
Agora criem o arquivo account.cpp e insiram o código abaixo: Código:
#include "account.h" 
#include <iostream.h> 
bool account::Withdraw(float Amount){ 
   if (Balance >= Amount){ 
      Balance -= Amount; 
      return true; 
   } 
   else{ 
      return false; 
   } 
} 
bool account::Deposit(float Amount){ 
   Balance += Amount;  
   return true; 
} 
float account::CheckBalance(){ 
   return Balance; 
} 
int account::CheckAccountNumber(){ 
   return AccountNumber; 
} 
void account::verConta(){ 
   cout<<"Saldo atual: "<<CheckBalance()<<endl; 
} 
int main(){ 
   account a1; 
   a1.Deposit(500); 
   a1.verConta(); 
   if (a1.Withdraw(900) == true){ 
      cout<<"Ta com a grana hein!"<<endl; 
   } 
   else{ 
      cout<<"Vai trabaiah! não pode tirar mais que tem!"<<endl; 
   } 
   a1.verConta(); 
   system("pause"); //dar um pause antes da janela fechar 
   return 0; 
}

Obs.:

1. coloquem os dois arquivos, neste caso, na mesma pasta para serem compilados

2. Executem o arquivo account.exe que será criado após a compilação do acount.cpp

3. Testem mudando, no main(), esta parte: a1.Withdraw(300) por exemplo, ou outros valores de preferência

4. Os headers (.h's), só pra lembrar: não devem ser compilados.

5. Para quem quiser se aprofundar melhor no assunto, pode clicar no link abaixo:

Classes and Objects

Link para o comentário
Compartilhar em outros sites

  • 0

Arrays

Estou abrindo este espaço para falar sobre arrays associando-as aos ponteiros. Isso porque, em C++, ponteiro é a mesma coisa que array. Para ilustrar isso, abaixo especificarei um código simples que ajuda a entender:

#include <iostream>
using namespace std;
void main(){
  int intvet[10];
  int *p;
  p=&intvet[2];
  *p = 2;
  cout<<*p<<", "<<intvet[2]<<endl;
}
pelo código, vamos simplesmente criar uma array de inteiros e um ponteiro para um valor inteiro. No caso do código acima, intvet é uma array de 10 posições e *p é um ponteiro. Primeiro, passamos a informação que p recebe o endereço de intvet. Poderíamos também especificar assim:
p = &intvet[0];
Então, p vai receber o valor do primeiro elemento da array intvet. Podemos concluir que, quando usamos:
p = intvet;
estamos especificando que p está recebendo o endereço da primeira posição da array especificada. Vou explicar melhor:
*(intvet + i)
i neste caso é o índice da array vezes o número de bytes para tipo de valor armazenado. Por exemplo, vamos dizer que &intvet[0] tem o número de endereço 1000. Nesse caso, *(1000 + 0 * 4) = *(1000). Então, o ponteiro correspondente apontará a posição 1000 da array, ou seja, intvet ou &intvet[0]. Vamos então agora apontar para a posição 3 da array.
p = &intvet[2];
*p = 11;
Nesse caso, p vai apontar o valor 11 que está no endereço de memória de intvet[2]. Aplicando a fórmula especificada: *(1000 + 3 * 4) = *(1012) Este é o endereço de memória que a posição 3 da array intvet irá ocupar. Agora indo mais à frente: Se foi dito que ponteiro é o mesmo que array, então podemos especificar que *intvet equivale a intvet[0]. Da mesma maneira, *(intvet + i) equivale a intvet.
#include <iostream>
using namespace std;
void main(){
  int intvet[10];
  int *p;
  p=&intvet[2];
  *p = 2;
  cout<<*p<<", "<<*(intvet + 12)<<endl;
  p=intvet;
  *p = 3;
  cout<<*p<<", "<<*intvet<<endl;
}

Por enquanto é só. Acho que deveria ter falado antes de Classes e Objetos, mas tá lançado. Aguardem! cool.gif

Link para o comentário
Compartilhar em outros sites

  • 0

Fazendo objetos diferentes comunicarem-se entre si

Agora, continuando com os conceitos de orientação a objeto, vou criar duas classes: uma que instancia objetos do tipo contas de um banco e outra que instancia um terminal de caixa eletrônico (nesse caso, só precisaremos instanciar um objeto do tipo caixa eletrônico).

Vamos definir o cabeçalho da classe account:

/*  ACCOUNT.H  */
#ifndef ACCOUNTH
#define ACCOUNTH

const int MAXACCOUNTS=10;

class account
{
private:
  float Balance;
  int AccountNumber;
public:
  bool Withdraw(float Amount);
  bool Deposit(float Amount);
  float CheckBalance();
  int CheckAccountNumber();
};
#endif
O valor de MAXACCOUNTS será posteriormente usado para armazenar o número de elementos em uma array. Neste caso, todos os objetos de account estarão armazenados em uma array de objetos. Esta array será acessada pela classe atm, pensando na vida real, onde todas as contas poderão ser acessadas pelo caixa eletrônico. Agora vamos definir os métodos da classe account no arquivo account.cpp:
#include "account.h"

bool account::Withdraw(float Amount)
{
  if (Balance >= Amount) {
    Balance -= Amount;
    return true;
  } else {
    return false;
  }
}

bool account::Deposit(float Amount)
{
  Balance += Amount;
  return true;
}

float account::CheckBalance() { return Balance; }

int account::CheckAccountNumber(){  return AccountNumber; }
Pronto, agora vamos especificar o cabeçalho da classe atm (automatic transfer machine, ou caixa eletrônico):
/*  ATM.H  */
#ifndef ATMH
#define ATMH
#include "account.h"

class atm
{
private:
  int CardCode, EnteredCode;
  account Account[MAXACCOUNTS];
public:
  void DisplayWait();
  void ReadCard();
  BOOL CheckCode();
  void ProcessChoice();
};
#endif
aqui, usamos a variável global de account.h (MAXACCOUNTS) para definirmos o tamanho da array de objetos de account. Reparem que, para declarar uma array de int's, faz-se:
int array[MAXACCOUNTS];
Aqui teremos uma array de número inteiros com 10 posições (índice 0 até 9). Para guardar objetos account em uma array, faz-se a mesma analogia:
account array[MAXACCOUNTS];
Agora aqui está o código do atm.cpp:
#include "atm.h"

void atm::DisplayWait()
{
  char card='\0';

  while (card != 'c') {
    cout << "Please insert your card.\n";
    cout << "Testing: enter 'c' and hit return ";
    cin >> card;
  }
  cout << "Please enter code on card: ";
  cin >> CardCode;
}

void atm::ReadCard()
{
  cout << "Please enter your personal code: ";
  cin >> EnteredCode;
}
BOOL atm::CheckCode()
{  return EnteredCode == CardCode ? true : false;}

void atm::ProcessChoice()
{
  int choice;  float amount;

  do {
    cout << "Select an option:\n";
    cout << "Withdraw cash -- Press 1\n";
    cout << "Deposit cash  -- Press 2\n";
    cout << "Check balance -- Press 3\n";
    cout << "Quit          -- Press 0\n";
    cin >> choice;
    if (choice < 0 || choice > 3)
      cout << "Invalid choice. Try again.\n";
  } while (choice < 0 || choice > 3);
switch (choice) {
  case 0:    return;
  case 1:
    cout << "Enter amount to withdraw: ";
    cin >> amount;
    if (Account[CardCode].Withdraw(amount))
      cout << "Transaction successful.\n";
    else
      cout << "Balance insufficient for withdrawal.\n";
    break;
  case 2: ... /* Trata depósito */
  case 3:
    cout << "Current balance of account is: ";
    cout << Account[CardCode].CheckBalance() << endl;
    break;
  }
}
Caso queiram, criem um arquivo cpp para chamar todo o programa. Neste arquivo, será especificado apenas a funcionalidade do programa.
#include "atm.h"

void main()
{
  atm autobank;

  while(true) {
    autobank.DisplayWait();
    autobank.ReadCard();
    if (autobank.CheckCode())
      autobank.ProcessChoice();
    else
      cout << "Incorrect code.\n";
  }
}

Pronto. Temos um programa que simula o funcionamento de um caixa eletrônico. Porém, dêem uma olhada e descubram o que ainda há de errado com o programa. Qualquer coisa, mandem-me email! Até já! cool.gif

Link para o comentário
Compartilhar em outros sites

  • 0

Bom, o único erro no programa é que os objetos de account nunca eram inicializados, então a array de objetos armazenava lixo, e não dados consistentes. É para isso que servem os Construtores, para instanciarem os objetos de forma adequada. Em breve, colocarei um material explicando conceitos de Construtores e uma palhinha de Destrutores(responsáveis por liberarem espaço de memória depois que determinado objeto foi utilizado).

Bem em breve postarei mais um material, aguardem! cool.gif

Link para o comentário
Compartilhar em outros sites

  • 0

Alocação Dinâmica

A alocação dinâmica de memória consiste em tratar de estruturas de tamanho variável. O operador new é usado para alocar memória do sistema retornando um ponteiro para o primeiro byte do bloco de memória alocado. Essa memória deve ser explicitamente liberada (uma vez que C++ não possui garbage collection) com o operador delete.

O operador de alocação new funciona da seguinte forma:

int *ptrVar = new int ( 80 );

int *ptrVar: declaração de um ponteiro para um inteiro;

new int ( 80 ): reservar espaço para um int armazenando o valor 80 nesse espaço (ou seja, a memória alocada é inicializada com o valor 80).

Dessa forma, se fizéssemos

cout << *ptrVar << endl;

teríamos o número 80 impresso na tela, pois ptrVar aponta para 80 (é interessante observar que ptrVar não aponta para uma variável que armazena o valor 80, e sim para uma área da memória que foi alocada e na qual o número 80 foi armazenado, ou seja, uma área não declarada).

Note que se usássemos a função malloc, de C, teríamos que especificar, com a função sizeof(), o tamanho do tipo alocado, como no exemplo:

int *ptrVar;

ptrVar = malloc( sizeof( int ) );

Já com o operador new, essa especificação de tamanho do tipo não é necessária.

Para liberar o espaço alocado por new basta usar o operador delete, da seguinte forma:

delete ptrVar;

Também se pode alocar memória para arrays da seguinte forma:

int *arrayInt;

// declaração da variável ponteiro que se transformará em um array

arrayInt = new int[ 20 ];

// aloca 20 espaços de tamanho int para arrayInt;

// arrayInt agora é um array de 20 posições

Nesse caso, para liberar a memória alocada para o array, fazemos:

delete [ ] arrayInt;

No caso de arrays com duas dimensões, primeiro alocamos as linhas fazendo um array de ponteiros. Depois percorremos esse array alocando as colunas. Assim:

int **arrayInt;

arrayInt = new int * [ 20 ];

for ( int i = 0; i < 20; i++ )

  arrayInt[ i ] = new int[ 20 ];

Agora "arrayInt" é uma matriz 20x20. E para deletar:

for ( int i = 0; i < 20; i++ )

  delete [] arrayInt[ i ];

delete [] arrayInt;

Os membros de dados de uma classe podem também ser ponteiros que, como veremos no próximo post sobre construtores e destruidores, devem ser inicializados apontando para alguma variável ou podem ter memória alocada com new.

Ok, pessoas.

Até o próximo post! tongue.gif

Link para o comentário
Compartilhar em outros sites

  • 0

Construtores

O construtor de uma classe é a função membro responsável por inicializar os seus dados pro caso de o usuário não fornecer nenhum. Nesse caso, os valores usados na inicialização seriam utilizados. Uma mesma classe pode conter mais de um ou nenhum construtor (no primeiro caso basta que os construtores tenham diferentes assinaturas e, nesse último, o compilador cria um construtor automaticamente, que não faz nada). Facilmente podemos reconhecer uma função construtor numa classe: ela possui o mesmo nome da classe.

class Classe {

public:

   Classe();                // assinatura do construtor

private:

   int dado;

};

Classe::Classe()

{

   dado = 0;                // inicializa a variável dado com 0

}

A função construtor de uma classe é chamada automaticamente sempre que um objeto daquela classe for instanciado. É importante notar que não se pode inicializar os membros de dados das classes na definição da classe, pois um erro seria apontado pelo compilador; para evitar isso, usa-se os construtores.

O uso de construtores não é obrigatório. Você pode, depois de instanciado o objeto, atribuir valores aos dados, mas é recomendável que você use construtores porque assim você assegura que os dados sejam inicializados e com valores válidos. Não se pode atribuir tipos de retorno para um construtor, pois construtores não retornam valores.

Vou exemplicar melhor o uso de construtores usando um na classe “account”. A nova definição da classe account (incluindo o construtor) é a seguinte:

class account

{

public:

   account();                         // assinatura do construtor

   bool Withdraw(float Amount);

   bool Deposit(float Amount);

   float CheckBalance();

   int CheckAccountNumber();

private:

   float Balance;

   int AccountNumber;

};

Agora, a implementação do construtor:

account::account()

{

   Balance = 0.0;

   AccountNumber = -1;

}

Os construtores podem receber argumentos (e estes podem possuir valores default, como veremos mais à frente). Para demonstrar isso, usarei o exemplo da classe “account”. Vamos ver a nova definição da classe “account”:

class account

{

public:

   account();          // assinatura do construtor s/ argumentos

   account(float NewBalance, int NewAccountNumber );

   // assinatura do construtor c/ argumentos

   bool Withdraw(float Amount);

   bool Deposit(float Amount);

   float CheckBalance();

   int CheckAccountNumber();

private:

   float Balance;

   int AccountNumber;

};

O primeiro construtor manteria a sua implementação, e o segundo teria a seguinte:

account::account( float NewBalance, int NewAccountNumber )

{

   Balance = NewBalance;

   AccountNumber = NewAccountNumber;

}

Na instanciação do objeto poderíamos fazer:

account Account = account( 10, 8 );

// Balance = 10 e AccountNumber = 8

account Account1( 5.3, 8 );

// (forma abreviada) Balance = 5.3 e AccountNumber = 8

account Account2;

// Balance = 0.0 e AccountNumber = -1 (uso do primeiro construtor)

Ou seja, o(s) número(s) entre parênteses ao lado do nome do objeto na instanciação deste é(são) o(s) valor(es) de inicialização dos dados daquele objeto (e também os valores passados como argumentos do construtor com argumentos).

E se o construtor tiver um valor default? Os valores de inicialização não fornecidos na instância do objeto seriam agora substituídos pelos valores default (assim como em funções com valores default). Vamos criar a classe “Calendario”. O usuário entrará com um dia, um mês e um ano quaisquer, e uma outra função que adicionaremos ao exemplo imprimirá a data no formato d / m / a. Vamos ver:

// Definição da classe (arquivo calendario.h)

#ifndef CALENDARIO_H

#define CALENDARIO_H

class Calendario {

public:

   Calendario( int d = 1, int m = 1, int a = 1 );

   // construtor com argumentos default

   void impData();                         // imprime a data fornecida

private:

   int dia;

   int mes;

   int ano;

};

#endif

// Implementação das funções membro (arquivo calendario.cpp)

#include <iostream>

#include “calendario.h”

Calendario::Calendario( int d, int m, int a )

{

   dia = d;

   mês = m;

   ano = a;

}

void Calendario::impData()

{

   cout << dia << “ / “ << mes << “ / “ << ano << endl;

}

// Arquivo principal (arquivo data.cpp)

#include <iostream>

#include “calendario.h”

int main()

{

   Calendario data1;

   // todos os valores serão default

   Calendario data2( 13 );

   // dia especificado (entre parênteses), os outros valores serão default

   Calendario data3( 23, 2 );

   // dia e mês especificados, o ano será default

   Calendario data4( 12, 12, 1212 );

   // todos os dados especificados

   data1.impData();

   // imprimirá “1 / 1 / 1”, pois todos os dados são dafault na declaração

   data2.impData();

   // imprimirá “13 / 1 / 1”, pois só o dia foi especificado na declaração

   data3.impData();

   // imprimirá “23/ 2 / 1”, pois o dia e o mês foram especificado na declaração

   data4.impData();

   // imprimirá “12 / 12 / 1212”, pois todos os dados foram especificados na declaração

  return 0;

}

Quanto à inicialização de ponteiros em construtores, existem três opções: inicializar com null (ou seja, zero), apontar para uma variável conhecida ou alocar dinamicamente a memória a ser apontada.

Para exemplificar usarei como exemplo a classe “TestPointer”, dada a seguir:

class TestPointer {

public:

   TestPointer();

   TestPointer(int intValue, int size);

private:

   int *singleInt;                      // ponteiro para um inteiro

   int *intArray, arraySize;     // intArray será um array dinâmico de tamanho arraySize

};

Temos, no exemplo, dois construtores: um com argumentos e outro não. O sem argumentos inicializa todos os dados com 0:

TestPointer::TestPointer()

{

   singleInt = 0;

   intArray = 0;

   arraySize = 0;

}

Para entender a implementação do construtor com argumentos dê antes uma olhada no post sobre “Alocação Dinâmica”. Implementação:

TestPointer::TestPointer( int intValue, int size )

{

   singleInt = new int ( intValue );

   intArray = new int[ size ];

   arraySize = size;

   for ( int aux = 0; aux < size; aux++ ) {    // inicialização dos elementos do array intArray

      intArray[ aux ] = 0;

   }

}

Veremos, no próximo post, como desalocar a memória alocada usando destruidores.

Até mais! biggrin.gif

Link para o comentário
Compartilhar em outros sites

  • 0

Destruidores

O destruidor é o método da classe que devolve a memória alocada pelo construtor. Ele é executados toda vez que a execução sai do escopo do objeto ao qual ele pertence. A definição de um destruidor se dá da seguinte forma:

~Nome_da_classe();
Assim como os construtores, os destruidores também devem ter o mesmo nome da classe e não retornam nenhum valor. Destruidores não recebem argumentos. Vejamos o seguinte exemplo:
// exemplo1.h

#ifndef EXEMPLO1_H
#define EXEMPLO1_H

class Exemplo1 {
public:
   Exemplo1();	// contrutor
   ~Exemplo1();	// destruidor
};

#endif
// exemplo1_1.cpp

#include <iostream>
#include "exemplo1.h"

using namespace std;

Exemplo1::Exemplo1()
{
   cout << "Construtor do objeto 1 chamado." << endl;
}

Exemplo1::~Exemplo1()
{
   cout << "Destruidor do objeto 1 chamado." << endl;
}
// exemplo2.h

#ifndef EXEMPLO2_H
#define EXEMPLO2_H

class Exemplo2 {
public:
   Exemplo2();  // contrutor
   ~Exemplo2();  // destruidor
};

#endif
// exemplo2_1.cpp

#include <iostream>
#include "exemplo2.h"

using namespace std;

Exemplo2::Exemplo2()
{
   cout << "Construtor do objeto 2 chamado." << endl;
}

Exemplo2::~Exemplo2()
{
   cout << "Destruidor do objeto 2 chamado." << endl;
}
// teste.cpp

#include <iostream>
#include "exemplo1.h"
#include "exemplo2.h"

using namespace std;

int main()
{
   Exemplo1 ob1;
   
   {
      Exemplo2 ob2;
   }
   
   return 0;
}
Esse programa imprimirá na tela as seguintes mensagens (na ordem):
Construtor do objeto 1 chamado. Construtor do objeto 2 chamado. Destruidor do objeto 2 chamado. Destruidor do objeto 1 chamado.
Isso porque o objeto 1 é instanciado primeiro, então seu construtor é chamado; depois, dentro de outro escopo (que está dentro do escopo do objeto 1, que, por sua vez, é a função "main"), o objeto 2 é criado, e seu construtor chamado. Quando a execução sai do escopo interno a "main", o destruidor do objeto 2 é chamado, pois a execução saiu do seu escopo; e, finalmente, quando a execução sai de "main", o destruidor do objeto 1 é executado. Agora, um exemplo simples de desalocação dinâmica com destruidores.
// exemplo.h

#ifndef EXEMPLO_H
#define EXEMPLO_H

class Exemplo {
public:
   Exemplo( int = 1 );
   ~Exemplo();

private:
   int *ePtr;
};

#endif
A implementação do construtor e do destruidor:
#include "exemplo.h"

Exemplo::Exemplo( int v )
{
   ePtr = new int[ v ];
}

Exemplo::~Exemplo()
{
   delete [] ePtr;
}

Então toda vez que um objeto dessa classe for instanciado, ele irá alocar memória dinamicamente. E toda vez que a execução deixar o escopo desse objeto, o destruidor entra em ação desalocando a memória alocada. Sacaram?

Valeu, pessoas!

Até a próxima!

Link para o comentário
Compartilhar em outros sites

  • 0

GABARITOS

Os gabaritos permitem que você, escrevendo uma só vez uma função ou classe, declarar várias funções (ou classes) sobrecarregadas. Por exemplo, você pode escrever uma função que retorne um tipo de dados X. Na hora de chamar a função, você especifica que tipo é X. Pode ser int, double, char, etc., ou seja, você tem um monte de funções sobrecarregadas escrevendo o seu código apenas uma vez.

Vamos começar pelas funções gabarito (funções que utilizam gabaritos). Pra definir uma função gabarito, você começa a definição com a definição do tipo gabarito, da seguinte forma:

template< class X >
Colocando a linha acima antes da definição de uma função você está qualificando a função como uma função gabarito. A partir daí você pode declarar variáveis do tipo X dentro da função (inclusive, como já foi dito, retornar dados do tipo X). Observe:
template< class X >
X funcao( X var )
{
    cout << var << endl;
    return var;
}
Ao chamar a função ela irá se adequar ao tipo que você passar para ela como argumento. Por exemplo:
#include <iostream>

using namespace std;

template< class X >
X funcao( X var );

int main()
{
    int x1 = 3, y1;
    double x2 = 3.141592, y2;
    char x3 = ‘D’, y3;

    y1 = funcao( x1 );
    y2 = funcao( x2 );
    y2 = funcao( x2 );

    cout << “y1: “ << y1 << endl
            << “y2: “ << y2 << endl
            << “y3: “ << y3 << endl;

    return 0;
}

template< class X >
X funcao( X var )
{
    cout << var << endl;
    return var;
}
Agora partamos ao estudo de gabaritos de classe. Da mesma forma dos gabaritos de funções, você pode criar classes de forma mais genérica. A definição de classes gabarito se dá de maneira análoga à de funções gabarito: adicionando a linha
template< class X >
antes da definição. Uma particularidade é que também se deve adicionar essa linha antes da definição dos métodos dessa classe.
#ifndef ARRAY_H
#define ARRAY_H

template< class X >
class Array {
public:
    Array( int = 10 );
    ~Array();

    void print() const;
    X *get() const;

private:
    X *array;
    int tamanho;
};

#endif

template< class X >
Array< X >::Array( int n )
{
    tamanho = ( n > 0 ? n : 10 );
    array = new X[ tamanho ];
}

template< class X >
Array< X >::~Array()
{
    delete [] array;
}

template< class X >
void Array< X >::print() const
{
    for ( int i = 0; i < tamanho; i++ )
        cout << array[ i ] << “ “;
}

template< class X >
X *Array< T >::get() const
{
    return array;
}
Bem, analisemos o código acima. Primeiro notemos que o constructor é declaredo com um valor default, ou seja, se, na instanciação de um objeto da classe Array, nenhum valor for passado para o construtor de Array, o valor default será passado. Observe o const nas funções “print” e “get”. Ele diz que essa função não irá modificar o objeto ao qual ela pertence, e, caso isso aconteça, um erro será apontado. De fato, nem “print” nem “get” em nenhum momento modificam o objeto. Agora vejamos um ponto essencial para a nossa discussão. Conforme já foi dito, usando gabarito de classes é como se você criasse um grande número de classes escrevendo o código uma só vez. Na hora de definirmos um método de uma classe, informamos a classe à qual ele pertence (ou seja, o seu escopo) e depois, usando o operador “::” (resolução de escopo), apontamos qual método queremos definir. Então, sendo que se definiu a classe de maneira genérica, seus métodos também devem ser definidos assim. Dessa forma, na hora de indicarmos a classe contendedora do método, usamos o nome da classe seguido de “< X >” (usamos “X” para esse caso, mas pode-se colocar qualquer nome pra definir o tipo genérico). Observe atentamente a definição dos métodos da classe Array para entender melhor. A instanciação de um objeto de uma classe gabarito também tem um ponto especial. Na hora de instanciar, deve-se indicar o tipo formador da classe entre os sinais “<” e “>”. Por exemplo, se quiséssemos instanciar um Array de inteiros, faríamos:
Array< int > a;
A mesma regra para os outros tipos de dados. A linha acima geraria um objeto Array de inteiros de tamanho 10 (uma vez que não foi passado nenhum valor para o construtor). Agora vamos cirar um Array de números reais de tamanho 100:
Array< double > d( 100 );

Então eu vou parar por aqui. Qualquer eventual erro e/ou dúvida, comentem.

Até mais!

Link para o comentário
Compartilhar em outros sites

  • 0

Olá galera!

Uma satisfação: esse tópico foi criado antes do meu cargo de moderador da sala de C/C++. Creio que o moderador passado ('Wicker Man') tenha criado essa idéia, juntamente com o membro 'bonoddr'.

Não vou retirar esse tópico, pois possui um ótimo conteúdo! O tópico ficará para eventual estudo \o/ Até a próxima galera!

Link para o comentário
Compartilhar em outros sites

Visitante
Este tópico está impedido de receber novos posts.


  • Estatísticas dos Fóruns

    • Tópicos
      152,1k
    • Posts
      651,8k
×
×
  • Criar Novo...