返回
软件
分类

借用了c/c++大非常多的口舌方法,一齐首就足以将成员声称为public

日期: 2020-03-13 18:55 浏览次数 : 196

1。7 语句(Statements)

看了3章了,发现有些东西不记一下,可能就瞬间就忘了。当做个电子笔记吧~

    MSDN:"尽管实际上对 C 或 C++ 中的每种指针类型构造,C# 都设置了与之对应的引用类型,但仍然会有一些场合需要访问指针类型。例如,当需要与基础操作系统进行交互、访问内存映射设备,或实现一些以时间为关键的算法时,若没有访问指针的手段,就不可能或者至少很难完成。为了满足这样的需求,C# 提供了编写不安全代码的能力。

c#借用了c/c++大多数的语句方法,不过仍然有些值得注意的地方。还有些地方是有所改动的。
在这里,我只提一些c#特有的东东。

 

在不安全代码中,可以声明和操作指针,可以在指针和整型之间执行转换,还可以获取变量的地址,等等。在某种意义上,编写不安全代码很像在 C# 程序中编写 C 代码。"

1。7。10 “foreach”语句
“foreach”语句列举一个集合内的所有元素,并对这些元素执行一系列的操作。还是看看例子吧:*/

属性是字段的一种自然延伸。与字段不同的是,属性不代表存储位置,它只是提供了访问机制,当它们的值被读写时可以执行特定的语句。 

不安全代码必须用修饰符 unsafe 明确地标记。

using System;
using System.Collections;
class Test
{
static void WriteList(ArrayList list) {
foreach (object o in list)
{
int i = (int) o;//如果是for语句,这里一定会报错!
Console.WriteLine(0);
Console.WriteLine(++i);
}
}
static void Main() {
ArrayList list = new ArrayList();
for (int i = 0; i < 10; i++)
list.Add(i);
WriteList(list);
}
}
/*这个例子用“foreach”扫描了整个“list”,并把“list”中所有的元素打印出来。有时候还是
挺方便的。

 

这里所谓的编写"不安全代码",就是要跳出.net CLR的限制,自己进行地址分配和垃圾回收.在C++里面,我们定义一个简单的指针,至少要做三件事,1.给他分配内存,2.保证类型转换正确3将内存空间释放,而在.net环境里,CLR将程序员从这些事情里解放出来,用户不再需要直接手工地进行内存操作。但是有时候,比如调用windows底层函数,或是效率上的原因使我们需要自己去操作地址空间,本文主要是说明在c#里面指针的用法.

1。7。15 安全检查开关(The checked and unchecked statements)
“checked”和“unchecked”语句用来控制数学运算和完整类型转换的检查工作。“checked”检查它
作用的域中可能出现的违例,并抛出一个异常;而“unchecked”则阻止所有的检查。举个例子:*/

任何枚举类型的默认值都是由整型值0转换而来。所以:

指针的使用、操作内存
1.& 和 *
    c++里面,我们很熟悉这两个东西.在c#里面他们也一样可以用,只不过含有他们的代码如果不在unsafe 标记下,编译器将会将它拒之门外.当然如果条件编译参数没有/unsafe 也是无法编译通过的(如果用vs.net集成编译环境,则在项目属性页-代码生成节将"允许不安全代码块"设置成true).

using System;
class Test
{
static int x = 1000000;
static int y = 1000000;
static int F() {
checked {return (x * y);} // 抛出 OverflowException
}
static int G() {
unchecked {return (x * y);} // 返回 -727379968
}
static int H() {
return x * y; // 缺省状态。
}
static void Main() {
F(); //可以注销掉此行试试。
Console.WriteLine(G());
Console.WriteLine(H());
}
}

必赢备用网址 ,Color c =0 ;    //okay

    &可以取得变量的地址,但是并不是所有的变量,托管类型,将无法取得其地址.c#里面,普通的值类型都是可以取得地址的,比如struct,int,long等,而class是无法取得其地址的,另外string是比较特殊的类型,虽然是值类型,但它也是受管的.这里插一下另一个运算符,sizeof,它也是仅可用于unsafe模式下.

