LucienXian's Blog


  • 首页

  • 归档

  • 标签

基于opengl绘制五星红旗

发表于 2017-10-01

基于opengl绘制五星红旗

趁着今天是国庆,就更新一下绘制五星红旗的博文吧~祖国万岁

flag
flag

操作步骤

确定绘制方法

这个实验分为两步,第一步是画出旗面,第二则是根据五角星的位置,画出大小、角度不同的三角形;根据《国旗法》要求,国旗长宽比为3:2,这个画起来难度不大,我们将旗面四个点的位置坐标设置为(-0.9,0.6),(0.9, 0.6),(0.9,-0.6),(-0.9, -0.6)。接下来关键是画五角星。

我们来看一下,绘制图形所使用的函数:

1
WINGDIAPI void APIENTRY glVertex3f (GLfloat x, GLfloat y, GLfloat z);

由于该函数只能绘制凸多边形,因此我们选择的绘制方法是,将五角星分成多个扇形三角形,然后顺时针逐个绘制三角形,这样能方便我们循环完成,如图:

star1
star1

计算相关参数

首先是得到各五角星中点的位置,首先是将国旗分为四个小长方形,大五角星的位置为左五右十,上五下五,其它各个中心点如图:

xy
xy

绘制三角形

根据上面确定的绘制方法,我们需要将五角星分为十个扇形三角形,因此要绘制每个三角形,我们只需要三个参数——中心点,长半径的顶点,短半径的顶点,如图:

根据几何计算,这两个角大小分别为18°和36°

tan
tan

因此要画出某一个的三角形,必须要得知中心点和其中的一个顶点。然后根据这两个点计算出长半径和短半径,如图,黑线代表长半径R,蓝线代表短半径r;

star2
star2
  • 首先根据两点之间的距离公式计算出长半径R;
  • 由以上的角度可以计算出:
sincos
sincos

接下来,由于我们每个三角形已知的参数中心点和长半径顶点的坐标(也就是已知第一条长半径的旋转角度),因此我们需要据此计算出短半径的坐标位置:

sincos
sincos

而我们又可以得知两边的夹角为36°,因此只需要将将36°带入β中即可求出下一条边的旋转角,如下:

1
2
tempC = cos1*cos36 - sin1*sin36;
tempS = sin1*cos36 + cos1*sin36;

再根据旋转角和长度利用级坐标公式求出x和y的坐标值(其中midx和midy是五角星中点坐标) :

nwdd
nwdd

代码如下

1
2
3
4
5
6
7
8
if (i % 2 == 0) { //偶数下标是外顶点
point[i][0] = l_length*tempC + midx;
point[i][1] = l_length*tempS + midy;
}
else { //奇数下标是内顶点
point[i][0] = s_length*tempC + midx;
point[i][1] = s_length*tempS + midy;
}

绘制红旗和五角星

样例代码已经可以绘制出红旗,因此只需要将绘制五角星的过程封装成函数,并传入相关参数即可:

1
2
3
4
5
DrawStar(-0.60, 0.30, -0.60, 0.48); 
DrawStar(-0.30, 0.48, -0.24, 0.48);
DrawStar(-0.18, 0.36, -0.24, 0.36);
DrawStar(-0.18, 0.18, -0.18, 0.24);
DrawStar(-0.30, 0.06, -0.24, 0.06);

并且,在函数中确定绘制颜色为黄色,绘制形状为三角形;

1
2
glColor3f(1, 1, 0);
glBegin(GL_TRIANGLE_FAN);

实验数据记录和处理

五星红旗的四个坐标点分别为(-0.9,0.6),(0.9, 0.6),(0.9,-0.6),(-0.9, -0.6);

五角星中最大的一个坐标为(-0.60, 0.30),其余四个分别为(-0.30, 0.48),(-0.18, 0.36), (-0.18, 0.18), (-0.30, 0.06)

实验结果与分析

绘制结果

res
res

完整代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
#include "gl/glut.h"
#include<math.h>

