C#与Java的语法差异

C#与Java的语法差异

前言

本文主要供有Java基础的读者快速掌握C#基本语法使用,重点落在C#的语法上。

程序结构

C#中使用using关键字引用命名空间(namespace),作用和Java中使用import导入包中的类基本相同,都是为了避免命名冲突。

C#中文件名和类名可以不相同,但Java中文件名必须和主类名相同。

C#中方法名一般是让所有单词的首字母大写(ToString()),Java中一般是驼峰式,即除第一个单词外首字母大写(toString())。

基本语法

C#中标识符可以以字母、下划线以及@开头;Java中可以以字母、下划线以及$开头。

数据类型

C#中byte是8位无符号整型,sbyte是8位有符号整型;Java中byte是8位有符号整型。

C#中还有uint、ulong、ushort数据类型,是int、long、short的无符号版。

C#中使用sizeof()查看数据类型所占字节数(如sizeof(int));Java中通过包装类实现(如Integer.SIZE)。

C#中的动态(dynamic)类型的变量可以存储任何类型的值,例:

    dynamic d = 100;

一般类型变量的类型检查在编译时发生,动态类型变量的类型检查在运行时才发生。比如下面的例子,编译器不会报错:

    dynamic d = 100.0;
    int x = d;

C#中可以使用指针,和C与C++中的指针功能相同。

C#中提供了一种特殊的类型:nullable类型。这种类型除了可以表示正常范围内的值外,还可以表示null值。使用方法是在数据类型后面加上一个?。例:

    int? x = null;
    double? y = null;

注意两点:
- 即便想使用null值,nullable类型仍需显示初始化为null。
- nullable类型和原类型不是同一种类型,如方法需要int,传递一个int?类型的变量会报错。

字符串

C#中可以使用一种逐字字符串,它会将所有转义字符当做普通字符处理,使用方法是在字符串前加上@:

    String str = @"c:\windows\";

会输出c:\windows\,而不会将\视为转义字符。

C#中基本类型(int,float等)也可以调用ToString()转换成字符串。

Java中String类提供的各类字符串操作方法基本都能在C#中找到功能类似的实现。

变量与常量

C#中用const声明常量,Java中使用final声明常量。

运算符

C#中包含以下运算符:
- typeof():返回class的类型。
- &,*:含义与使用方法同C,C++的指针。
- is:判断对象是否为某一类型,同Java的instanceof。
- as:强制将某个对象转成某个类型,转换失败也不会抛出异常。

要注意的是,在C#中,类内部声明的常量都是静态的,但不能加上static。示例:

    public class Persons
    {
        public const int SIZE = 10;
        private String[] names;
    }
    class Program
    {
        static void Main(string[] args)
        {
            int x = Persons.SIZE;
            Console.ReadLine();
        }
    }

可以看到,虽然SIZE没有用static修饰,它仍然是静态的。这和Java不同,Java中静态常量必须声明为static的。

判断语句

和Java基本相同。

循环语句

C#支持用foreach迭代数组或集合对象。语法示例如下:

    int[] array = new int[]{1,2,3,4,5};
    foreach (int element in array)
    {
    ...
    }

Java中的foreach如下:

    int[] array = new int[]{1,2,3,4,5};
    for(int element : array){
    ...
    }

访问权限

C#中public,private,protected的含义与Java相同。不同之处如下:
- C#中没有包的概念,自然也没有包级私有访问权限。
- C#特有:internal——同一个程序集的对象可以访问;protected internal——同一个程序集内的派生类可以访问,是internal与protected的交集。
- C#成员变量和成员方法默认private,类默认internal,接口方法默认public;Java除接口默认public外默认为包级私有。

方法

C#的参数传递分为三种:
(1)值参数:默认的方式,复制实参给形参,形参的改变不会改变实参。

(2)引用参数:复制实参的内存位置的引用给形参,形参的改变会影响实参。引用参数用ref声明:

    public static void Swap(ref int x, ref int y)
    {
        int temp;

        temp = x;
        x = y;
        y = temp;
    }

使用时也要给参数加上ref:

    int x = 1;
    int y = 2;
    Swap(ref x, ref y);

上面的方法调用会改变x和y的值。

(3)输出参数:C#的return只能从方法中返回一个值,但是使用输出参数就可以返回多个值。输出参数用out声明:

    public static void GetValue(out int x)
    {
        x = 5;
    }

提供给输出参数的变量不需要赋值,不过传递给方法时要加上out:

    int a;
    GetValue(out a);
    Console.WriteLine(a);

会发现a已经被成功赋值。
注意:如果方法中未给输出参数赋值,编译器会报错。

