理解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. 一张图看懂SharpImage

    通过下面的图片,可以瞬间看懂整个类库的脉络.图片比较大,如果看不清,可

  2. 前端开发 vue,angular,react框架对比1

    转载自:https://www.cnblogs.com/hubgit/p/6633214.html 首先,我们先了解什么是MVX框架模式? MVX框架模式:MVC+MVP+MVVM 1.MVC:Mod ...

  3. 使用springboot实现一个简单的restful crud——02、dao层单元测试,测试从数据库取数据

    接着上一篇,上一篇我们创建了项目.创建了实体类,以及创建了数据库数据.这一篇就写一下Dao层,以及对Dao层进行单元测试,看下能否成功操作数据库数据. Dao EmpDao package com.j ...

  4. Ubuntu 18.04 LTS版本 谷歌拼音输入法安装

    为何安装? 自带IBUS框架对中文支持不稳定 采用对中文支持稳定的fcitx框架 如何安装? 步骤如下: 卸载自带IBUS框架    命令:sudo remove ibus 安装fcitx框架    ...

  5. 刚接触HTML5应该先学哪里才好?

    好吧,话不多说,直接来点干货吧! 刚接触html的小白都感觉摸不着头脑?应该怎么学习呢,其实HTML5可能对于还没有接触过的小白来说会比较的难,听起来也比较新颖.这是个什么骚东西!其实不然,这个就是构 ...

  6. Availability-group DDL operations are permitted only when you are using the master database. Run the USE MASTER command, and retry your availability-group DDL command.

    Question: SQL SERVER  alwayson在向AG中添加DB最后一步在副本中将此DB添加入AG时报错: Availability-group DDL operations are p ...

  7. python代码组织及模块使用

    python代码组织 python和其他编程语言一样,采用包管理代码,一个包中可包含许多模块. 使用模块最大的好处是大大提高了代码的可维护性.其次,编写代码不必从零开始.当一个模块编写完毕,就可以被其 ...

  8. Flink原理(二)——资源

    前言 本文主要是想简要说明Flink在集群部署.任务提交.任务运行过程中资源情况,若表述有误欢迎大伙留言分享,非常感谢! 一.集群部署阶段 集群部署这里指的是Flink standalone模式,因为 ...

  9. Ajax -异步请求 -jquery中ajax分类 -第一层 $.ajax -第二层($.get /$.post) -第三层($.getJson/$.getScript) -相应演示

    Ajax 1.标准请求响应时浏览器的动作(同步操作) 1.1浏览器请求什么资源,跟随显示什么资源2.ajax:异步请求. 2.1局部刷新,通过异步请求,请求到服务器资源数据后,通过脚本修改页面中部分内 ...

  10. dns-prefetch应用好,网上速度能提高一半!

    今天一个朋友给我说在网页上添加dns-prefetch,网页访问速度能提高,于是我百度查询关于dns-prefetch. DNS Prefetch,即DNS预获取,是前端优化的一部分.一般来说,在前端 ...