• 这篇文章主要是给零基础想要Unity入门的关于C#编程的一些意见 二、参考文章 unity中的C#编程-零基础(Unity2017) 三、正文 1.什么是C#编程语言? 微软官方出版 2.编程工具(IDE) 3.创建第一个C#代码 4....

    ##一、前言
    这篇文章主要是给零基础想要Unity入门的关于C#编程的一些意见

    ##二、参考文章
    unity中的C#编程-零基础(Unity2017)

    ##三、正文

    ###

    1、支持Unity3D的编程语言

    • C#、JavaScript、UnityScript
      先说C#与JavaScript,因为JavaScript是动态的,肯定不如编译的C#好,但是JavaScript更快捷一点, JavaScript适合个人开发,敏捷快速。c#适合公司开发,规范严谨。

    • 然后说说这个C#和Unity内置的UnityScript
      UnityScript脱胎于.Net平台的第三方语言Boo,差距可能是全方位,立体式的。社区支持,代码维护,甚至是编译出来的CIL代码质量都可能有很大的差距。另外从Unity5.0版本开始就会停止对Boo的文档支持。同时消失的还有从菜单创建Boo脚本的选项“Create Boo Script”。从U3D团队对Boo的态度,也可以窥见和Boo联系密切的UnityScript未来的走势。
      ###

      2、编程工具(IDE)

    • Visual Studio 全系列

    • Visual Studio Code

    • Xamarin Studio
      这个主要是在Mac上使用的IDE,因为在Mac上不能使用VS,MonoDevelop也不能输入中文,就挺难受的,就可以使用XamarinStudio,关于这个IDE使用可以参考这篇文章。http://www.xuanyusong.com/archives/3545

    • MonoDevelop
      Unity3D内置的编辑器,操作感觉一般,但是胜在方便,在安装Unity3D的时候就可以一起安装了,不用另外安装IDE了

    • JetBrains Rider
      一款强大的强大的跨平台C#编辑器,官网https://www.jetbrains.com/zh/dotnet/promo/unity/

    ###

    3、创建C#代码

    1. 在工程目录下右键点击,选择Create->C# Script
      这里写图片描述
    2. 在任意对象上,点击Add Component->New Script
      这里写图片描述
      这个是创建成功后的初始脚本的样子
      这里写图片描述

    ###

    4、场景的保存和脚本的保存

    • 快捷键F2 点击场景名或者脚本名字可以重命名
    • 快捷键Ctrl+S 快速保存场景或者保存脚本
    • 状态栏中显示“*”号的都是未保存的
      这里写图片描述
    • 编辑器意外奔溃,场景未来得及保存?
      这时可以打开工程目录,找到/Temp/_Backupscenes/文件夹,可以看到有后缀名为.backup的文件,将该文件的后缀名改为.unity拖拽到项目视图,即可还原编辑器崩溃前的场景。
      这里写图片描述

    ###

    5、关于日志输出(指控制输出,其中Log有三类:正常、警告、错误输出)

    • print
      是MonoBehaviour的一个成员,必须继承MonoBehaviour才能使用,通过反编译可以看到print的方法为
    public static void print(object message)
    {
    	Debug.Log(message);
    }
    

    说明print方法还是通过Debug.Log实现的,print就是Debug.Log的一个简单封装。

    • Debug.Log
      输出log信息,可以在运行的时候查看关键数据的变化

    • Debug.LogWarning
      输出警告信息

    • Debug.LogError
      输出错误信息

    这里写图片描述
    这里写图片描述

    ###

    6、变量与常量
    因为两个比较相似,就放一块说

    • 变量
      一个变量只不过是一个供程序操作的存储区的名字。在 C# 中,每个变量都有一个特定的类型,类型决定了变量的内存大小和布局。范围内的值可以存储在内存中,可以对变量进行一系列操作。

    • 常量
      常量是固定值,程序执行期间不会改变。常量可以是任何基本数据类型,比如整数常量、浮点常量、字符常量或者字符串常量,还有枚举常量。
      常量可以被当作常规的变量,只是它们的值在定义后不能被修改。

    • 变量的类型

    类型 举例
    整数类型 sbyte、byte、short、ushort、int、uint、long、ulong 和 char
    浮点型 float 和 double
    十进制类型 decimal
    布尔类型 true 或 false 值,指定的值
    空类型 可为空值的数据类型
    其他变量类型 枚举enum,引用类型变量class,结构体类型struct
    - 变量的定义
    int i, j, k;
    char c, ch;
    float f, salary;
    double d;
    
    • 变量的初始化
    int d = 3, f = 5;    /* 初始化 d 和 f. */
    byte z = 22;         /* 初始化 z. */
    double pi = 3.14159; /* 声明 pi 的近似值 */
    char x = 'x';        /* 变量 x 的值为 'x' */
    

    实例

    namespace VariableDefinition
    {
        class Program
        {
            static void Main(string[] args)
            {
                short a;
                int b ;
                double c;
    
                /* 实际初始化 */
                a = 10;
                b = 20;
                c = a + b;
                Console.WriteLine("a = {0}, b = {1}, c = {2}", a, b, c);
                Console.ReadLine();
            }
        }
    }
    

    编译执行结果:

    a = 10, b = 20, c = 30
    
    • 常量的定义
     const double pi = 3.14159; // 常量声明
    
    • 常量的类型
      #####整数常量
      整数常量可以是十进制、八进制或十六进制的常量。前缀指定基数:0x 或 0X 表示十六进制,0 表示八进制,没有前缀则表示十进制。

    整数常量也可以有后缀,可以是 U 和 L 的组合,其中,U 和 L 分别表示 unsigned 和 long。后缀可以是大写或者小写,多个后缀以任意顺序进行组合。

    212         /* 合法 */
    215u        /* 合法 */
    0xFeeL      /* 合法 */
    078         /* 非法:8 不是一个八进制数字 */
    032UU       /* 非法:不能重复后缀 */
    85         /* 十进制 */
    0213       /* 八进制 */
    0x4b       /* 十六进制 */
    30         /* int */
    30u        /* 无符号 int */
    30l        /* long */
    30ul       /* 无符号 long */
    

    #####浮点常量
    一个浮点常量是由整数部分、小数点、小数部分和指数部分组成。您可以使用小数形式或者指数形式来表示浮点常量。
    这里有一些浮点常量的实例:

    3.14159       /* 合法 */
    314159E-5L    /* 合法 */
    510E          /* 非法:不完全指数 */
    210f          /* 非法:没有小数或指数 */
    .e55          /* 非法:缺少整数或小数 */
    

    使用小数形式表示时,必须包含小数点、指数或同时包含两者。使用指数形式表示时,必须包含整数部分、小数部分或同时包含两者。有符号的指数是用 e 或 E 表示的。

    #####字符常量
    字符常量是括在单引号里,例如,‘x’,且可存储在一个简单的字符类型变量中。一个字符常量可以是一个普通字符(例如 ‘x’)、一个转义序列(例如 ‘\t’)或者一个通用字符(例如 ‘\u02C0’)。

    在 C# 中有一些特定的字符,当它们的前面带有反斜杠时有特殊的意义,可用于表示换行符(\n)或制表符 tab(\t)。在这里,列出一些转义序列码:

    转义序列含义
    \\\ 字符
    \'' 字符
    \"" 字符
    \?? 字符
    \aAlert 或 bell
    \b退格键(Backspace)
    \f换页符(Form feed)
    \n换行符(Newline)
    \r回车
    \t水平制表符 tab
    \v垂直制表符 tab
    \ooo一到三位的八进制数
    \xhh . . .一个或多个数字的十六进制数
    实例:
    namespace EscapeChar
    {
        class Program
        {
            static void Main(string[] args)
            {
                Console.WriteLine("Hello\tWorld\n\n");
                Console.ReadLine();
            }
        }
    }
    

    编译执行结果:

    Hello   World
    

    #####字符串常量
    字符串常量是括在双引号 “” 里,或者是括在 @"" 里。字符串常量包含的字符与字符常量相似,可以是:普通字符、转义序列和通用字符

    使用字符串常量时,可以把一个很长的行拆成多个行,可以使用空格分隔各个部分。

    这里是一些字符串常量的实例。下面所列的各种形式表示相同的字符串。

    string a = "hello, world";                  // hello, world
    string b = @"hello, world";               // hello, world
    string c = "hello \t world";               // hello     world
    string d = @"hello \t world";               // hello \t world
    string e = "Joe said \"Hello\" to me";      // Joe said "Hello" to me
    string f = @"Joe said ""Hello"" to me";   // Joe said "Hello" to me
    string g = "\\\\server\\share\\file.txt";   // \\server\share\file.txt
    string h = @"\\server\share\file.txt";      // \\server\share\file.txt
    string i = "one\r\ntwo\r\nthree";
    string j = @"one
    two
    three";
    

    实例:

    using System;
    
    namespace DeclaringConstants
    {
        class Program
        {
            static void Main(string[] args)
            {
                const double pi = 3.14159; // 常量声明
                double r;
                Console.WriteLine("Enter Radius: ");
                r = Convert.ToDouble(Console.ReadLine());
                double areaCircle = pi * r * r;
                Console.WriteLine("Radius: {0}, Area: {1}", r, areaCircle);
                Console.ReadLine();
            }
        }
    }
    

    编译执行结果:

    Enter Radius: 
    3
    Radius: 3, Area: 28.27431
    

    ###

    7、方法的定义和调用

    • 方法的定义
      语法如下:
    <Access Specifier> <Return Type> <Method Name>(Parameter List)
    {
       Method Body
    }
    

    下面是方法的各个元素:

    • Access Specifier:访问修饰符,这个决定了变量或方法对于另一个类的可见性。
    • Return type:返回类型,一个方法可以返回一个值。返回类型是方法返回的值的数据类型。如果方法不返回任何值,则返回类型为 void
    • Method name:方法名称,是一个唯一的标识符,且是大小写敏感的。它不能与类中声明的其他标识符相同。
    • Parameter list:参数列表,使用圆括号括起来,该参数是用来传递和接收方法的数据。参数列表是指方法的参数类型、顺序和数量。参数是可选的,也就是说,一个方法可能不包含参数。
    • Method body:方法主体,包含了完成任务所需的指令集。
    实例: 下面的代码片段显示一个函数 FindMax,它接受两个整数值,并返回两个中的较大值。它有 public 访问修饰符,所以它可以使用类的实例从类的外部进行访问。 ``` class NumberManipulator { public int FindMax(int num1, int num2) { /* 局部变量声明 */ int result;
      if (num1 > num2)
         result = num1;
      else
         result = num2;
    
      return result;
    

    }

    }

    - 方法的调用
    
    

    using System;

    namespace CalculatorApplication
    {
    class NumberManipulator
    {
    public int FindMax(int num1, int num2)
    {
    /* 局部变量声明 */
    int result;

         if (num1 > num2)
            result = num1;
         else
            result = num2;
    
         return result;
      }
      static void Main(string[] args)
      {
         /* 局部变量定义 */
         int a = 100;
         int b = 200;
         int ret;
         NumberManipulator n = new NumberManipulator();
    
         //调用 FindMax 方法
         ret = n.FindMax(a, b);
         Console.WriteLine("最大值是: {0}", ret );
         Console.ReadLine();
      }
    

    }
    }

    编译执行后的结果:
    
    

    最大值是: 200

    您也可以使用类的实例从另一个类中调用其他类的公有方法。例如,方法 FindMax 属于 NumberManipulator 类,您可以从另一个类 Test 中调用它。
    
    

    using System;

    namespace CalculatorApplication
    {
    class NumberManipulator
    {
    public int FindMax(int num1, int num2)
    {
    /* 局部变量声明 */
    int result;

            if (num1 > num2)
                result = num1;
            else
                result = num2;
    
            return result;
        }
    }
    class Test
    {
        static void Main(string[] args)
        {
            /* 局部变量定义 */
            int a = 100;
            int b = 200;
            int ret;
            NumberManipulator n = new NumberManipulator();
            //调用 FindMax 方法
            ret = n.FindMax(a, b);
            Console.WriteLine("最大值是: {0}", ret );
            Console.ReadLine();
    
        }
    }
    

    }

    编译执行的结果:
    
    

    最大值是: 200

    
    - 递归方法的调用
    一个方法可以自我调用。这就是所谓的 递归。下面的实例使用递归函数计算一个数的阶乘:
    
    

    using System;

    namespace CalculatorApplication
    {
    class NumberManipulator
    {
    public int factorial(int num)
    {
    /* 局部变量定义 */
    int result;

            if (num == 1)
            {
                return 1;
            }
            else
            {
                result = factorial(num - 1) * num;
                return result;
            }
        }
    
        static void Main(string[] args)
        {
            NumberManipulator n = new NumberManipulator();
            //调用 factorial 方法
            Console.WriteLine("6 的阶乘是: {0}", n.factorial(6));
            Console.WriteLine("7 的阶乘是: {0}", n.factorial(7));
            Console.WriteLine("8 的阶乘是: {0}", n.factorial(8));
            Console.ReadLine();
    
        }
    }
    

    }

    编译执行后的结果:
    
    

    6 的阶乘是: 720
    7 的阶乘是: 5040
    8 的阶乘是: 40320

    -  参数传递
    当调用带有参数的方法时,您需要向方法传递参数。在 C# 中,有三种向方法传递参数的方式:
    <table>
    <tr><th>方式</th><th>描述</th></tr>
    <tr><td>值参数</td><td>这种方式复制参数的实际值给函数的形式参数,实参和形参使用的是两个不同内存中的值。在这种情况下,当形参的值发生改变时,不会影响实参的值,从而保证了实参数据的安全。</td> </tr>
    <tr><td>引用参数</td><td>这种方式复制参数的内存位置的引用给形式参数。这意味着,当形参的值发生改变时,同时也改变实参的值。</td> </tr>
    <tr><td>输出参数</td><td>这种方式可以返回多个值。</td> </tr>
    </table>
    - 按值参数传递
    
    这是参数传递的默认方式。在这种方式下,当调用一个方法时,会为每个值参数创建一个新的存储位置。
    实际参数的值会复制给形参,实参和形参使用的是两个不同内存中的值。所以,当形参的值发生改变时,不会影响实参的值,从而保证了实参数据的安全。下面的实例演示了这个概念:
    
    

    using System;
    namespace CalculatorApplication
    {
    class NumberManipulator
    {
    public void swap(int x, int y)
    {
    int temp;

         temp = x; /* 保存 x 的值 */
         x = y;    /* 把 y 赋值给 x */
         y = temp; /* 把 temp 赋值给 y */
      }
      
      static void Main(string[] args)
      {
         NumberManipulator n = new NumberManipulator();
         /* 局部变量定义 */
         int a = 100;
         int b = 200;
         
         Console.WriteLine("在交换之前,a 的值: {0}", a);
         Console.WriteLine("在交换之前,b 的值: {0}", b);
         
         /* 调用函数来交换值 */
         n.swap(a, b);
         
         Console.WriteLine("在交换之后,a 的值: {0}", a);
         Console.WriteLine("在交换之后,b 的值: {0}", b);
         
         Console.ReadLine();
      }
    

    }
    }

    
    编译执行后的结果:
    
    

    在交换之前,a 的值:100
    在交换之前,b 的值:200
    在交换之后,a 的值:200
    在交换之后,b 的值:100

    结果表明,swap 函数内的值改变了,且这个改变可以在 Main 函数中反映出来。
    
    - 按输出传递参数
    
    return 语句可用于只从函数中返回一个值。但是,可以使用 输出参数 来从函数中返回两个值。输出参数会把方法输出的数据赋给自己,其他方面与引用参数相似。
    
    下面的实例演示了这点:
    
    

    using System;

    namespace CalculatorApplication
    {
    class NumberManipulator
    {
    public void getValue(out int x )
    {
    int temp = 5;
    x = temp;
    }

      static void Main(string[] args)
      {
         NumberManipulator n = new NumberManipulator();
         /* 局部变量定义 */
         int a = 100;
         
         Console.WriteLine("在方法调用之前,a 的值: {0}", a);
         
         /* 调用函数来获取值 */
         n.getValue(out a);
    
         Console.WriteLine("在方法调用之后,a 的值: {0}", a);
         Console.ReadLine();
    
      }
    

    }
    }

    编译执行后的结果:
    
    

    在方法调用之前,a 的值: 100
    在方法调用之后,a 的值: 5

    提供给输出参数的变量不需要赋值。当需要从一个参数没有指定初始值的方法中返回值时,输出参数特别有用。请看下面的实例,来理解这一点
    
    

    using System;

    namespace CalculatorApplication
    {
    class NumberManipulator
    {
    public void getValues(out int x, out int y )
    {
    Console.WriteLine("请输入第一个值: ");
    x = Convert.ToInt32(Console.ReadLine());
    Console.WriteLine("请输入第二个值: ");
    y = Convert.ToInt32(Console.ReadLine());
    }

      static void Main(string[] args)
      {
         NumberManipulator n = new NumberManipulator();
         /* 局部变量定义 */
         int a , b;
         
         /* 调用函数来获取值 */
         n.getValues(out a, out b);
    
         Console.WriteLine("在方法调用之后,a 的值: {0}", a);
         Console.WriteLine("在方法调用之后,b 的值: {0}", b);
         Console.ReadLine();
      }
    

    }
    }

    编译执行后的结果:
    
    

    请输入第一个值:
    7
    请输入第二个值:
    8
    在方法调用之后,a 的值: 7
    在方法调用之后,b 的值: 8

    
    ###<h4 id="8">8、方法中的参数,返回值
    - 方法中参数的类型有三种
    
    #####in型参数
    
    int 型参数通过值传递的方式将数值传入方法中,即我们在Java中常见的方法。
    #####ref型参数
    
    该种类型的参数传递变量地址给方法(引用传递),传递前变量必须初始化。
    
    该类型与out型的区别在与:
    
     1).ref 型传递变量前,变量必须初始化,否则编译器会报错, 而 out 型则不需要初始化
     2).ref 型传递变量,数值可以传入方法中,而 out 型无法将数据传入方法中。换而言之,ref 型有进有出,out 型只出不进。
    ####out 型参数
    
    与 ref 型类似,仅用于传回结果。
    
    注意:
    
    1). out型数据在方法中必须要赋值,否则编译器会报错。
    
    eg:如下图若将代码中的sum1方法的方法体
    
    改为 a+=b; 则编译器会报错。原因:out 型只出不进,在没给 a 赋值前是不能使用的
    
    改为 b+=b+2; 编译器也会报错。原因:out 型数据在方法中必须要赋值。
    
    2). 重载方法时若两个方法的区别仅限于一个参数类型为ref 另一个方法中为out,编译器会报错
    
    eg:若将下面的代码中将方法名 vsum1 改为 sum(或者将方法名 sum 改为 sum1),编译器会报错。
    
    ######Error 1 Cannot define overloaded method ‘sum’ because it differs from another method only on ref and out 
    原因:参数类型区别仅限于 为 ref 与为 out 时,若重载对编译器而言两者的元数据表示完全相同。
    
    

    class C
    {
    //1. in型参数
    public void sum(int a, int b) {
    a += b;
    }
    //2. ref型参数
    public void sum(ref int a, int b)
    {
    a += b;
    }
    //3. out型参数
    public void sum1(out int a, int b)
    {
    a = b+2;
    }
    public static void Main(string[] args)
    {
    C c = new C();
    int a = 1, b = 2;
    c.sum(a,b);
    Console.WriteLine(“a:{0}”, a);
    a = 1; b = 2;
    c.sum(ref a, b);
    Console.WriteLine(“ref a:{0}”, a);
    a = 1; b = 2;
    c.sum1(out a, b);
    Console.WriteLine(“out a:{0}”, a);
    }
    }

    输出结果:
    ![这里写图片描述](http://www.runoob.com/wp-content/uploads/2018/03/1521532063-6576-.png)
    从代码也可以看出,int 型参数为值传递,所以当将变量 a 传入方法时,变量 a 的值并不会发生变化。而 ref 型参数,由于是引用传递,将变量的值和地址都传入方法中故变量值改变。out 型无法将变量的值传入。但可以将变量的地址传入并为该地址上的变量赋值。 
    
     - 按值传递参数的问题: 
    
    为什么即使在函数内改变了值,值也没有发生任何的变化呢?
    
    

    在交换之前,a 的值:100
    在交换之前,b 的值:200
    在交换之后,a 的值:100
    在交换之后,b 的值:200

    解析:
    
    调用一个方法时相当于复制一个对象到新分配的内存中,方法完毕对象也就del了,两个变量是不同的两个地址,a只不过是栈里的临时变量而已,和主函数里的变量没有关系,因此不会改变b的值。
    
    而指针和引用直接访问内存地址,故直接修改所指向的对象,两者指向同一变量。
    ###<h4 id="9">9、类的定义
    定义一个类的时候相当于定义了一个数据类型的蓝图,本身没有定义任何数据,但是它定义了类的名称意味着什么,就是说可以在这个类由什么对象组成,以及在这个对象上可以执行什么操作
    
    - 累的定义
    关键字 class 后跟类的名称,类的主体包含在一段花括号中{},下面是类的一般形式
    
    

    class class_name
    {
    // member variables
    variable1;
    variable2;

    variableN;
    // member methods
    method1(parameter_list)
    {
    // method body
    }
    method2(parameter_list)
    {
    // method body
    }

    methodN(parameter_list)
    {
    // method body
    }
    }

    
    <p>请注意:</p>
    <ul class="list">
    <li>访问标识符 &lt;access specifier&gt; 指定了对类及其成员的访问规则。如果没有指定,则使用默认的访问标识符。类的默认访问标识符是 <b>internal</b>,成员的默认访问标识符是 <b>private</b>。</li>
    <li>数据类型 &lt;data type&gt; 指定了变量的类型,返回类型 &lt;return type&gt; 指定了返回的方法返回的数据类型。</li>
    <li>如果要访问类的成员,你要使用点(.)运算符。</li>
    <li>点运算符链接了对象的名称和成员的名称。</li>
    </ul>
    <p>下面的实例说明了目前为止所讨论的概念:</p>
    
    

    using System;
    namespace BoxApplication
    {
    class Box
    {
    public double length; // 长度
    public double breadth; // 宽度
    public double height; // 高度
    }
    class Boxtester
    {
    static void Main(string[] args)
    {
    Box Box1 = new Box(); // 声明 Box1,类型为 Box
    Box Box2 = new Box(); // 声明 Box2,类型为 Box
    double volume = 0.0; // 体积

            // Box1 详述
            Box1.height = 5.0;
            Box1.length = 6.0;
            Box1.breadth = 7.0;
    
            // Box2 详述
            Box2.height = 10.0;
            Box2.length = 12.0;
            Box2.breadth = 13.0;
           
            // Box1 的体积
            volume = Box1.height * Box1.length * Box1.breadth;
            Console.WriteLine("Box1 的体积: {0}",  volume);
    
            // Box2 的体积
            volume = Box2.height * Box2.length * Box2.breadth;
            Console.WriteLine("Box2 的体积: {0}", volume);
            Console.ReadKey();
        }
    }
    

    }

    编译执行后的结果:
    
    

    Box1 的体积: 210
    Box2 的体积: 1560

    
    -  成员函数和封装
    类的成员函数是一个在类定义中有它的定义或原型的函数,就像其他变量一样。作为类的一个成员,它能在类的任何对象上操作,且能访问该对象的类的所有成员。
    
    成员变量是对象的属性(从设计角度),且它们保持私有来实现封装。这些变量只能使用公共成员函数来访问。
    
    让我们使用上面的概念来设置和获取一个类中不同的类成员的值:
    
    

    using System;
    namespace BoxApplication
    {
    class Box
    {
    private double length; // 长度
    private double breadth; // 宽度
    private double height; // 高度
    public void setLength( double len )
    {
    length = len;
    }

       public void setBreadth( double bre )
       {
            breadth = bre;
       }
    
       public void setHeight( double hei )
       {
            height = hei;
       }
       public double getVolume()
       {
           return length * breadth * height;
       }
    }
    class Boxtester
    {
        static void Main(string[] args)
        {
            Box Box1 = new Box();        // 声明 Box1,类型为 Box
            Box Box2 = new Box();         // 声明 Box2,类型为 Box
            double volume;                 // 体积
    
    
            // Box1 详述
            Box1.setLength(6.0);
            Box1.setBreadth(7.0);
            Box1.setHeight(5.0);
    
            // Box2 详述
            Box2.setLength(12.0);
            Box2.setBreadth(13.0);
            Box2.setHeight(10.0);
       
            // Box1 的体积
            volume = Box1.getVolume();
            Console.WriteLine("Box1 的体积: {0}" ,volume);
    
            // Box2 的体积
            volume = Box2.getVolume();
            Console.WriteLine("Box2 的体积: {0}", volume);
           
            Console.ReadKey();
        }
    }
    

    }

    编译执行后的结果:
    
    

    Box1 的体积: 210
    Box2 的体积: 1560

    - C# 中的构造函数
    类的 构造函数 是类的一个特殊的成员函数,当创建类的新对象时执行。
    
    构造函数的名称与类的名称完全相同,它没有任何返回类型。
    
    下面的实例说明了构造函数的概念:
    
    

    using System;
    namespace LineApplication
    {
    class Line
    {
    private double length; // 线条的长度
    public Line()
    {
    Console.WriteLine(“对象已创建”);
    }

      public void setLength( double len )
      {
         length = len;
      }
      public double getLength()
      {
         return length;
      }
    
      static void Main(string[] args)
      {
         Line line = new Line();    
         // 设置线条长度
         line.setLength(6.0);
         Console.WriteLine("线条的长度: {0}", line.getLength());
         Console.ReadKey();
      }
    

    }
    }

    编译执行后的结果:
    
    

    对象已创建
    线条的长度: 6

    默认的构造函数没有任何参数。但是如果你需要一个带有参数的构造函数可以有参数,这种构造函数叫做参数化构造函数。这种技术可以帮助你在创建对象的同时给对象赋初始值,具体请看下面实例:
    
    

    using System;
    namespace LineApplication
    {
    class Line
    {
    private double length; // 线条的长度
    public Line(double len) // 参数化构造函数
    {
    Console.WriteLine(“对象已创建,length = {0}”, len);
    length = len;
    }

      public void setLength( double len )
      {
         length = len;
      }
      public double getLength()
      {
         return length;
      }
    
      static void Main(string[] args)
      {
         Line line = new Line(10.0);
         Console.WriteLine("线条的长度: {0}", line.getLength()); 
         // 设置线条长度
         line.setLength(6.0);
         Console.WriteLine("线条的长度: {0}", line.getLength()); 
         Console.ReadKey();
      }
    

    }
    }

    当上面的代码被编译和执行时,它会产生下列结果:
    
    

    对象已创建,length = 10
    线条的长度: 10
    线条的长度: 6

    - C# 中的析构函数
    类的 析构函数 是类的一个特殊的成员函数,当类的对象超出范围时执行。
    
    析构函数的名称是在类的名称前加上一个波浪形(~)作为前缀,它不返回值,也不带任何参数。
    
    析构函数用于在结束程序(比如关闭文件、释放内存等)之前释放资源。析构函数不能继承或重载。
    
    下面的实例说明了析构函数的概念:
    
    

    using System;
    namespace LineApplication
    {
    class Line
    {
    private double length; // 线条的长度
    public Line() // 构造函数
    {
    Console.WriteLine(“对象已创建”);
    }
    ~Line() //析构函数
    {
    Console.WriteLine(“对象已删除”);
    }

      public void setLength( double len )
      {
         length = len;
      }
      public double getLength()
      {
         return length;
      }
    
      static void Main(string[] args)
      {
         Line line = new Line();
         // 设置线条长度
         line.setLength(6.0);
         Console.WriteLine("线条的长度: {0}", line.getLength());           
      }
    

    }
    }

    当上面的代码被编译和执行时,它会产生下列结果:
    > 对象已创建
    线条的长度: 6
    对象已删除
    
    - C# 类的静态成员
    我们可以使用 static 关键字把类成员定义为静态的。当我们声明一个类成员为静态时,意味着无论有多少个类的对象被创建,只会有一个该静态成员的副本。
    
    关键字 static 意味着类中只有一个该成员的实例。静态变量用于定义常量,因为它们的值可以通过直接调用类而不需要创建类的实例来获取。静态变量可在成员函数或类的定义外部进行初始化。你也可以在类的定义内部初始化静态变量。
    
    下面的实例演示了静态变量的用法:
    
    

    using System;
    namespace StaticVarApplication
    {
    class StaticVar
    {
    public static int num;
    public void count()
    {
    num++;
    }
    public int getNum()
    {
    return num;
    }
    }
    class StaticTester
    {
    static void Main(string[] args)
    {
    StaticVar s1 = new StaticVar();
    StaticVar s2 = new StaticVar();
    s1.count();
    s1.count();
    s1.count();
    s2.count();
    s2.count();
    s2.count();
    Console.WriteLine(“s1 的变量 num: {0}”, s1.getNum());
    Console.WriteLine(“s2 的变量 num: {0}”, s2.getNum());
    Console.ReadKey();
    }
    }
    }

    编译执行后的结果:
    > s1 的变量 num: 6
    s2 的变量 num: 6
    
    你也可以把一个成员函数声明为 static。这样的函数只能访问静态变量。静态函数在对象被创建之前就已经存在。下面的实例演示了静态函数的用法:
    
    

    using System;
    namespace StaticVarApplication
    {
    class StaticVar
    {
    public static int num;
    public void count()
    {
    num++;
    }
    public static int getNum()
    {
    return num;
    }
    }
    class StaticTester
    {
    static void Main(string[] args)
    {
    StaticVar s = new StaticVar();
    s.count();
    s.count();
    s.count();
    Console.WriteLine(“变量 num: {0}”, StaticVar.getNum());
    Console.ReadKey();
    }
    }
    }

    编译执行后的结果:
    > 变量 num: 3
    
    ###<h4 id="10">10、枚举类型:
    枚举是一组命名整型常量。枚举类型是使用 enum 关键字声明的。
    
    C# 枚举是值类型。换句话说,枚举包含自己的值,且不能继承或传递继承。
    - 声明
    语法
    
    

    enum <enum_name>
    {
    enumeration list
    };

    
    

    enum RoleType{ //关键字+命名
    apple, //对象,以逗号分隔
    pen,
    erase
    }
    RoleType rt = RoleType.apple; //使用枚举

    
    <p>其中,</p>
    <ul class="list">
    <li><i>enum_name</i> 指定枚举的类型名称。</li>
    <li><i>enumeration list</i> 是一个用逗号分隔的标识符列表。</li>
    </ul>
    <p>枚举列表中的每个符号代表一个整数值,一个比它前面的符号大的整数值。默认情况下,第一个枚举符号的值是 0.例如:</p>
    <pre class="prettyprint">
    enum Days { Sun, Mon, tue, Wed, thu, Fri, Sat };
    </pre>
    实例:
    下面的实例演示了枚举变量的用法:
    
    

    using System;
    namespace EnumApplication
    {
    class EnumProgram
    {
    enum Days { Sun, Mon, tue, Wed, thu, Fri, Sat };

      static void Main(string[] args)
      {
         int WeekdayStart = (int)Days.Mon;
         int WeekdayEnd = (int)Days.Fri;
         Console.WriteLine("Monday: {0}", WeekdayStart);
         Console.WriteLine("Friday: {0}", WeekdayEnd);
         Console.ReadKey();
      }
    

    }
    }

    编译执行后的结果:
    > Monday: 1
    Friday: 5
    
    ###<h4 id="11">11、组件的创建与使用
    ![这里写图片描述](https://img-blog.csdn.net/20180814113753576?watermark/2/text/aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3E3NjQ0MjQ1Njc=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70)
    
    展开全文
  • 1.Draw Call本身的含义就是CPU调用图像编程接口,像OpenGL或者 DirectX。简单来说,Draw Call就是由CPU发起的一个命令,添加到一个命令缓冲区,当GPU完成了上一次的渲染任务之后,会从命令缓冲区的命令队列中再取出...

    1.Draw Call本身的含义就是CPU调用图像编程接口,像OpenGL或者 DirectX。简单来说,Draw Call就是由CPU发起的一个命令,添加到一个命令缓冲区,当GPU完成了上一次的渲染任务之后,会从命令缓冲区的命令队列中再取出一个并执行。CPU每次调用Draw Call之前需要向GPU发送很多内容,包括状态,数据,命令等等。而GPU的渲染能力是很强的,渲染200个网格和2000个网格没什么区别。所以当CPU调用的Draw Call过多时,会造成比较大的性能消耗,命令缓冲区中的命令已经被GPU执行完了,CPU还在准备下一个Draw Call,而GPU处于等待状态,就影响了效率了。

    2.渲染流水线中的3个概念阶段:

    (1).应用阶段(由开发者设置每个模型的渲染状态,材质,纹理,shader等,输出渲染图元,CPU向GPU发起DrawCall命令)

    (2).几何阶段(顶点坐标变换到屏幕空间中,输入的渲染图元->屏幕空间的二维顶点坐标、每个顶点对应深度、着色等信息)

    (3).光栅化阶段(根据上个阶段传递的数据产生屏幕上的像素,并渲染出最终图像)

    3.单例模式:确保一个类只有一个实例,在系统中把这个实例当作一个工具来使用,当需要频繁创建销毁某个对象时,单例模式能提高性能。缺点:(1).不能继承(登记式模式可以被继承),难以拓展。(2).滥用单例模式会提高代码之间的耦合度。

    观察者模式是为了定义对象间的一种一对多的依赖关系,以便于当一个对象的状态改变时,其他依赖于它的对象会被自动告知并更新。是一种松耦合的设计模式。

    4. 引用类型继承自Systerm.Object类,值类型则是继承Systerm.Object的子类Systerm.ValueType类。值类型的数据存储在内存的栈中;引用类型的数据存储在内存的堆(new 出来的对象)中,在栈中存放数据引用或地址。值类型存取速度快,引用类型存取速度慢。值类型表示实际数据,引用类型表示指向存储在内存堆中的数据的指针或引用

    5. 指针其实是一个整型的变量,用来存储其它变量的地址,地址表示数据在内存中的位置

    6.string是String类的别名,字符串的本质是一个char数组,string类的内部定义了一个char类型的索引器,所有string可以用[ ]去读取内部的每一个字符,对string对象进行修改时,其实要重新分配内存,创建一个新的string对象,所以当需要频繁修改string的值的时候,会使GC频繁的去回收内存,性能消耗比较大;StringBuilder 实例化时可以指定一个容纳量,当修改的值没有超过容纳量的时候不会重新分配内存.

    7.装箱是指将值类型转换为引用类型的过程,首先从托管堆中为新生成的引用对象分配内存,然后将值类型的数据拷贝到刚刚分配的内存中,返回托管堆中新分配对象的地址。拆箱是指引用类型转换为值类型的过程,首先获取托管堆中属于值类型那部分字段的地址,这一步是严格意义上的拆箱,然后将引用对象中的值拷贝到位于线程堆栈上的值类型实例中。

    8.序列化是指对象的状态信息转换为可以存储或传输的形式的过程。序列化方式一般有两种,二进制和XML。

    9.委托上注册了多个函数后,如果委托和事件有返回值,那么调用委托和事件时,返回的将是最后一个注册函数的返回值 . ActionFunc的本质是委托,系统内部定义的,前者为Void类型不具有返回值,后者具有返回值。(当脚本销毁时一定要释放方法)

    10.事件对象的调用只能在声明事件的类的内部调用(安全),在类的外部只能对事件进行” +=”或者” -=”操作,相当于被限制了权限的委托。

    11.二分查找

    public static int binSearch(int[] array,int start,int end,int key)
    {
                int mid = (end - start) / 2 + start;
                if (array[mid] == key) return key;
                if (start >= end) return -1;
                else if (key > array[mid]) return binSearch(array, mid+1, end, key);
                else if (key < array[mid]) return binSearch(array, start, mid-1, key);
                return -1;
    }

    12.快速排序

    public static void QuickSort(int[] array, int start, int end)
            {
                if (start > end) return;    
                int index = Partition(array, start, end);
                QuickSort(array, start, index - 1);
                QuickSort(array, index + 1, end);
            }
    
            private static int Partition(int[] array, int start, int end)
            {
                int pivot = array[start];
                while (start < end)
                {
                    while (start < end && array[end] >= pivot) end--;
                    array[start] = array[end];
                    while (start < end && array[start] <= pivot) start++;
                    array[end] = array[start];
                }
                array[start] = pivot;
                return start;
            }

    13.反射可以通过程序集,类型,类型实例获取该程序集内所有类型+属性+属性类型,方法,方法的访问类型,参数和返回值等等

    14.Var用于在声明变量时,无法确定数据类型时使用。

    (1).必须在定义时初始化

    (2).一但初始化完成,就不能再给变量赋与初始化值类型不同的值

    (3). 必须是局部变量

    15.时间复杂度用于衡量一个算法执行所耗费的时间,空间复杂度用于衡量算法运行过程中临时占用的内存大小

    16. GC即垃圾回收,自动管理内存,当我们创建对象时,系统会为对象分配一个内存空间,GC就开始监控这个对象的地址、大小以及使用情况,当对象没有被任何引用指向,或者不再会被用到的时候,GC就把对象当作垃圾回收,释放内存 。但是GC并不是实时性的,GC并不是能释放所有的资源。它不能自动释放非托管资源(正则表达式,数据库连接,定时器)。减少new产生对象的次数 来减少GC。

    17.ref 关键字使参数按引用传递。使用 ref 参数,则方法定义和调用方法都必须显式使用 ref 关键字。ref指定的参数在进入方法前必须初始化。out指定的参数在进入函数时会清空自己,必须在函数内部赋初值。如果一个方法采用 ref 参数,而另一个方法采用 out 参数,无法重载这两个方法。

    18.数据结构

    哈希表又称散列表,是根据关键码值(Key value)而直接进行访问的数据结构,通过把关键码值映射到表中一个位置来访问记录,以加快查找的速度。这个映射函数叫做散列函数,存放记录的数组叫做散列表,值类型。当一个HashTable被占用一大半的时候我们通过计算散列值取得的地址值可能会重复指向同一地址,这就造成哈希冲突。

    字典是引用类型,一种变种的哈希表,实现方式与哈希表相同,但是在声明的时候必须指定key和item的类型,它采用一种分离链接散列表的数据结构来解决哈希冲突的问题。

    数组是最简单的数据结构. 1.顺序存储,数据存储在连续的内存上。2.数组的内容都是相同类型。3.数组可以直接通过下标访问,故索引时间非常快而且是固定的,与数组大小内容无关。

    ArrayList不必在声明ArrayList时指定它的长度, 可以存储不同类型的元素, 不是类型安全的。因为把不同的类型都当做Object来做处理,很有可能会在使用ArrayList时发生类型不匹配的情况。会导致大量拆箱,装箱操作

    List内部使用数组来实现,即确保了类型安全,也取消了装箱和拆箱的操作,而且长度可以灵活变化。

    链表LinkedList不是连续存储,而是靠各对象的指针所决定,所以向链表中插入删除元素的时候不用调整容量,但是访问某个元素的时候只能从头节点开始遍历。

    19.Unity3D 协程是在主线程中执行的,在Update()方法之后,LateUpdate()方法之前调用。Unity在每一帧都会去处理对象上的协程,遇到yield return 语句会挂起,直到条件满足才会被唤醒继续执行后面的代码。使用StartCoroutine和StopCoroutine()方法来开启,结束协程,也可以在启动协程时定义一个Coroutine变量去保存协程,然后调用stop方法去停止协程。

    20.Unity3D生命周期表

    21.动态语言是在运行时才确定数据类型的语言。变量使用之前不需要类型声明,通常变量的类型是被赋值的那个值的类型。 例如PHP、ASP、Python、JavaScript等等。

    静态语言是在编译时变量的数据类型就可确定的语言,多数静态类型语言要求在使用变量之前必须声明数据类型。 例如:C、C++、Golang、Java、C#等,优势:由于类型的强制声明,使得IDE有很强的代码感知能力,所以在实现复杂的业务逻辑、开发大型商业系统、以及那些生命周期很长的应用中,依托IDE对系统的开发很有保障;由于静态语言相对比较封闭,使得第三方开发包对代码的侵害性可以降到最低。

    解释型语言是不需要在运行前进行编译成机器语言,而是程序在运行时先翻译成中间代码,再由解释器对中间代码进行解释运行。这样解释型语言每执行一次就要翻译一次,效率比较低。常见的解释型语言包括:Java、C#、PHP、JavaScript、Python、VBScript、Perl、Ruby、MATLAB等;解释型语言的优点:①解释型语言提供了极佳的调试支持;②解释器比编译器容易实现;③中间语言代码的大小比编译型可执行代码小很多。例如,C/C++的.exe文件要比同样功能的Java的.class文件大很多;④可移植性好,只要有解释环境,可以在不同的操作系统上运行。比如在解释执行时可以动态改变变量的类型、对程序进行修改以及在程序中插入良好的调试诊断信息等,而将解释器移植到不同的系统上,则程序不用改动就可以在移植了解释器系统上运行;⑤解释型语言也可以保证高度的安全性——这是互联网应用迫切需要的。解释型语言的缺点:①运行需要解释环境,程序严重依赖平台;②运行起来比编译的要慢,占用的资源也要多一些,代码效率低。因为不仅要给用户程序分配空间,解释器本身也占用了宝贵的系统资源;③由于解释型应用的decode-fetch-execute(解码-抓取-执行)的周期,它们比编译型程序慢很多。

    编译型语言是相对于解释型语言存在的,编译型语言首先将源代码编译生成机器语言,再由机器运行机器码(二进制)。

    常见的有:C、C++等,优点:运行速度快,代码效率高,编译后程序不可以修改,保密性好;缺点:①代码需要经过编译方可运行,可移植性差,只能在兼容的操作系统上运行;②安全性不如解释性语言,一个编译型的程序可以访问内存的任何区域,并且可以对你的PC做它想做的任何事情(大部分病毒是使用编译型语言编写的)。

    脚本语言又被称为扩建的语言,或者动态语言,是一种编程语言,用来控制软件应用程序,脚本通常以文本保存,只在被调用时进行解释或编译。脚本语言有:PHP、Python、JavaScript、Lua、Scala、VBScript,ActionScript,MAXScript,ASP,JSP,SQL,Perl,Shell,Ruby,JavaFX,AutoIt等;它是一种解释性的语言,它不象c/c++等可以编译成二进制代码,以可执行文件的形式存在,脚本语言不需要编译,可以直接用,由解释器来负责解释。

    展开全文
  • 概要 撰写原因:当今市面上关于Unity的教程的...针对人群:有任意一门静态编程语言基础的(C||C++||Java||C#),对Unity引擎了解的(文主要讲解C#代码的编写,对于引擎里需要拖控件完成的工作不会有太多涉及) 编

    概要

    撰写原因:当今市面上关于Unity的教程的脚本几乎都是用js来当做教程语言的,因为js比较容易学习,所以如果是新学的朋友建议学js的版本,如有特殊要求需要学习C#写脚本的话那么这系列文章可能会对您有帮助
    针对人群:有任意一门静态编程语言基础的(C||C++||Java||C#),对Unity引擎了解的(文主要讲解C#代码的编写,对于引擎里需要拖控件完成的工作不会有太多涉及)

    编程工具及简介

    Unity里面自动为我们封装了一个适用的编程工具MonoDevelop,在您的下载安装目录里会发现
    就是这个东西,在以后的代码之路上我们会用他编程

    这里简单介绍一下Unity的游戏制作过程,比如我们要创建一个场景“一望无际的大草原上有一群奔腾的...可爱的马,马的品种无所谓了哈=w=”那么我们可以先利用Unity、里面的地形建造工具创建一个草原地形,适当的种些草和树在上面(都是拖控件),然后我们把马的原型(可以是3dmax等建模工具建好的放到上面)这时他们还不会动,我们这是就要通过写脚本来告诉他们怎么动,最后把脚本绑定(attach)到上面就好了

    用C#编程

    这里的数据类型运算符什么的和一般编程语言都差不多就不赘述了(为了加快有一定编程基础人的学习进度,如果没有,C语言是您的最好选择)

    输出输入语句

    打开MonoDevelop来创建一个demo



    然后我们写一段这样的代码

    using System;
    
    namespace Mode01
    {
    	class MainClass
    	{
    		public static void Main (string[] args)
    		{
    			for(;;)
    			{
    				Console.WriteLine (Console.ReadKey().Key);
    			}
    
    		}
    	}
    }

    这里面输出就是
    Console.WriteLine();
    而输入常用三种,第一种是
    (Console.Read();  //用来接收下一个用户从键盘输入的字符
    (Console.ReadKey(); //用来接收用户下一次从键盘输入的字符或功能键
    <pre name="code" class="html">(Console.ReadLine(); //接收下一个字符串
    
    这里面ReadKey()的返回类型比较特殊所以我们获得他的key来输出,就能输出他的值

    如果你是windows的话快捷键是ctrl+f5运行,然后我们输入空格机会出现这样的界面


    类型转换(decimal类型补充)

    decimal可能是我们之前没有见到过的一种数据类型
    /*
    decimal 关键字表示 128 位数据类型。同浮点型相比,decimal 类型具有更高的精度和更小的范围,这使它适合于财务和货币计算。
    */
    类型转化比较简单,如隐式转换:(由精度小到精度大)
    int age = 10;
    double sum = age;
    强制转换:(由精度大到精度小)
    double age = 10.4f;
    float sum = (float)age;
    (字符串to整形)
    string num = "123";
    int n = int.Parse(num);
    int m = Convert.ToInt16(num);

    以下是运行结果








    展开全文
  • using System.Collections;using System.Collections.Generic;using UnityEngine;public class Follow : MonoBehaviour {// Use this for initializationvoid Start () {}// Update is called once per framevoid Up
    using System.Collections;
    using System.Collections.Generic;
    using UnityEngine;
    
    public class Follow : MonoBehaviour {
    
    	// Use this for initialization
    	void Start () {
    		
    	}
    	
    	// Update is called once per frame
    	void Update () {
    		
    	}
    }

    新建一个C#程序,每个C#程序都是一个类,你可以理解为一个工具包,可以输入一些参数,可以输出一些参数,用这个工具你就能获得一定的结果,甚至你可以不用管工具里是什么结构,只要你给它输入数据,它给你想要的结果就可以,你不用理会他里面是多么复杂的运行结构,这就是封装类包的好处,每个项目都可以重复利用这个工具。

    简单的理解一下,类包里有两个基本的状态方法:

    void Start () //只需要初始化允许一次的放到这里面来
    void Update ()//需要过程中每帧都时刻允许的放到这里来
    
    

    
    

    下一步,对外提供一个参数敞口,可入输入场景物体等参数,代码如下:

    using System.Collections;
    using System.Collections.Generic;
    using UnityEngine;
    
    public class Follow : MonoBehaviour {
    
        public GameObject objectToFollow;
    
        // Use this for initialization
        void Start () {
    		
    	}
    	
    	// Update is called once per frame
    	void Update () {
    		
    	}
    }



    保存脚本,回到场景,建立一个物体,把脚本拉给物体,我们看到物体多出了脚本一项,

    脚本项目下面多了一个参数objectToFollow

    可以选择场景的物体作为参数内容



    展开全文
  • Unity3D提供支持三种脚本语言,但是Jscript并非一我们所熟知的那个Jscript,有着很大的不同,所以建议使用优美的C#语言来写脚本,下面是必须掌握的100个C#基本点。 1.C#中使用//(双斜杠)来表明本行的剩余部分代表...

    Unity3D提供支持三种脚本语言,但是Jscript并非一我们所熟知的那个Jscript,有着很大的不同,所以建议使用优美的C#语言来写脚本,下面是必须掌握的100个C#基本点。

    1.C#中使用//(双斜杠)来表明本行的剩余部分代表注释。

    2.C#中语句是按顺序执行的,每条语句以分号结尾。

    3.C#中的函数执行一系列语句的行为,称为语句块---一对大括号中包含0条或多条语句。

    4.一个函数可以通过定义参数来从调用者处接受输入数据,也可以通过定义返回类型来输出数据给调用者。

    5.Main函数定义为执行程序的默认入口点。Main函数可以不返回任何值,也可以返回一个整数给执行程序环境。Main函数也可以不定义任何参数,或者定义一个string数组作为参数(该参数将由传递给执行程序的参数填充)。

    6.Using指令用于使用命名空间。

    7.C#中的标识符以字母或下划线开头,并且大小写敏感。

    8.关键字是由编译器保留的名字,不能用作标识符,下面是C#的关键字列表:

    9.如果想将关键字作为标识符,那么可以加上前缀@以使它合法,例如:class @class。注意@符号并不构成标识符的一部分,所以@myVariable和myVariable是相同的。

    10.一些关键字是上下文相关的,它们不用使用符号@就能够被用作标识符。这些关键字如下:

    11.C#中所有的值都是一个特定类型的实例,一个值的含义,以及一个变量可以拥有的所有可能的值,是由它的类型决定的。

    12.预定义类型(也叫内建类型)是被编译器特别支持的类型,如int、float、bool等。

    13.自定义类型可以包含成员数据和成员函数。需要使用new运算符来创建一个新的自定义类型的实例,当new运算符实例化一个对象后,该对象的构造函数将被调用。构造函数的定义类似于普通函数定义,除了函数名必须和类名相同以及去除了返回类型。

    14.在类型的实例上操作的数据成员和函数成员称为实例成员。而不在类型的实例上,但在类型本身上操作的数据成员和函数成员,必须被标记为static。

    15.public关键字用于显露成员给其他类使用。

    16.C#能够在两个相兼容的类型之间进行转换,转换总是根据一个已存在的值而创建一个新值。转换可以是隐式的或者显式的,隐式的转换自动发生,如int x = 5; long y = x。而显式的转换需要一个映射,如int x = 5; short y = (short)x。一般来说,当编译器能够保证在两个类型之间转换总是成功的,且不丢失任何信息,那么就允许隐式转换,否则的话,就必须使用显式转换。

    17.C#中的类型可以分为值类型和引用类型。值类型包括了大多数内置类型(所有的数值类型(int, float等),char类型,bool类型以及struct与enum类型),引用类型包括了所有的class,array,delegate与interface类型。

    18.一个值类型的变量或常量包含的内容仅仅是一个值,对一个值类型的实例进行赋值总是会拷贝实际数据到该实例。引用类型的变量或常量包含的内容是对一个拥有实际数据的对象的引用,对一个引用类型的实例进行赋值总是会拷贝引用,而不会拷贝拥有实际数据的对象实例,这就允许多个变量引用同一个对象。

    19.一个引用类型的实例可以被设置为null,以表明没有引用任何对象,使用一个为null的实例的成员将产生一个运行时错误。相比之下,一个值类型的实例不能被设置为null。

    20.C#中的预定义类型分类:

    21.C#预定义的数字类型如下:

    22.整型可以使用10进制或16进制标记法,16进制标记法需要加0x前缀,如0x7F。实数可以用10进制或指数标记法,如1e06。

    23.浮点数转换为整数将丢弃小数部分,如果想要进行四舍五入的转换,则可以使用System.Convert类。

    24.需要注意从1个很大的整数转换为浮点数时可能会丢失精度,如int x = 100000001; float f = x; // 此时f = 100000000。

    25.算数运算符(+,-,*,/,%)可以用于所有的数值类型(除了8位和16位的整数类型)。

    26.自增,自减运算符用于使数值类型的变量加1或减1。运算符可以放在变量前(++x)或者变量后(x++),取决于想在表达式计算之前还是之后更新变量的值。

    27.整数除法总是会丢弃余数。如果除以一个值为0的变量将产生一个运行时错误(DivideByZeroException),除以字面值0将产生编译错误。

    28.在运行时,整数类型进行算术运算可能会造成溢出,默认情况下,不会有异常被抛出。C#文档表明对于溢出结果是不可预料的,但是Common Language Runtime(CLR)总是产生一个wraparound行为,也就是说,将最小的int值减1的结果是最大的int值,如int x = int.MinValue; x--; // 此时x = int.MaxValue。

    29.checked运算符通知运行时当溢出时抛出一个OverflowException异常,checked运算符可以用于++, --, -(一元), +, -, *, /以及整数类型之间的显示转换。checked运算符可以用于一个表达式或者一个语句块,如:

    复制代码
    1 int a = 1000000;
    2 int b = 1000000;
    3 int c = checked( a * b );   // 用于表达式
    4 
    5 checked
    6 {
    7     c = a * b;  // 用于语句块
    8 }
    复制代码

    可以使用/checked[+/-]编译选项来检测/不检测程序中所有发生的溢出,如果使用了/checked+来检测程序中所有发生的溢出,而对于某些特定的表达式或语句块又不想使用检测功能,那么可以像checked运算符一样使用unchecked运算符来关闭检测。

    30.C#提供了下面这些位运算符:

    31.8位和16位的整型包括了byte, sbyte, short和ushort,这些类型自身没有算数运算符,所以C#在需要时会将它们转换为更大的类型,这样就会导致如果接收运算结果的是一个较小的整型时,会发生编译错误,如short x = 1, y = 1; short z = x + y; // 此处编译错误。在这种情况下,为了使加法能够执行,x, y会被隐式的转换为int,这意味着结果也是int,而int不能被隐式的转换为short(因为会导致数据丢失),所以,为了使编译通过,我们需要加上显式转换,short z = (short)(x + y);

    32.float和double类有一些常量用于NaN(Not a Number), +∞, -∞, MaxValue, MinValue和Epsilon。当发生除0时将导致结果为无穷值,如:

    。当发生0除0或者无穷减无穷时将导致结果为NaN,如

    。当使用==时,一个NaN值永远不会等于另一个值,即使是另一个NaN值,所以如果要测试一个值是否为NaN,必须使用float.IsNaN或者double.IsNaN函数。当使用Object.Equals函数时,也可以判断两个NaN值是否相等,如object.Equals (0.0/0.0, double.NaN) // true。

    33.float类型适用于科学计算,而decimal类型适用于金融计算或者表示那些人为的值,下面是double类型与decimal类型的区别:

    34.由于float和double类型的数值在内部是以2为基表示的,所以许多以10为基的小数部分字面值无法被精确的表示,如

    ,这就是为什么float和double类型不适用于金融计算的原因。相比之下,由于decimal类型的数值是以10为基表示的,所以这种情况不会出现。

    35.C#的bool类型是一个可以被指定为true或者false的逻辑值。尽管一个bool类型仅需要1位的存储空间,但是运行时将使用1个字节的存储空间,因为这是运行时和处理器可以有效工作的最小单位。所以为了避免空间上的浪费,C#提供了一个BitArray类,该类位于System.Collections命名空间下,旨在只使用1位存储空间表示每个bool类型的值。

    36.对于引用类型,是否相等,默认情况下是取决于所引用的对象是否相同,而不是对象内部的实际数据。因此,一个对象的两个拥有相同数据的实例被认为是不相等的,除非该对象所属的类型重载了==运算符以达到使他们相等的效果。

    37.相等和比较运算符,==, !=, <, >, <=, >=,可以用于所有的数值类型,但是用于实数时需要谨慎小心(参见34)。这些运算符也可以用于enum(枚举)类型成员,比较的是他们代表的整型数值。

    38.&&和||运算符用于测试条件与和条件或,他们是短路运算符,也就是说,当前一个表达式不满足时,后续表达式将不再计算,如if ( a && b ),如果表达式a为假,那么表达式b将不会计算,这是十分有用的,例如:if (sb != null && sb.Length > 0),可以避免抛出NullReferenceException异常。&和|运算符也可用于测试条件与和条件或,但是他们不是短路运算符,所以,一般很少用于条件测试。还有一个三目运算符,形式如q ? a : b,当q为真时,a将被计算,否则b将被计算。

    39.C#的char类型代表一个Unicode字符,占用2个字节。一个char的字面表示在一个单引号中,如char c = 'A';转义字符用来表示那些不能显示的字符,一个转义字符是一个反斜杠紧跟一个字符,具有特定的含义,转义字符表如下:

    。\u(或\x)转义字符允许你通过使用4位16进制来指定任意的Unicode字符,如char newLine = '\u000A';

    40.当一个数值类型可以容纳一个unsigned short类型的值时,从char到该数值类型就可以隐式转换。否则的话,就需要显示转换。

    41.string类型表示一个不可变的Unicode字符序列。一个string的字面表示在一个双引号中,如string s = "hello";尽管string是引用类型,而不是值类型,但是它的==运算符却遵循值类型的比较语义,如string a = "test", b = "test"; Console.Write (a == b); // True。转义字符同样也可以用在string类型中,如string a = "\\\\server\\fileshare";C#还提供逐字字符串字面值,以@开头并且不再解析转义字符,如string b = @"\\server\fileshare";和上面的字符串a是等价的。

    42.+运算符用于连接两个字符串,如string a = "s" + "w";可能某个操作数不是string类型,那么该操作数类型的ToString函数将被调用,如string a = "s" + 5;等价于string a = "s" + 5.ToString();因为string类型是不可变的,所以多次的使用+运算符构建一个新的string是十分低效的。取而代之的,可以使用System.Text.StringBuilder类型,这代表了一个可变的字符串,以及拥有可以高效的添加,删除,插入,替换子串的方法。

    43.string类型不支持<和>运算符来进行比较操作,必须使用string类型的CompareTo函数。

    44.字符串的索引返回特定位置上的字符,如Console.Write ("word"[2]); // r。

    45.切记:string类型代表的字符串是不可变的,所有操作字符串的函数将返回一个新的字符串,原来的字符串不会被改变。

    46.一个数组(Array)代表一个特定类型的固定数量的元素。一旦数组被创建,它的长度就不能被改变了。数组元素总是被存储在连续的内存块中,以供高效的存取。一个数组以元素类型紧跟方括号来表示,如char[] vowels = new char[5]; 方括号也用于数组索引,存取一个特定位置的元素,如vowels[0] = 'a'; 在运行时所有的数组索引都会进行范围检测,如果使用了一个无效的索引,那么将抛出一个IndexOutOfRangeException异常。

    47.数组的初始化表达式可以方便的同时声明和赋值一个数组,如char[] vowels = new char[] {'a','e','i','o','u'}; 或者更简单的方式char[] vowels = {'a','e','i','o','u'}; 可以使用for循环来遍历一个数组中的所有元素,同时,由于数组总是实现了IEnumerable<T>,所以也能够使用foreach来枚举数组成员:

    复制代码
     1 char[] vowels = {'a','e','i','o','u'};
     2 
     3 for (int i = 0; i < vowels.Length; i++)
     4 {
     5     Console.Write (vowels[i]); 
     6 }
     7 
     8 foreach (char c in vowels)
     9 {
    10     Console.Write (c);
    11 }
    复制代码

    48.创建一个数组总是会使用默认值来预初始化每个元素,一个类型的默认值就是在内存中每一位都是0,对于值类型来说,就是0,对于引用类型来说,就是null。无论元素类型是什么,数组自身总是引用类型的。

    49.多维数组有两种形式:矩形数组(Rectangular arrays)和不规则数组(Jagged arrays)。矩形数组声明时使用逗号来分割每一维,如int[,] matrix = new int [3, 3]; 一个矩形数组可以像下面这样初始化:

    ?
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    int[,] matrix = new int[,]
    {
    {0,1,2},
    {3,4,5},
    {6,7,8}
    };
    // 更简洁的方式
    int[,] matrix =
    {
    {0,1,2},
    {3,4,5},
    {6,7,8}
    };

    不规则数组使用连续的方括号来代表每一维,如声明一个2维数组,最外维的大小为3:int[][] matrix = new int[3][]; 可以发现,声明时并没有指定内维的大小,与矩形数组不同的是,每个内维数组可以使任意大小的。因为内维数组被隐式初始化为null而不是一个空数组,所以内维数组必须手动创建:

    ?
    1
    2
    3
    4
    5
    6
    7
    int[][] matrix = new int[3][];
    for  (int i = 0; i < matrix.Length; i++)
    {
    matrix[i] = new int[3]; // 创建内维数组
    for(int  j = 0; j < matrix[i].Length; j++)
    matrix[i][j] = i * 3 + j;
    }

    不规则数组可以像下面这样初始化:

    ?
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    int[][] matrix = new int[][]
    {
    newint[] {0,1,2},
    newint[] {3,4,5},
    newint[] {6,7,8}
    };
    // 更简洁的方式
    int[][] matrix =
    {
    newint[] {0,1,2},
    newint[] {3,4,5},
    newint[] {6,7,8}
    };

    50.还有另一个简洁的数组初始化表达式,它省略了new关键字之后的类型名,让编译器去推断数组类型。当传递数组类型的参数时,这是一个十分有用的缩写:

    ?
    1
    2
    void  Foo (char[] data) { ... }
    Foo ( new[]{'a','e','i','o','u'} );

    51.栈和堆是存放变量与常量的地方,它们有截然不同的生命期语义。

    52.栈是用来存放局部变量和参数的内存块,当一个函数被调用和退出时,栈就会逻辑增长和减小,考虑下面的函数:

    复制代码
    1 static int Factorial( int n )
    2 {
    3     if ( 0 == n )
    4     {
    5         return 1;
    6     }
    7 
    8     return ( n * Factorial(n-1) );
    9 }
    复制代码

    这是一个递归函数,也就说它会自己调用自己。每次函数被调用时,就会有一个新的int参数被分配在栈上,而当函数退出时,int参数被释放。

    53.堆是用来存放对象(也就是引用类型的实例)的地方。无论何时当一个对象被创建时,它被分配在堆上,并且一个对该对象的引用将被返回。运行时有一个垃圾收集器,会定期的释放在堆上的对象,所以你的计算机不会耗尽内存。只要一个对象没有对它自身的任何引用,那么该对象就可以被垃圾收集器释放。堆也可以存放静态字段和常量,与在堆上分配的对象不同的是,他们不会被垃圾收集器释放,而是直到应用程序结束才会销毁。

    54.注意:不能像C++那样显示的释放对象,一个未被引用的对象最终会被垃圾收集器释放。

    55.C#执行明确的分配政策。在实践中,这意味着除了一个不安全的上下文以外,不可能访问到未初始化的内存。明确的分配政策有下面3个含义:

    1.局部变量在读取前必须指定一个值。

    2.当一个函数被调用时,必须提供函数需要的参数。(除非为默认参数)

    3.所有的其他变量(如数组元素)在运行时会自动初始化。

    56.所有类型的实例都有一个默认值,预定义类型的默认值就是内存中每一位都是0,引用类型的默认值是null,数值类型和枚举类型的默认值是0,char类型的默认值是'\0',bool类型的默认值是false。可以使用default关键字来获取任意类型的默认值。

    57.可以使用ref和out修饰符来控制如何传递参数,如下:

    58.默认情况下,C#中参数的传递方式是值传递。也就是说将值类型的实例传递给函数时,将创建一份值的副本。将引用类型的实例传递给函数时,将创建一份引用的副本,而不是所引用的对象的副本。举例如下:

    复制代码
     1 static void Foo( int n )
     2 {
     3     ++n;
     4     Console.WriteLine( n );
     5 }
     6 
     7 static void Main(string[] args)
     8 {
     9     int x = 10;
    10     Foo( x );   // 创建一份x的副本
    11     Console.WriteLine( x ); // x仍旧是10,因为Foo函数改变的是副本
    12 }
    13 ////////////////////////////////////////////////////////////////////////////
    14 
    15 static void Foo( StringBuilder strBuilder )
    16 {
    17     // strBuilder与sb引用同一个对象
    18     strBuilder.Append( "Reference Copy" );
    19             
    20     // strBuilder是引用的副本,所以改变它不会影响实参
    21     strBuilder = null;
    22 }
    23 
    24 static void Main(string[] args)
    25 {
    26     StringBuilder sb = new StringBuilder();
    27     Foo( sb );
    28     Console.WriteLine( sb.ToString() ); // Reference Copy
    29 }
    复制代码

    59.C#提供了ref修饰符来进行引用传递,例如:

    复制代码
     1 static void Foo( ref int n )
     2 {
     3     ++n;
     4     Console.WriteLine( n );
     5 }
     6 
     7 static void Main(string[] args)
     8 {
     9     int x = 10;
    10     Foo( ref x );  // 引用传递,也就是说形参n与实参x现在引用同一块内存
    11     Console.WriteLine( x ); // x现在是11
    12 }
    复制代码

    60.out修饰符与ref修饰符相似,除了:

    1.传递到函数之前无需指定值。

    2.函数返回之前必须被指定值。

    out修饰符通常用于从函数获取多返回值的情形。

    61.params修饰符可以用于一个函数的最后一个参数上,以表明该函数接受任意数量的特定类型的参数,但必须声明为数组形式,如:

    复制代码
    1 static int Sum (params int[] ints)
    2 {
    3     int sum = 0;
    4     for (int i = 0; i < ints.Length; i++) sum += ints[i];
    5     return sum;
    6 }
    7 
    8 // 可以这样调用Sum函数
    9 Console.WriteLine ( Sum (1, 2, 3, 4) );    // 10
    复制代码

    62.从C#4.0开始,提供了默认参数,当一个参数在声明时被指定了一个默认值时,它就是默认参数,如void Foo (int x = 23){ ... }。当调用函数时,默认参数可以被省略,如Foo();此时声明时指定的默认值将被传递给函数。

    63.除了使用位置指定参数,还可以使用名称指定参数,如void Foo (int x, int y){ ... }; Foo(x:1, y:2); 以名称指定参数可以使用任意顺序,如Foo(y:2, x:1)和Foo(x:1, y:2)是等价的。也可以混合使用位置和名称指定参数,但是以名称指定参数必须出现在最后,如Foo(1, y:2); 以名称指定参数与默认参数一起使用时显得特别有用,如void Bar (int a=0, int b=0, int c=0, int d=0) { ... } 就可以使用以下方式只提供一个值给d:Bar( d:3 );

    64.通常有在声明变量的同时初始化的情况,此时,如果编译器能够从初始化表达式中推断出类型的话,那么就可以使用var来代替类型声明,如var x = 5;等价于int x = 5; 这称为隐式类型变量,它是静态类型,如下面的语句将产生编译错误:var x = 5; x = "string"; // 编译错误,x是int类型

    65.一个赋值表达式使用=运算符将一个表达式的结果赋值给一个变量,如int x = x * 5; 赋值表达式能够和其他表达式组合,如y = 5 * (x = 2); 初始化多个变量时,这种方式特别有用:a = b = c = d = 0; 复合赋值运算符是组合赋值运算符与另一个运算符的语法简写,如x *= 2;等价于x = x * 2;

    66.当一个表达式包含多个运算符时,优先级和结合律决定了计算顺序。高优先级的运算符将在低优先级的运算符之前计算。当运算符的优先级相同时,运算符的结合律决定了运算顺序。如1+2*3的计算顺序为1+(2*3),因为*运算符的优先级高于+运算符。结合律分为左结合与右结合,左结合就是计算时从左至右,如8/4/2的计算顺序为(8/4)/2,因为/运算符是左结合。右结合就是计算时从右至左,如x=y=3,先将3赋值给y,然后将表达式y=3的结果(即3)赋值给x。

    67.下表以优先级从高至低的顺序列出了C#中的运算符,相同副标题下的运算符具有相同的优先级:

    68.一条声明语句声明了一个新的变量,在声明变量时可以选择是否使用表达式初始化该变量,声明语句以分号结尾。可以在一条声明语句中声明多个同类型的变量,当中以逗号分隔,如int a = 5, b = 6; 一个常量的声明与变量的声明类似,除了常量在声明后不能被改变,以及必须在声明时进行初始化。

    69.一个局部变量的生命期为当前语句块,不能在当前语句块或任何嵌套的语句块中声明另一个同名局部变量。

    70.表达式语句意味着表达式"做了"某些事情,如下:

    赋值或修改一个变量

    实例化一个对象

    调用一个函数

    一个没有做上面任何事情的表达式是非法的语句,如string s = "foo"; s.Length; // 非法的语句。 当调用一个构造函数或者一个有返回值的函数的时候,并不是必须使用该结果的,如new StringBuilder();是合法的语句。

    71.在switch语句中只能使用能够被静态求值的表达式,类型被限制在内建的整型,string类型和枚举类型。在每一个case从句的最后,必须使用一些跳转语句类型明确说明接下去该如何执行,可选的跳转语句如下:

    break - 跳转到switch语句的结尾

    goto case x - 跳转到另一个case从句

    goto default - 跳转到default从句

    任何其他的跳转语句 - 也就是return,throw,continue或goto标签

    当多个值需要执行相同的代码时,可以依次列出:

    复制代码
     1 switch (cardNumber)
     2 {
     3 case 13:
     4 case 12:
     5 case 11:
     6     Console.WriteLine ("Face card");
     7     break;
     8 default:
     9     Console.WriteLine ("Plain card");
    10     break;
    11 }
    复制代码

    72.foreach语句迭代一个可枚举对象中的每一个元素,C#中大多数表示元素集合或元素列表的类型都是可枚举的,比如数组和字符串,如:

    1 foreach ( char c in "foreach" )
    2 {
    3     Console.Write( c );
    4 }

    73.跳转语句包括break, continue, goto, return以及throw。break语句结束一个迭代语句或switch语句的执行。continue语句放弃循环中剩余的语句,立即开始下一次迭代。goto语句跳转到一个标签(使用冒号后缀表示)执行。return语句结束函数的执行,如果函数具有返回类型,那么必须返回一个该类型的表达式。

    74.命名空间是一个域,在该域中的类型名称必须是唯一的。命名空间不受成员访问权限(private, internal, public等等)的影响。没有定义在任何命名空间的类型就说处于全局命名空间。

    75.可以使用完整合格名称引用一个类型,也就是包括该类型定义所在的所有的命名空间。如:System.Security.Cryptography.RSA rsa = System.Security.Cryptography.RSA.Create(); 使用using指令导入命名空间后,就可以不需要使用完整合格名称来引用一个类型,如:using System.Security.Cryptography; RSA rsa = RSA.Create();

    76.在外层命名空间声明的名称可以在内层命名空间中直接使用,而不需要使用完整合格名称,如

    复制代码
     1 namespace Outer
     2 {
     3     namespace Middle
     4     {
     5         class Class1 {}
     6         
     7         namespace Inner
     8         {
     9             class Class2 : Class1 {}
    10         }
    11     }
    12 }
    复制代码

    如果想要使用同一命名空间层级下不同分支里的一个类型,只需要使用完整合格名称的一部分即可:

    复制代码
     1 namespace MyTradingCompany
     2 {
     3     namespace Common
     4     {
     5         class ReportBase {}
     6     }
     7 
     8     namespace ManagementReporting
     9     {
    10         class SalesReport : Common.ReportBase {}
    11     }
    12 }
    复制代码

    77.如果相同的类型名称同时出现在内层和外层的命名空间中,那么内层的命名空间中的类型将替代外层的命名空间中的类型。如果想要使用外层的命名空间中的类型,需要使用包括命名空间的合格名称。

    78.导入命名空间可能会导致类型名称冲突。相比导入整个命名空间,可以只导入需要的类型,并给类型一个别名,如

    1 using PropertyInfo2 = System.Reflection.PropertyInfo;
    2 class Program { PropertyInfo2 p; }

    命名空间也可以给定别名,如

    1 using R = System.Reflection;
    2 class Program { R.PropertyInfo p; }

    79.一个字段是一个类或结构体中的成员变量。一个字段可以使用readonly修饰符来防止它在构造后被修改,一个只读字段只能在声明时或构造函数中赋值。字段的初始化是可选的,未初始化的字段有默认值(0, \0, null, false),字段的初始化在构造函数之前,以他们声明出现的顺序执行。

    80.一个类型可以重载方法(有多个相同名称的方法),只要参数类型不同即可,如

    1 void Foo (int x);
    2 void Foo (double x);
    3 void Foo (int x, float y);
    4 void Foo (float x, int y);

    81.一个类或结构体可以重载构造函数,通过使用this关键字,一个重载的构造函数可以调用另外一个重载的构造函数,如

    1 public class Wine
    2 {
    3     public Wine (decimal price) {...}
    4     public Wine (decimal price, int year)
    5                  : this (price) {...}
    6 }

    当一个构造函数调用另外一个构造函数时,被调用的构造函数先执行。

    82.对于类来说,当且仅当没有定义任何构造函数时,编译器会自动生成一个无参的构造函数。对于结构体来说,无参的构造函数是结构体的内部结构,因此不能自己定义。结构体的无参的构造函数负责使用默认值初始化结构体中的每一个字段。

    83.为了简化对象的初始化,对象的可访问的字段或属性可以在构造后的单条语句中初始化,如

    复制代码
     1 public class Bunny
     2 {
     3   public string Name;
     4   public bool LikesCarrots, LikesHumans;
     5   public Bunny () {}
     6   public Bunny (string n) { Name = n; }
     7 }
     8 
     9 // 可以像下面这样初始化
    10 Bunny b1 = new Bunny {
    11                        Name="Bo",
    12                        LikesCarrots = true,
    13                        LikesHumans = false
    14                      };
    15 
    16 Bunny b2 = new Bunny ("Bo") {
    17                               LikesCarrots = true,
    18                               LikesHumans = false
    19                             };
    复制代码

    84.属性从外部看像字段,但在内部他们包含逻辑,像函数一样。一个属性像字段那样声明,但是附加了get/set块,如

    复制代码
    1 public class Stock
    2 {
    3     decimal currentPrice;
    4     public decimal CurrentPrice    // 属性
    5     {
    6          get { return currentPrice; }
    7          set { currentPrice = value; }
    8     }
    9 }
    复制代码

    get和set表示属性的访问器,当属性被读取的时候,get访问器就会运行,它必须返回与属性类型相同的一个值。当属性被赋值的时候,set访问器就会被运行,它包含一个名称为value的与属性类型相同的隐式参数。一个属性如果只定义了get访问器,那么它就是只读的。如果只定义了set访问器,那么它就是只写的。

    85.大多数属性的实现仅仅只是读与写一个与属性同类型的私有字段,一个自动属性声明可以告诉编译器提供这种实现,自动属性声明如下:

    1 public class Stock
    2 {
    3   public decimal CurrentPrice { get; set; }
    4 }

    此时编译器就会自动生成一个私有字段供属性读和写,该字段的名称是编译器生成的,所以不能引用。

    86.索引器提供了一种自然的语法来访问一个类或结构体中的元素。写一个索引器需要定义一个名称为this的属性,然后在方括号中指定参数:

    复制代码
    1 class Sentence
    2 {
    3   string[] words = "The quick brown fox".Split();
    4   public string this [int wordNum]      // 索引器
    5   {
    6     get { return words [wordNum];  }
    7     set { words [wordNum] = value; }
    8   }
    9 }
    复制代码

    一个索引器可以使用多个参数。每个类型可以定义多个索引器,只要它们的参数类型不同即可。如果省略set访问器,那么索引器就是只读的。

    87.一个常量是一个无法改变其值的字段。一个常量在编译时期被静态求值,无论何时使用它,编译器将用字面值替换它。一个常量使用关键字const进行声明,声明时必须被初始化:

    1 public class Test
    2 {
    3   public const string Message = "Hello World";
    4 }

    88.静态构造函数在每个类型上执行一次,而不像普通构造函数那样在每个实例上执行一次。一个类型只能定义一个静态构造函数,并且必须无参。当一个类型被使用之前,运行时会自动调用静态构造函数,有2件事会触发这种行为:实例化一个类型的对象以及访问类型中的静态成员。有个需要注意的地方:如果静态构造函数引发一个未处理的异常,那么在应用程序的生命期内,该类型都无法使用。

    89.一个类可以被标记为静态的,表明它必须完全由静态成员组成。

    90.终结器(Finalizers)是只有类拥有的函数,该函数在垃圾收集器回收一个未被引用的对象的内存时被调用。终结器的语法是类名加上前缀~:

    1 class Class1
    2 {
    3   ~Class1() { ... }
    4 }

    91.一个类只能有一个基类,但它自身可以作为许多类的基类。

    92.引用是多态的,也就是说一个类型的变量可以引用它的子类对象。多态的实现基于这样一个事实:子类拥有其基类的所有功能。

    93.一个对象的引用能够:

    隐式的向上转型到基类的引用

    显式的向下转型到子类的引用

    在兼容的引用类型之间向上转型或向下转型将执行引用转换,引用转换将创建一个指向同对象的引用。向上转型总是成功的,而向下转型是否成功取决于对象的类型是否合适。

    94.一个向上转型操作从子类引用创建一个基类引用。一个向下转型操作从基类引用创建一个子类引用,一个向下转型需要显式指定因为可能会失败,如果失败,将抛出InvalidCastException异常:

    House h = new House();
    Asset a = h;          // 向上转型总是成功
    Stock s = (Stock)a;   // 向下转型失败: a不是Stock类型

    95.as运算符执行向下转型,如果转型失败,会返回null,而不是抛出异常。as运算符不能用于数值转换。

    96.is运算符用于测试是否一个引用转换将会成功,换句话说,也就是是否一个对象派生自一个特定类(或实现了一个接口)。

    97.函数,属性,索引器以及事件都可以被声明为virtual,被标记为virtual就能够被想要提供特殊实现的子类覆盖(overridden),子类通过使用override修饰符来实现覆盖:

    复制代码
     1 public class Asset
     2 {
     3   public string Name;
     4   public virtual decimal Liability { get { return 0; } }
     5 }
     6 
     7 public class House : Asset
     8 {
     9   public decimal Mortgage;
    10   public override decimal Liability
    11     { get { return Mortgage; } } // 覆盖基类的属性,提供特殊实现。使用override修饰符
    12 }
    复制代码

    虚方法和覆盖方法的签名,返回类型以及访问权限必须一致。如果一个覆盖方法想要调用基类的实现,那么可以使用base关键字。

    98.一个被声明为抽象(abstract)的类不能被实例化。抽象类可以定义抽象成员,抽象成员类似于虚成员,但他们不需要提供一个默认实现。子类必须提供抽象成员的实现,除非子类也被声明为抽象类。

    99.一个基类和一个子类可以定义相同的成员,例如:

    1 public class A      { public int Counter = 1; }
    2 public class B : A  { public int Counter = 2; }

    此时可以说B类中的Counter字段隐藏了A类中的Counter字段。编译器会生成警告并且按如下的方法解决二义性:A类的引用对象绑定A.Counter,B类的引用对象绑定B.Counter。但是有时候,可能是故意想要隐藏成员,在这种情况下,可以使用new修饰符:

    public class A     { public     int Counter = 1; }
    public class B : A { public new int Counter = 2; }

    new修饰符其实只是使得编译器不再会生成警告,并且可以告诉其他程序员,这是故意的。

    100.一个覆盖的方法可以使用sealed关键字来密封它的实现,从而防止被其子类覆盖。也可以密封类,从而隐式的密封所有的虚函数。

    展开全文
  • C# Unity3d 添加场景

    2018-08-02 16:35:59
    SceneManager.LoadScene(stateName, LoadSceneMode.Additive); 删除场景 UnloadSceneAsync
  • Unity3D中所使用的编程语言有三种:C#,JS和BOO。同时自带了免费的编辑器,然而在windows下,编辑器还是VS最强大,所以本文就探讨如何配合Unity3D使用VS2008。1、创建一个Unity3D工程,在指定的目录下会生成Asset...
  • Unity 3DC#6.0 和 7.0 的支持
  • Unity3d-C#常用API

    2013-07-08 15:10:47
    游戏开发的时候有很多常用的功能,比如遍历目录下文件等。很多时候大家会自己写...好一点的会写到项目的库里面,差一点的可能各个源文件中到处都是。...这里是一些汇总,基于Unity3d以及C#,慢慢汇总,不
  • 前言:  在开始编写代码之前,我们首先需要明确:联网方式、联网步骤...我们这里使用的通信模式是Socket强连接的通信方式,并且使用C#作为编程语言,其实与.NET的Socket通信是一致的。   一、设计思想:  为...
  • unity3d c# 生成真正随机数
  • Unity实现c#热更新方案探究(一) 转载请标明出处:http://www.cnblogs.com/zblade/ 最近研究了一下如何在unity中实现c#的热更新,对于整个DLL热更新的过程和方案有一个初步的了解,这儿就写下来,便于...
  • Unity3dC#操作文件

    2012-04-13 11:40:24
    文章转自:...Unity3dC#操作文件 文件读写 创建 1 首先在工程面板所在的文件夹中(即Assets/文件夹下)新建一个FileOperation.txt文件,在文件中写入一些内容 2 将下面代码脚本附加到
  • 学习好Unity,其先决条件是一定要有稳固、扎实的编程基础!课程 《C# For Unity系列之入门篇》配套学习资料链接:http://pan.baidu.com/s/1gflxreN 密码:sou5;刘老师讲Unity学员群(2) 497429806 ...
  • 在使用c#进行Unity3D游戏开发中,良好的注释和文档能让开发更有效率,条理更清晰。 本讲分为两个部分: 一:编写注释 二: 生成文档  编写注释 开发注释是 // 帮助拓展代码 使用注释是 ...
  • 使用C#脚本控制游戏对象,是一项必备的基本技能。Unity3D可以使用的脚本有C#和javascript等。我们主要讲注意力集中在C#上。本文将会介绍怎样使用脚本控制场景中的游戏对象。
  • Unity3D开发为什么C#语言学习叫苦连连
  • 关于UnityVS的特色,我不做过多阐述,一句话Microsoft Visual Studio C#辅助开发插件。 0:Visual Studio and Unity连接 UnityVS显示它是连接到一个Unity实例,UnityVS通过UDP连接,如果你的防火墙会询问他们...
  • C# 事件和Unity3D

    2012-01-13 10:35:14
    翻译自:  http://www.everyday3d.com/blog/index.php/2010/10/04/c-events-and-unity3d/  zijan译  ...(括号内是译者自己对文章和技术的...(Unity3D是现在越来越流行的3D游戏引擎,它支持JavaScript,c#和Bo
  • 一、Hello World! 1. 代码 Ctrl + S :保存代码 2. 运行程序  菜单栏选择启动,或键盘F5. 3. 运行结果 4. 代码分析 ...//....//调用控制台的一个功能:写出一行,实现在控制台中展现出"...Hell...
1 2 3 4 5 ... 20
收藏数 24,081
精华内容 9,632