C#中的变长参数列表是通过在方法的参数列表中用params声明一个数组实现的:

    public int AddElements(params int[] arr)
    {
       int sum = 0;
       foreach (int i in arr)
       {
          sum += i;
       }
       return sum;
    }

数组

C#中只能使用类似int[] array的方式来声明数组,不能使用int array[]的方式。Java中二者均可。

C#多维数组:用[,]声明二维数组,[ , , ]声明三维数组,以此类推。示例:

    int[,] array = new int[3, 4]
    {
        {1, 2, 3, 4 },
        {5, 6, 7, 8 },
        {9, 10, 11, 12}
    };

使用array[x,y]引用上面的数组的元素。

C#交错数组:即数组的数组,用[][]这样的方式声明。示例:

    int[][] arr = new int[3][];

声明该数组时不会为其分配内存,需要单独创建它的每个元素(也是一个数组)。每个数组元素的长度可以不同。

结构

C#中能够定义和使用结构struct。和C,C++的结构体的区别在于:
- 结构可带有方法、字段、索引、属性、运算符方法和事件。
- 结构可以定义构造函数,但不能定义析构函数。不能覆盖结构的默认构造函数,它会自动被定义,且不能改变。
- 结构不能继承其他的结构或类,也不能作为其他结构和类的基础结构。
- 结构可实现一个或多个接口。
- 结构成员不能声明为abstract、virtual、protected。
- 结构即使不使用New操作符也能完成实例化,但也可使用New以调用合适的构造函数。
- 结构的字段必须先赋值后才能使用。

和类的区别在于:
- 类是引用类型,结构是值类型。
- 结构不支持继承。
- 结构不能声明默认构造函数。

枚举

C#中的枚举和C,C++类似,是一组命名整型常量,不能像Java那样在枚举中声明方法。示例:

    enum Days { Sun, Mon, tue, Wed, thu, Fri, Sat };

默认情况下,第一个枚举符号的值为0,后面依次递增1。不过也可以以赋值的方式自定义各个枚举符号的值。

C#的类支持析构函数,析构函数的名称是在类名前加上一个~。它没有返回值,也不带任何参数。

C#支持静态成员和静态方法。

C#的静态初始化块的写法和Java的static{}不同,是使用一种静态构造器的方式:

    // Static variable that must be initialized at run time.
    static readonly long baseline;

    // Static constructor is called at most one time, before any
    // instance constructor is invoked or member is accessed.
    static SimpleClass()
    {
        baseline = DateTime.Now.Ticks;
    }

继承

C#的继承写法:class Rectangle : Shape;Java的继承写法:class Rectangle extends Shape

关于在构造器中调用父类的构造器,C#中写法为:public Rectangle(String name) : base(name);Java中写法为在构造器第一行加上super(参数列表)。

关于串联构造器,C#中写法为:public Rectangle(String name) : this(参数列表),Java中写法为在第一行加上this(参数列表)。

C#的base与this和Java的super与this在其他用法上基本相同。

C#不支持多重继承,但可以实现多个接口,这点和Java一样。但C#实现接口不用implements,而是直接和继承类一样写在冒号后面,如:class Rectangle : Shape, SomeInterface

多态

C#的重载规则和Java类似,个数和类型不同能够触发重载,返回值类型不同不能触发重载。

C#中可被子类覆盖的成员方法需要在访问控制权限后面加上virtual关键字,Java中默认所有成员方法都可被子类覆盖,加上final可让方法无法被覆盖。

C#中,当子类覆盖父类的方法时,需要加上override关键字,Java中不需要。

C#中,可以将方法声明为abstract的,表示该方法应交由子类实现。包含abstract方法的类本身必须也是abstract的,即无法实例化的抽象类。

C#中,在类定义前加上sealed可以让类无法被继承,Java中使用final关键字实现该效果。

运算符重载

C#支持运算符重载。定义运算符重载的方法必须为静态方法,拥有返回值和参数列表,方法名为operator后跟对应的运算符。例:

    public class Point
    {
        public int x;
        public int y;
        public Point(int x, int y)
        {
            this.x = x;
            this.y = y;
        }

        //重载+运算符
        public static Point operator+ (Point a, Point b)
        {
            return new Point(a.x + b.x, a.y + b.y);
        }
    }

    static void Main(string[] args)
    {
        Point a = new Point(1, 2);
        Point b = new Point(2, 1);
        //运算符重载
        Point c = a + b;
        Console.WriteLine("x={0}, y={1}", c.x,c.y);
        Console.ReadLine();
    }

下面是对各个运算符能否重载的总结:
- +, -, !, ~, ++, – 这些一元运算符只有一个操作数,且可以被重载。
- +, -, *, /, % 这些二元运算符带有两个操作数,且可以被重载。
- ==, !=, <, >, <=, >= 这些比较运算符可以被重载。
- &&, || 这些条件逻辑运算符不能被直接重载。
- +=, -=, *=, /=, %= 这些赋值运算符不能被重载。
- =, ., ?:, ->, new, is, sizeof, typeof 这些运算符不能被重载。

