Share via


Sistema do tipo C++

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 pelo compilador antes de serem avaliadas. Alguns exemplos de tipos incluem tipos internos, como para armazenar valores inteiros, para armazenar valores de ponto flutuante ou tipos de biblioteca padrão, double como int 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 de enumeração, tipos de ponteiro, tipos de ponteiro para membro e std::nullptr_t. Os tipos fundamentais são tipicamente escalares.

Tipo composto: um tipo que não é 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 se refere em todo o escopo do código onde está definido. Em C++, a variável é frequentemente usada 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 não sejam também 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 tipada estaticamente, 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 de retorno e de cada argumento. Use o tipo void de valor de retorno 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 em algum momento posterior. No entanto, você pode copiar o valor da variável ou o valor de retorno de uma função em 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-lhe 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 de 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, , , , boolmais os char e wchar_t tipos para caracteres ASCII e UNICODE, doublelongrespectivamente. 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 governam 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++:

Diagram of the relative size in bytes of several built in types.

A tabela a seguir lista os tipos fundamentais mais usados e seus tamanhos na implementação do Microsoft C++:

Tipo Tamanho Comentar
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 de tipo , mas você pode declarar uma variável de tipo voidvoid * (ponteiro para void), que às vezes é necessário ao alocar memória bruta (não tipada). No entanto, os ponteiros para void não são seguros para o tipo e seu uso é desencorajado no C++ moderno. Em uma declaração de função, um valor de retorno significa que a função não retorna um valor, usá-lo como um tipo de retorno é um void uso comum e aceitável do void. Enquanto a linguagem C exigia funções que têm zero parâmetros para declarar void na lista de parâmetros, por exemplo, essa prática é desencorajada no C++ moderno, fn(void)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 const pela 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 "correção constante" é 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 como tipos de cadeia de caracteres nativos porque fazem parte das bibliotecas padrão incluídas em qualquer ambiente de compilação C++ em conformidade. 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 desreferenciação. 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 a 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ê deve usá-los para a propriedade do objeto, você deve 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 só aloca memória suficiente para armazenar um endereço: o local de memória ao qual o ponteiro se refere quando é desreferenciado. A declaração de ponteiro não aloca a memória necessária para armazenar o valor de dados. (Essa memória também é chamada de armazenamento 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 armazenamentos de backup para ponteiros são mais frequentemente tipos definidos pelo usuário que são alocados dinamicamente em uma área da memória chamada heap (ou armazenamento livre) usando uma new expressão de palavra-chave (na programação no estilo C, a função de biblioteca de tempo de execução C mais antiga malloc() era 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 envolver um ponteiro bruto em um ponteiro inteligente, que libera automaticamente a memória quando seu destruidor é chamado. (Ou seja, quando o código sai do escopo do ponteiro inteligente.) Usando ponteiros inteligentes, você praticamente elimina uma classe inteira 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 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 seguintes artigos.

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++