A linguagem C Conceitos gerais Novas facilidades da












- Slides: 12
A linguagem C++ Conceitos gerais Novas facilidades da linguagem C++ A linguagem de programação C++ foi concebida para: · Ser um C melhorado · Suportar Abstracção de Dados · Suportar Programação Orientada por Objectos
O suporte mínimo para a programação procedimental consiste em funções, operadores aritméticos, instruções de selecção, ciclos e entrada/saída de dados. O C++ herda todos estes mecanismos básicos e inclui alguns novos 1. Entrada/saída de dados Tal como em C, o C++ também define streams para os dispositivos de entrada e saída que são abertos quando um programa é executado Os streams são: 1. cout, equivalente ao stdout; 2. cin, equivalente ao stdin; 3. cerr, equivalente ao stderr; De facto cout é um objecto do tipo ostream. A classe ostream foi definida com o operador operator<< (“put to”) para fornecer as saídas dos tipos predefinidos nas linguagens C/C++.
Vamos abordar um exemplo: #include <iostream. h> int main() { cout << "This is the second argumentn"; } Isto é o primeiro argumento do operador (da função) operator<< Isto é o segundo argumento do operador (da função) operator<< O operador << (“put to”) escreve o valor do segundo argumento no primeiro argumento. Isto significa que, no exemplo, o texto This is the second argumentn será escrito no dispositivo de saída standard cout. cerr é um outro objecto do tipo ostream.
Por analogia cin é um objecto do tipo istream. A classe istream foi definida com o operador operator>> (“get from”) para fornecer as entradas dos tipos predefinidos nas linguagens C/C++. Vamos considerar o exemplo: int a; float b; char c; cin >> a >> b >> c; Os streams são declarados no ficheiro iostream. h Um programa poderá usar para entradas e saídas tanto a biblioteca standard de C como a de C++ (ou as duas)
2. Overloading (Sobrecarga) do nome das funções Funções diferentes têm geralmente nomes diferentes. Contudo, para funções que realizam tarefas semelhantes em tipos de objectos diferentes, por vezes é preferível que essas funções mantenham o mesmo nome. Quando o tipo dos seus argumentos é diferente, o compilador consegue diferenciá-las e escolher a função correcta a invocar. Por exemplo, podemos ter uma função potência para inteiros e outra para variáveis de vírgula flutuante. Vamos abordar o exemplo: int pow(int, int); double pow(double, double); //. . . x = pow (3, 5); // chamada pow(int, int) y = pow (2. 0, 4. 0); // chamada pow(double, double)
Este tipo de uso múltiplo de um nome de uma função é designado por function-name overloading ou simplesmente overloading. Os argumentos de uma função podem ser passados quer “por valor”, quer “por ponteiro”, quer “por referência”. void swap(int* a, int* b) { int temp = *a; *a = *b; *b = temp; } void swap(int& a, int& b) { int temp = a; a = b; b = temp; } void f(int i, int j) { swap(&i, &j); } void f(int i, int j) { swap(i, j); } Usando a passagem por referência podemos trabalhar com ponteiros implícitos
3. Referências Uma referência é um nome alternativo para um objecto Tal como um apontador, uma referência corresponde a um local onde se situa uma variável Com o tipo de dados referência, criamos um nome alternativo para uma variável previamente declarada Numa declaração, a notação T& significa referência para um valor do tipo T Seja, por exemplo: int i=3; int &j=i; j=2; //. . . A diferença entre os ponteiros e as referências é a seguinte: o utilizador pode usar uma referência como um objecto ordinário ainda que o acesso seja fornecido através do endereço
void main(void) { int i=3; int &j=i; j=2; //. . . void main(void) { int i=3; int *j=&i; * j=2; //. . . Uma referência é um ponteiro implícito 6] les es , bx [ bp i=3 ss bp-2 bp // mov word ptr [bp-2], 3 Isto // mov [bp-4], ss // mov [bp-6], ax ; lea ax, [bp-2] j=2 and i=2 mov es: word ptr [bx], 2 bx ss é um ponteiro explícito mov word ptr[bp-2], 3 lea ax, [bp-2] mov [bp-4], ss mov [bp-6], ax les bx, [bp-6] mov es: word ptr [bx], 2
Como uma referência é um ponteiro podemos obter um resultado inesperado, por exemplo int F(int& ri) { ++ri; return ri; } int main(. . . ) { int m 1=1; cout << F(m 1) << endl; cout << m 1 << endl; // o resultado é 2 } A referência para o objecto m 1 é passada à função F. Esta função alterou o valor da variável m 1 indirectamente através da referência De facto, uma referência é implementada pelo compilador como um apontador constante que é desreferenciado automaticamente de cada vez que foi usado
Na maioria dos casos as referências e os apontadores têm uma relação de equivalência, por exemplo: O seguinte código usa uma referência: O seguinte código usa um ponteiro: int ii=5; int *rii=ⅈ cout << ii << 't' << *rii << endl; *rii = 10; cout << ii << 't' << *rii << endl; int i=5; int &ri=i; cout << i << 't' << ri << endl; ri = 10; cout << i << 't' << ri << endl; Os resultados são iguais: 5 10 int &a, &b, &c; As referências devem ser inicializadas, por exemplo: int *aa, *bb, *cc; int k; int &a=k, &b=k, &c=k; // erro // Ok
Vamos considerar o seguinte código: X = Y; Esta atribuição está correcta se a expressão esquerda for um endereço (lvalue - a left value) e se a expressão direita produzir um valor (rvalue - a right value), que é um valor permissível. Como resultado o valor do lado direito (Y) vai ser copiado para a célula da memória que tem o endereço X. Uma referência é um endereço e por isso pode ser considerada como lvalue. Então, a referência pode ser escrita no lado esquerdo da expressão. Vamos considerar uma função. Podemos passar o argumento à função como valor. Neste caso este valor (de facto rvalue) vai ser copiado na pilha da função. A função não tem acesso ao valor da variável original que foi passada.
Podemos passar o argumento à função como referência. Neste caso o endereço (de facto lvalue) vai ser copiado na pilha da função. Usando este endereço (esta referência) podemos alterar o valor da variável original que foi passada. Por outras palavras a referência pode ser usada como um apontador implícito. A referência pode ser devolvida da função. Neste caso a função pode aparecer no lado esquerdo da expressão. Vamos abordar das seguintes funções: 1. int F(int& i) { return i; } 2. int& RF(int& j) { return j; } F(x) = 6; // erro RF(x) = 6; // Ok podemos, por exemplo obter um resultado inesperado int x=3; cout << " x = " << x << endl; RF(x) = 6; cout << " x = " << x << endl; // x=3 // x=6