Java中不能自定义运算符重载。

接口

C#中接口内方法默认public。接口支持继承。这些和Java基本一致。

C#中接口名称通常以I开头。

C#中,实现接口中的方法时不需要加上override(实际上是不能,会报错)。

命名空间

命名空间namespace是C#独有。命名空间的定义方法如下:

    namespace namespace_name
    {
        class A{

        }
    }

使用命名空间中的类时需要加上命名空间名和’.’:

    namespace_name.A a = new namespace_name.A();

可以在开头加上using 命名空间名,这样就不用每次引用命名空间中的类时加上前缀。

命名空间可以嵌套,即可以在一个命名空间中声明另一个命名空间。

其他命名空间用法:
(1)using static 指令:指定无需指定类型名称即可访问其静态成员的类型

    using static System.Math;var = PI; // 直接使用System.Math.PI

(2)起别名

    using Project = PC.MyCompany.Project;

(3)using语句:将实例与代码绑定

    using (Font font3 = new Font("Arial", 10.0f),
                font4 = new Font("Arial", 10.0f))
    {
        // Use font3 and font4.
    }

代码段结束时,自动调用font3和font4的Dispose方法,释放实例。

预处理器指令

C#的预处理器指令源于C,C++。可用的预处理器指令如下:

#define 它用于定义一系列成为符号的字符。
#undef  它用于取消定义符号。
#if 它用于测试符号是否为真。
#else   它用于创建复合条件指令,与 #if 一起使用。
#elif   它用于创建复合条件指令。
#endif  指定一个条件指令的结束。
#line   它可以让您修改编译器的行数以及(可选地)输出错误和警告的文件名。
#error  它允许从代码的指定位置生成一个错误。
#warning    它允许从代码的指定位置生成一级警告。
#region 它可以让您在使用 Visual Studio Code Editor 的大纲特性时,指定一个可展开或折叠的代码块。
#endregion  它标识着 #region 块的结束。

使用示例:

    #define DEBUG
    #define VC_V10
    using System;
    public class TestClass
    {
       public static void Main()
       {
          #if (DEBUG && !VC_V10)
             Console.WriteLine("DEBUG is defined");
          #elif (!DEBUG && VC_V10)
             Console.WriteLine("VC_V10 is defined");
          #elif (DEBUG && VC_V10)
             Console.WriteLine("DEBUG and VC_V10 are defined");
          #else
             Console.WriteLine("DEBUG and VC_V10 are not defined");
          #endif
          Console.ReadKey();
       }
    }

    输出:DEBUG and VC_V10 are defined

正则表达式

C#同样支持正则表达式。C#中的正则表达式的优势在于,可以使用逐字字符串(双引号前加@)来简化输入(不需要考虑字符串本身的转义,但正则表达式的转义仍需考虑)。具体的正则表达式语法规则和Java基本一致。

异常

C#中不能在方法后面用throws指明可能抛出的异常类型,其余和Java一致。

I/O

C#的I/O与文件系统体系和Java很像,但具体使用上会产生差异。
具体见http://www.runoob.com/csharp/csharp-file-io.html

泛型

C#中,声明泛型类的方法和Java相同,都是在声明类时在类名后面加<泛型参数列表>,但声明泛型方法的语法不太一样,体现在不需要声明泛型参数列表上。下面是C#中一个泛型方法的声明与使用:

    public static void Swap<T>(ref T a, ref T b)
    {
        T temp;

        temp = a;
        a = b;
        b = temp;
    }

    static void Main(string[] args)
    {
        int x = 10;
        int y = 20;
        Swap(ref x, ref y);
        Console.WriteLine(x);
        Console.WriteLine(y);
        Console.ReadLine();
    }

Java中的话,Swap方法的返回值前还得加上<T>。

集合

Java中的集合几乎都能在C#中找到对应的实现,名称也基本相同,除了Map。C#对应Map的集合类是Dictionary类。

属性

属性是C#独有的语法,它形式上类似于在字段的基础上添加了get和set两个访问器形成的。下面是一个例子:

    class Person
    {
        private int age;
        public int Age
        {
            get
            {
                return age;
            }
            set
            {
                if (value <= 200)
                {
                    age = value;
                }
                else
                {
                    throw new Exception("");
                }
            }
        }
    }

Age就是一个属性。当外部需要获取Age的值时会自动调用get,当外部需要设置Age的值时会自动调用set。注意set中的value,它指代的是外部想要为其设置的值。
对于声明了属性的类,还可以用下面的方式来初始化:

    Person person = new Person
    {
        Age = 15
    };

