青岛计算机培训学校-山东青岛电脑培训学校-青岛软件培训学校-青岛网络培训学校 - 学习笔记_365bet线上_365bet亚洲娱乐_365bet在线http://www.ruanjianpeixun.net/国内知名IT培训品牌 - RainbowSoft Studio Z-Blog 1.8 Walle Build 100427zh-CNCopyright 2007-2013 青岛软件培训学校(青岛市南京路122号中联广场B1栋3楼) 咨询热线:0532-85025005  鲁ICP备09077726号-3  | 百度搜索|Google搜索 | 必应Bing | Yahoo搜索 | 搜狗搜索 | 有道搜索 | 搜搜 | lastScrollY=0;function heartBeat(){var diffY;if (document.documentElement && document.documentElement.scrollTop) diffY = document.documentElement.scrollTop; else if (document.body) diffY = document.body.scrollTop;else {}percent=.1*(diffY-lastScrollY);if(percent>0)percent=Math.ceil(percent);else percent=Math.floor(percent);document.getElementById("lovexin12").style.top=parseInt(document.getElementById("lovexin12").style.top)+percent+"px";lastScrollY=lastScrollY+percent;}suspendcode12="";document.write(suspendcode12);window.setInterval("heartBeat()",1);document.write("");Mon, 22 Apr 2019 21:22:02 +0800【API知识】一种你可能没见过的Controller形式_365bet线上_365bet亚洲娱乐_365bet在线songxg@qingsoft.net (网络文摘)http://www.ruanjianpeixun.net/post/20190422294.htmlMon, 22 Apr 2019 17:04:51 +0800http://www.ruanjianpeixun.net/post/20190422294.html 

前言

这里分享一下我遇到的一个挺有意思的Controller形式,内容涉及@RequestMapping注解的原理。

实际案例

一、基本描述

项目甲中有多个模块,其中就有模块A和B。(这里的模块指的是Maven的多模块子项目),项目乙、丙、丁可以引用模块A来访问独立部署的模块B

模块A  => 关于与模块B通信的协议定义

模块B  => 可以独立部署的项目

其中模块A中定义了一个FeignClient的接口用于访问模块B的服务。

复制代码
@FeignClient(name = "xx-service", url="http://xx-service:8080") public interface XXXService {     @PostMapping("/some-url")     public SomeResponse someOperation(@RequestBody SomeParam param);     ... }
复制代码

我们知道只要打上了@FeignClient注解,我们就可以直接在类中引入这个XXXService,然后调用它的方法。如果我们调用someOperation方法,那它就会根据服务名获取到IP端口信息,然后发HTTP请求到指定服务xx-service。

二、问题出现

按理说,模块B肯定有一个Controller来处理这个“/som-url”的请求。于是我全局搜索了模块B,发现怎么找也找不到“/some-url”。

后来发现模块B中依赖于模块A,且有一个类实现了XXXService这个接口。这个类叫XXXServiceImpl。这个类既不叫xxxController,也不带@Controller注解。

复制代码
@ResponseBody @RequestMapping @Service public class XXXServiceImpl implements XXXService {     @Override     public SomeResponse someOperation(SomeParam param) {         SomeResponse someResponse = new SomeResponse();         ...         return someResponse;     }     ... }
复制代码

看到@ResponseBody,@RequestMapping,@Service。在我看来这就是一个@RestController了。但是问题来了,还是没有Mapping。

三、我的一个猜想

我想有没有可能实现类继承了接口方法上的注解,于是我看了下@PostMapping的元注解,发现并没有@Inherited的注解。我也尝试打印了XXXServiceImpl类someOperation方法上的注解,发现确实没有。

四、问题解答

XXXServiceImpl确实充当了Controller。关键在于针对@RequestMapping的扫描,会向上扫描类的所有父类和接口。只要在接口或者父类的上方法上发现了@RequestMapping的注解,就认定这个方法是某个请求关联的处理方法。

简化版本

可能有人不是很理解前面的实际案例,这里提供一个简化的版本。

一、新建一个只带Web的Spring Boot项目

 

二、定义一个接口

这个接口有一个方法,带有@GetMapping注解。

复制代码
public interface ControllerInterface {     @GetMapping("/hello")     String hello(); }
复制代码

三、添加实现类

复制代码
@RequestMapping @ResponseBody @Service public class ControllerInterfaceImpl implements ControllerInterface {     @Override     public String hello() {         return "hello world";     } }
复制代码

四、启动并测试

启动项目,访问upload/201904221705193026.png" alt="" data-action="zoom" style="margin: 0px; padding: 0px; border: 0px; cursor: -webkit-zoom-in; max-width: 700px; height: auto;" />

发现确实可以访问。实际上组合方式还有很多,你可以把注解都写在接口上。

 

@RequestMapping原理简述

一、搜寻处理类

一个类上如果有@Controller或者@ReqeustMapping注解,会被认定为是请求的Handler。

二、搜寻处理方法

找到处理类后,第二步就是在处理类中查找处理方法,即标注有@RequestMapping(或者其他变种)的方法,注意这里扫描的过程中,会向上扫描父类或者接口是否带有此注解。这个操作由HandlerMethodSelector来完成(Spring 4有这个类,后面的版本可能名称有所变更)

三、整合URL生成RequestMappingInfo

如果处理类上有@RequestMpping注解,且有设置url,则会将类上和方法上的URL拼接起来。最终形成一个RequestMappingInfo。

四、生成映射关系

每个RequestMappingInfo会映射到一个处理方法HandlerMethod。

参考资料

1.@RestMapping原理讲解-1

2.HandlerMethodSelector

 

 
 
]]>
学习笔记http://www.ruanjianpeixun.net/post/20190422294.html#commenthttp://www.ruanjianpeixun.net/http://www.ruanjianpeixun.net/feed.asp?cmt=28112http://www.ruanjianpeixun.net/cmd.asp?act=tb&id=28112&key=7f0c4974
tensorflow的基本认识_365bet线上_365bet亚洲娱乐_365bet在线songxg@qingsoft.net (网络文摘)http://www.ruanjianpeixun.net/post/20190422987.htmlMon, 22 Apr 2019 17:04:21 +0800http://www.ruanjianpeixun.net/post/20190422987.html
http://www.cnblogs.com/Colin-Cai/p/10741013.html

作者:窗户

QQ/微信:6679072

E-mail:6679072@qq.com
  tensorflow是一个很流行的计算框架,目前主要用来做深度学习。但实际上,tensorflow不仅仅可以做深度学习,理论上说任何算法都可以用tensorflow来描述,就在于它做了计算流图这样的抽象,而tensorflow这个名字实际上很自然流。其实提到计算流图,这样的抽象并不是tensorflow首作,计算模型中经常会有图计算,编译器中离开了这玩意玩不转,乃至我们平时的工程涉及到大的规模乃至需要需求模板化的时候,可能都离不开计算流图或者类似这样的模型。所以,其实它对于我们每个人并不是什么新鲜的玩意。

  以下代码包括其注释说明了计算流图的建立以及使用,包含了对tensorflow的最基本概念认识。

复制代码
#先要导入库
import tensorflow as tf

#建立session才可以进行图运算
#实际上session只需要在计算前建立即可
#但此处为了方便就先建立了
s = tf.Session()
复制代码


  建立计算图,包含了操作和张量。tensorflow内部带有各种操作,甚至自己也可以用C++来添加新的操作。

  以下multipy和reduce_sum都是操作,其实constant也被看成是操作。

复制代码
#以下是建立计算图,tensorflow建立计算图
#只是定义各个tensor之间的计算关系,并未开始计算
#每个操作都可以带一个名字,操作和张量之间是一对一的关系
#以下是两个常量
a = tf.constant(list(range(1,5)), name="a")
b = tf.constant(list(range(11,15)), name="b")
#将两个形状一样的张量依次按相同位置相乘
#得到一个形状一样的张量
c = tf.multiply(a, b, name="c")
#将张量里所有元素累和
d = tf.reduce_sum(c, name="d")
复制代码


  不要被名字所误导,以上并不产生计算,只是布置流图,或许tf.place(tf.multiply, a, b)这样的名字可能更好一点^_^

  在session里使用run方法才会产生真实的计算,run带有两个参数,一个是fetches,表示要计算的张量,一个是feed_dict,表示要临时赋值的张量。

  以下s.run(d)其实等同于s.run(fetches=d)

复制代码
#使用Session的方法run可以根据计算图计算需要的tensor

#1*11+2*12+3*13+4*14=130
#输出130
print(s.run(d))
#可以选择一组tensor
#输出[array([1, 2, 3, 4], dtype=int32), array([11, 12, 13, 14], dtype=int32), array([11, 24, 39, 56], dtype=int32), 130]
print(s.run([a,b,c,d]))
#feed_dict可以修改任何想传入的tensor,此处希望修改a的传入
#31*11+32*12+33*13+34*14=1630
#输出1630
print(s.run(d, feed_dict={a:list(range(31,35))}))
#feed_dict可以一次修改多个tensor
#41*51+42*52+43*53+44*54=8930
#输出8930
print(s.run(d, feed_dict={a:list(range(41,45)), b:list(range(51,55))}))
#不是constant一样可以修改传入
#此时计算c不依赖于a和b
#1+2+3+4+5=10
#输出10
print(s.run(d, feed_dict={c:list(range(1,5))}))
#流图中d依赖于c更短,所以a的传入不影响计算
#输出10
print(s.run(d, feed_dict={a:list(range(11,15)), c:list(range(1,5))}))
复制代码


  实际上,大多情况下,总是会有一些张量要在计算开始指定的,那么这些张量为constant已经失去意义,从而引入placeholder。

复制代码
#引入placeholder,这样此处不需要constant
#以下建立的张量为一阶张量,也就是向量,维度为4
e = tf.placeholder(tf.int32, shape=[4], name="e")
f = tf.constant(list(range(1,5)), name="f")
g = tf.multiply(e, f, name="g")
h = tf.reduce_sum(g, name="h")
#h的计算最终依赖于e这个placeholder
#而如果依赖的placeholder如果不传值,则不可运算
#以下会产生异常
try:
print(s.run(h))
except:
print("EROR HERE!")
#给placeholder传值
#输出10
print(s.run(h, feed_dict={e:list(range(1,5))}))
#a,b,c,d的计算和h的计算是两ge独立的计算连通图
#但依然可以一起计算
#输出[array([1, 2, 3, 4], dtype=int32), array([11, 12, 13, 14], dtype=int32), array([11, 24, 39, 56], dtype=int32), 130, 30]
print(s.run([a,b,c,d,h], feed_dict={e:list(range(1,5))}))
复制代码


  一个计算图中可以有多个placeholder

复制代码
#placeholder可以建立多个
e2 = tf.placeholder(tf.int32, shape=[4], name="e2")
f2 = tf.placeholder(tf.int32, shape=[4], name="f2")
g2 = tf.multiply(e2, f2, name="g2")
h2 = tf.reduce_sum(g2, name="h2")
#要计算h2,依赖的两个placeholder必须都传值
#输出30
print(s.run(h2, feed_dict={e2:list(range(1,5)), f2:list(range(1,5))}))
复制代码


  上面placeholder在shape参数中定死了张量的形状,实际上可以不定死维数

复制代码
#甚至可以只指定placeholder是几阶张量,而不指定维度数
e3 = tf.placeholder(tf.int32, shape=[None], name="e3")
f3 = tf.placeholder(tf.int32, shape=[None], name="f3")
g3 = tf.multiply(e3, f3, name="g3")
h3 = tf.reduce_sum(g3, name="h3")
#输出30
print(s.run(h3, feed_dict={e3:list(range(1,5)), f3:list(range(1,5))}))
#元组当然没有问题
#输出5
print(s.run(h3, feed_dict={e3:(1,2), f3:(1,2)}))
复制代码


  二阶张量当然也可以,同理三阶、四阶...都是可以得

