理解C++面向对象程序设计中的抽象理论

  很多书在一开始就开始学习josephus问题,为了让大家前面学起来较为容易我把前面涉及到此问题的地方都故意去掉了,现在我们已经学习过了结构体和类,所以放在这里学习可能更合适一些。

  在正式开始学习之前我们先回顾一下如何利用数组和结构体的方式来解决,最后我们再看一下如何利用面向对象的抽象理念进行解决此问题的程序设计,相互对比,找出效率最高,最容易理解,最方便维护的程序来,说明利用面向对象的抽象理念进行程序设计的好处。

  josephus问题其实就是一个游戏,一群小孩围成一个圈,设置一个数,这个数是个小于小孩总数大于0的一个整数,从第一个小孩开始报数,当其中一个小孩报到你设置的那个数的时候离开那个圈,这样一来反复报下去,直到只剩下最后一个小孩的时候那个小孩就是胜利者,写程序来找出这个小孩。

  以下是数组方法:

  由于数组的限制我们必须预先假设好有多少个小孩,离开的小孩他自身设置为0来标记离开状态。

  代码如下:

#include <iostream> 
using namespace std; 
void main() 

  const int num=10; 
  int interval; 
  int a[num]; 

  for(int i=0; i<num; i++) 
  { 
    a[i]=i+1; 
  }  

    cout <<"please input the interval: "; 

  cin >>interval; 
  for(int i=0; i<num; i++) 
  { 

    cout <<a[i] <<","; 
  } 

    cout <<endl; 
 
int k=1; 
int p=-1; 
 

while(1) 

    for(int j=0;j<interval;) 

    { 
        p=(p+1)%num; 
        if(a[p]!=0) 
        { 
            j++; 

        } 
    } 
    if(k==num) 
    { 

        break; 
    } 
    cout<<a[p]<<","; 
    a[p]=0; 
    k++; 


cout <<"\nNo." <<a[p] <<" boy've won.\n"; 

cin.get(); 
cin.get(); 
}

  就数组解决来看,程序简短但效率不高可读性也不好,此代码没有什么特别之处主要依靠一个加1取模的方式来回到首位置,形成环链:p=(p+1)%num;。

  以下是利用结构体的方法解决josephus问题:

  当我们学过结构体后,我们了解到结构体自身的成员指针可以指向自身对象的地址的时候,我们很容易想到解决这个数学问题,用结构体来描述是再合适不过的了,用它可以很完美的描述环形链表。

  代码如下:

#include <iostream>   

#include <string>   
using namespace std;     
 

struct Children 

    int number; 
    Children *next; 
}; 
 
void show(Children *point,int num)//环链输出函数 


    for(int i=1;i<=num;i++) 
    { 
        cout<<point->number<<","; 

        point = point->next; 
    } 


 
