Saltar para o conteúdo

Programar em C++/Variáveis e constantes

Origem: Wikilivros, livros abertos por um mundo aberto.

Compatível com a linguagem C, o C++ mantém as constantes básicas e introduz algumas novas funcionalidades possibilitadas pelo modificador const.


O uso do modificador const tem duas funções principais:

  1. Resguarda da inviolabilidade de valores apontados por ponteiros;
  2. Auxílio na compreensão das características de funções, durante a implementação.


Constantes simbólicas podem ser criadas com as diretivas do preprocessador #define. Neste modo os valores, de fato, não são interpretados imediatamente pelo compilador, antes são identificados e substituidos pelo preprocessador no estágio anterior à compilação. Por exemplo:

#define BUFFER_LENGTH 2048

...
...
...

char data[BUFFER_LENGTH];

Observe que o valor 2048 será usado logo abaixo no código, depois que o preprocessador substituir a constante simbólica BUFFER_LENGTH pelo valor que lhe foi atribuído.

Note que as constantes são escritas com todas as letras maiúsculas, isso não é uma regra, mas ajuda a identificar o que é constante simbólica dentro do programa, sendo adotado pela maioria dos desenvolvedores como uma boa prática de programação.

Neste caso, podemos definir valores simbólicos compostos, por exemplo:

#define BUFFER_LENGTH 2048
#define N_BUFFERS 100
#define MASTER_LENGTH ( BUFFER_LENGTH * N_BUFFERS )
...
...
...

char screen[MASTER_LENGTH];

Os valores podem ser simbólicos em formato de código, o que permite criar programas com melhor legibilidade. Para isso podemos colocar expressões com funcionalidades bem definidas substituídas por nomes que as identifiquem. Por exemplo:

float a[3];

#define PRINT_VECTOR cout << a[0] << " , " << a[1] << " , " << a[2] << endl
...
...

PRINT_VECTOR;

Desta forma, todas as vezes que quisermos mostrar o valor do vetor de três coordenadas podemos usar a constante PRINT_VECTOR.

Constantes literais podem ser declaradas da mesma forma que na linguagem "C", ou seja, podemos definir valores fixos em qualquer parte do programa, expressando-os diretamente no código através de seu valor significativo. Por exemplo, podemos definir números:

256 //decimal
0400 //octal
0x100 //hexadecimal

Também podemos definir valores para caracteres ou cadeias de caracteres, como segue:

'a'             // um caractere
"abc"           // uma cadeia de caracteres
"\xF3\x23\x12"  // uma cadeia de caracteres representada por seus valores em hexadecimal

Temos ainda a possibilidade de declarar constantes compostas por valores e operadores:

(4.23e14 * (12.75 + 12976.18/36)) // constante composta

Enumerações

[editar | editar código-fonte]

Valores enumerados são muito recorrentes nos ambientes de programação, por isso podemos contar com a declaração de enum em C++ também, o que segue a mesma sintaxe que temos em "C":

enum seq {A,B,C,D};

seq x;

ou ainda:

enum nomes {LANY=100,SANDRA=200,MARCIA=300,RODRIGO=400};

nomes x;

Porém, observamos uma diferença: a palavra enum pode ser dispensada na declaração da variável, enquanto que em C é obrigatório,apesar desta pequena diferença a funcionalidade do recurso é a mesma, ou seja, pode-se definir variáveis que assumem estritamente os valores presentes na declaração de enumeração.

Este recurso torna-se útil na padronização de valores a serem usados como entrada de funções, por exemplo. Pode ser considerada como uma funcionalidade mnemônica, seu uso não altera o código final caso modifiquemos o programa para que use variáveis inteiras ou strings de mesmo valor do enum.

A seguinte sintaxe:

seq x = 3;

Não é permitida, mesmo que o valor presente no enum seja avaliado como 3 pelo compilador em tempo de compilação. Isso pode parecer confuso, mas lembre-se de que os valores serão atribuidos pelo compilador, logo isso evita que o mesmo programa seja compilado em ambientes diferentes e tenha comportamento diferente.

As variáveis no C++ podem ser usadas da mesma forma que na linguagem "C", porém algumas poucas diferenças podem ser destacadas, principalmente aquelas que trazem à linguagem C++ características próprias da orientação a objetos.

