面向对象技术

面向对象技术几大关键特征:

  • 数据抽象,封装
    • 类实现
  • 继承
    • 类派生,能更容易地定义与其他类相似却又不相同的新类,并编写忽略相似类型之间区别的程序
  • 动态绑定
    • 编译器在运行时决定使用基类中定义的函数还是派生类中定义的函数

抽象

抽象是对具体问题(对象)进行概括,提炼出这一类对象的公共性质并加以描述的过程
先注意问题的本质及描述,其次是实现过程或细节

数据抽象:描述某类事物(对象)共有的属性或状态
行为抽象:描述某类事物(对象)共有的行为特征或具有的功能

抽象是相对的,而非绝对的
研究问题时,如果侧重点不同,可能产生不同的抽象结果
解决同一问题时要求不同,也可能产生不同的抽象结果

开发一个人事管理软件,关心的是员工的姓名,性别,工龄,工资,工作部门等相关信息
如果开发学籍管理系统,那么关心的是学生的姓名,性别,年龄,籍贯,所在学院等相关信息

抽象定义了一个事物的本质,从软件设计的角度而言,使用类的封装机制来实现抽象

类也是一种自定义的构造数据类型

定义和使用类的基本过程

  • 对事物进行抽象
  • 根据抽象结果定义类的特性
  • 实现类中成员函数的逻辑
  • 定义类的实例,使用类的公有成员

类的声明

类是一种抽象数据类型
抽象数据类型是指一组特定的数据类型和在该数据之上的一组操作
这里与抽象数据类型相对应的基本数据类型包括:整型,浮点型,字符,集合,序列,字典,栈,队列,优先队列,图,树等已经定义的数据类型/数据结构
另外数组,链表,哈希等都是具体的可以直接拿来用,除基本数据类型外不能直接拿来用的都是抽象的(取决于所在集合中是否定义这一类型)

声明的基本形式如下:形式与结构体相似

class 类名{数据成员;函数成员;};

class关键字用于声明一个类,类名通常大驼峰命名;
成员数据、函数分别用于表达数据抽象和行为抽象

例:声明时钟类

class Clock
{
    char Name[10],Supplier[20];
    int Hour,Minute,Second;
    void SetTime(int h,int m,int s);
    void CheckTime(int h,int m,int s);
    void ShowTime();
};  

访问控制

“类内”和“类外”。在类的声明以内成为类内,类的声明以外称为类外
数据封装的目的就是信息隐藏。为达到信息隐蔽,在c++类中,并非所有成员都是对外可见的(类外可以访问)
为了控制部分成员类外不可用,部分可用,可通过设置成员的访问控制属性实现对类成员的访问控制
这些控制属性有:public,private,protected,如:

class ClassName{public:公有成员;private:私有成员;protected:保护成员;};

public成员在类外部可见可用,private和protected成员在类外部不可见不可用,如果不特别指定默认成员是私有的
关键字public后声明的公有成员是类与外部的接口(对外提供的功能/行为/服务等的操作集)

任何类内,类外函数均可访问公有数据和函数,如:

public:
char Name[10]; 公有数据,时钟名字
void ShowTime(); 公有函数,展示时间

关键字private后声明的私有成员只允许本类中函数访问,任何类外函数都不能访问,例:

private:
int Hour,Minute,Second; 私有数据,时间单位的组成:时,分,秒
void CheckTime(int h,int m,int s); 私有函数,检查时间格式

关键字protected后声明的保护成员对类外成员的情况与private类似,两者区别表现在继承/派生时对派生类的影响不同,例:

protected:
char Supplier[20]; 保护数据,时钟出产厂商
void SetTime(int h,int m,int s); 保护函数,设置时间

声明类时,不同访问属性的声明顺序可以任意,并且同一修饰访问属性可以多次声明,例:

class ClassName{
public:
    int a,b;
public:
    int add(int a,int b);
};

一个成员只能有一种访问属性,否则会出现歧义,编译器会报错。不同访问属性可以声明多次,并且顺序是任意的。

另外比较有意思的一点是:虽然无法通过在类外使用对象(或对应类型的指针、引用)直接访问其私有、保护数据,但假如你明确知道对象的构成或内存分布,那么还是可以直接访问到内存某一位置的数据,例:

#include<iostream>
unsing namespace std;
class A{ 
public:
    A(int a,int b,int c);
private:
    int m_a;
    int m_b;
    int m_c;
}; 
A::A(int a,int b,int c):m_a(a),m_b(b),m_c(c){} 
int main(){
    A obj(10,20,30); 
    int a=*(int*)&obj;  
    int b=*(int*)(&obj+sizeof(int));  
    int c=*(int*)(&obj+2*sizeof(int)); 
    cout<<"a="<<a<<",b="<<b<<",c="<<c<<endl;  //可能输出正确结果:a=10,b=20,c=30 
    //主要是对象内存字节分布中可能存在碎片,如果真的想详细研究这个问题的话也许可以按位读吧,总之问题先留着暂时略过
    return 0;   
}