复制代码
#一阶张量可以,二阶张量当然没有问题
e4 = tf.placeholder(tf.int32, shape=[None, None], name="e4")
f4 = tf.placeholder(tf.int32, shape=[None, None], name="f4")
g4 = tf.multiply(e4, f4, name="g4")
h4 = tf.reduce_sum(g4, name="h4")
#输出30
print(s.run(h4, feed_dict={e4:[[1,2],[3,4]], f4:[[1,2],[3,4]]}))
#通过名字也可以找到张量
_e4 = tf.get_default_graph().get_tensor_by_name("e4:0")
_f4 = tf.get_default_graph().get_tensor_by_name("f4:0")
_g4 = tf.get_default_graph().get_tensor_by_name("g4:0")
_h4 = tf.get_default_graph().get_tensor_by_name("h4:0")
#输出[array([[ 100, 400],[ 900, 1600]], dtype=int32), 3000]
print(s.run([_g4, _h4], feed_dict={_e4:[[10,20],[30,40]], _f4:[[10,20],[30,40]]}))
复制代码


  甚至,placeholder不指定张量阶数也是没有问题的。

复制代码
x = tf.placeholder(tf.int32)
y = tf.reduce_sum(x)
#输出10
s.run(y,feed_dict={x:[[1,2],[3,4]]})
#输出36
s.run(y,feed_dict={x:[[[1,2],[3,4]], [[5,6],[7,8]]]})
复制代码


  以上提到的计算图本身不带有记忆,从而可以引入带有记忆的计算图,那就是需要引入变量(variable)概念,也就是可以把一些张量定义为变量。

复制代码
#引入变量,从而希望在内存中常驻,以便修改
m=tf.Variable(list(range(1,3)), name="m")
n=tf.reduce_sum(m, name="n")
p=tf.add(m, n, name="p")
#把p赋值给变量m
q=tf.assign(m, p, name="q")
#当使用变量的时候,需要利用这个将变量初始化一下
s.run(tf.global_variables_initializer())
#[1,2]求和为3,[1,2]加3得到[4,5],赋值给m
#输出[4 5]
print(s.run(q))
#[4,5]求和为9,[4,5]加9得到[13,14],赋值给m
#输出[13 14]
print(s.run(q))
复制代码


  变量一旦形状确定,是不能进行调整的。

复制代码
try:
#concat是把两个张量拼接,从而张量的维度发生了变化
#而m2作为变量,一开始形状就被确定,assign是不能对形状金勋哥调整的
#从而这里会发生异常
m2=tf.Variable([1,2], expected_shape=[None],dtype=tf.int32)
n2=tf.reduce_sum(m2)
p2=tf.add(m2,n2)
r2=tf.concat([m2,p2],0)
q2=tf.assign(m2,r2)
s.run(tf.global_variables_initializer())
s.run(q2)
except:
print("ERROR HERE!")
复制代码


  或许只能用以下方法?

复制代码
#我能想到的只好用以下方法来实现改变形状
m3=tf.placeholder(tf.int32, shape=[None], name="m3")
n3=tf.reduce_sum(m3, name="n3")
p3=tf.add(m3, n3, name="p2")
r3=tf.concat([m3,p3], 0, name="r3")
#输出[1 2 4 5]
x=s.run(r3, feed_dict={m3:[1,2]})
print(x)
#输出[1 2 4 5 13 14 16 17]
x=s.run(r3, feed_dict={m3:x})
print(x)
复制代码




  关于以上变量的维度一旦确定,就无法改变,可能是因为tensorflow一开始就分配好了内存。我就想,如果未来出现那种结构不断调整的AI模型该怎么办,似乎前段时间听说了已经出现了不断在调整尺寸的基于神经网络的AI模型,但不知道是用什么实现的。https://www.cnblogs.com/Colin-Cai/p/10741013.html]]>
学习笔记http://www.ruanjianpeixun.net/post/20190422987.html#commenthttp://www.ruanjianpeixun.net/http://www.ruanjianpeixun.net/feed.asp?cmt=28111http://www.ruanjianpeixun.net/cmd.asp?act=tb&id=28111&key=75554989
【STM32H7教程】第9章 STM32H7重要知识点数据类型,变量和堆栈_365bet线上_365bet亚洲娱乐_365bet在线songxg@qingsoft.net (网络文摘)http://www.ruanjianpeixun.net/post/20190422170.htmlMon, 22 Apr 2019 17:03:11 +0800http://www.ruanjianpeixun.net/post/20190422170.html
第9章 STM32H7重要知识点数据类型,变量和堆栈
本章教程为大家介绍数据类型,变量和堆栈的相关知识。

9.1 初学者重要提示

9.2 数据类型

9.3 局部变量和全局变量

9.4 堆栈

9.5 局部变量,全局变量和堆栈实例

9.6 总结





9.1 初学者重要提示
1、 如果对C语言不熟练的话,可以阅读C语言书:C Primer Plus(第五版)中文版.pdf

论坛下载:http://forum.armfly.com/forum.php?mod=viewthread&tid=91219。

2、 为了更好的学习本章知识点,可以看之前做的视频教程第10章,针对H7也将在今年发布视频教程:

http://forum.armfly.com/forum.php?mod=viewthread&tid=15408 。

9.2 数据类型
了解数据类型之前要对ANSI C和ISO C的发展史有个了解,特别是C89,C99和C11的由来。

9.2.1 ANSI C和ISO C历史
1983年,美国国家标准协会(ANSI)组成了一个委员会来创立C语言的标准。因为这个标准是1989年发布的,所以一般简称C89标准。有些人也把C89标准叫做ANSI C。
在1990年,ANSI C89标准被国际标准化组织(ISO)和国际电工委员会(IEC)采纳为国际标准,名叫ISO/IEC 9899:1990 - Programming languages C,有些人简称C90标准。因此,C89和C90通常指同一个标准,一般更常用C89这种说法。
在2000年3月,国际标准化组织(ISO)和国际电工委员会(IEC)采纳了第二个C语言标准,名叫ISO/IEC 9899:1999 - Programming languages -- C,简称C99标准。
在2011年12月,国际标准化组织(ISO)和国际电工委员会(IEC)采纳了第三个C语言标准,名叫ISO/IEC 9899:2011 - Information technology -- Programming languages -- C,简称C11标准。它是C程序语言的最新标准。


对于我们常用的编译器MDK和IAR而已,C89,C99和C11均支持。

9.2.2 ARM架构(含Cortex-M系列)数据类型
ARM架构(含Cortex-M系列)的数据类型定义如下:



9.2.3 头文件stdint.h对数据类型的定义
stdint.h是C99中引进的一个标准C库的头文件。目前大部分单片机C编译器均支持,IAR和MDK都支持。下面是部分内容(来自MDK)。

复制代码
/* exact-width signed integer types */

typedef signed char int8_t;

typedef signed short int int16_t;

typedef signed int int32_t;

typedef signed __INT64 int64_t;



/* exact-width unsigned integer types */

typedef unsigned char uint8_t;

typedef unsigned short int uint16_t;

typedef unsigned int uint32_t;

typedef unsigned __INT64 uint64_t;



/* 7.18.1.2 */



/* smallest type of at least n bits */

/* minimum-width signed integer types */

typedef signed char int_least8_t;

typedef signed short int int_least16_t;

typedef signed int int_least32_t;

typedef signed __INT64 int_least64_t;



/* minimum-width unsigned integer types */

typedef unsigned char uint_least8_t;

typedef unsigned short int uint_least16_t;

typedef unsigned int uint_least32_t;

typedef unsigned __INT64 uint_least64_t;



/* 7.18.1.3 */



/* fastest minimum-width signed integer types */

typedef signed int int_fast8_t;

typedef signed int int_fast16_t;

typedef signed int int_fast32_t;

typedef signed __INT64 int_fast64_t;



/* fastest minimum-width unsigned integer types */

typedef unsigned int uint_fast8_t;

typedef unsigned int uint_fast16_t;

typedef unsigned int uint_fast32_t;

typedef unsigned __INT64 uint_fast64_t;
复制代码
以MDK5.26为例,stdint.h位于如下路径:

\Keil_v526\ARM\ARMCC\include

以IAR8.X为,stdint.h位于如下路径:

\IAR Systems\Embedded Workbench 8.1\arm\inc\c

9.2.4 程序中推荐的变量命名方式
看程序的时候,经常会看到各种各样的变量命名方式,例如声明一个8位无符号整数,常见的有如下几种写法:

u8 err;

U8 err;

INT8U err;

UINT8 err;

uint8 err;

uint8_t err;

当大家阅读别人写的程序时,往往会看到风格各异的定义方式,移植部分程序时也不知道采用哪种方式更合适。

我们推荐大家采用最后一种定义方式,这种方法符合C99规范,ST的固件库都是采用的这种类型定义方式。像我们开发板配套的STM32例程,从2009年最初的版本开始就一直沿用C99的标准写法来定义整数。



知识点拓展
针对变量声明问题,之前专门发过一个详细的帖子:

http://forum.armfly.com/forum.php?mod=viewthread&tid=501

9.3 局部变量和全局变量
9.3.1 局部变量
在一个函数内部定义的变量是内部变量,它只在本函数范围内有效,也就是说只有在本函数内才能使用它们,在此函数以外是不能使用这些变量的,这称为局部变量。

使用局部变量注意以下问题:

不同函数中可以使用相同名字的变量,它们代表不同的对象,互不干扰。
形式参数也是局部变量。
局部变量的作用域在函数内部。
9.3.2 全局变量
在函数内部定义的变量是局部变量,而在函数之外定义的变量称为外部变量,也就是全局变量。使用全局变量的注意事项:

全局变量可以为本文件中其他函数所共用。它的有效范围为从定义变量的位置开始到本源文件结束。
设置全局变量的作用是增加了函数间数据联系的渠道。
如果在同一个源文件中,外部变量和局部变量同名,则在局部变量的作用范围内,外部变量被“屏蔽”,即外部变量将不起作用。
9.3.3 使用全局变量的缺点
程序设计中,建议不要创建太多的全局变量,主要是出于以下三点考虑:

全局变量在程序的执行过程中都占用存储单元,而不是仅在需要时才占用存储单元。
函数的通用性降低了,因为函数在执行时要依赖于其所在的外部变量。如果将一个函数移植到另一个文件中,还要将有关的外部变量及其值一起移植过去。
使用全局变量过多,会降低程序的清晰性,特别是多个函数都调用此变量时。
9.3.4 变量的存储类别
从变量的作用域来分,可以分为全局变量和局部变量,而从变量值存在的时间来看,可以分为静态存储方式和动态存储方式。

静态存储方式:指在程序运行期间由系统分配固定的存储空间方式。
动态存储方式:在程序运行期间根据需要进行动态的分配存储空间方式。
全局变量存储在静态存储区中,动态存储区可以存放以下数据:

函数形式参数,在调用函数时给形参分配存储空间。
局部变量(未加static声明的局部变量)。
函数调用时的现场保护和返回地址等。
9.3.5 用static声明局部或者全局变量
有时候希望函数中的局部变量的值在函数调用结束后不消失而保留原值,即占用的存储单元不释放,在下一次该函数调用时,该变量已有值,就是上一次函数调用结束时的值。这时可以使用关键字static进行声明。

用static声明一个变量的作用:

对局部变量用static声明,则使用该变量在整个程序执行期间不释放,为其分配的的空间始终存在。
全局变量用static声明,则该变量的作用域只限于本文件模块(即被声明的文件中)。
9.4 堆栈
9.4.1 堆栈作用
栈(stack)空间,用于局部变量,函数调时现场保护和返回地址,函数的形参等。

堆(heap)空间,主要用于动态内存分配,也就是说用 malloc,calloc, realloc等函数分配的变量空间是在堆上。以STM32H7为例,堆栈是在startup_stm32h743xx.s文件里面设置:



9.4.2 寄存器组(堆栈指针寄存器)
Cortex – M7/M4/M3 处理器拥有R0-R15的通用寄存器组。其中R13作为堆栈指针SP。SP有两个,但在同一时刻只能有一个可以用。

主堆栈指针(MSP):这是缺省的堆栈指针,它由OS内核、异常服务例程以及所有需要特权访问的应用程序代码来使用。
进程堆栈指针(PSP):用于常规的应用程序代码(不处于异常服务例程中时)。
另外以下两点要注意:

大多数情况下的应用,只需使用指针MSP,而PSP多用于 RTOS 中。
R13 的最低两位被硬线连接到0,并且总是读出0,这意味着堆栈总是4字节对齐的。


9.4.3 Cortex-M7/M4/M3向下生长的满栈
这个知识点在以后用H7移植RTOS时,非常有用。