void main() 


    int num;//孩子总数 

    int interval;//抽选号码 

    cout<<"请输入孩子总数:"; 
    cin>>num; 
    cout<<"请输入抽选号码:"; 
    cin>>interval; 
     

    Children *josephus = new Children[num];//设置圈的起点指针,并动态开辟堆空间用于存储数据 
 

    Children *point = josephus;//用于初化链表的指针,起始地址与josephus指针相同 
 
    for(int i=1;i<=num;i++) 
    { 

        point -> number = i; 

        point -> next = josephus + i % num;//利用+1取模的方式设置节点的next指针,当到最后的时候自动指向到第一个,形成环链 

        point = point->next;//将位置移到下一饿节点也就是下一个小孩的位置 
    } 
 

    show(point,num); 
 
    Children *cut_point; 

    point=&josephus[num-1];//把起始指针设置在最后一个节点,当进入循环的时候就会从0开始,这样就好让不需要的节点脱离 
    int k=0;//故意设置一个k观察while循环了多少次 

    while(point->next!=point)//通过循环不断的寻找需要放弃的节点 
    { 
        k++; 

        for(int i = 0;i<interval;i++)//找需要放弃的节点位置 

        { 
            cut_point=point;//存储截断位置指针 
            point=cut_point->next;//将point的指针移动到放弃的节点位置,此处也和while循环终止条件有关系 
        } 

        cut_point->next=point->next;//将截断出的next指针设置成放弃处节点的next指针,使放弃处节点也就是不需要的节点脱离 

        cout<<"k:"<<k<<endl; 

    } 
    cout<<"\n最后的赢家:"<<endl<<point->number<<endl<<point<<endl<<point->next<<endl; 

    delete[] josephus; 
    cin.get(); 
    cin.get(); 

}

  此代码较为难以理解的部分就是while循环的终止条件的设置,如果读者没有能够理解好这部分注意看下面的图式帮助学习。

  结构体的解法非常重要,对于我们全面理解面向对象的程序设计的抽象问题是基础,必须看明白我们才能够进行后面知识的学习,务必认真对待。

  这段代码比较前一个程序,可读性上有所加强,但仍然不太容易理解!

  为了更容易学习便于理解,我们的图例是以有两个小孩围成一圈,并且设置报数的数为1的情况来制作的。

  上面的两种解决Josephus问题的解决办法从代码上来看,都属于一杆子到底的解法,第二种从结构表达上优于第一种,但是这两个都属于纯粹的过程式程序设计,程序虽然简短,但很难让人看懂,程序的可读性不高,在我们没有学习面向对象的编程之前,聪明的人可能会把各各步骤分解出来做成由几个函数来解决问题。

  思路大致可以分为以下六个部分:

  1.建立结构

  2.初始化小孩总数,和数小孩的数

  3.初始化链表并构成环链

  4.开始通过循环数小孩获得得胜者

  5.输出得胜者

  6.返回堆内存空间

  从表上看这个程序为了便于阅读可以写成六个函数来分别处理这六个过程,的确,这么修改过后程序的可读性是提高了一大步,但是有缺点仍然存在,程序完全暴露在外,任何人都可以修改程序,程序中的一些程序作者不希望使用者能够修改的对象暴露在外,各对象得不到任何的保护,不能保证程序在运行中不被意外修改,对于使用者来说还是需要具备解决Josephus问题算法的能力,一旦程序变的越来越很,,每一个参与开发的程序员都需要通读程序的所有部分,程序完全不具备黑盒效应,给多人的协作开发带来了很大的麻烦,几乎每个人都做了同样的重复劳动,这种为了解决一个分枝小问题写一个函数,最后由很多个解决局部问题的函数组合成的程序我们叫做结构化程序设计,结构化编程较过程化编程相比可读性是提高了,但程序不能轻易的被分割解决一个个大问题的模块,在主函数中使用他们的时候总是这个函数调用到那个函数,如果你并不是这些函数的作者就很难正确方便的使用这些函数,而且程序的变量重名问题带来的困扰也是很让人头痛的……

那么面向对象的程序设计又是如何解决这些问题的呢?

  面向对象的程序设计的思路是这样的:

  程序 = 对象 +
对象 +对象..........

  这么组合而来的

  对于上面的josephus问题可以把问题分割成如下的方法进行设计(如下图所示)

  附件:点击下载(11K,
zip压缩文件)

  由上图可以看出:

  面向对象的程序设计是由类组合而成的,有类必然有类的对象,程序之间的交互主要是通过对象与对象之间的关系进行操作的。

  由于我们把josephus问题分解成了josephus类和ring类,在主函数中,用户只需要使用josephus类设计其对象明确知道Josephus类的外部接口函数也就是操作该对象的方法initial()就可以了,至于josephus的内部实现又是如何与Ring类进行操作的使用者一概不需要知道,只要拿来用知道接口和接口函数是什么就可以了,这样的程序设计很好的保护了各类成员数据的安全,主函数代码调用极其简单只有建立对象和调用对象方法的操作这两部而已,以后类一旦需要修改,只修改类体本身就可以,而主函数不需要做任何修改,这样就很好的做到了什么人做的事情什么人处理互不冲突。

  程序的代码如下,我把工程文件压缩了作为此帖的附件提供下载,希望读者仔细阅读仔细推敲,真正理解面向对象oop编程的特点和意图。

  主程序test4.cpp

#include <iostream> 

#include "josephus.h" 
using namespace std; 
 
void main() 

    Josephus a; 
    a.initial(); 

    cin.get(); 
    cin.get(); 
}

  josephus.h

class Josephus 

public: 
     Josephus(int num=10,int interval=1) 

     { 
         Josephus::num=num; 

         Josephus::interval=interval; 
     } 
     void initial(); 
protected: 

    int num; 
    int interval; 
};

  josephus.cpp

#include <iostream> 

#include "josephus.h" 
#include "ring.h" 
 
