C#和Java在语法上的一些区别

文章目录

    • 编译运行
    • 程序结构
    • 变量
    • 常量
    • 输入输出
    • 数据类型
    • 字符串
    • 判断语句
    • 循环语句
    • 访问权限
    • 方法
    • 数组
    • 枚举类型
    • 继承
    • 多态
    • 接口
    • 异常
    • 泛型

本人在大一时学习了Java,最近有学习C#的需求。发现C#和Java在某些方面还是挺像的,比如:

  • C#和Java都没有全局变量和全局函数,所有的变量和函数都属于某个类所有
  • 类都是从Object类派生而来,类的对象通过关键字new生成
  • C#和JAVA语言都使用“.”操作符,不再使用“->”和“::”操作符
  • C#和JAVA语言中的if语句都不允许采用整数作为判断条件

但是对于同一个知识点,这两门语言在语法上可能略有不同。本文将介绍C#和Java在语法的一些不同,侧重点会放在C#上,可帮助有Java基础的童鞋快速入门C#

编译运行

  • Java是先编译成字节码文件(class文件),然后由JVM解释成操作系统对应的机器码,每次运行都需要重新解释并执行

  • C#是.NET平台下的一门语言,这个平台还能兼容其他一些语言,比如VB。.NET的编译运行机制是先将源代码文件(不管是什么可兼容的语言写的)编译成统一的中间语言,然后运行时将中间语言翻译成对应的机器语言。所以也是有个 编译—>运行 的过程。
    拿C#来具体解释一下:C#先由C#编译器编译成程序集(exe或dll文件),程序集中就包含了公共中间语言(CIL)。然后CIL由.NET平台中的公共语言运行库(CLR)中的JIT编辑器编译成操作系统对应的机器码。后面再次运行时只要程序还驻留在内存中,就不需要CLR再次编译。

所以执行Java和C#的程序都有两个过程:先编译,再运行。但是Java运行时是解释代码的操作(一句一句翻译),C#运行时是依然是编译的操作(整段话统一翻译)

程序结构

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

using System;//C#
import java.util.*   //Java

C#中文件名和类名可以不相同,但Java中文件名必须和主类名相同。(注意,unity中的C#脚本文件名必须和主类名相同)

C#中方法名一般是让所有单词的首字母大写(ToString())。(这点Java转C#的童鞋可能要适应一段时间) Java中一般是驼峰式,即除第一个单词外首字母大写(toString())。这不是强制要求,但是一种编程上的规范。

变量

C#中的变量名可以以字母、下划线以及@开头;Java中可以以字母、下划线以及$开头。

常量

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

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

可以看到,虽然SIZE没有用static修饰,但是我们可以用 “类型.变量名 ”的方式使用变量,说明这个const变量是静态的。
但是在Java中,静态常量必须加static

输入输出

输出
Java输出用System.out.println(xxx) ,格式化输出用System.out.printf()

System.out.println("xxx");
int num=1;
System.out.printf("xxx%d",num);

C#输出可用Console.WriteLine(xxx) 输出一行,括号内可为空,或输出的内容,
C#的格式化输出使用如下:

int num=1;
string s="z";
Console.WriteLine("xxx {0} yyy {1}",num,s); //输出xxx 1 yyy z
//或者用:
string s=string.Format("xxx {0} yyy {1}",num,s); 
Console.WriteLine(s);

这里{0} {1}是占位符,占位符从0开始
从 C#6.0起还支持内插字符串,用“$"表示:

int num=1;
string s="z";
Console.WriteLine($"xxx {num} yyy {s}"); //输出xxx 1 yyy z

输入
Java输入用Scanner类

Scanner sc=new Scanner(System.in);
int num=sc.nextInt();
String s=sc.next();

C#输入用Console.ReadLine() Console.Read()
我们从C#初始Hello World程序中有看到这样的写法:

Console.WriteLine("Hello World!");
Console.ReadKey();

这个ReadKey作用是按下任意一个键关闭窗口,没有这行的话控制台窗口会一闪而过,因为程序执行完输出后就结束了,加了ReadKey相当于等待用户的输入
这行也能用Console.ReadLine(); 注意的是这种写法只有在用户输入回车后才会终止程序

数据类型

Java是不支持无符号类型的。byte,int,long等都是有符号类型。
C#的byte是8位无符号整型,sbyte是8位有符号整型。
C#中还有uint、ulong、ushort数据类型,是int、long、short的无符号版。