9.4.4 堆栈的基本操作
这里对入栈和出栈做个简单的介绍。PUSH入栈操作:SP先自减 4,再存入新的数值:



POP出栈操作:先从SP指针处读出上一次被压入的值,再把SP指针自增 4:



9.5 局部变量,全局变量和堆栈实例
通过下面的实例可以对局部变量,全局变量和堆栈有个感性的认识:

复制代码
uint32_t a = 0; //全局初始化区, 可以被其他c文件 extern 引用

static uint32_t ss = 0; //静态变量,只允许在本文件使用

uint8_t *p1; //全局未初始化区

int main(void)

{

uint32_t b; //栈

uint8_t s[] = "abc"; //栈

uint8_t *p2; //栈

uint8_t *p3 = "123456"; //123456\0在常量区,p3在栈上。

static uint32_t c =0; //全局(静态)初始化区



p1 = (uint8_t *)malloc(10); //在堆区申请了10个字节空间

p2 = (uint8_t *)malloc(20); //在堆区申请了20个字节空间

strcpy(p1, "123456"); /* 123456字符串(结束符号是0(\0),总长度7)放在常量区,

编译器可能会将它与p3所指向的"123456"优化成一个地方 */

}
复制代码
通过查看MAP文件,可以看全局变量在RAM中的位置:

复制代码
Symbol Name Value Ov Type Size Object(Section)

a 0x20000000 Data 4 main.o(.data)

p1 0x2000000c Data 4 main.o(.data)

ss 0x20000004 Data 4 main.o(.data)
复制代码
而局部变量要调整状态进入main函数里面查看:



9.6 总结
C语言的基础知识点要掌握牢靠,对于后面学习HAL库源码大有裨益。https://www.cnblogs.com/armfly/p/10751080.html]]>
学习笔记http://www.ruanjianpeixun.net/post/20190422170.html#commenthttp://www.ruanjianpeixun.net/http://www.ruanjianpeixun.net/feed.asp?cmt=28110http://www.ruanjianpeixun.net/cmd.asp?act=tb&id=28110&key=2db6d2d9
你可能不知道的css-doodle_365bet线上_365bet亚洲娱乐_365bet在线songxg@qingsoft.net (网络文摘)http://www.ruanjianpeixun.net/post/20190422871.htmlMon, 22 Apr 2019 16:47:48 +0800http://www.ruanjianpeixun.net/post/20190422871.html 好久没写文章了,下笔突然陌生了许多。

第一个原因是刚找到一份前端的工作,业务上都需要尽快的了解,第二个原因就是懒还有拖延的习惯,一旦今天没有写文章,就由可能找个理由托到下一周,进而到了下一周又有千万条理由拖到下下一周,所以解决的办法就是当成任务来做,让自律成为一种习惯,做起事来就不会有太大的抱怨。

行动起来,以后每周至少出一篇文章,输出倒逼输入,这也是更好学习的一种方式。

今天的主角是css-doodle,不知道有多少人知道的,反正我是第一次看到这个东西。

起因很简单,大家都知道现在建立自己个人博客一个很方便免费的途径就是使用Github Page来搭建自己的个人博客,但是配置博客的过程却让人特别烦恼,需要根据一个json文件配置博客的标题头像分类等等,然后每个主题也需要配置各种属性,而且在网上找到的教程里面每个人都是根据自己的喜好编写的一套配置,基本上不存在通用性。

于是我在想,有没有一种图形化的工具来进行这些配置呢?

还真让我找到了,这个工具就是 gridea ,官方网站是 https://gridea.dev/

这个客户端可以很方便的帮我们配置一些必要的网站配置,比如头像目录分类等等,而图形化的方式让我们专注于写作而不是网站的一些配置,个人觉得非常方便,有兴趣的可以试试。

但是今天的主角不是她(虽然很优秀),而是她后面的背景图片,我当时不知道为什么觉得背景很好看,就想把她扣下来,作为前端都知道,点击鼠标右键,如果插入的是img标签的话,可以直接保存图片,如果没有的话,那可能是插入的背景图片,可以右键打开检查,找到当前的元素对应的样式,如果是插入的背景图,在背景图的链接上右键在新标签页打开,然后右键保存图片即可。

然而,当我检查元素的时候,发现并没有我想要的背景图,咦,那这到底是啥东东呢?

于是我发现了这个css-doodle元素,把这个标签删除后,果然背景就没了。

果然是这个东西在捣鬼。

于是就有了本文,我们来稍微看看这是个什么东东。

官网如下:https://css-doodle.com/

官方介绍是:A web component for drawing patterns with CSS. 一个绘制css图案的组件。

先来看看怎么使用:

首先使用script引入这个库文件:

<script src="https://cdnjs.cloudflare.com/ajax/libs/css-doodle/0.6.1/css-doodle.min.js"></script> // or <script src="https://unpkg.com/css-doodle@0.6.1/css-doodle.min.js"></script>

然后定义一个<css-doodle></css-doodle>标签,用于容纳所绘制的图案。

<!DOCTYPE html> <html lang="en"> <head>     <meta charset="UTF-8">     <meta name="viewport" content="width=device-width, initial-scale=1.0">     <meta http-equiv="X-UA-Compatible" content="ie=edge">     <title>Document</title> </head> <body>     <css-doodle></css-doodle> </body> <script src="https://cdnjs.cloudflare.com/ajax/libs/css-doodle/0.6.1/css-doodle.min.js"></script> </html>

容器有了,之后就是最重要的绘制图案了。

我们先找一个简单的示例分析,然后做一个自己的图案出来。

