Documente Academic
Documente Profesional
Documente Cultură
C++
Básico
Sumário
O computador é uma ferramenta poderosa que possui as mais variadas aplicações no mundo moderno.
Encontramos sistemas controlados por computadores em quase todas as áreas do conhecimento humano,
como telecomunicações, indústrias, internet, medicina, etc. Entretanto, por mais potentes e flexíveis que
sejam os computadores, eles nada fariam sem que fossem orientados por listas de instruções detalhadas,
interpretadas e executadas pelo processador: os programas.
Tudo que é físido em um computador é chamado hardware, e tudo que ele entende são dois estados de
tensão que chamaremos, simbolicamente, de 0 e 1 (esses valores foram adotados somente para facilitar o
entendimento). Todos os processadores possuem a sua linguagem de máquina (ou código binário), um
conjunto de sequências de “zeros” e “uns” que produzem determinadas ações. É possível escrever
programas em linguagem de máquina, mas seria uma tarefa improdutiva porque para que o processador
execute tarefas comuns é necessário escrever muito código. Com o aumento da necessidade de
programas, criaram-se as linguagens de programação, códigos intermediários entre a linguagem humana
e a da máquina. Assim surgiram as linguagens Assembly, ADA, PL/I, CPL, BCPL, B, C, C++, Pascal, e
muitas outras. As linguagens de programação podem estar mais próximas da linguagem de máquina ou da
humana. Quanto mais próxima da linguagem humana, maior o nível de uma linguagem de programação.
A linguagens sofisticadas para ambientes visuais como Visual Basic e Delphi são de altíssimo nível por
estarem muito longe do código binário. Os programas de computador também podem ser chamados de
softwares.
Para que o computador interprete os programas escritos em uma linguagem de programação, eles devem
ser traduzidos em linguagem de máquina. Isso pode ser feito por compilação ou interpretação. A
linguagem é compilada quando o programa é traduzido inteiramente para a linguagem de máquina antes
da execução, e interpretada quando a tradução é feita linha por linha durante a execução. Linguagens
interpretadas tendem a ser mais lentas porque a tradução durante a execução representa um trabalho extra.
A linguagem C foi desenvolvida nos Laboratórios Bell por Dennis Ritchie em 1972. Sua origem está
muito ligada ao sistema operacional UNIX, escrito inicialmente em 1969 em linguagem montadora.
Para entendermos como a linguagem C surgiu, devemos retroceder para o Algol 60, projetado em 1960
por um Comitê Internacional. Essa linguagem possuía objetivos elevados para os computadores da época
e por isso nunca foi muito utilizada. Para trazer o Algol 60 mais próxmo da realidade, surgiu tanto em
Londres quanto em Cambridge em 1963 a linguagem CPL, que também fracassou por ser muito difícil.
Por isso Martim Richards criou em 1967 a linguagem BCPL, uma versão mais simples do CPL. Logo
depois, Ken Tompson desenvolveu linguagem B, ainda mais simples que a BCPL, para reescrever o
sofisticado sistema UNIX. Ainda havia uma problema: B e BCPL tornaram-se linguagens limitadas, úteis
apenas para alguns tipos de problemas.
Devido a esses e outros contratempos, como imcompatibilidade com alguns tipos de hardware, Dennis
Ritchie desenvolveu a sucessora de B, chamada de C.
Com a nova linguagem, um mundo rico em possibilidades foi aberto aos novos programadores de
sistemas. C permitia o contato íntimo com a máquina enquanto possibilitava implementar instruções de
alto nível nos mesmos programas, ou seja: uma linguagem de baixo e alto nível simultaneamente.
Características da linguagem C
C pode ser considerada a principal linguagem orientada para máquinas. Diversamente de linguagens
criadas para aplicações específicas, como FORTRAN, que são orientadas para problemas, C possui
flexibilidade suficiente para implementar soluções para os mais variados tipos de problemas nos mais
diversos hardwares.
As linguagens de alto nível permitiram que o programador se concentrasse no problema que tinha que
resolver, deixando a manipulação da máquina para o compilador ou interpretador. Para a engenharia ou
administração, são excelentes. No entanto, os programadores que desenvolviam sistemas ainda
dependiam das indigestas linguagens montadoras de suas máquinas. Para essa classe, C foi a solução
definitiva em termos de produtividade e eficiência: está distante o suficiente da máquina para
implementar rotinas de alto nível, e próxima o suficiente para especificar detalhes necessários para o
sistema operacional. Talvez por isso a popularidade de C seja tão grande.
Confie no programador.
Não impeça que o programador faça o que precisa ser feito.
Mantenha a linguagem pequena e simples.
A Linguagem C++
Em 1980, o Dr. Bjarne Stroustrup desenvolveu a sucessora de C, a linguagem C++, que possui a base na
linguagem C com idéias do Algol 68 e Simula67: operadores podem ser sobrecarregados e variáveis
podem ser declaradas em qualquer ponto dentro do bloco de instruções como no Algol 68; objetos podem
ser criados com funções virtuais e classes derivadas como no Simula67.
Quando foi necessário realizar algumas simulações orientadas a eventos, muito rigorosas e com conceitos
inexistentes nas linguagens de programação da época como objetos e eventos, e exigindo eficiência que
nenhuma linguagem existente era capaz de fornecer, nem mesmo C, um grupo liderado pelo Dr.
Stroustrup começou a trabalhar na implementação de uma nova linguagem, versátil e poderosa, mas que
deveria manter a compatibilidade com programas e bibliotecas escritos em C. Devido ao alcance absoluto
desse objetivo, a linguagem que nascia foi considerada sucessora de C e chamada C++.
Para que possamos ter idéia do que foi modificado em relação à linguagem C, citamos as principais
melhorias de C++:
1. Estruturas passam a ser de tipo nome, não exigindo o uso da palavra reservada na declaração;
2. Declarações de variáveis em qualquer lugar do bloco, desde que antes da utilização;
3. Operador resolução de escopo, permitindo variáveis locais de globais de mesmo nome;
4. Especificador const, bloqueando o valor de variáveis;
5. Uniões anônimas, recurso que economiza tempo e memória;
6. Conversões explícitas, que diminuem os efeitos colaterais de uma linguagem fracamente figurada;
7. Modelagem de função, especificando no cabeçalho da função os tipos de cada argumento;
8. Sobrecarga de função, permitindo que mais de uma função com o mesmo nome, desde que
tenham tipo de retorno e/ou argumentos de tipos diferentes;
9. Funções com número de argumentos indefinido;
10. Tipo referência, facilitando o entendimento e a sintaxe;
11. Especificador inline, deixando que o compilador decida se pode inserir o código de um função no
código em linha, economizando o tempo de chamada e armazenamento na pilha;
12. Operadores new e delete, melhorando a alocação dinâmida de memória;
13. Ponteiros void e funções tipo void, aumentando o poder dos ponteiros e formalizando funções
void;
14. Programação orientada ao objeto;
15. Biblioteca stream, melhorando sensivelmente as operações de I/O.
Fundamentos de C++
Todos os programas devem receber dados de alguma fonte de entrada; processar as informações recebidas
por meio de instruções que podem ser divididas genericamente em: simples, condicionais, laços e
funções; expressar os resultados da manipulação dos dados de entrada; possuir uma estrutura modular e
nomes autodocumentados (com significado) para facilitar a manutenção e a leitura posterior feita por
outros programadores.
Um programa C++ é constituído de funções, que são blocos de código cujas instruções executam ações de
rotina. Também existem variáveis que são locais da memória RAM onde são armazenadas informações.
Outros itens estarão presentes em um programa C++ e serão introduzidos oportunamente na compreensão
da linguagem. Vamos escrever nosso primeiro programa C++, que imprime uma mensagem na tela:
#include<iostream.h>
void main(void){
cout << "\nMeu primeiro programa!";
return;
}
// Fim do programa
Todo programa deve possuir comentários significativos para facilitar o acompanhamento de outros
programadores ou para manutenção posterior, que pode ocorrer muitos anos depois da criação do
programa. Esse comentário explica o que é o programa.
#include<iostream.h>
Essa instrução é uma diretiva do pré-processador que inclui o arquivo IOSTREAM.H no código do
programa. Esse arquivo é um dos vários arquivos de cabeçalho da linguagem C++, que são bibliotecas
com rotinas implementadas pelos programadores da linguagem.
Esse bloco de código é uma função C++. O símbolo { abre o código e } fecha o mesmo. Esses símbolos
também são utilizados para laços e estruturas de controle, que veremos mais adiante. A palavra void no
início da linha representa o tipo de retorno da função. Nesse caso a função não retorna nenhum valor, fato
evidenciado pela palavra void. A palavra main é o identificador (nome) da função. Todos os programas
C++ possuem uma única função main e a execução do programa começa sempre a partir dessa função. A
palavra entra parênteses void representa os argumentos da função. Nesse caso, a função não tem nenhum
argumento, o que é indicado pela mesma palavra void. Entre os delimitadores { e } do bloco, podem
existir instruções que são chamadas corpo da função:
Essa linha invoca a stream cout (console out) definida em IOSTREAM.H, que imprime o argumento
“Meu primeiro programa!” na tela. Merece atenção a presença da sequência \n que significa nova linha.
C e C++ possuem uma série de caracteres especiais que são formados por barra invertida (\) seguido de
uma letra ou símbolo. Eis os mais comuns:
\a Beep
\n nova linha
\t tabulação horizontal
\v tabulação vertical
\r retorno de carro
\b retrocesso (backspace)
\’ aspas simples
\” aspas duplas
\\ barra invertida
\f salta uma página
\0 nulo (NULL)
return;
Retorna da função para o processo anterior, no caso o sistema operacional. Essa instrução pode ser
dispensada nesse caso sem nenhum aviso do compilador.
// Fim do programa
O compilador C++ ignorará espaços repetidos, tabulações ou linhas em branco em quase todos os pontos
do programa. Portanto, o mesmo programa anterior pode ser escrito de várias formas, como por exemplo:
Esse programa é igualmente válido. Devemos observar que na diretiva do pré-processador não podem
haver espaços entre os símbolos < > e o nome do arquivo de cabeçalho IOSTREAM.H pode ser escrito
em maiúsculas. Todas as instruções C++ são terminadas em ponto-e-vírgula. Outra característica
marcante na sintaxe C e C++ é a diferença entre letras minúculas e maiúsculas. Em muitas linguagens,
como Pascal, não existe nenhuma diferença, mas em C++ isso é sentido pelo compilador.
Vamos agora analisar um segundo programa C++. O código apresenta declarações de variáveis e a função
adicao, cuja tarefa é calcular o valor da soma de dois números passados como parâmetros. As explicações
estão inseridas em forma de comentário:
// passando "n" e "m" como parâmetros para que a função soma(int, int)
// calcule a soma dos dois valores e retorne o resultado que é atribuído
// à variável soma
soma = adicao (n, m);
// Fim do programa
O próximo programa melhora a interface com o usuário solicitando os valores das variáveis n e m para
que a função adicao calcule a soma dos dois valores:
int resultado;
// passando "n" e "m" como parâmetros para que a função soma(int, int)
// calcule a soma dos dois valores e retorne o resultado que é atribuído
// à variável soma
soma = adicao (n, m);
// Fim do programa
Nos programas acima, vimos como é a sintaxe de declaração de funções e variáveis, como passar
parâmetros para uma função e como retornar valores para o processo anterior que chamou a função.
Depois disso, provavelmente será possível reconhecer um programa C++ se alguém nos apresentar um.
Passaremos agora a detalhar os aspectos específicos da linguagem C++.
Dados padrões
Todo programa C++ deve ter pelo menos a função main( ), e podem também conter dados que podem ser
variáveis ou constantes.
Todos os dados e funções C++ são referenciados por identificadores (nomes). Os identificadores não
podem começar com dígitos numéricos porque assim o compilador não poderia separar tais
identificadores de constantes.Eis a descrição suscinta dos tipos de dados padronizados C++:
char: utilizado para armazenar caractares alfanuméricos, o tipo char ocupa um byte na memória. Devido
ao fato de C++ ser fracamente figurada, um dado char pode ser visto como número ou caractere,
dependendo do desejo do programador na aplicação específica. Seu valor padrão pode variar de –128 a
127.
int: ocupa no modelo padrão uma palavra (nas máquinas da plataforma PC, isso corresponde a dois
bytes) na memória e seu valor padrão pode variar entre –32768 a 32767. Pode armazenar números
inteiros ou pode ser visto como caractere, dependendo da aplicação, mas o valor do segundo byte não é
considerado nesse caso.
float: pode armazenar valores em ponto flutuante e correspondem aos números reais. Ocupam duas
palavras na memória e seu valor padrão pode variar entre 3,4x10-38 até 3,4x10+38.
double: armazena valores em ponto flutuante de precisão dupla. Ocupam quatro palavras na memória e
seu valor padrão pode variar entre 1,7x10-308 e 1,7x+308.
enum: tipo de dado associado a um conjunto de constantes inteiras chamadas de conjunto de enumeração
(enumeration set), que possuem valor presumido mas que podem ser especificados pelo programador. A
sintaxe é:
enum semana {domingo, segunda, terça, quarta, quinta, sexta, sábado} dias;
A declaração acima cria o tipo enum semana e a variável dias, do tipo semana. A constante domingo tem
valor 0 e as seguintes têm o valor da anterior mais um.
void: tipo vazio para retorno e argumentos de funções. Seu uso é importante para ponteiros, definindo
ponteiros genéricos. Isso será visto quando tratarmos de dados dinâmicos.
Dados padrão podem ser iniciados com algum valor quando são declarados, como na declaração:
int inteiro = 7;
Se o programador esquecer de iniciar a variável na declaração ou fora dela, seu valor será desconhecido
até que lhe seja atribuído algum valor.
Os dados variáveis, em suma, são os que podem estar no lado esquerdo de uma instrução de atribuição,
ou em outros termos, são lvalues. Quaisquer outras expressões não são lvalues (localizador de objeto)
Pode-se conhecer o tamanho de qualquer tipo de dado C++ utilizando-se o operador sizeof que retorna o
tamanho em bytes do operando. Por exemplo: sizeof(int) retorna 2, que é o tamanho em bytes do tipo int.
typedef
Além dos dados padronizados, podem ser definidos outros tipos de dados com a palavra reservada
typedef:
Com essa definição, poderemos declarar um dado inteiro a como int a ou inteiro. Já que inteiro foi
definido como sendo equivalente ao tipo int. Na maioria dos casos, typedef é utilizado para conferir
significado aos tipos criados pelo usuário e facilitar a sintaxe, nos poucos casos restantes é uma
necessidade para que a linguagem suporte certas declarações ou passagem de argumentos para funções.
Estruturas de dados
Podem ser definidos tipos de dados heterogêneos, ou estruturas de dados, formados por combinações de
tipos válidos, padrões ou não. Existem três tipos de estruturas de dados em C++:
struct: a estrutura segue o conceito de registro: dados relacionados logicamente referenciados por um
único nome, e cada dado representa um campo. Considere um número complexo com a parte real e
imaginária. As duas partes podem ser referenciadas por um só nome, desde que declarada uma estrutura,
como por exemplo:
struct complexo {
double real;
double imag;
};
class: tipo de dado concebido para a criação de objetos e pode ser entendida com a uma evolução de
struct. As funções de uma classe são conhecidas como métodos. A classe abaixo possui dados (campos
ou instâncias) e métodos:
class exemplo{
int n,u;
float t;
public:
char letra;
};
int exemplo::metodo1(void){
return(0);
}
As instâncias e métodos de uma classe somente podem ser acessadas pelo programa diretamente se
especificadas com o modificador public. No exemplo, somente letra pode ser acessada fora da classe. Em
C++, tipos de dados struct também podem ter métodos, e suas instâncias são sempre presumidas
públicas. Para impedir o acesso de alguma instânca pelo programa, utilizamos o modificador private. A
palavra reservada this representa um ponteiro que aponta para o objeto atual.
Quando criamos uma class, não criamos um objeto, mas apenas um tipo de dado. Quando em algum lugar
do programa declaramos um dado do tipo de uma class definida, então criamos um objeto na memória.
Para que o objeto seja construído na memória, existe sempre uma função especial denominada construtor
do objeto e para dealocar o espaço reservado, o programa chama uma outra função, chamada destrutor. O
programador pode criar seus próprios construtores de destrutores, como veremos mais adiante.
union: um tipo interessante de estrutura de dados. Em uma union, todos os dados ocupam a mesma área
de memória, sendo que a memória ocupada pela union é igual ao tamanho do maior tipo de dado.
Consideremos a union abaixo:
union teste {
char caractere;
int inteiro;
float flutuante
double flutuante_duplo;
};
O maior tipo de dado é o campo flutuante_duplo, que ocupa 8 bytes. Na memória, esses campos estariam
de forma parecida com o esquema abaixo:
char
int float double
C45D:0780 C45D:0781 C45D:0782 C45D:0783 C45D:0784 C45D:0785 C45D:0786 C45D:0787
Em uma união, os campos estão no mesmo endereço, portanto se um deles tiver seu valor alterado, todos
podem sofrer a alteração.
Para acessar bits em um local de memória, C e C++ possuem uma forma de acessá-los com um tipo
especial de estrutura denominada campo de bits:
struct campo{
unsigned c1:1; // 1 bit
unsigned c2:1; // 1 bit
unsigned c3:4; // 4 bits
unsigned c4:2; // 2 bits
};
Os campos de bits em uma estrutura pode ocupar no máximo 16 bits. A configuração na memória seria
algo parecido com o esquema abaixo:
c4 c3 c2 c1
Se o número total de bits ocupado pelos campos de bits da estrutura for diferente de um múltiplo de 8, os
bits restantes até completar um múltiplo de 8 são “desperdiçados”, pois não será possível acessar os bits
restantes porque o processador só poderá endereçar bytes e não bits.
Modificadores de dados
Os tipos padrões char e int podem ter seus intervalos de valores alterados pelos modificadores signed e
unsigned. O modificador signed (sinalizado) é o estado padrão e não é necessário expressá-lo na
declaração. Já unsigned (sem sinal) deve ser explicitado para que o compilador saiba que o dado será
considerado sempre positivo. Nos dados signed o bit mais significativo representa o sinal, e quando
declaramos um dado unsigned o compilador utiliza todos os bits disponíveis para guardar a informação.
O efeito é a alteração dos intervalos de valores: unsigned int pode variar de 0 a 65535 e unsigned char
de 0 a 255.
O modificador long dobra o tamanho em bytes ocupado pelos tipos int e double. E short pode modificar
o tipo int, mas nenhum efeito ocorre na plataforma PC.
O resumo abaixo mostra o efeito nos intervalos e no tamanho em bytes dos modificadores de sinal e
tamanho nos tipos padrões C++:
Os dados podem ter seus valores inalteráveis pelo programa se modificados pela palavra reservada const,
e podem ter seu valor modificado por algum evento externo através do modificador volatile. Se um dado
variável é declarado normalmente como:
int a=0;
O valor contido na variável pode ser alterado pelo programa em uma instrução de atribuição qualquer,
mas se ele a variável for declarada com o modificador const:
O programador não poderá alterar o valor dessa variável, entretanto um processo externo pode se ela for
declarada como volatile, mesmo sendo especificado o modificador const:
Nesse caso, o compilador não presumirá qualquer valor para a constante, que pode estar sendo atualizada
por algum processo externo.
Conversões de dados
Quando escrevemos instruções que envolvem mais de um tipo de dado, eles são convertidos
automaticamente seguindo uma ordem hierárquica para que o compilador possa executar a instrução.
Essas operações são chamadas de modo misto. Essa necessidade de conversão existe porque tipos de
dados diferentes são armazenados de modos diferentes na memória. Os dados originais não são alterados,
pois é somente durante o cálculo da instrução que os tipos são alterados influenciando no resultado.
Consideremos a instrução abaixo:
Para executar a instrução de atribuição, o valor de termo2 é temporariamente convertido para float e o
valor de termo2 não é alterado. A hierarquia de conversão segue a sequência: double, float, long, int,
short.
Se um float é convertido para int a parte fracionária é perdida sem arredondamento, vejamos o exemplo:
Esse operação teria como resultado 22,62 se resultado fosse do tipo float ou double, mas como é uma
variável do tipo int a parte fracionária é perdida e resultado receberá o valor 22 após a instrução.
Em algumas instruções o resultado normal é indesejável e nesse caso os dados devem ser convertidos
com o operador cast, que modela o tipo de dado da melhor maneira para a aplicação:
Depois dessa instução, o valor de resultado é 5,7. Isso é estranho, já que os dados inteiros não exerceram
nenhuma influência. Isso ocorreu porque após a divisão termo2/termo3 o resultado deveria ser 0,75 mas
como os dois operandos são inteiros a parte fracionária é desprezada e o resultado da divisão é 0.
Para corrigir esse problema, C++ possui o operador de modelamento de dados (cast). Usando o operador
cast em um dos operandos inteiros, o resultado seria 4,275:
Quando o operador cast é aplicado em termo3, a divisão passa a considerar todos os dados como float,
conforme a hierarquia de conversão, por isso não é necessário modelar os dois operandos da operação.
Classes de armazenamento
Os dados em C++ podem ter seu armazenamento padrão modificado, e isso terá efeitos na visibilidade
(escopo) da variável dentro do arquivo e dentro do programa (se o programa conter mais de um arquivo).
Um dado declarado fora de qualquer função é um dado de escopo global, porém se ele for declarado
dentro de um bloco de código ele terá escopo local. As declarações de dados pode ser de nível interno ou
externo.
As classes possíveis em declarações de nível interno são auto (padrão) static e register. Um dado auto
ou static existe enquanto o programa estiver dentro do bloco onde está declarado, mas se for static a cada
chamada à função ele retém o último valor e deve ser iniciado com algum valor, ou o compilador
presumirá o valor zero. Se um dado é declarado com classe register o compilador tentará armazenar esse
dado em um registrador da CPU ou otimizá-lo. Devemos ter cuidado: register é uma solicitação e não
uma ordem, pois nem sempre será possível armazenar dado em um registrador.
Em declarações de nível externo, as classes podem ser somente extern ou static: extern refere-se a um
dado declarado em outro arquivo, e pode ser declarado apenas uma vez em um dos arquivos; dados static
podem ser declarados em mais de um arquivo, já que static limita o escopo no arquivo onde está
declarado. Uma função também pode ter classe de armazenamento modificada: funções static são visíveis
apenas dentro do arquivo onde está declarada e funções extern são visíveis em todos os arquivos que
formam o programa. Funções sem classe de armazenamento são extern por padrão Abaixo um exemplo
de declaração de de um dado com declarações de nível externo (para rodar, abra o projeto proj01.prj no
Turbo C++):
#include<iostream.h>
int a=0;
static int b;
void main(void){
cout << "\nO valor de a é: " << a;
modifica();
cout << "\n\nE o de B em PROG004.CPP é: " << b;
}
extern int a;
static int b=2;
void modifica(void){
a=a+1;
cout << "\n\nO valor de a mudou para: " << a;
cout << "\nE o de B em PROG005.CPP é: " << b;
}
Matrizes
Qualquer tipo de dado em C e C++ pode ser organizados em arranjos (array) contíguos na memória
RAM. Esses arranjos são chamados matrizes e têm grande importância na programação. As matrizes
podem ter uma ou mais dimensôes, e quando possuem uma dimensão podem ser denominadas de vetores.
A declaração de uma matriz de 5 inteiros de nome matriz seria:
int matriz[5];
int matriz[5][5];
O nº de elementos de uma matriz pode ser omitido na declaração, mas se a matriz não for iniciada o seu
tamanho será presumido como 0 pelo compilador. Para declarar uma matriz iniciando-a a sintaxe é:
int matriz[]={0,1,2,3,4,5,6};
No caso de matrizes de mais de uma dimensão, somente a dimensão mais à direita pode ficar sem o nº de
itens:
int matriz[5][]={{0,1,2,3},{4,5,6,7},{8,9,10,11},{12,13,14,15},{16,17,18,19}};
No caso de matrizes de caracteres (em Pascal, o tipo string é padrão, o que não ocorre com C e C++),a
inicialização pode ser feita como mostrada acima ou de forma mais direta:
Nesse caso, o compilador criará todos os itens com o tamanho do maior item.
Poderemos usar as matrizes com todas as possibilidades que elas oferecem quando estudarmos os
ponteiros, um assunto de extrema importância em C e C++ que é um dos motivos da escolha delas para
implementar sistemas operacionais.
Operadores
C++ possui alguns operadores que não são encontrados em outras linguagens de programação, como
operadores bit a bit, de atribuição composta, incremento e decremento e operador vírgula. Os tipos de
operadores C++ são:
Tratam os dados como sequências de bit individuais. São & (AND), | (OR), ^ (XOR), ~ (complemento de
um). O programa a seguir ilustra os efeitos dos operadores bit a bit:
#include<iostream.h>
void main(void){
cout << "\n\n 0 em binário é 0000000000000000";
cout << "\n127 em binário é 0000000001111111";
cout << "\n255 em binário é 0000000011111111\n";
cout << "\n0 & 255 = " << (zeros & uns) << " 0000000000000000";
cout << "\n0 | 255 = " << (zeros | uns) << " 0000000011111111";
cout << "\n0 ^ 255 = " << (zeros ^ uns) << " 0000000011111111";
cout << "\n~0 = " << (~zeros) << " 1111111111111111";
cout << "\n0 & 127 = " << (zeros & medio) << " 0000000000000000";
cout << "\n0 | 127 = " << (zeros | medio) << " 0000000001111111";
cout << "\n0 ^ 127 = " << (zeros ^ medio) << " 0000000001111111";
cout << "\n~127 = " << (~medio) << " 1111111110000000";
cout << "\n255 & 127 = " << (uns & medio) << " 0000000001111111";
cout << "\n255 | 127 = " << (uns | medio) << " 0000000011111111";
cout << "\n255 ^ 127 = " << (uns ^ medio) << " 0000000010000000";
cout << "\n~255 = " << (~uns) << " 1111111100000000";
}
Deslocamento
Também conhecidos como rotação de bits. Esses operadores, bem como os operadores bit a bit, são
bastante utilizados em programação de sistemas, por manipularem diretamente os bits dos dados. O
operador deslocamento à esquerda << move todos os bits uma posição para a esquerda, e o deslocamento
à direita >>, move todos os bits para a direita. Nos dois casos, os bits de um extremo é perdido e o do
outro extremo torna-se zero. O programa a seguir mostra como funcionam os operadores de deslocamento
de bits:
#include<iostream.h>
Incremento e decremento
Somar ou subtrair 1 de um número é tão comum em programação que C++ possui operadores especiais
para essas ações: ++ (incremento) e – (decremento). Eles só podem ser utilizados por dados variáveis e a
sua sintaxe pode ser observada no programa abaixo:
#include<iostream.h>
#include<conio.h>
int numero=10;
void main(void){
clrscr();
cout << "\nnumero incrementado: " << ++numero;
cout << "\nnumero novamente incrementado: " << numero++;
cout << "\nnumero decrementado: " << --numero;
cout << "\nnumero novamente decrementado: " << numero--;
}
Já que numero foi iniciado com valor 10 e foi incrementado 2 vezes e depois decrementado 2 vezes, então
a princípio a saída deve ser:
numero incrementado: 11
numero novamente incrementado: 12
numero decrementado: 11
numero novamente decrementado: 10
Mas a saída que ele apresenta é outra. Execute o programa para verificar e veja que o resultado é
diferente. Isso acontece porque a posição dos operadores ++ e – influi no resultado, pois altera a ordem de
execução da instrução. Quando escrevemos:
A ordem de execução é: primeiro a variável numero é incrementada e depois a sentença é impressa por
cout.
A ordem é: primeiro a sentença é impressa por cout e depois a variável numero é incrementada
O mesmo ocorre com decremento.
Aritméticos
Diferente de outras linguagens, C++ possui um operador de atribuição ao invés de uma instrução de
atribuição. Isso torna-se claro na instrução abaixo, que não seria possível em outras linguagens:
Ou seja: valor3 recebe o valor 2 e a operação retorna o valor contido em valor3, que é atribuído à variável
valor2, e a peração retorna o valor contido em valor2 e finalmente valor1 recebe 2.
C++ possui ainda um conjunto de operadores de atribuição compostos que permitem que o código seja
escrito de forma mais concisa sem perder a clareza. As instruções abaixo são equivalentes:
valor = valor + 5;
valor + = 5;
Outras operações que podem utilizar esses operadores são as demais perações aritméticas, deslocamento
de bits e bit a bit, gerando os seguintes operadores: +=, -=, *=, /=, %=, <<=, >>=, &=, ^=, |=.
Relacionais e lógicos
Os operadores relacionais C++ sempre estabelecem uma relação entre os valores dos operandos. Eles
retornam 0 se o teste for falso e 1 se for verdadeiro. Os operadores relacionais C e C++ são: == (igual,
note que ele é diferente do operador = de atribuição), != (diferente), > (maior), < (menor), >= (maior ou
igual), <= (menor ou igual).
Os operadores lógicos C e C++ retornam resultados de acordo com a relação lógica de seus argumentos:
&& (AND), || (OR), ! (NOT). O operador && retornará 1 quando os dois argumentos forem verdadeiros,
|| retornará 1 quando pelo menos 1 argumento for verdadeiro e ! inverte o valor que seria retornado. O
programa a seguir mostra os resultados de teste relacionais e lógicos de acordo com a entrada do usuário:
void main(void){
Esse operador torna o código compacto por envolver um decisão com pouco código. Sua sintaxe é:
A condição genérica na instrução acima é um teste, que pode envolver operadores condicionais e lógicos:
int a, b, c;
void main(void){
cout << "\n\nInforme o primeiro valor: ";
cin >>a;
cout << "Informe o segundo valor: ";
cin >>b;
cout << "Informe o terceiro valor: ";
cin >>c;
cout << "o "<<((a>=b && a>=c)?"1o.":(b>=c)?"2o.":"3o.")<<"no. ‚ maior";
}
Operador vírgula
O operador vírgula permite que duas expressões coexistam na mesma linha onde normalmente somente
uma seria premitida. Seu uso será evidenciado quando tratarmos de estruturas de controle de fluxo.
Resolução de escopo
C++ permite que uma variável local possua o mesmo nome de uma global, bastando que seja utilizado o
operador resolução de escopo ::. No trecho abaixo as há duas variáveis teste:
int teste;
...
void funcao(int teste){
::teste=teste;
}
Dentro da função funcao, a variável global recebeu o valor da variável local por meio do operador ::.
Assim como nas outras linguagens, os operadores C++ obedecem a níveis de precedência que
determinam a ordem que o compilador avaliará os operadores. Efeitos colaterais pode surgir quando os
operadores são colocados de forma confusa. As instruções abaixo podem causar efeitos colaterais:
Existem duas formas de avaliar a expressão: valor recebe 4 e em seguida resultado recebe 8 (4+4); ou
valor é usado como 2 e logo depois recebe 4, e resultado recebe 6.
A tabela abaixo relaciona os operadores C++ por ordem de precedência, sendo que alguns ainda não
foram descritos. Operadores da mesma categoria tem a mesma precedência:
Controle de fluxo
Para que possamos escrever programas C++ mais elaborados, é necessário dominar as estruturas de
controle de execução de instruções, que são, basicamente, as instruções condicionais e laços.
Quase todos os programas que escrevemos até o momento seguiram sempre uma sequência definida e o
programador não interferiu em nenhum momento para que o programa tomasse decisões. A exceção foi o
uso do operador condicional ternário.
Quando é feito o controle de fluxo em um programa, algumas instruções podem não ser executadas em
nenhum momento dependendo dos critérios adotados para a decisão.
if
Esse instrução executa condicionalmente um bloco de código. Um bloco de código com apenas uma
instrução não precisa ser delimitado com chaves { }, mas deve ser delimitado se houver mais de uma. A
sintaxe genérica da instrução if é:
if(expressão de teste)
ação;
if(expressão de teste){
instrução1;
instrução2;
...
}
void main(void){
int valor_teste;
cout << "\nDigite um valor inteiro: ";
cin >> valor_teste;
if(valor_teste==2){
cout << "\nFoi digitado o valor 2.";
return;
}
cout << "\nFoi digitado um valor diferente de 2.";
}
Nesse exemplo, se o usuário digitar o valor 2, o teste valor_teste==2 retorna 1 e a instrução if direciona o
fluxo para dentro do bloco, aparecendo a mensagem “Foi digitado o valor 2.” e o programa retorna para
o processo anterior com a instrução return. Caso contrário o programa segue com a instrução após o
bloco.
if-else
Com essa instrução o programa pode tomar duas direções baseado em um teste. A sintaxe geral seria:
if(expressão de teste){
instrução1;
instrução2;
...
}
else{
instrução1;
instrução2;
...
}
As instruções dentro do bloco else somente serão executadas quando o teste for falso (retornar 0).
switch
Quando é desejável testar uma variável em relação a vários valores, é improdutivo montarmos sequências
de instruções if. Nesses casos é aconselhável utilizar a instrução switch. A sintaxe geral é:
switch (expressão){
case constante_1:
instruções;
break;
case constante_2:
instruções;
break;
...
case constante_n:
instruções;
break;
default: instruções;
Devemos observar as peculiaridades da instrução switch. Quando o programa encontra a constante que
corresponde ao valor da expressão, ele executa as instruções abaixo do case até que seja encontrada uma
instrução break ou outra instrução de controle que provoque um devio na odem normal do programa.
for
É uma das estruturas de laço mais potentes em muitas linguagens de programação, mas em C e C++ o
laço for ultrapassa os limites normais de flexibilidade. Sua sintaxe geral é:
for(inicialização;expressão de teste;incremento){
instrução1;
instrução2;
...
}
O laço for deve ser usado preferencialmente quando o número de iterações é conhecido. O exemplo
abaixo mostra o uso do laço for e da instrução switch:
void main(void){
int valor;
cout << "\nDigite um valor inteiro: ";
cin >> valor;
while
Quando não podemos prever o número de iterações necessárias é recomendado o uso do laço while:
while(expressão de teste){
instrução1;
instrução2;
...
}
void main(void){
switch(valor){
case 1: cout << "\nVocê digitou o valor 1";
break;
do-while
A principal direfença entre esse instruçõ e while é que as ações dentro do bloco sempre serão executadas
pelo menos uma vez:
do{
instrução1;
instrução2;
...
}while(expressão de teste);
break e continue
Dentro de laços, a instrução break provoca a saída imediata do laço, e a instrução continue faz o
programa saltar imediatamente para a avaliação da expressão de teste. O uso comum de break é na
instrução switch, e continue é raramente usado.
return
Provoca a saída imediata de uma função para o processo anterior. Se a mesma for de tipo diferente de
void essa instrução deve vir acompanhada de um valor de retorno, caso contrário o compilador emitirá
um aviso que não impede a execução do programa. Se a função for main() o programa terminará.
exit( ) e atexit( )
A função exit( ) provoca a saída imediata do programa e pede um argumento inteiro que será o código de
erro para o sistema operacional, e atexit( ) faz com que uma função seja chamada imediatamente antes de
terminar o programa. Essa função pede um ponteiro de função com argumento.
goto
É uma instrução que desvia incondicionalmente o programa para um rótulo. Sua sintaxe geral é:
goto rotulo;
...
rotulo:
instrução;
Seu uso é condenável por estar fora da filosofia da programação estruturada, mas foi mantido na transição
de C para C++ porque em algumas situações seu uso é vantajoso. Exemplo genérico:
...
for(...){
for(...){
for(...){
for(...){
...
if(...)goto sair;
}
}
}
}
sair:
...
Nesse esquema de laços profundamente aninhados, não importam os testes dos laços externos ao for mais
interno, porque goto desvia incondicionalmente o fluxo para o rótulo especificado. A única restrição é
que o rótulo deve estar dentro da mesma função da instrução goto.
Laços infinitos
Se a expressão de teste de um laço for vazia ou se as variáveis envolvidas não sofrerem modificação
dentro do laço, deve existir alguma instrução para que o programa saia do laço por meio de break,
return, goto ou pelas função exit( ), caso contrário o laço nunca terminará.
Os laços infinitos podem ser usados quando o programa ou o laço termina mediante um evento produzido
pelo usuário ou alguma entrada de valor específica. O exemplo anterior pode ser reescrito novamente
utilizando-se um laço for(poderia ser while ou do-while, mas a expressão de teste não seria vazia)
infinito:
void main(void){
Ponteiros
Ponteiros são variáveis dinâmicas que guardam endereços de outra variáveis. A declaração de ponteiros é
feita com o operador de referência indireta *:
tipo *identificador;
Onde tipo é o tipo apontado pelo ponteiro. Para atribuição, podemos utilizar o operador endereço &:
tipo nome;
tipo *apontador = &nome;
O ponteiro apontador armazenará o endereço da variável nome, ou em outras palavras, apontará para ela.
Ponteiros podem apontar para ponteiros, aumentando o nível de referência indireta, que é limitado apenas
pela necessidade. Os ponteiros podem ser constantes ou variáveis. Ponteiros constantes são as matrizes ou
ponteiros que tenham classe de armazenamento const. O nome da matriz representa o endereço do 1º
item, ou seja, se array[50] é uma matriz, as expressões &array[0] ou array são equivalentes. Para
declarar um ponteiro de nível(ordem) maior, basta aumentarmos o nº de operadores de referência indireta:
Se o operador referência indireta for utilizado em instruções durante o programa, ele permitirá o acesso à
variável apontada pelo ponteiro. O programa a seguir mostra como isso ocorre:
// Funcionamento de ponteiros
// Curso de Linguagem de Programação C++ PROG015.CPP - Silvio Sabino
#include<iostream.h>
void main(void){
Aritmética
Para obter o endereço do elemento n de um ponteiro de nome array, que aponta para uma matriz,
podemos utilizar notação de matriz ou de ponteiro se o ponteiro for variável: &array[n] ou array+n. E
para acessar o valor desse elemento podemos escrever: array[n] ou *(array+n). Em qualquer caso, a
notação de ponteiro executa mais rapidamente do que a de matriz.
Modificadores de ponteiros
A memória do processador 8088 é segmentada, e cada segmento tem 64 kbytes (65535 bytes). Todo
endereçamento é feito com dois números denominados segmento e deslocamento (offset). Existem 4
segmentos em um programa “.EXE”: código, dados, pilha e extra. Se nada for informado ao compilador,
ele tentará sempre alocar espaço dentro do segmento de dados, mas podemos modificar ponteiros para
que sejam armazenados em outros locais com o uso de modificadores:
near: força o ponteiro a apontar somente para o deslocamento. Ocupam 2 bytes na memória;
far: força o ponteiro a contar sempre o valor do segmento e do deslocamento. Além e tornar o acesso
mais lento, ponteiros far ocupam 4 bytes;
huge: semelhante ao ponteiro far, mas quando é incrementado, o segmento pode ser alterado. Isso nunca
ocorre com um ponteiro far.
Para apontar para qualquer tipo de dado, um ponteiro pode ser declarado como void * e ser moldado para
qualquer tipo de acordo com a necessidade. Um ponteiro void simplesmente aponta para uma endereço de
memória sem estar associado a qualquer tipo de dado. Abaixo um programa para exemplificar:
int inteiro;
float flutuante;
void *pointer=&inteiro;
void main(void){
*(int *)pointer = 2;
pointer = &flutuante;
*(float *)pointer = 3.0;
cout << "\nValor int: " << inteiro << "\nValor float: " << flutuante;
}
Strings
C++ não possui cadeias de caracteres como tipo de dado padrão. Todas as cadeias de caracteres são
tratadas como ponteiros para o tipo char. O programador deve ter muito cuidado com strings, porque um
acesso fora da área reservada ou alocada não será avisado pelo compilador e pode levar à catástrofe: a
área invadida pode pertencer a outros dados, ao programa ou ao sistema operacional, no pior caso.
Strings em C++ são sempre terminadas com o caractere nulo (NULL), portanto sempre devemos alocar
espaço para um elemento além do tamanho da string.
Quando os programas tornam-se mais complexos, é comum que o programador não possa prever quanta
memória será necessária para o programa, devendo alocar memória dinamicamente. Os operadores new e
delete alocam e liberam memória, respectivamente. A sintaxe geral desses operadores é:
Quando o operador new é invocado pelo programa, este solicita ao sistema operacional um bloco de
tamanho igual a núm_itens*sizeof(tipo). Se não houver memória disponível o ponteiro recebe o valor
0(NULL). Devemos testar se a alocação foi feita com sucesso, porque se tentarmos usar um ponteiro que
aponta para NULL ou com valor desconhecido podemos causar uma catástrofe: os outros dados, o código
do programa ou de outros programas pode ser corrompido e o sistema pode cair, travando a máquina. O
programa a seguir mostra o uso dos operadores new e delete:
void main(void){
Referência
É possível simplificar a notacão de ponteiros usando o tipo referência. Assim, nas chamadas de funções
podemos utilizar os nomes das variáveis e passar o endereço para a função. O cabeçalho abaixo possui
um argumento que é passado por referência:
Quando o programa chamar essa função, a variável referência ref equivalerá ao argumento passado para a
função, porque terão o mesmo endereço. O uso do tipo referência C++ é indispensável quando não podem
ser utilizados ponteiros e tampouco é possível o uso de variáveis comuns. Em C, a única forma de alterar
o valor do parâmetro original era por meio de ponteiros.
Para referenciarmos membros de ponteiros que apontam para estruturas de dados, a referência indireta é
realizada com os operadores -> e ->*. Por exemplo: seja *pont um ponteiro para uma struct casa, que
possui uma instância denominada sala que e um ponteiro do tipo comodo.
A instância casa é referenciada pelo operador-> porque a estrutura externa (pont) é um ponteiro, e o
conteúdo apontado por casa é acessado com o operador ->*.
Funções
Conforme o conceitos da programação estruturada, todas as ações que são usadas rotineiramente devem
ser modularizadas para economizar tempo e facilitar o entendimento. Cada módulo ou sub-rotina em C++
é um função, como também foi explicado no início. A sintaxe geral de uma função C++ é:
O tipo de retorno é o tipo da função, ou seja, se ela retorna um tipo int dizemos que ela é do tipo int. Para
retornar um valor, basta que uma expressão constante ou variável seja argumento de return.
Argumentos
Uma função pode ter um n° de argumentos limitado apenas pela necessidade e capacidade da máquina.
Quando um argumento é passado para uma função, ele pode ser passado de duas formas:
Passagem por valor: quando passamos o valor dos dados. Os valores das variáveis originais da função
chamadora não são alterados porque apenas uma cópia de seus valores é armazenada na pilha para serem
utilizadas na função chamada. Funções chamadas por valor só podem retornar um valor;
Passagem por referência: nesse caso, são utilizados tipos referência e os dados originais podem ser
alterados pela função. Por isso podemos dizer que funções chamadas por referência podem retornar mais
de um valor (o normal com return e os outros que foram modificados). Em qualquer caso, a chamada por
referência é mais rápida, consumindo menos espaço na pilha e menos instruções.
Argumentos com valores padrões só podem ser os últimos da função. Portanto o cabeçalho:
É inválido porque após um argumento iniciado com valor padrão segiu-se um argumento normal.
O argumento argc é o n° de argumentos na linha de comando e argv[] é uma matriz de ponteiros para
char contendo os argumentos da linha de comando (o primeiro é o nome completo do programa).
Ponteiros de função
Em alguns casos, para que o código torne-se funcional, é aconselhável referenciar uma função
indiretamente por um ponteiro. Apontar para uma função é conter o endereço do início do seu código,
representado pelo nome da função. O programa abaixo mostra como funciona a sintaxe de um ponteiro de
função:
// Ponteiros de função
// Curso de Linguagem de Programação C++ PROG018.CPP - Silvio Sabino
#include<iostream.h>
void funcao_1(void){
cout << "\nExecutando a função 1!";
return;
}
void funcao_2(void){
cout << "\nExecutando a função 2!";
return;
}
void main(void){
int resposta;
do{
cout << "\nVocê deseja executar qual função (1 ou 2)?";
cin >>resposta;
}while(resposta!=1&&resposta!=2);
ponteiro = resposta==1?funcao_1:funcao_2;
ponteiro();
return;
}
O uso de ponteiros de função é uma das portas para a programação avançada orientada por eventos, onde
estruturas de dados genéricas são criadas e os objetos são diferenciados pelas funções apontadas.
Modificadores de acesso
C++ pode interagir com as linguagens Pascal e Assembly. Para que uma função possa ser utilizada por
um programa escrito em Pascal, ela deve ser modificada com a palavra reservada pascal. Assim a ordem
de empilhamento dos argumentos seguirá o padrão da linguagem Pascal: do primeiro para o último (em
C++ é o inverso). Diversamente, para garantir que a ordem de empilhamento dos argumento siga o padrão
C++ deve-se utilizar cdecl.
Assim como os dados referenciados por ponteiros, as funções também podem estar localizadas em
segmentos de código diferentes. Com o uso dos modificadores near e far o compilador pode forçar uma
chamada somente utilizando deslocamento (near) ou segmento e deslocamento (far). Chamadas distantes
(far) tornam todo o processo mais lento.
inline
Algumas vezes é vantajogo expandir em linha uma função pequena. Isso acontece porque existe um
código mínimo para chamar a função envolvendo pilha, argumentos e endereçamento que pode ser de
tamanho ou tempo de execução maior do que a colocação do código da função diretamente no local. Para
que o programador possa sugerir a expansão em linha, usamos a palavra reservada inline. A limitação é
que não podem haver laços na função “candidata” para a expansão.
Recursividade
Muitos problemas possuem solução recursiva ou recorrente, ou seja, a solução chama a si própria até que
uma condição básica seja satisfeita e ela começa a retornar. O fatorial de um número é um exemplo
clássico de aplicação de recursividade. Observe a função fatorial recursiva:
Sem dúvida, a recursividade torna o código claro. No exemplo do fatorial, a função chamará a si mesma
até que o argumento n seja menor do que 2. Nesse ponto ela começa a retornar as chamadas acumuladas
na pilha até a primeira. Para comparação, abaixo segue uma função de forma iterativa para o mesmo fim:
Sobrecarga
C++ permite que mais de uma função possua o mesmo nome, desde que sejam de tipos diferentes ou
tenham argumentos diferentes. Isso ajuda o programador porque só haverá um nome para a mesma tarefa,
que poderá aceitar diferentes tipos de argumentos. Destrutores não podem ser sobrecarregados. O
programa abaixo calcula o cubo de um número e aceita um argumento int ou float:
#include<iostream.h>
void main(void){
int inteiro;
float real;
Com exceção de ., .*, ::, ?:, os operadores em C++ são sobrecarregáveis para desempenhar funções
diferentes das originais. Uma sobrecarga que já conhecemos é a dos operadores de deslocamente de bits
<< e >> que são utilizados com os fluxos de entrada e saída cin e cout, que correspondem a stdin e stdout
em C. A sobrecarga de operador é realizada pela palavra reservada operator, cuja sintaxe é:
A definição da sobrecarga deve ter parâmetros de um tipo de class ou deve ser membro de uma class e a
precedência do operador não sofre alteração. O programa abaixo sobrecarrega o operador + para somar as
partes real e imaginária de dois números complexos e para concatenar duas strings. Atenção nos detalhes.
#include<string.h>
#include<iostream.h>
int n;
// programa principal
void main(void){
string1.setstr("string1");
string2.setstr(" + string2");
c3=c1+c2;
cout << "\nresultado com *char : " << string3.getstr();
cout << "\nresultado com complex: " << c3.real << " " << c3.imag;
}
Protótipos
Normalmente, uma função apenas poderá invocar outra se esta estiver escrita acima daquela:
No fragmento acima, a função abaixo pode chamar acima, mas não o contrário. Para que isso seja
possível, deve ser declarado um protótipo da função abaixo antes do código de acima.
int abaixo(void);
Modelos
Muitas vezes uma função pode executar o mesmo trabalho com tipos de dados diferentes, sem que isso
altere as instruções que fariam parte da função. Funções desse tipo devem ser modeladas. Para construir
um modelo, usamos as palavras reservadas template e class:
A modelo do programa seguinte aloca espaço na memória para n elementos de qualquer tipo de dado
conforme o argumento:
void main(void){
float *flutuante=NULL;
int n;
cout << "\nEscolha o número de elementos: ";
cin >>n;
cout << (aloca(&flutuante,n)?"Alocação executada":"Espaço insuficiente!");
if(flutuante)delete(flutuante);
return;
}
O programa apresenta um detalhe interessante além do uso do modelo de função: para que o ponteiro
flutuante seja alterado pela função modelada aloca, é passado o seu endereço, ou seja, um ponteiro de
ponteiro (ponteiro de 2a ordem), exemplificando o seu uso.
Diretivas do pré-processador
Em alguns casos, deve-se compilar condicionalmente um trecho de código, incluir um arquivo ou trocar
um texto por outro segundo definições prévias. Para tal, C++ possui várias diretivas para modificar a
compilação. As diretivas com suas sintaxes genéricas são:
#define <id1>[(id2,id3...)] <sentença>: define uma macro, que pode ser simplesmente uma expressão
constante ou com argumentos:
Depois de uma diretiva #define o pré-processador substituirá a primeira sentença pela segunda,
respeitando os argumentos. Por exemplo: maior(3,5) será substitído por ((3)>(5)?(3):(5)). O efeito de
uma macro com argumento é semelhante ao modelo abaixo, mas sempre será expandida em linha:
template <class tipo> inline tipo maior (tipo x, tipo y){ return(x>y?x:y); }
#if <expressão>, #ifdef <nome>, #ifndef <nome>: instrui o compilador a considerar ou não um trecho do
código-fonte de acordo com uma diretiva #define anterior. Isso é especialmente útil para manter a
compatibilidade com C e outras linguagens com C++. #ifdef equivale e #if defined(expressão) e #ifndef
significa #if !defined(expressão). A palavra defined só pode ser utilizada com estas diretivas.
#endif: termina o bloco onde as diretivas #if, #ifdef, #ifndef, #elif e #else exercem sua influência.
#error <msg>: instrui o compilador e emitir uma mensagem de erro cujo texto é msg.
#include <arquivo>: inclui um arquivo no local de sua ocorrênia, é a diretiva mais usada.
#line <constante> <arquivo>: faz com que o compilador considere que o a próxima linha do arquivo-
fonte atual seja constante e que seu nome seja nome.
Arquivos
C++ oferece uma biblioteca para funções de leitura e escrita em arquivos, operações necessárias em
muitas aplicações. A linguagem não oferece nenhum meio natural de entrada e saída, mas todas as
necessidades foram embutidas na linguagem através das bibliotecas. As ações normais no trabalho com
arquivos são: gravar, ler, testar o fim do arquivo, obter o tamanho do arquivo e percorrer o arquivo.
Um arquivo pode ser tratado em alto ou baixo nível. Em alto nível as operações são intermediadas por
armazenamento na memória (buffer), que acelera bastante o processo. Os dados são gravados em blocos,
para diminuir os acessos ao disco, muito mais lento do que a memória. Arquivos em alto nível são
conhecidos como streams (fluxos), e são referenciados por um ponteiro para uma struct FILE. Essa
estrutura de dados, bem como as funções que tratam streams, são definidas em STDIO.H. Em baixo nível,
C++ trata diretamente com os arquivos e não utiliza buffers. Os arquivos são referenciados por números
inteiros denominados manipuladores de arquivos, tratando diretamente com o sistema operacional.
// Arquivos em C++
// Curso de Linguagem de Programação C++ PROG022.CPP - Silvio Sabino
// inserindo arquivos cabeçalho necessários
#include<iostream.h>
#include<stdio.h>
#include<conio.h>
#include<ctype.h>
#include<string.h>
if(!(arq=fopen("C:\\exemplo.txt","a+"))){
cout << "Erro não abrir arquivo. Pressione qualquer tecla p/ voltar";
getch();
return(1);
}
return(0);
}
// limpando a tela
clrscr();
// exibindo o menu
cout << "\nPrograma exemplo de manipulação de arquivos\n\n";
cout << "1) Inserir\n";
cout << "2) Consultar\n";
cout << "3) Alterar\n";
cout << "4) Excluir\n";
cout << "5) Listar\n";
cout << "6) Sair";
// inserindo um colaborador
void insere(void){
colaborador novo;
fwrite((void*)&novo,tamanho,1,arq);
}
exibir(temp);
}
else if(temp.reg==reg){
if(z==ALTERA){
do{
gotoxy(1,11);
cout <<"\n\nAlterar nome ou CPF (N/C)?";
opcao=toupper(getch());
}while(opcao!='N'&&opcao!='C');
Acessar o modo gráfico significa mudar o modo de acesso ao vídeo. Em modo texto padrão, a tela é
dividida em 80 colunas e 25 linhas e no modo gráfico existem vários tipos possíveis dependendo do
hardware. No programa abaixo, o usuário informa os coeficientes de uma função do 2o grau (f(x) = ax2 +
bx + c) e o programa desenha o gráfico da função.
void main(void){
line(i,238,i,242);
if(i<480)line(318,i,322,i);
}
No segundo exemplo, o programa desenho um botão e espera até que o usuário pressione uma tecla para
então sair do programa.
setcolor(flag?15:8);
linerel(80,0);
linerel(0,-32);
setcolor(flag?8:15);
linerel(-80,0);
linerel(0,32);
// programa principal
void main(void){
getch();
botao(1);
Palavras reservadas
Todas as linguagens de programação possuem um conjunto de palavras reservadas que apenas devem ser
utilizadas de acordo com a especificação original da linguagem, não podendo ser usadas como nome de
funções ou variáveis e podem, embora seja altamente desaconselhável, ser definidas para outras
atribuições com a diretiva #define. O resultado dessa acrobacia certamente será um cataclisma.