类代码和头文件
在类定义之外定义成员函数
到目前为止我们编写的所有类都非常简单,我们已经能够直接在类定义本身内实现成员函数。例如,这是我们无处不在的Date类:
class Date
{
private:
int m_year;
int m_month;
int m_day;
public:
Date(int year, int month, int day)
{
setDate(year, month, day);
}
void setDate(int year, int month, int day)
{
m_year = year;
m_month = month;
m_day = day;
}
int getYear() { return m_year; }
int getMonth() { return m_month; }
int getDay() { return m_day; }
};
但是,随着类变得越来越复杂,在类中包含所有成员函数定义会使类更难管理和使用。使用已经编写的类只需要了解它的公共接口(公共成员函数),而不需要了解类的工作原理。成员函数实现细节带来的只有妨碍。
幸运的是,C ++提供了一种将类的“声明”部分与“实现”部分分开的方法。这是通过在类定义之外定义类成员函数来完成的。为此,只需将类的成员函数定义为正常函数,但使用作用域解析运算符(::)将类名称作为函数前缀(与命名空间相同)。
这是我们的Date类,其中Date构造函数和setDate()函数在类定义之外定义。请注意,这些函数的原型仍存在于类定义中,但实际实现已移到外部:
class Date
{
private:
int m_year;
int m_month;
int m_day;
public:
Date(int year, int month, int day);
void SetDate(int year, int month, int day);
int getYear() { return m_year; }
int getMonth() { return m_month; }
int getDay() { return m_day; }
};
// Date构造函数
Date::Date(int year, int month, int day)
{
SetDate(year, month, day);
}
// Date 成员函数
void Date::SetDate(int year, int month, int day)
{
m_month = month;
m_day = day;
m_year = year;
}
这非常简单。因为访问函数通常只有一行,所以它们通常留在类定义中,即使它们可以移到外面。
这是另一个包含具有成员初始化列表的外部定义构造函数的示例:
class Calc
{
private:
int m_value = 0;
public:
Calc(int value=0): m_value(value) {}
Calc& add(int value) { m_value += value; return *this; }
Calc& sub(int value) { m_value -= value; return *this; }
Calc& mult(int value) { m_value *= value; return *this; }
int getValue() { return m_value ; }
};
变为:
class Calc
{
private:
int m_value = 0;
public:
Calc(int value=0);
Calc& add(int value);
Calc& sub(int value);
Calc& mult(int value);
int getValue() { return m_value; }
};
Calc::Calc(int value): m_value(value)
{
}
Calc& Calc::add(int value)
{
m_value += value;
return *this;
}
Calc& Calc::sub(int value)
{
m_value -= value;
return *this;
}
Calc& Calc::mult(int value)
{
m_value *= value;
return *this;
}
将类定义放在头文件中
在头文件的课程中,您了解到可以将函数声明放在头文件中,以便在多个文件甚至多个项目中使用这些函数。类也不例外。类定义可以放在头文件中,以便于在多个文件或多个项目中重用。传统上,类定义放在与类同名的头文件中,并且在类外定义的成员函数放在与该类同名的.cpp文件中。
这是我们的Date类,分为.cpp和.h文件:
Date.h:
#ifndef DATE_H
#define DATE_H
class Date
{
private:
int m_year;
int m_month;
int m_day;
public:
Date(int year, int month, int day);
void SetDate(int year, int month, int day);
int getYear() { return m_year; }
int getMonth() { return m_month; }
int getDay() { return m_day; }
};
#endif
Date.cpp:
#include "Date.h"
// Date构造函数
Date::Date(int year, int month, int day)
{
SetDate(year, month, day);
}
// Date成员函数
void Date::SetDate(int year, int month, int day)
{
m_month = month;
m_day = day;
m_year = year;
}
现在任何其他想要使用Date类的头文件或代码文件都可以#include “Date.h”。请注意,Date.cpp也需要编译到使用Date.h的任何项目中,以便链接器知道Date的实现方式。
不在头文件中定义类违反了单定义规则吗?
它不至于。如果您的头文件具有正确的标头保护关于标头保护请看这个,则不应该将类定义多次包含在同一文件中。
类型(包括类)不受单定义规则的约束,即每个程序只能有一个定义。因此,没有问题#include包括多个代码文件的类定义(如果有的话,类不会有太多用处)。
不在头文件中定义成员函数违反了单定义规则吗?
这取决于在类定义中定义的成员函数被认为是隐式内联的。内联函数不受单定义规则的每个程序部分的一个定义的限制。这意味着在类定义本身内定义普通成员函数(例如访问函数)没有问题。
在类定义之外定义的成员函数被视为普通函数,并且遵循单定义规则的每个程序部分的一个定义。因此,这些函数应该在代码文件中定义,而不是在头文件中定义。这方面的一个例外是模板功能,我们将在以后的章节中介绍。
那么应该在头文件和.cpp文件中定义什么,以及类定义与外部的内容是什么?
您可能想要将所有成员函数定义放入类中的头文件中。虽然这会编译,但这样做有几个缺点。首先,如上所述,这会使您的类定义变得混乱。其次,类内定义的函数是隐式内联的。对于从许多地方调用的较大函数,这可能会使代码膨胀。第三,如果你更改了头文件中代码的任何内容,那么你需要重新编译包含该标题的每个文件。这可能会产生连锁反应,其中一个小的更改会导致整个程序需要重新编译(这可能很慢)。如果更改.cpp文件中的代码,则只需重新编译该.cpp文件!
因此,建议如下:
a:对于通常不可重用的一个文件中使用的类,直接在它们使用的单个.cpp文件中定义它们。
b:对于在多个文件中使用的类或用于一般重用的类,请在与该类同名的.h文件中定义它们。
c:可以在类中定义普通成员函数(普通构造函数或析构函数,访问函数等)。
d:应该在与该类同名的.cpp文件中定义不重要的成员函数。
在将来的课程中,我们的大多数类将在.cpp文件中定义,所有函数都直接在类定义中实现。这只是为了方便并保持示例简短。在实际项目中,将类放入自己的代码和头文件中更为常见,应该习惯这样做。
默认参数
成员函数的默认参数应该在类定义中(在头文件中)声明,#includes头部可以看到它们。
Libraries
对于可用于扩展程序的库,分离类定义和类实现非常常见。在整个程序中,您拥有属于标准库的#included标头,例如iostream,string,vector,array等。请注意,您不需要将iostream.cpp,string.cpp,vector.cpp或array.cpp添加到项目中。您的程序需要头文件中的声明,以便编译器验证您正在编写语法正确的程序。但是,属于C ++标准库的类的实现包含在链接阶段链接的预编译文件中。你永远不会看到代码。
在一些开源软件(提供.h和.cpp文件)之外,大多数第三方库只提供头文件以及预编译的库文件。这有几个原因:1)链接预编译库比每次需要时重新编译它更快
2)许多应用程序可以共享预编译库的单个副本,而编译后的代码可以编译到每个可执行文件中它使用它(膨胀文件大小)
3)知识产权的原因(你不希望人们窃取你的代码)。
将您自己的文件分成声明(头文件)和实现(代码文件)不仅是一种好的形式,它还使您可以更轻松地创建自己的自定义库。创建自己的库超出了我学的教程的范围,但将声明和实现分开是执行此操作的先决条件。