类和对象
在c语言中,程序是由若干的函数以及变量组成的,它们之间并没有很严格的关系,这就导致了当程序规模逐渐变大之后,就不易于程序的扩充以及维护。另外,当我们想要复用之前程序中的一些代码片段时,函数中的变量使得我们很难将其从程序中抽离出来。总之,结构化程序设计使得我们的程序难以扩充、难以维护、难以重用。
c++在c语言的基础上增加了面向对象程序设计(Object-oriented programming,OOP)的机制,而面向对象的这种机制恰好解决了c语言结构化设计的这种缺陷。简单来说,在面向对象程序设计中,我们可以依照以下的设计方法:
- 我们将某类客观事物的共同特点(属性)归纳出来,形成一个数据结构,可以用多个变量描述事物的属性。
- 将这类事物所能进行的行为也归纳出来,形成函数,这些函数就用来操作数据结构。
- 然后通过某种方式将这些数据结构与函数捆绑封装起来,从而形成一个类。
需要注意的是,被封装的函数只能访问与它封装在一起的数据结构,反之,这些数据结构也只能被与它封装在一起的函数所操作。这样就使得某一类事物的数据结构只能由与之封装的函数进行操作,而其它函数是无法访问其数据结构的。
案例
假设我们现在要实现一个矩形类,该如何表示呢?
- 矩形有长(length)和宽(weight)两个属性
作为一个矩形,可以有哪些行为呢?
- 设置长和宽
- 计算面积
- 计算周长
类的定义
我们将上面的矩形属性长(length)和宽(weight)以及与之对应的行为封装在一起,就能形成一个矩形类。
矩形属性成为“成员变量”,3个函数成为该类的“成员函数”,成员变量与成员函数统称为类的成员。
实际上,类看上去就像带函数的结构。
class Rectangle {
public:
double length;//长
double weight;//宽
void init(double length_,double weight_) {//设置长和宽
length = length_;//设置长
weight = weight_;//设置宽
}
double Area(){//计算面积
return length * weight;
}
double Perimeter() {//计算周长
return 2 * (length + weight);
}
};//分号不可省略
关键字 public 确定了类成员的访问属性。在类对象作用域内,公共成员在类的外部是可访问的。除了public,我们还可以指定成员为private 或 protected,我们将在后续进行讲解。
这样我们就得到了一个矩形类。
- length 和 weight 分别为矩形的长和宽
- init、Area、Perimeter分别为类的成员函数,函数中的length 和 weight 指的是成员变量的length 和 weight 。
定义 C++ 对象
普通定义
我们有了Rectangle类之后就要去使用它,声明类的对象,就像声明基本数据类型的变量一样,如下代码所示:
int main() {
Rectangle r;//声明一个对象
double length = 10.0;
double weight = 10.0;
r.init(length, weight);//初始化对象
cout << "矩形面积为:" << r.Area() << endl;
cout << "矩形周长为:" << r.Perimeter() << endl;
return 0;
}
我们声明了一个对象r
,类型为Rectangle
。我们通过.
运算符可以访问类的成员,通过r.init(length, weight);
来初始化对象,此时调用了类的成员函数void init(double length_,double weight_)
,设置了矩形的长和宽。
接下来,通过r.Area()
以及r.Perimeter()
计算矩形的面积以及周长,并通过cout
输出,运行上面的代码,我们可以得到如下结果:
矩形面积为:100
矩形周长为:40
通过指针定义
除了上面所展示的普通定义之外,我们还可以通过指针来定义类的对象,其用法与基本数据类型的指针定义相同。
int main() {
double length;//长
double weight;//宽
Rectangle *r;//声明一个对象指针
r = new Rectangle;//为对象动态分配内存空间
r->init(length, weight);//初始化对象
cout << "矩形面积为:" << r->Area() << endl;
cout << "矩形周长为:" << r->Perimeter() << endl;
delete r;//释放动态分配的空间
return 0;
}
对象指针的创建与基本数据类型的指针定义相同,通过Rectangle *r;
创建一个对象指针,并通过new
运算符为其动态分配内存空间,值得注意的是,在访问对象的成员时,需要使用->
运算符进行访问,而不再是.
运算符。
执行上面代码,我们可以获得与普通定义相同的结果:
矩形面积为:100
矩形周长为:40
最后,在使用完之后,我们需要使用delete
运算符来释放动态分配的空间。
通过类可以定义变量,类定义出来的变量,也成为类的实例,就是我们所说的对象。
访问数据成员
类的对象的公共数据成员(public)可以使用直接成员访问运算符.
或->
来访问。
使用.
访问
int main() {
Rectangle r;//声明一个对象
double length = 10.0;
double weight = 10.0;
r.init(length, weight);//初始化对象
cout << "矩形长为:" << r.length << endl;
cout << "矩形宽为:" << r.weight << endl;
return 0;
}
-
通过
r.init(length, weight);
,我们调用了初始化函数初始化了矩形的长和宽。 -
通过
r.length
,得到了矩形的长。 -
通过
r.weight
,得到了矩形的宽。
执行上面的代码,得到如下结果:
矩形长为:10
矩形宽为:10
使用->
访问
int main() {
double length;//长
double weight;//宽
Rectangle *r;//声明一个对象指针
r = new Rectangle;//为对象动态分配内存空间
r->init(length, weight);//初始化对象
cout << "矩形长为:" << r->length << endl;
cout << "矩形宽为:" << r->weight << endl;
delete r;//释放动态分配的空间
return 0;
}
执行代码,得到下面结果:
矩形长为:10
矩形宽为:10
与使用.
运算符的结果一致。
对象的内存分配
一般的,我们认为对象所占用的内存大小为,等于所有成员变量的大小之和。
对于上面的Rectangle
类,sizeof(Rectangle)=16
int main() {
double length;//长
double weight;//宽
Rectangle r;//声明一个对象
cout << "sizeof(Rectangle)=" << sizeof(Rectangle) << endl;
return 0;
}
运行上面代码,得到如下结果:
sizeof(Rectangle)=16
注意
- 并不是所有的类所占内存空间大小都为成员变量之和,有时还需要考虑对齐等情况,会有所差异。
- 一个类的成员函数只有一份,为所有成员共享,故类的成员函数所占用内存空间没有计算。
- 每个对象都有自己的存储空间,一个对象的值改变了并不会影响另一个变量
类的成员函数和类的定义分开写
当我们的类拥有很多的成员变量以及成员函数的时候,如果将所有的成员函数的实现都写在类的定义当中的话,就会显得十分的臃肿,不利于代码的阅读。此时,我们可以将类的定义以及实现分类来写,类的定义当中仅写成员函数的定义,而成员函数的实现则写到类的外面。
class Rectangle{
public:
double length;//长
double weight;//宽
void init(double length_, double weight_);//设置长和宽
double Area();//计算面积
double Perimeter(); //计算周长
};//分号不可省略
我们改写上面所示的代码,在类的实现当中仅写成员函数的定义。将成员函数写在类外,如下代码所示:
void Rectangle::init(double length_, double weight_) {
length = length_;
weight = weight_;
}
double Rectangle::Area() {
return length * weight;
}
double Rectangle::Perimeter() {
return 2 * (length * weight);
}
其基本格式为:
返回值类型 类名::函数名(参数表){
函数体
}
通过类名::
说明后面的函数为类的成员函数,而非普通函数。
类成员的可访问范围
在类的定义中,用以下的关键字来说明类成员的访问范围:
-
private:私有成员,只能在成员函数内访问
-
public:公用成员,可以在任何地方访问
-
protected:保护成员,具体用法以后讨论
-
以上三种关键字出现次数和先后顺序没有限制
-
没有说明访问范围的默认为private
class Student {
char name[10];//默认为private
public:
char sex;
void printInfo();
private:
int age;
protected:
time_t birthday;
};
例如在上面的学生类中,name属性没有指定访问范围,值默认缺省为private。
对于上面的类,假设我们有如下外部调用:
int main() {
Student stu;
stu.printInfo();//OK
stu.sex;//OK
stu.name;//错误,不能访问私有成员
stu.birthday;//错误,不能访问保护成员
}
我们仅能访问类的公有成员(public),不能访问其它属性的成员。
对于如下的内部调用:
void Student::printInfo() {
cout << age << endl;//OK
cout << birthday << endl;//OK
}
在类的成员函数内部,我们可以访问所有的成员。
注意
- private属性的成员只能通过类的成员函数访问,不能在类外部访问。
- 在类的成员函数内部,能够访问当前对象的全部属性、函数以及同类其它对象的全部属性、函数。
- 在类的成员函数以外的地方,就只能访问该类对象的公有成员(public)
设置私有成员的的机制叫“隐藏”,隐藏的目的时强制成员变量的访问一定要通过成员函数进行,使得成员变量不能被外部所访问到,确保了成员变量的安全,同时也易于以后的维护。
成员函数的重载与缺省
类的成员函数也可以进行重载以及缺省参数值,其用法与普通函数的重载与参数缺省相同。