const GLfloat PI = 3.1415926f;
const GLfloat cos36 = cos(PI*0.2);
const GLfloat sin36 = sin(PI*0.2);
const GLfloat cos72 = cos(PI*0.4);


void DrawStar(GLfloat midx, GLfloat midy, GLfloat vx, GLfloat vy)
{
GLfloat point[11][2]; //存下所有顶点的位置
GLfloat l_length = sqrt((midx - vx)*(midx - vx) + (midy - vy)*(midy - vy));//计算出顶点长半径的长度
GLfloat s_length = l_length * cos72 / cos36;//计算出顶点短半径的长度
GLfloat cos1 = (vx - midx) / l_length; //计算出第一个外接顶点与中心点的三角函数值
GLfloat sin1 = (vy - midy) / l_length;

GLfloat tempC, tempS;
point[0][0] = vx, point[0][1] = vy;

for (int i = 1; i < 11; i++)
{
tempC = cos1*cos36 - sin1*sin36;
tempS = sin1*cos36 + cos1*sin36;

if (i % 2 == 0) { //偶数下标是外顶点
point[i][0] = l_length*tempC + midx;
point[i][1] = l_length*tempS + midy;
}
else { //奇数下标是内顶点
point[i][0] = s_length*tempC + midx;
point[i][1] = s_length*tempS + midy;
}
cos1 = tempC;
sin1 = tempS;
}

glColor3f(1, 1, 0);
glBegin(GL_TRIANGLE_FAN);
glVertex3f(midx, midy, 0.5);
for (int i = 0; i < 11; i++)
glVertex3f(point[i][0], point[i][1], 0.5); //绘制三角形
glEnd();
}

void display()
{
glClear(GL_COLOR_BUFFER_BIT);
glColor3f(1, 0, 0);
glBegin(GL_QUADS);

glVertex3f(-0.9, 0.6, 0.5);
glVertex3f(0.9, 0.6, 0.5);
glVertex3f(0.9, -0.6, 0.5);
glVertex3f(-0.9, -0.6, 0.5); //旗面长宽比为3:2
glEnd();

DrawStar(-0.60, 0.30, -0.60, 0.48);
DrawStar(-0.30, 0.48, -0.24, 0.48);
DrawStar(-0.18, 0.36, -0.24, 0.36);
DrawStar(-0.18, 0.18, -0.18, 0.24);
DrawStar(-0.30, 0.06, -0.24, 0.06);

glutSwapBuffers(); //交换缓冲区
}

int main (int argc, char *argv[])
{
glutInit(&argc,argv);
glutInitDisplayMode(GLUT_RGB | GLUT_DOUBLE);
glutInitWindowPosition(10, 10);
glutInitWindowSize(400, 300);
glutCreateWindow("National Flag");

glutDisplayFunc(display);

glutMainLoop();

return 0;
}

心得

这是第一个图形学实验,让我好好回顾了一下三角函数的公式,总体花的时间不是特别多。其中犯的比较大的错误是在计算下一个顶点的旋转角的三角函数时,我没有替代原来的变量,而是在一次循环中直接在计算出正弦角后马上将新的正弦角带入计算余弦角,最后画出了一个不成形的五角星。好在最后找到了这个bug并成功修复。

Bresenham's line algorithm

发表于 2017-09-25

Introduction

计算机图形学课上介绍了一个由两点所决定的直线的算法——Bresenham 算法 ;

这个算法用到了快速的整数加法,减法和位元移位,是计算机图形里面描绘直线的较为快速的算法。

首先来介绍一下,绘制直线的基本方法:

假设我们需要由(x0, y0)这一点,绘画一直线至右下角的另一点(x1, y1),x,y分别代表其水平及垂直坐标,并且x1 - x0 > y1 - y0。

因为像素点需要的x及y皆为整数,但并非每一点x所对应的y皆为整数,因此没有必要去计算每一点x所对应之y值。反之由于此线的斜率介乎于1至0之间,因此我们只需要找出当x到达那一个数值时,会使y上升1,若x尚未到此值,则y不变。至于如何找出相关的x值,则需依靠斜率。斜率之计算方法为m=(y_{1}-y_{0})/(x_{1}-x_{0})。由于此值不变,故可于运算前预先计算,减少运算次数。