访问属性时时使用属性名,而不是字段名:

    int x = person.Age;
    person.Age++;

注意Age = 15后面没有分号。

属性可以只有get,也可以只有set,但不能两个都没有。

还能够像这样声明自动完成的属性,并设置初始值:

    public String Name { get; set; } = "father";

可以为属性的某个访问器声明访问控制权限。

类还可以使用virtual或者abstract声明一个属性,它们可以在子类中覆盖或实现。

索引器

C#的索引器允许一个对象像一个数组一样被索引。当为一个类声明了索引器,就可以用对象名[]的方式访问对象的一些字段。
索引器用下面的语法声明:

    element-type this[int index]
    {
       // get 访问器
       get
       {
          // 返回 index 指定的值
       }

       // set 访问器
       set
       {
          // 设置 index 指定的值
       }
    }

示例:

    public class Persons
    {
        public const int SIZE = 10;
        private String[] names = new String[SIZE];

        public Persons()
        {
            for(int i = 0; i < SIZE; i++)
            {
                names[i] = i.ToString();
            }
        }

        public String this[int index]
        {
            get
            {
                String temp = "";
                if(index >= 0 && index < SIZE)
                {
                    temp = names[index];
                }
                return temp;
            }
            set
            {
                if (index >= 0 && index < SIZE)
                {
                    names[index] = value;
                }
            }
        }
    }
    class Program
    {
        static void Main(string[] args)
        {
            Persons persons = new Persons();
            for(int i = 0; i < Persons.SIZE; i++)
            {
                Console.WriteLine(persons[i]);
            }
            Console.ReadLine();
        }
    }

索引器还可以被重载,重载规则和方法的相同。

委托

C#中有一种叫委托的引用类型,它和C,C++的函数指针很像,用来持有某个方法的引用。引用可在运行时改变。
委托在C#中常被用于实现事件和回调方法。

委托使用delegate关键字声明,形式上和接口中的一个方法很像。委托可指向一个与其签名相同的方法。下面是一个示例:

    public delegate int DoSomething(int x);

这个委托的实例可指向一个参数为一个int且返回值也为int的方法,下面是使用示例:

    class Program
    {
        public delegate int DoSomething(int x);

        public static int AddOne(int a)
        {
            return a + 1;
        }

        public int MinusOne(int a)
        {
            return a - 1;
        }

        static void Main(string[] args)
        {
            int x = 10;
            DoSomething doSomeThing = new DoSomething(Program.AddOne);
            Console.WriteLine(doSomeThing(x));

            Program program = new Program();
            DoSomething doSomething2 = new DoSomething(program.MinusOne);
            Console.WriteLine(doSomething2(x));

            Console.ReadLine();
        }
    }

委托实例还可以利用+、-、+=、-=、=等进行各种方法拼接、移除操作,这叫做委托的多播。调用这样的委托的实际效果相当于按照顺序依次调用一系列的方法。

事件

事件是C#的一种实现进程间通信的机制,使用发布-订阅模型。
事件由委托和event关键字声明。下面是完整地实现一个事件的过程:
(1)声明事件的委托类型:

    public delegate void NumManipulationHandler(int value);

(2)使用event关键字声明事件:

    public event NumManipulationHandler NumChanged;

(3)利用委托的多播,在事件上使用+、-等实现订阅/取消订阅。
下面是一个完整的例子:

    public class Publisher
    {
        private int value = 0;

        public delegate void NumManipulationHandler(int value);
        public event NumManipulationHandler NumChanged;

        protected virtual void OnNumChanged()
        {
            if(NumChanged != null)
            {
                NumChanged(value);
            }
        }

        public void SetValue(int x)
        {
            if(value != x)
            {
                value = x;
                OnNumChanged();
            }
        }
    }

    class Subscriber
    {
        public void Printf(int value)
        {
            int x = value;
            Console.WriteLine("new value: {0}",x);
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            Publisher publisher = new Publisher();
            Subscriber subscriber = new Subscriber();

            publisher.NumChanged += new Publisher.NumManipulationHandler(subscriber.Printf);

            publisher.SetValue(100);
            publisher.SetValue(200);

            Console.ReadLine();
        }
    }

其实这和Java中先定义回调接口,之后在外部实现回调接口的思想很像。

匿名方法

有些委托实现可能只会使用一次,因此可以用匿名的方式传递给需要委托的方法,和Java的匿名内部类非常相似。示例:

    public delegate void DoSomething(int x);

    DoSomething doSomething = delegate (int x)
    {
        Console.WriteLine(x);
    };
  • 23
    点赞
  • 92
    收藏
    觉得还不错? 一键收藏
  • 4
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值