1、面向程序的基本特点
- 抽象:面向对象方法中的抽象,是指对具体问题(对象)进行概括,抽出一类对象的公共性质并加以描述的过程。抽象应该包括两个方面:数据抽象、行为抽象。
- 封装:封装就是将抽象得到的数据和行为相结合,形成一个有机的整体。封装的目的是隐藏具体实现,使用户只需要通过外部接口就能够直接访问,不必了解类的细节。这增强了抽象性、数据安全性。
- 继承:继承就是将一般与特殊的关系搞清楚。比如:教师类型可以从人类类型中继承。继承允许在保持原始类型的基础上,进行更加具体、详细的说明。
- 多态:多态性指的是一段程序能够处理多种数据类型(对象)的能力。多态分为真的多态(数据类型转换、函数重载)、假的多态(包含多态—虚函数、类型参数多态—模板)。
2、类的定义
在面向对象程序开发中,程序的基本模块是由类构成的。类是对逻辑上相关的函数与数据的封装,它是对问题的抽象描述。
设计类就是设计类型:
- 此类型的"合法值"是什么?
- 此类型应该有什么样的函数和操作符?
- 新类型的对象应该如何被创建和摧毁?
- 如何进行对象的初始化和赋值?(使用构造函数进行初始化)
- 对象作为函数的参数如何进行值传递?
- 谁将实用此类型的对象成员?
类的定义、对象的创建、对象的存储
当定义一个类(数据类型)时,需要定义的数据结构和相关的操作。
#include <iostream>
using namespace std;
class Clock // 类由class关键字+类型进行定义
{
private: // 私有成员,只能在类中被访问
int hour, minute, second;
public: // 公有成员,可以在类外被访问,函数的具体实现在类外部
void setTime(int newH=0, int newM=0, int newS=0);//
void showTime();
};
void Clock::setTime(int newH, int newM, int newS)
{
hour = newH;
minute = newM;
second = newS;
}
void Clock::showTime()
{
cout << hour << ":" << minute << ":" << second <<endl;
}
int main(int argc, char const *argv[])
{
Clock myclock;
// 定义钟表类型的变量,该变量的存储空间中只用于存放数据成员,
// 函数成员不在每一个对象中存储副本,每个函数的代码在内存中只占据一份空间。
cout << "set first time: " << endl;
myclock.setTime(); // 对象通过.来访问成员函数
myclock.showTime();
cout << "set second time: " << endl;
myclock.setTime(10, 10, 10);
myclock.showTime();
return 0;
}
一个对象的this指针并不是对象本身的一部分,不会影响sizeof(对象)的结果。this作用域是在类内部,当在类的非静态成员函数中访问类的非静态成员的时候,编译器会自动将对象本身的地址作为一个隐含参数传递给函数。也就是说,即使你没有写上this指针,编译器在编译的时候也是加上this的,它作为非静态成员函数的隐含形参,对各成员的访问均通过this进行。 例如,调用myclock.setTime(10, 10, 10) <==> SetMonth(&myclock, 10,10,10),this帮助完成了这一转换 。
对象的存储空间中值储存了数据成员,没有储存函数成员。
3、对象的初始化
根据类定义的每个对象与基本变量类似,如Clock相当于int,对象myclock相当于变量a。为了保证统一,我们也需要对于对象进行初始化。在特定对象使用结束时,还经常需要进行一些清理工作。这些操作有构造函数和析构函数来完成。
3.1 构造函数
每一个变量都要占用一定的内存空间,在声明一个变量并初始化时,就意味着在给变量分配内存空间,在其中写入初始变量值。
对象也是一样的。但是不同类型的对象,数据成员各不相同,编译器不知道如何产生代码来实现初始化,因此就需要我们自己来编写初始化函数——即构造函数。构造函数就是在对象被创建时利用特定的值构造对象,将对象初始化为一个特定的状态。
- 函数名与类名相同(构造函数在创建对象是自动调用的,因此名称统一为类名)
- 不能定义返回值,没有return语句(构造函数的目的是进行初始化,返回值没有意义)
- 可以有形参,也可以没有形参
- 可以是内联函数(省去函数调用步骤,加快运行效率)
- 可以重载(满足不同的初始化要求、有参、无参等)
- 可以带参数默认值
3.2 构造函数的类型
3.2.1 默认构造函数(无形参构造函数)
由编译系统的生成。触发条件:在定义类中没有定义构造函数,一旦定义的任何构造函数,编译器都不再生成默认构造函数。
// 在类中的声明
Clock(); //默认构造函数
// 在类外的定义
Clock::Clock():hour(8), minute(8), second(8){} //默认构造函数函数体
3.2.2 普通构造函数(带参数)
// 在类中的声明
Clock(int, int, int); // 带参构造函数
// 在类外的定义
Clock::Clock(int newH, int newM, int newS)://构造函数函数体
hour(newH), minute(newM), second(newS){}//大括号前面是初始化列表,大括号内是函数体
3.2.3 委托构造函数
当在一个类中,多个构造函数的函数体相同,形参列表不同是,此时委托构造函数就可以调用其他构造函数完成初始化。
不使用委托构造函数:
Clock::Clock(int newH, int newM, int newS)://构造函数函数体
hour(newH), minute(newM), second(newS){}//大括号前面是初始化列表,大括号内是函数体
Clock::Clock():hour(8), minute(8), second(8){} //默认构造函数函数体
使用委托构造函数:
Clock(int newH, int newM, int newS)://构造函数函数体
hour(newH), minute(newM), second(newS){}//大括号前面是初始化列表,大括号内是函数体
Clock():Clock(0,0,0){}//这里是写在函数体内的,不是外面定义的地方
可以看到,默认构造函数带用了带参的构造函数,完成了初始化,实现了代码的重用。
3.2.4 复制构造函数
复制构造函数是一种特殊的构造函数,目的是实现对象之间的直接幅值,就像整型变量一样可以直接赋值。复制构造函数的形参是对象的引用(不然会造成递归定义),例如Clock& myclock。系统可以生成一个默认的复制构造函数——创建一个除地址外完全一样的对象。
// 类内拷贝构造函数的声明
Point(Point& p);
// 如果拷贝构造函数原型为Point(Point p);,则在进行参数传递过程中需要调用拷贝构造函数
// 这样就造成了递归调用
// 类外拷贝构造函数的定义
Point::Point(Point& p)
{
x = p.x;
y = p.y;
}
复制构造函数被调用的三种情况
- 定义一个对象时,以本类另一个对象作为初始值,发生复制构造;
- 如果函数的形参是类的对象,调用函数时,将使用实参对象初始化形参对象,发生复制构造。
- 如果函数的返回值是类的对象,函数执行完成返回主调函数时,将使用return语句中的对象初始化为一个临时无名对象,传递给主调函数,此时发生复制构造。这种情况也可以通过移动构造避免不必要的复制。
4、对象的清理
析构函数作用是负责对象被删除前的一些清理工作
在对象生存期即将结束时,析构函数将会被调用
析构函数不接受任何参数,如果不进行显示说明,系统也会生成一个函数体为空的隐含析构函数。