所谓学问 – 梁漱溟 1928年演讲

1928年在广州中山大学的讲演(节选)

作者:梁漱溟

 

所谓学问,就是对问题说得出道理,有自己的想法。

想法似乎人人都是有的,但又等于没有。因为大多数人的头脑杂乱无章,人云亦云,对于不同的观点意见,他都点头称是,等于没有想法。

我从来没有想过要做学问,走上现在这条路,只是因为我喜欢提问题。大约从十四岁开始,总有问题占据在我的心里,从一个问题转入另一个问题,一直想如何解答,解答不完就欲罢不能,就一路走了下来。

提得出问题,然后想要解决它,这大概是做学问的起点吧。

以下分八层来说明我走的一条路:

第一层境界:形成主见

用心想一个问题,便会对这个问题有主见,形成自己的判断。

说是主见,称之为偏见亦可。我们的主见也许是很浅薄的,但即使浅薄,也终究是你自己的意见。

许多哲学家的哲学也很浅,就因为浅便行了,胡适之先生的哲学很浅,亦很行。因为这是他自己的,纵然不高深,却是心得,而亲切有味。所以说出来便能够动人,能动人就行了!他就能自成一派,其他人不行,就是因为其他人连浅薄的哲学都没有。

第二层境界:发现不能解释的事情

有主见,才有你自己;有自己,才有旁人,才会发觉前后左右都是与我意见不同的人。

这时候,你感觉到种种冲突,种种矛盾,种种没有道理,又种种都是道理。于是就不得不第二步地用心思。

面对各种问题,你自己说不出道理,不甘心随便跟着人家说,也不敢轻易自信,这时你就走上求学问的正确道路了。

第三层境界:融汇贯通

从此以后,前人的主张、今人的言论,你不会轻易放过,稍有与自己不同处,便知道加以注意。

你看到与自己想法相同的,感到亲切;看到与自己想法不同的,感到隔膜。有不同,就非求解决不可;有隔膜,就非求了解不可。于是,古人今人所曾用过的心思,慢慢融汇到你自己。

你最初的一点主见,成为以后大学问的萌芽。从这点萌芽,你才可以吸收养料,才可以向上生枝发叶,向下入土生根。待得上边枝叶扶疏,下边根深蒂固,学问便成了。

这是读书唯一正确的方法,不然读书也没用处。会读书的人说话时,说他自己的话,不堆砌名词,不旁征博引;反之,引书越多的人越不会读书。

第四层境界:知不足

用心之后,就知道要虚心了。自己当初一点见解之浮浅,不足以解决问题。

学问的进步,不单是见解有进步,还表现在你的心思头脑锻炼得精密了,心气态度锻炼得谦虚了。

心虚思密是求学的必要条件。

对于前人之学,总不要说自己都懂。因为自己觉得不懂,就可以除去一切浮见,完全虚心地先求了解它。

遇到不同的意见思想,我总疑心他比我高明,疑心他必有我所未及的见闻,不然,他何以不和我作同样判断呢?疑心他必有精思深悟过于我,不然,何以我所见如此而他所见如彼呢?

第五层境界:以简御繁

你见到的意见越多,专研得愈深,这时候零碎的知识,片段的见解都没有了;心里全是一贯的系统,整个的组织。如此,就可以算成功了。到了这时候,才能以简御繁,才可以学问多而不觉得多。

凡有系统的思想,在心里都很简单,仿佛只有一两句话。凡是大哲学家皆没有许多话说,总不过一两句。很复杂很沉重的宇宙,在他手心里是异常轻松的—-所谓举重若轻。

学问家如说肩背上负着多沉重的学问,那是不对的;如说当初觉得有什么,现在才晓得原来没有什么,那就对了。道理越看得明透,越觉得无甚话可说,还是一点不说的好。心里明白,口里讲不出来。

反过来说,学问浅的人说话愈多,思想不清楚的人名词越多。让一个没有学问的人看见,真要把他吓坏了!其实道理明透了,名词便可用,可不用,或随意拾用。

第六层境界:运用自如

如果外面或里面还有解决不了的问题,那学问必是没到家。如果学问已经通了,就没有问题。

真学问的人,学问可以完全归自己运用。假学问的人,学问在他的手里完全不会用。

第七层境界:一览众山小

学问里面的甘苦都尝过了,再看旁人的见解主张,其中得失长短都能够看出来。这个浅薄,那个到家,这个是什么分数,那个是什么程度,都知道得很清楚;因为自己从前也是这样,一切深浅精粗的层次都曾经过。

第八层境界:通透

思精理熟之后,心里就没有一点不透的了。

bg2013092902

Delphi 实现事件侦听与触发

我最近在Cnblogs上看到有一些朋友在讨论关于事件侦听,他的想法是,给Delphi即有的事件,例如click, mouse加上多播机制,在我认为,这种做法是不可取的,delphi在设计之初,就是把所谓的事件当做是一个回调函数来处理,这就使得每个事件只能有一个响应者,在这之上给组件加上事件多播机制只会让代码更加难懂,更加难用,增加代码量。而且用这种方法增加的事件多播机制只能用于界面上, 不能很好的用于处理数理, 实现数据与界面的分离, 降低耦合度, 在此, 我把我的事件多播实现机制与原理分享给大家. 至于好与坏, 请在实践中进行检验.

 

武稀松的实现方法相似, 本方法在实现上用到了以下几个知识点:

  1. RTTI的 TMethod 结构体, 该结构体可用于比较两个事件处理方法是否相同, 用于移除事件侦听.
  2. 用于了指针, 用于传递事件数据, 需要了解如何获取内存, 释放内存
  3. 泛型. 用于保存事件数据.

下面来具体分析 代码.

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;

 

总体来看, 这种做法方便简单实用. 欢迎在使用过程中有什么问题与我交流!