Programar em C/Ponteiros
Origem: Wikilivros, livros abertos por um mundo aberto.
| << Strings | Índice | Tipos de dados definidos pelo usuário >> |
Poderíamos escrever um livro inteiro sobre ponteiros, pois o conteúdo é demasiadamente extenso. Por esse motivo este assunto foi dividido em básico, intermediário e avançado, assim o leitor poderá fazer seus estudos conforme suas necessidades.
É recomendável para quem está vendo programação pela primeira vez aqui que se preocupe apenas com o básico sobre ponteiros por enquanto.
Tabela de conteúdo |
[editar] Básico
[editar] O que é um ponteiro
Um ponteiro é simplesmente uma variável que armazena o endereço de outra variável.
Um exemplo : O que é o ponteiro de um relógio? É o que aponta para as horas, minutos ou segundos. Um ponteiro aponta para algo. Em programação, temos as variáveis armazenadas na memória, e um ponteiro aponta para um endereço de memória.
Imagine as variáveis como documentos, a memória do computador como pastas para guardar os documentos, e o ponteiro como atalhos para as pastas.
Não se desespere caso não consiga entender num primeiro momento, o conceito fica mais claro com a prática.
[editar] Declarando e acessando ponteiros
Um ponteiro, como qualquer variável, deve ter um tipo, que é o tipo da variável para a qual ele aponta. Para declarar um ponteiro, especificamos o tipo da variável para a qual ele aponta e seu nome precedido por asterisco:
int ponteiro ; /* declara uma variável comum do tipo inteiro */ int *ponteiro ; /* declara um ponteiro para um inteiro */
Tome cuidado ao declarar vários ponteiros em uma linha, pois o asterisco deve vir antes de cada nome de variável. Note os três exemplos:
int p, q, r; // estamos a declarar três variáveis comuns int *p, q, r; // cuidado! apenas p será um ponteiro! int *p, *q, *r; // agora sim temos três ponteiros
Para acessar o endereço de uma variável, utilizamos o operador & (E comercial), chamado "operador de referência" ou "operador de endereço". Como o nome sugere, ele retorna o endereço na memória de seu operando. Ele é unário e deve ser escrito antes do seu operando. Por exemplo, se uma variável nome foi guardada no endereço de memória 1000, a expressão &nome valerá 1000.
Com isso, fica claro o esquema ao lado: a variável a contém o valor 1234 e o ponteiro p contem o endereço de a (&a).
Para atribuir um valor ao ponteiro, usamos apenas seu nome de variável. Esse valor deve ser um endereço de memória, portanto obtido com o operador &:
int a; int *p; p = &a;
Claro que também podemos inicializar um ponteiro:
int *p = &a;
Nos dois casos, o ponteiro p irá apontar para a variável a.
Mas, como o ponteiro contém um endereço, podemos também atribuir um valor à variável guardada nesse endereço, ou seja, à variável apontada pelo ponteiro. Para isso, usamos o operador * (asterisco), que basicamente significa "o valor apontado por".
Ex:
int a ;
int *p = &a ;
*p = 20 ;
Para ver o resultado :
printf (" a :%i\n", a);
printf ("*p :%i\n", *p);
Cuidado! Você nunca deve usar um ponteiro sem antes inicializá-lo; esse é um erro comum. Inicialmente, um ponteiro pode apontar para qualquer lugar da memória do computador. Ou seja, ao tentar ler ou gravar o valor apontado por ele, você estará manipulando um lugar desconhecido na memória!
int *p; *p = 9;
Nesse exemplo, estamos a manipular um lugar desconhecido da memória! Se você tentar compilar esse código, o compilador deverá dar uma mensagem de aviso; durante a execução, provavelmente ocorrerá uma falha de segmentação (erro que ocorre quando um programa tenta acessar a memória alheia).
Um exemplo mais elaborado:
#include <stdio.h>
int main()
{
int i = 10 ;
int *p ;
p = &i ;
*p = 5 ;
printf ("%d\t%d\t%p\n", i, *p, p);
return 0;
}
Primeiramente declaramos a variável i, com valor 10, e o ponteiro p, que apontará para o endereço de i. Depois, guardamos o valor 5 no endereço apontado por p. Se você executar esse exemplo, verá algo parecido com:
5 5 0022FF74
É claro que os valores de i e de *p serão iguais, já que p aponta para i. O terceiro valor é o endereço de memória onde está i (e, conseqüentemente, é o próprio valor de p), e será diferente em cada sistema.
[editar] Ponteiro e NULL
Uma falha de segmentação ou em inglês (segmentation fault) ocorre quando um programa tenta aceder a um endereço na memória que está reservado ou que não existe.Nos sistemas Unix quando acontece este tipo de erro o sinal SIGSEGV é enviado ao programa indicando uma falha de segmentação.
Aqui o ponteiro contem null, definido com o endereço (0x00000000) que causa uma falha de segmentação .
/*Endereço invalido*/ #define null ( (char*) 0 ) int main(void){ int a = 5; int *p = null; *p = a; }
Esse programa termina anormalmente. Você esta tentando colocar o valor 5 em um endereço inválido.
Para que isso não aconteça o ponteiro deve ser inicializado com um endereço valido. Exemplo :
#include <stdio.h> #include <errno.h> #include <stddef.h> int main(void){ int a = 5; int *p = NULL; p = &a; /* A operação não é permitida */ if(p == NULL) return -EPERM ; else{ printf("Endereço a disposição:%p\n", p ); *p = a; /* Pode colocar 5 */ } }
NULL está definido dentro do cabeçalho stddef.h . Aqui você não espera que o programa acabe com algum tipo de mágica, se NULL é igual ao valor do ponteiro isso significa que não foi encontrado nem um endereço acessível, então você para. Caso contrario você estará executando uma operação que não é permitida. Ou colocar 5 em (0x00000000) .
[editar] Mais operações com ponteiros
Suponhamos dois ponteiros inicializados p1 e p2. Podemos fazer dois tipos de atribuição entre eles:
p1 = p2;
Esse primeiro exemplo fará com que p1 aponte para o mesmo lugar que p2. Ou seja, usar p1 será equivalente a usar p2 após essa atribuição.
*p1 = *p2;
Nesse segundo caso, estamos a igualar os valores apontados pelos dois ponteiros: alteraremos o valor apontado por p1 para o valor apontado por p2.
Agora vamos dar mais alguns exemplos com o ponteiro p:
p++;
Aqui estamos a incrementar o ponteiro. Quando incrementamos um ponteiro ele passa a apontar para o próximo valor do mesmo tipo em relação ao valor para o qual o ponteiro aponta. Isto é, se temos um ponteiro para um inteiro e o incrementamos, ele passa a apontar para o próximo inteiro. Note que o incremento não ocorre byte-a-byte!
(*p)++;
Aqui, colocamos *p entre parênteses para especificar que queremos alterar o valor apontado por p. Ou seja, aqui iremos incrementar o conteúdo da variável apontada pelo ponteiro p.
*p++
Neste caso, o efeito não é tão claro quanto nos outros exemplos. A precedência do operador ++ sobre o operador * faz com que a expressão seja equivalente a *(p++). O valor atual de p é retornado ao operador *, e o valor de p é incrementado. Ou seja, obtemos o valor atual do ponteiro e já o fazemos apontar para o próximo valor.
x = *(p + 15);
Esta linha atribui a uma variável x o conteúdo do décimo-quinto inteiro adiante daquele apontado por p. Por exemplo, suponhamos que tivéssemos uma série de variáveis i0, i1, i2, … i15 e que p apontasse para i0. Nossa variável x receberia o valor de i15.
Tente acompanhar este exemplo dos dois tipos de atribuição de ponteiros:
int *a, *b, c = 4, d = 2;
a = &c; // a apontará para c
b = &d; // b apontará para d
*b = 8; // altero o valor existente na variavel d
*a = *b; // copio o valor de d (apontado por b)
// para c (apontado por a)
*a = 1; // altero o valor da variável c
b = a; // b aponta para o mesmo lugar que a,
// ou seja, para c
*b = 0; // altero o valor de c
[editar] Intermediário
[editar] Ponteiros como parâmetros de funções
Comecemos por uma situação-problema: eu tenho 2 variáveis e quero trocar o valor delas. Vamos começar com um algoritmo simples, dentro da função main():
#include <stdio.h>
int main()
{
int a = 5, b = 10, temp;
printf ("%d %d\n", a, b);
temp = a;
a = b;
b = temp;
printf ("%d %d\n", a, b);
return 0;
}
Esse exemplo funcionará exatamente como esperado: primeiramente ele imprimirá "5 10" e depois ele imprimirá "10 5". Mas e se quisermos trocar várias vezes o valor de duas variáveis? É muito mais conveniente criar uma função que faça isso. Vamos fazer uma tentativa de implementação da função swap (troca, em inglês):
#include <stdio.h>
void swap(int i, int j)
{
int temp;
temp = i;
i = j;
j = temp;
}
int main()
{
int a, b;
a = 5;
b = 10;
printf ("%d %d\n", a, b);
swap (a, b);
printf ("%d %d\n", a, b);
return 0;
}
No entanto, o que queremos não irá acontecer. Você verá que o programa imprime duas vezes "5 10". Por que isso acontece? Lembre-se do escopo das variáveis: as variáveis a e b são locais à função main(), e quando as passamos como argumentos para swap(), seus valores são copiados e passam a ser chamados de i e j; a troca ocorre entre i e j, de modo que quando voltamos à função main() nada mudou.
Então como poderíamos fazer isso? Como são retornados dois valores, não podemos usar o valor de retorno de uma função. Mas existe uma alternativa: os ponteiros!
#include <stdio.h>
void swap (int *i, int *j)
{
int temp;
temp = *i;
*i = *j;
*j = temp;
}
int main ()
{
int a, b;
a = 5;
b = 10;
printf ("\n\nEles valem %d, %d\n", a, b);
swap (&a, &b);
printf ("\n\nEles agora valem %d, %d\n", a, b);
return 0;
}
Neste exemplo, definimos a função swap() como uma função que toma como argumentos dois ponteiros para inteiros; a função faz a troca entre os valores apontados pelos ponteiros. Já na função main(), passamos os endereços das variáveis para a função swap(), de modo que a função swap() possa modificar variáveis locais de outra função. O único possível inconveniente é que, quando usarmos a função, teremos de lembrar de colocar um & na frente das variáveis que estivermos passando para a função.
Se você pensar bem, já vimos uma função em que passamos os argumentos precedidos de &: é a função scanf()! Por que fazemos isso? É simples: chamamos a função scanf() para que ela ponha nas nossas variáveis valores digitados pelo usuário. Ora, essas variáveis são locais, e portanto só podem ser alteradas por outras funções através de ponteiros!
Quando uma função recebe como parâmetros os endereços e não os valores das variáveis, dizemos que estamos a fazer uma chamada por referência; é o caso desse último exemplo. Quando passamos diretamente os valores das variáveis para uma função, dizemos que é uma chamada por valor; foi o caso do segundo exemplo.
[editar] Ponteiros e vetores
Em C, os elementos de um vetor são sempre guardados seqüencialmente, a uma distância fixa um do outro. Com isso, é possível facilmente passar de um elemento a outro, percorrendo sempre uma mesma distância para frente ou para trás na memória. Dessa maneira, podemos usar ponteiros e a aritmética de ponteiros para percorrer vetores. Na verdade, vetores são ponteiros ― um uso particular dos ponteiros. Acompanhe o exemplo a seguir.
#include <stdio.h>
int main ()
{
int i;
int vetorTeste[3] = {4, 7, 1};
int *ptr = vetorTeste;
printf("%p\n", vetorTeste);
printf("%p\n", ptr);
printf("%p\n", &ptr);
for (i = 0; i < 3; i++)
{
printf("O endereço do índice %d do vetor é %p\n", i, &ptr[i]);
printf("O valor do índice %d do vetor é %d\n", i, ptr[i]);
}
return 0;
}
Começamos declarando um vetor com três elementos; depois, criamos um ponteiro para esse vetor. Mas repare que não colocamos o operador de endereço em vetorTeste; fazemos isso porque um vetor já representa um endereço, como você pode verificar pelo resultado da primeira chamada a printf().
Como você já viu anteriormente neste capítulo, podemos usar a sintaxe *(ptr + 1) para acessar o inteiro seguinte ao apontado pelo ponteiro ptr. Mas, se o ponteiro aponta para o vetor, o próximo inteiro na memória será o próximo elemento do vetor! De fato, em C as duas formas *(ptr + n) e ptr[n] são equivalentes.
Não é necessário criar um ponteiro para usar essa sintaxe; como já vimos, o vetor em si já é um ponteiro, de modo que qualquer operação com ptr será feita igualmente com vetorTeste. Todas as formas abaixo de acessar o segundo elemento do vetor são equivalentes:
vetorTeste[1]; *(vetorTeste + 1); ptr[1]; *(ptr + 1)
Veja mais este exemplo:
#include <stdio.h>
int main()
{
int numbers[5];
int *p;
int n;
p = numbers;
*p = 10;
p++;
*p = 20;
p = &numbers[2];
*p = 30;
p = numbers + 3;
*p = 40;
p = numbers;
*(p + 4) = 50;
for (n = 0; n < 5; n++)
cout << numbers[n] << ", ";
return 0;
}
Ele resume as várias formas de acessar elementos de um vetor usando ponteiros.
[editar] Indexação estranha de ponteiros
o C permite fazer um tipo indexação de um vetor quando uma variável controla seu índice. O seguinte código é válido e funciona: Observe a indexação i[vetor].
#include <stdio.h> int main () { int i; int vetor[10]; for (i = 0; i < 10; i++) { printf ("Digite um valor para a posicao %d do vetor: ", i + 1); scanf ("%d", &i[vetor]); //isso é equivalente a fazer *(x + i) } for (i = 0; i < 10; i++) printf ("%d\n", i[vetor]); return (0); }
Essa indexação, apesar de estranha, funciona corretamente e sem aviso na compilação. Ela é prática, mas, para os iniciantes, pode parecer complicada. É só treinar para entender.
[editar] Comparando endereços
Como os endereços são números, eles também podem ser comparados entre si. Veja o exemplo a seguir, com efeito equivalente ao primeiro exemplo da seção anterior:
#include <stdio.h> int main() { int vetorTeste[3] = {4, 7, 1}; int *ptr = vetorTeste; int i = 0; while (ptr <= &vetorTeste[2]) { printf("O endereço do índice %d do vetor é %p\n", i, ptr); printf("O valor do índice %d do vetor é %d\n", i, *ptr); ptr++; i++; } return 0; }
Esse programa incrementa o ponteiro enquanto esse endereço for igual (ou menor) ao endereço do último elemento do vetor (lembre-se que os índices do vetor são 0, 1 e 2).
[editar] Avançado
[editar] Ponteiros para ponteiros
Acompanhe este exemplo:
char a; char *b; char **c; a = 'z'; b = &a; c = &b; **c = 'M';
Perceba que temos dois "níveis": c aponta para b, e b aponta para a. Assim, para acessar a usando o ponteiro c, é necessário usar duas vezes o operador *: uma para obter o valor de b (cujo endereço está guardado em c), e a outra para obter o valor de a, apontado por b.
Uma aplicação de ponteiros para ponteiros está nas strings, já que strings são vetores, que por sua vez são ponteiros. Um vetor de strings seria justamente um ponteiro para um ponteiro.
| Este livro tem a seguinte tarefa pendente: Criar código exemplo sobre ponteiro para ponteiro |
[editar] Passando vetores como argumentos de funções
Os ponteiros podem ser passados como argumentos de funções.
Parâmetro ponteiro passando um array.
#include <stdio.h>
void atribuiValores(int[], int);
void mostraValores(int[], int);
int main()
{
int vetorTeste[3]; // crio um vetor sem atribuir valores
atribuiValores(vetorTeste, 3);
mostraValores(vetorTeste, 3);
return 0;
}
void atribuiValores(int valores[], int num)
{
for (int i = 0; i < num; i++)
{
printf("Insira valor #%d: ", i + 1);
scanf("%d", &valores[i]);
}
}
void mostraValores(int valores[], int num)
{
for (int i = 0; i < num; i++)
{
printf("Valor #%d: %d\n", i + 1, valores[i]);
}
}
Repare que passamos dois parâmetros para as funções:
- O "nome" do vetor, que representa o seu endereço na memória. (Temos 3 maneiras para passar o endereço do vetor: diretamente pelo seu "nome", via um ponteiro ou pelo endereço do primeiro elemento.)
- Uma constante, que representa o número de elementos do vetor. Isso é importante pois o C não guarda informações sobre o tamanho dos vetores; você não deve tentar alterar ou acessar valores que não pertencem ao vetor.
É claro que devemos passar o endereço do vetor (por "referência"), pois os seus valores são alterados pela função atribuiValores. De nada adiantaria passar o vetor por valor, pois o valor só seria alterado localmente na função (como já vimos no caso de troca do valor de duas variáveis).
Por causa dessa equivalência entre vetores e ponteiros, podemos fazer uma pequena alteração no protótipo (tanto na declaração quanto na definição) das funções atribuiValores e mostraValores, sem precisar alterar o código interno dessas funções ou a chamada a elas dentro da função main ? trocando
void atribuiValores(int[], int); void mostraValores(int[], int);
por
void atribuiValores(int*, int); void mostraValores(int*, int);
Para o compilador, você não fez mudança alguma, justamente por conta dessa equivalência. Em ambos os casos, foi passado o endereço do vetor para as funções.
[editar] Ponteiros para funções
Os ponteiros para funções servem, geralmente, para passar uma função como argumento de uma outra função. Neste exemplo
#include <stdio.h>
int soma(int a, int b)
{
return (a + b);
}
int operacao(int x, int y, int (*func)(int,int))
{
int g;
g = (*func)(x, y);
return (g);
}
int main ()
{
int m;
m = operacao(7, 5, soma);
printf("%d\n", m);
return 0;
}
Veja que criamos uma função que retorna a soma dos dois inteiros a ela fornecidos; no entanto, ela não é chamada diretamente. Ela é chamada pela função operacao, através de um ponteiro. A função main passa a função soma como argumento para operacao, e a função operacao chama essa função que lhe foi dada como argumento.
Note bem o terceiro argumento da função operacao: ele é um ponteiro para uma função. Nesse caso, ele foi declarado como um ponteiro para uma função que toma dois inteiros como argumentos e retorna outro inteiro. O * indica que estamos declarando um ponteiro, e não uma função. Os parênteses em torno de *func são essenciais, pois sem eles o compilador entenderia o argumento como uma função que retorna um ponteiro para um inteiro.
A forma geral para declarar um ponteiro para uma função é:
tipo_retorno (*nome_do_ponteiro)(lista de argumentos)
Para chamar a função apontada pelo ponteiro, há duas sintaxes. A sintaxe original é
(*nome_do_ponteiro)(argumentos);
Se ptr é um ponteiro para uma função, faz bastante sentido que a função em si seja chamada por *ptr. No entanto, a sintaxe mais moderna permite que ponteiros para funções sejam chamados exatamente da mesma maneira que funções:
nome_do_ponteiro(argumentos);
Por fim, para inicializar um ponteiro para função, não precisamos usar o operador de endereço (ele já está implícito). Por isso, quando chamamos a função operacao, não precisamos escrever &soma.
Veja mais um exemplo — na verdade, uma extensão do exemplo anterior:
#include <stdio.h>
int soma(int a, int b)
{
return (a+b);
}
int subtracao(int a, int b)
{
return (a-b);
}
int (*menos)(int, int) = subtracao;
int operacao(int x, int y, int (*func)(int,int))
{
int g;
g = func(x, y);
return (g);
}
int main()
{
int m, n;
m = operacao(7, 5, soma);
n = operacao(20, m, menos);
printf("%d\n", n);
return 0;
}
Aqui, criamos mais uma função, subtracao, além de criar um outro ponteiro para ela (uma espécie de "atalho"), menos. Na função main, referimo-nos à função de subtração através desse atalho.
Veja também que aqui usamos a sintaxe moderna para a chamada de ponteiros de funções, ao contrário do exemplo anterior.
[editar] C++
[editar] Passagem por referência
Ainda existe uma outra maneira para isto resulta é a passagem por referência
| 4º PROGRAMA – por referência |
#include <iostram>
using namespace std;
void Swap (int &i,int &j)
{
int *t;
t=i;
i=j;
j=t;
}
int main ()
{
int a,b;
a=5;
b=10;
cout<<a<<”\t”<<b;
Swap (a,b);
cout<<a<<”\t”<<b;
system (“pause”);
return 0;
}
|
Resulta temos passagem por referencia. Fizemos via references. veja-se o function header E a function call! os arrays são nomes alternativos para o alvo
[editar] Comparação entre passagem por reference e pointers
Para exercitar vamos criar um novo problema: Criar um função que duplique qq valor colocado pelo utilizador:
| 1º PROGRAMA-via references | 2º PROGRAMA – via pointers - endereço |
#include <iostream>
using namespace std;
void doubleIt(int&);//prototype com endereço de variavel
int main ()
{
int num;
cout << "Enter number: ";
cin >> num;
doubleIt(num); //chamo função, passando parametro num
cout << "The number doubled in main is " << num << endl;
system ("pause");
return 0;
}
void doubleIt (int& x)
{
cout << "The number to be doubled is " << x << endl;
x *= 2;
cout << "The number doubled in doubleIt is " << x << endl;
}
|
#include <iostream>
using namespace std;
void doubleIt(int*); //parametro por endereço
int main ()
{
int num;
cout << "Enter number: ";
cin >> num;
doubleIt(&num);//passei parametro como endereço
cout << "The number doubled in main is " << num << endl;
system (pause);
return 0;
}
void doubleIt (int* x)
{
cout << "The number to be doubled is " << *x << endl;
*x *= 2;
cout << "The number doubled in doubleIt is " << *x << endl;
}
|
Ou seja nestes dois códigos temos uma passagem por referência e outro por endereço. Com diferenças:
- Na chamada da função (dentro do main() )
doubleIt(num); // by reference doubleIt(&num); // by address
- No prototype da função (confirmar o ponto e virgula)
void doubleIt(int&); // by reference void doubleIt(int*); // by address
- Na function header
void doubleIt (int& x) // by reference void doubleIt (int* x) // by address
- dentro do body function (dentro da própria função)
x *x
Podemos pensar que passando por referência parece ser muito mais simples do que passado por address. Na verdade existem algumas vertas funções da biblioteca que só permitem a passagem por address.
[editar] Referências
| Este livro tem a seguinte tarefa pendente: Mover este tópico para o livro de C++ |
As referências actuam como nomes alternativos para o alvo. dito de outra forma: "chamei 'a' aquela porção de memória, pois bem, agora para além de se chamar 'a', também se pode chamar de 'b'". Para criar uma referência basta colocar o operador de referência & antes do nome de referência. int &referencia= some int
Note: o operador de referência não é igual ao operador de address, mas estão relacionados. Tudo depende é do contexto. Como já vimos antes com o *. é um dos grandes trunfos da linguagem c++. Assim permite que tenhamos de decorar menos coisas
Exemplo
#include <iostream>
using namespace std;
int main()
{
int val = 1;
int &ref = val;
cout << "val is " << val << endl;
cout << "ref is " << ref << endl;
cout << "Setting val to 2" << endl;
val = 2;
cout << "val is " << val << endl;
cout << "ref is " << ref << endl;
cout << "Setting ref to 3" << endl;
ref = 3;
cout << "val is " << val << endl;
cout << "ref is " << ref << endl;
system ("pause");
return 0;
}
Como se viu conseguimos alterar o valor de val alterando o valor de ref.
Afinal de contas é dar mais um nome á variavel!! e podemos acrescentar muitos mais. e pois até podemos declarar as reference através de outras já criadas, por exemplo no exemplo anterior bastava fazer int &a=ref; ou se quisermos int &a=val;
Existe apenas umas restrições para o seu uso:
- Teremos de inicializar, ie, no momento da declaração teremos de atribuir de imediato o valor. (se não fizermos isso dá erro de compilação)
- As reference não podem ser reassigned, ou seja eu no exemplo anterior tinha
int &ref = val; se mais tarde no código tentar-se fazer int &ref=m; (sendo m uma variável já declarada e iniciada por hipótese) o que acontece é que a 2ª instrução é completamente ignorada e ficamos sempre com a primeira. Bem na verdade deu erro de compilação. Bem deve ser da categoria do compilador
A real vantagem dos references é que quando elas são usadas para passar valores para as funções elas providenciam uma maneira de retornar valores das funções.
Vejamos o exemplo
#include <iostream>
using namespace std;
int main()
{
int val1 = 10;
int val2 = 20;
int &ref = val1;
cout << "val1 is " << val1 << endl;
cout << "val2 is " << val2 << endl;
cout << "ref is " << ref << endl;
ref = val2; //What does this do?
cout << endl << "ref = val2" << endl;
cout << "val1 is " << val1 << endl;
cout << "val2 is " << val2 << endl;
cout << "ref is " << ref << endl;
val2 = 30;
cout << endl << "Setting val2 = 30" << endl;
cout << "val1 is " << val1 << endl;
cout << "val2 is " << val2 << endl;
cout << "ref is " << ref << endl;
system ("pause");
return 0;
}
