Nota
O acesso a esta página requer autorização. Pode tentar iniciar sessão ou alterar os diretórios.
O acesso a esta página requer autorização. Pode tentar alterar os diretórios.
Os nomes dos elementos do programa, como variáveis, funções, classes e assim por diante, devem ser declarados antes de poderem ser usados. Por exemplo, você não pode simplesmente escrever x = 42 sem primeiro declarar 'x'.
int x; // declaration
x = 42; // use x
A declaração diz ao compilador se o elemento é um int, um double, uma função, um class ou alguma outra coisa. Além disso, cada nome deve ser declarado (direta ou indiretamente) em todos os arquivos .cpp em que é usado. Quando você compila um programa, cada arquivo .cpp é compilado independentemente em uma unidade de compilação. O compilador não tem conhecimento de quais nomes são declarados em outras unidades de compilação. Isso significa que, se você definir uma classe ou função ou variável global, deverá fornecer uma declaração dessa coisa em cada arquivo de .cpp adicional que a usa. Cada declaração dessa coisa deve ser exatamente idêntica em todos os arquivos. Uma ligeira inconsistência causará erros, ou comportamento não intencional, quando o vinculador tentar mesclar todas as unidades de compilação em um único programa.
Para minimizar o potencial de erros, o C++ adotou a convenção de usar arquivos de cabeçalho para conter declarações. Você faz as declarações em um arquivo de cabeçalho e, em seguida, usa a diretiva #include em cada arquivo de .cpp ou outro arquivo de cabeçalho que requer essa declaração. A diretiva #include insere uma cópia do arquivo de cabeçalho diretamente no arquivo .cpp antes da compilação.
Observação
No Visual Studio 2019, o recurso de módulos C++20 é introduzido como uma melhoria e eventual substituição para arquivos de cabeçalho. Para obter mais informações, consulte Visão geral de módulos em C++.
Exemplo
O exemplo a seguir mostra uma maneira comum de declarar uma classe e, em seguida, usá-la em um arquivo de origem diferente. Vamos começar com o arquivo de cabeçalho, my_class.h. Ele contém uma definição de classe, mas observe que a definição está incompleta; A função do_something de membro não está definida:
// my_class.h
namespace N
{
class my_class
{
public:
void do_something();
};
}
Em seguida, crie um arquivo de implementação (normalmente com uma extensão .cpp ou similar). Chamaremos o arquivo de my_class.cpp e forneceremos uma definição para a declaração de membro. Adicionamos uma #include diretiva para o arquivo "my_class.h" para que a declaração my_class seja inserida neste ponto no arquivo .cpp, e incluímos <iostream> para puxar a declaração para std::cout. Observe que as aspas são usadas para arquivos de cabeçalho no mesmo diretório que o arquivo de origem, e colchetes angulares são usados para cabeçalhos de biblioteca padrão. Além disso, muitos cabeçalhos de biblioteca padrão não têm .h ou qualquer outra extensão de arquivo.
No arquivo de implementação, podemos opcionalmente usar uma using declaração para evitar ter que qualificar cada menção de "my_class" ou "cout" com "N::" ou "std::". Não coloque using instruções em seus arquivos de cabeçalho!
// my_class.cpp
#include "my_class.h" // header in local directory
#include <iostream> // header in standard library
using namespace N;
using namespace std;
void my_class::do_something()
{
cout << "Doing something!" << endl;
}
Agora podemos usar my_class em outro arquivo .cpp. Nós #include o arquivo de cabeçalho para que o compilador puxe a declaração. Tudo o que o compilador precisa saber é que my_class é uma classe que tem uma função de membro público chamada do_something().
// my_program.cpp
#include "my_class.h"
using namespace N;
int main()
{
my_class mc;
mc.do_something();
return 0;
}
Depois que o compilador termina de compilar cada arquivo .cpp em .obj arquivos, ele passa os arquivos .obj para o vinculador. Quando o vinculador mescla os arquivos de objeto, ele encontra exatamente uma definição para my_class; ele está no arquivo .obj produzido para my_class.cpp, e a compilação é bem-sucedida.
Incluir guardas
Normalmente, os arquivos de cabeçalho têm um protetor de inclusão ou uma #pragma once diretiva para garantir que eles não sejam inseridos várias vezes em um único arquivo .cpp.
// my_class.h
#ifndef MY_CLASS_H // include guard
#define MY_CLASS_H
namespace N
{
class my_class
{
public:
void do_something();
};
}
#endif /* MY_CLASS_H */
O que colocar em um arquivo de cabeçalho
Como um arquivo de cabeçalho pode ser potencialmente incluído por vários arquivos, ele não pode conter definições que possam produzir várias definições do mesmo nome. Não são permitidos, ou são considerados práticas muito más:
- Definições de tipo internas no namespace ou no escopo global
- Definições de função não embutida
- Definições de variáveis não-const
- definições agregadas
- Namespaces sem nome
- Utilização de diretivas
O uso da using diretiva não necessariamente causará um erro, mas pode potencialmente causar um problema porque traz o namespace para o escopo em cada arquivo .cpp que inclui direta ou indiretamente esse cabeçalho.
Exemplo de arquivo de cabeçalho
O exemplo a seguir mostra os vários tipos de declarações e definições permitidas em um arquivo de cabeçalho:
// sample.h
#pragma once
#include <vector> // #include directive
#include <string>
namespace N // namespace declaration
{
inline namespace P
{
//...
}
enum class colors : short { red, blue, purple, azure };
const double PI = 3.14; // const and constexpr definitions
constexpr int MeaningOfLife{ 42 };
constexpr int get_meaning()
{
static_assert(MeaningOfLife == 42, "unexpected!"); // static_assert
return MeaningOfLife;
}
using vstr = std::vector<int>; // type alias
extern double d; // extern variable
#define LOG // macro definition
#ifdef LOG // conditional compilation directive
void print_to_log();
#endif
class my_class // regular class definition,
{ // but no non-inline function definitions
friend class other_class;
public:
void do_something(); // definition in my_class.cpp
inline void put_value(int i) { vals.push_back(i); } // inline OK
private:
vstr vals;
int i;
};
struct RGB
{
short r{ 0 }; // member initialization
short g{ 0 };
short b{ 0 };
};
template <typename T> // template definition
class value_store
{
public:
value_store<T>() = default;
void write_value(T val)
{
//... function definition OK in template
}
private:
std::vector<T> vals;
};
template <typename T> // template declaration
class value_widget;
}