Programar em C++/Operadores

Origem: Wikilivros, livros abertos por um mundo aberto.

Os operadores realizam, como o nome sugere, operações entre dois tipos de dados. Existem muitos operadores, mas citaremos aqui os fundamentais e, conforme as necessidades dos tópicos posteriores, citaremos todos os demais.

Compatibilidade[editar | editar código-fonte]

Os operadores padrões da linguagem "C" podem ser usados de forma semelhante na linguagem C++. Aliadas às funcionalidades tradicionais do "C" podemos criar operações diferentes para os operadores. Esta funcionalidade é conhecida como sobrecarga de operadores e será descrita em capítulo posterior.

Como C++ interpreta os operadores[editar | editar código-fonte]

Para dar uma base de entendimento para a sobrecarga de operadores iremos introduzir o modo como o compilador C++ entende os operadores. Este modo de interpretar os operadores é implementado para dar suporte aos recursos de POO da linguagem. O entendimento do modo de funcionamento do mecanismo de compilação pode diminuir as dúvidas que surgem no estudo de operadores em C++.

É importante entender que a linguagem deve servir de base para a criação de entidades de dados autônomas e operáveis. Logo, o compilador deve ser generalista ao enxergar operações com tipos primitivos e tipos criados pelo programador (classes). Por estes fatores, o compilador utiliza-se de uma interface funcional para implementação dos recursos de operadores.

Os operadores são tratados como funções, de forma a possibilitar a alteração do seu comportamento em determinados casos. Os operandos são tratados como argumentos de funções, enquanto que o resultado da operação é tratado como retorno da função do operador. Esta interface faz com que possamos programar o comportamento das operações e alterá-las quando nos seja conveniente.

Entendendo o operador[editar | editar código-fonte]

Basicamente, temos dois tipos de implementação para operadores, o tipo global e o tipo membro de classe. Os dois tipos são usados regularmente nas implementações mais comuns. Analisaremos o tipo global, uma vez que ainda não temos uma noção de classes suficiente para abordar o tipo membro de classe.

Digamos que temos uma estrutura ponto, como definida abaixo:

 struct Ponto
  { int x;
    int y;
  };

Uma vez que tenhamos definido um ponto, nada mais natural que queiramos somar, subtrair, enfim operar, pontos diferentes de acordo com nossas necessidades. Para isso podemos criar operadores para fazer isso, da seguinte forma:

  • Observamos a quantidade de parâmetros, o retorno e a forma de chamar o operador que queiramos definir e criamos uma função que execute a operação desejada;
  • Inserimos o código da referida função dentro de uma chamada de operador, usando a palavra reservada operator seguida do operador que desejamos definir:
  Ponto operator+ ( Ponto a, Ponto b )
        { Ponto soma; 
          soma.x = a.x + b.x;
          soma.y = a.y + b.y;
          return soma;
        }

E assim, o operador é entendido como uma função, sendo a sobrecarga um processo de definição da operação a ser executada. Recebe esse nome porque todo operador já existe e a definição de uma nova funcionalidade apenas adiciona (sobrecarrega) as habilidades anteriores do operador. Embora isto seja comum, é bom lembrar que operações de tipos primitivos não poderão ser modificadas, restando apenas a funcionalidade de criar operadores para nossos tipos (classes).

Os argumentos[editar | editar código-fonte]

Agora vejamos como os argumentos são vistos pelo compilador durante a chamada ao operador. Essa sintaxe, muitas vezes, confunde iniciantes, mas é bastante intuitiva. Veja:

c = a + b;

Consideremos que a,b,c são pontos. Em termos gerais, qualquer operador binário (com dois argumentos) definido em escopo global, receberá a e b como primeiro e segundo argumento da função que define o operador.

Podemos ver a chamada da seguinte forma:

 c = operator+( a, b );

No momento da operação, o compilador montará a chamada do operador "+" da forma que escrevemos acima, ou seja como uma função que recebe os pontos "a" e "b" como argumentos e retorna o resultado, como operado na declaração anterior, no ponto "c". De fato, "c" será ( a.x + b.x, a.y + b.y ) como foi operado na declaração da função ( operator+ ) discutida no tópico anterior.

Operadores aritméticos[editar | editar código-fonte]

Operadores aritméticos são utilizados para efetuar operações matemáticas entre dados. São 5 operadores aritméticos em C++:

#include <iostream>
using namespace std;

