- 要无条件自信,即使在做错的时候。
- 不要想太多,定时清除消极思想。
- 学会忘记痛苦,为阳光记忆腾出空间。
- 敢于尝试,敢于丢脸。
- 每天都是新的,烦恼痛苦不过夜。
- 面对别人的优秀时,发自内心地赞美。
- 做人最高境界不是一味低调,也不是一味张扬,而是始终如一的不卑不亢。
所谓学问 – 梁漱溟 1928年演讲
1928年在广州中山大学的讲演(节选)
作者:梁漱溟
所谓学问,就是对问题说得出道理,有自己的想法。
想法似乎人人都是有的,但又等于没有。因为大多数人的头脑杂乱无章,人云亦云,对于不同的观点意见,他都点头称是,等于没有想法。
我从来没有想过要做学问,走上现在这条路,只是因为我喜欢提问题。大约从十四岁开始,总有问题占据在我的心里,从一个问题转入另一个问题,一直想如何解答,解答不完就欲罢不能,就一路走了下来。
提得出问题,然后想要解决它,这大概是做学问的起点吧。
以下分八层来说明我走的一条路:
第一层境界:形成主见
用心想一个问题,便会对这个问题有主见,形成自己的判断。
说是主见,称之为偏见亦可。我们的主见也许是很浅薄的,但即使浅薄,也终究是你自己的意见。
许多哲学家的哲学也很浅,就因为浅便行了,胡适之先生的哲学很浅,亦很行。因为这是他自己的,纵然不高深,却是心得,而亲切有味。所以说出来便能够动人,能动人就行了!他就能自成一派,其他人不行,就是因为其他人连浅薄的哲学都没有。
第二层境界:发现不能解释的事情
有主见,才有你自己;有自己,才有旁人,才会发觉前后左右都是与我意见不同的人。
这时候,你感觉到种种冲突,种种矛盾,种种没有道理,又种种都是道理。于是就不得不第二步地用心思。
面对各种问题,你自己说不出道理,不甘心随便跟着人家说,也不敢轻易自信,这时你就走上求学问的正确道路了。
第三层境界:融汇贯通
从此以后,前人的主张、今人的言论,你不会轻易放过,稍有与自己不同处,便知道加以注意。
你看到与自己想法相同的,感到亲切;看到与自己想法不同的,感到隔膜。有不同,就非求解决不可;有隔膜,就非求了解不可。于是,古人今人所曾用过的心思,慢慢融汇到你自己。
你最初的一点主见,成为以后大学问的萌芽。从这点萌芽,你才可以吸收养料,才可以向上生枝发叶,向下入土生根。待得上边枝叶扶疏,下边根深蒂固,学问便成了。
这是读书唯一正确的方法,不然读书也没用处。会读书的人说话时,说他自己的话,不堆砌名词,不旁征博引;反之,引书越多的人越不会读书。
第四层境界:知不足
用心之后,就知道要虚心了。自己当初一点见解之浮浅,不足以解决问题。
学问的进步,不单是见解有进步,还表现在你的心思头脑锻炼得精密了,心气态度锻炼得谦虚了。
心虚思密是求学的必要条件。
对于前人之学,总不要说自己都懂。因为自己觉得不懂,就可以除去一切浮见,完全虚心地先求了解它。
遇到不同的意见思想,我总疑心他比我高明,疑心他必有我所未及的见闻,不然,他何以不和我作同样判断呢?疑心他必有精思深悟过于我,不然,何以我所见如此而他所见如彼呢?
第五层境界:以简御繁
你见到的意见越多,专研得愈深,这时候零碎的知识,片段的见解都没有了;心里全是一贯的系统,整个的组织。如此,就可以算成功了。到了这时候,才能以简御繁,才可以学问多而不觉得多。
凡有系统的思想,在心里都很简单,仿佛只有一两句话。凡是大哲学家皆没有许多话说,总不过一两句。很复杂很沉重的宇宙,在他手心里是异常轻松的—-所谓举重若轻。
学问家如说肩背上负着多沉重的学问,那是不对的;如说当初觉得有什么,现在才晓得原来没有什么,那就对了。道理越看得明透,越觉得无甚话可说,还是一点不说的好。心里明白,口里讲不出来。
反过来说,学问浅的人说话愈多,思想不清楚的人名词越多。让一个没有学问的人看见,真要把他吓坏了!其实道理明透了,名词便可用,可不用,或随意拾用。
第六层境界:运用自如
如果外面或里面还有解决不了的问题,那学问必是没到家。如果学问已经通了,就没有问题。
真学问的人,学问可以完全归自己运用。假学问的人,学问在他的手里完全不会用。
第七层境界:一览众山小
学问里面的甘苦都尝过了,再看旁人的见解主张,其中得失长短都能够看出来。这个浅薄,那个到家,这个是什么分数,那个是什么程度,都知道得很清楚;因为自己从前也是这样,一切深浅精粗的层次都曾经过。
第八层境界:通透
思精理熟之后,心里就没有一点不透的了。
Delphi 实现事件侦听与触发
我最近在Cnblogs上看到有一些朋友在讨论关于事件侦听,他的想法是,给Delphi即有的事件,例如click, mouse加上多播机制,在我认为,这种做法是不可取的,delphi在设计之初,就是把所谓的事件当做是一个回调函数来处理,这就使得每个事件只能有一个响应者,在这之上给组件加上事件多播机制只会让代码更加难懂,更加难用,增加代码量。而且用这种方法增加的事件多播机制只能用于界面上, 不能很好的用于处理数理, 实现数据与界面的分离, 降低耦合度, 在此, 我把我的事件多播实现机制与原理分享给大家. 至于好与坏, 请在实践中进行检验.
与武稀松的实现方法相似, 本方法在实现上用到了以下几个知识点:
- RTTI的 TMethod 结构体, 该结构体可用于比较两个事件处理方法是否相同, 用于移除事件侦听.
- 用于了指针, 用于传递事件数据, 需要了解如何获取内存, 释放内存
- 泛型. 用于保存事件数据.
下面来具体分析 代码.
unit EventListener; interface uses System.Generics.Collections; type TNwEventProc = procedure(const EventType:string;EventData:Pointer) of object; INiEventListener = interface procedure PerformEvent(EventType: String; EventData: Pointer); procedure removeEventListener(EventType: String; EventProc: TNwEventProc); procedure addEventListener(EventType: String; EventPro: TNwEventProc); end; TEventData = record EventType:String; EventProc:TNwEventProc; end; TNTEventList = TList<TEventData>; TNwEventListener = class(TInterfacedObject, INiEventListener) protected FEventList:TNTEventList; public procedure PerformEvent(EventType: String; EventData: Pointer); procedure removeEventListener(EventType: String; EventProc: TNwEventProc); procedure addEventListener(EventType: String; EventPro: TNwEventProc); destructor Destroy; override; constructor Create(); end;
TNwEventProc 是事件处方法, 有两个参数, 一个是事件类型EventType, 一个是事件数据 EventData. 事件类型是字符串, 用于自己定义事件. 事件数据是一个指针, 指向事件的数据所在内存. 这个内存需要你自己手动去管理.
INiEventListener 是事件处理器的接口, 它包括三个就去, 分别是执行事件PerformEvent, 添加事件 AddEventListener 和删除事件RemoveEventListener. 当我们需要在某一事件发生的时候处理某数据时, 就可以调用添加事件AddEventListener来增加某个事件的侦听, 当这个事件发生时, 相应的事件就会被处理. 如果你不想再这个事件发生时处理数据, 就需要调用删除事件RemoveEventListener来移除事件的侦听. 在使用时, 你可以使用一个全局的事件处理器来管理事件; 如果你只想对某一类增加事件处理功能, 这时, 你的类就需要继承自TNwEventListener, 该类已经为你实现了INiEventListener接口, 在需要让某一个事件触发时,你只需要调用PerformEvent就可以. 这个时候的事件数据就需要事先进行约定.
下面附上全部的事件处理器代码.
unit EventListener; interface uses System.Generics.Collections; type TNwEventProc = procedure(const EventType:string;EventData:Pointer) of object; INiEventListener = interface procedure PerformEvent(EventType: String; EventData: Pointer); procedure removeEventListener(EventType: String; EventProc: TNwEventProc); procedure addEventListener(EventType: String; EventPro: TNwEventProc); end; TEventData = record EventType:String; EventProc:TNwEventProc; end; TNTEventList = TList<TEventData>; TNwEventListener = class(TInterfacedObject, INiEventListener) protected FEventList:TNTEventList; public procedure PerformEvent(EventType: String; EventData: Pointer); procedure removeEventListener(EventType: String; EventProc: TNwEventProc); procedure addEventListener(EventType: String; EventPro: TNwEventProc); destructor Destroy; override; constructor Create(); end; var IEventListener:INiEventListener; implementation uses System.SysUtils, Rtti; { TNwEventListener } procedure TNwEventListener.addEventListener(EventType: String; EventPro: TNwEventProc); var EventData:TEventData; begin EventData.EventType := EventType; EventData.EventProc := EventPro; FEventList.Add(EventData); end; constructor TNwEventListener.Create; begin inherited; FEventList := TNTEventList.Create(); end; destructor TNwEventListener.Destroy; begin FEventList.Clear; FreeAndNil(FEventList); inherited; end; procedure TNwEventListener.PerformEvent(EventType: String; EventData: Pointer); var I:Integer; begin for I := FEventList.Count - 1 downto 0 do begin if FEventList.Items[I].EventType = EventType then FEventList.Items[I].EventProc(EventType, EventData); end; end; procedure TNwEventListener.removeEventListener(EventType: String; EventProc: TNwEventProc); var I: Integer; EventData:TEventData; ItemProc:TNwEventProc; function SameMethod(S, T:TNwEventProc):Boolean; var MethodS, MethodT:TMethod; begin MethodS := TMethod(S); MethodT := TMethod(T); Result := ( MethodS.Code = MethodT.Code ) and ( MethodS.Data = MethodT.Data ); end; begin for I := FEventList.Count - 1 downto 0 do begin EventData := FEventList.Items[I]; ItemProc := EventData.EventProc; if (EventData.EventType = EventType) and SameMethod(ItemProc, EventProc) then begin FEventList.Delete(I); end; end; end; end.
你可能会关心如何使用的问题, 下面,附上使用代码
//使用全局对象的方法 type TUseEventListener = class protected procedure OnEvent( EventType:String; EventData:Pointer ); public procedure InitEvents; end; var UseEvent:TUseEventListener; begin { TUseEventListener } procedure TUseEventListener.InitEvents; begin IEventListener.addEventListener( 'OnMessage', OnEvent ); end; procedure TUseEventListener.OnEvent( EventType:String; EventData: Pointer ); begin ShowMessage( String( PChar( EventData ) ) ); end; IEventListener := TNwEventListener.Create; UseEvent := TUseEventListener.create; UseEvent.InitListener; IEventListener.PerformEvent( 'OnMessage', PChar( 'The Message should be shown.' )); end.
使用类对象的方法:
const evtDataChanged = 'evtDataChanged'; Type PData = ^TData; TData = record Name: String; City: String; CellPhone: String; Age: Integer; end; //数据处理类, 用于提供数据 TNwDataClass = class( TNwEventListener ) public procedure AddData( Name, City, CellPhone:String; Age: Integer ); end; //界面显示类 TNwInterface = class( TForm ) procedure FormCreate( Sender: TObject ); protected procedure OnEvent( EventType:String; EventData:Pointer ); public procedure AddDataToList( Data: TData ); end; // TNwDataClass 应该有一个全局的实例, 用于提供数据. 在下面的代码中, 就以 // instanceDataClass 为这个实例 implementation { TNwDataClass } procedure TNwDataClass.AddData( Name, City, CellPhone:String; Age: Integer ); var Data: PData; begin //数据处理代码. GetMem( Data, SizeOf( TData ) ); try Data^.Name := Name; Data^.City := City; Data^.CellPhone := CellPhone; Data^.Age := Age; Self.PerformEvent( evtDataChanged, Data ); finally FreeMem( Data ); end; end; { TNwInterface } procedure TNwInterface.FormCreate( Sender: TObject ); begin instanceDataClass.addEventListener( evevtDataChanged, OnEvent ); end; procedure TNwInterface.OnEvent( EventType:String; EventData: Pointer ); begin AddDataToList( PData( EventData )^ ); end; procedure TNwInterface.AddDataToList( Data: TData ); begin //用于处理显示数据的代码. end;
总体来看, 这种做法方便简单实用. 欢迎在使用过程中有什么问题与我交流!
基于THREE JS的 JSON 模型格式 理解
THREE.js 是一个开源的基于WebGL的渲染引擎,提供了很多方便有用的类与方法,为我们使用WebGL提供了大大的便利,简化了我们的学习成本,让你只要专注于项目本身,而不会花太多时间留在WebGL的技术实现细节。当你在处理3D网络应用或项目时,你就会遇到一个问题,选择一个适合的模型格式就会摆在你的案头。obj, 3ds, dae还是其他什么呢?对于那些以二进制进行数据保存的格式而已,网络加载是一个问题,js处理二进制也是一个问题, 所以,如果有一个可以完美支持JS特性的格式,那真是令人高兴的事情,JSON Model Format 就是这样一种模式格式。
JSON Model Format 的基本格式如下:
{ "metadata": { "formatVersion" : 3 }, "materials": [ { "DbgColor" : 15658734, // => 0xeeeeee "DbgIndex" : 0, "DbgName" : "dummy", "colorDiffuse" : [ 1, 0, 0 ], } ], "vertices": [ 0,0,0, 0,0,1, 1,0,1, 1,0,0, ... ], "normals": [ 0,1,0, ... ], "colors": [ 1,0,0, 0,1,0, 0,0,1, 1,1,0, ... ], "uvs": [ [ 0,0, 0,1, 1,0, 1,1 ], ... ], "faces": [43, 0, 4, 5, 1, 0, 0, 1, 2, 3, 0, 1, 2, 3] }
其中Metadata 中包含formatVersion 用于指示当前文件所用的格式版本号,这将决定于解析器如何解析下面的文件内容。
接下来另一个重要的域是Materials, 指定该模型所用到的素材,一个模型可能用到多个素材,所以,Materials是一个数组,每一项都是material对象。每个Material对象都包括DbgIndex 指示该Material的索引,DbgName显示Matrial的名字。
接下来的一个域是vertices, 也就是顶点了,顶点以三个数为一组,每三个数组成一个顶点坐标,x, y, z. Normals用于保存法线坐标,跟顶点一样,每三个数组成一个法线向量。 Colors是顶点颜色, THREE.js 允许给每个顶点指定颜色。 uvs是贴图坐标,faces是模型的面了,我们都知道模型是由三角面、四角面或多边形面组成的, faces就是用来保存这个面的信息。
一个面可以包括多个信息,比如说,他是三角面还是四边形面或是其他?每个顶点是不是有颜色?是不是只有一个面法线还是有面顶点法线?要区分并保存这些信息,faces是如何做的吗?其他他的格式是这样的,基本上可以看做是流式。以一个标志为开头,后面跟一系列的数据说明。例如:
[ 43, 0, 4, 5, 1, 0, 0, 1, 2, 3, 0, 1, 2, 3 ]
第一个数, 43 就是这个标志. 把这个标志表示成二进制,它的每个二进制位都代表着不同的意思。43 表示成二进制如下:
00 10 10 11
43 表示成一个字节,8个二进制位,每个位都有特定含意,如果这个位为1(置位),表示这个位在后续的数据流中有表示,如果为0,则说明后续的数据流中没有该位所代表的特定含意的数据表示。那么这8个位都表示什么意思呢?
二进制位 |
含意 |
0 |
面的类型。如果为0,表示三角面,为1表示四边形面 |
1 |
指示该面是否有素材,0表示没有,1表示有 |
2 |
指示该面是否有贴图坐标,0表示没有,1表示有 |
3 |
指示该面是否有顶点贴图坐标,0表示没有,1表示有 |
4 |
指示该面是否有面法线,0表示没有,1表示有 |
5 |
指示该面是否有顶点法线,0表示没有,1表示有 |
6 |
指示是否为面指定了颜色,0表示没有,1表示有 |
7 |
指示是否有顶点颜色,0表示没有,1表示有 |
后面的数据是按照从小大到的顺序排列的。 例如如果标志为43的话,表示 四边形面,有素材,有顶点贴图坐标,有顶点法线。所以,43后面跟着的四位数:0, 4, 5, 1 就是对应顶点的索引。 那么接下来的 0 就表示当前面的素材索引为 0, 然后接下来的 0, 1, 2, 3, 表示顶点贴图坐标的索引,最后的0, 1, 2, 3 则是顶点法线的索引。.
以上说的是大部分模型都会有的数据。有些特殊的模型还会有别的域,比如说
"morphTargets" : [{ "name": "animation_000000", "vertices": [ ... ] }, ... { "name": "animation_000000", "vertices": [ ... ]}], "bones" : [], "skinIndices" : [], "skinWeights" : [], "animation" : {}
morphTargets存在表示模型存在变形动画,后面跟着的都是每帧的顶点数据。 bones用于保存骨骼信息,animation用于保存动画信息。
以上是基本的对JSON Model Format 的说明与解析,希望能对你有所帮助。
本文为原创,如果你想转载,请注明“梦溪笔记”出品!
汇率和利率的关系
汇率和利率应该大致上服从“利率平价”规则。利率平价的意思是说,你的1块钱人民币存一年获得的收益,应该和这1块钱人民币兑换成美元、在美国存一年、再换回人民币以后获得的收益一样多。
如果可以自由兑换,并不需要通过物价。
现有有两个国家货币是A和B,0年汇率1A可兑换2B,记作1A=2B,A所在国利率若为5%,B所在国利率若为10%,并作如下假设:
a.存贷利率相等且不变
b.货币可完全自由兑换
c.利率为无风险利率,且不受任何其他因素影响
则可通过下述步骤套利:
1、借入A,假设1000单位A
2、兑换为B,得到2000B
3、B存入1年得到2200B
4、兑换为A,得到1100A
5、归还1050A,净得50A。
由上可以看到,如果利率和汇率不变,则存在无风险套利手法。而根据“免费午餐不存在”这一基本法则,可以看到两者间如果存在利差,则A需求较B为少,则会从实际中促进A、B两者1年后汇率发生改变,如果市场是完全高效的,则到到期日,A、B的汇率变为1050A=2200B,即刚好无风险套利所得为0。此处假设利率已经锁定,在实际中,活期利率和汇率会同时受到影响而改变,最后趋向于一个平衡状态。凡是处于不平衡状态的利率/汇率都在理论上存在套利机会,但并非无风险,因为假设c实际上不成立。
天朝的情况需要通过物价,因为假设b不成立,此种情况下无法通过利差来平衡物价,需要引入购买力的概念。“购买力平价比”代表的是这两种货币潜在的汇率预期。我们假设市场上仅有一种商品C且两者都能生产,汇率依然是1A=2B不变,并作如下假设:
d.假设商品可以自由流通
e.两地以当地货币计价的商品C价格相等,为1A或1B,且两者消耗的资源也相等。
f.两者都可以生产超过两者总需求之和的C,且C的标价不变
g.货币总量不变
则我们发现在A所在国,产品C的价格是1A,若出口到B所在国标价是2B,但当地生产只需要1B,因此根据替代原理,A生产的C销量是0,而反向从B所在国进口C,标价仅需0.5A。此时相当于B在补贴A,A所在国的消费者获得了更好的价格,而B的资源被相对低估了。从而在原理上,两者的资源应当是同等价值的。那么A所在国将必须大量用A兑换B来购买C,使得A相对B贬值,最后两者趋向于相等。
而目前,为了获得资金,我朝即B,将上述的修正机制短路,通过不断购买A货币债券将A返回到A所在国,并在固定汇率下发行B使A可以兑换B购买商品C,其结果就是B获得了大量A债券资产和B现金,A获得了商品C,但大量负债。