Compartilhar via


Sistema do tipo C++ (C++ moderno)

O conceito de tipo é muito importante na linguagem 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, cada expressão (incluindo valores literais) recebe implicitamente um tipo do compilador antes de ser avaliada. Alguns exemplos de tipos incluem int, para armazenar valores integrais, double, para armazenar valores de ponto flutuante (também conhecidos como tipos de dados escalares), ou a classe de Biblioteca Padrão std::basic_string, para armazenar texto. É possível criar seu próprio tipo definindo uma class ou struct. O tipo especifica a quantidade de memória que será atribuída à variável (ou o resultado da expressão), os tipos de valores que podem ser armazenados nessa variável, como os valores (como padrão de bits) são interpretados e as operações que podem ser executadas nele. Este artigo contém uma visão geral informal dos principais recursos do sistema de tipos C++.

Terminologia

Variável: nome simbólico de uma quantidade de dados para que o nome possa ser usado para acessar os dados aos quais ele se refere em todo o escopo do código onde está definido. Em C++, “variável” costuma ser usada para se referir a instâncias de tipos de dados escalares, e as instâncias de outros tipos são chamadas geralmente de “objetos”.

Objeto: para simplificar e manter consistência, este artigo usa o termo “objeto” para se referir a qualquer instância de uma classe ou estrutura, e 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 também é estaticamente tipada; cada objeto tem um tipo e esse tipo nunca muda (não deve ser confundido com objetos de dados estáticos).
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 cada argumento e o valor de retorno, ou void se nenhum valor for retornado pela função. A exceção se dá quando você está usando modelos de função, que permitem argumentos de tipos arbitrários.

Após a primeira declaração de uma variável, você não pode alterar seu tipo em um momento posterior. No entanto, você pode copiar o valor da variável ou o valor de retorno de uma função para outra variável de 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.

Ao declarar uma variável do tipo POD, é altamente recomendável inicializá-la, ou seja, 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. Esse é um aspecto importante da linguagem C++ a ser lembrado, especialmente se você estiver vindo de outra linguagem que manipule a inicialização para você. Ao declarar 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 implementação do Visual C++ da linguagem inclui muitos tipos fundamentais, também conhecidos como tipos internos. Isso inclui tipos numéricos como int, double, long, bool, além dos tipos char e wchar_t para caracteres ASCII e UNICODE, respectivamente. A maioria dos tipos fundamentais (exceto bool, double, wchar_t e tipos relacionados) tem todas as versões sem sinal, 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.

Os tipos fundamentais são reconhecidos pelo compilador, que tem regras internas que controlam que operações você poderá executar neles e como eles serão convertidos em outros tipos fundamentais. Para obter uma lista completa de tipos internos e seus limites de tamanho e de números, consulte Tipos fundamentais (C++).

A ilustração a seguir mostra os tamanhos relativos dos tipos internos:

Tamanho em bytes dos tipos internos

A tabela a seguir lista os tipos fundamentais usados com mais frequência:

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). Esse é o tipo de caractere usado em cadeias de caracteres do tipo std::wstring.

unsigned char

1 byte

O C++ não tem o tipo 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 valores inteiros muito grandes.

O tipo void

