总结一下,chrome买票 基本就是下面的几个地址复制进去地址栏安装插件
有G+的可以去看看具体详细操作https://plus.google.com/117239089659078825668/posts/fRMcziZUGk4
1. 登录 12306 Auto Login , kevintop
https://gist.github.com/raw/1570973/f200dd587f6d68ab81edf74436b1fb7d91c79973/12306AutoLogin.user.js

2. 查看和提醒 12306 Auto Query , quietlynn 高亮+音乐提醒
https://gist.github.com/raw/1554666/ad02648835b0d61b45d9b87f2017ad0da4a86a34/12306.user.js

3 在chrome支付 (使用银联支付-在线支付可以不安装,他支持各种银行,密码控件也支持
选择:
刚才发现 1和2的合并版本 zzdhidden(据G+说360浏览器已经征求意见准备使用了)
https://github.com/zzdhidden/12306/raw/master/12306BookingAssistant.user.js
经测试 Firefox和IE也是可以的 借助油猴GreaseMonkey ,不过高亮和音乐提示没有了
完整的艰难历程
定义
接口 (interface) 定义了一个可由类和结构实现的协定。接口可以包含方法、属性、事件和索引器。接口不提供它所定义的成员的实现 — 它仅指定实现该接口的类或结构必须提供的成员。接口可支持多重继承。
接口是包含一组虚方法的抽象类型,其中每一种方法都有其名称、参数和返回值。C#接口中不能包含任何静态成员。
一个接口定义一个协定。实现某接口的类或结构必须遵守该接口定义的协定。一个接口可以从多个基接口继承,而一个类或结构可以实现多个接口。
接口可以包含方法、属性、事件和索引器。接口本身不提供它所定义的成员的实现。接口只指定实现该接口的类或结构必须提供的成员。
public delegate void StringListEvent(IStringList sender);
public interface IStringList
{void Add(string s);//方法
int Count { get; }//属性
event StringListEvent Changed;//事件
string this[int index] { get; set; }//索引器
}
接口不能包含常量、字段、运算符、实例构造函数、析构函数或类型,也不能包含任何种类的静态成员。
所有接口成员都隐式地具有 public 访问属性。接口成员声明中包含任何修饰符都属于编译时错误。
关于继承
当两个或更多个不相关(互不继承)的基接口中声明了具有相同名称或签名的成员时,就会发生多义性。在所有情况下,都可以使用显式强制转换来解决这种多义性。
多重继承接口中的直观隐藏规则是:如果成员在任一访问路径中被隐藏,那么它在所有访问路径中都被隐藏。如果类或结构实现一个包含被隐藏成员的接口,那么一些成员必须通过显式接口成员实现来实现。
interface IBase{void F(int i);
}
interface ILeft : IBase{new void F(int i);
}
interface IRight : IBase{ void G();}
interface IDerived : ILeft, IRight { }class A{ void Test(IDerived d) { d.F(1); // Invokes ILeft.F ((IBase)d).F(1); // Invokes IBase.F ((ILeft)d).F(1); // Invokes ILeft.F ((IRight)d).F(1); // Invokes IBase.F}
}
显式实现
在方法调用、属性访问或索引器访问中,不能直接访问“显式接口成员实现”的成员,即使用它的完全限定名也不行。“显式接口成员实现”的成员只能通过接口实例来访问,并且在通过接口实例访问时,只能用该接口成员的名称来引用。
显式接口成员实现有两个主要用途:
由于显式接口成员实现不能通过类或结构实例来访问,因此它们就不属于类或结构的自身的公共接口。当需在一个公用的类或结构中实现一些仅供内部使用(不允许外界访问)的接口时,这就特别有用。
显式接口成员实现可以消除因同时含有多个相同签名的接口成员所引起的多义性。如果没有显式接口成员实现,一个类或结构就不可能为具有相同签名和返回类型的接口成员分别提供相应的实现,也不可能为具有相同签名和不同返回类型的所有接口成员中的任何一个提供实现。
接口映射
关于类或结构 C 的接口映射就是查找 C 的基类列表中指定的每个接口的每个成员的实现。对某个特定接口成员 I.M 的实现(其中 I 是声明了成员 M 的接口)的定位按下述规则执行:从 C 开始,按继承顺序,逐个检查它的每个后续基类(下面用 S 表示每个进行检查的类或结构),直到找到匹配项:
· 如果 S 包含一个与 I 和 M 匹配的显式接口成员实现的声明,那么此成员就是 I.M 的实现。
· 否则,如果 S 包含与 M 匹配的非静态的 public 成员声明,则此成员就是 I.M 的实现。如果找到多个匹配成员,则无法确定哪个成员是 I.M 的实现。只有 S 是构造类型(在此情况下,泛型类型中声明的两个成员具有不同的签名,但类型参数却使他们的签名相同)时,才会出现此情况。
如果不能为在 C 的基类列表中指定的所有接口的所有成员找到实现,则将发生编译时错误。请注意,接口的成员包括那些从基接口继承的成员。
根据接口映射的含义,类成员 A 在下列情况下与接口成员 B 匹配:
· A 和 B 都是方法,并且 A 和 B 的名称、类型和形参表都相同。
· A 和 B 都是属性,A 和 B 的名称和类型相同,并且 A 与 B 具有相同的访问器(如果 A 不是显式接口成员实现,则它可以具有其他访问器)。
· A 和 B 都是事件,并且 A 和 B 的名称和类型相同。
· A 和 B 都是索引器,A 和 B 的类型和形参表相同,并且 A 与 B 具有相同的访问器(如果 A 不是显式接口成员实现,则它可以具有其他访问器)。
近日看到的一个文章,搜索的来,不知哪位前辈,感谢了~
有关构造函数,调用这两个方面的内容
让我们先来看看class和struct在调用构造函数时的区别。class使用newobj指令而struct使用initobj指令来构造对象。newobj在堆上申请一块内存并调用相应的构造函数进行初始化,然后将对象地址返回给计算栈。initobj则是从本地变量表中载入已经分配出来的struct实例然后初始化struct的各字段。这个初始化过程是CLR内部执行的,而不像class编译器会给class添加一个默认构造函数(这就是为什么struct不能给字段添加默认值的原因。但在类中如果给字段添加了默认值编译器就会自动在构造函数中添加字段赋值操作)。如果给struct中定义了一个有参数的构造函数,那么系统就不会使用initobj指令,而是直接用call指令调用带参数的构造函数。
我们最常见最常用的调用函数的指令是call和callvirt。对于静态函数使用call指令,对于class使用callvirt指令(不论class中的函数是不是虚的)。只有子类调用父类的函数的时候(避免递归调用)以及构造函数中(由编译器添加保证父类字段被初始化)使用call指令。而对于struct我们发现只要调用的函数是struct本身定义的都是使用call指令。call和callvirt指令的差别在于,call会把调用的函数当作静态函数看待,而不会关心调用当前函数时实例指针(this)是否为空。这就是struct调用函数时为什么都是call因为struct实例是不可能被置为null的。实际上class在调用非虚函数时实际上也是使用call的只是多做了一步验证——this是否为空,让我们来验证一下。
using System;using System.Collections.Generic;using System.Linq;using System.Text;namespace CLR { class Class_Test {public void Test1() { Console.WriteLine("Test 1"); }
public virtual void Test2() { Console.WriteLine("Test 2"); }
public static void Test3() { Console.WriteLine("Static Method"); }
public override string ToString() {
return base.ToString();
}
}
class Program {static void Main(string[] args) {
Class_Test c=new Class_Test();c.Test1();
c.Test2();
Class_Test.Test3();
string str=c.ToString();Console.ReadLine();
}
}
}
对应的汇编如下:
c.Test1(); //实例非虚函数 0000006b mov ecx,esi //将this放到ecx中,ecx在.net函数调用规则中保存第一个参数 0000006d cmp dword ptr [ecx],ecx //验证this是否为空,空指针的话dword ptr [ecx]就会报错 0000006f call FFEEC130 //调用函数 00000074 nop c.Test2(); //实例虚函数 00000075 mov ecx,esi 00000077 mov eax,dword ptr [ecx] //得到方法表地址,引用类型在堆上开始4个字节是方法表地址 00000079 call dword ptr [eax+38h] //因为是虚函数每次调用的时候都要计算要调用的函数地址 0000007c nop Class_Test.Test3(); //静态函数 00000083 call FFEEC140 //调用函数 00000088 nop public override string ToString() //子类调用父类函数 { //省略前面的汇编 return base.ToString(); //如果使用callvirt就会死循环 00000026 mov ecx,edi //从ecx中得到this 00000028 call 77A00F68 //调用函数 0000002d mov esi,eax //.net函数调用规则中eax保存返回值 0000002f mov ebx,esi 00000031 nop 00000032 jmp 00000034 }
通过上边的汇编我们可以看出class调用非虚函数时本质上使用了call指令,而调用父类函数时就是直接使用call,并且因为在实例函数中所以不需要验证this是否为空。这里说点题外话,在IL中我们经常会看到执行函数时将本地变量加载到计算栈中或者将计算栈中的结果保存到本地变量中这不是很慢的操作吗?实际上在大多数情况下是通过esi,edi这些寄存器来当缓存的,如果局部变量比较多才会保存到相应的栈上。从这里我们又印证了事实,.net的线程栈在每次执行函数时所创建的栈帧包含参数表,本地变量表,返回地址和计算栈。
继续说call指令的问题,我前面说了struct本身定义的都是使用call指令调用的如果你亲自动手实验的就会发现我说不对。如果struct覆写了基类的函数(GetHashCode,ToString)在调用是IL会使用callvirt来调用,我真的错了吗?
using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace CLR { struct Struct_Test { bool _a; int _b; int _c; public Struct_Test(bool a,int c,int b) { this._a=a; this._b=b; this._c=c; } public void Test() { } public override string ToString() { return string.Format("{0}, {1}, {2}",this._a,this._b,this._c); } } class Program { static void Main(string[] args) { Struct_Test s=new Struct_Test(true,15,20); string str=s.ToString(); Console.ReadLine(); } } }
对应的IL代码
IL_0001: ldloca.s s IL_0003: ldc.i4.1 IL_0004: ldc.i4.s 15 IL_0006: ldc.i4.s 20 IL_0008: call instance void Test_Console.Struct_Test::.ctor(bool, int32, int32) IL_000d: nop IL_000e: ldloca.s s IL_0010: constrained. Test_Console.Struct_Test IL_0016: callvirt instance string [mscorlib]System.Object::ToString() IL_001b: stloc.1
如果你仔细观察会发现在callvirt调用的上面有这么一条指令constrained。让我们看看msdn里让人头晕的解释:
- 如果 callvirtmethod 指令前面带有前缀 constrainedthisType,该指令将按照以下步骤执行:
- 如果 thisType 为引用类型(相对于值类型),则 ptr 被取消引用,并作为“this”指针传递到 method 的callvirt。
- 如果 thisType 为值类型,而且 thisType 实现 method,则 ptr 作为“this”指针在不作任何修改的状态下传递到 callmethod 指令,以便 thisType 实现 method。
- 如果 thisType 为值类型,而且 thisType 不实现 method,则将取消对 ptr 的引用,对它进行装箱,然后将它作为“this”指针传递到 callvirtmethod 指令。
Struct_Test s=new Struct_Test(true,15,20); Console.WriteLine(GC.GetTotalMemory(false)); int hash=0; for(int i=0;i<10000000;++i) { hash=s.GetHashCode(); } Console.WriteLine(GC.GetTotalMemory(false)); Console.WriteLine(GC.CollectionCount(0));
运行结果为:141200 399104 127
从上面的结果可以看到如果没有覆写虚函数确实引起了装箱。让我在对比一下与调用ToString()时的不同,s.ToString()请看反汇编;
s.ToString();
0000003d lea ecx,[ebp-44h]
00000040 call FFE4C0B0
00000045 nop
s.GetHashCode();
00000046 mov ecx,7C3810h //Struct_Test方法表地址
0000004b call FFE31FAC //在堆上分配空间
00000050 mov ebx,eax
00000052 lea edi,[ebx+4]
00000055 cmp ecx,dword ptr [edi]
00000057 lea esi,[ebp-44h] //将栈上数据拷贝到堆上
0000005a movq xmm0,mmword ptr [esi]
0000005e movq mmword ptr [edi],xmm0
00000062 add esi,8
00000065 add edi,8
00000068 movs dword ptr es:[edi],dword ptr [esi]
00000069 mov ecx,ebx
0000006b mov eax,dword ptr [ecx] //虚函数调用
0000006d call dword ptr [eax+30h]
所以我们使用struct要小心不要因为忘记了覆写虚函数而造成不必要的性能损失。而且在这里因为没有调用Struct_Test本身的函数所以不会触发静态构造的执行。最后说一下struct在调用函数的时候首先要得到this指针,比如IL_000e: ldloca.s s。大家注意看这里不是ldloc所以对于Struct_Test的函数调用来说第一个参数是ref Struct_Test,感觉ref的这个参数修饰用在这里才是最能体现价值的。
using System;class A {public void F() { Console.WriteLine("A.F"); }
public virtual void G() { Console.WriteLine("A.G"); }
}
class B : A {new public void F() { Console.WriteLine("B.F"); }
public override void G() { Console.WriteLine("B.G"); }
}
class Test {static void Main() {
B b = new B();A a = b;
a.F();
b.F();
a.G();
b.G();
}
}
这是今天看书的一段代码,mark!
在该示例中,A 引入一个非虚方法 F 和一个虚方法 G。类 B 引入一个新的非虚方法 F,从而隐藏了继承的 F,并且还重写了继承的方法 G。根据对象初始化的过程,着眼于方法表的顺序:关注对象,从自身类向派生类搜索到第一个可以访问的同名方法。“在虚方法调用中,该调用所涉及的那个实例的运行时类型 (run-time type) 确定了要被调用的究竟是该方法的哪一个实现。在非虚方法调用中,相关的实例的编译时类型 (compile-time type) 是决定性因素。”(——《C#规范4.0》)由此可知,实例a b内存的布局前半部分是一致的,运行时类型b:B.F B.G 。编译类型a:A.F A.G B.G B.F ,然后根据名称找方法,分别是 A.F B.F B.G B.G。
对调用哪个实际方法实现起决定作用的是该实例的运行时类型(即引用类型 A),而不是该实例的编译时类型(即实际内存分配对象B)。
class A {public virtual void F() { Console.WriteLine("A.F"); }
}
class B : A {public override void F() { Console.WriteLine("B.F"); }
}
class C : B {new public virtual void F() { Console.WriteLine("C.F"); }
}
class D : C {public override void F() { Console.WriteLine("D.F"); }
}
class Test {static void Main() {
D d = new D();A a = d;
B b = d;
C c = d;
a.F();
b.F();
c.F();
d.F();
}
}
C 类和 D 类包含两个具有相同签名的虚方法:一个是 A 引入的,另一个是 C 引入的。但是,由 C 引入的方法隐藏了从 A 继承的方法。因此,D 中的重写声明所重写的是由 C 引入的方法,D 不可能重写由 A 引入的方法。内存都D的布局,a.F搜索到B类有满足方法,b.F也是直接该类满足,c和d同理。此例产生输出:B.F B.F D.F D.F。
1: using System;
2: using System.Collections.Generic;
3: using System.Linq;
4: using System.Text;
5: 6: namespace 实例构造器 {
7: /* class A {
8: private static int a = 1;
9: 10: static A() {
11: a = 3;
12: }
13: 14: public string b = "bjq";
15: 16: public A() {
17: a = 2;
18: }
19: 20: static void Main(string[] args) {
21: Console.WriteLine(a);
22: Console.ReadLine();
23: }
24: */
25: 26: class Program {
27: public static int num1;
28: public static int num2 = 1;
29: public static int num3;
30: static void Main(string[] args) {
31: Console.WriteLine(num2);32: Console.WriteLine(B.num5); //这里引用了类B的静态成员
33: Console.ReadLine(); 34: }35: static Program() {
36: Console.WriteLine(num1); 37: num3 = 5; ; 38: Console.WriteLine(num3); 39: } 40: } 41: 42: class A {
43: public static int num4 = 1;
44: static A() //注意这里是静态的构造函数
45: { 46: num4 = 9; 47: } 48: } 49: 50: class B {
51: public static int num5 = A.num4 + 1; //类B中引用了类A的静态成员
52: B() { } //注意这里改为了静态的构造函数
53: } 54: }还有一段代码,也是说明这个使用的:
关键是静态字段以及改变顺序的this和base,注意有参数和没有参数时候的构造函数调用,基本没有问题啦
using System;class Base { static Base() { Console.WriteLine("基类静态构造函数被调用。");}
private static Component baseStaticField = new Component("基类静态字段被实例化。");
private Component baseInstanceField = new Component("基类实例成员字段被实例化。");
public Base() { Console.WriteLine("基类构造函数被调用。");}
}
//此类型用作派生类,同基类一样,它也包含静态构造函数,以及静态字段、实例成员字段各一个。class Derived : Base { static Derived() { Console.WriteLine("派生类静态构造函数被调用。");}
private static Component derivedStaticField = new Component("派生类静态字段被实例化。");
private Component derivedInstanceField = new Component("派生类实例成员字段被实例化。");
public Derived() { Console.WriteLine("派生类构造函数被调用。");}
}
//此类型用于作为Base类和Derived类的成员//此类型在实例化的时候可以在控制台输出自定义信息,以给出相关提示class Component { public Component(String info) {Console.WriteLine(info);
}
}
//在主程序里实例化了一个子类对象class Program {static void Main(string[] args) {
Derived derivedObject = new Derived();Console.Read();
}
}
总的原则,也就是根据内存的布局,CLR加载方法的过程确定的,基本知道对象的创建过程就是没有问题了~
大体的顺序:
| 静态字段 |
| 静态构造函数(值类型不一定执行~) |
| 实例字段 |
| 实例构造函数(递归到System.Object逐个基类执行)使用base和this可以改变类内默认的调用顺序 |










