一周学会C#系列


一周学会C#(前言)

大家好!C#作为微软在21世纪推出的新语言,它有着其他语言无法比拟的优势。但如何在短时间内迅速掌握它,却是一个比较难的问题。但如果你看完这个教程后,你一定会理解并掌握C#。

这个教程共分六个部分,今天先介绍C#中比较基本的概念。

1.总体框架

//Hiker.cs 类名不一定等于文件名
using System; //每一个程序必须在开头使用这一语句
public sealed class HitchHiker
{
    public static void Main()//程序从Main开始执行
    {
        int result;
        result = 9 * 6;
        int thirteen;
        thirteen = 13;
        Console.Write(result / thirteen); //输出函数
        Console.Write(result % thirteen);
    }
}
//上面各语句的具体用法以后会介绍
/* 这个程序用来 
 * 演示C#的总体框架
 */ 

注意:上面的程序中,符号//表示注释,在//后面的同一行上的内容是注释; /**/ 这间的内容都是注释
你可以在windows的命令行提示符下键入:csc Hiker.cs
进行编译产生可执行文件Hiker.exe
然后在windows的命令行提示符下键入:Hiker,你就可以看到在屏幕上显视42 (注:你必须装有.net framework)

和Java不一样,C#源文件名不一定要和C#源文件中包含的类名相同。
C#对大小写敏感,所以Main的首字母为大写的M(这一点大家要注意,尤其是熟悉C语言的朋友)。
你可以定义一个返回值为int的Main函数,当返回值为0时表示成功:
public static int Main() { ... return 0; }
你也可以定义Main函数的返回值为void:
public static void Main() { ... }
你还可以定义Main函数接收一个string数组:

public static void Main(string[] args)
{
    foreach (string args in args) {
        System.Console.WriteLine(arg);
    }
}

程序中的Main函数必须为static

2.标识符

标识符起名的规则:

局部变量、局部常量、非公有实例域、函数参数使用camelCase规则;其他类型的标识符使用PascalCase规则。

  • privateStyle camelCase规则(第一个单词的首字母小写,其余单词的首字母大写)
  • PublicStyle PascalCase规则(所有单词的首字母大写)

尽量不要使用缩写。

Message,而不要使用msg。

不要使用匈牙利命名法。

public sealed class GrammarHelper
{   
    ...
    public QualifiedSymbol Optional(AnySymbol symbol) { ... }
    private AnyMultiplicity optional = new OptionalMultiplicity();

}

3.关键字

C#中76个关键字:

abstract   as        base          bool         break
byte       case      catch         char         checked
class      const     continue      decimal      default
delegate   do        double        else         enum
event      explicit  extern        false        finally
fixed      float     for           foreach      goto
if         implicit  in            int          interface
internal   is        lock          long         namespace
new        null      object        operator     out
override   params    private       protected    public
readonly   ref       return        sbyte        sealed
short      sizeof    stackalloc    static       string
struct     switch    this          throw        true
try        typeof    uint          ulong        unchecked
unsafe     ushort    using         virtual      void
while

5个在某些情况下是关键字:get set value add remove

C#中有76个在任何情况下都有固定意思的关键字。另外还有5个在特定情况下才有固定意思的标识符。例如,value能用来作为变量名,但有一种情况例外,那就是它用作属性/索引器的set语句的时候是一关键字。
但你可以在关键字前加@来使它可以用作变量名:int @int = 42;
不过在一般情况下不要使用这种变量名。

你也可以使用@来产生跨越几行的字符串,这对于产生正则表达式非常有用。例如:

string pattern = @"
           (               # start the group
             abra(cad)?  # match abra and optional cad
           )+";           // one or more occurrences

如果你要在字符串中包含双引号,那你可以这样:string quote = @"""quote""";

一周学会C#(前言续)

C#才鸟(QQ:249178521)

4.标点符号

{} 组成语句块
;分号表示一个语句的结束

using System;
public sealed class Hiker
{
    public static void Main()
    {
        int result;
        result = 9 * 6;
        int thirteen;
        thirteen = 13;
        Console.Write(result / thirteen);
        Console.Write(result % thirteen);
    }
}

