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
版权声明:本文为博主原创文章,转载请附上博文链接!