分类

课内:
不限
类型:
不限 毕业设计 课程设计 小学期 大作业
汇编语言 C语言 C++ JAVA C# JSP PYTHON PHP
数据结构与算法 操作系统 编译原理 数据库 计算机网络 软件工程 VC++程序设计
游戏 PC程序 APP 网站 其他
评分:
不限 10 9 8 7 6 5 4 3 2 1
年份:
不限 2018 2019

资源列表

  • 基于JAVA的文件系统

    一、项目需求在内存中开辟一个空间作为文件存储器,在其上实现一个简单的文件系统。退出这个文件系统时,需要该文件系统的内容保存到磁盘上,以便下次可以将其恢复到内存中来。
    二、具体技术细节
    文件存储空间管理可采取显式链接(如FAT)或者其他方法。(即自选一种方法)
    空闲空间管理可采用位图或者其他方法。如果采用了位图,可将位图和FAT表合二为一
    文件目录采用多级目录结构。至于是否采用索引节点结构,自选。目录项目中应包含:文件名、物理地址、长度等信息。同学可在这里增加一些其他信息

    三、开发工具
    环境: intellij
    语言: Java

    四、文件系统管理方案4.1 存储空间概述
    盘区数量:10
    盘区大小:1024KB

    4.2 存储空间管理方式
    FAT:在本次项目中,我的文件系统管理方案采用了显式链接的方式,在每一个盘区都开了一个文件用来存储当前盘区所有存储块的使用情况。并记录盘区内所有文件所占用内存块的情况
    优点:不存在内外内存碎片,最大程度地利用了盘区空间

    4.3 空闲空间管理方式
    位图:在本次项目中,我的文件系统空闲空间管理方案采用了位图。使用一串0和1来标识内存块的状态。0为未使用,1表示已经被占用。创建文件时从最前方开始检索空闲块,确定使用后将0置为1
    优点:一目了然,易于管理

    4.4 文件目录结构
    多级目录结构
    4.5 FCB4.5.1 概述该文件系统的FCB将直接显示在生成的txt文本中,以文本头的形势呈现。需要编辑内容可以将内容写在FCB下方的划分线内。
    4.5.2 内容
    大小
    文件名
    地址
    最近更新时间

    五、程序操作指南5.1 特殊文件说明5.1.1 BitMap&&Fat.txt
    该文件存储了盘区的存储块的状态,用0表示还未分配,1表示已经分配
    该文件存储了盘区内存在的文件按照链接方式实现的的内存分配情况,列出了对应内存块的位置

    5.1.2 recover.txt该文件是当前盘区的备份文档,在再次启动该应用程序的时候通过读取该文件的信息还原出上次的运行状态,从而实现复盘
    5.2 程序概述5.2.1 界面构成
    搜索框
    文件树
    显示框
    底部盘信息栏

    5.2.1.1 搜索框搜索框位于顶部可以在搜索栏中键入文件名或者文件夹名然后点击“start”来进行检索,如果检索成功将直接打开,失败则弹框提醒
    5.2.1.2 算法采用dfs深度搜索的方式
    // Search a file public boolean searchFile(String fileName, File parent){ File [] files = parent.listFiles(); for (File myFile:files){ if (myFile.getName().equals(fileName)){ try { if(Desktop.isDesktopSupported()) { Desktop desktop = Desktop.getDesktop(); desktop.open(myFile); return true; } } catch (IOException e1) { JOptionPane.showMessageDialog(null, myFile.getPath() + " Sorry, some thing wrong!", "Fail to open", JOptionPane.ERROR_MESSAGE); return true; } } if (myFile.isDirectory() && myFile.canRead()){ if(searchFile(fileName, myFile)){ return true; } } } return false; }
    5.2.1.3 文件树
    文件树位于左侧,呈现文件的目录结构,详情见目录结构
    5.2.1.4 显示框
    显示框位于程序右侧,用于显示文件的名称、路径、类型、大小和最近修改时间
    5.2.1.5 底部盘信息栏
    底部盘信息栏位于程序的底部,用于显示当前选中盘区的名称、已使用空间、未使用空间、文件数量
    5.2.2 菜单项
    create a file(生成一个文件)
    create a dir(生成一个文件夹)
    delete(删除一个文件或文件夹)
    format(格式化)
    rename(重命名)

    5.3 注意事项
    必须先选择一个系统内的文件夹作为模拟工作目录
    文件操作需要先选中一个文件树中的节点右键才能进行操作
    本程序重在模拟,并不是真正地为文件开了这么大的空间
    仅支持生成txt文件,在输入文件名时不需要输入.txt
    相同目录下已经存在该文件重命名将失败
    文本中将自动生成FCB,不支持用户去修改FCB
    非法输入都将导致文件生成失败
    如果存档文件recover.txt被破坏,将无法在再次打开该程序时恢复盘区信息

    六、文件系统实现功能6.1 文件目录文件目录将呈现在程序的左侧,以十个以数字命名的盘为基目录。
    点击文件夹左侧箭头可以将文件夹展开
    当执行创建和删除文件等操作,必须重新展开一次父目录才能刷新

    6.2 创建文件6.2.1 概述选中某一文件后,右键展开功能选项,选择第一项生成文件或第二项就会弹出对话框要求键入文件名和文件大小。文件生成成功之后只要重新展开父目录就可以看到新生的文件。双击程序右侧的显示区的文件就可以打开该文件看到它的FCB
    6.2.2 实现方法通过调用File类的createFile()方法来实现,文件创建成功之后将刷新FAT和位图,如果存储空间不足或是输入非法将导致文件生成失败
    // create a file public boolean createFile(String filePath){ boolean result = false; File file = new File(filePath); if(!file.exists()){ try { result = file.createNewFile(); } catch (IOException e) { e.printStackTrace(); } } return result; }
    当执行创建文件操作,必须重新展开一次父目录才能刷新

    6.3 删除文件6.3.1 概述选中某一文件后,右键展开功能选项,选择第三项删除即可删除所选项
    6.3.2 实现方法通过调用File类的delete()删除 针对文件夹需要递归删除,文件删除成功之后将刷新FAT和位图
    // delete a file public boolean deleteFile(String filePath){ boolean result = false; File file = new File(filePath); if(file.exists() && file.isFile()){ result = file.delete(); } return result; }
    当执行删除文件操作,必须重新展开一次父目录才能刷新

    6.4 重命名(更改当前目录)6.4.1 概述选中某一文件后,右键展开功能选项,选择第四项重命名即可对文件或文件夹进行重命名
    6.4.2 实现方法通过调用File类的renameTo()方法进行重命名,如果相同目录下有相同的文件,则重命名将失败
    当执行重命名文件操作,必须重新展开一次父目录才能刷新

    6.5 打开文件双击程序中右侧的显示面板中的文件即可打开对应文件
    6.6 关闭文件打开文件之后点击文件上方的关闭按钮就可以将对应文件关闭
    6.7 写文件打开文件之后在FCB的内容下方输入文本即可
    6.8 读文件双击程序右侧的显示面板中的文件即可打开对应文件进行查看
    6.9 格式化6.9.1 概述选中某一文件夹之后右键选择第四项格式化就可以将该文件夹格式化
    6.9.2 实现方法即递归删除该目录下的所有文件并更新FAT和位图
    七、 数据结构盘区(Block)
    public class Block { // 名称 private int blockName; // 盘区内的文件 private File blockFile; // 位图存储文件 private File blockBitMap; // 恢复文件 private File recover; // 位图存储文件书写器 private FileWriter bitWriter; // 恢复文件书写器 private FileWriter recoverWriter; // 文件数量 private int fileNum; // 盘区已占用空间 private double space; // 内存块数组 public int [][] bitmap = new int[32][32]; // 文件和内存块对应表 private Map<String, int[][] > filesBit = new HashMap<String, int[][]>(); // 文件列表 private ArrayList<File> files = new ArrayList<File>(); public Block(int name, File file, boolean rec) throws IOException { // 初始化 } public File getBlockFile(){ return blockFile; } public void putFCB(File file, double capacity) throws IOException { // 将FCB写入文件 } public void rewriteBitMap() throws IOException { // 重写位图存储文件 } public void rewriteRecoverWriter() throws IOException{ // 重写恢复文件 } public boolean createFile(File file, double capacity) throws IOException { // 在盘区内创建文件 } public boolean deleteFile(File file, double capacity){ // 在盘区内删除文件 } public boolean renameFile(File file, String name, double capacity) throws IOException { // 重命名盘区内的文件 } public int getFileNum() { return fileNum; } public double getSpace() { return space; }}
    1 评论 103 下载 2018-11-03 00:44:21 下载需要3点积分
  • 基于JAVA的生产者消费者问题

    一、需求分析为了更好地理解进程同步的机制和过程,决定设计实现生产者消费者问题的解决,以实现进程的同步控制。
    题目描述:有n个生产者在生产产品,这些产品将提供给m个消费者去消费,为了使生产者和消费者能并发执行,在两者之间设置一个具有k个缓冲区的缓冲池,生产者将它生产的产品放入一个缓冲区中,消费者可以从缓冲区中取走产品进行消费,显然生产者和消费者之间必须保持同步,即不允许消费者到一个空的缓冲区中取产品,也不允许生产者向一个已经放入产品的缓冲区中再次投放产品。
    由此为题,编程实现:输入生产者个数、消费者个数、缓冲区个数、每个生产者生产产品的个数,实现输出:生产者消费者同步执行情况下的具体执行过程。
    二、概要设计为了简化编码过程,将题目转变成由线程实现同步,以达到相同的目标,并采用Java实现,在控制台将程序执行的整个过程生产者消费者的执行过程输出出来。
    设计初步方案:使用Java的Thread来实现线程的生成,并继承Thread类,重写run()方法,来设定线程中的执行代码。
    生产者流程图

    消费者流程图

    三、详细设计3.1 设计思想同步机制,首先采用Java的synchronized来实现对缓冲区的互斥访问,再设置一个信号量来实现对缓冲区为空和为满的状态标记。生产者通过在synchronized同步代码块中先对缓冲区是否为满作出判断,若缓冲区为满,将当前线程添加到缓冲区的等待列表中,线程阻塞,并且在每次生产完一件产品之后唤醒缓冲区的所有等待列表;消费者通过在synchronized同步代码块中先对缓冲区是否为空作出判断,若缓冲区为空,将当前线程加入到缓冲区的等待列表中,并且在每次消费完一件产品之后唤醒缓冲区的所有等待列表。
    3.2 模块设计3.2.1 消费者线程实现类public class Consumer implements Runnable { private String name;// 线程名称 private List<Object> list;// 共享区 public Consumer(String name, List<Object> list) { this.name = name; this.list = list; } @Override public void run() { loop: while (true) { // 控制权在消费者 synchronized (list) {// 同步块,实现线程互斥 while (list.size() <= 0) // 没产品了 try { // 进程还未完成 if (!Main.completed) list.wait(); else // 进程完成,跳出外循环,结束该线程 break loop; } catch (InterruptedException e) { e.printStackTrace(); throw new RuntimeException(e); } // 缓冲区还有产品 list.remove(list.size() - 1); System.out.println("线程:" + name + " 消费了1个产品,原来有: " + (list.size() + 1) + " ,当前产品总数:" + list.size()); try { Thread.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); } // 唤醒其他线程 list.notifyAll(); } } }}
    3.2.2 生产者线程实现类public class Producer implements Runnable { private String name;// 线程名称 private List<Object> list;// 共享区 private int productCount;// 每个生产者生产产品数 private int producerCount;// 生产者总个数 public Producer(String name, int productCount, List<Object> list, int producerCount) { this.name = name; this.list = list; this.productCount = productCount; this.producerCount = producerCount; } @Override public void run() { int i = 0; // 生产productCount个产品 while (i < productCount) { // 控制权在生产者 synchronized (list) {// 同步块,实现线程互斥 while (list.size() >= Main.bufferCount) // 缓冲区满,等待 try { list.wait(); } catch (InterruptedException e) { e.printStackTrace(); throw new RuntimeException(e); } // 缓冲区还有空间 list.add(new Object()); System.out.println("线程:" + name + " 生产了1个产品,原来有: " + (list.size() - 1) + " ,当前产品总数:" + list.size() + " ,还需生产产品数:" + (productCount - i - 1)); try { Thread.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); } // 唤醒其他线程 list.notifyAll(); } i++; } // 生产者完成任务,刷新标记位 synchronized (Main.producerCompleted) { Main.producerCompleted.producerCompleted++;// 生产完成,记录标记 // 所有生产者均完成任务 if (Main.producerCompleted.producerCompleted == this.producerCount) { // 进程标记完成 Main.completed = true; // 通知消费者结束线程 synchronized (list) { list.notifyAll(); } } } }}
    3.2.3 进程结束信号量public class Complete { public int producerCompleted;// 生产完成的生产者数}
    3.2.4 执行代码public class Main { public static int bufferCount = 10;// 缓冲区,默认为10 public static List<Object> list = new ArrayList<Object>();// 生产好的产品列表 public static Complete producerCompleted = new Complete();// 标记生产完成了的生产者数目 public static boolean completed = false;// 标记本进程是否完成任务,完成了通知消费者结束线程 public static void main(String[] args) { producerCompleted.producerCompleted = 0;// 标记生产完成了的生产者数目,刚开始为0 Scanner scanner = new Scanner(System.in); System.out.println("请输入生产者个数:"); int producerCount = scanner.nextInt(); System.out.println("请输入消费者个数:"); int consumerCount = scanner.nextInt(); System.out.println("请输入缓冲区个数:"); bufferCount = scanner.nextInt(); System.out.println("请输入每个生产者生产产品个数:"); int productCount = scanner.nextInt(); for (int i = 1; i <= producerCount; i++) { Thread productThread = new Thread(new Producer("生产者" + i, productCount, list,producerCount)); productThread.start(); } for (int i = 1; i <= consumerCount; i++) { Thread consumerThread = new Thread(new Consumer("消费者" + i, list)); consumerThread.start(); } }}
    四、调试分析测试数据

    生产者数量:10
    消费者数量:5
    缓冲区数量:10
    每个生产者生产产品数:3

    执行结果



    结果分析
    每当缓冲区产品数到了10,也就是满,生产者就会停下来让消费者消费,每当缓冲区产品为0时,消费者就会停下来等待生产者生产产品。
    五、用户使用说明在Eclipse中点击 File——Import——Existing Projects into WorkSpace——Browser——找到ProductorConsumer文件夹的路径位置,将本工程导入到Eclipse中,然后右键工程 Run As Java Application,程序便开始执行,根据控制台文字提示输入相应的生产者数量,消费者数量,缓冲区个数,每个生产者生产产品数,便可看见程序的执行过程。
    环境说明:本工程建立在 JDK1.7 环境下。
    1 评论 6 下载 2018-11-03 00:39:21 下载需要4点积分
  • 基于JAVA的内存管理模拟

    一、需求分析为了更好地理解操作系统内存分配和管理的过程和机制,决定通过编程模拟操作系统内存分配的过程,以更好的理解操作系统内存分配过程中的具体执行流程。
    题目描述如下:
    编写一个程序,包括两个线程,一个线程用于模拟内存分配活动,另一个用于跟踪第一个线程的内存行为,要求两个线程之间通过信号量实现同步,模拟内存活动的线程可以从一个文件中读出要进行的内存操作。每个内存操作包含如下内容:

    时间:每个操作等待时间
    块数:分配内存的粒度
    操作:包括保留一个区域、提交一个区域、释放一个区域、回收一个区域、加锁与解锁一个区域。可将它们的编号放置于一个文件中

    保留是指保留进程的虚地址空间,而不分配物理地址空间提交是指在内存中分配物理地址空间回收是指释放物理地址空间,而保留进程的虚地址空间释放是指将进程的物理地址与虚拟地址空间全部释放
    大小:块的大小
    访问权限:共五种 PAGE_READONLY, PAGE_READWRIYE, PAGE_EXEXUTE, PAGE_EXEXUTE_READ, PAGE_EXEXUTE_READWRIYE

    二、概要设计根据题意,要实现两个线程,通过内存分配线程从操作文件中读取操作参数,实现模拟内存分配的过程,监听线程则监视内存分配线程的执行过程,对内存分配过程中的模拟内存详细信息进行输出。采用Java的Thread实现对线程的创建,并通过重写run()方法,来设定线程中的执行代码。
    内存分配线程流程图

    内存监视线程流程图

    三、详细设计3.1 设计思想进程的虚拟地址空间中也有三种状态的页面:空闲页面、保留页面和提交页面。空闲(Free)页面:空闲页面是指那些可以保留或提交的可用页面。保留(Reserved)页面:保留页面是逻辑页面已分配但没有分配物理存储的页面。设置这种状态的效果是可以保留一部分虚拟地址,这样,如果不预先释放这些地址,就不能被其他应用程序(如 Malloc,LocalAlloc 等)的操作所使用。试图读或写空闲页面或保留页面将导致页面出错异常。保留页面可被释放或提交。提交(Committed)页面:提交页面是物理存储(在内存中或磁盘上)已被分配的页面。可对它加以保护,不许访问或允许只读访问,或允许读写访问。提交也可以被回收以释放存储空间,从而变成保留页面。
    在本项目中,首先执行Main类中main函数的makeOperationFile()方法生成随机输入文件,其中包含对内存要求作的各种操作;然后执行Main类中main函数的memoryOperation()方法,实现输入文件所要求的各项内存管理操作。
    3.2 模块设计3.2.1项目包类图
    3.2.2 成员类说明
    Main:程序入口,执行代码,主要有生成操作文件和执行内存分配程序两个入口
    MemoryBlock:内存块类,模拟代表内存分配的内存块,一个内存块中有多个内存页,记录着内存块起始地址,内存页数,内存页区域,操作权限和锁定状态
    MemoryHandler:内存分配处理类:通过对模拟内存块和模拟内存页的操作,模拟实现内存保留、提交、锁定、解锁、回收、释放六个操作
    MemoryMonitorThread:内存监听线程,和内存分配线程同步执行,实时输出内存分配状态到文件中
    MemoryPage:模拟内存页,包含了每个页面的内存大小,默认4K
    MemoryThread:内存分配线程,和内存监视线程同步,通过读取操作文件中的操作记录实现对模拟内存的分配操作
    Mutex:内存分配线程和内存监视线程的同步信号量
    Operation:内存分配操作参数,包括内存操作大小,等待时间,操作权限,操作类型
    Trace:内存操作返回状态信息类,包括当前处理内存的虚拟地址和物理地址
    VirtualAddress:虚拟地址类,模拟实现内存的虚拟分页页号地址,包括地址其实页号,内存块页总数,内存块对应物理起始地址,操作权限,锁定状态,内存页空间

    3.2.3 内存分配线程主要执行代码: @Override public void run() { MemoryHandler memory = MemoryHandler.getInstance(); for (int i = 0; i < operations.size(); i++) { synchronized (mutex) { // 信号量不为内存操作标记,等待 while (mutex.mutex != Mutex.MUTEX_MEMORY) { try { mutex.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } Operation operation = operations.get(i); // 等待 waitForTime(operation.getTime()); // 根据不同操作类型进行相应内存操作 switch (operation.getType()) { case 0:// 保留区域 // 操作 traces[i % 5].virtualAddress = memory.reserveMemory( operation.getBlock(), operation.getPermision()); // 输出相关信息 System.out.println("保留区域"); System.out.println(operation.toString()); System.out.println("开始页号:"+ traces[i % 5].virtualAddress . .getStartPage() + ",结束页号:" + (traces[i % 5].virtualAddress.getStartPage() + traces[i % 5].virtualAddress.getBlock() - 1)); System.out.println("\n"); break; case 1:// 提交区域 if (traces[i % 5].virtualAddress != null) { // 操作 traces[i % 5].memoryBlock = memory.commitMemory(traces[i % 5].virtualAddress.getStartPage()); // 输出相关信息 System.out.println("提交区域"); System.out.println(operation.toString()); System.out.println("开始地址:" + traces[i % 5].memoryBlock.getStartAddress() + ",结束地址:" + (traces[i % 5].memoryBlock.getStartAddress() + traces[i % 5].memoryBlock.getBlock() * MemoryPage.PAGE_SIZE - 1)); System.out.println("\n"); } else System.out.println("当前没有待操作的虚拟内存地址!"); break; case 2:// 锁定区域 if (traces[i % 5].virtualAddress != null) { // 操作 memory.lockMemory(traces[i % 5].virtualAddress.getStartPage()); // 输出相关信息 System.out.println("锁定区域"); System.out.println(operation.toString()); System.out.println("开始地址:" + traces[i % 5].memoryBlock.getStartAddress() + ",结束地址:" + (traces[i % 5].memoryBlock.getStartAddress() + traces[i % 5].memoryBlock.getBlock() * MemoryPage.PAGE_SIZE - 1)); System.out.println("\n"); } else System.out.println("当前没有待操作的虚拟内存地址!"); break; case 3:// 解锁区域 if (traces[i % 5].virtualAddress != null) { // 操作 memory.unlockMemory(traces[i % 5].virtualAddress.getStartPage()); // 输出相关信息 System.out.println("解锁区域"); System.out.println(operation.toString()); System.out.println("开始地址:" + traces[i % 5].memoryBlock.getStartAddress() + ",结束地址:" + (traces[i % 5].memoryBlock.getStartAddress() + traces[i % 5].memoryBlock.getBlock() * MemoryPage.PAGE_SIZE - 1)); System.out.println("\n"); } else System.out.println("当前没有待操作的虚拟内存地址!"); break; case 4:// 回收区域 if (traces[i % 5].virtualAddress != null) { // 操作 memory.decommitMemory(traces[i % 5].virtualAddress.getStartPage()); // 输出相关信息 System.out.println("回收区域"); System.out.println(operation.toString()); System.out.println("开始地址:" + traces[i % 5].memoryBlock.getStartAddress() + ",结束地址:" + (traces[i % 5].memoryBlock.getStartAddress() + traces[i % 5].memoryBlock.getBlock() * MemoryPage.PAGE_SIZE - 1)); System.out.println("\n"); } else System.out.println("当前没有待操作的虚拟内存地址!"); break; case 5:// 释放区域 if (traces[i % 5].virtualAddress != null) { // 操作 memory.releaseMemory(traces[i % 5].virtualAddress.getStartPage()); // 输出相关信息 System.out.println("释放区域"); System.out.println(operation.toString()); System.out.println("开始页号:" + traces[i % 5].virtualAddress.getStartPage() + ",结束页号:" + (traces[i % 5].virtualAddress.getStartPage() + traces[i % 5].virtualAddress .getBlock() - 1)); System.out.println("\n"); } else System.out.println("当前没有待操作的虚拟内存地址!"); break; default: break; } // 睡眠 try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); throw new RuntimeException(e); } // 更改标记位为监听线程 mutex.mutex = Mutex.MUTEX_MONITOR; // 唤醒监听线程 mutex.notifyAll(); } } }
    3.2.4 内存监视线程主要执行代码: @Override public void run() { // 结果输出文件 File file = new File("result.txt"); if (!file.exists()) try { file.createNewFile(); } catch (IOException e1) { e1.printStackTrace(); throw new RuntimeException(e1); } FileOutputStream fos = null; try { MemoryHandler memory = MemoryHandler.getInstance(); fos = new FileOutputStream(file); // 监视内存操作,并输出内存状态 for (int i = 0; i < 30; i++) { synchronized (mutex) { // 信号量不是监听线程标记,等待内存操作线程完成 while (mutex.mutex != Mutex.MUTEX_MONITOR) { mutex.wait(); } // 向文件输出监控到的操作信息 fos.write(new String("[ 操作:" + Operation.names[i / 5] + " ]\t当前系统内存占用如下(*范围内):\r\n").getBytes()); fos.write(new String("******************************************************************\r\n").getBytes()); fos.write(memory.printStatus().getBytes()); fos.write(new String("******************************************************************\r\n\r\n\r\n").getBytes()); // 睡眠 Thread.sleep(100); // 信号量改为内存操作线程标记 mutex.mutex = Mutex.MUTEX_MEMORY; // 唤醒等待的内存操作线程 mutex.notifyAll(); } } } catch (FileNotFoundException e) { e.printStackTrace(); throw new RuntimeException(e); } catch (InterruptedException e) { e.printStackTrace(); throw new RuntimeException(e); } catch (IOException e) { e.printStackTrace(); throw new RuntimeException(e); } finally { if (fos != null) { try { fos.close(); } catch (IOException e) { e.printStackTrace(); throw new RuntimeException(e); } fos = null; } } }
    四、调试分析项目右键,执行,程序便会从项目目录下的operations.txt读取执行参数,将执行结果输出到项目目录下的result.txt中。
    由于执行结果较长,一下仅用一部分进行说明,详情请参照源码执行结果。


    五、用户使用说明在Eclipse中点击 File——Import——Existing Projects into WorkSpace——Browser——找到MemoryManager文件夹的路径位置,将本工程导入到 Eclipse 中,然后右键工程 Run As Java Application,程序便开始执行,可通过修改 Main 类中的 main 方法来实现生成操作文件和内存分配两种功能的选择。
    环境说明:本工程建立在JDK1.7环境下。
    1 评论 9 下载 2018-11-03 00:30:19 下载需要6点积分
  • 基于JAVA的进程调度算法

    一、需求分析在Java开发环境下,模拟进程调度算法,其中该算法所需要的具体功能为:采用最高优先数优先的调度算法(即把处理机分配给优先数最高的进程)和先来先服务算法(将用户作业和就绪进程按提交顺序或变为就绪状态的先后排成队列,并按照先来先服务的方式进行调度处理)。
    算法的具体描述为:每个进程有一个进程控制块( PCB)表示。进程控制块可以包含如下信息:进程名、优先数、到达时间、需要运行时间、已用CPU时间、进程状态等等。进程的优先数、需要的运行时间及到达时间可以事先人为地指定(也可以由随机数产生)。进程的运行时间以时间片为单位进行计算。每个进程的状态可以是就绪 W(Wait)、运行R(Run)、或完成F(Finish)三种状态之一。就绪进程获得 CPU后都只能运行一个时间片。用已占用CPU时间加1来表示。 如果运行一个时间片后,进程的已占用 CPU时间已达到所需要的运行时间,则撤消该进程,如果运行一个时间片后进程的已占用CPU时间还未达所需要的运行时间,也就是进程还需要继续运行,此时应将进程的优先数减1(即降低一级),然后把它插入就绪队列等待CPU。每进行一次调度程序都打印一次运行进程、就绪队列、以及各个进程的 PCB,以便进行检查。 重复以上过程,直到所要进程都完成为止。
    二、概要设计为了符合题目需求,该工程主要有三个实现类:

    Process(进程类),主要用来实例化各个不同的进程
    ProcessBlock(进程控制块类),用来为每个进程分配PCB,该类实例化后为进程类的类成员变量
    ProcessControl(进程控制类),为主类,用来调度进程。

    其中,在进程调度中,声明了三个队列,分别为待插入进程队列(按到达时间从小到大排序),就绪队列(按优先级从大到小排序,按照到达时间先后进行排序),完成队列。都ArrayList<Process>类型变量。
    调度算法描述:

    程序开始时随机为初始化5个进程(程序太多不容易观察运行结果)
    声明时间变量t,while循环下调度程序一直运行,每运行一次,t++
    然后循环判断待插入队列队首进程是否到达,若到达,则将该进程插入到就绪队列中,并从待插入队列删除该进程;若没有到达,则从该循环中跳出
    然后从就绪队列中取出队首进程并分配时间片。当该进程时间片用完后,判断该进程是否已经完成,若完成,则将该进程插入到完成队列;若没有完成,则将该进程的优先级减一并重新插入到就绪队列中
    一直重复该循环,一直到待插入队列和就绪队列都为空为止

    三、详细设计3.1 设计思想采用最高优先数优先的调度算法(即把处理机分配给优先数最高的进程)和先来先服务算法(将用户作业和就绪进程按提交顺序或变为就绪状态的先后排成队列,并按照先来先服务的方式进行调度处理)。
    3.2 模块设计所使用的数据结构(简化,仅成员变量,具体请见源码)
    public class ProcessBlock { private String name; //进程名 private int priority; //优先数 1-10 private int arriveTime; //到达时间 1-50 private int needTime; //需要时间 1-20 private int waitTime; //等待时间 1-20 private int alreadyUseTime; //已占用CPU时间 private char status; //进程状态 W, R, F}public class Process { private ProcessBlock pcb;}public class ProcessControl { private final static int uTime = 1; // 时间片 private static int time = 0; // 模拟时间 ArrayList<Process> processA = new ArrayList();// 待插入的进程 ArrayList<Process> processB = new ArrayList();// 就绪队列 ArrayList<Process> processFinish = new ArrayList();// 完成队列}

    输入子模块
    系统自动随机产生5个进程,并且随机对它们进行初始化,并按照进程产生的创建的时间进行排序插入到待插入队列中
    进程调度子模块
    采用最高优先数优先的调度算法(即把处理机分配给优先数最高的进程)和先来先服务算法(将用户作业和就绪进程按提交顺序或变为就绪状态的先后排成队列,并按照先来先服务的方式进行调度处理)对就绪队列中的进程进行进程调度
    输出子模块
    系统每调度进程一次,都会产生输出,将正在运行中的进程和就绪队列,完成队列中的进程信息全部打印出来

    四、调试分析
    遇到的问题
    当进程太多时,输出结果非常多,不利于分析系统调度算法的正确性
    解决方案
    将进程数量相应的减少之后,使输出结果相应的减少之后,使可分析程度保证在可接受的范围之内。
    调试结果(输出结果太多,仅部分输出结果)

    >各进程到达信息表:>0 name-> process4,priority-> 10,arriveTime-> 1,needTime-> 8,alreadyUseTime-> 0,waitTime-> 0,status-> W>1 name-> process1,priority-> 2,arriveTime-> 2,needTime-> 7,alreadyUseTime-> 0,waitTime-> 0,status-> W>2 name-> process2,priority-> 5,arriveTime-> 2,needTime-> 9,alreadyUseTime-> 0,waitTime-> 0,status-> W>3 name-> process0,priority-> 5,arriveTime-> 3,needTime-> 7,alreadyUseTime-> 0,waitTime-> 0,status-> W>4 name-> process3,priority-> 4,arriveTime-> 10,needTime-> 9,alreadyUseTime-> 0,waitTime-> 0,status-> W>时间:>1>正在运行的进程:>name-> process4,priority-> 10,arriveTime-> 1,needTime-> 8,alreadyUseTime-> 0,waitTime-> 0,status-> R>就绪队列>完成队列---------------------------------------------------------------->时间:>2>正在运行的进程:>name-> process4,priority-> 9,arriveTime-> 1,needTime-> 8,alreadyUseTime-> 1,waitTime-> 0,status-> R>就绪队列>name-> process2,priority-> 5,arriveTime-> 2,needTime-> 9,alreadyUseTime-> 0,waitTime-> 0,status-> W>name-> process1,priority-> 2,arriveTime-> 2,needTime-> 7,alreadyUseTime-> 0,waitTime-> 0,status-> W>完成队列---------------------------------------------------------------->时间:>3>正在运行的进程:>name-> process4,priority-> 8,arriveTime-> 1,needTime-> 8,alreadyUseTime-> 2,waitTime-> 0,status-> R>就绪队列>name-> process2,priority-> 5,arriveTime-> 2,needTime-> 9,alreadyUseTime-> 0,waitTime-> 1,status-> W>name-> process0,priority-> 5,arriveTime-> 3,needTime-> 7,alreadyUseTime-> 0,waitTime-> 0,status-> W>name-> process1,priority-> 2,arriveTime-> 2,needTime-> 7,alreadyUseTime-> 0,waitTime-> 1,status-> W>完成队列---------------------------------------------------------------->时间:>39>正在运行的进程:>name-> process1,priority-> 0,arriveTime-> 2,needTime-> 7,alreadyUseTime-> 6,waitTime-> 2,status-> R>就绪队列>name-> process3,priority-> 0,arriveTime-> 10,needTime-> 9,alreadyUseTime-> 8,waitTime-> 0,status-> W>完成队列>name-> process4,priority-> -1,arriveTime-> 1,needTime-> 8,alreadyUseTime-> 8,waitTime-> -1,status-> F>name-> process0,priority-> -1,arriveTime-> 3,needTime-> 7,alreadyUseTime-> 7,waitTime-> -1,status-> F>name-> process2,priority-> -1,arriveTime-> 2,needTime-> 9,alreadyUseTime-> 9,waitTime-> -1,status-> F---------------------------------------------------------------->时间:>40>正在运行的进程:>name-> process3,priority-> 0,arriveTime-> 10,needTime-> 9,alreadyUseTime-> 8,waitTime-> 1,status-> R>就绪队列>完成队列>name-> process4,priority-> -1,arriveTime-> 1,needTime-> 8,alreadyUseTime-> 8,waitTime-> -1,status-> F>name-> process0,priority-> -1,arriveTime-> 3,needTime-> 7,alreadyUseTime-> 7,waitTime-> -1,status-> F>name-> process2,priority-> -1,arriveTime-> 2,needTime-> 9,alreadyUseTime-> 9,waitTime-> -1,status-> F>name-> process1,priority-> -1,arriveTime-> 2,needTime-> 7,alreadyUseTime-> 7,waitTime-> -1,status-> F---------------------------------------------------------------->时间:>41>正在运行的进程:>就绪队列>完成队列>name-> process4,priority-> -1,arriveTime-> 1,needTime-> 8,alreadyUseTime-> 8,waitTime-> -1,status-> F>name-> process0,priority-> -1,arriveTime-> 3,needTime-> 7,alreadyUseTime-> 7,waitTime-> -1,status-> F>name-> process2,priority-> -1,arriveTime-> 2,needTime-> 9,alreadyUseTime-> 9,waitTime-> -1,status-> F>name-> process1,priority-> -1,arriveTime-> 2,needTime-> 7,alreadyUseTime-> 7,waitTime-> -1,status-> F>name-> process3,priority-> -1,arriveTime-> 10,needTime-> 9,alreadyUseTime-> 9,waitTime-> -1,status-> F>1.5用户使用说明>由于该程序是在java环境下编写的,故用户请在eclipse开发工具中打开项目,并运行该项目即可。
    1 评论 15 下载 2018-11-03 00:24:18 下载需要4点积分
  • 基于C++的请求分页虚拟页面替换算法

    一、需求分析实现OPT、FIFO、LRU、Clock等页面替换算法。接收用户输入参数,包括程序长度(页面数)、页框个数及页面大小,输出结果采用不同颜色区分命中、替换及直接加入空闲块。

    OPT(最佳置换算法):其所选择的被淘汰页面将是以后永不使用的,或许是在最长(未来)时间内不再被访问的页面,但是由于无法预知一个进程在内存中的若干个页面中,哪一个页面是未来最长时间内不被访问的,因而该算法无法实现
    FIFO(先进先出页面是换算法):该算法总是淘汰最先进入内存的页面,即选择在内存中驻留时间最久的页面予以淘汰
    LRU(最近最久未使用置换算法):只能利用“最近的过去”作为“最近的将来”的近似,因此,LRU置换算法是选择最近最久未使用的页面予以淘汰
    Clock:使用的较多的一种LRU近似算法

    二、概要设计
    FIFO算法:在该模拟系统中,使用队列来存储页面并且按照其到达队列的先后顺序进行排序,因此,每次需要进行页面替换时,便选择队首或者标识符指示其驻留时间最长的页面进行替换
    LRU算法:同样使用数组队列来模拟,数组下标指示位置,而数组值则作为标识符来计算该页面自最后一次被访问到现在为止经历了多长时间,当选择被置换的页面时,则扫描该数组队列,找到标识符最大的页面进行替换
    Clock算法:同样使用数组队列来模拟,数组下标指示内存位置,而数组值则作为标识符在替换过程中发挥作用。当选择被替换的页面时,则扫描该数组队列,检查数组值,若值为0,则进行替换;若数组值为1,则将数组值置为1,继续扫描下一个数组值。若扫描完毕没有合适的页面进行替换时,则对数组进行第二次扫描

    三、详细设计3.1 设计思想
    FIFO算法:先进先出思想,即选择在内存中驻留时间最久的页面给予淘汰
    LRU算法:根据页面调入内存后的使用情况作出决策,选择最近最久未使用耳朵页面予以淘汰
    Clock算法:设置访问位,循环检查各页面使用情况,是一种LRU近似算法

    3.2 模块设计
    输入子模块:接收用户的输入,用户的输入共有程序长度(页面数)、页框个数及页面大小三种
    模拟页面置换子模块:该模块共有三种函数,分别模拟FIFO算法,LRU算法,Clock算法

    FIFO算法描述
    void fifo(int pageNum,int pageFrameNum,int size){ int m[pageFrameNum]; for(int i = 0 ; i < pageFrameNum ; i++) m[i] = 0; //模拟页面访问 for(int i = 0 ; i < pageNum; i++) { for(int k = 0; ; k++) { for(int j = 0 ; j < pageFrameNum ; j++) { if(m[j] == k) { m[j] = k+1; for(int h = 0 ; h < pageFrameNum ; h++) { if(m[h] == 0) cout << "# "; else { if(k == 0) { if(h!=j) cout << "% "; else cout << "+ "; } else { if(h!=j) cout << "% "; else cout << "t "; } } } cout << endl; k = -1; break; } } if(k < 0 ) break; } }}
    LRU算法描述
    void lru(int pageNum,int pageFrameNum,int size){ int m[pageFrameNum]; for(int i = 0 ; i < pageFrameNum ; i++) { m[i] = 0; } for(int i = 0 ; i < pageNum; i++) { int j; for(j = 0; j < pageFrameNum; j++) { if(m[j] == 0) { m[j] = 1; for(int k = 0 ; k < pageFrameNum; k++) { if(m[k] == 0) cout << "# "; else { if(k != j) cout << "% "; else cout << "+ "; } } cout << endl; break; } } if(j > pageFrameNum-1) { int max = 0,tmp; for(int d = 0; d < pageFrameNum; d++) { if(m[d] > max) { max = m[d]; tmp = d; //该位置页面被替换 } } m[tmp] = 1; for(int f = 0; f < pageFrameNum; f++) { if(f != tmp) cout << "% "; else cout << "t "; } cout << endl; } //模拟页面访问过程 while(true) { int tmp = rand()%(pageFrameNum+1); if(m[tmp] > 0) { for(int t = 0; t < pageFrameNum; t++) { if(m[t] > 0) m[t]++; } m[tmp] = 1; } break; } }}
    Clock算法描述
    void clock(int pageNum,int pageFrameNum,int size){ int m[pageFrameNum]; for(int i = 0 ; i < pageFrameNum ; i++) { m[i] = -1; } for(int i = 0 ; i < pageNum; i++) { int j; for(j = 0; j < pageFrameNum; j++) { if(m[j] == -1) { m[j] = 1; for(int k = 0 ; k < pageFrameNum; k++) { if(m[k] == -1) cout << "# "; else { if(k != j) cout << "% "; else cout << "+ "; } } cout << endl; break; } } if(j > pageFrameNum-1) { int flag = 0,tmp; for(int d = 0; d < pageFrameNum; d++) { if(m[d] == 0) { m[d] = 1; tmp = d; flag = 1; break; } else { m[d] = 0; } } if(flag == 0) { for(int d = 0; d < pageFrameNum; d++) { if(m[d] == 0) { m[d] = 1; break; } } } for(int f = 0; f < pageFrameNum; f++) { if(f != tmp) cout << "% "; else cout << "t "; } cout << endl; } //模拟页面访问过程 while(true) { int tmp = rand()%(pageFrameNum+1); if(m[tmp] == 0) { m[tmp] = 1; } break; } }}
    输出子模块:输出页面置换后的内存调用结果,其中输出符号表示:

    t:替换
    +:直接加入空闲区
    #:页框空闲
    %:页框不空闲

    四、调试分析输入程序长度(页面数)、页框个数、页面大小分别为20,10,2,三种算法输出结果如下(其中t表示替换,+表示直接加入空闲区,#表示页框空闲,%表示页框不空闲)。
    FIFO算法

    LRU算法

    Clock算法

    五、用户使用说明该模拟系统为 C++ 语言模拟系统,请在 Codeblocks 等 C++ 编辑程序中打开编译运行,并根据提示进行输入。
    1 评论 7 下载 2018-11-03 00:07:19 下载需要6点积分
  • 基于C++的类UNIX文件系统

    一、题目要求使用一个普通的大文件(如 c:\myDisk.img ,称之为一级文件)模拟 UNIX V6++的一个文件卷,一个文件卷实际上就是一张逻辑磁盘,磁盘中存储的信息以块为单位。每块 512字节。


    磁盘文件结构

    定义自己的磁盘文件结构SuperBlock 结构磁盘 Inode 节点结构,包括:索引结构及逻辑块号到物理块号的映射磁盘 Inode 节点的分配与回收算法设计与实现文件数据区的分配与回收算法设计与实现
    文件目录结构

    目录文件结构目录检索算法的设计与实现
    文件打开结构:选作
    磁盘高速缓存:选作
    文件操作接口

    • void fformat(); 格式化文件卷 • void ls(); 列目录 • int fopen(char *name, int mode); 打开文件 • void fclose(int fd); 关闭文件 • int fread(int fd, char *buffer, int length); 读文件 • int fwrite(int fd, char *buffer, int length);写文件 • int flseek(int fd, int position); 定位文件读写指针 • int fcreat(char *name, int mode); 新建文件 • int fdelete(char *name); 删除文件

    主程序
    初始化文件卷,读入 SuperBlock图形界面或者命令行方式,等待用户输入根据用户不同的输入,返回结果

    二、需求分析根据对题目要求分析和用户使用分析,文件系统采用控制台命令作为输入,按照系统给定的命令即可完成相应的功能。
    控制台的命令应包括如下:
    " man : 手册 \n" " fformat : 格式化 \n" " exit : 正确退出 \n" " mkdir : 新建目录 \n" " cd : 改变目录 \n" " ls : 列出目录及文件 \n" " create : 新建文件 \n" " delete : 删除文件 \n" " open : 打开文件 \n" " close : 关闭文件 \n" " seek : 移动读写指针 \n" " write : 写入文件 \n" " read : 读取文件 \n" " at|autoTest : 自动测试 \n"
    每条命令具体分析如下:
    命令:fformat = "Command : fformat -进行文件系统格式化 \n" "Description : 将整个文件系统进行格式化,即清空所有文件及目录! \n" "Usage : fformat \n" "Parameter : 无 \n" "Usage Demo : fformat \n" ;
    命令:exit = "Command : exit -退出文件系统 \n" "Description : 若要退出程序,最好通过exit命令。此时正常退出会调用析构函数,\n" " : 若有在内存中未更新到磁盘上的缓存会及时更新,保证正确性。若点 \n" " : 击窗口关闭按钮,属于给当前程序发信号强制退出,不会调用析构函 \n" " : 数,未写回部分信息,再次启动时可能出现错误! \n" "Usage : exit \n" "Parameter : 无 \n" "Usage Demo : exit \n" ;
    命令:mkdir = "Command : mkdir -建立目录 \n" "Description : 新建一个目录。若出现错误,会有相应错误提示! \n" "Usage : mkdir <目录名> \n" "Parameter : <目录名> 可以是相对路径,也可以是绝对路径 \n" "Usage Demo : mkdir dirName \n" " mkdir ../dirName \n" " mkdir ../../dirName \n" " mkdir /dirName \n" " mkdir /dir1/dirName \n" "Error Avoided : 目录名过长,目录路径不存在,目录超出根目录等 \n" ;
    命令:ls = "Command : ls -列目录内容 \n" "Description : 列出当前目录中包含的文件名或目录名。若出现错误,会有相应错误提示!\n" "Usage : ls \n" "Parameter : 无 \n" "Usage Demo : ls \n" ;
    命令:cd = "Command : cd -改变当前目录 \n" "Description : 改变当前工作目录。若出现错误,会有相应错误提示! \n" "Usage : cd <目录名> \n" "Parameter : <目录名>默认为当前目录;\n" " <目录名> 可以是相对路径,也可以是绝对路径 \n" "Usage Demo : ls \n" " ls ../dirName \n" " ls ../../dirName \n" " ls /dirName \n" " ls /dir1/dirName \n" "Error Avoided : 目录名过长,目录路径不存在,目录超出根目录等 \n" ;
    命令:create = "Command : create -新建文件 \n" "Description : 新建一个文件。若出现错误,会有相应错误提示! \n" "Usage : create <文件名> <选项> \n" "Parameter : <文件名> 可以包含相对路径或绝对路径 \n" " <选项> -r 只读属性 \n" " <选项> -w 只写属性 \n" " <选项> -rw == -r -w 读写属性 \n" "Usage Demo : create newFileName -rw \n" " create ../newFileName -rw \n" " create ../../newFileName -rw \n" " create /newFileName -rw \n" " create /dir1/newFileName -rw \n" "Error Avoided : 文件名过长,目录路径不存在,目录超出根目录等 \n" ;
    命令:delet = "Command : delete -删除文件 \n" "Description : 删除一个文件。若出现错误,会有相应错误提示! \n" "Usage : delete <文件名> \n" "Parameter : <文件名> 可以包含相对路径或绝对路径 \n" "Usage Demo : delete fileName \n" " delete ../fileName \n" " delete ../../fileName \n" " delete /fileName \n" " delete /dir1/fileName \n" "Error Avoided : 文件名过长,目录路径不存在,目录超出根目录等 \n" ;
    命令:open = "Command : open -打开文件 \n" "Description : 类Unix|Linux函数open,打开一个文件。若要进行文件的读写必须先open。\n" " 若出现错误,会有相应错误提示! \n" "Usage : open <文件名> <选项> \n" "Parameter : <文件名> 可以包含相对路径或绝对路径 \n" " <选项> -r 只读属性 \n" " <选项> -w 只写属性 \n" " <选项> -rw == -r -w 读写属性 \n" "Usage Demo : open fileName -r \n" " open ../fileName -w \n" " open ../../fileName -rw \n" " open /fileName -r -w \n" " open /dir1/fileName -rw \n" "Error Avoided : 文件名过长,目录路径不存在,目录超出根目录等 \n" ;
    命令:close = "Command : close -关闭文件 \n" "Description : 类Unix|Linux函数close,关闭一个文件。可以对已经打开的文件进行关闭\n" " 若出现错误,会有相应错误提示! \n" "Usage : close <file descriptor> \n" "Parameter : <file descriptor> 文件描述符 \n" "Usage Demo : close 1 \n" "Error Avoided : 文件描述符不存在或超出范围 \n" ;
    命令:seek = "Command : seek -写入文件 \n" "Description : 类Unix|Linux函数fseek,写入一个已经打开的文件中。若出现错误,会有相应错误提示! \n" "Usage : seek <file descriptor> <offset> <origin> \n" "Parameter : <file descriptor> open返回的文件描述符 \n" " <offset> 指定从 <origin> 开始的偏移量 可正可负 \n" " <origin> 指定起始位置 可为0(SEEK_SET), 1(SEEK_CUR), 2(SEEK_END) \n" "Usage Demo : seek 1 500 0 \n" "Error Avoided : 文件描述符不存在或超出范围,未正确指定参数 \n" ;
    命令:write = "Command : write -写入文件 \n" "Description : 类Unix|Linux函数write,写入一个已经打开的文件中。若出现错误,会有相应错误提示! \n" "Usage : write <file descriptor> <InFileName> <size> \n" "Parameter : <file descriptor> open返回的文件描述符 \n" " <InFileName> 指定写入内容为文件InFileName中的内容 \n" " <size> 指定写入字节数,大小为 <size> \n" "Usage Demo : write 1 InFileName 123 \n" "Error Avoided : 文件描述符不存在或超出范围,未正确指定参数 \n" ;
    命令:read = "Command : read -读取文件 \n" "Description : 类Unix|Linux函数read,从一个已经打开的文件中读取。若出现错误,会有相应错误提示! \n" "Usage : read <file descriptor> [-o <OutFileName>] <size> \n" "Parameter : <file descriptor> open返回的文件描述符 \n" " [-o <OutFileName>] -o 指定输出方式为文件,文件名为 <OutFileName> 默认为shell \n" " <size> 指定读取字节数,大小为 <size> \n" "Usage Demo : read 1 -o OutFileName 123 \n" " read 1 123 \n" "Error Avoided : 文件描述符不存在或超出范围,未正确指定参数 \n" ;
    命令:autoTest = "Command : autoTest -自动测试 \n" "Description : 帮助测试,在系统启动初期帮助测试。测试不一定所有命令都是正确的,但是系统具有容错性,\n" " : 不会使系统异常。\n" "Usage : autoTest | at \n" "Parameter : 无 \n" "Usage Demo : at \n" ;
    三、概要设计3.1 模块划分经过分析,整个文件系统可以由以下几个部分组成:

    DeviceDriver:设备驱动模块,直接负责磁盘文件直接读写
    BufferManager:高速缓存管理模块,主要负责管理系统中所有的缓存块,包括申请、释放、读写、清空一块缓存的功能函数接口,以及系统退出时刷新所有缓存块
    FileSystem:系统盘块管理模块,主要负责对镜像文件的存储空间管理,包括SuperBlock 空间占用、DiskINode 空间分布、数据块区空间分布的管理。需要提供分配、回收 DiskINode 节点、数据块节点以及格式化磁盘文件的接口
    FileManager:系统文件操作功能实现模块,主要封装文件系统中对文件处理的操作过程,负责对文件系统访问的具体细节。包括打开文件、创建文件、关闭文件、Seek 文件指针、读取文件、写入文件、删除文件等系统功能实现
    OpenFileManager:打开文件管理模块,负责对打开文件的管理,为用户打开文件建立数据结构之间的勾连关系,为用户提供直接操作文件的文件描述符接口
    User:用户操作接口模块,主要将用户的界面执行命令转化为对相应函数的调用,同时对输出进行处理,也包含检查用户输入的正确性与合法性

    3.2 数据结构定义3.2.1 DeviceDriver:设备驱动模块
    3.2.2 BufferManager:高速缓存管理模块

    3.2.3 FileSystem:系统盘块管理模块
    3.2.4 FileManager:系统文件操作功能实现模块
    3.2.5 OpenFileManager:打开文件管理模块

    3.2.6 User:用户操作接口模块
    3.3 数据结构图示3.3.1 磁盘存储空间分布:
    3.3.2 文件索引结构:
    3.3.3 内存文件打开结构:
    3.3.4 高速缓存数据结构:
    3.4 总体流程程序的总体流程为:main 函数等待用户输入,用户输入后,对其命令进行简单解析,然后由命令查找 User 提供的操作接口,将参数交由 User 函数进行处理和判断合法性,若基本符合命令的参数约定,则由User调用FileManager中功能函数实现文件系统的具体功能。而 FileManger 中函数的具体功能则依赖于 FileSystem、BufferManager、OpenFileManager的相关函数接口调用。
    总体流程图如下:

    3.5 模块调用关系下图为模块间调用关系图示:

    四、详细设计4.1 外存磁盘文件管理外存索引节点分配 IAlloc 函数:外存 DiskINode 索引节点采用栈式管理 DiskINode,当需要分配 DiskINode 时,如果 s_ninode 不为 0,则将栈顶节点分配,栈指针减一;否则s_ninode 为 0,说明外存索引节点表中已不包含任何空闲节点,就重新搜索整个 DiskINode区,将找到的 DiskINode 号顺次登入 s_inode 表中,直到该表已满或者已搜索完整个DiskINode 区。

    外存索引节点分释放 IFree 函数:当释放 DiskINode 节点时,如果超级块索引节点表中空闲 DiskINode 数小于 SuperBlock::MAX_NINODE,则将该索引节点编号记入“栈顶”,若其中记录的空闲 DiskINode 已满,则将其散落在磁盘 DiskINode 区中。

    外存空闲盘块分配 Alloc 函数:外存中的空闲盘块采用分组链式索引法进行管理。超级块中的空闲块索引表用栈式管理空闲数据块,但是它最多只能直接管理 MAX_NFREE(100)个空闲块。所有空闲块按照每 MAX_NFREE(100)个进行构成一组,最后一组直接由超级块中的空闲索引表进行管理,其余各组的索引表分别存放在它们下一组第一个盘块的开头中。
    空闲盘块分组链式索引示例:

    分配空闲盘块时,总是从索引表中取其最后一项的值,即 s_free[—s_nfree],相当于出栈,当及时直接管理的最后一个空闲盘块时,就将该盘块的前 404 字节读入超级块的s_nfree 和索引表 s_free 数组中,使得用间接方式管理的下一组变为直接管理。

    4.2 目录搜索和地址变换目录搜索 NameI 函数:将路径名到索引节点转换的过程,与 Unix 执行的目录搜索函数基本类似,流程图如下:

    地址变换 Bmap 函数:将文件逻辑号变换成文件在存储设备的物理块号,与 Unix 执行的 Bmap 函数基本类似,流程图如下:

    4.3 内存高速缓存模拟实现Unix 系统采用自由队列和设备队列进行对缓存块的管理,但是因为本次课程设计项目是模拟 Unix 文件系统,并且整个模拟时串行执行,无并行操作,所以经过考虑,可以将自由队列和设备队列进行合并为一个缓存队列,具体细节如下:
    高速缓存初始化:即构造相应的双向循环链表,初始化成员变量。

    分离一个节点 DetashNode 函数:采用 LRU 算法,每次将第一个节点分离,第一个节点即为过去一段时间最久未使用的节点。

    插入一个节点 InsertTail 函数:采用 LRU 算法,每次将插入的节点放置在缓存队列的最尾部,视为最近使用的缓存节点。

    申请一块缓存 GetBlk 函数:申请一块缓存,将其从缓存队列中取出,用于读写设备块上的 blkno。执行过程为:首先从缓存队列中寻找是否有缓存块的 blkno 为目标 blkno,如有则分离该缓存节点,并返回该节点;没有找到说明缓存队列中没有相应节点为 blkno,需要分离第一个节点,将其从缓存队列中删除。若其带有延迟写标志,则立即写回,清空标志,将缓存 blkno 置为 blkno,返回该缓存块。

    五、调试分析5.1 测试为了方便自己程序调试和老师测试,添加了一个自动测试命令,用于测试二级文件系统容错性和正确性。
    自动测试命令较为完备,对各个环节均有考虑到,自动测试命令序列如下:
    "mkdir /dir1","mkdir dir2","create file1 -rw","create file2 -rw","ls","delete /file2","ls","mkdir /dir1/dir11","create /dir1/file11-rw","cd dir1","ls","cd ..","ls","open file1-rw","write 6 file1-2000.txt 800","seek 6 500 0","read 6 20","seek 6 500 0","read 6-o readOut1.txt 20","seek 6-20 1","read 6 100","close 6","cd dir1","ls","open file11-rw","write 6 file11-100000.txt 100000","seek 6 0 0","read 6 100","read 6 100","seek 6 50000 0","read 6 100"
    测试结果如下:



    5.2 调试中的问题解决问题 1:缓存刷新算法逻辑错误
    经过基本的功能测试后,添加了最后的 exit 命令,由前面介绍的高速缓存模拟知道,在退出时,可能有些缓存块上带有延迟写标记,尚未更新到磁盘文件中,那么在退出时需要调用虚构函数,将 Buffer 中带有延迟写标志的数据块写入到磁盘中,没有细想将缓存队列进行线性扫描写回,错误代码如下:

    调用 exit 命令后引发异常,进行了 NULL 指针的读写访问,经过仔细的逻辑思考,这个代码会造成死循环,并将循环队列尾节点的前后指针置为 NULL,接下来 pb 就变为了 NULL指针,自然就错了。不应进行按照循环队列的次序写回,不应该进行调用 Bwrite 写回。纠错后代码如下:

    问题 2:磁盘物理序号错写为数据分区盘块序号
    在做自动测试的时候,发现小文件测试没有问题,本来想欢欢喜喜以为基本完成了,但是当把文件大小设置大 100000 字节约等于 100KB 文件大小时,就出现了下面的错误:No Sapce left on device!。显然 100KB 大小是绝对可以存储的下的,于是新的问题产生了。

    通过提示来看,不难定位错误位置,通过搜索错误关键词,在 FileSystem.cpp 文件中找到了相应位置。如下红色标记:

    于是开始了问题分析:既然小文件没有问题,说明基本功能是没有问题的,文件大小为100KB 肯定会分配多个磁盘扇区,肯定是相应分配算法没有写对或者磁盘初始化的时候代码有问题,定位到磁盘初始化函数,经过仔细分析,发现将盘块在数据分区中序号写入 s_free表中,但是读取的时候是按照磁盘盘块的物理块号读取,结果就出现了以上问题。

    代码修改为:

    问题 3:分配空闲盘块后清空 Buffer 内容的 Bclear 函数错写
    解决上一个问题后,自以为这下总可以好好写报告了吧,天不遂人愿。测试没有问题,但是进行 exit 命令退出控制台时,控制台卡住几秒,然后磁盘镜像文件莫名变为了 GB 级大小。经过思考,我觉得文件这么大肯定是写入参数异常造成,而整个系统的读写都要通过BufferManager 的 Bread 和 Bwrite 函数进行读写,在函数中加入 debug 函数,打印 Buffer的相关标记信息,结果如下:

    最终的镜像文件变为了 GB 级就是因为这个 blkno 是 GB 大小的 blkno。blkno 发生错误,但是根本不知道是哪一个环节发生错误,那怎么找到错误原因呢?当然需要进一步分析何时这个编号为 45 的 Buffer 中的 blkno 变为了异常值,方法如下:

    我可以在 Bread 函数中进行条件判断,当 blkno 变为 812542311 时,设置断点,查看调用堆栈,调用关系如下:

    发现调用函数为 INode.cpp 中的 WriteI 函数,用同样的方法使函数停下来,然后查看相应局部变量。

    在此函数中进行单步执行,发现是在 Bmap()函数返回时错误,继续在 Bmap 函数中添加代码使之在指定条件停住。

    发现 Buffer 中数据与预期不和,而数据的改变只能是两个地方,一个是初始化写入时不正确,另一个是读入 Buffer 中不正确,仔细看了一下初始化的代码,并没有错误。然后看了 FileSystem.cpp 中 Alloc 分配空闲盘块时会进行清空,看了一下清空的函数,结果大吃一惊,明明要清空整个 Buffer 的内容,偏偏不知道怎么就多了一个 sizeof。

    问题 4:vs 下编译运行正确而 Linux 下出现编译及运行错误
    在 window 下 vs 集成环境下编译正确并且测试也正确之后,觉得交整个工程文件有点庞大,不够简洁,决定自己写一个 Makefile 编译,简洁舒服。但是写好 Makefile 后在 Linux下使用 G++编译就报了错误,错误如下:

    因为在 vs 下指针占用空间为四字节,int 也为四字节,没有问题。但是在 Linux 下的G++编译器中指针占用八字节空间,这样就会报精度损失错误。
    解决方法:将 arg 定义为 long 型数组,这样在 Linux 下大小为 8 字节,在 Window 下long 占用 4 字节。
    解决编译问题后,运行竟然报错了,错误为段错误。

    在 window 下编译运行正确,但是在 Linux 下发生错误,肯定是两者编译器或系统有细微区别,要想找出问题,必须在 Linux 下进行调试,选用工具为 GDB 调试。在编译命令选项中加入-g –rdynamic 可以进行 gdb 调试,调试过程如下:
    进入 GDB 调试,运行命令 r

    运行中会在相应地方报段错误的信号 SIGSEGV,找到是什么函数导致段错误。

    输入GDB命令bt打印当前函数调用栈,得知是在NameI函数中的memcpy调用出现问题。

    再次运行,设置断点为 FileManager.cpp:115 行处,准备进行单步调试查看变量。

    上面设置好断点以后,调试如下:

    发现 unsigned int nindex 这个变量不等于 string::npos 值,大概知道是因为类型转换的问题,于是写了一个测试程序如下:

    在 windows 下编译结果如下:

    同样的代码放在 Linux 下结果却不一样,原因就在这。

    由测试可以知道 Linux 64 位系统下 string::npos 的值为 unsigned long long 型,但是 windows 确是 unsigned int 型。程序错误就是 unsigned long long 截断为 unsigned int型再和 unsigned long long 型比较显然不会是 true,导致后续访问内存越界,程序段错误。更改代码如下,进行强制转换比较即可正确。

    结果验证:

    六、用户使用说明项目结构
    [1453381@sdn os]$ tree.|-- Buffer.cpp |-- Buffer.h |-- BufferManager.cpp |-- BufferManager.h |-- DeviceDriver.cpp |-- DeviceDriver.h |-- file11-100000.txt |-- file1-2000.txt |-- File.cpp |-- File.h |-- FileManager.cpp |-- FileManager.h |-- FileSystem.cpp |-- FileSystem.h|-- INode.cpp|-- INode.h|-- main.cpp|-- Makefile|-- OpenFileManager.cpp|-- OpenFileManager.h|-- readOut1.txt|-- User.cpp|-- User.h|-- Utility.cpp`-- Utility.h0 directories, 25 files
    编译方法
    可以将源程序放在集成环境下编译,也可利用 GNU 编译工具,通过写好的 Makefile 进行编译,make 如下:
    [1453381@sdn os]$ makeg++ -std=c++11 -w -c -o main.o main.cppg++ -std=c++11 -w -c -o Buffer.o Buffer.cppg++ -std=c++11 -w -c -o BufferManager.o BufferManager.cppg++ -std=c++11 -w -c -o DeviceDriver.o DeviceDriver.cppg++ -std=c++11 -w -c -o File.o File.cppg++ -std=c++11 -w -c -o FileManager.o FileManager.cppg++ -std=c++11 -w -c -o FileSystem.o FileSystem.cppg++ -std=c++11 -w -c -o INode.o INode.cppg++ -std=c++11 -w -c -o OpenFileManager.o OpenFileManager.cppg++ -std=c++11 -w -c -o User.o User.cppg++ -std=c++11 -w -c -o Utility.o Utility.cppg++ -o unix-fs main.o Buffer.o BufferManager.o DeviceDriver.o File.oFileManager.o FileSystem.o INode.o OpenFileManager.o User.o Utility.o
    运行说明
    在 Windows 下直接点击生成的 exe 文件执行,在 Linux 平台直接./unix-fs 即可运行。运行界面为控制台的命令行方式,命令较为简单,通俗易懂,初始界面如下:
    ++++++++++++++++++++ Unix 文件系统模拟 ++++++++++++++++++++Command : man -显示在线帮助手册Description : 帮助用户使用相应命令Usage : man [命令]Parameter : [命令] 如下: man : 手册fformat : 格式化exit : 正确退出mkdir : 新建目录cd : 改变目录ls : 列出目录及文件create : 新建文件delete : 删除文件open : 打开文件close : 关闭文件seek : 移动读写指针write : 写入文件read : 读取文件at|autoTest : 自动测试Usage Demo : man mkdir
    若对任何命令有不太清楚的地方,可直接 man [Command]查看详细说明,以 read 命令为例:
    [unix-fs / ]$ man readCommand : read -读取文件Description : 类 Unix|Linux 函数 read,从一个已经打开的文件中读取。若出现错误,会有相应错误提示!Usage : read <file descriptor> [-o <OutFileName>] <size>Parameter : <file descriptor> open 返回的文件描述符[-o <OutFileName>] -o 指 定 输 出 方 式 为 文 件 , 文 件 名 为<OutFileName> 默认为 shell<size> 指定读取字节数,大小为 <size>Usage Demo : read 1 -o OutFileName 123read 1 123Error Avoided : 文件描述符不存在或超出范围,未正确指定参数
    正确退出
    若要退出程序,最好通过 exit 命令。此时正常退出会调用析构函数。若有在内存中未更新到磁盘上的缓存会及时更新,保证正确性。若点 击窗口关闭按钮,属于给当前程序发信号强制退出,不会调用析构函 数,未写回部分信息,再次启动时可能出现错误!
    格式化
    格式化命令为 fformat,运行命令后系统会进行文件系统格式化,然后正常退出,再次进入时为初始环境。
    七、实验总结这学期课程设计很多,操作系统可能是自己花费时间精力最多的课程设计了。付出就有回报,通过本次操作系统课程设计,我觉得无论是系统分析和设计架构的能力,编程语言C++的运用和理解,还是寻找项目代码 bug 的能力,分析错误调试程序的能力都有较大的提升。
    要想使项目的整体规划和模块划分具有较好的合理性,做好需求分析是非常重要的。如果对系统功能要求不够明确,那么便不能很好的开展后续的工作。需求分析脚踏实地了,才能整个项目有一个较好的全局认识,然后在此基础上进行系统架构的设计和模块的划分。具体就是确定好模块的大致功能,模块之间的相互调用关系,每个功能模块类的定义,函数接口的定义,做好这些准备工作,才能一步一步稳扎稳打的实际编程。
    C++语言是建立在 C 语言之上的一门面向对象的语言,虽然从计算机入门到现在个人技术栈一直都是以 C/C++语言为主,语言的语法知识应该比较熟悉了,但是仍从本次课程设计学到不少知识。例如类的静态成员数据是不占用类的大小的,每一个类的占用空间大小是有规则定义,可能同样的成员,定义顺序不一样占用空间的大小是不一样的;不同编译器对C++语法编译规则是有细微差别的,不同操作系统运行一样的标准 C++语法代码也是有着一定区别。
    本次课程设计时间跨度比较大,本来准备先写好操作系统课程设计,写到一半又被其它课程设计和作业无情中断,导致前前后后费时较多。某些模块的小问题在这种来回切换的模式下被遗留忘记,导致后面整体测试时大费周章的寻找错误。不过这样也有好处,不知不觉中提高了自己调试代码的能力,进一步的提升了如何分析错误的原因,快速准确定位 bug所在。
    Unix 系统是现代各类 Unix 操作系统的源头,Unix v6 是非常优秀的系统程序,结构清晰,短小精悍,Unix v6++又用面向对象的思想对其重新进行了设计,并用 C++加以实现。本次课程设计就是仿照 Unix v6++中的文件系统进行改造而来,在这过程中,不免需要通透了解 Unix v6++的设计思想,详细阅读 Unix v6++中的源码,到现在可以说对其文件系统这一块的代码较为熟悉。但是 Unix v6++中的文件系统仅仅是其中的一个子部分,还有进程管理、内存管理、设备管理等模块,对整个 Unix 操作系统的进一步认识还有很长的路程要走。
    八、参考文献[1] 尤晋元,UNIX 操作系统教程[M],西安:电子科技大学出版社,1985 年 6 月
    [2] John Lions,莱昂氏 UNIX 源代码分析[M],机械工业出版社,2006 年 8 月
    [3] 汤小丹、梁红兵等,计算机操作系统[M], 西安:电子科技大学出版社,2013 年 11 月
    [4] Unix v6++源码
    1 评论 23 下载 2018-11-02 23:55:06 下载需要5点积分
  • 基于有限自动机的词法分析器构造

    一、目标本次实验的主要目的是对自定义的程序语言的词法分析器程序构造,我从 C 语言当中选择了部分具有代表性的子集,实现词法分析器,主要是对编译原理课程中学习的从正则达式转化为 NFA,再从 NFA 转化为 DFA 以及后续的代码生成的过程有更深刻的认识。同时,也希望对于在编译原理课程中所体现出的计算机科学当中的一些朴素而优美的思想有更多的体会。
    二、内容概述本报告主要描述了一个简单的词法分析器构造过程,包括最终成品的功能概要,实现过程中的理论推导,具体的核心算法和数据结构的描述,以及个人的收获和体会。
    三、实验环境
    操作系统 Win8.1实验的编译器 eclipse编码格式 Utf-8
    3.1 语言定义3.1.1 记号定义3.1.1.1 保留字我选择了 C 语言当中一部分具有代表性的保留字,同时为了保持整个编译原理课程实验的延续性和后续语义分析的实验内容,选择的保留字能够涵盖程序设计中的顺序、分支、和循环结构,这样也可以很好的对正则表达式当中的三种基本运算连接、或、闭包运算形成很好的对应,除此之外,选择了能够形成对函数调用,变量声明进行支持的部分保留字。具体选择的保留字有如下几个:
    else, if,int,double,char,do,continue,break,float, return, void, while,for
    3.1.1.2 特殊符号
    算术运算符:+、-、*、/
    比较运算符:<、<=、>、>=、==、!=
    分割符号:;、,
    括号:()、[]、{}
    数字:支持整数int,浮点数doule、float、char

    四、思路和方法首先,依据复杂性,把上文定义的 lexeme patterns 进行分类,我一共把它们分为三种类别,一种是简单的 lexeme patterns,包括所有单个字符且不成为任意其他任何 pattern 的前缀的 pattern;第二种是其正则表达式是有限长度的子序列的 pattern,也即其自动机不包含回路的情况;第三种是正则表达式子序列的长度可以是无限的 pattern,也就是其有限自动机包含回路,这种分类可以便于基于不同复杂度进行分别处理。
    对于第一种类型的 pattern,为其定义对应的 toekn 枚举类型为



    符号
    Token type 枚举变量




    +
    PLUS


    -
    MINES


    *
    MUTIPLY


    /
    DIVISION


    (
    LPAREN


    )
    RPAREN


    ;
    SEMI


    ,
    COMMA


    [
    LSQUARE


    ]
    RSQUARE


    {
    LBRACE


    }
    RBRACE


    !=
    NEQ


    <
    LT


    <=
    LTE


    >
    GT


    >=
    GTE


    ==
    EQ


    整数
    INT


    整数
    DOUBLE


    保留字
    KEY_WORD



    程序的大致流程是,从文件当中读入源文件,一次读取一行进行解析,忽略空白,然后依次对一行的缓存当中的每一个字符进行扫描,由状态机进行匹配,返回识别的 token以及具体信息或者报出错误提示。
    五、相关有限自动机描述对于数字:

    num—->INT|DOUBLEINT—>digit dight*DOUBLE—>dight*digit*.dightdigit*digit—>0|1|2|3|4|5|6|7|8|9

    在实现时,一旦检测到 digit,就进入 INNUM 状态,自此之后,程序将不对新扫描的digit 进行存储和输出,直到遇到一个非 digit 为止,此时 INNUM 状态转变为 DONE 状态,对缓存的数字进行输出,并把 token 类型定义为 NUM
    对于保留字和变量:本程序包含以下划线或者字母开头,包含数字或者字母的变量名,其正则表达式如下:

    digit—>0|1|2|3|4|5|6|7|8|9Leter—>[a-z]|[A-Z]Identifier—>(letter|_)(digit|leter|_)*

    一旦扫描到字母就进入 INID状态,此后不保存当前字符,知道遇到第一个非数字和字母的字符为止。对于任意一个识别出的字母序列,都要在保留字列表里面进行查找,用于发现是否在表保留字当中,不是就返回ID,否则就是keyWord。
    逻辑表达式符号:
    以>和>=为例子,正则表达式:GE → >,GTE→>=
    对这些转化为DFA,状态机如下

    而对于其他字符:
    以(和)为列子,Lparen→(,Rparen→) 其他的符号类似。

    六、核心算法描述这个词法分析器程序的核心算法就是 getToken()这个函数,它消耗字符并根据DFA 返回下一个被识别的记号。具体来说,getToken实现用 if语句对当前输入的ch进行下一次状态判断,在每个状态当中,再用Case语句对当前字符的特性进行判断,这个双重嵌套的情况分析就是对 DFA 的代码实现,识别了每个状态之后,在 case 语句下面进行是否存储当前字符,是否输出,是否回退等操。
    七、测试用例7.1 测试输入测试代码输入在项目中的program1.txt文件中。
    在这个测试用例当中,包括了多行注释,单行注释,有返回值为 int 的函数声明,有选择结构,循环结构,整形常量,标识符等,基本可以验证所有的特性。
    char c;int j=1;for(int i=0;i<10 && j!=5;i++){ j=j-1.123; double x=2.6; int a[10] x++; if(x>=6){ break; }}int lenth=10;while(length==0){ length--;}
    7.2 测试输出测试输出如下截图所示,同时打印出了行号,用于后续的错误跟踪信息的处理。如果是标识符就打印为 ID,输出标识符的内容,如果是保留字,就用KEY_WORD标志,整形数字用INT,浮点数字用DOUBLE,其余符号正常输出,也对应其标识符,可以看出,可以实现上文所述的特性。在测试时会在控制台打印出来,也会在项目中的result.txt文件中生成一份。

    八、困难与解决方案在实现过程中遇到的问题主要有系统的整体设计思路、保留字的处理、 对于需要回退情况的处理
    8.1 整体思路首先是将系统划分为输入,输出,得到 token,三个部分,这是一种非常经典的划分方式,也很好用。然后对和性的 getToken 进行处理。把 token 的识别放在一个函数当中,单独的函数处理比如读入字符,维护字符缓冲区,放回字符等内容,来隔离复杂度。每次处理一个char以后不是舍弃掉,因为是存在数组中,只是移动数组的下标来获取新的字符ch。
    8.2 保留字的处理关于保留字和标识符的冲突问题,这一点在龙书上就有叙述,在正则表达式的描述当中,保留字和标识符可以被不同的正则表达式匹配,比如if 和 while 的串既可以是标识符也可以是保留字,而正则表达式无法给出约束,所以要在程序语言定义中规定解释方式,而且是一个无二义性的规则,来处理所有可能出现二义性的情况。有两种典型的规则,一种是当串既可以是标识符也可以是关键字时,这种情况下我采取的是和龙书上所提把保留字预先存储在符号表当中(此处我用的是一个保留字数组进行存储,也即是一个字符串的数组),当新的标识符插入符号表的过程中发现此序列在符号表当中已经存在时,就认定此序列为保留字,改变其 token 的类型。
    8.3 回退处理对于判断一个数字的结尾,一个标识符的结尾,一个<符号而非<=的情况下,需要读入下一个字符,才能判断当前 tokenString的类型和状态已经到达了结束,但是这个提前读入的字符必须不能被消耗。我采取的处理方法是,我进行处理的方法是将字符放置于一个数组当中,设置一个表示处理当前字符的指向器p(就是数组的下标),然后当读出的字符不符合,不处理,下标不变,使用递归的方法调用当前的方法,传入将要处理的字符位置p。
    1 评论 18 下载 2018-11-02 16:09:30 下载需要11点积分
  • 基于swift的词法分析程序

    一、编写环境
    OS X 10.11.6
    Xcode7.3.1
    Swift2.2

    二、大致过程计算正则式:

    读入正则表达式
    对正则表达式处理、建图、生成 ε-NFA
    将 ε-NFA 去除空节点、转化为 NFA
    将 NFA 转化为 DFA
    对 DFA 图进行遍历每个节点,获取到每个节点通过某个字符到达哪下一个状态, 并找到终态、构造 DFA 表、输出显示

    验证字符串:

    在成功通过正则表达式构建 DFA 图的基础上,读入任意字符串 从字符串第一个字符、DFA 图的第一个节点开始
    判断是否有当前的字符串的字符可以使当前的 DFA 图节点走到下一个节点
    若有,则走向下一个节点,重复 2 操作,若无,则返回 false
    当字符串从头到尾监测完成后,判断当前所在节点是否是终态节点,若是,则返 回 true,反之则返回 false

    三、详细过程3.1 正则表达式->ε-NFA
    字符集
    字符集是正则表达式最基本的元素,因此反映到状态图上,字符集也会是构成状 态图的基本元素。对于字符集 C,如果有一个规则只接受 C 的话,这个规则对应 的状态图将会被构造成以下形式:

    解释:这个状态图的初始状态是 Start,结束状态是 End。Start 状态读入字符集 C 跳转到 End 状态,不接受其他字符集。

    串联 (形如正则表达式 abc)
    如果我们使用 AB 表示规则 A 和规则 B 的串联,我们可以很容易的知道串联这 个操作具有结合性,也就是说(AB)C=A(BC)。因此对于 n 个规则的串联,我们只 需要先将前 n-1 个规则进行串连,然后把得到的规则看成一个整体,跟最后一个 规则进行串联,那么就得到了所有规则的串联。如果我们知道如何将两个规则串 联起来的话,也就等于知道了如何把 n 个规则进行串联。
    为了将两个串联的规则转换成一个状态图,我们只需要先将这两个规则转换 成状态图,然后让第一个状态的结束状态跳转到第二个状态图的起始状态。这种 跳转必须是不读入字符的跳转,也就是令这两个状态等价。因此,第一个状态图 跳转到了结束状态的时候,就可以当成第二个状态图的起始状态,继续第二个规 则的检查。因此我们使用了 ε 边连接两个状态图:


    并联(形如正则表达式 0(010|101)1)
    并联的方法跟串联类似。为了可以在起始状态读入一个字符的时候就知道这 个字符可能走的是并联的哪一些分支并进行跳转,我们需要先把所有分支的状态 图构造出来,然后把起始状态连接到所有分支的起始状态上。而且,在某个分支 成功接受了一段字符串之后,为了让那个状态图的结束状态反映在整个状态图的 结束状态上,我们也把所有分支的结束状态都连接到大规则的结束状态上。如下所示:


    重复(形如正则表达式 01(010)*101)
    对于一个重复,我们可以设立两个状态。第一个状态是起始状态,第二个状 态是结束状态。当状态走到结束状态的时候,如果遇到一个可以让规则接受的字 符串,则再次回到结束状态。这样的话就可以用一个状态图来表示重复了。于是对于重复,我们可以构造状态图如下所示:


    可选(形如 1(0)*11)
    为可选操作建立状态图比较简单。为了完成可选操作,我们需要在接受一个 字符的时候,如果字符串的前缀被当前规则接受则走当前规则的状态图,如果可选规则的后续规则接受了字符串则走后续规则的状态图,如果都接受的话就两个 图都要走。为了达到这个目的,我们把规则的状态图的起始状态和结束状态连接起来,得到了如下状态图:

    通过上述的转换,可以将输入的正则表达式转化为对应的 ε-NFA,如下所示: ((aa|bb)|((ab|ba)(aa|bb)*(ab|ba)))*

    3.2去除空节点(将 ε-NFA 转换为 DFA)一个 DFA 中不可能出现ε边,所以我们首先要消除ε边。消除ε边算法 基于一个很简单的想法:如果状态 A 通过ε边到达状态 B 的话,那么状态 A 无需读入字符就可以直达状态 B。如果状态 B 需要读入字符 x 才可以到达状 态 C 的话,那么状态 A 读入 x 也可以到达状态 C。因为从 A 到 C 的路径是 A B C,其中 A 到 B 不需要读入字符。 方法: 消除从状态 A 出发的 ε 边,只需要寻找所有从 A 开始仅通过 ε 边就可以到达的状态,并把从这些状态触出发的非 ε 边复制到 A 上即可。 剩下的工作就是删除所有的 ε 边和一些因为消除 ε 边而变得不可到达的状 态。为了更加形象地描述消除 ε 边算法,我们从正则表达式(ab|cd)*构造 一个 ε-NFA,并在此状态机上应用消除 ε 边算法。 正则表达式(ab|cd)*的状态图如下所


    找到所有有效状态
    有效状态就是在完成了消除 ε 边算法之后仍然存在的状态。我们可以在 开始整个算法之前就预先计算出所有有效状态。有效状态的特点是存在非 ε 边的输入。同时,起始状态也是一个有效状态。结束状态不一定是有效状态, 但是如果存在一个有效状态可以仅通过 ε 边到达结束状态的话,那么这个状 态应该被标记为结束状态。因此对一个 ε-NFA 应用消除 ε 边算法产生的 NFA 可能出现多个结束状态。不过起始状态仍然只有一个。 我们可以把“存在非 ε 边的输入或者起始状态”这个判断方法应用在上图 的每一个状态上,计算出其中所有的有效状态。结果如下图所示。


    添加所有必要的边
    接下来我们要对所有的有效状态都应用一个算法。这个算法分成两步。第一 步是寻找一个状态的 ε 闭包,第二步是把这个状态的 ε 闭包看成一个整 体,把所有从这个闭包中输出的边全部复制到当前状态上。从标记有效状态 的结果我们得到了图 5.1 状态图的有效状态集合是{S/E 3 5 7 9}。我们依 次对这些状态应用上述算法。第一步,计算 S/E 状态的 ε 闭包。所谓一个 状态的 ε 闭包就是从这个状态出发,仅通过 ε 边就可以到达的所有状态的集合。下图中标记出了状态 S/E 的 ε 闭包:

    现在,我们把状态 S/E 从状态 S/E 的 ε 闭包中排除出去。因为从状态 A 输出的非 ε 边都属于从状态 A 的 ε 闭包中输出的非 ε 边,复制这些边是没 有任何价值的。接下来就是找到从状态 S/E 的 ε 闭包中输出的非 ε 边。在 图 5.3 我们可以很容易地发现,从状态 1 和状态 6分别输出到状态 3 和状态 7 的标记了 a 或者 b 的边,就是我们所要寻找的边。 接下来我们把这些边复制到状态 S/E 上,边的目标状态仍然保持不变,可以 得到下图

    至此,这个算法在 S/E 上的应用就结束了,接下来我们分别对剩下的有 效状态{3 5 7 9}分别应用此算法,可以得到下图:


    删除所有 ε 边和无效状态
    这一步操作是消除 ε 边算法的最后步骤。我们只需要删除所有的 ε 边 和无效状态就完成了整个消除 ε 边算法。现在我们对状态机应用 第三步,得到如下状态图:

    至此,已经将 ε-NFA 转换为 DFA
    3.3 从 NFA 到 DFANFA 是非确定性的状态机,DFA 是确定性的状态机。确定性和非确定性的 最大区别就是:从一个状态读入一个字符,确定性的状态机得到一个状态,而 非确定性的状态机得到一个状态的集合。如果我们把 NFA 的起始状态 S 看成一 个集合{S}的话,对于一个状态集合 S’,给定一个输入,就可以用 NFA 计算出 对应的状态集合 T’。因此我们在构造 DFA 的时候,只需要把起始状态对应到 S’,并且找到所有可能在 NFA 同时出现的状态集合,把这些集合都转换成 DFA 的一个状态,那么任务就完成了。因为 NFA 的状态是有限的,所以 NFA 所有状 态的集合的幂集的元素个数也是有限的,因此使用这个方法构造 DFA 是完全可 能的。 为了形象地表达出这个算法的过程,我们将构造一个正则表达式,然后给 出该正则表达式转换成 NFA 的结果,并把构造 DFA 的算法应用在 NFA 上。假设 一个字符串只有 a、b 和 c 三种字符,判断一个字符串是不是以 abc 开始并且 以 cba 结束正则表达式如下:
    abc(a|b|c)*cba
    通过上文的算法,可以得出如下图所示的 NFA:

    现在开始构造 DFA,具体算法如下:

    把{S}放进队列 L 和集合 D 中。其中 S 是 NFA 的起始状态。队列 L 放置的是未被 处理的已经创建了的 DFA 状态,集合 D 放置的是已经存在的 DFA 状态。根据上文 的讨论,DFA 的每一个状态都对应着 NFA 的一些状态。
    从队列 L 中取出一个状态,计算从这个状态输出的所有边所接受的字符集的并 集,然后对该集合中的每一个字符寻找接受这个字符的边,把这些边的目标状态 的并集 T 计算出来。如果 T∈D 则代表当前字符指向了一个已知的 DFA 状态。否 则则代表当前字符指向了一个未创建的 DFA 状态,这个时候就把 T 放进 L 和 D 中。在这个步骤里有两层循环:第一层是遍历所有接受的字符的并集,第二层是 对每一个可以接受的字符遍历所有输出的边计算目标 DFA 状态所包含的 NFA 状态 的集合。
    如果 L 非空则跳到 2。

    对于上图所示的状态机应用 DFA 构造算法
    首先执行第一步,我们得到:

    从上到下分别是队列 L、集合 D 和 DFA 的当前状态。就这样一直执行该算法直到状态 3 进入集合 D,我们得到:

    现在从队列 L 中取出{3},经过分析得到状态集合{3}接受的字符集合为{a b c}。 {3}读入 a 到达{4},读入 b 到达{5},读入 c 到达{6 7}。因为{4}、{5}和{6 7}都不 属于 D,所以把它们都放入队列 L 和集合 D:

    从队列中取出 4 进行计算,我们得到:

    显然,对于状态{4}的处理并没有发现新的 DFA 状态。于是处理{5}和{6 7},我们可以得到:

    在处理状态{5}的时候没有发现新的 DFA 状态,处理{6 7}在输入{a c}之后的跳转也没有发现新的 DFA 状态,但是我们发现了{6 7}输入 b 却得到了新的 DFA 状态{5 8}。把算法执行完,我们得到一个 DFA:

    至此,对状态机应用 DFA 构造算法的流程就结束了,我们得到了 DFA, 其功能与NFA 等价。在 DFA 中,起始状态是 0,结束状态是 4E。凡是包含了 NFA 的结束状态的 DFA 状态都必须是结束状态。
    四、程序演示:(注:表格中第一行为跳转字符、第一列为各节点的表示值)
    输入字符串 abc(a|b|c)*cba 构建 dfa 表

    验证字符串 abcccacabcacba

    验证字符串 abcba

    输入字符串 adc(a*be)*a 构建 dfa 表

    验证字符串 adcaaaaabeabebea

    验证字符串 adcabeabeabe
    1 评论 1 下载 2018-11-02 15:52:31 下载需要6点积分
  • 基于LL1文法的语法分析

    一、目标本次实验的目的是对编译器进行词法分析的过程进行模拟,我选择了在实际中更为通用的自底向上的词法分析器的分析过程,最终产生规约序列。对于LR(0)和LR(1)问题,我的程序对于LR(0)和LR(1)是通用的,因为只要给出合法的parsing table和上下文无关文法, 程序就能进行相应的词法分析,而parsing table和文法都是用户输入文件给出。
    二、内容概述本文档描述了编译原理课程实验中,语法分析器部分的实验内容,实验方案以及结果。
    三、实验环境
    操作系统:win8.1编译器:eclipse使用的工具:github编码格式:utf-8
    四、思路和核心思想根据给出的文法,文法要求是非二义性的、非左递归的上下文无关文法,输入到product的文件中。从文件中读出输入的文法,先通过对输入的文法求每一个产生式中的非终结符的first和follow集合。来构建LL(1)的预测分析表。然后使用预测分析表来进行表格驱动,对于输入的串进行预测分析,使用书上的算法,现在在此处附上这算法,因为比较难打字,我附图:

    五、测试输入与输出:测试输入的文法产生式是书中的例子文法4-28,因为表示空的字符我打不出,就使用了#代替。书中的产生式还中的左边可以有或(|)进行连接,在我的输入中只能分开,当作多个产生式,同时不能有其他字符。对于终结符,只能有一个字母限定,书中的id我使用i代替。由于输入的原因,我把把书中的输入形式做了一下转变,实质上是同一个文法。
    同时,输入串中输入的文件在项目当中,截图如下:

    程序中产生的first和follow,如下,也存在项目中的文件first_follow.txt中:

    产生的预测分析表:

    预测分析过程:

    这些测试的结果都会以文件的形式产生在项目里的文件中。也会打印到控制台上。
    1 评论 13 下载 2018-11-02 16:00:40 下载需要3点积分
  • 基于C++实现的LZW压缩算法

    1 特点基于C++实现的LZW压缩算法,特点如下所示:

    使用stl::map键值对作为字典存储
    感觉算是简单的文件操作
    字典无限长,字典自生长。但是字典只能解析存储ascii编码之类存在,中文符号之类的碰到就挂

    2 逻辑设计2.1 总体思路
    2.2 模块划分

    3 总结LZW算法的核心时字典的生成,因为字典是不保存的,是动态生成的,不管是在压缩还是解压缩的情况下都是如此。既然字典是自生成的,就需要解决解压或者压缩第一个输入字符的问题。在这里我使用的是ASCII码,在字典中声明256个空间,在读入第一个字符便可以对应之。关于编码存储,可以使用16进制来存储。
    同时,既然是一种压缩算法,压缩率和压缩速度便是其两大评价标准。关于压缩率,限于算法性能,在压缩小型且重复率低文件时甚至会有副作用,这是无可避免的,只能在这种前提下避免使用这种算法。至于压缩速度,在我第一次编写程序时,因为使用的是Map来做字典,而且为了解压缩的方便,两种方法使用同一种字典,即<编码值, 关键字>存储,这样造成的问题是,在压缩时,要经常且循环检查关键字是否在字典中存在,而又因为map是形如红黑树的构造,在查找键的环境下时间效率是对数n,但是查找关键字就需要借助自行编写的迭代器,就造成了很大时间性能限制。所以,在第二次改进时,压缩变成了<关键字, 编码值>,解压缩则还是<编码值, 关键字>。这样虽然在同时进行压缩和解压缩的情况下会加大内存开销,但是在提升4-5倍速度的情况下我认为还是值得付出的代价。
    4 程序主界面
    1 评论 26 下载 2018-11-02 15:56:05 下载需要4点积分
  • 基于QT实现的昆特牌棋牌类游戏

    一、游戏画面使用了 QGraphicsView, QGraphicsScene, QGraphicsItem等部件,没有使用Qt提供的Ui Designer。主要原因是 QGraphicsView, QGraphicsItem直接支持鼠标点击、拖动等事件,可拓展性较好,而普通的label加载图片如果需要响应比较复杂的事件,实现起来比较麻烦。但另一方面,由 于使用了QGraphicsItem,导致难以再加入布局管理器,所以在调窗口大小上遇到了比较大的困难。
    游戏一共有八个界面。分别是“欢迎”、“编辑卡组”、“选择卡组”、“替换手牌”、“游戏”、“回合结 束”、“游戏结束”、“选择界面”(对应某些牌的效果)。每一个界面都是一组QGraphicsScene 和 QGraphicsView。
    二、游戏操作2.1 编辑卡组游戏多可以存储四套卡牌,可以在开始游戏之前选择使用哪一套。点击选卡界面“横栏”的左右按钮可以实现翻页,点击卡片就可以将卡片加入或移除卡组。组卡完毕后,如果卡牌数量满足要求, 点击确认即可保存卡组。
    2.2 选择卡组点击图片即可选择卡组,默认使用第一套卡组。点击“确认”,开始游戏。
    2.3 替换手牌鼠标滚乱滑动可实现手牌翻页,点击手牌即可替换手牌。替换完成后,点击“确定”,即可进入游戏对战环节。
    2.4 游戏对战点击手牌中的卡片选中手牌,如果是随从,点击场上特定位置选择随从放到的位置,如果该随从有效果,则触发效果(选择其他卡牌,或者选择某一列,或者生成某些牌),之后可看到该随从已经置于场上。
    当手牌为空时,自动放弃跟牌,长按空格放弃跟牌。
    三、逻辑3.1 文件逻辑模块之间之间使用信号槽联系。

    3.2 游戏逻辑
    3.3 网络逻辑
    实现了将本地卡组编码,并将回合产生的信息编码,写入xml文件,发送到服务器。服务器经过转发,发送给另一个客户端,另一个客户端接收信息,并将收到的xml解析出来。服务器使用了“阿里云服务器”,在上安装了Qt,实现了数据转发的功能。
    但是,xml文件经过发送后,在另一个客户端无法解析,后也没有找出原因,所以后放弃了网络对战,只能改成单机对战。
    1 评论 23 下载 2018-11-02 15:42:06 下载需要2点积分
  • 基于C语言的语法高亮设计与实现

    一 需求分析在所需高亮的关键字或字符串前后加上class标签,在css定义颜色。
    二 程序设计2.1 设计思路把.html文件和.css文件中的内容存在两个字符数组中,在.cpp用文件操作写入。
    2.2 文件组织架构
    Syntax highlighting.exe所在目录为根目录
    源代码命名:Syntax highlighting.cpp
    文件操作函数头文件:openfile.h
    包涵变量的头文件:global var.h


    2.3 高亮颜色设计1.关键字及其他
    颜色码:#66d9ef

    以此种颜色表示的字符串(49个):
    “asm”,”auto”,”char”,”bool”,”catch”,”class”,”delete”,”const”,”cdecl”,”double”,”enum”,”extern”,”float”,”far”,”huge”,”near”,”interrupt”,”int”,”long”,”pascal”,”register”,”short”,”signed”,”static”,”signed”,”struct”,”typedef”,”union”,”unsigned”,”void”,”volatile”,”sprintf”,”puts”,”gets”,”scanf”,”printf”,”fputs”,”fputc”,”fgets”,”fgetc”,”memset”,”strlen”,”sizeof”,”strstr”,”fclose”,”FILE”,”malloc”,”fopen”,”system”。
    2.底色
    颜色码:#272822

    3.循环相关的关键字及其他
    颜色码:#f92672

    以此种颜色表示的字符串(24个):
    “if”,”else”,”do”,”while”,”for”,”break”,”continue”,”default”,”goto”,”case”,”switch”,”#define”,”#error”,”#include”,”#elif”,”#if”,”#else”,”#endif”,”#ifdef”,”#ifndef”,”#undef”,”#line”,”#pragma”,”return”。
    4.数字颜色
    颜色码:#ae81ff

    以此种颜色表示的字符(10个):
    0,1,2,3,4,5,6,7,8,9。
    5.mian函数颜色
    颜色码:#a6e22e

    以此种颜色表示的字符串(1个):
    main。
    6.字符串颜色
    颜色码:#e6db74

    以此种颜色表示的:所以字符串。
    7.注释颜色
    颜色码:#75715e

    以此种颜色表示的:所有注释。
    8.其他以白色表示。
    三 程序实现3.1 解决方案
    “分区正则模式”将代码预处理分块,然后再利用正则表达式
    “词法分析”利用编译器的词法分析器原理,遍历代码流,将关键字等一一分开

    综上,采用“分区”和“词法分析”相结合的思想,把“ ”和()中的内容分区出来加上标签,再将其他部分采用“词法分析”切割,与存关键字的数组比较分别加上不同的标签。
    3.2 关键函数
    FILE *openr(char tit[])

    功能:输入路径及文件名打开文件
    参数:char型数组tit[] 存入路径和文件名
    返回值:文件读取指针地址

    FILE *openw(void)

    功能:打开.html文件,不存在则创建
    参数:无
    返回值:返回写入指针地址

    FILEFILE *opencss(void)

    功能:打开.css文件,不存在则创建
    参数:无
    返回值:返回写入指针地址


    3.3 程序运行流程
    打开cpp文件,创建html文件指针和css文件指针
    区分出注释
    按关键字分类插入html标签
    关闭句柄指针
    选择结束或继续

    如下图所示:

    四 运行测试主界面

    输入成功

    再来一次

    输入错误

    五 参考资料[1] http://www.ffsky.com/article/bc0_a14977.aspx
    1 评论 2 下载 2018-11-02 10:07:43 下载需要3点积分
  • 基于php与sqlite数据库的运动社交网站

    一、总体设计1.1 开发环境本系统采用php作为主要开发语言,服务端主要使用php+sqlite+Apache,客户端使用html+css+js。用Apache作为服务器,采用sqlite作为后台数据管理系统。

    开发环境:Windows 10
    开发工具:phpstorm
    测试浏览器:Chrome
    服务器:Apache2

    1.2 模块结构
    1.3 系统设计模式本系统采用CodeIgniter框架进行开发,基于MVC设计模式。

    CodeIgniter框架在处理请求时采用以下的数据流程处理方式:


    index.php 文件作为前端控制器,初始化运行 CodeIgniter 所需的基本资源
    Router 检查 HTTP 请求,以确定如何处理该请求
    如果存在缓存文件,将直接输出到浏览器,不用走下面正常的系统流程
    在加载应用程序控制器之前,对HTTP 请求以及任何用户提交的数据进行安全检查
    控制器加载模型、核心类库、辅助函数以及其他所有处理请求所需的资源
    最后一步,渲染视图并发送至浏览器,如果开启了缓存,视图被会先缓存起来用于后续的请求

    1.4 软件架构
    Restful架构
    二、数据库设计
    三、导航设计3.1 导航内容


    导航栏
    侧边栏




    首页



    身体数据
    我的运动,身体管理,睡眠分析,数据注入


    健身活动
    所有活动,我发起的,我参与的


    粉丝好友
    关注我的,我关注的



    3.2 原型界面UI界面原型如图所示。




    四、接口设计4.1 首页4.2.1 提供的接口


    url
    /home




    方法类型
    get


    控制器
    HomeController


    方法
    getHome()


    参数



    前置条件



    后置条件
    返回主页界面



    4.2.2 需要的接口


    服务名
    服务









    4.2 登录/注册4.2.1 提供的接口登录界面



    url
    /login




    方法类型
    get


    控制器
    AuthController


    方法
    getLogin()


    参数



    前置条件
    用户未登录


    后置条件
    返回登录界面



    登录请求



    url
    /login




    方法类型
    post


    控制器
    AuthController


    方法
    postLogin($username, $password)


    参数
    用户名,密码(密文)


    前置条件
    用户未登录


    后置条件
    返回登录结果



    注册界面



    url
    /register




    方法类型
    get


    控制器
    AuthController


    方法
    getRegister()


    参数



    前置条件
    用户未登录


    后置条件
    返回注册界面



    注册请求



    url
    /register




    方法类型
    post


    控制器
    AuthController


    方法
    postRegister($username, $password)


    参数
    用户名,密码(密文)


    前置条件
    用户未登录


    后置条件
    返回注册结果



    4.2.2 需要的接口


    服务名
    服务




    UserModel::create
    创建新用户


    UserModel::login
    查找用户并核对密码



    4.3 身体数据4.3.1 提供的接口运动数据界面



    url
    /analyze/sport




    方法类型
    get


    控制器
    AnalyzeController


    方法
    getSport()


    参数



    前置条件
    用户已登录


    后置条件
    返回我的运动界面



    身体管理界面



    url
    /analyze/body




    方法类型
    get


    控制器
    AnalyzeController


    方法
    getBody($days)


    参数
    历史体重数据天数


    前置条件
    用户已登录


    后置条件
    返回身体管理界面



    睡眠分析界面



    url
    /analyze/sleep




    方法类型
    get


    控制器
    AnalyzeController


    方法
    getSleep($date)


    参数
    需要获取睡眠数据的日期


    前置条件
    用户已登录


    后置条件
    返回睡眠分析界面



    数据注入界面



    url
    /analyze/inject




    方法类型
    get


    控制器
    AnalyzeController


    方法
    getInject()


    参数



    前置条件
    用户已登录


    后置条件
    返回数据注入界面



    数据注入界面



    url
    /analyze/inject




    方法类型
    post


    控制器
    AnalyzeController


    方法
    postInject($data)


    参数
    需要注入的数据


    前置条件
    用户已登录


    后置条件
    返回数据注入结果



    4.3.2 需要的接口


    服务名
    服务




    AnalyzeModel::getSport
    获取运动数据


    AnalyzeModel::getBody
    获取身高体重数据


    AnalyzeModel::getSleep
    获取睡眠数据


    AnalyzeModel::injectData
    注入数据



    4.4 健身活动4.4.1 提供的接口所有活动列表



    url
    /activity/all




    方法类型
    get


    控制器
    ActivityController


    方法
    getAllActivity($page)


    参数
    页数


    前置条件
    用户已登录


    后置条件
    返回所有活动列表



    我参与的活动列表



    url
    /activity/join




    方法类型
    get


    控制器
    ActivityController


    方法
    getJoinActivity($page)


    参数
    页数


    前置条件
    用户已登录


    后置条件
    返回我参与的活动列表



    我发起的活动列表



    url
    /activity/launch




    方法类型
    get


    控制器
    ActivityController


    方法
    getLaunchActivity($page)


    参数
    页数


    前置条件
    用户已登录


    后置条件
    返回我发起的活动列表



    活动详细信息



    url
    /activity/{id}




    方法类型
    get


    控制器
    ActivityController


    方法
    getActivityDetail()


    参数



    前置条件
    用户已登录


    后置条件
    返回活动详细信息



    发起新的活动



    url
    /activity/launch




    方法类型
    post


    控制器
    ActivityController


    方法
    launchActivity($info)


    参数
    活动信息


    前置条件
    用户已登录


    后置条件
    返回发起结果(成功或失败)



    加入活动



    url
    /activity/join




    方法类型
    post


    控制器
    ActivityController


    方法
    joinActivity($id)


    参数
    活动id


    前置条件
    用户已登录


    后置条件
    返回加入结果



    举报



    url
    /activity/report




    方法类型
    post


    控制器
    ActivityController


    方法
    reportActivity($msg)


    参数
    举报理由


    前置条件
    用户已登录


    后置条件
    返回举报结果



    4.4.2 需要的接口


    服务名
    服务




    ActivityModel::getAllActivity
    获取所有活动


    ActivityModel::getJoinActivity
    获取参加的活动


    ActivityModel::getLaunchActivity
    获取发起的活动


    ActivityModel::getActivityDetail
    获取活动详细信息


    ActivityModel::launchActivity
    发布新的活动


    ActivityModel::joinActivity
    加入活动


    ActivityModel::reportActivity
    举报活动



    4.5 粉丝管理4.5.1 提供的接口关注我的人列表



    url
    /fans/follower




    方法类型
    get


    控制器
    FansController


    方法
    getFollower($page)


    参数
    页数


    前置条件
    用户已登录


    后置条件
    返回关注我的人列表



    我关注的人列表



    url
    /fans/following




    方法类型
    get


    控制器
    FansController


    方法
    getFollowing($page)


    参数
    页数


    前置条件
    用户已登录


    后置条件
    返回我关注的人列表



    粉丝参与的活动



    url
    /fans/activities




    方法类型
    post


    控制器
    FansController


    方法
    getFansActivities($userid, $page)


    参数
    粉丝id,页数


    前置条件
    用户已登录


    后置条件
    返回粉丝参加的活动列表



    4.5.2 需要的接口


    服务名
    服务




    FansModel::getFollower
    获取关注我的人


    FansModel::getFollowing
    获取我关注的人


    ActivityModel::getJoinActivity
    获取粉丝参加的活动



    4.6 个人信息管理4.6.1 提供的接口修改密码



    url
    /user/updatepassword




    方法类型
    post


    控制器
    UserController


    方法
    updatePassword($password)


    参数
    密码(密文)


    前置条件
    用户已登录


    后置条件
    返回修改结果



    修改头像



    url
    /user/updateavatar




    方法类型
    post


    控制器
    UserController


    方法
    updateAvatar($avatar)


    参数
    头像


    前置条件
    用户已登录


    后置条件
    返回修改结果



    4.6.2 需要的接口


    服务名
    服务




    UserModel::updatePassword
    更新用户密码


    UserModel::updateAvatar
    更新用户头像


    1 评论 13 下载 2018-11-01 22:21:10 下载需要11点积分
  • 基于LR分析法的简单分析法

    一、课程设计目的通过设计、编制、调试一个简单计算器程序,加深对语法及语义分析原理的理解,并实现词法分析程序对单词序列的词法检查和分析。
    二、课程设计内容及步骤本次课程设计需要使用 LR 分析法完成简单计算器的设计,其中算术表达式的文法如下:

    〈无符号整数〉∷= 〈数字〉{〈数字〉} 〈标志符〉∷= 〈字母〉{〈字母〉|〈数字〉} 〈表达式〉∷=[+|-]〈项〉{〈加法运算符〉〈项〉} 〈项〉∷= 〈因子〉{〈乘法运算符〉〈因子〉} 〈因子〉∷= 〈标志符〉|〈无符号整数〉|‘(’〈表达式〉‘)’ 〈加法运算符〉∷= +|- 〈乘法运算符〉∷= * |/
    本次课程设计分为如下步骤完成:

    根据题目要求的文法写出产生式进行文法拓广,根据产生式画出识别活前缀的 DFA根据 DFA 写出 LR(0)或 SLR(1)分析表编写程序,对输入串进行分析设计若干用例,上机测试并通过所设计的分析程序
    三、翻译方法3.1 词法分析词法分析是计算机科学中将字符序列转换为单词(Token)序列的过程。进行语法分析的程序或者函数叫作词法分析器(Lexical analyzer,简称 Lexer),也叫扫描器(Scanner)。
    词法分析器一般以函数的形式存在,供语法分析器调用。词法分析是编译过程中的第一个阶段,在语法分析前进行。也可以和语法分析结合在一起作为一遍,由语法分析程序调用词法分析程序来获得当前单词供语法分析使用。简化设计、改进编译效率、增加编译系统的可移植性。词法分析是编制一个读单词的过程,从输入的源程序中,识别出各个具有独立意义的单词,即基本保留字、标识符、常数、运算符、分隔符五大类。并依次输出各个单词的内部编码及单词符号自身值。单词的分类主要分为五类:

    关键字:由程序语言定义的具有固定意义的标识符,也称为保留字或基本字标识符:用来表示程序中各种名字的字符串常 数:常数的类型一般有整型、实型、布尔型、文字型运算符:如+、-、*、/等界限符:如逗号、分号、括号等
    这里将词法分析程序设计成一个子程序,每当语法分析程序需要一个单词时,则调用该子程序。词法分析程序每调用一次,便从源程序文件中读入一些字符,直到识别出一个单词。
    3.2 语法分析语法分析是编译程序的核心部分,其主要任务是确定语法结构,检查语法错误,报告错误的性质和位置,并进行适当的纠错工作。 语法分析的主要工作是识别由词法分析给出的单词序列是否是给定的正确句子(程序)。语法分析常用的方法自顶向下的语法分析和自底向上的语法分析两大类。此次设计中语法分析中主要通过 LR 分析法对语法分析处理过程进行控制,使表达式结果能正确得出,同时识别语法分析中的语法错误。
    语法分析是编译过程的一个逻辑阶段。语法分析的任务是在的基础上将单词序列组合成各类语法短语,如“程序”、“语句”、“表达式”等等。语法分析程序判断源程序在结构上是否正确。源程序的结构由上下文无关文法描述。语法分析程序可以用 YACC 等工具自动生成。
    在语法分析的同时可由语法分析程序调用相应的语义子程序进行语义处理,完成附加在所使用的产生式上的语义规则描述,并完成移进—归约过程。词法分析程序和语法分析程序的关系如图所示:

    3.3 中间代码生成中间代码,也称中间语言,是复杂性介于源程序语言和机器语言的一种表示形式。为了使编译程序有较高的目标程序质量,或要求从编译程序逻辑结构上把与机器无关和与机器有关的工作明显的分开来时,许多编译程序都采用了某种复杂性介于源程序语言和机器语言之间的中间语言。中间代码(语言)是一种特殊结构的语言,编译程序所使用的中间代码有多种形式。按其结构分常见的有逆波兰式(后缀式)、三地址代码(三元式、四元式)和树形表示(抽象语法树)、DAG 表示。本次课程设计不需要生成中间代码。
    四、文法及属性文法描述4.1 文法描述根据题目要求的文法,产生式如下:
    (1) S′→ S (文法拓广)(2) S → E(3) E → E+T(4) E → E-T(5) E → T(6) T → T * F(7) T → T / F(8) T → F(9) F → (E)(10)F → +i(11)F → -i(12)F → i
    4.2 属性文法描述对该文法的属性文法描述如下:

    五、LR 分析法5.1 LR 分析法概述LR 分析法是一种能根据当前分析栈中的符号串(通常以状态表示)和向右顺序查看输入串的 k 个(k≥0)符号就可以唯一地确定分析器的动作是移进还是归约和用哪个产生式归约,因而也就能唯一地确定句柄。LR 分析法的归约过程是规范推导的逆过程,所以 LR 分析过程是一种规范归约的过程。
    LR(k)分析方法是 1965 年 Knuth 提出的,括号中的 k 表示向右查看输入串符号的个数。这种方法比起自顶向下的 LL(k)分析方法和自底向上的优先分析方法对文法的限制要少得多,也就是说对于大多数用无二义性上下文无关文法描述的语言都可以用相应的LR 分析器进行识别,而且这种方法还具有分析速度快,能准确、即时地指出出错位置。它的主要缺点是对于一个实用语言文法的分析器的构造工作量相当大,k 愈大构造愈复杂,实现比较困难。因此,目前许多实用的编译程序,都运用了 LR 分析器。
    5.2 识别活前缀的 DFA本课程设计根据产生式得到的识别活前缀的 DFA 如图所示:

    5.3 SLR(1)分析表根据图可知,I2、I3、I16、I17 存在移进—归约冲突,可用 SLR(1)方法解决冲突。SLR(1)分析表如下:

    5.4 分析过程LR 分析法的规约过程是规范推到的逆过程,所以 LR 分析过程是一种规范规约的过程。其分析过程为:由文法构造出该文法项目集,再根据项目集构造该文法的 DFA,再+判断是否有移进—规约和规约—规约冲突,若没有冲突则该文法为 LR(0)的,若有冲突则该文法是 SLR(1)的,最后可以构造出 LR(0)分析表。然后根据 LR(0)分析表进行语法分析,分析过程就是进栈和规约的过程。若能规约出开始符 S,则语法正确。反之,语法错误。LR 分析法过程流程图如下:

    其中 Sp 为栈顶指针,S[i]为状态栈,X[i]为文法符号栈。状态转换表内容按关系GOTO[Si, X]=Sj 确定,改关系式是指当前栈顶状态为 Si 遇到当前文法符号为 X 时应转向状态 Sj。X 为终结符或非终结符。ACTION[Si, a]规定了栈顶状态为 Sj 时遇到输入符号 c[i]应该执行的动作。动作有以下四种可能:

    移进:当 Sj=GOTO[Si, a]成立,则把 Sj 移入到文法符号栈。其中 i,j 表示状态号规约:当在栈顶形成句柄为 b 时,则用 b 归约为相应的非终结符 A,即当文法中有A→b 的产生式,而 b 的长度为 r,则从状态栈和文法符号栈中自栈顶向下去掉 r 个符号。并把 A 移入文法符号栈内,再把满Sj=GOTO[Si, A]的状态移进状态栈,其中 Si 为修改指针后的栈顶状态接受:当归约到文法符号栈中只剩下文法的开始符号 S 时,并且输入符号串已结束:即当前输入符是‘#’,则为分析成功报错:当遇到状态栈顶为某一状态下出现不该遇到的文法符号时,则报错,说明输入串不是该分发能接受的句子
    六、课程设计结果本次课程设计采用的文法和产生式:

    本次课程设计取了多组测试用例,测试结果如下:
    -0.2*5

    1+2*3

    0.6*(18-3)

    -1++-2-3+4*2.5

    前三个测试用例输入的是正确的表达式,程序的得出的计算结果也正确。最后一个是错误的表达式,第 4 个位置的加号根据产生式 10 被识别为正号,而后面的减号(或负号)无法被识别,因而程序报错。
    七、程序评价及心得体会本次课程设计使用 LR 分析法完成简单计算器的实现。通过对文法的分析,写出了相应产生式,并完成了识别活前缀的 DFA 的绘制、SLR(1)分析表的填写,以及编程实现对输入串的分析过程。并且对题目要求进行了扩展,在词法分析环节加入了对小数的识别,使该计算器支持小数运算。最终通过测试,可验证产生式、分析表和程序的正确性。本次课程设计通过文件读取将文件内容读入到内存中。由于考虑到可能存在的格式问题,词法分析环节会过滤掉首部和尾部空格,再对输入串进行分析。通过本次课程设计,我对 LR 分析法有了更深刻的认识,感到受益匪浅。
    参考文献[1]张素琴,吕映之,蒋维杜,戴桂兰.编译原理(第二版)[M].北京:清华大学出版社.2005.2
    [2][美]Alfred V.Aho,Monica S.Lam,Ravi Sethi,Jeffrey D.Ullman.编译原理[M].北京:机械工业出版社,2009.1.1
    [3][美]Andrew W.Appel.现代编译原理:C 语言描述[M].北京:人民邮电出版社,2006.4.1
    [4][美]Steven S.Muchnick 著,赵克佳,沈志宇译.高级编译器设计与实现[M].北京:机械工业出版社,2005.7.3
    1 评论 26 下载 2018-11-01 21:49:02 下载需要1点积分
  • 基于MFC实现的WEB浏览器

    一、系统设计1.1 总体设计本次课程设计所实现的 Web 浏览器首先要实现设计要求中的功能,要有友好的界面,能正常的浏览网页,能实现后退、前进、刷新等基本功能。
    此外,在要求的功能之上,对 Web 浏览器的功能进行了扩充,能实现网页保存、打印网页、在网页上查找、选中全部内容、标签式多窗口浏览、关闭浏览器提示、页面缩放、网页加载进度显示、浏览器状态以及界面样式更换等功能。
    1.2 详细设计本 Web 浏览器具有较多的功能,因而采用了模块化的设计思想,将每个功能做成了相应的模块,便于开发。另外由于 Web 浏览器的自身性质,本次开发采用了 MFC 的多文档(MDI)模式,使用 App—MainFrame—ChildFrame—View 的模式开发。功能模块的关系如下:

    1.2.1 用户界面设计本 Web浏览器的界面采用类似Microsoft Office 2007 的 Ribbon 界面,摒弃了传统的菜单栏与工具栏组合的界面,使用户界面更加友好。其中,Ribbon界面是由微软公司设计开发,并被加入了MFC 类库,因而在 MFC 框架下使用 Ribbon 界面较为容易,并且符合此次课程设计的要求。

    考虑到标签式的 Web 浏览器是如今的主流,本 Web 浏览器也采用了这种设计,将每个浏览窗口放入标签中,并在标签上添加了关闭按钮,方便用户操作。另外,标签也支持拖动排序,可以拖动标签来修改页面的先后顺序。另外,Windows7 以及更高版本操作系统推出了任务栏显示缩略图功能,本程序用到了MFC 中的这项功能,限定了现实范围,使任务栏只显示网页的缩略图。并且对多标签页面做了适配,使一个主窗口在任务栏上能同时显示多个标签页的内容。如图所示:

    此外,为了丰富界面元素,本 Web 浏览器使用了 Basicset 图标集中的一些图标。另外支持更换界面样式,有5 套界面皮肤可供选择。界面皮肤更换的代码如下:
    void CMainFrame::OnApplicationLook(UINT id){ CWaitCursor wait; theApp.m_nAppLook = id; switch (theApp.m_nAppLook) { case ID_VIEW_APPLOOK_WIN_2000: CMFCVisualManager::SetDefaultManager(RUNTIME_CLASS(CMFCVisual Manager)); m_wndRibbonBar.SetWindows7Look(FALSE); break; case ID_VIEW_APPLOOK_OFF_XP CMFCVisualManager::SetDefaultManager(RUNTIME_CLASS(CMFCVisualMan agerOfficeXP)); m_wndRibbonBar.SetWindows7Look(FALSE); break; case ID_VIEW_APPLOOK_WIN_XP: CMFCVisualManagerWindows::m_b3DTabsXPTheme = TRUE; CMFCVisualManager::SetDefaultManager(RUNTIME_CLASS(CMFCVisualManagerWindows)); m_wndRibbonBar.SetWindows7Look(FALSE); break; case ID_VIEW_APPLOOK_OFF_2003: CMFCVisualManager::SetDefaultManager(RUNTIME_CLASS( CMFCVisualManagerOffice2003)); CDockingManager::SetDockingMode(DT_SMART); m_wndRibbonBar.SetWindows7Look(FALSE); break; case ID_VIEW_APPLOOK_VS_2005: CMFCVisualManager::SetDefaultManager(RUNTIME_CLASS(CMFCVisualManagerVS2005)); CDockingManager::SetDockingMode(DT_SMART); m_wndRibbonBar.SetWindows7Look(FALSE); break; case ID_VIEW_APPLOOK_VS_2008: CMFCVisualManager::SetDefaultManager(RUNTIME_CLASS(CMFCVisualManagerVS2008)); CDockingManager::SetDockingMode(DT_SMART); m_wndRibbonBar.SetWindows7Look(FALSE); break; case ID_VIEW_APPLOOK_WINDOWS_7: CMFCVisualManager::SetDefaultManager(RUNTIME_CLASS(CMFCVisualManagerWindows7)); CDockingManager::SetDockingMode(DT_SMART); m_wndRibbonBar.SetWindows7Look(TRUE); break; default: CMFCVisualManager::SetDefaultManager(RUNTIME_CLASS(CMFC VisualManagerOffice2007)); CDockingManager::SetDockingMode(DT_SMART); m_wndRibbonBar.SetWindows7Look(FALSE); } RedrawWindow(NULL, NULL, RDW_ALLCHILDREN | RDW_INVALIDATE | RDW_UPDATENOW | RDW_FRAME | RDW_ERASE); theApp.WriteInt(_T("ApplicationLook"), theApp.m_nAppLook);}
    1.2.2 多标签模块设计本程序采用的是 MFC 的多文档模式,并对多文档进行了管理,统一到界面的标签上。新建功能是用来创建新的子窗口,并向父窗口注册,显示在标签上。新建窗口分两种情况,一种是用户触发 ID_FILE_NEW 产生的,无论是从 Ribbon 功能区上点击,还是下拉菜单上点击。另外一种是浏览器触发,当浏览器需要弹出新窗口时新建子窗口,这个问题后面会有解决方案的阐述。
    使用 CHtmlView 访问网页的时候,经常会出现脚本错误的提示框,本程序采用了对其设置 SetSilent(TRUE)来屏蔽脚本错误提示。
    1.2.3 浏览模块设计浏览模块作为 Web 浏览器的核心部分,设计使需要考虑很多问题。首先是地址栏。地址栏可以接受用户输入网址,并将信息传给 View 模块,使其加载网页。另外,地址栏还需要能显示当前页面的网址,并且切换标签页的时候,地址栏的网址会随之改变。为了实现以上功能,在 MainFrm.cpp 中 OnAddr 用来接受用户数据并处理,ChangeAddr用来修改地址栏内容。OnAddr首先获取地址栏的指针,然后获取用户输入的内容,如果内容非空,再判断输入的内容是否与当前网页地址相同,防止重复加载。不相同的话,再获取当前活动的子窗口,并将网址传递给子窗口的View。代码如下:
    void CMainFrame::OnAddr(){ CMFCRibbonEdit *pEdit=DYNAMIC_DOWNCAST(CMFCRibbonEdit, m_wndRibbonBar.FindByID(ID_ADDR)); //获取地址输入框 CString strUrl=pEdit->GetEditText(); //获取地址框文本 if(strUrl=="") //数据合法性判断 MessageBox(L"请输入要访问的网址!",L"提示",MB_ICONWARNING); else { CChildFrame *pChildFrame=(CChildFrame*)GetActiveFrame(); CHtmlView *pBrowser=(CHtmlView*)pChildFrame->GetActiveView(); if(pBrowser->GetLocationURL()!=strUrl) pBrowser->Navigate(strUrl); }}
    当活动窗口的网页加载完成之后,会执行 OnDocumentComplete 函数,并调用ChangeAddr函数,将改变传递给主框架。同时,通过IHTMLDocument2 接口获取网页标题,并显示在标签上。因为实践测试,GetLocationName方法获取的网页标题并不准确,因而不采用此方法。
    OnDocumentComplete 的代码如下:
    void CMyView::OnDocumentComplete(LPCTSTR lpszURL){ //浏览器加载完成 CComPtr<IHTMLDocument2> pDoc = (IHTMLDocument2*)this->GetHtmlDocument(); BSTR *bstrTitle; pDoc->get_title(bstrTitle); CString title; title.Empty(); title=*bstrTitle; title=title.Left(20); //只截取前 20 个字符,防止标题过长 GetMainFrame(); //这个函数用来获取主框架和当前子框架的指针 pChildFrame->SetWindowText(title); pMainFrame->ChangeAddr(GetLocationURL()); //调用函数修改 pMainFrame->UpdateFrameTitleForDocument(title); CHtmlView::OnDocumentComplete(lpszURL);}
    ChangeAddr的代码如下:
    void CMainFrame::ChangeAddr(CString str){ //修改地址输入框文本 CMFCRibbonEdit *pEdit=DYNAMIC_DOWNCAST(CMFCRibbonEdit,m_wndRibbonBar.FindByID(ID_ADDR)); pEdit->SetEditText(str);}
    此外,CHtmlView 类有个需要注意的地方,当有新窗口打开时,它会默认调用 IE 打开新窗口。因而需要对其WM_NEWURL 消息进行处理,使其在新标签页打开。代码如下:
    ON_MESSAGE(WM_NEWURL, &CMainFrame::OnNewUrl) //消息映射afx_msg LRESULT CMainFrame::OnNewUrl(WPARAM wParam, LPARAM lParam){ //CHtmlView 类打开了新窗口 LPDISPATCH* ppDispatch=(LPDISPATCH*)wParam; SendMessage(WM_COMMAND, ID_FILE_NEW, 0); CChildFrame* pChildFrame = (CChildFrame*)GetActiveFrame(); *ppDispatch=((CHtmlView*)pChildFrame->GetActiveView())->GetApplication(); return 0;}
    同时处理 CHtmlView 的 OnNewWindow2 事件。代码如下:
    void CMyView::OnNewWindow2(LPDISPATCH* ppDisp, BOOL* Cancel){ //浏览器弹出新窗口(自定义消息) ::SendMessage(AfxGetMainWnd()->m_hWnd,WM_NEWURL,(WPARAM)ppDi sp,NULL); *Cancel=TRUE; CHtmlView::OnNewWindow2(ppDisp, Cancel);}
    1.2.4 操作按钮模块设计 操作按钮即指后退、前进、刷新、停止等功能按钮,其中后退、前进需要特殊处理。因为需要判断当前页面是否可以后退、可以前进,并且对按钮发出可用或禁用消息。并且切换标签页时同步后退、前进按钮的状态。后退、前进按钮的状态可利用 OnCommandStateChange 事件判断。代码如下:
    void CMyView::OnCommandStateChange(long nCommand, BOOL bEnable){ //设置按钮状态 if(pMainFrame) if(nCommand==CSC_NAVIGATEFORWARD) isForward=bEnable; else if(nCommand==CSC_NAVIGATEBACK) isBack=bEnable; GetMainFrame(); pMainFrame->SetButtonState(isForward,isBack); CHtmlView::OnCommandStateChange(nCommand, bEnable);}
    其中 isForward、isBack 是布尔型的成员变量,用于记录状态,并传递给MainFrame。由于CMFCRibbonButton 类没有提供禁用的方法,我们需要自行发送消息来禁用按钮。
    MainFrame的相应代码如下:
    ON_UPDATE_COMMAND_UI(ID_EDIT_BACK,&CMainFrame::ButtonEnable)ON_UPDATE_COMMAND_UI(ID_EDIT_FORWARD,&CMainFrame::ButtonEnable)void CMainFrame::ButtonEnable(CCmdUI *pCmdUI){ //设置按钮状态 if(pCmdUI->m_nID==ID_EDIT_FORWARD) pCmdUI->Enable(isForward); else if(pCmdUI->m_nID==ID_EDIT_BACK) pCmdUI->Enable(isBack); else pCmdUI->Enable(TRUE);}
    另外,后退、前进的功能实现上对按钮状态进行了判断,防止出现意外情况,增强程序的稳定性。代码如下:
    void CMyView::OnEditBack(){ //浏览器后退 if(isBack) GoBack();}void CMyView::OnEditForward(){ //浏览器前进if(isForward) GoForward();}
    Web浏览器的刷新、停止功能可直接调用相应方法,主页即访问百度首页,查找、打印、另存为、全选等功能使用的是CHtmlView 的 ExecWB 方法,具体使用方法不再赘述,可参考微软MSDN 的文档。
    1.2.5 页面缩放模块设计页面缩放用到了 CMFCRibbonSlider 类作为缩放控件,通过IHTMLDocument2 接口修改网页 Document 的缩放级别。
    代码如下:
    void CMyView::OnSlider(){ //页面缩放 GetMainFrame(); double fZoom=pMainFrame->GetZoom(); CComPtr<IHTMLDocument2> pDoc = (IHTMLDocument2*)this->GetHtmlDocument(); ASSERT(pDoc); CComPtr<IHTMLElement> pElem; pDoc->get_body(&pElem); ASSERT(pElem); CComPtr<IHTMLStyle> pStyle; pElem->get_style(&pStyle); CString str; str.Format(L"zoom:%f;",fZoom); pStyle->put_cssText(str.AllocSysString());}
    1.2.6 状态栏模块设计浏览器的状态栏使用了CMFCRibbonStatusBar 类,并分为三个 Panel,第一个部分使用CMFCRibbonStatic 类显示浏览器的状态,比如加载图片、完成等。第二个部分使用CMFCRibbonProgressBar 类以进度条的形式显示浏览器加载进度。第三个部分是静态文本,始终显示”CrazyUrus 浏览器”。如图所示:

    创建 Panel 对象的代码如下:
    m_wndStatusBar.AddDynamicElement(new CMFCRibbonLabel( strTitlePane1,FALSE););m_wndStatusBar.AddExtendedElement(new CMFCRibbonProgressBar( ID_STATUSBAR_PROGRESS,160),L"进度");m_wndStatusBar.AddSeparator();m_wndStatusBar.AddExtendedElement(new CMFCRibbonStatusBarPane (ID_STATUSBAR_PANE2,strTitlePane2, TRUE), strTitlePane2);
    当 Web浏览器执行加载过程时,会触发OnProgressChange 事件,同时将当前进度和进度最大值返回。程序将返回的数据传递给 MainFrame 的进度条即可。代码如下:
    void CMyView::OnProgressChange(long nProgress,long nProgressMax){ //浏览器进度改变,修改程序的进度条 GetMainFrame(); if(pMainFrame) pMainFrame->SetProgress(nProgress,nProgressMax); CHtmlView::OnProgressChange(nProgress, nProgressMax);}
    修改进度条的代码如下:
    void CMainFrame::SetProgress(long value,long max){ //设置进度条 if(ProgressBar) { ProgressBar->SetRange(0,max); ProgressBar->SetPos(value); }}
    浏览器状态修改原理与进度条类似,当状态改变时会触发 OnStatusTextChange 事件,返回状态内容。代码如下:
    void CMyView::OnStatusTextChange(LPCTSTR lpszText){ //浏览器状态改变,修改程序的状态栏 GetMainFrame(); if(pMainFrame) pMainFrame->SetStatusText(lpszText); CHtmlView::OnStatusTextChange(lpszText);}
    修改状态文本的代码如下:
    void CMainFrame::SetStatusText(LPCTSTR str){ //设置状态栏文本 CString tmp=str; static BOOL isFinish=TRUE; if(StatusBar&&!tmp.IsEmpty()&&isFinish) StatusBar->SetText(str); isFinish=tmp.Find(L"完成",0);}
    1.2.7 收藏夹模块设计 收藏夹模块是一个较为简单的模块,因为本浏览器采用了预设的机制,为使用者提供了一些常用的网站,使用者并不能修改收藏夹。目前收藏夹对网站分成了导航、搜索、门户、电子商务、社交和其它几类。每个收藏的网页都对应一个按钮,点击之后会执行相应操作,
    Web浏览器执行Navigate 打开相应网页。

    1.2.8 窗体关闭模块设计窗体关闭模块是用来提醒用户,防止用户错误操作的。由于本 Web 浏览器采用了标签式的浏览方式,因而容易出现关闭主窗口导致所有标签页被关闭的情况,所以需要在关闭主窗口时添加提示,告知用户是否关闭所有标签页,或者只关闭当前的标签页。本程序添加了一个对话框窗口 IDD_CLOSE,如图所示:

    MainFrame 里的 OnClose 函数映射到消息WM_CLOSE,代码如下:
    void CMainFrame::OnClose(){ CCloseDlg closeDlg; UINT r=closeDlg.DoModal(); //打开关闭对话框 if(r==IDALL) CMDIFrameWndEx::OnClose(); else if(r==IDCUR) GetActiveFrame()->DestroyWindow();}
    关闭对话框将以模态打开,并利用 EndDialog 函数实现点击不同的按钮返回不同的值。MainFrame根据返回值执行不同的操作,即可实现不同的关闭方式。
    此外,为了防止用户厌烦此对话框,本程序添加了一个 CheckBox 控件。如果用户勾选,则不再出现此对话框。
    1.3 系统平台、语言和工具 本次课程设计在操作系统为 64 位 Windows 7 的个人计算机上完成,采用C/C++程序设计语言编写代码,Visual Studio 2010 开发和编译程序。由于 MFC 的 Ribbon 界面库具有良好的兼容性,本Web 浏览器程序可在 Windows XP、WindowsVista、Windows 7、Windows 8、Windows8.1 以及更高版本 Windows 操作系统上使用,并且使用了 dll 静态调用技术,运行环境即便缺少 MFC 运行库本程序仍可正常使用。
    二、调试过程及操作说明2.1 启动 Web 浏览器本 Web 的启动方式与其它 Windows 程序无任何区别,双击图标即可启动。如出现缺少dll等情况而无法启动,请请前往微软网站更新MFC 运行库。本程序编译时已经将运行库编译到程序里,上述情况一般不会发生。
    2.2 浏览网页程序启动后,会显示程序的主界面,并自动打开百度首页。如果希望输入网址访问指定网页,请在程序右上角的地址栏中输入网址,并按下回车,浏览器就会在当前窗口加载此网页。如果希望访问收藏家中的网页,可直接点击收藏夹下的按钮或图标,浏览器会在当前窗口加载。如果希望在新窗口打开网页,请先点击“新建”,出现新窗口后再输入网址访问。

    此外,本 Web 浏览器提供了一系列对网页的操作,可以使用后退、前进、刷新、停止等功能,直接点击相应按钮即可使用。另外,本Web 浏览器还提供了查找、打印、保存、全选等功能。如果希望将当前网页保存到本地,可以点击“保存”按钮,程序会弹出对话框,选择位置即可保存。如果希望打印网页,可以直接点击“打印”按钮,在打印对话框中配置好参数,即可打印。如果在打印之前希望预览打印效果,可点击“打印”按钮下的小箭头,出现下拉菜单,并点击“打印预览”。如下图所示:

    如果在打印之前需要对页面大小进行设置,可在下拉菜单中点击“页面设置”。

    如果需要在当前网页查找内容,可点击“查找”,并在对话框中输入内容。

    如果感觉页面字体较大或较小,可通过“视图”中的页面缩放功能调整字体大小。拖动滑动条,页面字体大小会随之变化。
    2.3 修改界面样式及查看帮助本Web浏览器提供了5 种界面样式,其中 4 种是类似 Office 2007 的样式,另外1 种是类似 Windows7 画图程序的样式。如需更换程序界面的样式,请点击程序右上角的“样式”按钮,会出现下拉菜单,在下拉菜单中可以选择喜欢的样式。

    如果需要帮助,可点击右上角的“帮助”按钮,或者功能区中的“帮助”按钮,即会打开帮助文件。由于此次课程设计时间有限,帮助文件并未编写,打开之后里面并没有实质内容,因而使用过程中需要帮助还请参考此设计报告。
    如果需要查看此程序的开发信息,请点击功能区的“关于”按钮,会弹出关于对话框。
    2.4 退出程序退出程序的方式有三种,一是直接在主窗口点击右上角的关闭按钮,程序会弹出关闭对话框(参见图 ),点击“关闭所有选项卡”即退出程序。二是点击左上角的圆形图标(Windows样式下是下拉箭头),会出现下拉菜单,在下拉菜单中点击“退出”,会直接退出程序。如图所示:

    第三种方法是在任务栏上的程序图标,点击右键弹出菜单,在菜单中点击“关闭”,便会弹出的关闭对话框,然后按第一种方法即可退出。
    1 评论 16 下载 2018-11-01 20:41:28 下载需要4点积分
显示 750 到 765 ,共 15 条
eject