对于布尔类型,Java用boolean表示,C#用bool表示。

如果我们要知道数据类型所占的字节数,Java用包装类实现(如Integer.SIZE),C#用sizeof(xxx)

除此之外,C#添加了几种特殊的数据类型:

  1. var
    var是种推断类型,根据所赋数据的值推断出变量的类型。如 var a=1;

  2. dynamic
    C#中的动态(dynamic)类型的变量可以存储任何类型的值,例:dynamic d = 100;
    任何类型都能隐式转换成dynamic类型,如 dynamic d = 100.0;
    反过来,dynamic类型也能转换成任何类型,如 int x = d; 编译器不会报错
    看起来,dynamic和var有点相似。但实际上,var作用于静态的编译时期,看下面这个例子:

var str="xx";
str=1;

在Visual Studio中,我们一写完这段代码第二行就会报错。因为var在编译时期定义数据类型,一旦确定了数据类型,就无法在运行时更改。所以无法将int类型的1赋给string类型的str,它们也无法隐式类型转换。
而dynamic作用于动态的运行时期。编译期不给予类型检查。如:

dynamic str="xx";
str++;

写完这段代码并不会报错,可当我们运行这段程序时VS会给出运行时错误,因为不能对string进行++操作。

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

nullable类型和原类型不是同一种类型,如方法需要int类型的参数,传递一个int?类型的变量会报错。
??表示Null合并运算符,如果第一个操作数的值为 null,则运算符返回第二个操作数的值,否则返回第一个操作数的值。例如:

double? num1 = null;         
double num3;         
num3 = num1 ?? 5.34; // num1如果为null返回5.34

字符串

在定义的时候:

  • Java中的字符串是String类,C#中的字符串是string类(注意大小写)。
    可能有的小伙伴会在C#代码中看到String的出现,其实String是.NET 框架的类,它并不是C#关键字,所以我们如果把一个变量命名成String是不会报错的,并且在C# IDE中String不会显现出特殊颜色,也能说明它不是关键字。
    虽然C#的编译器最终还是会把string编译成String,但是我们在写C#代码时为了规范还是建议用string

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

string str = @"d:\coding\";
//等价于
string str="d:\\coding\\";

使用@后\就不会被当作转义字符了。

提供的方法上:

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

  • C#比较字符串的内容直接用 “==”判断,Java则是要用String类的equals方法。在Java中,用 “==”比较字符串是比较字符串的内存地址而不是内容。

  • C#中基础数据类型(int,float等)的变量能直接调用ToString()方法转成字符串。Java要使用诸如String.valueOf(xxx) 或Integer.toString(xxx) 等方法

//C#
int num=1;
string str=num.ToString();
  • Java中提取字符串某一位置的字符是用s.charAt(i) ,C#直接s[i] ,像数组一样使用。

判断语句

Java和C#的语法基本相同。
但是要注意的是:C#的switch-case语句的每一个case必须以break结尾。Java可以不加break

循环语句

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

//C#
int[] array = new int[]{1,2,3,4,5};  
foreach (int element in array) {    
      ...   
}
//Java 
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传参,C#的属性会在后面提到)。引用参数用ref声明:

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

        temp = x;
        x = y;
        y = temp;
    }
//调用时传参也要加上ref,这种方式会交换x,y的值
int x=1,y=2;
Swap(ref x,ref y);

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

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

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

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

可变长参数
如果我想在一个方法中传入不确定数量的同种类型的参数,可以使用可变长参数列表。
C#中的可变长参数列表是通过在方法的参数列表中用params声明一个数组实现的(Java中用…):

//C#语法,Java则是用int ... arr
public int AddElements(params int[] arr) {       
     int sum = 0;       
     foreach (int i in arr) {  
          sum += i;       
     }       
     return sum;    
 }
 //然后在调用此方法时就可以用AddElement(1,2,3); 的形式,方法也可以不传参数

注意点:
1.带 params 关键字的参数类型必须是一维数组,不能使用在多维数组上;
2.不允许和 ref、out 同时使用;
3.带 params 关键字的参数必须是最后一个参数,并且在方法声明中只允许一个 params 关键字。 例如:

public void Test(int num,string s,params float[] arr){}

4.不要仅使用 params 来使用重载方法。来看下面这个例子:

public static void Test(params int[] arr){}
public static void Test(params string[]arr){}

这时我们像下面这样调用,不传递参数:

Test();

这时候编译器会报错。因为编译器无法确定没有参数时调用的是哪个方法。

5.没有 params 关键字的方法的优先级高于带有params关键字的方法的优先级。来看下面这个例子:

public static void Test(string s1,string s2){
    Console.Write("xx"); 
}
public static void Test(params string[]arr){
     Console.Write("hh");
}

然后我们调用这个方法:

Test("C#","nb");

这时候会输出xx
按理来说两个方法都匹配,但是实际上调用了第一个,可见第一个方法优先级高于第二个。

数组

一维数组
C#也是使用: 数据类型[ ] 数组名=new 数据类型[数组长度] 这种形式创建一维数组。
需要注意的是C#中只能使用类似int[ ] array的方式来声明数组,不能使用int array[ ]的方式。Java中二者均可,但推荐用int[ ]arr的形式。
多维数组
Java中几维数组就用几个[ ],比如创建int二维数组就用: int[ ][ ] arr=new int[行数][列数] 的形式。然后访问某个位置的元素就用arr[i][j]的形式(注意数组索引从0开始,也就是第一行第一列的元素是arr[0][0])

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

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

然后使用array[x,y]就能引用上面的数组的元素。如array[1,2]代表7(C#数组索引也是从0开始,所以代表第二行第三列的元素)

另外,在C#中 int[ ][ ] arr这种声明方式也是存在的,但此时不是简单地声明一个二维数组,而是声明了一个交错数组在交错数组中,数组的每一个元素存的又是一个数组,每个数组的长度又可以不同,所以它又叫做数组的数组。
来看一个创建交错数组的例子:

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

注意:
1)交错数组本质上是一维数组,只是里面的元素又是数组
2)声明该数组时不会为其分配内存,要想使用它还需要单独创建它的每个元素(实例化每一个数组元素)。每个数组元素的长度可以不同。
就上面的例子来说,我们只是规定了数组可以存三个元素,每个元素是一个数组,但是我们还未定义每个数组元素的长度。
我们可以进行以下初始化方式:(注意到每个数组元素的长度可以不同)

arr[0] = new int[5];
arr[1] = new int[4];
arr[2] = new int[2];

我们还可以直接初始化每个数组元素内的每一个元素:

int[][] arr = new int[2][]{
	new int[]{100,93,94},
	new int[]{85,60,70,88}
};

3)取交错数组的元素就用arr[i][j]的方式(和Java一样)
4)创建交错数组用int[ ][ ] arr=new int[i][j]是错误的 j处是不需要在定义时确定大小的,我们要把交错数组和二维数组的概念区分开,交错数组只是个特殊的一维数组,我们只需用一个参数来确定数组的大小。至于里面每个数组元素有多大,是放到初始化的工作。

番外: Java中int[ ][ ][ ] arr是三维数组,C#也有这种写法,但它仍是交错数组。只不过多套了一层娃。声明的还是一维数组,每个数组元素是一个交错数组(一个一维数组,数组里每个元素又是一个数组)。这种用法可能理解起来比较绕,但是一般情况下用得比较少。

枚举类型

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

 //C#用法
 enum Days { Sun, Mon, tue, Wed, thu, Fri, Sat };

默认情况下,第一个枚举符号的值为0,后面依次递增1。不过也可以以赋值的方式自定义各个枚举符号的值。
而Java枚举是可以带方法和构造器的,比如写一个API返回结果的枚举:

public enum ApiErrorCode {
    SUCCESS(200, "操作成功"),

    private final Integer code;
    private final String message;

    ApiErrorCode(int code, String message) {
        this.code = code;
        this.message = message;
    }
    public Integer getCode() {
        return code;
    }
    public String getMessage() {
        return message;
    }
    
}

  • C#的类支持C++中的析构函数,而Java是不支持的。析构函数是在类名前加上一个~。它没有返回值,也不带任何参数。类的对象被销毁时会调用析构函数。
public class Person{
     ~Person(){
          Console.Write("person");
      }
      static void Main(){
            Person p=new Person();
       }
}

我们仅仅创建了一个Person类的对象,当运行这段程序时,会发现输出了person,这是因为程序运行结束时会销毁对象p,此时会调用类的析构函数。

  • C#的静态初始化块的写法和Java的static{}不同,是使用一种静态构造器的方式:
