Observação
O acesso a essa página exige autorização. Você pode tentar entrar ou alterar diretórios.
O acesso a essa página exige autorização. Você pode tentar alterar os diretórios.
O conceito de tipo é importante em C++. Cada variável, argumento de função e valor de retorno de função deve ter um tipo para ser compilado. Além disso, todas as expressões (incluindo valores literais) recebem implicitamente um tipo do compilador antes de serem avaliadas. Alguns exemplos de tipos incluem tipos internos, como int
armazenar valores inteiros, double
armazenar valores de ponto flutuante ou tipos de Biblioteca Padrão, como classe std::basic_string
, para armazenar texto. É possível criar seu próprio tipo definindo um class
ou struct
. O tipo especifica a quantidade de memória alocada para a variável (ou resultado da expressão). O tipo também especifica os tipos de valores que podem ser armazenados, como o compilador interpreta os padrões de bits nesses valores e as operações que você pode executar neles. Este artigo contém uma visão geral informal dos principais recursos do sistema de tipos C++.
Terminologia
Tipo escalar: um tipo que contém um único valor de um intervalo definido. Os escalares incluem tipos aritméticos (valores integrais ou de ponto flutuante), membros do tipo enumeração, tipos de ponteiro, tipos de ponteiro para membro e std::nullptr_t
. Os tipos fundamentais são tipicamente tipos escalares.
Tipo composto: um tipo que não é um tipo escalar. Os tipos compostos incluem tipos de matriz, tipos de função, tipos de classe (ou struct), tipos de união, enumerações, referências e ponteiros para membros de classe não estáticos.
Variável: o nome simbólico de uma quantidade de dados. O nome pode ser usado para acessar os dados aos quais ele se refere em todo o escopo do código em que ele é definido. Em C++, variable é frequentemente usado para se referir a instâncias de tipos de dados escalares, enquanto instâncias de outros tipos são normalmente chamadas de objetos.
Objeto: Para simplicidade e consistência, este artigo usa o termo objeto para se referir a qualquer instância de uma classe ou estrutura. Quando é usado no sentido geral, inclui todos os tipos, até mesmo variáveis escalares.
Tipo POD (dados antigos simples): essa categoria informal de tipos de dados em C++ faz referência a tipos escalares (consulte a seção Tipos fundamentais) ou são classes POD. Uma classe POD não tem membros de dados estáticos que também não sejam PODs e não tem construtores definidos pelo usuário, destruidores definidos pelo usuário ou operadores de atribuição definidos pelo usuário. Além disso, uma classe POD não tem função virtual, nenhuma classe base e nenhum membro de dados não estático particular ou protegido. Os tipos POD são quase sempre usados para a troca de dados externos, por exemplo, com um módulo escrito na linguagem C (que tem apenas tipos POD).
Especificando tipos de variável e de função
C++ é uma linguagem fortemente tipada e uma linguagem estaticamente tipada ; cada objeto tem um tipo e esse tipo nunca muda. Ao declarar uma variável em seu código, você deve especificar seu tipo explicitamente, ou usar a palavra-chave auto
para instruir o compilador a deduzir o tipo do inicializador. Ao declarar uma função em seu código, você deve especificar o tipo de seu valor retornado e de cada argumento. Use o tipo void
de valor retornado se nenhum valor for retornado pela função. A exceção é quando você está usando modelos de função, que permitem argumentos de tipos arbitrários.
Depois de declarar uma variável pela primeira vez, você não poderá alterar seu tipo posteriormente. No entanto, você pode copiar o valor da variável ou o valor retornado de uma função para outra variável de um tipo diferente. Essas operações são chamadas de conversões de tipos, que às vezes são necessárias, mas que também são fontes potenciais de perda de dados ou de incorreção.
Quando você declara uma variável do tipo POD, é altamente recomendável inicializá-la , o que significa dar a ela um valor inicial. Até que você inicialize uma variável, ela terá o valor “garbage”, que consiste nos bits que estavam nesse local de memória anteriormente. É um aspecto importante do C++ a ser lembrado, especialmente se você estiver vindo de outra linguagem que lida com a inicialização para você. Quando você declara uma variável do tipo de classe não POD, o construtor manipula a inicialização.
O exemplo a seguir mostra algumas declarações de variável simples com algumas descrições para cada uma. O exemplo também mostra como o compilador usa informações de tipo para permitir ou não permitir determinadas operações subsequentes na variável.
int result = 0; // Declare and initialize an integer.
double coefficient = 10.8; // Declare and initialize a floating
// point value.
auto name = "Lady G."; // Declare a variable and let compiler
// deduce the type.
auto address; // error. Compiler cannot deduce a type
// without an intializing value.
age = 12; // error. Variable declaration must
// specify a type or use auto!
result = "Kenny G."; // error. Can't assign text to an int.
string result = "zero"; // error. Can't redefine a variable with
// new type.
int maxValue; // Not recommended! maxValue contains
// garbage bits until it is initialized.
Tipos (internos) fundamentais
Ao contrário de algumas linguagens, C++ não tem tipo base universal do qual todos os outros tipos são derivados. A linguagem inclui muitos tipos fundamentais, também conhecidos como tipos internos. Esses tipos incluem tipos numéricos como int
, double
, , long
bool
, , além dos char
tipos e wchar_t
para caracteres ASCII e UNICODE, respectivamente. A maioria dos tipos fundamentais integrais (exceto bool
, double
, wchar_t
e tipos relacionados) tem todas as versões unsigned
, que modificam o intervalo de valores que a variável pode armazenar. Por exemplo, um int
, que armazena um inteiro de 32 bits com sinal, pode representar um valor de -2.147.483.648 a 2.147.483.647. Um unsigned int
, que também é armazenado como 32 bits, pode armazenar um valor de 0 a 4.294.967.295. O número total de valores possíveis em cada caso é o mesmo; somente o intervalo é diferente.
O compilador reconhece esses tipos internos e tem regras internas que regem quais operações você pode executar neles e como eles podem ser convertidos em outros tipos fundamentais. Para obter uma lista completa de tipos internos e seus limites de tamanho e de números, consulte tipos integrados.
A ilustração a seguir mostra os tamanhos relativos dos tipos internos na implementação do Microsoft C++:
A tabela a seguir lista os tipos fundamentais mais usados e seus tamanhos na implementação do Microsoft C++:
Tipo | Tamanho | Comentário |
---|---|---|
int |
4 bytes | A opção padrão para valores integrais. |
double |
8 bytes | A opção padrão para valores de ponto flutuante. |
bool |
1 byte | Representa valores que podem ser true ou false. |
char |
1 byte | Use os caracteres ASCII em cadeias de caracteres do estilo C mais antigo ou objetos std::string que nunca precisarão ser convertidos em UNICODE. |
wchar_t |
2 bytes | Representa os valores de caractere "largos" que podem ser codificados no formato UNICODE (UTF-16 no Windows, outros sistemas operacionais podem ser diferentes).
wchar_t é o tipo de caractere usado em cadeias de caracteres do tipo std::wstring . |
unsigned char |
1 byte | O C++ não tem o tipo de byte interno. Use unsigned char para representar um valor de bytes. |
unsigned int |
4 bytes | Escolha padrão para sinalizadores de bit. |
long long |
8 bytes | Representa um intervalo muito maior de valores inteiros. |
Outras implementações C++ podem usar tamanhos diferentes para determinados tipos numéricos. Para obter mais informações sobre os tamanhos e as relações de tamanho que exige o padrão C++, consulte tipos internos.
O tipo void
O void
tipo é um tipo especial; você não pode declarar uma variável do tipo void
, mas pode declarar uma variável do tipo void *
(ponteiro para void
), o que às vezes é necessário ao alocar memória bruta (não tipada). No entanto, os ponteiros para void
não são fortemente tipados e seu uso é desencorajado no C++ moderno. Em uma declaração de função, um void
valor retornado significa que a função não retorna um valor; usá-lo como um tipo de retorno é um uso comum e aceitável de void
. Embora a linguagem C exigisse funções que não tivessem parâmetros para declarar void
na lista de parâmetros, por exemplo, fn(void)
, essa prática é desencorajada no C++ moderno; uma função sem parâmetros deve ser declarada fn()
. Para obter mais informações, consulte Conversões de tipo e segurança de tipo.
const
qualificador de tipo
Qualquer tipo interno ou definido pelo usuário pode ser qualificado pela const
palavra-chave. Além disso, as funções membro podem ser qualificadas por const
e, até mesmo, sobrecarregadas por const
. O valor de um const
tipo não pode ser modificado depois de inicializado.
const double PI = 3.1415;
PI = .75; //Error. Cannot modify const variable.
O const
qualificador é usado extensivamente em declarações de função e variável e "const correctness" é um conceito importante em C++; essencialmente, significa usar const
para garantir, em tempo de compilação, que os valores não sejam modificados involuntariamente. Para obter mais informações, consulte const
.
Um const
tipo é distinto de sua não-versãoconst
; por exemplo, const int
é um tipo distinto de int
. É possível usar o operador const_cast
do C++ nessas ocasiões raras em que é necessário remover const-ness de uma variável. Para obter mais informações, consulte Conversões de tipo e segurança de tipo.
Tipos de cadeia de caracteres
Estritamente falando, a linguagem C++ não tem tipo interno de cadeia de caracteres char
e wchar_t
armazenam caracteres simples. Você deve declarar uma matriz desses tipos para aproximar uma cadeia de caracteres, adicionando um valor de terminação nula (por exemplo, ASCII '\0'
) para o primeiro elemento da matriz após o último elemento válido (também chamado de cadeia de caracteres de estilo C). As cadeias de caracteres de estilo C exigiam que muito mais códigos fossem escritos ou o uso de funções da biblioteca de utilitários de cadeia de caracteres externos. Mas em C++ moderno, temos os tipos de Biblioteca Padrão std::string
(para cadeias de caracteres do tipo char
de 8 bits) ou std::wstring
(para cadeias de caracteres do tipo wchar_t
de 16 bits). Esses contêineres da Biblioteca Padrão do C++ podem ser considerados tipos de cadeia de caracteres nativos porque fazem parte das bibliotecas padrão incluídas em qualquer ambiente de build C++ compatível. Use a #include <string>
diretiva para disponibilizar esses tipos em seu programa. (Se você estiver usando MFC ou ATL, a CString
classe também estará disponível, mas não fará parte do padrão C++.) O uso de matrizes de caracteres terminadas em nulo (as cadeias de caracteres de estilo C mencionadas anteriormente) é desencorajado no C++ moderno.
Tipos definidos pelo usuário
Ao definir class
, struct
, union
ou enum
que essa construção é usada no restante do código como se fosse um tipo fundamental. Ele tem um tamanho conhecido na memória e certas regras sobre como pode ser usado aplicado para verificar o tempo de compilação e, no runtime, para a vida útil de seu programa. As principais diferenças entre os tipos internos fundamentais e os tipos definidos pelo usuário são:
O compilador não tem conhecimento interno de um tipo definido pelo usuário. Ele aprende o tipo quando encontra a definição durante o processo de compilação pela primeira vez.
Você especifica que operações podem ser executadas em seu tipo, e como ele pode ser convertido em outros tipos, definindo (por meio de sobrecarga) os operadores apropriados, como membros de classe ou funções de não membro. Para obter mais informações, consulte Sobrecarga de função
Tipos de ponteiro
Como nas versões mais antigas da linguagem C, o C++ continua a permitir que você declare uma variável de um tipo de ponteiro usando o declarador *
especial (asterisco). Um tipo de ponteiro armazena o endereço do local na memória em que o valor real de dados é armazenado. No C++ moderno, esses tipos de ponteiro são chamados de ponteiros brutos e são acessados em seu código por meio de operadores especiais: *
(asterisco) ou ->
(traço com maior que, geralmente chamado de seta). Essa operação de acesso à memória é chamada de desreferenciamento. O operador que você usa depende se você está desreferenciando um ponteiro para um escalar ou um ponteiro para um membro em um objeto.
O trabalho com tipos de ponteiro foi durante muito tempo um dos aspectos mais desafiadores e confusos do desenvolvimento de programas em C e C++. Esta seção descreve alguns fatos e práticas para ajudar a usar ponteiros brutos, se desejar. No entanto, no C++ moderno, não é mais necessário (ou recomendado) usar ponteiros brutos para propriedade de objetos, devido à evolução do ponteiro inteligente (discutido mais no final desta seção). Ainda é útil e seguro usar ponteiros brutos para observar objetos. No entanto, se você precisar usá-los para propriedade de objetos, deverá fazê-lo com cautela e com consideração cuidadosa de como os objetos de propriedade deles são criados e destruídos.
A primeira coisa que você deve saber é que uma declaração de variável de ponteiro bruto aloca apenas memória suficiente para armazenar um endereço: o local de memória ao qual o ponteiro se refere quando é desreferenciado. A declaração do ponteiro não aloca a memória necessária para armazenar o valor dos dados. (Essa memória também é chamada de repositório de backup.) Em outras palavras, ao declarar uma variável de ponteiro bruto, você está criando uma variável de endereço de memória, não uma variável de dados real. Se você desreferenciar uma variável de ponteiro antes de se certificar de que ela contém um endereço válido para um repositório de backup, isso causará um comportamento indefinido (geralmente um erro fatal) em seu programa. O exemplo a seguir demonstra esse tipo de erro:
int* pNumber; // Declare a pointer-to-int variable.
*pNumber = 10; // error. Although this may compile, it is
// a serious error. We are dereferencing an
// uninitialized pointer variable with no
// allocated memory to point to.
O exemplo remove a referência de um tipo de ponteiro sem ter memória alocada para armazenar os dados inteiros reais ou um endereço de memória válido atribuído a ele. O código a seguir corrige esses erros:
int number = 10; // Declare and initialize a local integer
// variable for data backing store.
int* pNumber = &number; // Declare and initialize a local integer
// pointer variable to a valid memory
// address to that backing store.
...
*pNumber = 41; // Dereference and store a new value in
// the memory pointed to by
// pNumber, the integer variable called
// "number". Note "number" was changed, not
// "pNumber".
O exemplo de código corrigido usa a memória de pilha local para criar o repositório de backup para o qual pNumber
aponta. Usamos um tipo fundamental para simplificar. Na prática, os repositórios de backup para ponteiros são, na maioria das vezes, tipos definidos pelo usuário que são alocados dinamicamente em uma área da memória chamada heap (ou repositório gratuito) usando uma new
expressão de palavra-chave (na programação no estilo C, a função de biblioteca de runtime C mais antiga malloc()
foi usada). Uma vez alocadas, essas variáveis são normalmente chamadas de objetos, especialmente se forem baseadas em uma definição de classe. A memória que é alocada com new
deve ser excluída por uma função delete
correspondente (ou, se você usou a função malloc()
para atribuí-la, a função de runtime C free()
).
No entanto, é fácil esquecer de excluir um objeto alocado dinamicamente, especialmente em código complexo, o que causa um bug de recurso chamado vazamento de memória. Por esse motivo, o uso de ponteiros brutos é desencorajado no C++ moderno. Quase sempre é melhor encapsular um ponteiro bruto em um ponteiro inteligente, que libera automaticamente a memória quando seu destruidor é invocado. (Ou seja, quando o código sai do escopo do ponteiro inteligente.) Usando ponteiros inteligentes, você praticamente elimina toda uma classe de bugs em seus programas C++. No exemplo a seguir, suponha que MyClass
seja um tipo definido pelo usuário que tem um método público DoSomeWork();
void someFunction() {
unique_ptr<MyClass> pMc(new MyClass);
pMc->DoSomeWork();
}
// No memory leak. Out-of-scope automatically calls the destructor
// for the unique_ptr, freeing the resource.
Para obter mais informações sobre ponteiros inteligentes, consulte Ponteiros inteligentes.
Para obter mais informações sobre conversões de ponteiro, consulte Conversões de tipo e segurança de tipo.
Para obter mais informações sobre ponteiros em geral, consulte Ponteiros.
Tipos de dados do Windows
Na programação Win32 clássica para C e C++, a maioria das funções usa typedefs específicas do Windows e macros #define
(definido em windef.h
) para especificar os tipos de parâmetros e valores de retorno. Esses tipos de dados do Windows são principalmente nomes especiais (aliases) dados a tipos internos do C/C++. Para obter uma lista completa desses typedefs e definições do pré-processador, consulte Tipo de dados do Windows. Alguns desses typedefs, como HRESULT
e LCID
são úteis e descritivos. Outros, como INT
não têm significado especial e são apenas alias para tipos C++ fundamentais. Outros tipos de dados do Windows têm nomes que foram mantidos desde a época da programação em C e de processadores de 16 bits, e não têm finalidade ou significado em hardware ou sistemas operacionais modernos. Também existem tipos de dados especiais associados à Biblioteca do Windows Runtime, listados como tipo de dados de base do Windows Runtime. No C++ moderno, a diretriz geral é preferir os tipos fundamentais do C++, a menos que o tipo Windows comunique algum significado extra sobre como o valor deve ser interpretado.
Mais informações
Para obter mais informações sobre o sistema de tipos C++, consulte os artigos a seguir.
Tipos de valor
Descreve tipos de valores juntamente com os problemas relacionados ao seu uso.
Conversões de tipo e segurança de tipo
Descreve problemas de conversão de tipos comuns e mostra como evitá-los.
Confira também
Bem-vindo outra vez ao C++
Referência da linguagem C++
Biblioteca Padrão do C++