Como na linguagem "C", os tipos nativos do compilador em uso são referenciados por:

 char
 int
 float
 double

Que correspondem a números com tamanho relativos, com os significados respectivos: caractere, inteiro, ponto flutuante e ponto flutuante de dupla precisão. De qualquer forma a extensão dos mesmos depende da máquina que se pretende programar. Considerando que nem sempre teremos que programar apenas computadores, poderemos ter extensões bem distintas dependendo do hardware a ser programado, por exemplo, computadores domésticos tipicamente tem processadores de 32 ou 64 bits hoje em dia, enquanto que dispositivos embarcados podem ter processadores de 8, 16 ou 32 bits. Portanto, o compilador para cada caso atribui faixas diferentes para cada tipo em cada situação.

A linguagem C++ introduz o tipo bool, que representa o valor booleano, falso ou verdadeiro, o que não existe na linguagem "C", porém seu tamanho na memória depende da capacidade de otimização do compilador usado. Tipicamente os compiladores para computadores usam uma variável do tamanho de char para representar o valor, o que poderia ser considerado um desperdício, mas devido à abundância de memória não chega a ser inadequado. Porém em sistemas pequenos há compiladores que armazenam o valor booleano em apenas um bit. Obviamente, se o processador possuir recursos de manipulação de bits isso é muito útil e pode ser usado como um fator de melhoria da qualidade do software desenvolvido. Em outros ambientes, onde a manipulação de bits traga prejuízo para o desempenho usa-se a estratégia padrão de desperdiçar um pouco de espaço em favor de uma agilidade maior nas operações. Portanto, embora as variações de utilização do espaço sejam muitas, o compilador sempre fará a mais apropriada para cada ambiente de utilização da linguagem.

Modificadores

[editar | editar código-fonte]

O C++ conta com os modificadores de amplitude (short,long) presentes na linguagem "C" e modificadores de acesso, alguns exclusivos do C++, que estão diretamente ligados a características da POO (programação orientada a objetos). Desta forma descreveremos apenas os tipos relevantes exclusivamente para a programação na linguagem escopo do livro presente sem nos aprofundarmos na teoria por trás dos mesmos. A prática do uso dos mesmos é melhor indicada como meio de aprofundamento do tema.

Assim contamos com os modificadores da linguagem "C":

static
short
long
unsigned 
signed

A linguagem C++ introduz um novo modificador chamado const, que tem comportamento variado dependendo do local onde está sendo declarado. Sua função, basicamente, é estabelecer um vínculo entre declaração e obrigatoriedade da coerência no uso do símbolo declarado.

A princípio, quando declaramos uma constante com este modificador fazemos com que seja obrigatório o uso do símbolo de forma que o mesmo não possa ter seu valor alterado. Assim, se fizermos:

const int x = 4;

O inteiro x não poderá deixar de ter valor igual a 4. Qualquer tentativa de modificar o valor da constante ao longo do programa será reportada como erro pelo compilador. Porém podemos considerar esta funcionalidade como óbvia e trivial, ainda temos o uso do modificador de uma forma mais proveitosa, na passagem de parâmetros para funções, por exemplo:

void inhibitX(const int *x)
{
...
...
 BASEADDRESS = z*((*x) - 23p*71);
...
...
}

Neste caso, a função acima recebe um valor inteiro através de um ponteiro, que não obrigatoriamente precisa ser constante no escopo fora da função, porém dentro da mesma a variável será constante. Fazendo este simples procedimento teremos como fazer com que um símbolo seja variável fora da função e constante dentro da mesma, de forma que dentro do escopo da mesma só façamos leituras do seu valor. O artifício cria duas consequëncias importantes: a primeira é a melhor legibilidade do código, visto que ao usarmos uma função teremos certeza de que os valores não serão alterados dentro da função; a segunda é que poderemos evitar erros inadvertidos de atribuição de valores à variável quando da construção da função.

