C#与Java的语法差异

时间:2022-05-05 14:51:48

 

C#与Java的语法差异
C与Java的语法差异
前言
程序结构
基本语法
数据类型
字符串
变量与常量
运算符
判断语句
循环语句
访问权限
方法
数组
结构
枚举

继承
多态
运算符重载
接口
命名空间
预处理器指令
正则表达式
异常
IO
泛型
集合
属性
索引器
委托
事件
匿名方法
前言
本文主要供有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;
1
一般类型变量的类型检查在编译时发生,动态类型变量的类型检查在运行时才发生。比如下面的例子,编译器不会报错:

dynamic d = 100.0;
int x = d;
1
2
C#中可以使用指针,和C与C++中的指针功能相同。

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

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

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

String str = @"c:\windows\";
1
会输出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();
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
可以看到,虽然SIZE没有用static修饰,它仍然是静态的。这和Java不同,Java中静态常量必须声明为static的。

判断语句
和Java基本相同。

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

int[] array = new int[]{1,2,3,4,5};
foreach (int element in array)
{
...
}
1
2
3
4
5
Java中的foreach如下:

int[] array = new int[]{1,2,3,4,5};
for(int element : array){
...
}
1
2
3
4
访问权限
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;
}
1
2
3
4
5
6
7
8
使用时也要给参数加上ref:

int x = 1;
int y = 2;
Swap(ref x, ref y);
1
2
3
上面的方法调用会改变x和y的值。

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

public static void GetValue(out int x)
{
x = 5;
}
1
2
3
4
提供给输出参数的变量不需要赋值,不过传递给方法时要加上out:

int a;
GetValue(out a);
Console.WriteLine(a);
1
2
3
会发现a已经被成功赋值。
注意:如果方法中未给输出参数赋值,编译器会报错。

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

public int AddElements(params int[] arr)
{
int sum = 0;
foreach (int i in arr)
{
sum += i;
}
return sum;
}
1
2
3
4
5
6
7
8
9
数组
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}
};
1
2
3
4
5
6
使用array[x,y]引用上面的数组的元素。

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

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

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

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

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

enum Days { Sun, Mon, tue, Wed, thu, Fri, Sat };
1
默认情况下,第一个枚举符号的值为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;
}
1
2
3
4
5
6
7
8
9
继承
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();
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
下面是对各个运算符能否重载的总结:
- +, -, !, ~, ++, – 这些一元运算符只有一个操作数,且可以被重载。
- +, -, *, /, % 这些二元运算符带有两个操作数,且可以被重载。
- ==, !=, <, >, <=, >= 这些比较运算符可以被重载。
- &&, || 这些条件逻辑运算符不能被直接重载。
- +=, -=, *=, /=, %= 这些赋值运算符不能被重载。
- =, ., ?:, ->, new, is, sizeof, typeof 这些运算符不能被重载。

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

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

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

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

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

namespace namespace_name
{
class A{

}
}
1
2
3
4
5
6
使用命名空间中的类时需要加上命名空间名和’.’:

namespace_name.A a = new namespace_name.A();
1
可以在开头加上using 命名空间名,这样就不用每次引用命名空间中的类时加上前缀。

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

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

using static System.Math;var = PI; // 直接使用System.Math.PI
1
(2)起别名

using Project = PC.MyCompany.Project;
1
(3)using语句:将实例与代码绑定

using (Font font3 = new Font("Arial", 10.0f),
font4 = new Font("Arial", 10.0f))
{
// Use font3 and font4.
}
1
2
3
4
5
代码段结束时,自动调用font3和font4的Dispose方法,释放实例。

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

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

#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
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
正则表达式
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();
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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("");
}
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
Age就是一个属性。当外部需要获取Age的值时会自动调用get,当外部需要设置Age的值时会自动调用set。注意set中的value,它指代的是外部想要为其设置的值。
对于声明了属性的类,还可以用下面的方式来初始化:

Person person = new Person
{
Age = 15
};
1
2
3
4
访问属性时时使用属性名,而不是字段名:

int x = person.Age;
person.Age++;
1
2
注意Age = 15后面没有分号。

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

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

public String Name { get; set; } = "father";
1
可以为属性的某个访问器声明访问控制权限。

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

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

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

// set 访问器
set
{
// 设置 index 指定的值
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
示例:

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();
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
索引器还可以被重载,重载规则和方法的相同。

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

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

public delegate int DoSomething(int x);
1
这个委托的实例可指向一个参数为一个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();
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
委托实例还可以利用+、-、+=、-=、=等进行各种方法拼接、移除操作,这叫做委托的多播。调用这样的委托的实际效果相当于按照顺序依次调用一系列的方法。

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

public delegate void NumManipulationHandler(int value);
1
(2)使用event关键字声明事件:

public event NumManipulationHandler NumChanged;
1
(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();
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
其实这和Java中先定义回调接口,之后在外部实现回调接口的思想很像。

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

public delegate void DoSomething(int x);

DoSomething doSomething = delegate (int x)
{
Console.WriteLine(x);
};
---------------------
作者:swt369
来源:CSDN
原文:https://blog.csdn.net/swt369/article/details/78238130
版权声明:本文为博主原创文章,转载请附上博文链接!