int main() {

int soma = 5 + 5; // o operador '+' realiza somas.
double subtracao = 5 - 5; // o operador '-' efetua subtração.
float multiplicacao = 5.1 * 0.5; // o operador '*' efetua multiplicação.
char divisao = 100 / 2; // o operador '/' efetua divisão.
int modulo = 51 % 5; // retorna o resto da divisão inteira.

cout << "Resultados: " << soma << ", " << subtracao << ", " << multiplicacao << ", "
     << divisao << ", " << modulo << endl;

}

A saída desse programa gera no console o seguinte:

Resultados: 10, 0, 2.55, 2, 1.

O quarto resultado é '2' pois 50 é o código decimal deste caracter.

Tipo de retorno[editar | editar código-fonte]

Você pode realizar operações aritméticas, obviamente, entre números. Como dito no tópico anterior, você também pode realizar operações aritméticas com os tipos char e wchar_t.

O retorno da operação será também um número (real, inteiro ou até mesmo um caracter, conforme os tipos dos operandos).

Divisão inteira e divisão real[editar | editar código-fonte]

Existe, para a linguagem, diferença entre uma divisão entre números inteiros e entre números reais (ponto flutuante). Se você fizer a divisão entre os inteiros 3 e 2, o resultado não será 1.5, será 1. Já se fizer a divisão entre os números reais (em ponto flutuante) dos deles, então sim obterá 1.5.

O motivo é que há 2 tipos de divisão: a inteira e a decimal.

Divisão inteira e o operador módulo[editar | editar código-fonte]

A divisão inteira retorna o quociente da divisão sem a parte fracionária. Isso ocorre porque a linguagem efetua a divisão enquanto o resto for maior que o divisor (logo, a divisão nunca apresentará parte fracionária).

Para obter o resto da divisão, você pode usar o operador módulo (%). Esse operador retorna, em vez do quociente, o resto da divisão inteira. É por isso que no nosso exemplo 51 % 5 resultou em 1, pois 5x10 + 1 = 51, onde 5 é o divisor, 10 é o quociente, 1 é o resto e 51 o dividendo.

Divisão real[editar | editar código-fonte]

A divisão real é aquela efetuada entre tipos ponto flutuante ou entre ponto flutuante e inteiros/caracteres. Isso efetuará a divisão até que o resto seja zero, ou quando o resto repetir-se indefinidamente (no caso de dízimas periódicas como, por exemplo, 10/3).

Se quisermos que a divisão entre inteiros retorne a divisão real, deveremos efetuar uma conversão explícita, conforme o exemplo:

int num = 3;
int num2 = 2;

cout << "Resultado: " << (float) num/num2 << endl;
// o resultado foi convertido para ponto flutuante explicitamente.

Aritméticos com atribuição[editar | editar código-fonte]

Temos, inspirados pelas operações de processador, operadores que efetuam as operações e em seguida atribuem o valor das operações a uma das variáveis participantes do processo. Temos os operadores "+=", "-=", "*=", "/=" e "%=" que basicamente funcionam da seguinte maneira:

int a,b;

a = 2; 
b = 3;
a += 3;

cout << a << endl; // Mostrará o valor da soma de "a" e "b" antes da operação, neste caso: "5";

a = 8;
b = 5;
a -= b;

cout << a << endl; // Mostrará o valor da subtração de "a" e "b" antes da operação, neste caso: "3";

a = 4;
b = 7;
a *= b;

cout << a << endl; // Mostrará o valor da multiplicação de "a" e "b" antes da operação, neste caso: "28";

a = 30;
b = 3;
a /= b;

cout << a << endl; // Mostrará o valor da divisão de "a" e "b" antes da operação, neste caso: "10";

a = 28;
b = 5;
a %= b;

cout << a << endl; // Mostrará o resto da divisão de "a" e "b" antes da operação, neste caso: "3";

Observemos o que há em comum nas operações acima, em todos os exemplos temos duas variáveis "a" e "b" e um operador aritmético com atribuição. O que efetua-se em todos os exemplos é uma operação aritmética com uma atribuição final do valor calculado a uma das variáveis, que neste caso é o "a". A sintaxe é simples de entender, sempre o valor de "a" é operado com o valor de "b" e depois o valor do resultado é depositado em "a".

Operadores lógicos bit a bit[editar | editar código-fonte]

Existem operadores que nos permitem fazer operações lógicas, os mais básicos são os operadores "&" , "|" , "^" e "~", que são respectivamente correspondentes às operações lógicas "E", "OU", "OU exclusivo" e "Não bit a bit". Vejamos como podemos operar números usando-os:

Se tivermos que operar os números hexadecimais: "1A2C" e "2B34" podemos fazê-lo da seguinte forma:

int Va = 0x1A2C;
int Vb = 0x2B34;

int Vc = Va & Vb; // temos Va = 0001 1010 0010 1100 e Vb = 0010 1011 0011 0100
                  // Façamos Va = 0001 1010 0010 1100
                  //         Vb = 0010 1011 0011 0100
                  //    Va & Vb = 0000 1010 0010 0100 logo 0A24

cout << hex << Vc << endl;  // Isto mostrará na saída: "a24" que corresponde a operação "E" dos dois números.

Vc = Va | Vb;     // Executaremos Va "OU" Vb
                  //    Va | Vb = 0011 1011 0011 1100 logo 3B3C

cout << hex << Vc << endl;  // Isto mostrará na saída: "3b3c" que corresponde a operação "OU" dos dois números.

Vc = Va ^ Vb;     // Executaremos Va "OU exclusivo" Vb
                  //    Va ^ Vb = 0011 0001 0001 1000 logo 3118

cout << hex << Vc << endl;  // Isto mostrará na saída: "3118" que corresponde a operação "OU exclusivo" dos dois números.

Vc = ~Va;     // Executaremos "Não bit a bit" ou "complemento" de Va
                  //    Va ^ Vb = 1110 0101 1101 0011 logo E5D3

cout << hex << (Vc & 0xFFFF) << endl;  // Isto mostrará na saída: "e5d3" que corresponde a operação "complemento" de Va.

Observe que convertemos, nos comentários, os números para binário a fim de facilitar a visualização de cada operação feita bit a bit. De forma simples o resultado é obtido e convertemos na saída o número para o formato hexadecimal, apenas para facilitar a visualização.

Devemos observar o detalhe da última operação com mais critério: Vejam que quando fomos mostrar o resultado da mesma tivemos que efetuar a operação (Vc & 0xFFFF). Isto se deve ao fato de que ao operar o número armado em "Va" o compilador poderá ver o número 1A2C como 00001A2C, ou 0000000000001A2C, ou outros valores, dependendo do tamanho do tipo int gerado pela máquina a qual se destina a compilação. Neste caso o resultado da operação seria acrescida de FFFF no início do número do resultado. Portanto se fizermos a operação (Vc & 0xFFFF) eliminamos os números FFFF ou FFFFFFFFFFFF do início do número resultante.

A estes operadores lógicos bit a bit podemos ainda adicionar os operadores compostos da linguagem "C": "&=", "|=" e "^=" que são, respectivamente: "E" com atribuição, "Ou" com atribuição e "Ou exclusivo" com atribuição. A operação dos três operadores tem em comum o fato de funcionarem de forma semelhante. Primeiro se faz a operação "E", "Ou" ou "Ou exclusivo" entre a variável à esquerda e o número ou variável à direita, depois se atribui o resultado da operação à variável da esquerda. Vejamos os exemplos abaixo:

int Va = 0x1A2C;
int Vb = 0x2B34;

Va &= Vb;
cout << hex << Va << endl;  // Isto mostrará na saída: "a24" que corresponde a operação "E" dos dois números.

Va = 0x1A2C;
Va |= Vb;
cout << hex << Va << endl;  // Isto mostrará na saída: "3b3c" que corresponde a operação "OU" dos dois números.

Va = 0x1A2C;
Va ^= Vb;
cout << hex << Va << endl;  // Isto mostrará na saída: "3118" que corresponde a operação "OU exclusivo" dos dois números.

Notemos que os resultados são os mesmos que obtivemos quando operamos com os operadores sem atribuição, mostrados anteriormente. A diferença no modo de operação está no fato de que, ao operarmos duas variáveis, a primeira sempre recebe o resultado da operação. Este comportamento, na verdade, imita o modo de operação de alguns registradores dentro dos microprocessadores. Certamente o compilador aproveitará esta capacidade, dependendo do processador, para otimizar o código de máquina gerado durante o processo de compilação.

Operadores comparativos[editar | editar código-fonte]

Para efetuarmos comparações dentro do código dos programas precisamos de operadores que as efetuem, nas linguagens "C" e "C++" temos os operadores: "<" (menor que), ">" (maior que), "==" (igual a), "<=" (menor ou igual a), ">=" (maior ou igual a), "!=" (diferente de) que retornam o valor booleano, ou seja verdadeiro ou falso. Além destes comparativos podemos efetuar operações lógicas entre duas variáveis ou valores com os operadores lógicos comparativos "&&" (E), "||" (Ou), que retornam valor booleano, e podemos comparar com o complemento lógico de um valor ou variável usando o "!" (Não).

Todos estes operadores funcionam em conjunto com as operações condicionais, como o "if" por exemplo. Vejamos alguns exemplos:

#include <iostream>

using namespace std;

int main()
{
 int a,b;

 cout << "Digite um valor para a: ";
 cin >> a;

 cout << "Digite um valor para b: ";
 cin >> b;

 if ( a < b ) cout << "a é menor que b." << endl;

 if ( a > b ) cout << "a é maior que b." << endl;

 if ( a == b ) cout << "a é igual a b." << endl;

 if ( a <= b ) cout << "a é menor ou igual a b." << endl;

 if ( a >= b ) cout << "a é maior ou igual a b." << endl;

 if ( a != b ) cout << "a é diferente de b." << endl;

 return 0;
}

Neste simples programa, usamos os operadores comparativos básicos. Nele, o usuário poderá digitar dois números e depois o programa informará os comparativos de magnitude dos dois números, embora os faça de maneira bastante simples, o usuário terá condições de saber se os números são iguais ou diferentes e se um é maior ou menor que o outro.

Se quisermos aninhar condições, criando decisões mais complexas, podemos usar os operadores "&&", "||" e "!". Com esses operadores podemos combinar duas ou mais condições, por exemplo: Se quisermos estabelecer uma condição onde um número "b" está entre um valor "a" e outro valor "c" podemos fazer:

#include <iostream>

using namespace std;

int main()
{
 int c;

 cout << "Digite um valor entre 5 e 14: ";
 cin >> c;

 if ( (c < 14) && (c > 5) )
   cout << "O valor está dentro do limite esperado." << endl;

 return 0;
}

Neste simples exemplo, se o usuário digitar um número dentro do limite entre 5 e 14 o programa emitirá a mensagem, caso contrário não. Note que precisamos condicionar o número a estar abaixo de 14 "e" acima de 5, assim estabelecemos a faixa onde o programa reconhece como correta. A operação "(c < 14)" retorna verdadeiro quando c é menor que 14 e a operação "(c > 5)" retorna verdadeiro quando c é maior que 5, de fato, se tivermos verdadeiro nas duas operações o operador "&&" retorna verdadeiro, se qualquer das duas operações retornar falso o operador "&&" retorna falso.

Demais operadores[editar | editar código-fonte]

Os demais operadores da linguagem C++ desempenham diversas funções, o que impossibilita agruparmos eles em grupos específicos. Vejamos o que cada um faz e assim teremos como usá-los nos exemplos logo abaixo, o que nos possibilitará ter um breve contato com o uso dos mesmos.

O operador =[editar | editar código-fonte]

O operador "=" é conhecido como operador de atribuição, como sua função em matemática é bem conhecido seu uso é bem intuitivo, apenas atribui um valor ou o conteúdo de uma variável no lado direito a uma variável do seu lado esquerdo. Acreditamos que este não necessita de exemplo, já que foi usado nos exemplos anteriores.

O operador [][editar | editar código-fonte]

O operador "[]" usa-se "a[n]" é conhecido como operador de arranjo ou acesso a elemento de vetor. Este é bem mais específico das linguagens de programação baseadas na linguagem "C" e devemos analisar melhor o seu funcionamento. Digamos que temos que criar um conjunto de valores em forma de arranjo, ou "array", designado pela letra "a", então temos a1, a2, a3, ... an. Neste caso devemos declará-lo da seguinte forma:

 int a[5];

Isto significa que alocamos 5 posições de memória de tipo "int" para ser usadas como arranjo, cujo nome é "a". Veja que a função de "[]" é de alocar a quantidade de 5 elementos para serem usados no arranjo. Porém, este mesmo operador tem outra função, que é de acesso aos valores dentro do arranjo, vejamos o próximo exemplo:

 b = a[3];

Esta linha de código mostra como podemos atribuir à variável "b" o valor contido na quarta posição do vetor que declaramos anteriormente. Observe que apesar de termos colocado a[3] obtemos o quarto valor pois a posição a1 está atribuída ao elemento a[0], assim, se quisermos acessar o elemento a4, devemos acessar o elemento a[3].

O opeador ()[editar | editar código-fonte]

O operador "()" - parênteses também tem duas funções, embora que bem distintas. A primeira e a de agrupar operações matemáticas, como podemos ver no exemplo logo a seguir:

 c = ( a + b ) * 10;

Nesta simples operação agrupamos a operação "a + b" para que seja efetuada antes da operação de multiplicação, trata-se do uso trivial da matemática, funciona da mesma forma: adiciona-se o que está entre parênteses antes de efetuar a multiplicação declarada logo após os mesmos. Os parênteses podem ser combinados de diversas formas, sempre seguindo o uso análogo dos agrupadores matemáticos, a diferença é que não fazemos, como na matemática, o uso dos colchetes "[]" e das chaves "{}" como agrupadores. Para estas funções usamos sempre parênteses, não importando se já existam parênteses dentro de um certo agrupamento. Vejamos outro exemplo:

 e = ((( a + b ) * 10 - 1)*c - d)*8 - 3;

Em matemática faríamos:

Observe que, na linguagem "C" ou "C++", podemos sempre adicionar um parêntese para agrupar, não precisamos passar de parêntese para colchetes e depois para chaves, na verdade não podemos fazer isso, pois estes são operadores com outras funções nas linguagens de programação.

A outra função do operador "()" é a chamada e declaração de função, além da participação em outros elementos de linguagem como laços, operações lógicas e switch. Vejamos alguns exemplos:

void myswitch(int a)
{
 while (a < 4)
 { switch(a)
   { 
     case 0: load();
        break;
     case 1: configure();
        break;
     case 2: execute();
        break;
     case 3: unload();
        break;   
   }
   process_next();
 }
}

Observemos que o operador "()" é bastante usado nas linguagens "C", "C++" e derivadas... Destacamos neste exemplo a declaração da função "myswitch", o uso no laço "while" e no comando "switch", além do uso na chamada de funções "load", "configure", "execute" e "unload". Seu uso é bastante extenso e poderá ser bem explorado no decorrer da leitura deste livro.

O operador () ao preceder uma variável ou valor pode agir como agente de "typecasting", ou seja, forçar que o compilador entenda que o tipo de dado é o apresentado entre parênteses. Vejamos o exemplo:

 void func( int x );
 ...
 float a;
 ...
 func( (int)a );

Neste trecho de código vemos que a função espera uma variável inteira como argumento de entrada, porém o programador quer passar uma variável tipo "float" ( que em uma máquina arbitrária qualquer tem o mesmo tamanho de um inteiro) para a função e para isto ele usa o "typecasting" na chamada da função, com isto o compilador ignora a verificação de tipo na entrada da função e aceita a variável como se ela fosse inteira.

Outro uso interessante de uso do operador "()" é a conversão de tipo, faz-se "tipo(a)" para transformar uma variável de um tipo específico para outro. Observe que isto é um pouco diferente de fazer (tipo)a que não transforma nenhum tipo em outro, mas sim fazer como no exemplo abaixo:

 void func( char y );
 ...
 int a = 'v';
 char x;
 x = char(a);
 ...
 func(char(a) );

Neste caso a variável "a" será convertida em um inteiro. Imaginem uma máquina em que o tipo "char" tem 8 bits e tipo "int" tem 32 bits, então o valor inteiro será transformado de 32 bits para 8 bits. Isto se torna mais relevante na chamada da função, quando só existem 8 bits na entrada da mesma, e por isso, a chamada de "char(a)" transforma o inteiro em um caractere.

O opeador *a[editar | editar código-fonte]

Como já vimos anteriormente o operador "*" é um dos operadores aritméticos, o da multiplicação, mas podemos usá-lo com outra função, como declarador de ponteiro e acesso a valor apontado por ponteiro. Primeiramente se declaramos algo como:

 int *a;

Neste caso estamos declarando um ponteiro para um valor inteiro armazenado na memória. Significa dizer que se quisermos colocar um endereço de memória na variável "a" poderemos fazê-lo, desde que o endereço pertença a um valor inteiro, assim, poderemos manipulá-lo quando quisermos. Isso nos indica a necessidade de uma segunda função ao operador "*" quando usado com ponteiros, a de apontar para o valor armazenado na memória cujo endereço está em "a", vejamos:

 int *a = 0x3B12242C58E50023;
 *a = 5 + 8;
 cout << *a;