using namespace std; 
 
void Josephus::initial() 

    int num,interval; 
    cout<<"请输入孩子总数:"; 
    cin>>num; 
    if(num<2) 
    { 
        cout<<"孩子总数不能小于2,否则不能构成环链!"; 
        return; 
    } 
    cout<<"请输入抽选号码"; 
    cin>>interval; 
    if(interval<1|interval>num) 
    { 

        cout<<"请输入抽选号码不能小于1或者大于小孩总数!"; 

        return; 
    } 

    Josephus::num=num; 
    Josephus::interval=interval; 

    Ring a(num); 
    a.ShowRing(num); 
    cout<<endl; 
    for(int i=1;i<num;i++) 
    { 

        a.CountInterval(interval); 
        a.ShowWiner_loser(); 

        a.OutChild(); 
    } 
    cout<<endl<<"胜利者是:"; 

    a.ShowWiner_loser(); 
}

  ring.h

struct Children 

    int number; 
    Children *next; 
}; 
 
class Ring 

public: 

    Ring(int num) 
    { 

        josephus = new Children[num]; 
        point = josephus; 
        for(int i=1;i<=num;i++) 
        { 

            point->number = i; 

            point->next = josephus + i % num; 
            point=point->next; 

        } 
        point = &josephus[num-1]; 

    } 
    ~Ring() 
    { 
        delete[] josephus; 
    } 
    void ShowRing(int num); 
    void CountInterval(int interval); 

    void OutChild(); 
    void ShowWiner_loser(); 
protected: 
    Children *josephus; 

    Children *point; 
    Children *cut_point; 
};

  ring.cpp

#include <iostream> 

#include "ring.h" 
 
using namespace std; 
void Ring::ShowRing(int num) 


        point=josephus;//也可以写成point=point->next;但前着效率高一点点 
        for(int i=1;i<=num;i++) 

        { 
            cout<<point->number<<","; 

            point=point->next; 
        } 

        point=&josephus[num-1];//输出过后恢复point应该在的位置 

void Ring::CountInterval(int interval)//数小孩 


    for(int i=0;i<interval;i++) 
    { 

        cut_point = point; 
        point = cut_point->next; 
    } 

void Ring::OutChild() 

    cut_point->next = point->next;//将不要节点断离 

    point=cut_point; 

void Ring::ShowWiner_loser() 

    cout<<point->number<<","; 
}

  程序中需要注意的小地方是在这里

class Josephus 

public: 
     Josephus(int num=10,int interval=1) 

     { 
         Josephus::num=num; 

         Josephus::interval=interval; 
     } 
     void initial(); 
protected: 

    int num; 
    int interval; 
};

  代码中的

Josephus::num=num;
Josephus::interval=interval;

  使用域区分符的目的就是为了区分成员变量和局部变量Josephus(int num=10,int interval=1)

  相信读者认真读完程序认真理解后应该就可以理解面向对象程序设计的用意和好处了,切记认真推敲!

  大家看到面向对象程序设计的解决办法,可能觉得它的代码太多了,会怀疑它执行的效率是否足够好,呵呵!

  这里只能这么说,程序的效率不是单单看程序的长短来看的,优秀的程序应该是便于维护,关系清楚的,面向对象的程序设计其实和过程式或者是结构化程序设计的思路是不冲突的,在不同的地方使用不同的方法,优势互补才是正道!

 
 