public class Person{
     public static int id;
     static Person(){ //这里不用加public
          id=1;
     }
}
  • Java 中一个普通类是不可以定义为 static 的, 只有内部类可以为静态类。
    而 C# 中是可以直接定义一个静态类的。

    Java静态内部类它能访问外部类的非静态成员。要创建静态内部类对象时候,也不需要实例化外部类对象,直接可以: Outer.Inner iner = new Outer.Inner();
    C#的静态类不能有非静态的字段或方法,且静态类不能被实例化。

继承

假设现在有一个基类Shape,它有一个子类Sphere
1)基础语法
C#的继承写法:class Sphere : Shape;
用:来表示继承,这和C++的语法一样。

Java的继承写法:class Sphere extends Shape。 使用关键字extends

C#和Java都只支持单继承。

2)在构造器中调用父类的构造器
C#中写法为:public Sphere(string name) : base(name);

Java中写法为在构造器里第一行加上super(参数列表)。如:

public Sphere(String name) {
	super(name);
	...
}

在子类中调用父类的方法,C#就是用base.方法名 ;Java是用super.方法名。base和super都只能调用父类非静态的方法。

3)串联构造器
比如我想在一个构造器里调用另一个构造器,
C#中写法为:public Sphere(string name) : this(参数列表)
Java中写法为在构造方法第一行加上this(参数列表)。

多态

1)重载
C#的重载规则和Java类似,个数和类型不同能够触发重载,返回值类型不同不能触发重载。
2)重写
C#中可被子类覆盖的成员方法需要在访问控制权限后面加上virtual关键字(类似于C++),Java中默认所有成员方法都可被子类覆盖。

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

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

//C#
public class Person{
	public virtual void Speak(){
		Console.WriteLine("I am a person.");
	}
}
public class Student : Person{
	public override void Speak(){
		Console.WriteLine("I am a student.");
	}
}

如果签名相同的方法在基类和派生类中都进行了声明,但是该方法没有分别声明为virtual和override,派生类就可以添加new关键字隐藏基类方法。
如:

class MyBaseClass{
     public int MyMethod(){}
}
//派生类(在派生类中把基类同名的方法隐藏掉了)
class MyDerivedClass :MyBaseClass{ 
 	 public new void MyMethod() {}    
}

比如项目中前期考虑不足,我没有在基类(ClassA)中写入A方法,但是派生类(ClassB)中由于需求早早的写完了A方法,并且又有许多类(ClassC,ClassD…)又继承了派生类(ClassB),并且重写了A方法。当这个时候我又想向最初的基类中(ClassA)添加A方法,又不想给它的派生类再加上override。那么有一种方法便是在ClassB中的A方法添加new关键字,来隐藏基类中的A方法

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

接口

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

class Rectangle : Shape, MyInterface

C#中接口内方法的访问权限默认是public。接口支持继承,定义接口也是用interface关键字。这些和Java基本一致。

之前我们说了C#中,重写的方法要加上override关键字,但是实现接口中的方法是个例外,我们不需要在重写接口方法时加上override

异常

C#中不能在方法签名后面用throws指明可能抛出的异常类型,其余的用法如(try-catch或者throw )和Java一致。

泛型

C#中,声明泛型类的方式和Java相同,都是在声明类时在类名后面加<泛型参数列表>,但声明泛型方法的语法不太一样,体现在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的泛型方法,要把移到void的前面。即:

public static <T> void swap(T a, T b){}

(Java 没有ref引用传参,这里就不写ref了)

另外在C#定义泛型的时候,可以使用where关键字来限制参数的范围。
例如:

void Swap<T> (ref T a ,ref T b) where T:class{}

这样限定了传入的参数只能是引用类型的。如果我调用方法时传入int类型就不行了,因为int是值类型。
泛型限定条件:

  • T:struct(类型参数必须是值类型。可以指定除 Nullable 以外的任何值类型)
  • T:class (类型参数必须是引用类型,包括任何类、接口、委托或数组类型)
  • T:new() (类型参数必须具有无参数的公共构造函数。当与其他约束一起使用时new() 约束必须最后指定)
  • T:基类名 (类型参数必须是指定的基类或派生自指定的基类 )
  • T:接口名称 (类型参数必须是指定的接口或实现指定的接口。可以指定多个接口约束。约束接口也可以是泛型的)
  • T:U (为 T 提供的类型参数必须是为 U 提供的参数或派生自为 U 提供的参数。也就是说T和U的参数必须一样)

你可能感兴趣的:(#,C#知识,java,c#)