如图
如图


Problem

但这里遇到了一个问题,由于计算机图形学对速度要求较高,而上面提到的算法中用到了大量的浮点运算;而且,经过大量的浮点运算,误差会被大量积累。因此为了避免这种情况发生,应该将浮点运算转换为整数运算。

因此就引入了这种算法:Bresenham's line algorithm

  • 首先来看这张图:
line
line
  • 由此可见,每次xi增加时,yi都有两个选择:y(i+1) =yi or y(i+1) =yi + 1;因此我们需要比较d1和d2的大小:
    • y = m(xi + 1) + b
    • d1 = y - yi
    • d2 = yi + 1 - y
    • If d1-d2 > 0,then y(i+1)=yi + 1,else yi+1 = yi
  • 根据上面的式子和m = dy/dx (dy = y1 - y0;dx = x1 - x0),我们可以计算得:
    • d1-d2= 2y - 2yi - 1= 2dy/dxxi + 2dy/dx + 2b - 2yi - 1*
  • 关键点来了,为了避免浮点数运算,我们在两边乘以dx;假设 dx*(d1-d2) = Pi:
    • Pi = 2xidy - 2yidx + 2dy + (2b-1)dx
    • 同理:P(i+1) = 2x(i+1)dy - 2y(i+1)dx + 2dy + (2b-1)dx

这时,当Pi > 0,下一个y取 yi + 1,否则还是yi;

  • 因此我们可以得到一个增量函数:P(i+1) = Pi+2dy-2(y(i+1)-yi)dx;
  • 既然是增量函数:我们要求起始P0:
    • 由上面的式子,P0 = 2x0dy - 2y0dx + 2dy + (2b-1)dx;
    • 两边除以dx,得到 P0/dx = 2x0m - 2y0 + 2m + (2b-1);
    • 结合: 2x0m - 2y0 + 2b = 0,得到P0 = 2mdx - dx = 2dy - dx ;

以上就是,我们对此算法的推导,通过整数运算,移位等,根据Pi的正负,即可快速描绘出直线;

Pseudocode

1
2
3
4
5
6
7
function line(x0, x1, y0, y1)//假设x0 < x1; y0 < y1
draw(x0, y0),dx=x1-x0,dy=y1-y0
int P0 := 2dy - dx
for x from x0 to x1
if Pi>0,then y(i+1) = yi+1,else y(i+1) = yi;
draw(x(i+1), y(i+1))
if Pi>0,then P(i+1) = Pi + 2dy - 2dx,else P(i+1) = Pi + 2dy;//calculate P(i+1)


至于线段方向改变等问题,则只要交换x和y即可;

istringstream in c++

发表于 2017-09-23

问题起源

在刷leetcode的时候,看到了discuss里面有一位大佬提出很有意思的解法。

这是一道mini parser的题,题目描述如下:

Given a nested list of integers represented as a string, implement a parser to deserialize it.

Each element is either an integer, or a list -- whose elements may also be integers or other lists.