Imaginemos que, numa máquina hipotética, tenhamos a sequência de código acima, então no endereço 3B12242C58E50023, será armazenado o valor 13, resultado da operação acima, logo após será mostrado este valor na saída do dispositivo. Porém, devemos nos atentar a uma particularidade das linguagens baseadas em "C"... na primeira linha, quando fizemos: "int *a = 0x3B12242C58E50023;" estamos declarando a variável ponteiro "a" e, só neste caso, o número que colocamos se refere ao endereço de memória para o qual ela aponta, nos demais casos que se seguem, quando fazemos "*a = " estamos colocando valores dentro da memória apontada e quando fazemos "*a" estamos nos referindo ao valor armazenado na memória apontada e não em "a". Parece um pouco confuso, não é? Mas tudo isso será abordado em mais detalhes na seção onde se aborda ponteiros e mais ainda no livro "Programar em C". Na verdade, para melhor entendimento, recomendamos que o leitor inicie o estudo de ponteiros pelo livro da linguagem "C", certamente o entendimento será muito melhor.

O opeador &a[editar | editar código-fonte]

Podemos chamar este operador de "endereço de" ou "referência a", quando o operamos junto com ponteiro podemos extrair o endereço de uma variável e, assim, colocá-lo no ponteiro. Sempre que quiseremos atribuir a um ponteiro um endereço de uma variável podemos usar o operador "&" como vemos no exemplo abaixo:

 int x = 23; 
 ...
 int *a;
 ...
 a = &x;
 cout << "O valor apontado pelo ponteiro a é " << *a <<endl;

Como resultado deste trecho de código teremos na saída padrão: "O valor apontado pelo ponteiro a é 23". Note que quando fazemos "a = &x" estamos colocando o endereço da variável "x" no ponteiro "a", assim, quando fazemos "*a" acessamos o valor armazenado pela variável, que neste caso é "23".

Temos outro uso do operador "&" na linguagem C++ que é dar referência de uma variável a outra. Em termos gerais funciona como dar um apelídio a um símbolo de forma que este passe a operar o conteúdo do outro. Vejamos um exemplo:

 int a;
 int &b = a;

 a = 2;
 cout << "O valor armazenado em a é: " << b << endl;

Assim, o valor armazenado em "a" será o mesmo ao qual nos referimos quando acessamos "b", podemos usar tanto "a" como "b" para operar valores na variável em qualquer parte do programa pois eles se referem a uma só variável com dois nomes diferentes.

Quando declaramos uma função e usamos, na declaração dos argumentos, o operador "&" passamos a operar os argumentos por chamada de referência, ou seja, se alterarmos o valor do argumento dentro do escopo da função também alteraremos o valor da variável que foi passada na chamada da função, ou seja, escopo acima. Vejamos um exemplo:

 void func(int &x)
     {
       ...
       x = 8;
       ...
     }

 int a = 3;
 func(a);
 cout << "O valor da variável é: "<< a << endl;

Observe que o valor mostrado na saída padrão será "8" pois a função "func" recebe a variável "x" como referência de uma variável externa ao seu corpo, desta forma, quando se chama a função "func(a)" temos "a" e "x" como nomes de uma mesma variável e dentro da função, esta variável é alterada para o valor "8".

O opeador ::[editar | editar código-fonte]

Este operador é característico da linguagem C++ e apresenta uma função pertencente à orientação a objeto, refere-se a característica de declarar e se referir a elementos de outros escopos que não pertencem ao escopo atual. Por exemplo, quando queremos criar uma classe e não desejamos colocar o código das funções membro dentro da declaração da mesma utilizamos o operador de resolução de escopo, veja o código a seguir:

class top{

       int a, b;
       public:
              int c;
              void print();
              int get_a();
              int get_b();
};

void top::print()
{  cout << "Valores armazenados: " << a <<"; "<< b <<"; "<< c << "; " << endl;
}

int top::get_a()
{  return a;
}

int top::get_b()
{  return b;
}

Como podemos ver as funções membro "print()", "get_a()" e "get_b()" foram declaradas fora do escopo da classe, e para isso temos que adicionar o nome da classe seguido do operador "::" imediatamente antes do nome da função, esta sintaxe faz com que indiquemos ao compilador que a função que está sendo escrita pertence a classe "top", que propositalmente foi denominada desta forma para melhor entendimento. A classe topo, abriga os elementos: "top::a", "top::b", "top::c", "top::print()", "top::get_a()", "top::get_b()" e assim eles podem ser referenciados fora do corpo da mesma desde que obedecendo as regras da linguagem.