Uma variável "volátil", como a própria expressão sugere, é uma variável que pode ser modificada sem o conhecimento do programa principal, mesmo que esta ainda esteja declarada dentro do escopo onde o programa está sendo executado. Isso está relacionado, principalmente a processos concorrentes e "threads", estes podem alterar o conteúdo da variável em eventos fora da previsibilidade do tempo de compilação. Em outras palavras, o compilador não pode prever com segurança se pode otimizar trechos de programa onde esta variável se encontra.

A palavra reservada volatile é destinada as situações onde uma variável pode ter seu valor alterado por fatores diversos, e portanto, não pode ser otimizada. Usando-a o programador informa ao compilador que não deve interferir na forma com que o programa foi escrito para acesso a esta variável. Desta forma impede que erros inseridos por otimização estejam presentes na versão final do executável.

O uso desta palavra implica em mudança no comportamento do compilador durante a interpretação do código. As classes de objetos do tipo voláteis só poderão ser acessadas por rotinas que declarem aceitar como entrada dados voláteis, da mesma forma que apenas objetos voláteis podem acessar variáveis voláteis. Esta "amarração" faz com que o uso de tais variáveis se torne mais seguro.

Podemos declarar variáveis voláteis da seguinte forma:

volatile int x;

Enquanto que para funções que acessam tais variáveis teremos consequências visíveis na montagem do código, por exemplo, se tivermos o seguinte trecho de programa:

int x = 1265;

void main_loop()
{
  while( x == 1265)
      {
       // fazer alguma coisa
      }
}

Poderemos ter uma otimização gerada pelo compilador como segue:

int x = 1265;

void main_loop_optimized()
{
  while( true )
      {
       // fazer alguma coisa
      }
}

Considerando que em um programa que foi desenhado para ambiente multitarefa isso não pode ser considerado verdadeiro, pois o programa pode estar esperando que uma das tarefas modifique o estado da variável para prosseguir seu curso, a otimização acima será um desastre, uma vez que a função acima jamais será encerrada.

Para evitar isso fazemos:

volatile int x = 1265;

void main_loop()
{
  while( x == 1265)
      {
       // fazer alguma coisa
      }
}

E o compilador não poderá mais avaliar que o valor de x pode ser otimizado para o valor corrente, pois informamos na declaração que o valor da variável pode ser alterado sem seu conhecimento. Desta forma o mesmo não alterará o algorítmo e fará o teste da variável dentro do while.

Nomeando tipos

[editar | editar código-fonte]

A linguagem "C" possui recursos de nomeação de tipos simples e compostos através das palavras chaves typedef e struct. Adicionada a estas o C++ acrescenta a palavra chave class. Vejamos como devemos definir um novo tipo através desta palavra chave.

A palavra class atribui a um conjunto de tipos de dados o estado de modelo de objeto. Este conceito é fundamental para o modo avançado de programar usando o C++. Com este identificador declaramos objetos, da mesma forma que declaramos estruturas.

Uma classe pode ser definida em um cabeçalho "header", da seguinte forma:

class nome_da_classe
{ <tipo_1> variavel_1;
  <tipo_2> variavel_2;
  .
  .
  .
  <tipo_n> variavel_n;
  -----
  <tipo_n> nome_funcao ( <tipo_1> variavel_1, <tipo_2> variavel_2, <tipo_3> variavel_3 ...);
};

O mais interessante de observar é a presença de uma função dentro da declaração acima. Então poderíamos perguntar: Por que colocar uma função dentro de um tipo? A resposta é simples: Para manipular os dados dentro do tipo! Não apenas por esta característica, mas por várias outras que abordaremos nos capítulos seguintes, o tipo class é extremamente flexível e poderoso.

É importante ressaltar que em C++ a declaração do identificador: enum, struct, class, etc... é dispensado quando se declara uma variável ou objeto para o referido tipo. Desta forma podemos ter também as seguintes declarações como válidas, além do uso padrão da linguagem "C":

struct data{ int a; 
             int b; 
             int c; 
           };

class object{ int a;
              char b; 
              long w;
              float p;
              void getData();
            };

...
...
...
...

void func()
{ data x;
  object y;
  ...
  ...
  y.getData();
}

Como podemos ver na função acima se a variável x for declarada para uma estrutura data o uso da palavra struct não é obrigatório, assim como também não o é para outros tipos de dados compostos.