Note: You may assume that the string is well-formed:

  • String is non-empty.
  • String does not contain white spaces.
  • String contains only digits 0-9, [, - ,, `]

他的解法是:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
/**
* // This is the interface that allows for creating nested lists.
* // You should not implement it, or speculate about its implementation
* class NestedInteger {
* public:
* // Constructor initializes an empty nested list.
* NestedInteger();
*
* // Constructor initializes a single integer.
* NestedInteger(int value);
*
* // Return true if this NestedInteger holds a single integer, rather than a nested list.
* bool isInteger() const;
*
* // Return the single integer that this NestedInteger holds, if it holds a single integer
* // The result is undefined if this NestedInteger holds a nested list
* int getInteger() const;
*
* // Set this NestedInteger to hold a single integer.
* void setInteger(int value);
*
* // Set this NestedInteger to hold a nested list and adds a nested integer to it.
* void add(const NestedInteger &ni);
*
* // Return the nested list that this NestedInteger holds, if it holds a nested list
* // The result is undefined if this NestedInteger holds a single integer
* const vector<NestedInteger> &getList() const;
* };
*/
class Solution {
public:
NestedInteger deserialize(string s) {
istringstream in(s);
return deserialize1(in);
}
private:
NestedInteger deserialize1(istringstream& in){
int num;
if(in >> num) return NestedInteger(num);
in.clear();
in.get();
NestedInteger lst;
while (in.peek()!=']'){
lst.add(deserialize1(in));
if(in.peek()==',') in.get();
}
in.get();
return lst;
}
};


关于istringstream

但是我今天要说的不是leetcode的解法,而是要介绍一种内存I/O——istringstream。

这个类的对象可以直接从string中读取数据,最基本的

istringstream是基于空格来提取字符串的,从而完成从字符串到其它类型的改变


基于空格提取字符串

1
2
3
4
5
6
7
8
9
10
11
12
#include <sstream>
#include <string>
using std::string;
using std::istringstream;
int main()
{
string a("123a 456 [789]]]");
istringstream in(a);
string s1, s2, s3;
in >> s1 >> s2 >> s3; //s1:"123a", s2:"456", s3:"[789]]]";
return 0;
}


从字符串转化成数字(int / double)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#include <sstream>
#include <string>
#include <iostream>
using std::string;
using std::istringstream;
using std::cout;
using std::endl;
int main()
{
string a("123a ,[456,[789]]]");
istringstream in(a);
int i1, i2;
string s1, s2;
in >> i1;
//in.clear();
in >> s1 >> s2;
cout << i1 << endl;
cout << s1 << endl;
cout << s2 << endl;
return 0;
}
  • 这里的i1为123,s1为“a”,s2为“,[456,[789]]]”;
  • in能顺利将流输入到int类型的i1中,当遇到a时,流输入暂停;开始下一次的输入;


低层的I/O操作

首先引入几个流状态:

iostate value(member constant) indicates functions to check state flags
good() eof() fail() bad() rdstate()
goodbit No errors (zero value iostate) true false false false goodbit
eofbit End-of-File reached on input operation false true false false eofbit
failbit Logical error on i/o operation false false true false failbit
badbit Read/writing error on i/o operation false false true true badbit

引用自:http://www.cplusplus.com/reference/ios/ios/clear/

因此我们来修改一下啊刚才的例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#include <sstream>
#include <string>
#include <iostream>
using std::string;
using std::istringstream;
using std::cout;
using std::endl;
int main()
{
string a("a123a ,[456,[789]]]");
istringstream in(a);
int i1, i2;
string s1;
in >> i1;
//in.clear();
in >> s1;
cout << i1 << endl;
cout << s1 << endl;
return 0;
}
  • 此时,s1,i1都没有被输入正确的数,由于刚开始遇到了'a',流状态被设置为failbit;此时认为流已经结束;

为了避免这种情况发生,我们使用类内成员clear()去清除该状态,使得流状态处于goodbit;把注释去掉,我们可以看到s1变成了"a123a",也就是从错误的地方开始;


此时已经解决了部分的问题,但是如果我们仍然想要123这个整数怎么办?

此时引入了另外几个成员函数:get(),peek();

  • is.get():将is的下一个字节作为int返回,并从流中删除它;
  • is.peek():将is的下一个字节作为int返回,但不从流中删除它;

观察刚刚的例子,我们可以看到第一个字符为'a',因此我们可以从流中获取并删除它:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include <string>
#include <iostream>
using std::string;
using std::istringstream;
using std::cout;
using std::endl;
int main()
{
string a("a123a ,[456,[789]]]");
istringstream in(a);
int i1, i2;
string s1;
in >> i1;
in.clear();
auto m = in.get();
cout << m << endl;
in >> i1;
in >> s1;
cout << i1 << endl;
cout << s1 << endl;
return 0;
}
  • 此时,输出m为字符'a'的ascii码97,i1为123,s1为“a";

C++11新标准 对象移动和右值引用

发表于 2017-09-09

在看c++ primer的时候,了解到了一个c++11的新标准,故记录一下自己的见解。

std::move(对象移动)

需求

首先,这是C++为了避免copy的一种新做法。

  • 正如我们之前了解到的,程序运行过程中很多情况下会发生对象拷贝,但如果在某些特殊情况下,object被copy后就开始销毁,那么如果使用对象move就能大幅度提高性能。
  • 另一个原因则是,像IO类或者unique_ptr类是不允许共享资源的,因此这些类不可以被copy。由此引发的一个是,在旧标准里面,容器只能保存可以被拷贝的类。而新标准里,可以保存不被拷贝的类,只要该类能被移动即可。

含义

举个例子,通过copy constructor,容器vector能将内部指向数据的指针copy起来,原指针则使其为null,这样就避免了对数据内容的copy。

简单说明一下,copy constructor就是分配新的资源,而move constructor则是接管了原来的资源,获得使用了原来资源的权利。

用c++ primer的说法是“窃取”了资源

rvalue reference(右值引用)

需求

为了支持move操作,c++11新标准引进了一个新的引用类型——rvalue reference。

含义

顾名思义,右值引用则是绑定到右值得引用,这其中就涉及到了一个非常有用的性质——右值引用只能绑定到一个将要销毁的对象。只有这样,我们才能将原对象资源move到新的对象当中去。

  • 我们把常规的引用叫做左值引用,通过&来取得;不能将其绑定到需要转换的表达式,字面常量和返回右值得表达式
  • 而通过&&则可以获得右值引用;同理,右值引用则有着相反的性质,不能将右值应用绑定到左值上;

举个例子:

1
2
3
4
5
6
7
8
9
int i = 42;
int &r = i; // lvalue reference
int &&rr = i; // rvalue reference (Error: i is a lvalue)
int &r2 = i*42; // lvalue reference (Error: i*42 is a rvalue)
const int &r3 = i*42; // reference to const (bind to a rvalue)
int &&rr2 = i*42; // rvalue reference
/*****************************************************/
int &&rr1 = 42 // rvalue reference (Error: 42 is a lvalue)
int &&rr2 = rr1 // rvalue reference (Error: rr1 is a lvalue)

注意一下,我们可以将一个const的左值引用或右值引用绑定到右值表达式上

总结一下,左值是持久的,右值是短暂的;因为只有将要被销毁的对象,我们才可以从绑定到右值引用的对象上获取其状态。

使用

这里先借用一下stackoverflow某位答主的话:

std::move() is a cast that produces an rvalue-reference to an object, to enable moving from it

为了我们可以将右值引用直接绑定到左值上,我们可以利用std::move这个标准库函数来获得绑定到左值上的右值引用。该函数定义在头文件utility中。

1
int &&rr2 = std::move(rr1); // ok
  • tips:由于move的名字冲突比其他标准库函数要频繁得多,因此最好加上限定语,不适用using。

MyLove.Diesel

发表于 2017-08-27

240days

  • 螃蟹在剥我的壳,键盘在敲打我。
  • 漫天的我落在壮阔的海面上。
  • 而你在想我。

MyLove]

Python中的repr与的str

发表于 2017-08-20

在学习UCB的CS61a时遇到了一个新的知识点,就是python中repr与str的区别:

总体上来说,这两者在性能和表现上非常相似;

repr返回的是一个对象的更加明确的字符串表示;

str则致力于返回一个通读易懂的字符串表示;

  • 借用stackoverflow的一句话:My rule of thumb: __repr__ is for developers, __str__ is for customers.

str()与repr()

首先来看内置函数str()与repr(),str()致力于为用户的最终输出,而repr()则经常用来调试和开发。repr()计算一个对象的“官方”的字符串表示(一个具有关于该对象的所有信息的表示),而str()用于计算一个对象的“非正式”字符串表示形式(用于打印目的)。

举个例子:

1
2
3
4
5
6
7
8
9
10
>>> s = 'Hello, world.'
>>> str(s)
'Hello, world.'
>>> repr(s)
"'Hello, world.'"
>>> str(str(s))
'Hello, world.'
>>> repr(repr(s))
'"\'Hello, world.\'"'
>>> eval(eval(repr(repr(s))))

eval()可以执行repr返回字符串里面的对象。


类中的__str__与__repr__

我们先来测试一下,可见在这种情况,打印出来的类信息都是一样的,除了object的id之外别无它物。而且类中也有内置的method——repr和str。

1
2
3
4
5
6
7
8
9
10
11
12
>>> class Test(object): pass
...
>>> print(str(Test()))
<__main__.Test object at 0x000000BD373D86A0>
>>> print(repr(Sic()))
<__main__.Test object at 0x000000BD373D86A0>
>>> a = Test()
>>> a.__repr__()
<__main__.Test object at 0x000000BD373D86A0>
>>> a.__str__()
<__main__.Test object at 0x000000BD373D86A0>
>>>

接下来我根据课程上展示的代码来重载__repr__和__str__两个方法,输出结果如注释所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
class Bear():
"""docstring for Bear"""
"""
a bear
a bear
Bear()
my str
my repr
[Finished in 0.1s]
"""
def __init__(self):
self.__repr__ = lambda: 'my repr'
self.__str__ = lambda: 'my str'

def __repr__(self):
return 'Bear()'

def __str__(self):
return 'a bear'
oski = Bear()

print(oski)
print(str(oski))
print(repr(oski))
print(oski.str())
print(oski.repr())

通过对比发现,在只重载repr这个方法的时候,str()这个函数返回的也是类内置的方法repr的内容;

如果重载了str这个方法,str()函数返回的才是类内置方法str的内容;

也就是str中包含了repr的内容,也就是覆盖了str的表达,使其表达更为易懂;

大致使用方法如下:

1
2
3
4
5
6
7
8
def repr(x):
return type(x).__repr__(x)
def str(x):
t = type(x)
if hasattr(t, '__str__'):
return t.__str__(x)
else:
return repr(x)


总结

尽量在类中实现重载repr的方法,使其成为继init之后的第二个性质,以便开发者明白这是怎么实现的;如果你希望在面对用户而不是开发人员时使用更为pretty的表达,就实现str的方法,这是可选的。

python的函数装饰器(UCB cs61a)

发表于 2017-08-09

深入学习装饰器

在学习UCB的cs61a的时候,遇到了一个之前学python没有认真了解过的知识点,因此这回花了一点时间去认真学习它。

Python编程语言有一个有趣的句法功能,称为装饰器。 让我们用一个例子来说明如何以及为什么要使用Python装饰器。这也是CS61a中使用的例子。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
def fib(n):
if n in (0, 1):
return n
else:
return fib(n - 1) + fib(n - 2)
>>> fib(0)
0
>>> fib(1)
1
>>> fib(4)
3
>>> fib(10)
55
>>> fib(30)
832040
>>> fib(35)
9227465

经过测试,我们可以发现,当n越大的时候,计算斐波那契数列的时间就越长。原因我们可以用一个树递归来看看计算fib(5)的时候,发生了什么?

image
image

不难发现,在计算fib(5)的时候,fib(0)计算了三次,fib(1)计算了五次等等。简而言之,就是在计算fib)(5)的时候出现了大量计算。为了使其运行速度更快,我们可以使用一种名为memoization的技术。memoization在缓存中存储与某些特定输入集相对应的结果。 在以后再对其进行调用时,将使用以前存储在缓存中的结果,从而避免重新计算。 这意味着调用具有某些参数的斐波那契数列的主要成本是在第一次调用所花费的,

以下就是我们使用的memorization结果:

1
2
3
4
5
6
7
def memoize(f):
cache = {}
def helper(x):
if x not in cache:
cache[x] = f(x)
return cache[x]
return helper

memoize函数将返回另一个函数,称为helper,helper将为通过参数f接收的函数提供附加功能。helper在创建时将范围内的变量绑定记录了下来,即计算过的斐波那契数列将会被记录在缓存中,下次调用的时候不需要再次计算,而是直接从cache中取出来。

再次调用fib,计算fib(100)的值,可以看到它将迅速计算出结果

1
2
3
>>>fib = memoize(fib)
>>>fib(100)
354224848179261915075

fib = memoize(fib)可以理解成,fib函数被memoize修饰了。在python语法中,有一种简便的使用方式:

1
2
3
4
5
6
7
@some_decorator
def some_function():
# function body...
#等同于
def some_function():
# function body...
some_function = some_decorator(some_function)

因此,本质上,decorator就是一个返回函数的高阶函数。


  • 另一个非常有趣的是,在python装饰器中,我们可以链接多个装饰器在一起。借此,我们来扩展我们的例子来打印我们的调用日志:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
def memoize(f):
# Same code as before...

def trace(f):
def helper(x):
call_str = "{0}({1})".format(f.__name__, x)
print("Calling {0} ...".format(call_str))
result = f(x)
print("... returning from {0} = {1}".format(
call_str, result))
return result
return helper

@memoize
@trace
def fib(n):
if n in (0, 1):
return n
else:
return fib(n - 1) + fib(n - 2)

>>> fib(5)
Calling fib(5) ...
Calling fib(4) ...
Calling fib(3) ...
Calling fib(2) ...
Calling fib(1) ...
... returning from fib(1) = 1
Calling fib(0) ...
... returning from fib(0) = 0
... returning from fib(2) = 1
... returning from fib(3) = 2
... returning from fib(4) = 3
... returning from fib(5) = 5
5


  • 还有一个,decorator本身也可以传入参数。

在这里,我借用的例子来解释一下。原因是,如果装饰器本身需要参数的话,decorator也会变得复杂。因此,我写了一个装饰器,既支持带参数的@log,也支持不带参数的@log

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
import functools
def log2(*args):
text = args[0] if isinstance(args[0],str) else 'without_var'
def decorator(func):
@functools.wraps(func)
def wrapper(*args, **kw):
print('%s, before calling %s():' % (text, func.__name__))
result = func(*args, **kw)
print('%s, after calling %s():' % (text, func.__name__))
return result
return wrapper
return decorator if isinstance(args[0],str) else decorator(args[0])

@log2
def now1():
print('2017-08-09')

@log2('with_var')
def now2():
print('2017-08-09')

"""
>>> now1()
>>> now2()
without_var, before calling now1():
2017-08-09
without_var, after calling now1():
with_var, before calling now2():
2017-08-09
with_var, after calling now2():
"""

不难发现,带参数的装饰器,实则是一个三层嵌套的装饰器。因此调用本质是这样的:

1
2
>>> now2 = log('with_var')(now2)
>>> now1 = log(now1)

解释一下:

如果是带参数的log,如上所示,首先log('with_var')返回了decorator函数,再将now2传进去,返回的则是wrapper函数;

如果是不带参数的log,log('without_var')返回的decorator(args[0]),也就是wrapper函数,此时args[0]是now1函数。

至于,为什么在wrapper前使用@functools.wraps(func),是因为,函数作为一个对象,它的__name__属性在被装饰器修饰过之后,已经从now2和now1都变为了wrapper。@functools.wraps(func)的功能就是还原这一个属性,避免依赖函数签名的代码在执行时出错。

总结

decorator对于扩展函数的功能非常有用,虽然有时在定义的时候非常复杂,需要对高阶函数有一定的熟悉,但当你真正使用起来会发现其具有非常好的便利性。Python直接从语法层次支持decorator,这也是我对python极其喜爱的一大原因。

参考资料


http://programmingbits.pythonblogs.com/27_programmingbits/archive/50_function_decorators.html

https://www.liaoxuefeng.com/wiki/0014316089557264a6b348958f449949df42a6d3a2e542c000/0014318435599930270c0381a3b44db991cd6d858064ac0000#0

http://composingprograms.com/pages/17-recursive-functions.html

bomb lab解题报告(CMU15213)

发表于 2017-08-05

phase5

  1. 应该输入一个字符串,可以知道字符串长度为6;rbx是这个字符串所在位置;
image
image
  1. 从最下面可以知道,这一段是循环,循环六次跳出;从+41开始,首先是rbx+rax所指向的值赋予给ecx,ecx的低八位赋给rsp的所指向的地址,然后再赋值给rdx,然后将这个值的低八位再赋值给rsp+rax+16;

    总结一下,由于rbx存着输入字符串的地址,eax贡献了从0~5的偏移,也就是把输入字符串的六个字符分别赋值给rdx,然后通过与0xf与操作,取出低4位,这个值作为偏移量取0x4024b0后某个变量的值,将这个变量的低八位放到rsp+rax+16上,也就是循环结束时,rsp+16 ~ rsp+21 这6个字节上存储着从0x4024b0开始的一段地址内搬运来的6个变量。

image
image
  1. 紧接着是比较新的字符串和0x40245e内的字符串是否相等,如果相等,则返回0,成功跳出函数
image
image
  1. 查看0x4024b0和0x40245e的值,计算偏移量为9 15 14 5 6 7,对应的二进制1001 1111 1100 0101 0110 0111

    然后查看ascii码表,找出对应的低四位为上面哪几个字符就好,例如9FC567

image
image

phase6

参考资料:http://blog.csdn.net/jason_ranger/article/details/51593255

第六个非常长啊。。。。看了我好久,还好有前人的解题报告可以参考;

首先:

image
image

第一部分,函数(read_six_numbers)将输入两个数字存在rsp连续的24个字节中;然后将rsp存在r14中,r12被赋值0;r13所指的值是第一个输入数

开始while循环,将r13的值赋予rbp,r13所指的值赋给eax,然后eax-1要小于或者等于5,则输入第一个数要小于或者等于6;然后r12加1,与6比较(while循环6次),然后将r12赋值给ebx,再将其赋值给eax(也就是由1~6);接着rsp+4*rax与rbp(rbp与r13挂钩)比较,必须不想等,否则bomb;如果成功跳过,ebx+1与5比较(for循环);这一段就是num1要与num2,num3,num4,num5,num6都不相等;

然后r13加4,也就是,重复while循环,num2分别与num3,num4,num5,num6都不相等。。。。。。。。。

第二部分: image

就是把输入的六个数变为7-num,例如1,2,3,4,5,6变为6,5,4,3,2,1

第三部分:

image
image

这一部分是参考上述链接的:

如果小于等于1则跳到401183:edx赋成0x6032d0,rdx被赋给rsp+2*rsi+32所指向的为止,然后rsi加上4,与24比较,如果相等则跳出这个循环,否则将ecx被赋值成rsp+rsi所指向的值,继续与1比较的过程

不妨假设我们输入的第一个数就是6,所以从401174跳到401197,ecx到这里变成了1,这样满足40119d的跳跃条件,跳到401183,edx赋成6032d0,然后把rdx存到rsp+32的位置(这时rsi是0),然后rsi加上4;

这时可以查看0x6032d0及其后面连续的地址存着什么:

image
image

例如我们输入的1,2,3,4,5,6,7-num之后,链表顺序变为node6,node5,node4,node3,node2,node1

第四部分是将其连起来:

image
image

第五部分:

image
image

这首先把eax赋值成rbx+8的值(rbx在上一个循环中等于rsp+32,第一个节点的地址),然后将eax与rbx所指的结点比较,这里是一个循环,循环五次,依次将前一个结点与后一个比较,要求前面的更大;

观察第三部分的第二张图,可以知道重排链表是: node3 node4 node5 node6 node1 node2;由于7-num;因此输入的是4 3 2 1 6 5;

<i class="fa fa-angle-left"></i>1…2728

278 日志
29 标签
RSS
© 2025 LucienXian
由 Hexo 强力驱动
主题 - NexT.Pisces