O tipo void é especial; você não pode declarar uma variável do tipo void, mas pode declarar uma variável do tipo void * (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, geralmente, seu uso é altamente desaconselhável em C++ moderno. Em uma declaração de função, um valor de retorno void significa que a função não retorna um valor; esse é um uso comum e aceitável de void. Enquanto a linguagem C exigia funções com parâmetros de valor zero para declarar void na lista de parâmetros, por exemplo, fou(void), essa prática foi desencorajada no C++ moderno e deve ser declarada fou(). Para obter mais informações, consulte Conversões e segurança de tipo (C++ moderno).

Qualificador do tipo const

Qualquer tipo interno ou definido pelo usuário pode ser qualificado pela palavra-chave const. Além disso, as funções de membro podem ser qualificadas por const e, até mesmo, sobrecarregadas por const. O valor de tipo const não pode ser modificado depois de inicializado.

    const double PI = 3.1415;
    PI = .75 //Error. Cannot modify const variable.

O qualificador const é amplamente usado em declarações de função e de variável e a "exatidão de const" é um conceito importante em C++; essencialmente, significa usar const para garantir, no momento da compilação, que os valores não sejam inadvertidamente alterados. Para obter mais informações, consulte const (C++).

Um tipo const é diferente da sua versão não const; por exemplo, const int é um tipo diferente 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 e segurança de tipo (C++ moderno).

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 STL podem considerados tipos de cadeia de caracteres nativos porque fazem parte das bibliotecas padrão incluídas em qualquer ambiente de compilação C++ em conformidade. Basta usar a diretiva #include <string> para tornar esses tipos disponíveis em seu programa. (Se você estiver usando o MFC ou o ATL, a classe CString também estará disponível, mas não fará parte do padrão C++.) O uso de matrizes de caracteres de terminação nula (as cadeias de caracteres de estilo C mencionadas anteriormente) não é nada aconselhável em C++ moderno.

Tipos definidos pelo usuário

Ao definir class, struct, union ou enum, 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 tempo de execução, 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.

  • Não precisam ser estaticamente tipados (a regra é que o tipo de um objeto nunca muda). Por meio dos mecanismos de herança e polimorfismo, uma variável declarada como tipo definido de classe pelo usuário (conhecido como instância do objeto de uma classe) pode ter um tipo diferente no tempo de execução do que no tempo de compilação. Para obter mais informações, consulte Classes derivadas.

Tipos de ponteiro

Desde as primeiras versões da linguagem C, o C++ continua permitindo 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. Em C++ moderno, eles são referidos como ponteiros originais e acessados em seu código por meio de operadores especiais * (asterisco) ou -> (traço com maior que). Isso é chamado de remoção de referência, e qual você usa depende se a remoção de referência for de um ponteiro para um escalar ou de 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 eventos e práticas para ajudar a usar ponteiros brutos se você desejar, mas em C++ moderno não é mais necessário (ou recomendado) usar ponteiros brutos para propriedade de objeto, devido à evolução do ponteiro inteligente (discutido em mais detalhes no final dessa seção). Ainda é útil e seguro usar ponteiros brutos para observar objetos, mas se você tiver de usá-los para a propriedade de objeto, faça isso com muito cuidado e considerando como os objetos possuídos por eles são criados e destruídos.

A primeira coisa que você deve saber é que a declaração de uma variável de ponteiro bruto alocará somente a memória necessária para armazenar um endereço do local da memória a que o ponteiro fará referência quando sua referência for removida. A alocação da memória para o valor de dados em si (também chamado repositório de backup) não é atribuída ainda. Ou seja, declarando uma variável de ponteiro bruto, você está criando uma variável do endereço de memória, não uma variável de dados real. Remover a referência de uma variável de ponteiro antes de verificar se ela contém um endereço válido para um repositório de backup 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, o repositório de backup para ponteiros são mais frequentemente os tipos definidos pelo usuário dinâmico alocados dinamicamente em uma área de memória chamada heap (ou “repositório livre") usando uma expressão de palavras-chave new (na programação de estilo C, a função C mais antiga da biblioteca em tempo de execução malloc() era usada). Uma vez alocadas, essas "variáveis" são normalmente mencionadas como "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 C free() de tempo de execução).

No entanto, é fácil esquecer de excluir um objeto alocado dinamicamente, especialmente em um código complexo, o que causa um bug de recurso chamado vazamento de memória. Por esse motivo, o uso de ponteiros brutos é altamente desaconselhável em C++ moderno. É quase sempre melhor encapsular um ponteiro bruto em um ponteiro inteligente, que irá liberar automaticamente a memória quando o destruidor for chamado (quando o código sai do escopo para o 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 (C++ moderno).

Para obter mais informações sobre conversões de ponteiros, consulte Conversões e segurança de tipo (C++ moderno).

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 e macros #define específicas do Windows (definidas em windef.h) para especificar os tipos de parâmetros e valores de retorno. Esses "tipos de dados do Windows" são, em sua maioria, apenas nomes especiais (aliases) dados para tipos internos C/C++. Para obter uma lista completa desses typedefs e definições do pré-processador, consulte Windows Data Types. 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 aliases 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 em Tempo de Execução do Windows, listados como Windows Runtime base data types. Em C++ moderno, a orientação geral é dar preferência aos tipos C++ fundamentais, a menos que o tipo do Windows comunique qualquer significado adicional sobre como o valor deve ser interpretado.

Mais informações

Para obter mais informações sobre o sistema de tipos C++, consulte os tópicos a seguir.

Tipos de valor (C++ moderno)

Descreve tipos de valores juntamente com os problemas relacionados ao seu uso.

Conversões e segurança de tipo (C++ moderno)

Descreve problemas de conversão de tipos comuns e mostra como evitá-los.

Consulte também

Outros recursos

Bem-vindo ao C++ (C++ moderno)

Referência de linguagem C++

Referência da Biblioteca Padrão C++