从此可以看出访问限制是针对对象语法层次的,大概对取成员运算符.和->起作用,实际内存还是可以读取

类的实现

实现一个类,就是按照所设定的功能语义去实现类中的每个成员函数
可以在类内实现成员函数,例:

class Clock
{
    char Name[10],Supplier[20];
    int Hour,Minute,Second;
    void SetTime(int h,int m,int s)       //类内实现函数,编译器优先按内联函数处理
    {
        Hour=h,Minute=m,Second=s;
    }
    void CheckTime(int h,int m,int s);
    void ShowTime();
};

也可以在类内只声明函数原型,在类外实现成员函数,例:

void Clock::ShowTime()                    //::作用域符标识成员的作用域,限定成员的作用范围,如果前面不指定范围的话默认是全局的
{
    cout<<"Current Time:";
    cout<<Hour<<':'<<Minute<<':'<<Second<<endl;
} 

类的使用——对象

与结构体非常相似,类是一种数据类型,而定义的类类型的变量称为类的实例,即对象
定义对象的基本格式:

类名 对象名 如:Clock aclock;

现实世界的事物抽象出事物的种类,映射到计算机世界就是类,类的实例化就是对象,对象就是现实世界各种事物在计算机中的映射

访问类成员的方法

通过对象来访问成员,基本格式:

对象名.公有成员函数名(实参列表); 如:aclock.ShowTime();
对象名.公有数据成员 如:aclock.Name

通过对象指针访问成员,基本格式:

对象指针->公有成员函数名(实参列表); 如:clock *c=aclock;c->ShowTime();
对象指针->公有数据成员 如:c->Name

定义类变量时,每个变量包含了类中各数据成员的存储空间,但共享类中的成员函数(成员函数入口在访问时被调用或替换,不会在每个变量中都存储)
每个成员函数的功能相同,只有数据不同,例:

class Clock{
private:
    int Hour;
public:
    void SetTime(int h);
    void addHour(int h);
    void ShowTime();
};
void Clock::SetTime(int h){Hour=h;}
void Clock::addHour(int h){Hour+=h;}
void Clock::ShowTime(){cout<<"Current Time:";cout<<Hour<<" ";}
int main(){
    Clock clock1,clock2,*c1=&clock1,*c2=NULL;
    clock1.SetTime(15),clock2.SetTime(16);
    clock1.addHour(7),clock2.addHour(9);
    clock1.ShowTime(),clock2.ShowTime();
    c1->SetTime(18),c2=c1;
    c2->SetTime(19);
    c1->ShowTime();
    c2->ShowTime();
}
// 输出结果:Current Time:22 Current Time:25 Current Time:19 Current Time:19

通过类对象访问类也是在类外访问,因此不能访问私有和保护成员,只有类内的函数才是类自身的一部分

类与对象示例程序:实现简单的int数组类
写代码前:抽象

  • 数据抽象
    • 数组大小,数组所占据的内存区(首地址)
  • 行为抽象
    • 能够获得数组的大小,能够将数据保存到数组中,能够获得数组中保存数据的最大值,最小值
#include<iostream>
using namespace std;
const int n=50;
class IntArray
{
private:
    int size,*data;
public:
    void SetArray(int len,int *in);
    int GetSize();
    void Append(int pos,int value);
    int GetMaxMin(int condition);
};
void IntArray::SetArray(int len,int *in)     定义一个初始化函数(此例中没有定义构造函数)
{
    int i;
    size=len;
    data=new int[len];
    for(i=0;i<len;i++)data[i]=in[i];
}

int IntArray::GetSize()
{
    return size;
}
void IntArray::Append(int pos,int value)
{
    if(pos<size&&pos>=0)data[pos]=value;
}
int IntArray::GetMaxMin(int condition)
{
    int i,max=*data,min=*data;
    for(i=0;i<size;i++)
    {
        if(max<data[i])max=data[i];
        if(min>data[i])min=data[i];
    }
    if(condition==0)return min;
    else return max;
}
int main()
{
    IntArray array;
    int i,len,value,in[n];
    cin>>len;
    for(i=0;i<len;i++)cin>>in[i];
    array.SetArray(len,in);
    cout<<"数组长度为:"<<array.GetSize()<<endl;
    for(i=0;i<array.GetSize();i++)
    {
        cin>>value;
        array.Append(i,value);
    }
    cout<<"最大值是:"<<array.GetMaxMin(1)<<' '<<"最小值是:"<<array.GetMaxMin(0);
}