【前言】在深入软件白盒测试的过程中,程序流程图的存在感越发强烈。而手动从源代码转换为流程图的过程又是一项繁琐但并不复杂的过程,想必有很多自动化的方法来实现。
参考:
https://code2flow.com/
http://fatesoft.com/s2f/
http://www.athtek.com/code-to-flowchart.html#.WxIFuUxuLZs
大概调研了下,大部分提到的国外几款源码转流程图甚至是流程图转源码工具都是要收费的。
1、程序与程序设计语言的基本知识
1)程序:为解决某一问题而采用程序设计语言编写的一个指令集合。
程序=算法(对操作的描述)+数据结构(对数据的描述)+程序设计语言+语言工具和环境。
2)程序的特点:目的性、有序性、分步性、有限性、操作性。
3)程序设计语言---按发展过程分类
(1)机器语言
由一连串的0和1二进制代码组成,能被计算机直接理解和执行的指令集合。
指令格式:
操作码操作数
(2)汇编语言
使用“助词符”来表示指令的操作码,使用存储单元或寄存器的名字表示地址码。
可读性好,易查错,方便修改,占存储空间少,执行速度快。
(3)高级语言
与机器指令系统无关,独立于机器的程序设计语言,所使用的符号接近人类的自然语言,表达形式接近于被描述的问题。
高级程序设计语言分类:
面向过程的语言:程序不仅要说明做什么,还要详细说明如何做,程序需要详细描述解题的过程和细节;目的在于高效的实现各种算法。
面向问题的语言:又称为非过程化语言或第四代语言,只需指出要计算机做什么、数据的输入和输出形式,就能得到所需结果;目的在于高效、直接地实现各种应用系统。
面向对象的语言:将客观事物看作具有属性和行为的对象,通过抽象找出同一类对象的共同属性和行为,形成类,通过继承和多态重用。
4)翻译程序(编译器)
(1)定义:其他编程语言必须经过一个翻译过程才能转换成机器语言,实现编译过程的工具是语言处理程序。
(2)汇编程序
定义:将汇编语言编制的程序(称源程序)翻译成机器语言程序(称目标程序)的工具。
翻译工作的步骤:
用机器操作码代替符号化的操作符。
用数值地址代替符号名称。
将常数翻译为机器的内部表示。
分配指令和数据的存储单元。
作用:
(3)翻译程序
定义:将高级语言编写的源程序翻译成目标程序的工具。
工作方式:
解释方式:解释程序
解释程序对源程序逐条地解释执行,不产生目标代码,程序执行时,解释程序和源程序一起参加运行。
解释方式执行速度较慢的原因:
每次运行需重新解释。
若程序较大,且错误发生在程序后面,则前面的运行是无效的。
解释程序只看到一句语句,无法对整个程序优化。
编译方式:编译程序
对整个源程序经过编译处理后,产生一个与源程序等价的目标程序。
编译过程的六个阶段:词法分析、语法分析、语义分析、中间代码生成、代码优化、目标代码生成。
5)程序设计语言的支持环境
操作系统是各种软件的核心和基础,是整个系统的控制管理中心,为其他软件提供运行环境。
6)程序设计语言的基本成分和语法
(1)程序的基本语法单位
定义:程序是由语句构成的,而语句又是由数据、表达式、函数等基本语法单位组成的,构成表达式、函数等的最小单位是字符。
字符集:
定义:字符是构成程序设计语言的最小语法单位,每一种程序设计语言都有自己的合法字符集。
基本字符集:数字:0-9
字符:a-z,A-Z
分隔符:,;
运算符:+,-,*,/
数据类型:
定义:数据就是描述客观事物的数字、字符、图片等,即所有能输入到计算机中并能被计算机处理的符号的集合。
分类:基本数据类型:程序设计语言系统内置的。
特点:其值不可再分解。整数类型、实数类型、字符类型、逻辑类型等。
构造数据类型:由基本数据类型按某种方式组合而成的。
数组类型、记录类型、枚举类型、集合类型、字符类型、文件等。
使用数据类型的目的:
决定该类型数据的取值形式、范围和在计算机中的存储和表示方式。
决定了该类型的数据所能执行的操作种类,即能够施加到不同类型数据上的运算是不同的。
表达式:
定义:表达式由变量、常量、函数调用和运算符组成。
常量和变量:数据存入内存→存放数据的内存单元命名→内存单元名来访问其中的数据
常量:在程序运行中数据不变的量。
变量:在程序运行中数据变化的量。
变量在使用前必须声明其类型和名称,编译程序根据其数据类型,在内存中分配相应的存储空间,以存储该变量的值。
运算符:
算术运算:加减乘除和乘方。
字符运算:连接、取子串
关系运算:大于、大于等于、小于、小于等于、等于、不等于
逻辑运算:与、或、非
内部函数:按功能分为数学函数、字符串函数、日期函数、转换函数。
赋值语句:表达式和赋值号构成了程序设计的一条赋值语句。
(2)数据的输入和输出
一类:程序之间以文件形式传送数据。
二类:人机交互,将人们可以识别的形式按一定格式输入到程序的变量中,输出则相反,按用户的要求格式将变量或常量的值显示或打印。
(3)基本控制结构
顺序结构:指程序的执行按照语句出现的先后顺序一次执行。
选择结构:根据条件判断,决定程序的执行顺序。
循环结构:指重复执行某个操作。
(4)过程:指重复处理的程序段或者分解的子功能编写成一个逻辑上独立的过程,当程序需要该过程时进行调用。
2、算法的基本知识
1)算法的描述:程序---计算机语言表述的算法。
流程图---图形化的算法。
2)计算机的算法
(1)数值计算算法
目的:求数值解。
特点:少量输入、输出,复杂的运算。
(2)非数值计算算法
目的:对数据的处理。
特点:大量输入、输出,简单的运算。
3)算法的定义:是解决问题方法的精确描述,但算法并不给出问题的精确解,只是说明怎样才能得到解。
4)算法的性质:有穷性、确定性、有效性、零个或多个输入、一个或多个输出
5)算法=数据对象的运算和操作(算数运算、逻辑运算、关系运算、数据运算)+控制结构(顺序结构、选择结构、循环结构)。
6)描述算法的方法
(1)自然语言
优点:通俗易懂
缺点:易产生歧义,往往根据上下文才能判断其含义,不严格;语句繁琐、冗长,很难清楚地表达算法的逻辑流程。
(2)流程图法
定义:用图框、线条、文字说明,形象直观的描述算法。
流程图符号:
---起止框:表示流程的开始或结束。
---输入输出框:表示输入数据或输出结果。
---判断框:根据条件判断,决定算法的继续执行的走向。
---处理框:表示基本功能的处理的描述。
---流程线:表示流程的路径和方向。
---连接点:表示流程中“向”和“来自”其他地点的输出或输入。
(3)N-S流程图法:全部算法以一个大的矩形框表示,内包含一些从属于它的小矩形框。
顺序语句:
选择语句:
循环语句:
(4)伪代码法:简称伪码,伪代码是介于自然语言和计算机语言之间的文字和符号来描述算法。
7)算法评价
(1)正确性:设计和评价算法的首要条件。
(2)算法的时间特性:指依据算法编制成程序后在计算机中运作所耗费时间的长短。
算法的时间复杂度:把程序中语句重复执行次数之和作为程序运行时的时间特性。
(3)算法的空间特性:指依据算法编制成程序后在计算机中运行所占用空间的大小。
算法的空间复杂度:在计算机上运行所占用的空间同样也是问题规模n的一个函数。
(4)算法的易理解性。
3、程序设计的过程。
1)程序设计步骤
2)测试的前提:程序通过编译,没有语法和连接上的错误。
3)测试用例:为了发现程序中的错误而设计的测试数据。
4)黑盒测试:又称功能测试或数据驱动测试,不考虑程序的内部结构和处理过程,只对程序的接口进行测试,检查程序是否能适当地接受输入数据并产生正确的输出信息。
黑盒测试技术有事务处理流程测试、等价类划分、边界值分析。
5)白盒测试:又称结构测试或逻辑驱动测试,检验程序内部的逻辑来测试,检查程序中每条通路是否正确工作。
百合测试有路径测试、语句测试、分支测试、条件组合测试。
4、程序设计思路
1)三种基本程序结构
(1)顺序结构:
特点:只有一个入口点和一个出口点。
组成:说明语句、赋值语句、输入输出语句、子函数调用语句、返回语句。
(2)选择结构:
特点:程序的处理步骤出现分支,根据某一特定的条件选择其中的一个分支执行。
形式:单选泽、双选择、多选择。
(3)循环结构
当型循环:先判断后执行。
直到型循环:先执行后判断。
2)结构化程序设计的基本思想
采用“自顶向下,逐步求精”(基本原则)的程序设计方法,结构化设计以模块化设计为中心,每个模块各个击破,最后再统一组装。
3)结构化程序设计的特征
(1)以三种基本结构的组合来描述程序
(2)整个程序采用模块化结构。
(3)以控制结构为单位,每个结构只有一个入口一个出口,各单位之间接口简单,逻辑清晰。
(4)采用结构化程序设计语言书写程序,并采用一定的书写格式使程序结构清晰,易于阅读。
(5)注意程序设计风格。
(6)有限制地使用转移语句,在非用不可的情况下,只限于在一个结构内部跳转,不允许从一个结构跳到另一个结构。
4)良好的设计风格
语句形式化、程序一致性、结构规范化、适当使用注释、标识符贴近实际。
5)面对对象的基本概念
(1)对象(object)---最简单的实体
对现实世界中对象的模式化,是数据和代码的组合,具有自己的特征和行为。
对象的特征用数据来表示,称属性。
对象的行为用对象中的代码来实现,称为对象的方法。
任何对象都由属性和方法组成的。
将反映对象的属性和行为封装在一起,是面向对象编程的基本元素,是面向对象设计的核心。
(2)类(class)
类是创建对象实例的模板,是同种对象的集合与抽象,它包含所创建对象的属性描述和行为特征的定义,对象是类的实例。
(3)属性(property)
属性用来表示对象的特征,不同的对象有不同的属性。
(4)方法(method)
方法是对对象的属性的各种操作。
(5)事件、事件过程和事件驱动
事件是面向对象程序设计中对应于“消息”的术语。
对象的事件是指系统事先设定的,能被对象识别和响应的动作。
事件过程:应用程序处理事件的步骤。
事件驱动的编程机制是程序执行后系统等待某个事件的发生,然后去执行处理此事件过程,待事件过程执行完后,系统又处于等待某事件发生的状态。
(6)封装
封装机制将数据和代码捆绑在一起,避免了外界的干扰和不确定性。一个对象就是一个封装了数据和操作这些数据的代码的逻辑实体,封装的目的在于将对象的使用者和对象的设计者分开。
封装一方面通过数据抽象把相关的信息结合在一起,另一方面简化了接口。
封装性可降低开发过程的复杂性,提高效率和质量,同时也保证了程序中数据的完整性和安全性。
(7)继承
继承是可以让某个类型的对象获得另一个类型的对象的属性的方法。
(8)多态性
多态机制使具有不同内部结构的对象可以共享相同的外部接口,虽然针对不同对象的具体操作不同,但通过一个公共类,他们可以通过相同的方式调用。
优点:大大提高程序的抽象程度和简洁性,降低类和模块之间的耦合性,有利于程序的开发和维护。
(9)消息传递
6)OOP的基本过程
(1)分析现实问题。
(2)建立模型(类属性/方法的确定及类之间关系的确定)。
(3)编程建立数据模型(属性、方法)。
(4)用类声明对象,通过对象间传递信息(方法调用)完成预订功能。
7)OOP的优点
(1)通过继承,减少多余的代码并扩展现有代码。
(2)在标准的模块上构建自己的程序,减少软件开发时间并提高生产效率。
(3)数据隐藏---保护程序免受外部代码的侵袭。
(4)允许一个对象的多个实例同时存在,彼此之间相互不干扰。
(5)允许将问题空间中的对象直接映射到程序中。
(6)基于对象的工程可以很容易地分割为独立部分。
(7)以数据为中心的设计方法允许我们抓住可实现模型的更多细节。
(8)面向对象的系统很容易从小到大逐步升级。
(9)对象间通信所使用的消息传递技术与外部系统接口部分描述更简单。
(10)便于控制软件复杂度。
【前言】在深入软件白盒测试的过程中,程序流程图的存在感越发强烈。而手动从源代码转换为流程图的过程又是一项繁琐但并不复杂的过程,想必有很多自动化的方法来实现。
参考:
https://code2flow.com/
http://fatesoft.com/s2f/
http://www.athtek.com/code-to-flowchart.html#.WxIFuUxuLZs
大概调研了下,大部分提到的国外几款源码转流程图甚至是流程图转源码工具都是要收费的。
转载于:https://www.cnblogs.com/cathygx/p/9124851.html
前言
这段时间小编在整理开发代码问题时发现开发同学在使用block时经常出现一些BUG,其中还有一些隐藏的很深的问题,这里小编就为大家介绍一下block的原理,简单用法和常见问题。
Block概要
Block:带有自动变量的匿名函数。
匿名函数:没有函数名的函数,一对{}包裹的内容是匿名函数的作用域。
Block表达式语法:^ 返回值类型 (参数列表) {表达式}
返回类型为空:
参数列表为空:
声明Block类型变量语法:返回值类型 (^变量名)(参数列表) = Block表达式
声明一个变量名为blk的Block:
Block实现原理
Block实际上是作为极普通的C语言源码来处理的:含有Block语法的源码首先被转换成C语言编译器能处理的源码,再作为普通的C源代码进行编译。首先我们先写一个简单的block。
利用终端编译生成C++代码:
clang -rewrite-objc main.m
static void __main_block_func_0( struct __main_block_impl_0 *__cself) { int count = __cself->count; // bound by copy NSLog((NSString *)&__NSConstantStringImpl__var_folders_64_vf2p_jz52yz7x4xtcx55yv0r0000gn_T_main_d2f8d2_mi_0, count); }
这是一个函数的实现,对应Block中{}内的内容,这些内容被当做了C语言函数来处理,函数参数中的__cself相当于Objective-C中的self。
struct __main_block_impl_0 { struct __block_impl impl; struct __main_block_desc_0* Desc; //描述Block大小、版本等信息 int count; //构造函数函数 __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _count, int flags=0) : count(_count) { impl.isa = &_NSConcreteStackBlock; //在函数栈上声明,则为_NSConcreteStackBlock impl.Flags = flags; impl.FuncPtr = fp; Desc = desc; } };
__main_block_impl_0即为main()函数栈上的Block结构体,其中的__block_impl结构体声明如下:
struct __block_impl { void *isa;//指明对象的Class int Flags; int Reserved; void *FuncPtr;};
__block_impl结构体,即为Block的结构体,可理解为Block的类结构。
再看下main()函数翻译的内容:
int main() { int count = 10; void (* blk)() = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, count)); ((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk); }
由此,可以看出,Block也是Objective-C中的对象。
Block常见问题
问题1. Block对外部变量进行修改,最后没有生效导致出现BUG
首先,为什么Block不能修改外部自动变量?
自动变量存于栈中,在当前方法执行完,会释放掉。一般来说,在 block 中用的变量值是被复制过来的,自动变量是值类型复制,新开辟栈空间,所以对于新复制变量的修改并不会影响这个变量的真实值(也称只读拷贝)。大多情况下,block是作为参数传递以供后续回调执行的。所以在你想要在block中修改此自动变量时,变量可能已被释放,所以不允许block进行修改是合理的。对于 static 变量,全局变量,在 block中是有读写权限的,因为此变量存于全局数据区(非栈区),不会随时释放掉,也不会新开辟内存创建变量, block 拷贝的是指向这些变量的指针,可以修改原变量。那么怎么让自动变量不被释放,还能被修改呢?
__block修饰符把所修饰的变量包装成一个结构体对象
,即可完美解决。Block既可以强引用此结构体对象,使之不会自动释放,也可以拷贝到指向该结构体对象的指针,通过指针修改结构体的变量,也就是__block所修饰的自动变量。问题2. Block在使用过程中出现循环引用
在测试过程中,我们经常遇到内存泄漏问题,这里提到的循环引用就是引起内存泄漏的元凶之一,而且Block的循环引用很难被开发同学察觉,因此也需要我们重点注意。
举例说明:
//DemoObj.m@interface DemoObj ()@property (nonatomic, strong) NSMutableArray *myBlocks;@end#pragma mark 将代码改为调用self的方法-(NSMutableArray *)myBlocks{ if (_myBlocks == nil) { _myBlocks = [NSMutableArray array]; } return _myBlocks;}- (instancetype)init{ self = [super init]; if (self) { int(^sum)(int, int) = ^(int x, int y) { return [self sum:x y:y]; }; [self.myBlocks addObject:sum]; } return self;}-(int)sum:(int) x y:(int)y{ return x + y;}#pragma mark 对象被释放时自动调用- (void)dealloc{ NSLog(@"DemoObj被释放");}
大家阅读完上述代码,请问创建的对象可以被正常销毁吗?
答案是否定的,产生问题的原因就是
int(^sum)(int, int) = ^(int x, int y) { return [self sum:x y:y];};
此时sum的block对self强引用,在加上self对myBlocks强引用:
@property (nonatomic, strong) NSMutableArray *myBlocks;
以及sum block被添加到数组时,会被数组强引用:
[self.myBlocks addObject:sum];
这三个引用之间形成了循环引用,如下图:
那我们如何解除循环引用呢?
1. 在block代码中不要引用self以及其他局部变量
int(^sum)(int, int) = ^(int x, int y) { return x + y;};
2. 使用__weak关键字,可以将局部变量声明为弱引用
- (instancetype)init{ self = [super init]; if (self) { __weak DemoObj *weakSelf = self; int(^sum)(int, int) = ^(int x, int y) { return [weakSelf sum:x y:y]; }; [self.myBlocks addObject:sum]; } return self;}
结语
在开发代码中,Block的使用特别的频繁,因此我们在做代码分析时也要重点关注其代码中的常见问题,以免将这类问题遗漏到测试末期,造成产品delay或产生更大的工作量。
大学里学过好多程序流程图,好像是《软件工程》的课。
基本都忘了,有次用visio画图,看到几张图,感觉有印象,才想起是大学里读过的。
流程图是在结构化编程里确实比较有用,认真回忆起来,也确实曾经代码怎么写怎么不对,心急火燎,最终还是换出纸,心平气和的画了张图,才发现自己的谬误在哪。
Code::Blocks 在10.5正式版之后,又搞了好多每夜构建版本,忘了哪个版本,突然就有了一个和流程图有关的功能。
说过了流程图有好多,这个是:
Nassi–Shneiderman diagram
http://en.wikipedia.org/wiki/Nassi%E2%80%93Shneiderman_diagram
我仍然是只记得有这个东东名字,为了好玩,就试了几把。
它能够将一段代码转换成一个流程图,也能够的反过来编辑那张图,然后再生成一段代码。
可能因为前面有布局配置的原因,这个插件一开始我找不到这的工具栏,心里感觉应该有。
方法很简单,通过View -> Perspectives,选中Code::Blocks default。如果没有一排新的工具栏出现,就选中“delete current”,新工具栏就出现了。
本文仅示意从代码到图形:
- //reset width and height
- _width = 0;
- _height = 0;
- //from right to left..
- for (int col=3; col>=0; --col)
- {
- for (int row=0; row<4; ++row)
- {
- if (this->CellAt(row, col))
- {
- _width = col + 1;
- break;
- }
- }
- if (_width != 0)
- break;
- }
这是一个函数中的一段代码。选中它们,右键,选:Nassi Shneiderman -> Create diagram。
就会打开新的一页,图形如下:
如何编辑,关键是如果要在图上修改代码,记得一定要直接点在代码上,就会出现编辑框。其它的大家试。