《挑战30天C++入门极限》理解C++面向对象程序设计中的抽象理论的更多相关文章

  1. 《挑战30天C++入门极限》C++面向对象编程入门:构造函数与析构函数

        C++面向对象编程入门:构造函数与析构函数 请注意,这一节内容是c++的重点,要特别注意! 我们先说一下什么是构造函数. 上一个教程我们简单说了关于类的一些基本内容,对于类对象成员的初始化我们 ...

  2. 《挑战30天C++入门极限》C++面向对象编程入门:类(class)

        C++面向对象编程入门:类(class) 上两篇内容我们着重说了结构体相关知识的操作. 以后的内容我们将逐步完全以c++作为主体了,这也意味着我们的教程正式进入面向对象的编程了. 前面的教程我 ...

  3. 《挑战30天C++入门极限》在c/c++中利用数组名作为函数参数传递排序和用指针进行排序的例子。

        在c/c++中利用数组名作为函数参数传递排序和用指针进行排序的例子. 以下两个例子要非常注意,函数传递的不是数组中数组元素的真实值而是数组在内存中的实际地址. #include <std ...

  4. 《挑战30天C++入门极限》图例实解:C++中类的继承特性

        图例实解:C++中类的继承特性 整个c++程序设计全面围绕面向对象的方式进行,类的继承特性是c++的一个非常非常重要的机制,继承特性可以使一个新类获得其父类的操作和数据结构,程序员只需在新类中 ...

  5. 《挑战30天C++入门极限》入门教程:实例详解C++友元

        入门教程:实例详解C++友元 在说明什么是友元之前,我们先说明一下为什么需要友元与友元的缺点: 通常对于普通函数来说,要访问类的保护成员是不可能的,如果想这么做那么必须把类的成员都生命成为pu ...

  6. 《挑战30天C++入门极限》C++类静态数据成员与类静态成员函数

        C++类静态数据成员与类静态成员函数 在没有讲述本章内容之前如果我们想要在一个范围内共享某一个数据,那么我们会设立全局对象,但面向对象的程序是由对象构成的,我们如何才能在类范围内共享数据呢? ...

  7. 《挑战30天C++入门极限》C++类对象的复制-拷贝构造函数

        C++类对象的复制-拷贝构造函数 在学习这一章内容前我们已经学习过了类的构造函数和析构函数的相关知识,对于普通类型的对象来说,他们之间的复制是很简单的,例如: int a = 10; int ...

  8. 《挑战30天C++入门极限》引言

    作为一个长篇的C++入门教程,无论如何也应该有这么个引言,可是文笔并不好的我,想了很久也不知道该如何写...... 仔细想想,与其把这篇短文当作教程的引言,其实它更应该是一篇引导初学者步入C++殿堂的 ...

  9. 《挑战30天C++入门极限》C++的iostream标准库介绍(1)

        C++的iostream标准库介绍(1) 我们从一开始就一直在利用C++的输入输出在做着各种练习,输入输出是由iostream库提供的,所以讨论此标准库是有必要的,它与C语言的stdio库不同 ...

随机推荐

  1. PDF时间戳 服务器

    好用权威免费的PDF文件数字签名时间戳服务器URL http://tss.pki.gva.es:8318/tsa

  2. Linux系统内核正式进入5.0版本时代

    知名Linux内核开发人员兼维护人员Greg Kroah-Hartman今天宣布,Linux Kernel 4.20内核分支已经结束并督促用户尽快升级至更新的内核分支. Linux Kernel 4. ...

  3. 【转载】Javascript使用Math.random()随机数函数生成1至1000的随机数

    在Javascript代码编写过程中,有时候我们需要使用Js来生成随机数,清除ajax的get请求缓存的时候我们会带上一个随机数来解决此问题,此外在其他应用中也可能使用到随机数,在Javascript ...

  4. vue项目在ie中空白问题

    vue项目在ie浏览器中出现空白,f12打开后发现在body下面就只有一个div盒子,因此我们可以猜测就是js没有引入导致的,所有网上看了一些相关的才知道,在ie中无法解析es6或者版本更高的语法,所 ...

  5. 代替for-in 遍历对象

    object.keys() object.getOwnPropertyName()

  6. 设置yum自动更新

    [wang@brady ~]$ sudo yum -y install cronie yum-cron sudo vim /etc/yum/yum-cron.conf apply_updates = ...

  7. django使用admin站点上传图片

      Django有提供文件系统支持,在Admin站点中可以轻松上传图片.使用Admin站点保存图片,需要安装Python的图片操作包 pip install Pillow 1 配置 默认情况下,Dja ...

  8. git/github安装与使用教程

    新学习了Git和GitHub的使用,记一下笔记,也希望可以帮助他人,这里只是初级阶段,以后还会不断的修改和完善. 1.什么是git 开源的分布式版本控制系统,与svn比独特的地方: 1.解决了svn后 ...

  9. DRF 视图初识

    from django.shortcuts import render from rest_framework.generics import ListAPIView,CreateAPIView,Up ...

  10. CentOS上使用ntfs-3g挂载NTFS分区

    U盘做过系统盘,是NTFS格式的,Centos7竟然不识别,而且因为一些原因,我的服务器没有联网,只能用U盘 查过资料才知道Centos7上默认是不支持挂载NTFS格式的分区的,需要安装ntfs-3g ...