一个C#的“类/结构/枚举”的定义不需要一个终止的分号。

public sealed class Hiker
{
    ...
} // 没有;是正确的

然而你可以使用一个终止的分号,但对程序没有任何影响:

public sealed class Hiker
{
    ...
}; //有;是可以的但不推荐

在Java中,一个函数的定义中可以有一个结尾分号,但在C#中是不允许的。

public sealed class Hiker
{
    public void Hitch() { ... }; //;是不正确的
} // 没有;是正确的

5.声明

声明是在一个块中引入变量

  • 每个变量有一个标识符和一个类型
  • 每个变量的类型不能被改变
using System;
public sealed class Hiker
{
    public static void Main()
    {
        int result;
        result = 9 * 6;
        int thirteen;
        thirteen = 13;
        Console.Write(result / thirteen);
        Console.Write(result % thirteen);
    }
}

这样声明一个变量是非法的:这个变量可能不会被用到。例如:

if (...)
    int x = 42; //编译时出错
else
    ...

6.表达式

表达式是用来计算的!

  • 每个表达式产生一个值
  • 每个表达式必须只有单边作用
  • 每个变量只有被赋值后才能使用
using System;
public sealed class Hiker
{
    public static void Main()
    {
        int result;
        result = 9 * 6;
        int thirteen;
        thirteen = 13;
        Console.Write(result / thirteen);
        Console.Write(result % thirteen);
    }
}

C#不允许任何一个表达式读取变量的值,除非编译器知道这个变量已经被初始化或已经被赋值。例如,下面的语句会导致编译器错误:

int m;
if (...) {
    m = 42;
}

Console.WriteLine(m);// 编译器错误,因为m有可能不会被赋值

7.取值

类型取值解释
booltrue false布尔型
float3.14实型
double3.1415双精度型
char'X'字符型
int9整型
string"Hello"字符串
objectnull对象

一周学会C#(前言续二)

8.操作符

操作符类型
+ - * / %(取余数)算术
&& || ! ?:逻辑
< <= > >=关系
== !=相等
=赋值

9.编程风格

较正规的编程风格

  • 在一个二元操作符的每一边都加一个空格
  • 在每一个逗号后面而不是前面加一个空格
  • 每一个关键字后面加一个空格
  • 一行一个语句
  • 分号前不要有空格
  • 函数的园括号和参数之间不加空格
  • 在一元操作符和操作数之间不加空格

在一个二元操作符的每一边都加一个空格:

Console.WriteLine("{0}", result / 13);  //推荐
Console.WriteLine("{0}", result/13);  //不推荐

在每一个逗号后面而不是前面加一个空格:

Console.WriteLine("{0}", result / 13); //推荐
Console.WriteLine("{0}",result / 13); //不推荐

每一个关键字后面加一个空格:

if (OneLine(comment)) ...   //推荐
if(OneLine(comment)) ...    //不推荐

分号前不要有空格:

Console.WriteLine("{0}", result / 13); //推荐
Console.WriteLine("{0}", result / 13) ; //不推荐

函数的园括号和参数之间不加空格:

if (OneLine(comment)) ...  //推荐
if (OneLine( comment )) ... //不推荐

在一元操作符和操作数之间不加空格:

++keywordCount; //推荐
++ keywordCount; //不推荐

10.找错

bool checked;
...                                         1

public static void main()
{ ... }                                     2

int matched = symbol.Match(input)
if (matched > 0)
{                                           3
    ....
}

char optional = "?";
string theory = 'complex';                  4

int matched = 0_or_more(symbol);            5
...
  • 第1段程序的错误:checked是一个关键字
  • 第2段程序的错误:不是main,而是Main
  • 第3段程序的错误:变量声明语句没有分号
  • 第4段程序的错误:字符值必须用单引号表示,字符串必须用双引号表示
  • 第5段程序的错误:第一个错误是标识符不能以数字开头;第二个错误是不能用下划线作标识符。

一周学会C#(函数一)

1.前言

  • C#不支持全局函数

    所有的函数必须在类内部声明

  • 无源文件和头文件之分

    所有的函数必须声明的时候被实现