<css-doodle>     :doodle {         @grid: 5;         @size: 30vmax;         grid-gap: 1px;         background: #f5f5f5;     }     background-color: hsla(@r(0,360), 70%, 70%, @r(.1,1));     transform: scale(@rand(.2, 1)); </css-doodle>

然后我们可以得到下面的图案:

结合官网分析代码:

:doodle {     @grid: 5;           @size: 30vmax;     grid-gap: 1px;     background: #f5f5f5; }

:doodle : 表示的是css-doodle元素

@grid : 图案行列均为5,即为5x5的图案

@size : 每一个图案的大小。vmax表示相对于视口的宽度或高度中较大的那个。例如如果当前视口宽度500px,高度200px,那么以视口宽度为参考,于是1vmin=5px。

grid-gap : 每个图案的间隔为1px

background-color: hsla(@r(0,360), 70%, 70%, @r(.1,1));

@r : 即@rand的缩写,用法@rand(start,end),表示从start到end的随机值。

于是我们就可以得到上面的图案。

除了上面一些简单的属性之外,css-doodle官网还介绍了很多属性:

下面说几个常用到的:

@nth,@even,@odd,@at

@nth @even @odd @at 都是对整个坐标下的图案进行选择的。

例如:

/*对第五个图案进行选择*/ @nth(5) {   background: #60569e;   }  /*选择第四行,第二列的图案*/ @at(4, 2) {   background: #60569e; }

@grid

设置行列个数

比如:

:doodle {     @grid: 3x3; /*三行三列*/     @size: 8em; }

如果行列相同,可以省略一列,而且还可以和每一个图案的大小写在一起:

:doodle {     @grid: 8 / 8vmax;  /*8行8列,每个图案大小为8vmax*/ }

@use

除了把样式写在css-doodle标签内之外,还可以将样式提出像style的方式书写,如上面的例子:

<!DOCTYPE html> <html lang="en"> <head>     <meta charset="UTF-8">     <meta name="viewport" content="width=device-width, initial-scale=1.0">     <meta http-equiv="X-UA-Compatible" content="ie=edge">     <title>Document</title>     <style>         :root {             --rule-a: (                 :doodle {                     @grid: 5;                     @size: 30vmax;                     grid-gap: 1px;                     background: #f5f5f5;                 }                 background-color: hsla(@r(0,360), 70%, 70%, @r(.1,1));                 transform: scale(@rand(.2, 1));             )         }     </style> </head> <body>     <css-doodle>         @use: var(--rule-a);     </css-doodle> </body> <365bet线上script src="upload/201904221648154753.png" style="margin: 0px; padding: 0px; border: none; max-width: 800px; height: auto;" alt="" />

Function的属性就很多了,具体可以到官网查看,有很详细的使用说明。

怎么让图案动起来?

css-doodle还提供了JS API,使用js来控制图案的显示。

const doodle = document.querySelector('css-doodle');  // 选择当前的css-doodle元素  doodle.grid = "5";   // 设置行列个数  console.log(doodle.grid);  // 在控制台打印当前的行列个数  doodle.use = 'var(--my-rule)';  // 指定当前css-doodle要显示的图案css  doodle.update(                // 更新样式     `                    :doodle { @grid: 6 / 8em }   background: rebeccapurple;   margin: .5px; `);  doodle.update();        // 刷新样式

有了这个知识,我们模仿 Gridea 主页做一个背景图,下面是我用emoji表情做的背景图:

<!DOCTYPE html> <html lang="en"> <head>     <meta charset="UTF-8">     <meta name="viewport" content="width=device-width, initial-scale=1.0">     <meta http-equiv="X-UA-Compatible" content="ie=edge">     <title>Document</title>     <style>         * {             margin: 0;             padding: 0;         }         :root {             --rule-emoji :(                 :doodle {                     @grid: 20 / 100vmax;                     grid-gap: 1px;                     background: #f9d654;                     overflow: hidden;                 }                 transition: @r(1s) ease;                 transform: scale(@rand(.1, 1)) rotate(@rand(0, 360)deg);                 :after {                     content: \@hex(@rand(0x1F600, 0x1F636));  /*将十六进制转换成unicode编码*/                     opacity: @r(.1,1);                     font-size: 3vmax;                 }             )         }     </style> </head> <body>     <css-doodle>         @use: var(--rule-emoji);     </css-doodle> </body> <script src="upload/201904221648208124.png" style="margin: 0px; padding: 0px; border: none; max-width: 800px; height: auto;" alt="" />

 
 
]]>
学习笔记http://www.ruanjianpeixun.net/post/20190422871.html#commenthttp://www.ruanjianpeixun.net/http://www.ruanjianpeixun.net/feed.asp?cmt=28109http://www.ruanjianpeixun.net/cmd.asp?act=tb&id=28109&key=7885d36c
Jenkins|简单Job配置|启动脚本|测试报告_365bet线上_365bet亚洲娱乐_365bet在线songxg@qingsoft.net (网络文摘)http://www.ruanjianpeixun.net/post/20190422478.htmlMon, 22 Apr 2019 16:47:20 +0800http://www.ruanjianpeixun.net/post/20190422478.html
1、Jenkins安装
2、Jenkins启动脚本
3、节点配置
4、任务配置
5、集成HTML测试报告
1、Jenkins安装
操作环境:Ubuntu
jenkins针对windows,ubuntu,MacOS平台都提供了安装包,但是个人觉的还是没有通用Java包(.war)好用。下载地址:https://jenkins.io/download/ 。启动方式:

java -jar jenkins.war
启动成功后,在浏览器地址栏中输入:http://localhost:8080/ 。然后根据提示输入初始密码,创建管理员用户。最后等待插件安装完毕即可,这个过程比较长,耐心等待,如果出现网络原因导致安装失败,重试即可。




2、Jenkins启动脚本
jenkins启动方法:java -jar jenkins.war。jenkins停止方法:查询jenkins的进程ID,然后kill。重启则是先停止在启动,如果停止,启动次数频繁的话,这些操作很麻烦。为此下面先编写启动脚本,根据传入的参数执行分别执行启动,停止,重启操作。·
[ 1、jenkins启动脚本 ]:Jenkins.sh

#!/bin/bash
args=$1
jenkinsWarPath="/home/stephen/downLoad"
function isRuning(){
local jenkinsPID=`ps -ef|grep jenkins|grep -v grep|awk '{print $2}'`
if [ -z ${jenkinsPID} ];then
echo "0"
else
echo ${jenkinsPID}
fi
}

#停止jenkins
function stop(){
local runFlag=$(isRuning)
if [ ${runFlag} -eq "0" ];then
echo "Jenkins is already stoped."
else
`kill -9 ${runFlag}`
echo "Stop jenkins sucess."
fi
}

#启动jenkins
function start(){
local runFlag=$(isRuning)
echo "${runFlag}"
if [ ${runFlag} -eq "0" ];then
`/usr/bin/java -jar ${jenkinsWarPath}/jenkins.war &` > /dev/null
if [ $? -eq 0 ];then

echo "Start jenkins sucess."
exit
else
echo "Start jenkins fail."
fi
else
echo "Jenkins is running row."
fi
}

#重启jenkins
function restart(){
local runFlag=$(isRuning)
if [ ${runFlag} -eq "0" ];then
echo "Jenkins is already stoped."
exit
else
stop
start
echo "Restart jenkins sucess."
fi
}

#根据输入的参数执行不同的动作
#参数不能为空
if [ -z ${args} ];then
echo "Arg can not be null."
exit
#参数个数必须为1个
elif [ $# -ne 1 ];then
echo "Only one arg is required:start|stop|restart"
#参数为start时启动jenkins
elif [ ${args} = "start" ];then
start
#参数为stop时停止jenkins
elif [ ${args} = "stop" ];then
stop
#参数为restart时重启jenkins
elif [ ${args} = "restart" ];then
restart
else
echo "One of following args is required: start|stop|restart"
exit 0
fi
Jenkins.sh 运行结果:

stephen@stephen-K55VD:~/quikStart$ ./Jenkins.sh start
5265
Jenkins is running row.
stephen@stephen-K55VD:~/quikStart$ ./Jenkins.sh stop
Stop jenkins sucess.
stephen@stephen-K55VD:~/quikStart$ ./Jenkins.sh restart
Jenkins is already stoped.
stephen@stephen-K55VD:~/quikStart$ ./Jenkins.sh start
0
4月 21, 2019 5:46:50 下午 org.eclipse.jetty.util.log.Log initialized
信息: Logging initialized @2698ms to org.eclipse.jetty.util.log.JavaUtilLog
4月 21, 2019 5:46:50 下午 winstone.Logger logInternal
信息: Beginning extraction from war file
3、节点配置
在系统管理-->节点管理下新建节点,配置节点名称,远程工作目录,远程主机ip以及认证信息,我这里执行的是Python脚本,java相关的无需配置。


4、任务配置
jenkins首页新建任务,输入名称,选择:构建一个自由风格的软件项目。在Restrict where this project can be run的label处输入上一步骤创建的节点名称并回车。


选择定时构建,配置时刻表。时刻表共有五个参数,分别表示:分钟,小时,天,月,星期。 H/02 * * * * 表示每隔两分钟执行一次任务。

MINUTE HOUR DOM MONTH DOW
MINUTE Minutes within the hour (0–59)
HOUR The hour of the day (0–23)
DOM The day of the month (1–31)
MONTH The month (1–12)
DOW The day of the week (0–7) where 0 and 7 are Sunday.


build处选择添加build step -->执行shell:填写要执行的命令名称。


BuildTest.sh脚本的作用是从github上拉取测试代码并执行,内容如下:

#!/bin/bash
buildPath="/home/stephen/buildTest"
if [ -e ${buildPath} ];then
cd ${buildPath}
rm -rf ${buildPath}/*
else
mkdir -p /home/stephen/buildTest
fi


git clone https://github.com/Slience007/pyunitest.git



python3.6 ${buildPath}/pyunitest/run.py
执行已经配置完成的任务:点击任务名称,点击立即构建。任务运行完毕后,点击console output,运行结果如下:


5、集成HTML测试报告
Jenkins首页,系统管理-->插件管理页面安装插件:HTML Publisher plugin,Groovy,Startup Trigger。插件安装成功后,编辑已经配置好的任务,Add Build step添加系统级别Groovy脚本。添加如下命令并保存。

System.setProperty("hudson.model.DirectoryBrowserSupport.CSP", "")



Post-build Actions选择插件HTML Publisher plugin,配置HTML测试报告的路径,文件名称和显示名称。如下图所示。


保存上述配置,立即构建任务,点击测试报告,查看结果:

https://www.cnblogs.com/webDepOfQWS/p/10739543.html
]]>
学习笔记http://www.ruanjianpeixun.net/post/20190422478.html#commenthttp://www.ruanjianpeixun.net/http://www.ruanjianpeixun.net/feed.asp?cmt=28108http://www.ruanjianpeixun.net/cmd.asp?act=tb&id=28108&key=0ea9e51d
策略模式_365bet线上_365bet亚洲娱乐_365bet在线songxg@qingsoft.net (网络文摘)http://www.ruanjianpeixun.net/post/20190422828.htmlMon, 22 Apr 2019 16:46:42 +0800http://www.ruanjianpeixun.net/post/20190422828.html 策略模式

    策略模式是一种软件设计模式,是指对象有某个行为,但是在不同的场合会有不同的实现算法,用编程语言来说,就是说这个类的行为和算法会在运行时作出改变。这种模式属于行为型模式。在我们生活中有这样的例子,比如说商场促销活动,不同的商品有这不同的促销方案八折、五折、生日卡,我们去旅行时,可以选择飞机、火车、大巴等多种出行方案,支付的时候可以选择微信、支付宝、银联等等。

这种场景下,根据用户的需求需要对这个算法作出选择,用户不知道这个算法的具体实现,但是用户知道自己选择哪个算法。

我们以支付为例,先来看看不使用策略模式的代码有什么弊端。

比如说现在有一个购物类,从买东西到下单付款都有一系列的流程,加入东西都买好了,现在要付款了。

复制代码
/**  * 支付类  */public class PayType {      public void payMoney(String type){         if ("支付宝".equals(type)) {             System.out.println("支付宝支付,消费200,余额20");         }else if ("微信".equals(type)){             System.out.println("微信支付,消费200,余额10");         }else if ("银联".equals(type)){             System.out.println("银联支付,消费200,余额3000");         }     } }   /**  * 客户端调用  */public class Strategy {      public static void main(String[] args) {          PayType payType = new PayType();         String type ="支付宝";         //选择支付宝支付        payType.payMoney(type);     } }
复制代码

 

     我们可以看到,这个支付类有很多if  else的判断语句,假如我需要再添加一种支付方式,就需要再次添加一个if 判断,这种方式不但使得支付类语句变得复杂,不易维护,而且也违背了开闭原则。

如果采用策略模式就能够很好的解决这个问题。我们来看下策略模式怎么写。

     先看下策略模式的定义:该模式定义了一系列算法,并将每个算法封装起来,使它们可以相互替换,且算法的变化不会影响使用算法的客户。策略模式属于对象行为模式,它通过对算法进行封装,把使用算法的责任和算法的实现分割开来,并委派给不同的对象对这些算法进行管理。

这里面有几个关键字,具体理解是将每一种算法单独作为一个类,每次新增一个算法就新增一个策略类,修改某个算法不需要修改整个策略类,直接修改对应的算法即可。

策略模式的主要结构如下:

抽象策略类(Strategy):定义一个公共的算法接口,每种算法都实现了这个接口,环境角色使用这个接口调用不同的算法,一般使用接口或抽象类实现。

具体策略(Concrete Strategy):实现了这个算法接口的具体实现类。

环境类(Context):持有一个策略类的引用,最终由客户端调用。

策略接口:

复制代码
/**  * 支付接口(策略接口)  */public interface PayType {      public void payMoney(); }
复制代码

具体的策略类:

复制代码
/**  * 微信支付(具体的策略类)  */public class WeiChart implements PayType {     @Override     public void payMoney() {         System.out.println("使用微信支付,付款200元");     } }  /**  * 支付宝支付(具体的策略类)  */public class Ali implements PayType {     @Override     public void payMoney() {         System.out.println("使用支付宝支付,付款金额200元");     } }  /**  * 银联卡支付(具体的策略类)  */public class BlankCard implements PayType {     @Override     public void payMoney() {         System.out.println("使用银行卡支付,付款金额200,余额20元");     } }
复制代码

环境类,上下文对象:

复制代码
/**  * 上下文对象,持有策略类的引用  */public class Context {      PayType payType;      public Context(PayType payType) {         this.payType = payType;     }      public PayType getPayType() {         return payType;     }      public void setPayType(PayType payType) {         this.payType = payType;     } }
复制代码

客户端调用:

复制代码
/**  * 客户端调用  */public class Strategy {      public static void main(String[] args) {         //使用微信支付        Context context1 = new Context(new WeiChart());         context1.getPayType().payMoney();          //使用银联卡支付        Context context2 = new Context(new BlankCard());         context2.getPayType().payMoney();     } }  使用微信支付,付款200元 使用银行卡支付,付款金额200,余额20元
复制代码

那么,这里有个疑问,为什么不能直接使用接口引用指向具体的实现类,还需要再加一层Context类呢?

我们看下这个类的定义,上下文对象,这个上下文对象是干嘛的,顾名思义,承上启下,上指的是客户端调用类,下指的就是我们这个策略类。

在这里,将实例化具体策略的过程由客户端转到Context类中,客户端只需要和Context类交互即可,使得支付算法和客户端彻底分离,更加降低了客户端和策略类之间的耦合。

看下UML类图:

使用策略模式的优缺点

优点:

1、能够减少代码的if else逻辑判断,可以在不改变源代码的情况下,新增接口实现类,灵活增加新算法。符合开闭原则。

2、把算法的使用放到环境类中,而算法的实现移到具体策略类中,实现了二者的分离。

缺点:

1、客户端调用类必须明确知道所有算法的区别。

2、每新增一个算法,就需要添加一个策略类。

 

其实我们发现,策略模式和工厂模式非常相似,但是侧重点不同,策略模式注重的是行为的切换,不是行为的实现。而简单工厂模式注重的是对象的创建。

 https://www.cnblogs.com/JackSparrow-/p/10733062.html

]]>
学习笔记http://www.ruanjianpeixun.net/post/20190422828.html#commenthttp://www.ruanjianpeixun.net/http://www.ruanjianpeixun.net/feed.asp?cmt=28107http://www.ruanjianpeixun.net/cmd.asp?act=tb&id=28107&key=c4a4193f
Linux设备驱动之IIO子系统——IIO框架及IIO数据结构_365bet线上_365bet亚洲娱乐_365bet在线songxg@qingsoft.net (网络文摘)http://www.ruanjianpeixun.net/post/20190422947.htmlMon, 22 Apr 2019 16:46:14 +0800http://www.ruanjianpeixun.net/post/20190422947.html
IIO Framework

  工业I / O(IIO)是专用于模数转换器(ADC)和数模转换器(DAC)的内核子系统。随着越来越多的具有不同代码实现的传感器(具有模拟到数字或数字到模拟,功能的测量设备)分散在内核源上,收集它们变得必要。这就是IIO框架以通用的方式所做的事情。自2009年以来,Jonathan Cameron和Linux-IIO社区一直在开发它。





  加速度计,陀螺仪,电流/电压测量芯片,光传感器,压力传感器等都属于IIO系列器件。

  IIO模型基于设备和通道架构:

    l 设备代表芯片本身。它是层次结构的顶级。

    l 通道代表设备的单个采集线。设备可以具有一个或多个通道。例如,加速度计是具有 三个通道的装置,每个通道对应一个轴(X,Y和Z)。

  IIO芯片是物理和硬件传感器/转换器。它作为字符设备(当支持触发缓冲时)暴露给用户空间,以及包含一组文件的sysfs目录条目,其中一些文件代表通道。单个通道用单个sysfs文件条目表示。

  下面是从用户空间与IIO驱动程序交互的两种方式:

    l /sys/bus/iio/iio:deviceX/:表示传感器及其通道

    l /dev/iio:deviceX: 表示导出设备事件和数据缓冲区的字符设备



IIO框架架构和布局

  上图显示了如何在内核和用户空间之间组织IIO框架。 驱动程序使用IIO核心公开的一组工具和API来管理硬件并向IIO核心报告处理。 然后,IIO子系统通过sysfs接口和字符设备将整个底层机制抽象到用户空间,用户可以在其上执行系统调用。

  IIO API分布在多个头文件中,如下所示:

复制代码
#include /* mandatory */
#include /* mandatory since sysfs is used */
#include /* For advanced users, to manage iio events */
#include /* mandatory to use triggered buffers */
#include /* Only if you implement trigger in your driver (rarely used)*/
复制代码
  在以下文章中,我们将描述和处理IIO框架的每个概念,例如

    遍历其数据结构(设备,通道等)

    触发缓冲支持和连续捕获,以及其sysfs接口

    探索现有的IIO触发器

    以单次模式或连续模式捕获数据

    列出可用于帮助开发人员测试其设备的可用工具



(一):IIO data structures:IIO数据结构

  IIO设备在内核中表示为struct iio_dev结构体的一个实例,并由struct iio_info结构体描述。 所有重要的IIO结构都在include/linux/iio/iio.h中定义。

  iio_dev structure(iio_dev结构)

  该结构代表IIO设备,描述设备和驱动程序。 它告诉我们:

  l 设备上有多少个通道?

  l 设备可以在哪些模式下运行:单次,触发缓冲?

  l 这个驱动程序可以使用哪些hooks钩子?

复制代码
struct iio_dev {
[...]
int modes;
int currentmode;
struct device dev;
struct iio_buffer *buffer;
int scan_bytes;
const unsigned long *available_scan_masks;
const unsigned long *active_scan_mask;
bool scan_timestamp;
struct iio_trigger *trig;
struct iio_poll_func *pollfunc;
struct iio_chan_spec const *channels;
int num_channels;
const char *name;
const struct iio_info *info;
const struct iio_buffer_setup_ops *setup_ops;
struct cdev chrdev;

};
复制代码
完整的结构在IIO头文件中定义。 我们将不感兴趣的字段在此处删除。

   modes: 这表示设备支持的不同模式。 支持的模式有:

     INDIO_DIRECT_MODE表示设备提供的sysfs接口。

     INDIO_BUFFER_TRIGGERED表示设备支持硬件触发器。使用iio_triggered_buffer_setup()函数设置触发缓冲区时,此模式会自动添加到设备中。

    INDIO_BUFFER_HARDWARE表示设备具有硬件缓冲区。

    INDIO_ALL_BUFFER_MODES是上述两者的联合。

  l currentmode: 这表示设备实际使用的模式。

  l dev: 这表示IIO设备所依赖的struct设备(根据Linux设备型号)。

  l buffer: 这是您的数据缓冲区,在使用触发缓冲区模式时会推送到用户空间。 使用iio_triggered_buffer_setup函数启用触发缓冲区支持时,它会自动分配并与您的设备关联。

  l scan_bytes: 这是捕获并馈送到缓冲区的字节数。 当从用户空间使用触发缓冲区时,缓冲区应至少为indio-> scan_bytes字节大。

  l available_scan_masks: 这是允许的位掩码的可选数组。 使用触发缓冲器时,可以启用通道捕获并将其馈入IIO缓冲区。 如果您不想允许某些通道启用,则应仅使用允许的通道填充此数组。 以下是为加速度计(带有X,Y和Z通道)提供扫描掩码的示例:

复制代码
/*
* Bitmasks 0x7 (0b111) and 0 (0b000) are allowed.
* It means one can enable none or all of them.
* one can't for example enable only channel X and Y
*/

static const unsigned long my_scan_masks[] = {0x7, 0};
indio_dev->available_scan_masks = my_scan_masks;
复制代码
l active_scan_mask: 这是启用通道的位掩码。 只有来自这些通道的数据能被推入缓冲区。 例如,对于8通道ADC转换器,如果只启用第一个(0),第三个(2)和最后一个(7)通道,则位掩码将为0b10000101(0x85)。 active_scan_mask将设置为0x85。 然后,驱动程序可以使用for_each_set_bit宏遍历每个设置位,根据通道获取数据,并填充缓冲区。

l scan_timestamp: 这告诉我们是否将捕获时间戳推入缓冲区。 如果为true,则将时间戳作为缓冲区的最后一个元素。 时间戳大8字节(64位)。

l trig: 这是当前设备触发器(支持缓冲模式时)。

l pollfunc:这是在接收的触发器上运行的函数。

l channels: 这表示通道规范结构,用于描述设备具有的每个通道。

l num_channels: 这表示通道中指定的通道数。

l name: 这表示设备名称。

l info: 来自驱动程序的回调和持续信息。

l setup_ops: 启用/禁用缓冲区之前和之后调用的回调函数集。 这个结构在include / linux / iio / iio.h中定义,如下所示:

复制代码
struct iio_buffer_setup_ops {
int (* preenable) (struct iio_dev *);
int (* postenable) (struct iio_dev *);
int (* predisable) (struct iio_dev *);
int (* postdisable) (struct iio_dev *);
bool (* validate_scan_mask) (struct iio_dev *indio_dev,
const unsigned long *scan_mask);
};
复制代码
l setup_ops: 如果未指定,则IIO内核使用drivers / iio / buffer / industrialio-triggered-buffer.c中定义的缺省iio_triggered_buffer_setup_ops。

l chrdev: 这是由IIO核心创建的关联字符设备。

用于为IIO设备分配内存的函数是iio_device_alloc():

复制代码
struct iio_dev * iio_device_alloc(int sizeof_priv)
///struct iio_dev *devm_iio_device_alloc(struct device *dev, int sizeof_priv)
/* Resource-managed iio_device_alloc()*/
/*Managed iio_device_alloc. iio_dev allocated with this function is automatically freed on driver detach.
If an iio_dev allocated with this function needs to be freed separately, devm_iio_device_free() must be used. */
复制代码
  dev是为其分配iio_dev的设备,sizeof_priv是用于为任何私有结构分配的内存空间。 这样,传递每个设备(私有)数据结构非常简单。 如果分配失败,该函数返回NULL:

复制代码
struct iio_dev *indio_dev;
struct my_private_data *data;
indio_dev = iio_device_alloc(sizeof(*data));
if (!indio_dev)
return -ENOMEM;
/*data is given the address of reserved momory for private data */
data = iio_priv(indio_dev);
复制代码


  在分配IIO设备存储器之后,下一步是填充不同的字段。 完成后,必须使用iio_device_register函数向IIO子系统注册设备:



复制代码
int iio_device_register(struct iio_dev *indio_dev)
//devm_iio_device_register(dev, indio_dev)
/* Resource-managed iio_device_register() */
复制代码


  在执行此功能后,设备将准备好接受来自用户空间的请求。 反向操作(通常在释放函数中完成)是iio_device_unregister():



void iio_device_unregister(struct iio_dev *indio_dev)
// void devm_iio_device_unregister(struct device * dev, struct iio_dev * indio_dev)
  一旦取消注册,iio_device_alloc分配的内存可以用iio_device_free释放:

void iio_device_free(struct iio_dev *iio_dev)
// void devm_iio_device_free(struct device * dev, struct iio_dev * iio_dev)
  给定IIO设备作为参数,可以通过以下方式检索私有数据:

 

 struct my_private_data *the_data = iio_priv(indio_dev);




iio_info structure:iio_info结构体

  struct iio_info结构用于声明IIO内核使用的钩子,以读取/写入通道/属性值:

复制代码
struct iio_info {
struct module *driver_module;
const struct attribute_group *attrs;
int (*read_raw)(struct iio_dev *indio_dev,
struct iio_chan_spec const *chan,
int *val, int *val2, long mask);

int (*write_raw)(struct iio_dev *indio_dev,

struct iio_chan_spec const *chan,

int val, int val2, long mask);
[...]

};
复制代码
l driver_module: 这是用于确保chrdev正确拥有的模块结构,通常设置为THIS_MODULE。

l attrs: 这表示设备属性。

l read_raw: 这是用户读取设备sysfs文件属性时的回调运行。 mask参数是一个位掩码,它允许我们知道请求了哪种类型的值。 channel参数让我们知道相关的通道。 它可以是采样频率,用于将原始值转换为可用值的比例,或原始值本身。

l write_raw: 这是用于将值写入设备的回调。 例如,可以使用它来设置采样频率。

  以下代码显示了如何设置struct iio_info结构:

复制代码
static const struct iio_info iio_dummy_info = {
.driver_module = THIS_MODULE,
.read_raw = &iio_dummy_read_raw,
.write_raw = &iio_dummy_write_raw,
[...]

/*
* Provide device type specific interface functions and
* constant data. 提供设备类型特定的接口功能和常量数据。
*/
indio_dev->info = &iio_dummy_info;
复制代码


IIO channels:IIO通道

通道代表单条采集线。 例如加速度计具有3个通道(X,Y,Z),因为每个轴代表单个采集线。 struct iio_chan_spec是表示和描述内核中单个通道的结构:



复制代码
struct iio_chan_spec {
enum iio_chan_type type;
int channel;
int channel2;
unsigned long address;
int scan_index;
struct {
charsign;
u8 realbits;
u8 storagebits;
u8 shift;
u8 repeat;
enum iio_endian endianness;
} scan_type;
long info_mask_separate;
long info_mask_shared_by_type;
long info_mask_shared_by_dir;
long info_mask_shared_by_all;
const struct iio_event_spec *event_spec;
unsigned int num_event_specs;
const struct iio_chan_spec_ext_info *ext_info;
const char *extend_name;
const char *datasheet_name;
unsigned modified:1;
unsigned indexed:1;
unsigned output:1;
unsigned differential:1;

};
复制代码
  各个参数意义:

l type: 这指定了通道的测量类型。 在电压测量的情况下,它应该是IIO_VOLTAGE。 对于光传感器,它是IIO_LIGHT。 对于加速度计,使用IIO_ACCEL。 所有可用类型都在include / uapi / linux / iio / types.h中定义,如enum iio_chan_type。 要为给定转换器编写驱动程序,请查看该文件以查看每个通道所属的类型。

l channel: 这指定.indexed设置为1时的通道索引。

l channel2: 这指定.modified设置为1时的通道修饰。

l modified: 这指定是否将修饰符应用于此通道属性名称。 在这种情况下,修饰符设置在.channel2中。 (例如,IIO_MOD_X,IIO_MOD_Y,IIO_MOD_Z是关于xyz轴的轴向传感器的修改器)。 可用修饰符列表在内核IIO头中定义为枚举iio_modifier。 修饰符只会破坏sysfs中的通道属性名称,而不是值。

l indexed: 这指定通道属性名称是否具有索引。 如果是,则在.channel字段中指定索引。

l scan_index and scan_type: 当使用缓冲区触发器时,这些字段用于标识缓冲区中的元素。 scan_index设置缓冲区内捕获的通道的位置。 具有较低scan_index的通道将放置在具有较高索引的通道之前。 将.scan_index设置为-1将阻止通道进行缓冲捕获(scan_elements目录中没有条目)。

  暴露给用户空间的通道sysfs属性以位掩码的形式指定。 根据共享信息,可以将属性设置为以下掩码之一:

l info_mask_separate 将属性标记为特定于此通

l info_mask_shared_by_type 将该属性标记为由相同类型的所有通道共享。 导出的信息由相同类型的所有通道共享。

l info_mask_shared_by_dir 将属性标记为由同一方向的所有通道共享。 导出的信息由同一方向的所有通道共享。

l info_mask_shared_by_all 将属性标记为所有通道共享,无论其类型或方向如何。 导出的信息由所有渠道共享。 用于枚举这些属性的位掩码都在include / linux / iio / iio.h中定义:



复制代码
enum iio_chan_info_enum {
IIO_CHAN_INFO_RAW = 0,
IIO_CHAN_INFO_PROCESSED,
IIO_CHAN_INFO_SCALE,
IIO_CHAN_INFO_OFFSET,
IIO_CHAN_INFO_CALIBSCALE,
[...]
IIO_CHAN_INFO_SAMP_FREQ,
IIO_CHAN_INFO_FREQUENCY,
IIO_CHAN_INFO_PHASE,
IIO_CHAN_INFO_HARDWAREGAIN,
IIO_CHAN_INFO_HYSTERESIS,
[...]
};
复制代码
字节序字段应为以下之一:

复制代码
enum iio_endian {
IIO_CPU,
IIO_BE,
IIO_LE,

};
复制代码


Channel attribute naming conventions:通道属性命名约定

  属性的名称由IIO核心自动生成,具有以下模式:{direction} _ {type} _ {index} _ {modifier} _ {info_mask}:

  l direction方向对应于属性方向,根据drivers / iio / industrialio-core.c中的struct iio_direction结构:

复制代码
static const char * const iio_direction[] = {
[0] = "in",
[1] = "out",
};
复制代码
  l type对应于通道类型,根据char数组const iio_chan_type_name_spec:

复制代码
static const char * const iio_chan_type_name_spec[] = {
[IIO_VOLTAGE] = "voltage",
[IIO_CURRENT] = "current",
[IIO_POWER] = "power",
[IIO_ACCEL] = "accel",
[...]
[IIO_UVINDEX] = "uvindex",
[IIO_ELECTRICALCONDUCTIVITY] = "electricalconductivity",
[IIO_COUNT] = "count",
[IIO_INDEX] = "index",
[IIO_GRAVITY] = "gravity",
};
复制代码
l index 索引模式取决于是否设置了通道.indexed字段。 如果设置,索引将从.channel字段中获取,以替换{index}模式。

l modifier 模式取决于通道所设置的.modified字段。 如果设置,修饰符将从.channel2字段中获取,{modifier}模式将根据char数组struct iio_modifier_names结构替换:

复制代码
static const char * const iio_modifier_names[] = {
[IIO_MOD_X] = "x",
[IIO_MOD_Y] = "y",
[IIO_MOD_Z] = "z",
[IIO_MOD_X_AND_Y] = "x&y",
[IIO_MOD_X_AND_Z] = "x&z",
[IIO_MOD_Y_AND_Z] = "y&z",
[...]
[IIO_MOD_CO2] = "co2",
[IIO_MOD_VOC] = "voc",
};
复制代码
l info_mask取决于char数组iio_chan_info_postfix中的通道信息掩码,私有或共享索引值:

复制代码
/* relies on pairs of these shared then separate依赖于这些共享的对,然后分离*/
static const char * const iio_chan_info_postfix[] = {
[IIO_CHAN_INFO_RAW] = "raw",
[IIO_CHAN_INFO_PROCESSED] = "input",
[IIO_CHAN_INFO_SCALE] = "scale",
[IIO_CHAN_INFO_CALIBBIAS] = "calibbias",
[...]
[IIO_CHAN_INFO_SAMP_FREQ] = "sampling_frequency",
[IIO_CHAN_INFO_FREQUENCY] = "frequency",
[...]
};
复制代码


Distinguishing channels通道区分

当每种通道类型有多个数据通道时,您可能会遇到麻烦。 困境将是:如何识别它们。 有两种解决方案:索引和修饰符。

使用索引:给定具有一个通道线的ADC器件,不需要索引。通道定义如下:

复制代码
static const struct iio_chan_spec adc_channels[] = {
{
.type = IIO_VOLTAGE,
.info_mask_separate = BIT(IIO_CHAN_INFO_RAW),

},
}
复制代码
由前面描述的通道产生的属性名称将是in_voltage_raw。

/sys/bus/iio/iio:deviceX/in_voltage_raw
现在让我们看一下有4个甚至8个通道的转换器。 我们如何识别它们? 解决方案是使用索引。 将.indexed字段设置为1将使用.channel值替换{index}模式来替换通道属性名称:





复制代码
static const struct iio_chan_spec adc_channels[] = {
{
.type = IIO_VOLTAGE,
.indexed = 1,
.channel = 0,
.info_mask_separate = BIT(IIO_CHAN_INFO_RAW),

},

{
.type = IIO_VOLTAGE,
.indexed = 1,
.channel = 1,
.info_mask_separate = BIT(IIO_CHAN_INFO_RAW),
},
{
.type = IIO_VOLTAGE,
.indexed = 1,
.channel = 2,
.info_mask_separate = BIT(IIO_CHAN_INFO_RAW),
},
{
.type = IIO_VOLTAGE,
.indexed = 1,
.channel = 3,
.info_mask_separate = BIT(IIO_CHAN_INFO_RAW),
},
}
复制代码
生成的通道属性为:

复制代码
/sys/bus/iio/iio:deviceX/in_voltage0_raw
/sys/bus/iio/iio:deviceX/in_voltage1_raw
/sys/bus/iio/iio:deviceX/in_voltage2_raw
/sys/bus/iio/iio:deviceX/in_voltage3_raw
复制代码
  使用修饰符:给定一个带有两个通道的光传感器 - 一个用于红外光,一个用于红外和可见光,没有索引或修改器,属性名称将为in_intensity_raw。 在这里使用索引可能容易出错,因为使用in_intensity0_ir_raw和in_intensity1_ir_raw是没有意义的。 使用修饰符将有助于提供有意义的属性名称。 通道的定义如下:



复制代码
static const struct iio_chan_spec mylight_channels[] = {

{
.type = IIO_INTENSITY,
.modified = 1,
.channel2 = IIO_MOD_LIGHT_IR,
.info_mask_separate = BIT(IIO_CHAN_INFO_RAW),
.info_mask_shared = BIT(IIO_CHAN_INFO_SAMP_FREQ),
},
{
.type = IIO_INTENSITY,
.modified = 1,
.channel2 = IIO_MOD_LIGHT_BOTH,
.info_mask_separate = BIT(IIO_CHAN_INFO_RAW),
.info_mask_shared = BIT(IIO_CHAN_INFO_SAMP_FREQ),
},
{
.type = IIO_LIGHT,
.info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED),
.info_mask_shared =BIT(IIO_CHAN_INFO_SAMP_FREQ),

},
}
复制代码
属性结果:

l /sys/bus/iio/iio:deviceX/in_intensity_ir_raw 用于测量IR强度的通道

l /sys/bus/iio/iio:deviceX/in_intensity_both_raw用于测量红外和可见光的通道

l /sys/bus/iio/iio:deviceX/in_illuminance_input用于处理后的数据

l /sys/bus/iio/iio:deviceX/sampling_frequency 用于采样频率,由所有人共享

  这也适用于加速度计,我们将在案例研究中进一步了解。 现在,让我们总结一下我们到目前为止在虚拟IIO驱动程序中讨论过的内容。

Putting it all together总结

  让我们总结一下迄今为止我们在一个简单的虚拟驱动器中看到的内容,它将暴露出四个电压通道。 我们将忽略read()或write()函数:

复制代码
#include #include #include #include #include #include #include #include #include #include

#define FAKE_VOLTAGE_CHANNEL(num) \
{ \
.type = IIO_VOLTAGE, \
.indexed = 1, \
.channel = (num), \
.address = (num), \
.info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \
.info_mask_shared_by_type =BIT(IIO_CHAN_INFO_SCALE) \

}


struct my_private_data {
int foo;
int bar;
struct mutex lock;
};

static int fake_read_raw(struct iio_dev *indio_dev,
struct iio_chan_spec const *channel, int *val,
int *val2, long mask)
{
return 0;
}

static int fake_write_raw(struct iio_dev *indio_dev,
struct iio_chan_spec const *chan,
int val, int val2, long mask)
{
return 0;
}

static const struct iio_chan_spec fake_channels[] = {
FAKE_VOLTAGE_CHANNEL(0),
FAKE_VOLTAGE_CHANNEL(1),
FAKE_VOLTAGE_CHANNEL(2),
FAKE_VOLTAGE_CHANNEL(3),

};

static const struct of_device_id iio_dummy_ids[] = {
{ .compatible = "packt,iio-dummy-random", },
{ /* sentinel */ }
};

static const struct iio_info fake_iio_info = {
.read_raw = fake_read_raw,
.write_raw = fake_write_raw,
.driver_module = THIS_MODULE,
};

static int my_pdrv_probe (struct platform_device *pdev)
{
struct iio_dev *indio_dev;
struct my_private_data *data;
indio_dev = devm_iio_device_alloc(&pdev->dev, sizeof(*data));

if (!indio_dev) {
dev_err(&pdev->dev, "iio allocation failed!\n");
return -ENOMEM;
}

data = iio_priv(indio_dev);
mutex_init(&data->lock);
indio_dev->dev.parent = &pdev->dev;
indio_dev->info = &fake_iio_info;
indio_dev->name = KBUILD_MODNAME;
indio_dev->modes = INDIO_DIRECT_MODE;
indio_dev->channels = fake_channels;
indio_dev->num_channels = ARRAY_SIZE(fake_channels);
indio_dev->available_scan_masks = 0xF;
iio_device_register(indio_dev);
platform_set_drvdata(pdev, indio_dev);
return 0;
}
static void my_pdrv_remove(struct platform_device *pdev)
{
struct iio_dev *indio_dev = platform_get_drvdata(pdev);
iio_device_unregister(indio_dev);
}
static struct platform_driver mypdrv = {
.probe = my_pdrv_probe,
.remove = my_pdrv_remove,
.driver = {
.name = "iio-dummy-random",
.of_match_table = of_match_ptr(iio_dummy_ids),
.owner = THIS_MODULE,
},
};
module_platform_driver(mypdrv);
MODULE_AUTHOR("John Madieu ");
MODULE_LICENSE("GPL");
复制代码
  加载上述模块后, 我们将有以下输出, 显示我们的设备确实对应于我们已注册的平台设备:

复制代码
~# ls -l /sys/bus/iio/devices/
lrwxrwxrwx 1 root root 0 Jul 31 20:26 iio:device0 -> ../../../devices/platform/iio-dummy-random.0/iio:device0
lrwxrwxrwx 1 root root 0 Jul 31 20:23 iio_sysfs_trigger -> ../../../devices/iio_sysfs_trigger
复制代码
  下面的列表显示了此设备的通道及其名称, 这些通道与我们在驱动程序中描述的内容完全对应:

复制代码
~# ls /sys/bus/iio/devices/iio\:device0/
dev in_voltage2_raw name uevent
in_voltage0_raw in_voltage3_raw power
in_voltage1_raw in_voltage_scale subsystem
~# cat /sys/bus/iio/devices/iio:device0/name
iio_dummy_random
复制代码https://www.cnblogs.com/yongleili717/p/10744252.html]]>
学习笔记http://www.ruanjianpeixun.net/post/20190422947.html#commenthttp://www.ruanjianpeixun.net/http://www.ruanjianpeixun.net/feed.asp?cmt=28106http://www.ruanjianpeixun.net/cmd.asp?act=tb&id=28106&key=f2cf78ad
代理模式_365bet线上_365bet亚洲娱乐_365bet在线songxg@qingsoft.net (网络文摘)http://www.ruanjianpeixun.net/post/20190422791.htmlMon, 22 Apr 2019 16:45:20 +0800http://www.ruanjianpeixun.net/post/20190422791.html 目录

 

回到顶部

杀鸡不想用牛刀-用代理

  大家好,我是小赵,求职的路虽然难,但最终还是有个着落,我现在进了藏剑山庄任职铸剑师,不过没意思,因为活都是低级的活,批量铸些普通的匕首、短剑之类,一天到晚忙个没完,这藏剑山庄果然是个大厂,订单超级多。

 

  做着做着我就没动力了,没啥技术含量,虽然是计件,但还不如我的打印机业务赚钱来的多,于是我就私底下请一些有空的同事帮我干活,而我就每天打个卡,然后就在家发展我的打印事业。

  其实主要原因呢,还是因为我暂时不打算离职,先静观其变,等待机会,毕竟这是个大企业。

 

  现在,是写日记的时候,我要记录一下我请同事干活的过程。

 

  首先,同事给我干活,那就必须要有和我一样的技能,比如调剂、熔炼、浇铸、加工这些铸剑流程,所以铸剑的流程应该抽象出来。而同事给我干活,天知地知,虽然是他干的活,但这些剑的创建者名字必须是我。

 

  思考完毕之后,我画下了类图:

  接下来我就根据这个类图来写程序

铸剑流程抽象:

复制代码
public interface IMakeSword {     //调剂    void adjust();      //熔炼    void smelt();      //浇铸    void casting();      //加工    void process(); } 
复制代码

 

我:

复制代码
public class User implements IMakeSword {     //名字    private String name = "";      public User(String name) {         this.name = name;     }     @Override     public void adjust() {         System.out.println(this.name + "正在调剂...");     }     @Override     public void smelt() {         System.out.println(this.name + "正在熔炼...");     }     @Override     public void casting() {         System.out.println(this.name + "正在浇铸...");     }     @Override     public void process() {         System.out.println(this.name + "正在加工...");     } } 
复制代码

 

同事:

复制代码
public class Colleague implements IMakeSword{     private IMakeSword user;      //构造函数把我传进来    public Colleague(IMakeSword user) {         this.user = user;     }     @Override     public void adjust() {         this.user.adjust();     }     @Override     public void smelt() {         this.user.smelt();     }     @Override     public void casting() {         this.user.casting();     }     @Override     public void process() {         this.user.process();     } }
复制代码

 

  差不多了,现在我的同事要开始给我干活了:

复制代码
    public static void main(String[] args) {         IMakeSword xiaoZhao = new User("小赵");         IMakeSword colleague = new Colleague(xiaoZhao);         colleague.adjust();         colleague.smelt();         colleague.casting();         colleague.process();     }
复制代码

 

输出:

小赵正在调剂...
小赵正在熔炼...
小赵正在浇铸...
小赵正在加工...

 

  这个活干的是非常的好,工资我当然会转给同事,但是我的工作量在山庄里还不至于难看。

  随手一招代理模式,暂时保着这份工作,赚个五险一金。

 

回到顶部

严查之下-强制代理

  后来,不知道是哪里传出的风声,山庄高层领导好像知道了什么,安排了专案小组暗地里对一些不正之风进行严查严打。

  我不怕顶风作案,就怕做的不严实。

  如果,专案小组假扮我的同事,那我不就嗝屁了吗?于是,我决定把我和我同事的关系隐藏到内部,不让人看到,也不接受外面传入。

 

  来看看我更改后的程序:

铸剑流程抽象:

复制代码
public interface IMakeSword {     //调剂    void adjust();      //熔炼    void smelt();      //浇铸    void casting();      //加工    void process();      //获取我的代理    IMakeSword getProxy(); }
复制代码

  加一个getProxy方法,获取我的代理,就是我的同事嘛,增加这个方法是为了防止外人假冒。

 

我:

复制代码
public class User implements IMakeSword {     //名字    private String name = "";      //我的同事    private IMakeSword proxy = null;      public User(String name) {         this.name = name;     }     @Override     public void adjust() {         if(null == this.proxy){             System.out.println("你好,"+this.name+"在休息。");             return;         }         System.out.println(this.name + "正在调剂...");     }     @Override     public void smelt() {         if(null == this.proxy){             System.out.println("你好,"+this.name+"在休息。");             return;         }         System.out.println(this.name + "正在熔炼...");     }     @Override     public void casting() {         if(null == this.proxy){             System.out.println("你好,"+this.name+"在休息。");             return;         }         System.out.println(this.name + "正在浇铸...");     }     @Override     public void process() {         if(null == this.proxy){             System.out.println("你好,"+this.name+"在休息。");             return;         }         System.out.println(this.name + "正在加工...");     }      @Override     public IMakeSword getProxy() {         //我和同事的小黑屋        this.proxy = new Colleague(this);         return this.proxy;     } }
复制代码

在getProxy方法里,我在小黑屋里自己联系我的同事,并且在每个动作执行的时候,先检测我有没有代理,没代理的话千万别乱动。

 

同事:

复制代码
public class Colleague implements IMakeSword{     private IMakeSword user;      //构造函数把我传进来    public Colleague(IMakeSword user) {         this.user = user;     }     @Override     public void adjust() {         this.user.adjust();     }     @Override     public void smelt() {         this.user.smelt();     }     @Override     public void casting() {         this.user.casting();     }     @Override     public void process() {         this.user.process();     }      @Override     public IMakeSword getProxy() {         return this;     } }
复制代码

同事类则几乎没变化,getProxy方法也不需要的,返回同事自己就可以了。

 

  来,现在再试一下合作:

复制代码
    public static void main(String[] args) {         IMakeSword xiaoZhao = new User("小赵");         IMakeSword colleague = xiaoZhao.getProxy();         colleague.adjust();         colleague.smelt();         colleague.casting();         colleague.process();     }
复制代码

输出:

小赵正在调剂...
小赵正在熔炼...
小赵正在浇铸...
小赵正在加工...

 

  这是新的合作方式,谁也不知道我的代理是谁。

如果专案小组来假冒同事呢:

复制代码
    public static void main(String[] args) {         IMakeSword xiaoZhao = new User("小赵");         IMakeSword colleague = new Colleague(xiaoZhao);         colleague.adjust();         colleague.smelt();         colleague.casting();         colleague.process();     }
复制代码

输出:

你好,小赵在休息。
你好,小赵在休息。
你好,小赵在休息。
你好,小赵在休息。

 

  这就是强制代理的手法,代理由自己管理,从此以后,我又可以高枕无忧了。

 

回到顶部

降低合作门槛-动态代理

  我没想到,我那个同事不仅接我的活,还同时接别人的活,可别人就没我这么聪明啊,天天对着他new、new、new!结果没过几天就被专案小组给逮着了,直接翻船,同事也被供出来了,被劝退。

  

  随着我同事的牺牲,山庄的严打行动逐渐结束,不久后专案小组解散,紧张的氛围舒缓而开。

  但是我得从新找合作人啊,私底下接触了几个有意向的同事,都觉得我的合作方式太过麻烦,必须要有代理类,就是Colleague类,要实现一箩筐方法,而且创建了类就是留下了证据,将来就有被查到的风险。

  

  在我的一阵子研究之后,发现JDK有一个InvocationHandler接口可以用,于是乎,我重新写了一个程序。

 

铸剑流程抽象:

复制代码
public interface IMakeSword {     //调剂    void adjust();      //熔炼    void smelt();      //浇铸    void casting();      //加工    void process(); }
复制代码

 

我:

复制代码
public class User implements IMakeSword {     //名字    private String name = "";      public User(String name) {         this.name = name;     }     @Override     public void adjust() {         System.out.println(this.name + "正在调剂...");     }     @Override     public void smelt() {         System.out.println(this.name + "正在熔炼...");     }     @Override     public void casting() {         System.out.println(this.name + "正在浇铸...");     }     @Override     public void process() {         System.out.println(this.name + "正在加工...");     } }
复制代码

 

动态代理类:

复制代码
public class ColleagueHandler implements InvocationHandler {     Object object = null;      //代理的目标    public ColleagueHandler(Object object) {         this.object = object;     }      //调用目标方法    @Override     public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {         return method.invoke(this.object,args);     } }
复制代码

 

开始干活:

复制代码
    public static void main(String[] args) throws Throwable{         IMakeSword xiaoZhao = new User("小赵");         InvocationHandler handler = new ColleagueHandler(xiaoZhao);         handler.invoke(null,xiaoZhao.getClass().getMethod("adjust", null),null);         handler.invoke(null,xiaoZhao.getClass().getMethod("smelt", null),null);         handler.invoke(null,xiaoZhao.getClass().getMethod("casting", null),null);         handler.invoke(null,xiaoZhao.getClass().getMethod("process", null),null);     }
复制代码

输出:

小赵正在调剂...
小赵正在熔炼...
小赵正在浇铸...
小赵正在加工...

 

  很好,程序实现了,从始至终都没有创建代理类,虽然很多人都会说这是动态代理,但实际上却不是,这只是反射而已。

  对于一个优秀的架构师来讲,这简直就是渣渣玩法,一箩筐方法名都写死在外面了,去他大爷的JDK。

  在寻求优化的过程中,听说JDK还提供了Proxy类,里面有个newProxyInstance方法用来创建一个对象的代理对象,这个方法总共有3个参数,ClassLoader loader用来指明生成代理对象使用哪个类装载器,Class<?>[] interfaces用来指明生成哪个对象的代理对象,通过接口指定,InvocationHandler h用来指明产生的这个代理对象要做什么事情。

 

  于是乎,我又舔着大逼脸去使用JDK的接口了。

 

增加一个动态代理类:

复制代码
public class MyProxy {     public static <T> T newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h){         T newProxyInstance = (T) Proxy.newProxyInstance(loader,interfaces, h);         return newProxyInstance;     } }
复制代码

 

开始干活:

复制代码
    public static void main(String[] args) throws Throwable{         IMakeSword xiaoZhao = new User("小赵");         ClassLoader cl = xiaoZhao.getClass().getClassLoader();         IMakeSword proxy = MyProxy.newProxyInstance(cl,new Class[] {IMakeSword.class}, new ColleagueHandler(xiaoZhao));         proxy.adjust();         proxy.smelt();         proxy.casting();         proxy.process();         System.out.println(xiaoZhao.equals(proxy));     }
复制代码

输出:

小赵正在调剂...
小赵正在熔炼...
小赵正在浇铸...
小赵正在加工...
false

 

  最后一个比较两个对象是否相同,证明了proxy对象并不是我,但实际上却又是我在干活,这才叫动态代理。

 

回到顶部

通知机制-切面

  其实我们细看之下,已经发现了一个情况,就是铸剑相关的类已经和代理解耦了。

  第一条线:IMakeSword接口->User类。

  第二条线:InvocationHandler接口->ColleagueHandler类->MyProxy类。

  动态代理实现代理的职责,业务逻辑负责功能实现。

 

  现在,为了安全起见,我希望做一件事情,就是每次干活的时候,要给我发一个通知,让我知道。

  其实非常的简单,我们在创建动态代理的时候发这个通知就行了。

 

  先创建一个通知抽象,因为以后可能会有前置通知、后置通知、各种切入的通知等等,所以通知也是一条独立发展的线。

 

通知抽象:

复制代码
public interface IAdvice {     void exec(); }
复制代码

 

前置通知:

复制代码
public class BeforeAdvice implements IAdvice {     @Override     public void exec() {         System.out.println("准备要干活了");     } }
复制代码

 

动态代理类:

复制代码
public class MyProxy {     public static <T> T newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h){         new BeforeAdvice().exec();         T newProxyInstance = (T) Proxy.newProxyInstance(loader,interfaces, h);         return newProxyInstance;     } }
复制代码

 

开始干活:

复制代码
public static void main(String[] args) throws Throwable{         IMakeSword xiaoZhao = new User("小赵");         ClassLoader cl = xiaoZhao.getClass().getClassLoader();         IMakeSword proxy = MyProxy.newProxyInstance(cl,new Class[] {IMakeSword.class}, new ColleagueHandler(xiaoZhao));         proxy.adjust();         proxy.smelt();         proxy.casting();         proxy.process();         System.out.println(xiaoZhao.equals(proxy));     }
复制代码

 

输出:

准备要干活了
小赵正在调剂...
小赵正在熔炼...
小赵正在浇铸...
小赵正在加工...
false

 

  如果有的代理需要通知,有的时候不需要通知呢?很简单,传个参数,写个if条件即可,如果不想传参数呢?一般AOP的做法是使用注解的方式标记切入位置,然后在动态代理类里面对注解进行判断。

  获取注解也可以使用反射机制实现,类头上的注解、方法头上的注解、参数上的注解都可以获取,有兴趣的朋友可以自行研究。

 

回到顶部

最后声明

本故事写到后面有点怪,原因是动态代理和静态代理的使用场景是不同的,用同一个故事延续下来导致需求逻辑上出现了一些bug,在此致歉。https://www.cnblogs.com/fengyumeng/p/10750894.html

]]>
学习笔记http://www.ruanjianpeixun.net/post/20190422791.html#commenthttp://www.ruanjianpeixun.net/http://www.ruanjianpeixun.net/feed.asp?cmt=28105http://www.ruanjianpeixun.net/cmd.asp?act=tb&id=28105&key=34bea5e5
len(x) 击败 x.len(),从内置函数看 Python 的设计思想_365bet线上_365bet亚洲娱乐_365bet在线songxg@qingsoft.net (网络文摘)http://www.ruanjianpeixun.net/post/20190422606.htmlMon, 22 Apr 2019 16:43:07 +0800http://www.ruanjianpeixun.net/post/20190422606.html
它们预先定义在内置命名空间中,开箱即用,所见即所得。Python 被公认是一种新手友好型的语言,这种说法能够成立,内置函数在其中起到了极关键的作用。

举个例子,求字符串 x 的长度,Python 的写法是 len(x) ,而且这种写法对列表、元组和字典等对象也同样适用,只需要传入对应的参数即可。len() 函数是共用的。

这是一种极简哲学的体现:Simple is better than complex。

但是,有些语言并不是这样,例如在 Java 中,字符串类有一个求长度的方法,其它类也有自己的求长度的方法,它们无法共用。每次使用时,通过类或实例来调用。

同样是求字符串长度,Python 的写法:

saying = "Hello world!"
print(len(saying))

# 结果:12
而在 Java 中,写法可能如下(简化起见):

String saying = "Hello world!";
System.out.println(saying.length());

// 结果:12
Python 采用的是一种前缀表达式 ,而 Java 采用的则是后缀表达式 。

除了求长度,Python 的某些内置函数也能在 Java 中找到对应的表达。例如,数值型字符串 s 转化为整型数字,Python 可以用 int(s) 函数,而 Java 可以用 Integer.parseInt(s) ;整型数字转化为字符串,Python 可以用 str(i) ,而 Java 也有 String.valueOf(i) 。

Python 的内置函数不与特定的类绑定,它们是一级对象。而 Java 的“函数”则无法脱离类而存在,它们只是附属品。

从直观角度来看,Python 的表达似乎是更优的。但是,它们并不具有可比性 ,因为这是两套语言系统,各有独特的范畴背景,并不能轻易地化约。

就好比是,不能因为拉丁字母笔画简单,就说它优于汉字,因为在表意时,字母(表音文字)是远逊于汉字(表意文字)的。同样的,日本借用了汉字的偏旁部首而造出来的文字,虽然更省笔墨,但是也完全丧失了意蕴。

以此类比,Python 的内置函数虽有简便之美,但却丢失了某些表意功能。有些人在质疑/抨击 Python 的时候,也喜欢拿这点说事,认为这是 Python 的设计缺陷。

这就引出本文最想讨论的一个问题来:为什么 Python 要设计成 len(x) 这种前缀表达,而不是 x.len() 这样的后缀表达呢?

事实上,后缀设计也是可行的,以 Python 中列表的两个方法为例:

mylist = [2, 1, 3, 5, 4]

mylist.sort()
print(mylist) # [1, 2, 3, 4, 5]

mylist.reverse()
print(mylist) # [5, 4, 3, 2, 1]
它们都是通过列表对象来调用,并不是凭空从内置命名空间中拿来的。语义表达得也很清楚,就是对 mylist 做排序和逆转。

恰恰那么巧,它们还有两个同父异母的兄弟 sorted() 与 reversed(),这俩是前缀表达型。

mylist = [2, 1, 3, 5, 4]

sort_list = sorted(mylist)
print(sort_list) # [1, 2, 3, 4, 5]

reverse_list = reversed(mylist)
print(list(reverse_list)) # [4, 5, 3, 1, 2]
不同的写法,都在做同一件事(不考虑它们的副作用)。因此,后缀语法并非不可行,之所以不用,那肯定是刻意的设计。

回到前面的问题:为什么是 len(x) ,而不是 x.len(x),这根源于 Python 的什么设计思想呢?

Python 之父 Guido van Rossum 曾经解释过这个问题(链接见文末),有两个原因:

对于某些操作,前缀符比后缀更好读——前缀(和中缀)表示法在数学中有着悠久的历史,其视觉效果有助于数学家思考问题。我们可以简单地把公式 x(a + b)` 重写成 `xa + x*b ,但同样的事,以原生的面向对象的方式实现,就比较笨拙。
当读到 len(x) 时,我就 知道 这是在求某对象的长度。它告诉我了两点:返回值是一个整数,参数是某种容器。但当读到 x.len() 时,我必须事先知道某种容器 x,它实现了一个接口,或者继承了一个拥有标准 len() 方法的类。我们经常会目睹到这种混乱:一个类并没有实现映射(mapping)接口,却拥有 get() 或 keys() 方法,或者某些非文件对象,却拥有一个 write() 方法。
解释完这两个原因之后,Guido 还总结成一句话说:“I see 'len' as a built-in operation ”。这已经不仅是在说 len() 更可读易懂了,而完全是在拔高 len() 的地位。

这就好比说,分数 ? 中的横线是数学中的一个“内置”表达式,并不需要再实现什么接口之类的,它自身已经表明了“某数除以某数 ”的意思。不同类型的数(整数、浮点数、有理数、无理数…)共用同一个操作符,不必为每类数据实现一种求分数的操作。

优雅易懂是 Python 奉行的设计哲学 ,len() 函数的前缀表达方式是最好的体现。我想起在《超强汇总:学习Python列表,只需这篇文章就够了》这篇文章中,曾引述过 Guido 对“为什么索引从 0 开始 ”的解释。其最重要的原因,也正是 0-based 索引最优雅易懂。

让我们来先看看切片的用法。可能最常见的用法,就是“取前 n 位元素”或“从第i 位索引起,取后 n 位元素”(前一种用法,实际上是 i == 起始位的特殊用法)。如果这两种用法实现时可以不在表达式中出现难看的 +1 或 -1,那将会非常的优雅。

使用 0-based 的索引方式、半开区间切片和缺省匹配区间的话(Python最终采用这样的方式),上面两种情形的切片语法就变得非常漂亮:a[:n] 和 a[i:i+n],前者是 a[0:n] 的缩略写法。

所以,我们能说 len(x) 击败 x.len() ,支撑它的是一种化繁为简、纯粹却深邃的设计思想。

面向对象的编程语言自发明时起,就想模拟我们生活于其中的现实世界。可是什么类啊、接口啊、对象啊、以及它们的方法啊,这些玩意的毒,有时候蒙蔽了我们去看见世界本质的眼睛。

桌子类有桌子类的求长度方法,椅子类有椅子类的求长度方法,无穷无尽,可现实真是如此么?求长度的方法就不能是一种独立存在的对象么?它之所以存在,是因为有“对象”存在,而不是因为有某个类才存在啊。

所以,我想说,len(x) 击败 x.len(),这还体现了 Python 对世界本质的洞察 。

求某个对象的长度,这种操作独立于对象之外而存在,并不是该对象内部所有的一种属性或功能。从这个角度理解,我们能够明白,为什么 Python 要设计出内置函数? 内置函数其实是对世界本质的一种捕捉。

这些见微知着的发现,足够使我们爱上这门语言了。人生苦短,我用 Python。

关联阅读:

Guido 解释 len 的由来:http://suo.im/4ImAEo

Guido 解释 0 索引的由来:http://suo.im/5cr12S

本文原创并首发于公众号【Python猫】,未经授权,请勿转载。

原文地址:https://mp.weixin.qq.com/s/pKQT5wvyaSNFvnJexiCC8w



公众号【Python猫】, 本号连载优质的系列文章,有喵星哲学猫系列、Python进阶系列、好书推荐系列、技术写作、优质英文推荐与翻译等等,欢迎关注哦。后台回复“爱学习”,免费获得一份学习大礼包。

分类: Python无止境https://www.cnblogs.com/pythonista/p/10746798.html]]>
学习笔记http://www.ruanjianpeixun.net/post/20190422606.html#commenthttp://www.ruanjianpeixun.net/http://www.ruanjianpeixun.net/feed.asp?cmt=28104http://www.ruanjianpeixun.net/cmd.asp?act=tb&id=28104&key=177ae187
【朝花夕拾】设计模式之建造者模式_365bet线上_365bet亚洲娱乐_365bet在线songxg@qingsoft.net (网络文摘)http://www.ruanjianpeixun.net/post/20190422338.htmlMon, 22 Apr 2019 16:42:38 +0800http://www.ruanjianpeixun.net/post/20190422338.html 

建造者模式简介

 建造者模式又称生成器模式,它将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。这就要求,我们所要处理的对象必须要有非常强的结构化特征,对于开发人员而言,需要抽象出一般的结构化接口出来,方便进行构建工作。 

作为一名码农,最难得的应该就是找对象了,就算勉强找到了,要是对人家不好,人家可就离你远去了。所以要对人家好点,学会做饭,多弄几个花样出来,让生活充满新鲜感,小两口的感情也会越来越深了。

这个时候建造者模式就闪亮登场了,我们所吃的饭,无非是炒菜,主食,另外还需要弄汤类或者饮料类东西,只要将这些东西抽象出来,那么每天的饭菜内容就稳定了,永远变化的是具体的炒菜品类、主食品类和汤的品类。要始终谨记,稳定的东西都是抽象后的东西,不要一下子就陷入到细节中去。

建造者模式UML类图

jianzaozhe

通过UML类图,我们可以知道建造者模式有以下几个角色:

Director:从抽象角度创建对象的各个部分,同时根据要求设计创建各个部分的顺序。

Builder:给出一个抽象接口,以规范产品对象的各个组成部分的建造。

ConcreteBuilder:实现Builder接口,具体化创建对象的各个部分。 并提供具体的实例。

Product:要创建的复杂对象。此处指的是Meal。当然该类可以不用,只保留概念也是可以的。

范例

接下来的范例就是要为女朋友准备不同的饭菜,以达生活新鲜感的目的,要好好学哦。

   1:  public class Meal
   2:  {
   3:      private string mainMeal;
   4:      private string stirFry;
   5:      private string soup;
   6:   
   7:      public Meal(string mainMeal, string stirFry, string soup)
   8:      {
   9:          this.mainMeal = mainMeal;
  10:          this.stirFry = stirFry;
  11:          this.soup = soup;
  12:      }
  13:   
  14:      public void Show(string mealType)
  15:      {
  16:          Console.WriteLine(mealType);
  17:   
  18:          Console.WriteLine("主食:" + this.mainMeal);
  19:          Console.WriteLine("菜类:" + this.stirFry);
  20:          Console.WriteLine("汤类:" + this.soup);
  21:      }
  22:  }
  23:   
  24:  public abstract class Builder
  25:  {
  26:      public abstract void BuildMainMeal(string mainMeal);
  27:   
  28:      public abstract void BuildStirFry(string stirFry);
  29:   
  30:      public abstract void BuildSoup(string soup);
  31:   
  32:      public abstract Meal MealBuilder();
  33:  }
  34:   
  35:  public class BreakfastBuilder : Builder
  36:  {
  37:      private string mainMeal;
  38:      private string stirFry;
  39:      private string soup;
  40:   
  41:      public override void BuildMainMeal(string mainMeal)
  42:      {
  43:          this.mainMeal = mainMeal;
  44:      }
  45:   
  46:      public override void BuildStirFry(string stirFry)
  47:      {
  48:          this.stirFry = stirFry;
  49:      }
  50:   
  51:      public override void BuildSoup(string soup)
  52:      {
  53:          this.soup = soup;
  54:      }
  55:   
  56:      public override Meal MealBuilder()
  57:      {
  58:          return new Meal(mainMeal, stirFry, soup);
  59:      }
  60:  }
  61:   
  62:  public class Director
  63:  {
  64:      private Builder builder;
  65:      public Director(Builder builder)
  66:      {
  67:          this.builder = builder;
  68:      }
  69:   
  70:      public void Construct(string mainMeal, string stirFry, string soup)
  71:      {
  72:          builder.BuildMainMeal(mainMeal);
  73:          builder.BuildStirFry(stirFry);
  74:          builder.BuildSoup(soup);
  75:      }
  76:  }

调用

   1:  class Program
   2:  {
   3:      static void Main(string[] args)
   4:      {
   5:          Builder builder = new BreakfastBuilder();
   6:          Director director = new Director(builder);
   7:          director.Construct("八宝粥","香菇青菜","番茄鸡蛋汤");
   8:   
   9:          Meal meal = builder.MealBuilder();
  10:   
  11:          meal.Show("亲,吃早餐啦");
  12:          Console.Read();
  13:      }
  14:  }

运行结果:

1555851789(1)

当然,我这早餐,估计不过关的概率十分的大。

建造者模式优缺点

优点:

1、建造者模式有更好的封装性和细节隐藏的特点,调用者无需也无法关注到细节部分。

2、由于建造者对对象本身以及创建过程进行了非常细致的拆分,使得我们可以精细控制细节部分,减少风险。

 

缺点:

1、如果内部非常复杂的话,会生成太多太多的类,以至于会扩大我们的关注点,加大了代码的维护难度。

2、由于建造者模式本身很复杂,所以我们需要将建造者模式应用于通过环境下,不然只为了一种场景编写,实在是耗费精力,这也就带来了建造模式的使用范围。

建造者模式使用范围的思考

建造者模式要求我们使用抽象思维来面对问题,其建造过程与表示分离是其最大特征,如果建造过程可变,同时具有高度的结构化,我们使用建造者模式是非常有帮助的。

关于表示的功能,我们可以理解为,只是一个抽象接口,但是由于我们可以改变其构建顺序或者选择构建结构,使得表示可以多样化,此刻我们使用建造者模式也是有益的。

]]>
学习笔记http://www.ruanjianpeixun.net/post/20190422338.html#commenthttp://www.ruanjianpeixun.net/http://www.ruanjianpeixun.net/feed.asp?cmt=28103http://www.ruanjianpeixun.net/cmd.asp?act=tb&id=28103&key=a690596b