/*
在编译过程中不会有任何错误出现。因为“checked”和“unchecked”只在运行时才起作用。值得一说的是
H()。它的缺省状态和编译器当前的缺省溢出检查的状态有关。但返回的结果肯定和F()或G()中的任一个相同。
再看一个例子:*/

 

看下面这段代码,里面简单的用到了*,&,sizeof,还有c#里很少见但c++里大家很熟的->: 

using System;
class Test
{
const int x = 1000000;
const int y = 1000000;
static int F() {
checked {return (x * y);} // 编译器警告(Compile warning):溢出(overflow)
}
static int G() {
unchecked {return (x * y);} // 返回 -727379968
}
static int H() {
return x * y; // 编译器警告(Compile warning):溢出(overflow)
}
static void Main() {
Console.WriteLine(F()); //可以注销掉此行试试。
Console.WriteLine(G());
Console.WriteLine(H()); //可以注销掉此行试试。
}
}

“在决定把一个internal类型的成员声明为public还是internal的时候,如果稍后这个类型被提升为public,是不是希望这个成员也变成public?如果是,一开始就可以将成员声明为public”

必赢备用网址 1必赢备用网址 2//代码

/* 当F()和H()求值的时候,就会引起一个编译警告。而在G()中,因为有了“unchecked”,屏蔽了这个警
告。要注意的是“checked”和“unchecked”都不能对函数的返回值进行操作!比如:*/
class Test
{
static int Multiply(int x, int y) {
return x * y;
}
static int F() {
checked{ return Multiply(1000000, 1000000); } // 与 return Multiply(1000000, 1000000);
} // 有相同的效果。
}
/* 其实大家稍微想一下知道为什么m$没有这么做!对这个内容的讨论超出本文的范围和俺的能力之外哦。

 

    class Class1
    {
        struct Point
        {
            public int x;
            public int y;
        }
        public static unsafe void Main() 
        {
            Point pt = new Point();
            Point* pt1 = &pt;
            int* px = &(pt1->x);
            int* py = &(pt1->y);
            Console.WriteLine("Address of pt is :0x{0:X} ",(uint)&pt);
            Console.WriteLine("size of the struct :{0} ",sizeof(Point));
            Console.WriteLine("Address of pt.x is :0x{0:X} ",(uint)&(pt.x));
            Console.WriteLine("Address of pt.y is :0x{0:X} ",(uint)&(pt.y));
            Console.WriteLine("Address of px is :0x{0:X} ",(uint)&(*px));
            Console.WriteLine("Address of py is :0x{0:X} ",(uint)&(*py));
            Console.ReadLine();
        }
    }

在c#中,所有的十六进制数都是uint。如果用强制类型转换会引起编译器报错。用“unchecked”则可以
跳过这个机制,把uint的十六进制数转化为int。如:*/

class A

我这里运行的输出结果是:

class Test
{
public const int AllBits = unchecked((int)0xFFFFFFFF);
public const int HighBit = unchecked((int)0x80000000);
}