int NotAllowed()      //错误,C#没有全局函数
{
    ...
}
sealed class Methods
{
    void Inline()
    { ...
    }
    void Error() 
    { ...
    };                //错误,函数不能有结尾分号
    int AlsoError();  //错误,函数必须声明的时候被实现
}

和Java一样,C#不允许有全局函数。所有的函数必须在类或结构内实现。函数是类或结构的成员,函数也被称为方法。

C#允许可以在类的声明中加入结尾分号,例如:

sealed class Methods
{
    ...
};//可以有结尾分号

但是,C#不允许在函数的声明中加入结尾分号,例如:

sealed class Methods
{
    void NotAllowed() {...} ; //错误,函数不能有结尾分号
}

2.声明函数

数参数列表

  • 各参数以逗号隔开
  • 参数必须命名
  • 没有参数时括号不能省略
sealed class Methods
{
    void Error(float) //错误,参数没有命名
    { ...
    }
    void NoError(float delta)
    { ...
    } 

    int Error(void) //错误,无参数时不允许使用void
    { ... 
    } 
    int NoError() 
    { ... 
    }
} 

3. 值型参数

一般的函数参数是实参的一个拷贝

  • 实参必须预先被赋值
  • 实参可以是常量类型
sealed class ParameterPassing
{
    static void Method(int parameter)
    {
        parameter = 42;
    }
    static void Main()
    {
        int arg = 0;
        Console.Write(arg); //结果为0
        Method(arg);
        Console.Write(arg); //结果为0
    }
}

注:为了叙述的方便,以后所出现的“参数”这个词均指函数参数,也就是所谓的形参

没有被refout修饰的函数参数是一个值型参数。值型参数只有在该参数所属的函数被调用的时候才存在,并且用调用时所传递的实参的值来进行初始化。当函数调用结束时,值型参数不复存在。

只有被预先赋值的实参才能被传递给值型参数,例如:

int arg;    // arg没有被赋初值
Method(arg);//错误,实参必须预先赋初值

传递给函数的实参可以是纯粹的数而不是变量,例如:

Method(42);
Method(21 + 21);

一周学会C#(函数二)

4.引用型参数

引用型参数是实参的一个别名

  • 没有发生复制
  • 实参必须预先被赋值
  • 实参必须是一个变量类型
  • 实参和函数参数都要有ref
sealed class ParameterPassing
{
    static void Method(ref int parameter)
    {
        parameter = 42;
    }

    static void Main()
    {
        int arg = 0;
        Console.Write(arg); //结果为0
        Method(ref arg);
        Console.Write(arg); //结果为42
    }
}

函数参数有ref修饰符时,被称为引用型参数。引用型参数不产生新的存储区间。实际上,引用型参数是函数调用时所传递的实参所代表的变量的别名。结果是引用型参数只是实参所代表的变量的另一个名字。

ref修饰符必须同时出现在函数声明语句和函数调用语句中。

只有被预先赋值的实参才能被传递给引用型参数,例如:

int arg;    // arg没有被赋初值
Method(ref arg);//错误,实参必须预先赋初值

传递给引用型参数的实参必须是变量类型,而不能是纯粹的值或常量。

Method(ref 42);  //错误,引用型参数的实参不能是纯粹的值
const int arg = 42;
Method(ref arg); //错误,引用型参数的实参不能是常量

5.out型参数

out型参数是实参的一个别名

  • 没有发生复制
  • 实参不必预先赋值
  • 实参必须是变量类型
  • 函数参数必须被预先赋值才能使用
  • 实参和函数参数都要有out
sealed class ParameterPassing
{
    static void Method(out int parameter)
    {
        parameter = 42;
    }
    static void Main()
    {
        int arg;
        //Console.Write(arg);
        Method(out arg);
        Console.Write(arg); //结果为42
    }
}

函数参数有out修饰符时,被称为out型参数。out型参数不产生新的存储区间。实际上,out型参数是函数调用时所传递的实参所代表的变量的别名。结果是out型参数只是实参所代表的变量的另一个名字。

out修饰符必须同时出现在函数声明语句和函数调用语句中。

没有被预先赋值的实参能够被传递给引用型参数,例如:

