Para trabalharmos com funções que alteram o valor de uma variável, precisamos usar os ponteiros, independente do escopo da função.
Crie esse código com essa função aqui:
#include <iostream>
using namespace std;
void somar(int var, int valor); // Não esqueça do protótipo
int main() {
int num = 0;
somar(num, 15);
cout << num << endl; // Não será alterado
return 0;
}
void somar(int var, int valor) {
var += valor;
}
No caso acima, ele continuará retornando 0 por causa do escopo das variáveis, que foram criadas em endereços diferentes.
Para utilizar sempre o mesmo endereço, precisaremos criar ponteiros, no caso, serão nas variáveis var dos parâmetros da função, e indicar o endereço de num, que ficará assim:
#include <iostream>
using namespace std;
void somar(int *var, int valor); // Não esqueça do protótipo
int main() {
int num = 0;
somar(&num, 15);
cout << num << endl;
return 0;
}
void somar(int *var, int valor) {
*var += valor;
}
Da forma acima fará a função corretamente, já que ela trabalhará no mesmo endereço de num, alterando o valor corretamente.
Com arrays, não precisaremos indicar o endereço na invocação da função e nem ponteiros nas posições dos vetores, apenas nos parâmetros, dessa forma:
#include <iostream>
using namespace std;
void iniVet(int *v);
int main() {
int vetor[5];
iniVet(vetor);
for(int i = 0; i < 5; i++) {
cout << vetor[i] << endl;
}
return 0;
}
void iniVet(int *v) {
v[0] = 1;
v[1] = 2;
v[2] = 3;
v[3] = 4;
v[4] = 5;
}
Se tirar o ponteiro do parâmetro, ele dará erro.
A função que recebe o vetor como parâmetro poderia ser declarada também como void iniVet(int v[])
.
Para entendermos que o uso de ponteiros e referência independe do escopo, veja esse código aqui sem ponteiros e referências:
void testar(int n1, int n2);
int main() {
setlocale(LC_ALL, "portuguese");
int n1 = 10;
int n2 = 20;
cout << "Valores antes de chamar a função: ";
cout << "n1 = " << n1 << " e n2 = " << n2 << endl;
testar(n1, n2);
cout << "Valores depois de chamar a função: ";
cout << "n1 = " << n1 << " e n2 = " << n2 << endl;
return 0;
}
void testar(int n1, int n2) {
n1 = -1;
n2 = -2;
cout << "Valores dentro da função testar: " << endl;
cout << "n1 = " << n1 << " e n2 = " << n2 << endl;
}
Agora o mesmo com os ponteiros e referências:
int testar(int *n1, int *n2);
int main() {
setlocale(LC_ALL, "portuguese");
int n1 = 10;
int n2 = 20;
cout << "Valores antes de chamar a função: ";
cout << "n1 = " << n1 << " e n2 = " << n2 << endl;
testar(&n1, &n2);
cout << "Valores depois de chamar a função: ";
cout << "n1 = " << n1 << " e n2 = " << n2 << endl;
return 0;
}
int testar(int *n1, int *n2) {
*n1 = -1;
*n2 = -2;
cout << "Valores dentro da função testar: " << endl;
cout << "n1 = " << *n1 << " e n2 = " << *n2 << endl;
return 0;
}
PS: Podemos ter também ponteiros duplos, isso é chamado de indireção múltipla, veja um exemplo abaixo:
int num = 50;
int *pont; // Ponteiro normal
int **duplPont; // Ponteiro de ponteiro
pont = # // Aponta pra num
duplPont = &pont; // Aponta pra pont, que aponta pra num, ele não aponta diretamente pra num.
cout << "Endereço de num.........................................: " << &num << endl;
cout << "Conteúdo de num.........................................: " << num << endl;
cout << "Endereço de pont........................................: " << &pont << endl;
cout << "Endereço apontado por pont..............................: " << pont << endl;
cout << "Conteúdo apontado por pont..............................: " << *pont << endl;
cout << "Endereço de duplPont....................................: " << &duplPont << endl;
cout << "Endereço apontado por duplPont..........................: " << duplPont << endl;
cout << "Endereço apontado por pont que é apontado por duplPont..: " << *duplPont << endl;
cout << "Conteúdo apontado por pont que é apontado por duplPont..: " << **duplPont << endl;
delete pont;
delete duplPont;
A alocação de memória é útil para reservar a quantidade necessária de memória RAM para o nosso programa, sem desperdício nem falta.
Para começar, criaremos um programa sem alocação, dessa forma:
#include <iostream>
using namespace std;
int main() {
char nome[50];
cout << "Digite o nome: ";
cin >> nome;
cout << nome;
return 0;
}
Mas tem um problema, caso colocarmos um nome com espaço, ele imprimirá apenas o primeiro nome, já que o char ignora tudo que vem após espaços (espaço é considerado caractere terminador).
Para contornar isso, usaremos o fgets (também usado em C), dessa forma (importe cstdio):
int main() {
char nome[50];
cout << "Digite o nome: ";
fgets(nome, 50, stdin); // Importe cstdio
// Isso é pra remover a quebra de linha:
size_t len;
len = strlen(nome); // Inclua cstring
if(nome[len - 1] == '\n') nome[--len] = 0;
cout << nome;
return 0;
}
Mas até então, não mexemos com alocação de memória.
O problema da alocação, está no nosso array de char, que reservou espaço para 50 caracteres na memória, e ao colocar um nome com menos caracteres, teremos espaços sendo perdidos e ocupados inutilmente (no caso, seriam sempre 50 bytes), e se colocarmos algo com mais de 50 caracteres, ele ocupará mais espaço do que o reservado, que pode ser um problema para o sistema operacional.
Para trabalharmos com alocação de memória, precisaremos usar a função malloc, que necessita da biblioteca cstdlib. Teremos também que transformar a variável char num ponteiro, sem indicar o número de espaços, dessa forma:
#include <iostream>
#include <cstdio> // Para fgets
#include <cstdlib> // Para malloc
#include <cstring> // Para strlen
using namespace std;
int main() {
char *nome;
nome = (char*)malloc(sizeof(char));
cout << "Digite o nome: ";
scanf("\n"); // Isso é apenas para evitar que os dados inseridos não sejam lidos, pode funcionar também colocando no lugar fflush(stdin).
fgets(nome, sizeof(nome), stdin);
// Isso é pra remover a quebra de linha:
size_t len;
len = strlen(nome); // Inclua cstring
if(nome[len - 1] == '\n') nome[--len] = 0;
cout << nome;
return 0;
}
Pode ver que acima, precisamos colocar um ponteiro em char, e atribuir à variável a função malloc, que também precisou de um typecast com ponteiro para retornar um valor. A função sizeof retorna o tamanho da variável armazenada.
PS: Há quem coloque mais alguma multiplicação ou soma dentro do malloc para reservar um espacinho a mais na memória, mas cuidado pra não desperdicar espaço demais, prefira a soma nesse caso, assim:
nome = (char*)malloc(sizeof(char) + 1);
Ou podemos criar uma nova realocação após usar o malloc com o realloc
, assim:
nome = (char*)realloc(nome, sizeof(char) + 4);
Para excluir um ponteiro ou memória alocada, use o free, dessa forma:
free(nome);
Em C++, portanto, é mais comum usar o delete para excluir ponteiros e alocações, dessa forma:
delete nome;
As unions se declaram de forma parecida com as structs, mas ela ocupa menos espaço na memória.
Veja um exemplo de uso:
union exemplo {
int inteiro;
char caractere;
};
As variáveis do unions ocupam o mesmo endereço de memória.
Vamos comparar uma union e uma struct:
union estrutura01 {
int inteiro;
char caractere;
float decimal;
};
struct estrutura02 {
int inteiro2;
char caractere2;
float decimal2;
};
E na função principal, faça isso:
cout << "União: " << sizeof(estrutura01) << endl;
cout << "Estrutura: " << sizeof(estrutura02) << endl;
Dessa forma vemos a comparação entre o tamanho da union e da struct.
PS: As unions podem ser utilizadas dentro de structs ou de outras unions também.
Como fizemos com as enuns, podemos criar nossos "tipos" próprio com qualquer tipo primitivo, struct, union, etc.
Veja um exemplo de uso logo abaixo:
typedef int inteiro;
typedef float flutuante;
typedef char caractere;
typedef const char* texto;
int main() {
setlocale(LC_ALL, "portuguese");
inteiro num = 50;
flutuante real = 9.85;
caractere letra = 'A';
texto frase = "Introdução à Typedef em C++";
cout << num << endl;
cout << real << endl;
cout << letra << endl;
cout << frase << endl;
return 0;
}
Os comandos printf e scanf na verdade são utilizados em C, mas eles podem ser usados em C++ também.
Veja um exemplo abaixo (para usar eles usamos a biblioteca stdio.h ou cstdio):
#include <iostream>
#include <cstdio> // Para printf e scanf
using namespace std;
int main() {
printf("Uma frase qualquer!\nCurso de C++");
return 0;
}
Com o printf, podemos usar sequências de escape e máscaras de formatação também. A \n pula uma linha e a \0 delimita o final da string.
Vamos trabalhar com variáveis em outro exemplo, onde colocamos as máscaras, que servem de referência da variável:
#include <iostream>
#include <cstdio> // Para printf e scanf
using namespace std;
int main() {
int num1 = 123;
printf("Valor da variável: %d\n", num1);
return 0;
}
E podemos colocar mais de uma variável e máscara no mesmo printf, sempre respeitando a ordem (incluindo de tipos):
#include <iostream>
#include <cstdio> // Para printf e scanff
using namespace std;
int main() {
int num1, num2, num3;
num1 = 1;
num2 = 2;
num3 = 3;
printf("Valor das variáveis: \n%d\n%d\n%d\n", num1, num2, num3);
return 0;
}
Veja algumas máscaras de formatação, no C chamadas de especificadores de conversão:
Máscara | Tipo de Dado | Descrição |
---|---|---|
%d | int | Número Inteiro Decimal com Sinal |
%c | char | Caractere Único |
%f | float ou double | Número Decimal (Real ou de Ponto Flutuante) |
%i | int | Número Inteiro Decimal (Diferentes na Entrada e Saída de Dados) |
%ld | long int | Número Inteiro Longo |
%e | float ou double | Número Real Exponencial (Científico) |
%E | float ou double | Número Real Exponencial (Científico) |
%o | int | Número Inteiro em Formato Octal sem Sinal |
%x | int | Número Inteiro em Formato Hexadecimal sem Sinal. Caracteres de a a f |
%X | int | Número Inteiro em Formato Hexadecimal sem Sinal. Caracteres de A a F |
%u | int | Número Inteiro Decimal sem Sinal |
%h ou %l | --- | Colocado Antes de um Especificador Inteiro para Indicar Valor Short ou Long |
%L | --- | Colocado Antes de um Especificador de Ponto Flutuante para indicar valor Long Double |
%s | char | Cadeia de Caracteres (String ou Ponto pra char) |
%p | pointer | Ponteiros |
Veja outro exemplo:
#include <iostream>
#include <cstdio> // Para printf e scanf
using namespace std;
int main() {
char nome[3]; // Array de char
nome[0] = 'A';
nome[1] = 'B';
nome[2] = 'C';
printf("Valor da variável: %s", nome);
return 0;
}
Para ler do teclado usando scanf, a forma é parecida, mas devemos colocar a variável com o operador &, que indica o endereço da variável:
#include <iostream>
#include <cstdio> // Para printf e scanf
using namespace std;
int main() {
int num1, num2, num3;
printf("Digite um valor: ");
scanf("%d", &num1);
printf("Valor da variável: %d", num1);
return 0;
}
Podemos também usar mais de uma entrada no mesmo scanf, mas não é recomendado:
#include <iostream>
#include <cstdio> // Para printf e scanf
using namespace std;
int main() {
int num1, num2, num3;
char nome[10];
printf("Digite um número e uma string: ");
scanf("%d %s", &num1, &nome);
printf("Valor das variáveis: \n%d\n%s\n", num1, nome);
return 0;
}
Veja mais um exemplo:
#include <iostream>
#include <cstdio> // Para printf e scanf
using namespace std;
int main() {
char nome[10];
int ano;
printf("Digite seu nome: ");
scanf("%s", &nome);
printf("Informe o ano de nascimento: ");
scanf("%d", &ano);
printf("\nNome: %s\nAno: %d", nome, ano);
return 0;
}