{

Address of pt is :0x12F698
size of the struct :8
Address of pt.x is :0x12F698
Address of pt.y is :0x12F69C
Address of px is :0x12F698
Address of py is :0x12F69C

/* 上例所有的常数都是uint,而且超过了int的范围,没有“unchecked”,这种转换会引发一个编译器错
误。注意:上面用的是“unchecked”操作符。不是语句。不过它们之间除了一个用“()”,另一个用
“{}”以外,几乎一样。BTW,“checked”同样。

    int x;

可以看出struct的首地址与第一个成员变量的地址相同,而这个struct的长度是8个字节(=4+4).

1。7。16 “lock”语句(The lock statement)
“lock”获得一个相互排斥的对象锁定。(俺查过一些资料,但都没有清晰说明,暂不介绍)

    static void F(B b){

2.fixed
虽然在unsafe模式下可以使用指针,但是unsafe的代码仍然是受管代码.CLR会对它的对象进行管理以及垃圾回收,CLR在这个过程中就会对内存进行重定位,可能过一段时间后,根据指针指向的地址就找不到原来的对象了,岂不是说指针在c#里没有什么实际的作用?别急,还有fixed.

        b.x=1;            //okay

fixed 语句设置指向托管变量的指针并在fixed里的语句块执行期间“锁定”该变量(或者是几个变量)。如果没有 fixed 语句,则指向托管变量的指针将作用很小,因为垃圾回收可能不可预知地重定位变量。(实际上,除非在 fixed 语句中,否则 C# 不允许设置指向托管变量的指针。)

   }

看一段与刚才类似的代码,不同的地方是这里的输出地址的语句都在fixed块里.为什么不直接像第一个例子那样直接输出呢?这是因为我们对Point进行了一个小小的改动,它不再是struct了,它现在是class借用了c/c++大非常多的口舌方法,一齐首就足以将成员声称为public。!它是托管类型了,它的成员都没有固定的地址.但是在fixed块里,它的地址是固定的.

}

必赢备用网址 3必赢备用网址 4//代码

class B:A

        class Point
        {    
            public static int x;
            public int y;
        }
        public static unsafe void Main() 
        {
            Point pt = new Point();
            int[] arr = new int[10];
            //如果不用fixed语句,无论是静态成员还是实例成员,都将无法取得其地址。
            //int* ps = &CPoint.StaticField;
            //PrintAddress(ps);
            fixed (int* p = &Point.x)
                Console.WriteLine("Address is 0x{0:X}",(int)p);
            fixed (int* p = &pt.y) 
                Console.WriteLine("Address is 0x{0:X}",(int)p);
            fixed (int* p1 = &arr[0],p2 = arr)
            {
                Console.WriteLine("Address is 0x{0:X}",(int)p1);
                Console.WriteLine("Address is 0x{0:X}",(int)p2);
            }
            Console.ReadLine();
        }

{

我这里运行的输出结果是:

    static void F(B b){

Address is 0x3D5404
Address is 0x4BF1968
Address is 0x4BF1978
Address is 0x4BF1978

        b.x=1;            //error,x not accessible

3.分配内存
在堆栈上分配内存
c#提供stackalloc ,在堆栈上而不是在堆上分配一个内存块,语句为 type * ptr = stackalloc type [ expr ];它的大小足以包含 type 类型的 expr 元素;该块的地址存储在 ptr 指针中。此内存不受垃圾回收的制约,因此不必使用fixed将其固定。此内存块的生存期仅限于定义该内存块的方法的生存期。如果内存空间不足,将会抛出System.StackOverflowException异常.

    }

以下是一段示例程序(form msdn):

}

必赢备用网址 5必赢备用网址 6//代码

B类从A类里继承了私有成员x。不过因为这个成员是私有的,所以它只能在A的类主题里访问到。

public static unsafe void Main() 
{
    int* fib = stackalloc int[100];
    int* p = fib;
    *p++ = *p++ = 1;    //fib[0]=fib[1]=1
    for (int i=2; i<100; ++i, ++p)
        *p = p[-1] + p[-2];//fib[i]=fib[i-1]+fib[i-2];
    for (int i=0; i<10; ++i)
        Console.WriteLine (fib[i]);
    Console.ReadLine();
}

 

 

public class A

在堆上分配内存
既然有stackalloc,有没有heapalloc呢?答案是没有,c#没有提供这样的语法.想在堆上动态分配内存,只能靠自己想办法了.

{

通过Kernel32.dll里的HeapAlloc()和HeapFree()可以达到这个目的.看下面的代码:

        protected int x;

必赢备用网址 7必赢备用网址 8//代码

        static void F(A a,B b){

using System;
using System.Runtime.InteropServices;
public unsafe class Memory
{
    const int HEAP_ZERO_MEMORY = 0x00000008;//内存起始地址
    //获得进程堆的句柄
    [DllImport("kernel32")]
    static extern int GetProcessHeap();
    //内存分配
    [DllImport("kernel32")]
    static extern void* HeapAlloc(int hHeap, int flags, int size);
    //内存释放
    [DllImport("kernel32")]
    static extern bool HeapFree(int hHeap, int flags, void* block);
    static int ph = GetProcessHeap();//获得进程堆的句柄
    private Memory() {}
    public static void* Alloc(int size) //内存分配
    {
        void* result = HeapAlloc(ph, HEAP_ZERO_MEMORY, size);
        if (result == null) throw new OutOfMemoryException();
        return result;
    }
    public static void Free(void* block) //内存释放
    {
        if (!HeapFree(ph, 0, block)) throw new InvalidOperationException();
    }
}
class Test
{
    unsafe static void Main() 
    {
        byte* buffer = (byte*)Memory.Alloc(256);
        for (int i = 0; i < 256; i++) 
            buffer[i] = (byte)i;
        for (int i = 0; i < 256; i++) 
            Console.WriteLine(buffer[i]);
        Memory.Free(buffer);
        Console.ReadLine();
    }
}

               a.x = 1;        //okay

 

               b.x = 1;       //okay

 

         }

}

public class B:A

{

        static void F(A a,B b){

                a.x = 1;    //error,must access through instance of B

                b.x = 1;    //okay

        }

}

在A里,通过A和B的实例都可以访问到x,因为不管在哪种情况下,访问都是经由A的实例或则是A子类的实例发生的。但是B里的情况不同,这里通过实例A是无法访问到x的,因为A并非继承自B。

 

class A{...}

public class B:A{...}

B类会导致编译期错误,因为A没有达到B的可访问性。

class A{...}

public class B

{

    A F(){...}

    internal A G(){...}

    public A H(){...}

}

B中的H方法会导致编译期错误,因为返回类型A没有达到这个方法的可访问性。

 

readonly修饰符表示那些字段是只读字段。只有在字段声明阶段或者是在这个类的构造函数里才允许对readonly的字段进行赋值。

"readonly保护的是字段的位置(而非那个位置上的值)不会在构造函数之外被改变。”

public class Names
{

     public static readonly StringBuilder FirstBorn = new StringBuilder("Joe");

     public static readonly StringBuilder SecondBorn = new StringBuilder("Sue");

}

在构造函数之外,直接修改FirstBorn实例导致编译错误:

Names.FirstBorn = new StringBuilder("Biff");  //Compile error

但是修改StringBuilder实例却完全没有问题:

Names.FirstBorn.Remove(0,6).Append("Biff"); //okay

 

 方法在形式参数的类型里出现的任何类型参数都不是根据它的名字来识别,而是根据在方法的类型参数列表中的序号位置来识别的。

void F(string a,int b) //okay

void F(int b,string a) //okay

void F(int x) //F(int)

void F(ref int x) //F(ref int)

void F(out int x) //F(out int) error

void F(string[] a) //F(string[])

void F(params string[] a) //F(string[]) error

任何ref和out参数修饰符都是签名的一部分。但是ref和out却不能再同一个类型里声明,因为它们的签名仅仅依靠ref和out来区分。

返回类型和params修饰符也不属于签名的一部分。

 

”可被销毁“和”可被回收“之间的区别很重要。A的析构函数已经先执行了,但是A的方法还是有可能被另一个析构函数调用。同时,运行对象的析构函数可能让一个队形在程序的主线中再次变得可用。

 

整型字量的类型规则:

没有后缀---int,uint,long,ulong.

后缀为U或u---uint,ulong.

后缀为L或l---long,ulong.

后缀为UL,Ul,uL,LU,Lu,lU或lu---ulong.

 

实数字量规则:

没有后缀,默认为double。

后缀为F或f,为float类型。

后缀为D或d,为double类型。

后缀为M或m,为decimal类型。

 

位操作符是对数据按二进制位进行运算的操作符。c#位操作符包括按位与(&) | ~ << >>
int a=6&3;
Console.WriteLine("a={0}",a);
//6的二进制是00000110,3的二进制是00000011,按位与后等于00000010,即2
int b=6|3;
Console.WriteLine("b={0}",b);
//6的二进制是00000110,3的二进制是00000011,按位或后等于00000111,即7
int c=~6;
Console.WriteLine("c={0}",c);
//6的二进制是00000110,按位取反后是11111001即-7
int d=6^3;
Console.WriteLine("d={0}",d);
//6的二进制是00000110,3的二进制是00000011,按位异或后等于00000101,即5
int e=6<<3;
Console.WriteLine("e={0}",e);
//6的二进制是00000110,左移三位后等于00101000,即48
int f=6>>2;
Console.WriteLine("f={0}",f);
//6的二进制是00000110,右移二位等于00000001,即1

 

带类型参数的显示转换:

class X<T>

{

      public static long F(T t){

            return  (long)(object)t;   //okay,but will only work when T is long

     }

       // 进一步扩展:

      static T Cast<T>(object value){  return (T)value;  }

}

//只有当类型在编译期就已知为数字的时候才会考虑标准数字转换

X<int>.F(7)  //error 装箱的int不能直接转换为long

string s = Cast<string>("s");    //okay - reference conversion

int i = Cast<int>(3);    //okay - unboxing

long l = Cast<long>(3);    // invalidCastException:attempts an unboxing

                                      //  instead of an int->long numeric conversion

 

抱怨>:<转换实在太麻烦,太繁琐了,好多没看懂。

匿名函数转换

泛型委托类型Func<A,R>,以表示一个接受类型A的参数并返回类型R的值的函数:

delegate R Func<A,R>(A arg);

在下面的赋值中,

Func<int,int> f1 = x => x + 1;  //okay

Func<int,double> f2 = x => x+1; //okay

Func<double,int> f3 = x => x+1;  //error

每个匿名函数的参数和返回类型都是由赋值给匿名函数的变量的类型决定的。

几个不同特点的匿名函数和C#构造代码的相应翻译。

public delegate void D();    
没有捕捉任何外部变量的匿名函数:    
static void F(){
     D d = () => { Console.WriteLine("test"); };

}

翻译:

static void F(){

      D d = new D(_Method1);

}

static void _Method1(){

      Console.WriteLine("test");

}   

引用了this的实例成员:

int x;

void F(){
      D d = () => { Console.WriteLine(x); };

}

翻译:

int x;

void F(){

     D d = new D(_Method1);

}

void _Method1(){

      Console.WriteLine(x);

}

捕捉局部变量:

void F(){
      int y = 123;

      D d = () => { Console.WriteLine(y); };

}

局部变量的生命周期就必须延长到至少和匿名函数委托的生命周期一样。

翻译:

void F(){

      __Locals1  __locals1 = new __Locals1();

      __locals1.y = 123;

      D d = new D(__locals1.__Method1);

}

class __Locals1

{

      public int y;

      public void __Method1(){

               Console.WriteLine(y);

      }

}

 

表达式里操作符的计算顺序是由操作符的优先级和结合性决定的。表达式里的操作数按照从左至右的顺序进行计算。例如在F(i)+G(i++)*H(i)中,方法F调用时使用的是i的旧值,然后方法G调用时也是使用i的旧值,最后方法H调用时使用的是i的新值。

从高到低所有操作符的优先级:

1 基本 (x)     x.y        f(x)      a[x]       x++       x―― new      typeof     sizeof       checked     unchecked 
2 单目 +    -    ! ~ ++x      ――x      (T)x
3 乘法与除法 *       /       %
4 加法与减法 +        -
5 移位运算 ≤      ≥
6 关系运算 ﹤       >       <=       >=    is
7 条件等 = =        ! =
8 位逻辑与 &
9 位逻辑异或 ^
10 位逻辑或 |
11 条件与 &&
12 条件或 ‖
13 条件 ?:
14 赋值 =       *=       /=      %=     +=    -=     <<=    >>=     &=   ^=     |=

赋值操作符合条件操作符(?:) 是右结合的,就是说操作符是从右向左进行的。例如,x=y=z 会作为x= (y=z)来计算。

在x+y*z的列子里,x会率先被求职,接着是y,然后才是z。不能因为乘法是“发生”在加法之前的,所以就认为y和z是在x之前求职的。

可重载的一元操作符:

+  - ! ~ ++ -- true fasl

可重载的二元操作符:

+ - * / % & | ^ << >> == != > < >= <=

 

数字提升:

byte a = 1, b = 2;

byte c =  a + b ; //compile-time error

在这种情况下,变量a和b都被提升为int,所以

byte c = (byte)(a + b) ;

 

简单名字:简单名字由一个标示符构成,有时候还会跟一个类型参数列表。

“任何会导致一个简单名字的语义发生变化的重构都会被编译期捕捉到。”

class Test
{

      double x;

      void F(bool b){

           x = 1.0;

           if(b){

               int x;

               x = 1;

            }

       }

}

会导致编译期错误,因为x在外层块里指向了不同的实体。

class Test

{

      double x;

      void F(bool b){

            if(b){

                x = 1.0;

             }

             else{

                 int x;

                 x = 1;

              }

      }

}

是合法的,因为x在外层块里从来没有被使用过。

 

成员访问:

在E.I形式的成员访问里,如果E是一个标识符,且E作为简单名字的含义是一个常量、字段、属性、局部变量或者是和作为类型名的E的含义拥有相同类型的参数的话,那么E的这两种可能的含义都是合法的。(例如访问E的静态成员和嵌套类型)

struct Color

{

      public static readonly Color White = new Color(...);

      public static readonly Color Black = new Color(...);

      public Color Complement(){...};

}

class A

{

      public Color Color;

      void F(){

            Color = Color.Black;

            Color = Color.Complement();

       }

       static void G(){

             Color c = Color.Write;

       }

}

 

简单名字和成员访问的产生式可以在表达式的文法里制造歧义。例如:

F(G<A,B>(7));

可以解释为两个参数G<A 和 B>(7)的调用F。或者,它也可以解释为对泛型方法G的调用F。

 

调用表达式:

实例方法比扩展方法的优先级更高,在内层命名空间声明里的扩展方法比外层命名空间声明里的扩展方法优先级更高,直接在一个命名空间里声明的扩展方法比用using命名空间指令导入到同一个命名空间里的扩展方法优先级更高。例如,

public static class E

{

      public static void F(this object obj,int i){ }

      public static void F(this object obj,string s){ }

}

class A{}

class B

{

      public void F(int i){ }

}

class C
{

      public void F(object obj){ }

}

class X

{

      static void Test(A a,B b,C c){

            a.F(1);               //E.F(object , int)

            a.F("hello");        //E.F(object ,string)

            b.F(1);              //B.F(int)

            b.F("hello");       //B.F(object , string)

            c.F(1);              //C.F(object);

            c.F("hello");       //C.F(object);  

}

 

this访问:

this访问只允许出现在实例构造函数、实例方法或者实例访问器的块里。

base访问:

base访问用来访问在当前类或结构里被同名成员隐藏的基类成员。这类访问只能出现在实例构造函数、实例方法或者实例访问器的块中。

后缀递增和递减操作符:

class A{

      public static A operator++(A x){

             return new A();

      }

}

class B:A{

      static void Main(){

           B x = new B();

           x++;   //error cs0266

      }

}

x++或x--的值是操作之前x的值,而++x或--x的值是操作之后保存在x里的值。

new操作符:

new操作符可以用来创建类型的新实例。new创建值类型时,不会进行动态内存分配。

对象创建表达式的类型必须是类类型、值类型或类型参数。但是它不可以是abstract类类型。

对象初始化语句指定了一个对象的零到多个字段或属性的值。

public class Point

{

      public int X { get; set; }

      public int Y { get; set; }

}

Point a = new Point { X = 0, Y = 1};

集合初始化语句指定了集合的元素。

List<int> digits = new List<int> {0,1,2,3,4,5,6,7,8,9};

集合对象必须是实现了System.Collections.IEnumerable的类型。

数组创建表达式可以用来创建数组类型的新实例。

new int [,]  {{0,1},{2,3},{4,5}} 同 new int[3,2] {{0,1},{2,3},{4,5}}

数组创建表达式允许用数组类型的元素实例化一个数组,但是这样数组的元素必须是手动初始化的。例如,

int [][] a = new int[100][];

for(int i = 0;i<100;i++) a[i] = new int[5];

委托创建表达式可用于创建委托类型的新实例。委托创建表达式的参数必须是方法组、匿名函数或委托类型的值。

匿名对象创建表达式可用于创建匿名类型的对象。匿名对象初始化语句声明了一个匿名类型,并返回一个那个类型的实例。

var p1 = new {Name = "Lawnmower", Price = 495.00 };

var p2 = new {Name = "Shovel", Price = 26.95 };

p1 = p2 ; //okay

 

typeof 操作符

typeof操作符可用来获取一个类型的System.Type对象。

typeof(type)

typeof(unbound-type-name)

typeof(void)

 

checked和unchecked操作符

checked和unchecked操作符可在整数类型的算术操作和转换时控制溢出检查上下文。

 

默认值表达式

默认值表达式可用来获取类型的默认值。

default(type)

 

转换表达式

转换表达式可用于显式地将一个表达式转换为给定的类型。

 

按位求补操作符

对已~x形式的操作,操作结果都是对x按位求补。 

 

位移操作符

<<和>>操作符可用于进行移位操作。

对于x<<count或x>>count形式的操作,<<操作符将x向左移位,超出x结果范围的高位字节将会被丢弃,而低位空出来的字节位会被设为零。

>>操作符将x向右移位,当x是int或long时,x的低位字节将会被丢弃,剩下的字节位向右移,如果x是非负数,那么高位空出来的字节位就设为零,若x为负数,那么就设为1.当x是uint或ulong时,x的低位字节会被丢弃,剩下的字节位向右移,且高位空出来的字节位会被设为零。

 

is操作符

is操作符可用来动态地检查一个对象的运行时类型是不是和给定的类型兼容。

as操作符

as操作符可用来显示地将一个值转换为给定的引用类型或可空值类型。如果转换无法完成,结果为null。

if(x is Foo){ (Foo)x).DoFoo(); }

不如

Foo foo = x as Foo;

if( foo!=null) { foo.DoFoo(); }

因为is、as和类型转换在.net的底层操作里其实就是一回事,所以只进行一次操作,看结果是不是null,然后继续非null结果的操作比进行操作要好。

 

整数逻辑操作

&操作符将对两个操作数进行按位逻辑与,|操作符将对两个操作数进行按位逻辑或,而^操作符将会对两个操作数进行按位逻辑异或。

 

条件逻辑操作符

&&和||操作符被称为条件操作符。也称作“短路”逻辑操作符。

x&&y操作符和X&y操作符相对应,不过y只有在x不为false的时候才会计算。

x||y操作和x|y操作相对应,不过y只有在x不为true的时候才会计算。

 

Null拼接操作符

??操作符被称为null拼接操作符。

a??b形式的null拼接操作表达式要求a是一个可空值类型或引用类型。如果a非null,那么a??b的结果就是a;否则,其结果为b。操作只有在a为null的情况下才会计算b。

 

条件操作符

?:操作符被称为条件操作符。

b?x:y形式的条件表达式会首先计算条件b。然后,如果b为真,那么计算x并将其结果作为操作的结果。否则,计算y并将其结果作为操作的结果。永远不会同时计算x和y。

条件操作符是右结合的,例如a?b:c?d:e形式表达式会被当成a?b:(c?d:e)来计算。

 

匿名函数表达式

匿名函数是一个表示了“内联的“方法定义的表达式。匿名函数自身并不含有任何值,但是它可以转换为兼容的委托或表达式树类型。

=>操作符的优先级和赋值操作符是一样的,并且它是右结合的操作符。

匿名函数的例子,

x=>x+1   // implicitly typed,expression body

x=>{return x+1; } //implicitly typed,statement body

(int x) => x+1  //explicitly typed,expression body

(int x) => {return x+1;} //explicitly typed,expression body

(x,y) => x * y  //multiple parameters

() => Console.WriteLine() //no paramters

delegate (int x) {return x+1;} //anonymous method expression

delegate {return 1+1; }  //parameter list omitted

 

外部变量

任何作用域包含了lambda表达式或匿名表达式的局部变量、值参数或参数数组,它们都被称为匿名函数的外部变量。在一个类实例函数成员里,this值被认为是值参数,并且是任何包含在函数成员里的匿名函数的外部变量。

当匿名函数引用一个外部变量时,可以说这个外部变量被匿名函数捕捉了。被捕捉的外部变量的生命期被延长。

 

?看不懂的局部变量实例化- -||

using System;

delegate void D();

class Test

{

static D[] F(){

      D[] result = new D[3];

      int x;

      for(int i=0;i<3;i++){

            x = i * 2 + 1;

            result[i] = () => {Console.WriteLine(x);};

      }

      return result;

}

static void Main(){

       foreach(D d in F()) d();

}

 输出全为:5

 

查询表达式

查询表达式提供了一种集成式语言查询语法,这种语法是与sql和xquery等关系与层次查询语言相似的语法。

查询表达式从from子句开始,到select或group子句结束。打头的from子句后面可以跟零到多个from、let、where、join或orderby子句。每个from子句都是一个引用了范围变量的生成器,这里的范围是序列的元素。每个let子句则引入一个表示通过计算前面范围变量所得得值的范围变量。每个where子句都是一个过滤器,负责从结果里排除掉一些项。每个join子句会用原序列里的特定的键去和另一个序列里的键相比较,产生配对。每个orderby子句会根据指定的条件重新排序结果。而最后的select或group子句则指定了结果在范围变量里的形态。最后,into子句可以通过将一个查询的结果作为子查询里的生成器来”拼接“多个查询。

查询表达式翻译成一系列的方法调用:

Where,Select,SelectMany,Join,GroupJoin,OrderBy,OrderByDescending,ThenBy,ThenByDescending,GroupBy,Cast.

?= =||又是一大堆不懂得,查询表达式。

 

透明标识符

特定的翻译会将*代表的透明标识符注入范围变量里。

 

组合赋值

byte b=0;

char ch='';

int i = 0;

b+=1;   //okay

b+=1000;   //error,b = 1000 not permitted

b+=i;   //error,b = i not permitted

b+=(byte)i;  //okay

ch+=1;   //error,ch=1 not permitted

ch+=(char)1;  //okay

 

常量表达式

常量表达式是一个可以在编译期就完整计算出来的表达式。

诸如装箱、拆箱和非null值的隐式转换都不允许出现在常量表达式。

 

Extern 别名

extern别名可以为一个命名空间引入一个作为别名的标识符。被指定的别名命名空间是在程序的源代码之外的,同时它还应用于别名命名空间的嵌套命名空间。

extern alias X;

extern alias Y;

class Test

{

   X::N.A a;

   X::N.B b1;

   X::N.B b2;

   X::N.C c;

}

 

using 指令

using别名指定会为命名空间或类型引入一个新的别名。

using命名空间指令会导入一个命名空间的类型成员。

 

类型声明

类型声明可以是类声明、结构声明、接口声明、枚举声明或者委托声明。