int arg;    // arg没有被赋初值
Method(out arg);//正确,实参可以不赋初值

传递给out型参数的实参必须是变量类型,而不能是纯粹的值或常量。

Method(out 42);  //错误,out型参数的实参不能是纯粹的值
const int arg = 42;
Method(out arg); //错误,out型参数的实参不能是常量

6.in型参数?

readonly, constin, 都是C# 关键字

  • 它们不能被用于函数参数
  • ref/out 型参数总是被赋于写的权力

7.函数重载

一个类中的函数可以有同一个名字,称为重载

  • 函数名和参数称为标识
  • 标识必须唯一
  • 返回值类型不是标识
namespace System
{
    public sealed class Console
    {
        public static void WriteLine()
        { ... }
        public static void WriteLine(int value)
        { ... }
        public static void WriteLine(double value)
        { ... }
        ...
        public static void WriteLine(object value)
        { ... }
        ...
    }
}

和C++与Java一样,C#允许一个类声明两个以上的同名函数,只要参数的类型或个数不同。这就是重载。但是,一个类不能包含标识为相同的实例函数和静态函数,例如:

sealed class Illegal
{
    void Overload() { ... }
    static void Overload() { ... }//错误
}

和C++与Java一样,返回值的类型不是标识的一部分,不能被用作重载的标准,例如:

sealed class AlsoIllegal
{
    int Random() { ... }
    double Random() { ... }//错误
}

一周学会C#(函数三)

8.ref/out重载

ref/out 在大部分情况下是标识的一部分!

  • 你可以重载一个ref型参数和一个普通参数
  • 你可以重载一个out型参数和一个普通参数
  • 你不可以重载一个ref型参数和一个out型参数
sealed class Overloading
{
    void Allowed(int parameter)
    { ... }
    void Allowed(ref int parameter)
    { ... }
    //正确,重载一个ref型参数和一个普通参数

    void AlsoAllowed(int parameter)
    { ... }
    void AlsoAllowed(out int parameter)
    { ... }

    //正确,重载一个out型参数和一个普通参数
    void NotAllowed(ref int parameter)
    { ... }
    void NotAllowed(out int parameter)
    { ... }
    //错误,不能重载一个ref型参数和一个out型参数
}

refout修饰符可以是一个函数的标识。但是你不能同时重载refout型参数。refout修饰符在某种意义上是“安全的“,因为只有ref型实参才能传递给ref型函数参数,只有out型实参才能传递给out型函数参数。但是,当调用函数的时候,你会非常容易忘记refout修饰符,所以最好不要重载refout型参数。例如:

sealed class Overloading
{
    public static void Example(int parameter)
    { ... }
    public static void Example(ref int parameter)
    { ... }
    static void Main()
    {
        int argument = 42;
        Example(argument);//在这儿非常容易忘记ref修饰符
    }
}

9.访问规则

函数参数或返回值不能比所属函数的访问级别低

sealed class T { ... }  //类的默认访问级别是internal
public sealed class Bad
{
    public void Parameter(T t)    //错误,函数的访问级别(public)比参数高
    { ... }
    public T Return()             //错误,函数的访问级别(public)比返回值高
    { ... }
}

public sealed class Good
{
    private void Parameter(T t)   //正确,函数的访问级别(private)比参数低
    { ... }
    private T Return()            //正确,函数的访问级别(private)比返回值低
    { ... }
}

10.找错误

sealed class Buggy
{
    void Defaulted(double d = 0.0)             1
    { ...
    }
    void ReadOnly(const ref Wibble w)          2
    { ... 
    }
    ref int ReturnType()                       3
    { ... 
    }
    ref int fieldModifier;                     4
}
  • 第1个函数的错误是:C#中函数不能拥有缺省参数。
  • 第2个函数的错误是:ref型参数不能用const修饰,因为ref型参数是可能变化的。
  • 第3,4个函数的错误是:refout型参数只能用于函数参数和实参。

C#中可以通过函数重载的办法实现缺省参数的功能,以下是实现的方法:

sealed class Overload
{
    void DefaultArgument() { DefaultArgument(0.0); }
    void DefaultArgument(double d) { ... }
}
Contributors: FHL