但是对于同一个知识点,这两门语言在语法上可能略有不同。本文将介绍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#添加了几种特殊的数据类型:
var
var是种推断类型,根据所赋数据的值推断出变量的类型。如 var a=1;
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进行++操作。
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和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#的参数传递有三种形式:
(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;
}
}
public class Person{
~Person(){
Console.Write("person");
}
static void Main(){
Person p=new Person();
}
}
我们仅仅创建了一个Person类的对象,当运行这段程序时,会发现输出了person,这是因为程序运行结束时会销毁对象p,此时会调用类的析构函数。
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的泛型方法,要把
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是值类型。
泛型限定条件: