1.1 类的概念
C++ 里类概念的目的是为程序员提供一种建立新类型的工具,使这些新类型的使用能够像内部类型一样方便。
一个类就是一个用户定义类型。我们设计一个新类型,是为了给某个在内部类型中没有直接对应物的概念提供一个定义。举例,我们可能在某个处理电话的程序里提供一个类型Trunk_line(长途电话线),在某个电子游戏程序中提供类型Explosion(爆炸)。
如果一个程序里提供的类型与应用中的概念有直接的对应,与不具有这种情况的程序相比,这个程序很可能就更容易理解,也更容易修改,一组进过很好选择的用户定义类型可能是使一个程序更简洁。
一个类定义:
class X { ....};
一个类定义可以由#include的使用而在不同的源文件里重复出现。
1.2 成员函数
现在考虑用一个struct实现日期的概念,方式是定义一个data的表示和一组对这种类型的数据进行操作的函数。
struck Date{
int d, m, y;
}
void init_date(Date& d, int, int, int); //初始化d
void add_year(Date& d, int n); //d加n年
void add_month(Date& d, int n); //d加n月
void add_day(Date& d, int n; //d加n天
在日期类型与这些函数之间没有任何显示的联系。我们可以通过将这些函数声明为成员建立起这种联系:
struck Date{
int d, m, y;
void init_date(int dd, int, int, int); //初始化d
void add_year( int n); //d加n年
void add_month(int n); //d加n月
void add_day( int n); //d加n天
}
在一个类(struck也是一个类)里声明的函数被称为成员函数,这种函数只能通过适当类型的特定变量,采用标准的构造成员访问语法形式调用。
例如:
Date my_birthday;
void f()
{
Date today;
today.init(21,03,2014);
my_birthday.init(12,06,1992);
Date tomorrow = today; //类对象复制
tomorrow.add_day(1);
//.....
}
由于不同的结构里有可能存在具有同样名字的成员函数,我们在定义成员函数时就必须给出结构的名字:
void Date :: init(int dd, int mm, int yy)
{
d = dd;
m = mm;
y = yy;
}
1.3 访问控制
前面Date声明提供了一组操作Date的函数,这些函数就是直接依赖于Date的表示的全部函数,而且也是仅有的能够直接访问Date的对象的函数,这些限制可以通过用class代替struck而描述清楚:
class Date{
int d, m, y;
public:
void init(int dd, int mm, int yy);//初始化
void add_year(int n);//加n年
void add_month(int n);//加n月
void add_day(int n);//加n天
}
标号public将这个类的体分为了两个部分,其中的第一部分(私用部分)只能由成员函数使用;而第二部分(公有部分)则构成了该类的对象的公有界面。一个struck也是一个class,但是其成员的默认方式是公用的。成员函数的定义和使用与以前完全一样。例如:
inline void Date::add_year(int n)
{
y+=n;
}
然而,非成员函数将禁止访问私有成员,例如:
void timewarp(Date& d)
{
d.y-=200; //错误:Date::y为私有成员
}
对数据结构的严格访问限制明确说明的一组函数,这样做有许多优点。首先,导致Date保存某个非法的值(例如 ,1985年12月36日)的任何错误必然是某个成员函数的代码造成的,这就意味着第一个层次排错——局部化——在程序运行前就已经完成了。其次,类型Date行为的任何改变都能够而且必然是受到其成员函数改变的影响,如果一个一个类的表示方式改变了,我们只需要修改成员函数,就可以使用这种新表示。用户代码只需要依赖公有界面,因此不需要重新写。另外,一个潜在的用户学习使用一个类,也只需要考察其成员函数的定义。
1.3 构造函数
使用init()一类的函数提供的对象类的初始化,这样做既不优美又容易出错。因为没有任何地方说一个对象必须经过初始化,程序员有可能忘记去做这件事——或者做了两次,一种更好的途径是让程序员有能力去声明一个函数,其明确目的就是完成对象的初始化。因为这样的一个函数将构造起一个给定类型的值,它就被称为构造函数。
构造函数很容易辨认,它具有与类同样的名字。例如
class Date{
//...
Date(int, int, int); //构造函数
};
如果一个类有一个构造函数,这个类的所有对象的初始化都将通过对某个构造函数的调用而完成进行初始化。如果该构造函数要求传参,那么就必须提供这些参数:
Date today = Date(23,6,1983);
Date xmas(25,12,1990); //简写形式
Date my_birthday; //错误:缺少初始式
Date release_0(10,12); //错误:缺少第3个参数
允许多种方式初始化类对象也很好。这种事情可以通过提供多个构造函数的方式完成例如:
class Date{
int d , m, y;
public:
//....
Date(int, int, int);//日, 月, 年
Date(int, int);//日, 月,本年年
Date(int); //日,本月本年
Date(); //默认日期:今天
Date(const char*); //字符串表示的日期
}