Skip to content

3. C# 语言基础

3.1. 简介

我们将首先将 C# 视为一种经典的编程语言。关于类的内容我们稍后再讨论。一个程序包含两部分:

  • 数据
  • 处理这些数据的指令

我们通常会尝试将数据与指令分离:

3.2. C# 数据

C# 使用以下数据类型:

  1. 整数
  2. 实数
  3. 十进制数
  4. 字符和字符串
  5. 布尔值
  6. 对象

3.2.1. 预定义数据类型

C# 类型
.NET 类型
表示的数据
后缀
字面值
编码
值域
char
字符 (S)
字符
 
2字节
Unicode字符(UTF-16)
字符串
字符串 (C)
字符串
  
对一串 Unicode 字符的引用
int
Int32 (S)
整数
 
4字节
[-2³¹ , 2³¹-1] [-2147483648, 2147483647]
uint
UInt32 (S)
..
U
4 字节
[0, 2³²-1] [0, 4294967295]
long
Int64 (S)
..
L
8 字节
[-263, 263 -1] [-9223372036854775808, 9223372036854775807]
ulong
UInt64 (S)
..
UL
8 字节
[0, 264 -1] [0, 18446744073709551615]
sbyte
 
..
 
1 字节
[-27, 27 -1] [-128, +127]
字节
字节 (S)
..
 
1 字节
[0, 28 -1] [0,255]
短整型
Int16 (S)
..
 
2字节
[-215, 215-1] [-32768, 32767]
ushort
UInt16 (S)
..
 
2字节
[0, 2¹⁶-1] [0,65535]
浮点数
单精度 (S)
实数
F
4字节
[1.5 × 10⁻⁴⁵, 3.4 × 10³⁸(绝对值)
双精度
双精度 (S)
..
D
8 字节
[-1.7 × 10³⁰⁸, 1.7 × 10³⁰⁸(绝对值)
十进制
十进制 (S)
十进制数
M
16 字节
[1.0 10⁻²⁸, 7.9 10²⁸] 绝对值,有效数字为 28 位
bool
布尔 (S)
..
 
1 字节
true, false
对象
对象 (C)
对象引用
  
对象引用

上文中的 C# 类型均以其对应的 .NET 类型表示,若类型为结构体则标注 (S),若类型为类则标注 (C)。我们发现 32 位整数有两种可能的类型:intInt32。其中 int 类型是 C# 类型。 Int32 是属于 System 名称空间的结构体,其全名为 System.Int32。类型 int 是 C# 对 .NET 结构体 System.Int32 的别名。同样地,C# 中的 string 是 .NET 类型 System.String 的别名。System.String 是一个,而非结构体。这两个概念虽相似,但存在以下根本区别:

  • 结构类型(Structure)的变量可通过其进行操作
  • 类型的变量可通过其地址(在面向对象语言中称为引用)进行操作。

结构和都是带有属性和方法的复杂类型。例如,我们可以编写:

string nomDuType=3.GetType().FullName;

上文中的数字 3 在 C# 中默认是 int 类型,即 .NET 中的 System.Int32。该结构拥有一个 GetType() 方法,该方法会返回一个封装了 System.Int32 数据类型特征的对象。其中包括 FullName 属性,该属性返回该类型的完整名称。如您所见,数字 3 比乍看之下要复杂得多。

以下是一个说明这些要点的程序:


using System;
 
namespace Chap1 {
    class P00 {
        static void Main(string[] args) {
             // example 1
            int ent = 2;
            float fl = 10.5F;
            double d = -4.6;
            string s = "essai";
            uint ui = 5;
            long l = 1000;
            ulong ul = 1001;
            byte octet = 5;
            short sh = -4;
            ushort ush = 10;
            decimal dec = 10.67M;
            bool b = true;
            Console.WriteLine("Type de ent[{1}] : [{0},{2}]", ent.GetType().FullName, ent,sizeof(int));
            Console.WriteLine("Type de fl[{1}]: [{0},{2}]", fl.GetType().FullName, fl, sizeof(float));
            Console.WriteLine("Type de d[{1}] : [{0},{2}]", d.GetType().FullName, d, sizeof(double));
            Console.WriteLine("Type de s[{1}] : [{0}]", s.GetType().FullName, s);
            Console.WriteLine("Type de ui[{1}] : [{0},{2}]", ui.GetType().FullName, ui, sizeof(uint));
            Console.WriteLine("Type de l[{1}] : [{0},{2}]", l.GetType().FullName, l, sizeof(long));
            Console.WriteLine("Type de ul[{1}] : [{0},{2}]", ul.GetType().FullName, ul, sizeof(ulong));
            Console.WriteLine("Type de b[{1}] : [{0},{2}]", octet.GetType().FullName, octet, sizeof(byte));
            Console.WriteLine("Type de sh[{1}] : [{0},{2}]", sh.GetType().FullName, sh, sizeof(short));
            Console.WriteLine("Type de ush[{1}] : [{0},{2}]", ush.GetType().FullName, ush, sizeof(ushort));
            Console.WriteLine("Type de dec[{1}] : [{0},{2}]", dec.GetType().FullName, dec, sizeof(decimal));
            Console.WriteLine("Type de b[{1}] : [{0},{2}]", b.GetType().FullName, b, sizeof(bool));
        }
    }
}
  • 第 7 行:声明一个整型变量 ent
  • 第 19 行:变量 v 的类型可通过 v.GetType().FullName 获取。S 结构的大小可通过 sizeof(S) 获取。指令 Console.WriteLine("... {0} ... {1} ...", param0, param1, ...) 会在屏幕上输出其第一个参数的文本,并将每个 {i} 标记替换为表达式 parami 的值。
  • 第 22 行:无法使用 string 运算符,因为它指定的是类而非结构体 sizeof

结果如下:

Type de ent[2] : [System.Int32,4]
Type de fl[10,5]: [System.Single,4]
Type de d[-4,6] : [System.Double,8]
Type de s[essai] : [System.String]
Type de ui[5] : [System.UInt32,4]
Type de l[1000] : [System.Int64,8]
Type de ul[1001] : [System.UInt64,8]
Type de b[5] : [System.Byte,1]
Type de sh[-4] : [System.Int16,2]
Type de ush[10] : [System.UInt16,2]
Type de dec[10,67] : [System.Decimal,16]
Type de b[True] : [System.Boolean,1]

显示结果为 .NET 类型,而非 C# 别名。

3.2.2. 字面量数据的表示法

整数 int (32 位)
145、-7、0xFF(十六进制)
整数 long(64 位) - 后缀 L
100000L
双精度实数
134.789, -45E-18 (-45 × 10⁻¹⁸)
浮点数(后缀 F)
134.789F, -45E-18F (-45 × 10⁻¹⁸)
十进制实数(后缀 M)
100000M
字符 char
'A', 'b'
字符串 string
"today" "c:\chap1\paragraph3" @"c:\chap1\paragraph3"
布尔值 bool
true, false
日期
new DateTime(1954,10,13) (年, 月, 日) 表示 1954年10月13日

请注意以下两个字面字符串:"c:\chap1\\paragraph3" 和 @"c:\chap1\paragraph3"。在字面字符串中,\ 字符会被解释。因此,"\n" 代表行尾标记,而不是 \ 和 n 这两个字符的连续组合。如果我们想要这个连续组合,就必须写成 "\\n",这样该序列才会被解释为单个 \ 字符。 你也可以写 @"\n" 来获得相同的结果。语法 @"text" 要求将文本完全按原样处理。这有时被称为字符串原样引用

3.2.3. 数据报告

3.2.3.1. 声明的作用

程序操作的数据由名称和类型来定义。这些数据存储在内存中。当程序被编译时,编译器会为每个数据项分配一个由地址和大小定义的内存位置。它通过程序员编写的声明来完成这一操作。

声明还能帮助编译器检测编程错误。例如,

x=x*2;

如果 x 是字符串,则会被判定为错误。

3.2.3.2. 常量的声明

常量的声明语法如下:

    const type nom=valeur;          //définit constante nom=valeur

例如:

const float myPI=3.141592F;    

为什么要声明常量?

  1. 如果给常量起一个有意义的名称,程序会更易于阅读:
    const  float taux_tva=0.186F;
  1. 如果“常量”发生变化,修改程序会更加容易。例如,在前面的例子中,如果增值税税率变为 33%,唯一需要修改的就是定义其值的语句:
    const float taux_tva=0.33F;

如果程序中显式使用了0.186,则将不得不修改许多语句。

3.2.3.3. 变量声明

变量通过名称来标识,并指向一种数据类型。C# 区分大小写。因此 FINend 是不同的。

变量可以在声明时进行初始化。声明一个或多个变量的语法如下:

 Identificateur_de_type variable1[=valeur1],variable2=[valeur2],...;

其中类型标识符可以是预定义类型,也可以是程序员定义的类型。此外,变量在声明时也可以进行初始化。

您还可以通过使用关键字 var 代替 类型标识符 来避免指定变量的确切类型:

 var variable1=valeur1,variable2=valeur2,...;

关键字 var 并不意味着变量没有特定类型。变量 variablei 被赋予了数据类型 valuei。此处初始化是强制要求的,以便编译器能够推导出变量的类型。

以下是一个示例:


using System;
 
namespace Chap1 {
    class P00 {
        static void Main(string[] args) {
            int i=2;
            Console.WriteLine("Type de int i=2 : {0},{1}",i.GetType().Name,i.GetType().FullName);
            var j = 3;
            Console.WriteLine("Type de var j=3 : {0},{1}", j.GetType().Name, j.GetType().FullName);
            var aujourdhui = DateTime.Now;
            Console.WriteLine("Type de var aujourdhui : {0},{1}", aujourdhui.GetType().Name, aujourdhui.GetType().FullName);
        }
    }
}
  • 第 6 行:显式类型的数据
  • 第 7 行:(data).GetType().Name 是 (data) 的短名称,(data).GetType().FullName 是 (data) 的全名
  • 第 8 行:隐式类型数据。由于 3 的类型为 int,因此 j 的类型也将是 int
  • 第 10 行:隐式类型数据。由于 DateTime.Now 的类型是 DateTime,因此 today 的类型也将是 DateTime

执行后,我们得到以下结果:

1
2
3
Type de int i=2 : Int32,System.Int32
Type de var j=3 : Int32,System.Int32
Type de var aujourdhui : DateTime,System.DateTime

通过关键字 var 隐式声明的变量无法更改类型。例如,在代码第 10 行之后,该行:


            var aujourdhui = "aujourd'hui";

我们稍后将看到,可以在表达式中“动态”声明类型。在此情况下,它是一个匿名类型,即用户未为其命名的类型。编译器会为该新类型命名。如果要将匿名类型赋值给变量,声明它的唯一方法是使用关键字 var

3.2.4. 数字与字符串之间的转换

数字 -> 字符串
nombre.ToString()
字符串 -> 整数
int.Parse(string) 或 System.Int32.Parse
字符串 -> long
long.Parse(string) 或 System.Int64.Parse
字符串 -> 双精度
double.Parse(string) 或 System.Double.Parse(string)
字符串 -> 浮点数
float.Parse(string) 或 System.Float.Parse(string)

如果字符串不表示一个有效的数字,将字符串转换为数字可能会失败。 引发名为 exception 的致命错误。可以通过以下 try/catch 语句处理此错误:


try{
    appel de la fonction susceptible de générer l'exception
} catch (Exception e){
    traiter l'exception e
}
instruction suivante

如果函数没有抛出异常,我们就执行下一条指令;否则,我们就进入 catch 子句的主体,然后执行下一条指令。我们稍后会再讨论异常处理。下面是一个演示数字与字符串之间转换技巧的程序。在这个示例中,函数 poster 会将其参数的值输出到屏幕上。因此,poster(S) 会将 S 的值输出到屏幕上,其中 S 的类型为字符串


using System;
 
namespace Chap1 {
    class P01 {
        static void Main(string[] args) {
 
             // data
            const int i = 10;
            const long l = 100000;
            const float f = 45.78F;
            double d = -14.98;
 
             // number --> string
            affiche(i.ToString());
            affiche(l.ToString());
            affiche(f.ToString());
            affiche(d.ToString());
 
             //boolean --> string
            const bool b = false;
            affiche(b.ToString());
 
             // string --> int
            int i1;
            i1 = int.Parse("10");
            affiche(i1.ToString());
            try {
                i1 = int.Parse("10.67");
                affiche(i1.ToString());
            } catch (Exception e) {
                affiche("Erreur : " + e.Message);
            }
 
             // chain --> long
            long l1;
            l1 = long.Parse("100");
            affiche(l1.ToString());
            try {
                l1 = long.Parse("10.675");
                affiche(l1.ToString());
            } catch (Exception e) {
                affiche("Erreur : " + e.Message);
            }
 
             // chain --> double
            double d1;
            d1 = double.Parse("100,87");
            affiche(d1.ToString());
            try {
                d1 = double.Parse("abcd");
                affiche(d1.ToString());
            } catch (Exception e) {
                affiche("Erreur : " + e.Message);
            }
 
             // string --> float
            float f1;
            f1 = float.Parse("100,87");
            affiche(f1.ToString());
            try {
                d1 = float.Parse("abcd");
                affiche(f1.ToString());
            } catch (Exception e) {
                affiche("Erreur : " + e.Message);
            }
 
         }// fine hand
 
        public static void affiche(string S) {
            Console.Out.WriteLine("S={0}",S);
        }
     }// end of class
}

第 30-32 行处理可能发生的异常。e.Message 是与异常 e 关联的错误消息。

结果如下:

S=10
S=100000
S=45,78
S=-14,98
S=False
S=10
S=Erreur : Input string was not in a correct format.
S=100
S=Erreur : Input string was not in a correct format.
S=100,87
S=Erreur : Input string was not in a correct format.
S=100,87
S=Erreur : Input string was not in a correct format.

请注意,字符串形式的实数必须使用逗号,而不是小数点。因此,我们写成

double d1=10.7; 

double d2=int.Parse("10,7");

3.2.5. 数据表

C# 数组是一种用于将同类型数据归类到同一标识符下的对象。其声明如下:

Type[] tableau[]=new Type[n]

n 是数组可容纳的数据项数量。语法 Table[i] 表示第 i 个数据项,其中 i 属于区间 [0,n-1]。任何对数据 Table[i] 的引用,若 i 不属于区间 [0,n-1],都将引发异常。数组在声明时也可以进行初始化:

    int[] entiers=new int[] {0,10,20,30};

或者简写为:

    int[] entiers={0,10,20,30};

数组有一个名为 Length 的属性,它表示数组中的元素个数。

二维数组可以声明如下:

Type[,] array = new Type[n, m];

其中 n 表示行数,m 表示列数。语法 Table[i,j] 表示表格第 i 行中的第 j 个元素。二维数组也可以在声明时同时进行初始化:

    double[,] réels=new double[,] { {0.5, 1.7}, {8.4, -6}};

或者简写为:

    double[,] réels={ {0.5, 1.7}, {8.4, -6}};

每个维度的元素个数可通过 GetLength(i) 获取,其中 i=0 表示对应第一个索引的维度,i=1 表示对应第二个索引的维度,依此类推

维数总数可通过 Rank 属性获取,元素总数可通过 Length 获取。

表的表声明如下:

Type[][] array = new Type[n][];

上述语句创建了一个包含 n 行表的数组。每个元素 table[i] 都是一个一维数组引用。在上述声明中,这些引用 array[i] 并未被初始化。它们的值是引用 null

以下示例演示了表数组的创建:


            // un tableau de tableaux
            string[][] noms = new string[3][];
            for (int i = 0; i < noms.Length; i++) {
                noms[i] = new string[i + 1];
            }//for
            // initialisation
            for (int i = 0; i < noms.Length; i++) {
                for (int j = 0; j < noms[i].Length; j++) {
                    noms[i][j] = "nom" + i + j;
                }//for j
            }//for i
  • 第 2 行:一个名为 names 的 3 维字符串数组(string[][])。每个元素都是一个数组指针(对象引用),其元素类型为 string[]
  • 第3-5行:数组 names 的 3 个元素被初始化。现在每个元素都“指向”一个类型为 string[] 的数组。names[i][j] 即为由 names[i] 引用的第 j string[] 数组。
  • 第 9 行:在双重循环内初始化数组 names[i][j] 的元素。此处 names[i] 是一个包含 i+1 个元素的数组。由于 names[i] 是一个数组,因此 names[i].Length 表示其元素个数。

以下是关于我们刚刚介绍的三种表类型的示例:


using System;
 
namespace Chap1 {
     // tables
 
    using System;
 
     // test class
    public class P02 {
        public static void Main() {
             // an initialized 1-dimensional array
            int[] entiers = new int[] { 0, 10, 20, 30 };
            for (int i = 0; i < entiers.Length; i++) {
                Console.Out.WriteLine("entiers[{0}]={1}", i, entiers[i]);
            }//for
 
             // an initialized 2-dimensional array
            double[,] réels = new double[,] { { 0.5, 1.7 }, { 8.4, -6 } };
            for (int i = 0; i < réels.GetLength(0); i++) {
                for (int j = 0; j < réels.GetLength(1); j++) {
                    Console.Out.WriteLine("réels[{0},{1}]={2}", i, j, réels[i, j]);
                }//for j
            }//for i
 
             // a table of tables
            string[][] noms = new string[3][];
            for (int i = 0; i < noms.Length; i++) {
                noms[i] = new string[i + 1];
            }//for
             // initialization
            for (int i = 0; i < noms.Length; i++) {
                for (int j = 0; j < noms[i].Length; j++) {
                    noms[i][j] = "nom" + i + j;
                }//for j
            }//for i
             // display
            for (int i = 0; i < noms.Length; i++) {
                for (int j = 0; j < noms[i].Length; j++) {
                    Console.Out.WriteLine("noms[{0}][{1}]={2}", i, j, noms[i][j]);
                }//for j
            }//for i
         }//Main
     }//class
}//namespace

执行后,我们得到以下结果:

entiers[0]=0
entiers[1]=10
entiers[2]=20
entiers[3]=30
réels[0,0]=0,5
réels[0,1]=1,7
réels[1,0]=8,4
réels[1,1]=-6
noms[0][0]=nom00
noms[1][0]=nom10
noms[1][1]=nom11
noms[2][0]=nom20
noms[2][1]=nom21
noms[2][2]=nom22

3.3. C# 基础语法

区分

1 计算机执行的基本指令。

2 用于控制程序顺序的指令。

当你考察微计算机及其外围设备的结构时,基本指令的含义便会变得清晰。

  1. 从键盘读取信息

  2. 信息处理

  3. 将信息写入屏幕

3.3.1. 在屏幕上显示

屏幕写入指令有多种:

Console.Out.WriteLine(expression)
Console.WriteLine(expression)
Console.Error.WriteLine (expression)

其中表达式可以是任何能够转换为字符串并在屏幕上显示的数据类型。所有 C# 或 .NET 对象都具有 ToString() 方法,用于执行此转换。

System.Console 类提供了屏幕写入操作(WriteWriteLine)。Console 类具有两个属性 Out 和 Error,它们是 TextWriter 类型的写入流

  • Console.WriteLine()Console.Out.WriteLine() 效果相同,通常会写入与屏幕关联的 Out 流。
  • Console.Error.WriteLine() 则写入 Error 流,该流通常与屏幕相关联。

如稍后将看到,OutError 流可在程序运行时重定向到文本文件。

3.3.2. 读取输入的数据

来自键盘的数据流由类型为 TextReaderConsole.In 对象表示。此类对象可用于通过 ReadLine 方法读取一行文本:

    string ligne=Console.In.ReadLine();

Console 类提供了一个 ReadLine 方法,该方法默认与 In 相关联。因此我们可以这样编写:

    string ligne=Console.ReadLine();

键盘上输入的行会被存储在变量 ligne* 中,随后程序可以使用该变量。与 **Out**Error 流一样,In* 流也可以重定向到文件。

3.3.3. 输入输出示例

以下是一个简短的程序,用于演示键盘/屏幕输入输出操作:


using System;
 
namespace Chap1 {
     // test class
    public class P03 {
        public static void Main() {
 
             // write to Out feed
            object obj = new object();
            Console.Out.WriteLine(obj);
 
             // write to the Error stream
            int i = 10;
            Console.Error.WriteLine("i=" + i);
 
             // reading a line entered on the keyboard
            Console.Write("Tapez une ligne : ");
            string ligne = Console.ReadLine();
            Console.WriteLine("ligne={0}", ligne);
         }//fine hand
     }//end of class
}
  • 第 9 行:obj 是一个对象引用
  • 第 10 行:将 obj 输出到屏幕上。默认情况下,会调用 obj.ToString()
  • 第 14 行:你也可以这样写:

            Console.Error.WriteLine("i={0}",i);

第一个参数 "i={0}" 是显示格式,其余参数则是待显示的表达式。{n} 元素是“位置”参数。在运行时,{n} 参数会被第 n 个表达式的值替换。

结果如下:

1
2
3
4
System.Object
i=10
Tapez une ligne : je suis là
ligne=je suis là
  • 第 1 行:代码第 10 行生成的显示内容。obj.ToString() 显示了变量 obj 的类型名称:System.Object。类型 object 是 .NET System.Object 类型的 C# 别名。

3.3.4. I/O 重定向

在 DOS 和 UNIX 系统中,有三个被称为的标准设备:

  1. 标准输入设备——默认连接键盘,编号为 0
  2. 标准输出设备——默认指向显示器,编号为 1
  3. 标准错误设备——默认指向屏幕,编号为 2

在 C# 中,Console.Out 写入设备 1,写入流 Console.Error 写入设备 2,而读取流 Console.In 从设备 0 读取数据。

当您在 DOS 或 Unix 环境下运行程序时,可以设置执行程序将使用哪些设备(0、1 和 2)。请考虑以下命令行:

pg arg1 arg2 .. argn

argi 程序 pg 的实现背后,标准 I/O 设备可以重定向到文件:

0<in.txt
标准输入流 0 被重定向到 in.txt。因此,在程序中,Console.In 将从 in.txt 中获取数据。
1>out.txt
将第 1 号输出重定向到文件 out.txt。这意味着在程序中,Console.Out 会将数据写入 out.txt
1>>out.txt
同上,但写入的数据将追加到 out.txt 文件的现有内容中。
2>error.txt
将第 2 号输出重定向到文件 error.txt。这意味着在程序中,Console.Error 会将其数据写入 error.txt
2>>error.txt
同上,但写入的数据将追加到当前文件 error.txt 的内容中。
1>out.txt 2>error.txt
设备 1 和 2 均被重定向至

请注意,要将程序的 I/O 流重定向到文件,无需修改程序本身。操作系统会自动识别外设 0、1 和 2 的类型。请看以下程序:


using System;
 
namespace Chap1 {
 
     // redirections
    public class P04 {
        public static void Main(string[] args) {
            // lecture flux In
            string data = Console.In.ReadLine();
             // write Out feed
            Console.Out.WriteLine("écriture dans flux Out : " + data);
            // écriture flux Error
            Console.Error.WriteLine("écriture dans flux Error : " + data);
         }//Main
     }//class
}

让我们生成此源代码的可执行文件:

  • 在 [1] 中:通过右键单击项目并选择“构建”来创建可执行文件
  • [2]:在 DOS 窗口中,可执行文件 04.exe 已生成在项目的 bin/Release 目录下。

现在在 DOS 窗口 [2] 中执行以下命令:

1
2
3
4
5
6
7
8
...\04\bin\Release>echo test >in.txt
...\04\bin\Release>more in.txt
test
...\04\bin\Release>04 0<in.txt 1>out.txt 2>err.txt
...\04\bin\Release>more out.txt
écriture dans flux Out : test
...\04\bin\Release>more err.txt
écriture dans flux Error : test
  • 第 1 行:in.txt 文件中的字符串 test
  • 第 2-3 行:显示 in.txt 文件内容以供验证
  • 第 4 行:执行程序 04.exeIn 流重定向至 in.txtOut 流重定向至 out.txtError 流重定向至 err.txt。执行过程中未产生任何输出。
  • 第 5-6 行:out.txt 文件内容。该内容表明:
  • in.txt 文件已被读取
  • 屏幕输出已重定向至 out.txt
  • 第 7-8 行:对 err.txt 文件进行类似检查

我们可以清楚地看到,OutIn 流并未写入相同的设备,因为它们已被分别重定向。

3.3.5. 将表达式的值赋给变量

这里我们关注的是操作 variable=expression;

表达式的类型可以是:算术、关系、布尔、字符

3.3.5.1. 赋值运算的解释

运算 variable=expression;

本身是一个表达式,其求值过程如下:

  • 先求值赋值语句的右侧:结果是一个 V 值。
  • 将值 V 赋给变量
  • 将值 V 赋给变量该 V 值同时也是赋值表达式的值,此时将其视为一个表达式。

这就是

    V1=V2=expression

是合法的。由于运算优先级,最右侧的 = 运算符将被求值。因此我们得到

    V1=(V2=expression)

表达式 V2=expression 被求值并赋值为 V。求值该表达式导致 V 被赋值给 V2。随后,接下来的 = 运算符被求值为:

    V1=V

该表达式的值仍然是 V。其求值导致 V 被赋值给 V1。

因此,V1=V2=表达式

是一个表达式,其求值

  • 会将表达式的值赋给变量 V1 和 V2
  • 表达式的值赋给变量 V1 和 V2。

这可以推广为如下形式的表达式:

V1=V2=....=Vn=expression

3.3.5.2. 算术表达式

算术表达式的运算符如下:

  • 加法

  • 减法

* 乘法

/ 除法:如果至少有一个操作数是实数,结果就是精确的商。如果两个操作数都是整数,结果就是整数商。因此 5/2 -> 2,而 5.0/2 -> 2.5。

% 除法:无论被除数和除数是什么,结果都是余数,商为整数。因此,这是一种运算。

数学函数种类繁多。以下列举其中几种:

double Sqrt(double x)
平方根
double Cos(double x)
余弦
double Sin(double x)
正弦
double Tan(double x)
正切
double Pow(double x, double y)
x 的 y 次方 (x>0)
double Exp(double x)
指数
double Log(double x)
自然对数
double Abs(double x)
绝对值

等等...

所有这些函数都在一个名为 Math 的 C# 类中定义。使用时,必须在函数名前加上其所属类的名称。例如,编写:

double x, y=4;
x=Math.Sqrt(y);

Math类的完整定义如下:

Image

Image

Image

Image

Image

3.3.5.3. 算术表达式的运算优先级

在求解算术表达式时,运算符的优先级如下(从高到低):

[fonctions], [ ( )],[ *, /, %], [+, -]

同一组 [ ] 内的运算符具有相同的优先级。

3.3.5.4. 关系表达式

运算符如下:

       <, <=, ==, !=, >, >=

运算符优先级

>, >=, <, <=
==, !=

若表达式为假,则关系表达式的结果为布尔值 false;否则为 true

      bool fin;
      int x=...;
      fin=x>4;

两个特征的比较

考虑两个字符 C1 和 C2。可以使用运算符对它们进行比较

    <, <=, ==, !=, >, >=

随后会比较它们的 Unicode 码(即数字)。在 Unicode 排序中,它们之间存在以下关系:

espace < .. < '0' < '1' < .. < '9' < .. < 'A' < 'B' < .. < 'Z' < .. < 'a' < 'b' < .. <'z'

比较两个字符串

它们按字符逐个比较。当两个字符之间首次出现不等关系时,即判定两个字符串之间存在相同含义的不等关系。

示例

比较“Cat”和“Dog”这两个字符串

这个最后的不等式意味着“猫” < “狗”。

或者比较“Cat”和“Kitten”这两个链。在“Cat”链耗尽之前,两者始终处于平局状态。在这种情况下,耗尽的链被宣布为“最小”的。因此,我们得到如下关系:

    "Chat" < "Chaton".

用于比较两个字符串的函数

您可以使用关系运算符 == 和 != 来测试两个字符串是否相等,或者使用 System.StringEquals 类。对于 <、<=、> 和 >= 关系,请使用 System.StringCompareTo 类:


using System;
 
namespace Chap1 {
    class P05 {
        static void Main(string[] args) {
            string chaine1="chat", chaine2="chien";
            int n = chaine1.CompareTo(chaine2);
            bool egal = chaine1.Equals(chaine2);
            Console.WriteLine("i={0}, egal={1}", n, egal);
            Console.WriteLine("chien==chaine1:{0},chien!=chaine2:{1}", "chien"==chaine1,"chien" != chaine2);
        }
    }
}

第 7 行,变量 i 的值将为:

    0    如果两个字符串相等

    1    若通道 1 &gt; 通道 2

    -1    若链 1 &lt; 链 2

第8行,如果两个字符串相等,变量egal的值为true,否则为false。第10行使用==和!=运算符来检查两个字符串是否相等。

执行结果:

i=-1, egal=False
chien==chaine1:False,chien!=chaine2:False

3.3.5.5. 布尔表达式

可使用的运算符包括与 (&&)、或 (||) 和非 (!). 布尔表达式的结果是一个布尔值。

运算符优先级

  1. !
  2. &&
  3. ||

            double x = 3.5;
            bool valide = x > 2 && x < 4;

关系运算符具有优先级更高的运算符 && 和 ||。

3.3.5.6. 位运算

运算符

ij 是两个整数。

i<<n
将 i 向左移 n 位。移入的位均为零。
i>>n
将 i 向右移位 n 位。如果 i 是带符号整数(signed char、int、long),则保留符号位。
i & j
对 i 和 j 进行逐位逻辑与运算。
i | j
对 i 和 j 进行逐位或运算。
~i
将 i 补为 1
i^j
对 i 和 j 进行异或运算

或者以下代码:


short i = 100, j = -13;
ushort k = 0xF123;
Console.WriteLine("i=0x{0:x4}, j=0x{1:x4}, k=0x{2:x4}", i,j,k);
Console.WriteLine("i<<4=0x{0:x4}, i>>4=0x{1:x4},k>>4=0x{2:x4},i&j=0x{3:x4},i|j=0x{4:x4},~i=0x{5:x4},j<<2=0x{6:x4},j>>2=0x{7:x4}", i << 4, i >> 4, k >> 4, (short)(i & j), (short)(i | j), (short)(~i), (short)(j << 2), (short)(j >> 2));
  • 格式 {0:x4} 以十六进制格式 (x) 显示第 0 个参数,并使用 4 个字符 (4)。

结果如下:

i=0x0064, j=0xfff3, k=0xf123
i<<4=0x0640, i>>4=0x0006,k>>4=0x0f12,i&j=0x0060,i|j=0xfff7,~i=0xff9b,j<<2=0xffcc,j>>2=0xfffc

3.3.5.7. 运算符组合

a=a+b 可以写成 a+=b

a=a-b 可以写成 a-=b

运算符 /, %, *, <<, >>, &, |, ^ 也适用此规则。因此 a=a/2; 可以写成 a/=2;

3.3.5.8. 递增和递减运算符

变量++ 表示 variable=variable+1variable+=1

-- 运算符应用于变量即表示 variable = variable - 1,或者说 variable += -1

3.3.5.9. 三元运算符?

该表达式

    expr_cond ? expr1:expr2

的求值过程如下:

1 求值表达式 expr_cond。这是一个条件表达式,其条件为

2 若为真,则该表达式的值为 expr1 的值,且 expr2 不被求值。

3 若为假,则情况相反:该表达式的值为 expr2 的值,且 expr1 不被求值。

运算 i=(j>4 ? j+1:j-1); 将被赋值给变量 i:若 j>4 则为 j+1,否则为 j-1。这等同于编写 if(j>4) i=j+1; else i=j-1;,但更为简洁。

3.3.5.10. 运算符的一般优先级

() []  函数
gd
! ~ ++ --
dg
new (类型) 类型转换运算符
dg
*  /  %
gd
+  -
gd
<<  >>
gd
< <=  > >= instanceof
gd
==    !=
gd
&
gd
^
gd
|
gd
&&
gd
||
gd
?   :
dg
= += -= 等。
dg

gd 表示在优先级相同时遵循从左到右的优先级规则。这意味着当表达式中存在优先级相同的运算符时,表达式中最左侧的运算符将首先被计算。dg 表示从右到左的优先级。

3.3.5.11. 类型转换

在表达式中,您可以临时更改值的编码。这被称为更改数据类型或类型转换。在表达式中更改值类型的语法如下:

    (type) valeur

此时,该值将采用指定的类型。这会导致该值的编码发生改变。


using System;
 
namespace Chap1 {
    class P06 {
        static void Main(string[] args) {
            int i = 3, j = 4;
            float f1=i/j;
            float f2=(float)i/j;
            Console.WriteLine("f1={0}, f2={1}",f1,f2);
        }
    }
}
  • 第 7 行,f1 的值为 0.0。由于两个操作数均为 int 类型,因此 3/4 的除法是整数除法。
  • 第8行,(float)i 表示将变量 i 的值转换为 float 类型。此时,我们进行的是 float 类型的实数与 int 类型的整数之间的除法运算,即实数之间的除法。变量 j 的值也会被转换为 float 类型,随后对这两个实数进行除法运算。因此,f2 的值将为 0.75。

以下是计算结果:

f1=0, f2=0,75

(float)i 中:

  • i 是一个精确的 2 字节编码值
  • (float) i 是作为真实的 4 字节近似值编码的相同值

i 的值。这种转码仅在计算期间发生,且变量 i 始终保持其 int 类型。

3.4. 程序流程控制指令

3.4.1. 停止

在“环境”中定义的 Exit 会停止程序执行。

syntaxe        void Exit(int status)
action        arrête le processus en cours et rend la valeur status au processus père

Exit 终止当前进程并将控制权交还给调用进程。调用进程可使用 status 的值。在 DOS 环境下,该 status 变量映射到系统变量 ERRORLEVEL,其值可在批处理文件中进行检测。在 Unix 环境下,使用 Bourne 命令解释器时,变量 $? 可获取 status 的值。

    Environment.Exit(0);

将以状态值 0 终止程序执行。

3.4.2. 简单选择结构

 syntaxe :  if (condition) {actions_condition_vraie;} else {actions_condition_fausse;}

  • 条件用大括号括起来。
  • 每个操作以分号结尾。
  • 大括号不以分号结尾。
  • 只有当存在多个操作时才需要使用大括号。
  • else 子句可以省略。
  • 没有 then 子句。

该结构的算法等价形式为 if .. then ... otherwise

示例


    if (x>0)  { nx=nx+1;sx=sx+x;} else dx=dx-x;

选择结构可以嵌套:

if(condition1)
if (condition2)
        {......}
      else         //condition2
         {......}
else         //condition1
     {.......}

有时会出现以下问题:


using System;
 
namespace Chap1 {
    class P07 {
        static void Main(string[] args) {
            int n = 5;
            if (n > 1)
                if (n > 6)
                    Console.Out.WriteLine(">6");
                else Console.Out.WriteLine("<=6");
        }
    }
}

在上一个示例中,第 10 行的 else 指的是哪个 if 语句?规则是 else 总是指最近的 if 语句:在本例中即第 8 行的 if(n>6)。再来看另一个示例:


            if (n2 > 1) {
                if (n2 > 6) Console.Out.WriteLine(">6");
       } else Console.Out.WriteLine("<=1");    

这里我们希望在 if(n2>1) 之后添加 else 语句,而在 if(n2>6) 之后不添加 else 语句。鉴于前面的说明,我们必须在 if(n2>1) {...} else ... 之后添加大括号。

3.4.3. 案例结构

语法如下:

switch(expression) {
    case v1:     
            actions1;
            break;
    case v2:     
            actions2;
            break;
         . .. .. .. .. ..
    default:     
            actions_sinon;
            break;
}

注释

  • switch 的值可以是整数、字符或字符串
  • 表达式 用方括号括起。
  • 默认子句可以省略。
  • vi 是表达式的可能取值。如果表达式的值为 vi,则执行 case vi 子句后面的操作。
  • break 语句将使我们退出 case 结构。
  • 与值 vi 关联的每个指令块必须以分支指令(break、goto、return 等)结尾,否则编译器会报告错误。

示例

访问 算法

                selon la valeur de choix
                    cas 0
                         fin du module
                    cas 1
                         exécuter module M1
                    cas 2
                        exécuter module M2
                    sinon
                         erreur<--vrai
                findescas

在 C# 中

int choix = 2; 
bool erreur = false;
switch (choix) {
   case 0: return;
   case 1: M1(); break;
   case 2: M2(); break;
   default: erreur = true; break;
}
}// end Main

static void M1() {
    Console.WriteLine("M1");
}

static void M2() {
    Console.WriteLine("M2");
}
}

3.4.4. 循环结构

3.4.4.1. 已知的重复次数

用于

语法如下:


    for (i=id;i<=if;i=i+ip){ 
       actions; 
      } 

注释

  • for 语句的 3 个参数用圆括号括起,并用分号分隔。
  • 每个 for 语句都以分号结尾。
  • 只有当存在多个操作时才需要大括号。
  • 大括号后不跟分号。

其算法等价形式为 for 循环:

pour i variant de id à if avec un pas de ip
    actions
finpour

可以翻译为:

    i  id
    tantque i<=if
        actions
        i i+ip
    fintantque

foreach 结构

语法如下:


foreach (Type variable in collection)
    instructions; 
}

注释

  • collection 是一组可枚举对象。我们已知的可枚举对象集合是数组
  • Type 是集合中对象的类型。对于数组而言,即为数组元素的类型
  • 变量是循环内的局部变量,它将依次取集合中的所有值作为其值。

因此,以下代码:


            string[] amis = { "paul", "hélène", "jacques", "sylvie" };
            foreach (string nom in amis) {
                Console.WriteLine(nom);
         }

将显示:

paul
hélène
jacques
sylvie

3.4.4.2. 重复次数未知

C# 中有许多适用于这种情况的结构。

结构 tantque (while)


    while(condition){
          actions;
        } 

只要条件成立,我们就继续循环。该循环可能永远不会被执行。

  • 条件用方括号括起来。
  • 每个操作以分号结尾。
  • 只有当存在多个操作时才需要大括号。
  • 大括号后不跟分号。

相应的算法结构是 tantque :

tantque condition
    actions
fintantque

重复结构直到(do while)

语法如下:


    do{
       instructions;
    }while(condition); 

我们循环直到条件变为假。在此情况下,循环至少执行一次。

注释

  • 条件用方括号括起来。
  • 每个操作以分号结尾。
  • 只有当存在多个操作时才需要大括号。
  • 大括号后不跟分号。

对应的算法结构是 repeat ... until

répéter
    actions
jusqu'à condition

通用(for)结构

语法如下:


for(instructions_départ;condition;instructions_fin_boucle){
    instructions; 
}

只要条件为真(在每次循环迭代前进行评估),程序就会继续循环。首次进入循环前会执行起始语句,每次循环结束后会执行循环结束语句

注释

  • instructions_depart instructions_fin_boucle 中的各项指令需用逗号分隔。

相应的算法结构如下:

instructions_départ
tantque condition
    actions
    instructions_fin_boucle
fintantque

示例

以下代码片段均计算前10个整数的和。

            int i, somme, n=10;
            for (i = 1, somme = 0; i <= n; i = i + 1)
                somme = somme + i;

            for (i = 1, somme = 0; i <= n; somme = somme + i, i = i + 1) ;

            i = 1; somme = 0;
            while (i <= n) { somme += i; i++; }

            i = 1; somme = 0;
            do somme += i++;
            while (i <= n);

3.4.4.3. 循环管理指令

break
用于退出 loop、while 和 do ... while 循环。
continue
将 for、while、do ... while 循环推进到下一次迭代。while

3.5. 异常处理

许多 C# 函数可能会引发异常,即错误。当一个函数可能引发异常时,程序员应进行异常处理,以期获得更具容错能力的程序:必须始终避免应用程序发生剧烈的“崩溃”。

异常的处理方法如下:

try{
    code susceptible de générer une exception
} catch (Exception e){
    traiter l'exception e
}
instruction suivante

如果函数未抛出异常,则执行下一条指令;否则,进入 catch 子句的主体,然后执行下一条指令。请注意以下几点:

  • e 是一个 Exception 类型或其派生类型的对象。您可以通过使用 IndexOutOfRangeException、FormatException、SystemException 等具体类型来更精确地指定:异常类型多种多样。编写 catch (Exception e) 表示您希望处理所有类型的异常。如果 try 块中的代码可能引发多种类型的异常,您可能需要通过使用多个 catch 语句来更精确地处理异常:
try{
    code susceptible de générer les exceptions
} catch ( IndexOutOfRangeException e1){
    traiter l'exception e1
}
} catch ( FormatException e2){
    traiter l'exception e2
}
instruction suivante
  • 我们可以在 try/catch 语句中添加一个 finally 语句:
try{
    code susceptible de générer une exception
} catch (Exception e){
    traiter l'exception e
}
finally{
    code exécuté après try ou catch
}
instruction suivante

无论是否发生异常,finally 代码块都会被执行。

  • catch 块中,您可能不希望使用可用的 Exception 类型。因此,不要写成 catch (Exception e){..},而应写成 catch(Exception){...} 或简写为 catch {...}
  • Exception 类有一个名为 Message 的属性,它详细描述了发生的错误。因此,如果我们要显示该信息,应编写如下代码:
catch (Exception ex){
    Console.WriteLine("L'erreur suivante s'est produite : {0}",ex.Message);
    ...
}//catch
  • Exception 类有一个 ToString 方法,该方法返回一个字符串,其中包含异常的类型和 Message 的值。因此,我们可以这样写:
catch (Exception ex){
    Console.WriteLine("L'erreur suivante s'est produite : {0}", ex.ToString());
    ...
}//catch

我们还可以这样写:

catch (Exception ex){
    Console.WriteLine("L'erreur suivante s'est produite : {0}",ex);
    ...
}//catch

编译器会将 ex.ToString() 的值赋给 {0} 参数。

以下示例展示了因使用不存在的数组元素而引发的异常:


using System;
 
namespace Chap1 {
    class P08 {
        static void Main(string[] args) {
             // declaring & initializing an array
            int[] tab = { 0, 1, 2, 3 };
            int i;
             // table display with for
            for (i = 0; i < tab.Length; i++)
                Console.WriteLine("tab[{0}]={1}", i, tab[i]);
             // table display with a for each
            foreach (int élmt in tab) {
                Console.WriteLine(élmt);
            }
             // generating an exception
            try {
                tab[100] = 6;
            } catch (Exception e) {
                Console.Error.WriteLine("L'erreur suivante s'est produite : " + e);
                return;
             }//try-catch
            finally {
                Console.WriteLine("finally ...");
            }
        }
    }
}

在上文中,第 18 行会引发异常,因为数组 tab 中不存在第 100 个元素。运行程序会得到以下结果:

tab[0]=0
tab[1]=1
tab[2]=2
tab[3]=3
0
1
2
3
L'erreur suivante s'est produite : System.IndexOutOfRangeException: L'index se trouve en dehors des limites du tableau.
   à Chap1.P08.Main(String[] args) dans C:\data\travail\2007-2008\c# 2008\poly\Chap1\08\Program.cs:ligne 7
finally ...
  • 第 9 行:发生异常 [System.IndexOutOfRangeException]
  • 第 11 行:尽管第 21 行包含一个 return 语句用于退出方法,但代码中的 finally 子句(第 23-25 行)仍被执行。请注意,finally 子句总是会被执行。

以下是另一个示例,演示如何处理将字符串赋值给整型变量时(该字符串不表示整数)引发的异常:


using System;
 
namespace Chap1 {
    class P08 {
        static void Main(string[] args) {
 
             // example 2
             // We ask for the name
            Console.Write("Nom : ");
             // reading response
            string nom = Console.ReadLine();
             // age requested
            int age = 0;
            bool ageOK = false;
            while (!ageOK) {
                 // question
                Console.Write("âge : ");
                 // read-verify answer
                try {
                    age = int.Parse(Console.ReadLine());
                    ageOK = age>=1;
                } catch {
                 }//try-catch
                if (!ageOK) {
                    Console.WriteLine("Age incorrect, recommencez...");
                }
             }//while
             // final display
            Console.WriteLine("Vous vous appelez {0} et vous avez {1} an(s)",nom,age);
        }
    }
}
  • 第 15-27 行:用于输入人物年龄的循环
  • 第 20 行:通过 int.Parse 将键盘输入的字符串转换为整数。如果无法进行转换,该方法会抛出异常。这就是为什么该操作被放在 try/catch 块中的原因。
  • 第22-23行:若抛出异常,则进入catch块,该处不执行任何操作。因此,第14行设为false的布尔变量ageOK将保持false状态
  • 第 21 行:若执行到此行,说明字符串转整数操作已成功。但需检查所得整数是否大于等于 1。
  • 第24-26行:如果年龄不正确,则会输出错误信息。

部分性能测试结果:

1
2
3
Nom : dupont
âge : 23
Vous vous appelez dupont et vous avez 23 an(s)
1
2
3
4
5
6
7
Nom : durand
âge : x
Age incorrect, recommencez...
âge : -4
Age incorrect, recommencez...
âge : 12
Vous vous appelez durand et vous avez 12 an(s)

3.6. 应用示例 - V1

我们建议编写一个程序来计算纳税人的所得税。简化情况是纳税人仅需申报工资收入(2004年数据,对应2003年收入):

  • 员工份额数按以下方式计算:nbParts=nbEnfants/2 +1(未婚),nbEnfants/2+2(已婚),其中nbEnfants代表其子女数。
  • 若其子女数至少为三名,则额外增加半个份额
  • 计算应税收入 R=0.72*S,其中 S 为其年薪
  • 计算您的家庭系数 QF=R/nbParts
  • 计算您的税额 I. 请参考下表:
4262
0
0
8382
0.0683
291.09
14753
0.1914
1322.92
23888
0.2826
2668.39
38868
0.3738
4846.98
47932
0.4262
6883.66
0
0.4809
9505.54

每行有 3 个字段。要计算税款 I,请查找 QF<=champ1 的第一行。例如,如果 QF=5000,则找到该行

    8382        0.0683        291.09

税额 I 即等于 0.0683*R - 291.09*nbParts。如果 QF 使得关系 QF<=champ1 从未被检查,则使用最后一行中的系数。此处:

0                0.4809    9505.54

由此得出税款 I=0.4809*R - 9505.54*nbParts。

相应的 C# 程序如下:


using System;
 
namespace Chap1 {
    class Impots {
        static void Main(string[] args) {
             // data tables required for tax calculation
            decimal[] limites = { 4962M, 8382M, 14753M, 23888M, 38868M, 47932M, 0M };
            decimal[] coeffR = { 0M, 0.068M, 0.191M, 0.283M, 0.374M, 0.426M, 0.481M };
            decimal[] coeffN = { 0M, 291.09M, 1322.92M, 2668.39M, 4846.98M, 6883.66M, 9505.54M };
 
             // marital status is restored
            bool OK = false;
            string reponse = null;
            while (!OK) {
                Console.Write("Etes-vous marié(e) (O/N) ? ");
                reponse = Console.ReadLine().Trim().ToLower();
                if (reponse != "o" && reponse != "n")
                    Console.Error.WriteLine("Réponse incorrecte. Recommencez");
                else OK = true;
             }//while
            bool marie = reponse == "o";
 
             // number of children
            OK = false;
            int nbEnfants = 0;
            while (!OK) {
                Console.Write("Nombre d'enfants : ");
                try {
                    nbEnfants = int.Parse(Console.ReadLine());
                    OK = nbEnfants >= 0;
                } catch {
                 }// try
                if (!OK) {
                    Console.WriteLine("Réponse incorrecte. Recommencez");
                }
             }// while
 
             // salary
            OK = false;
            int salaire = 0;
            while (!OK) {
                Console.Write("Salaire annuel : ");
                try {
                    salaire = int.Parse(Console.ReadLine());
                    OK = salaire >= 0;
                } catch {
                 }// try
                if (!OK) {
                    Console.WriteLine("Réponse incorrecte. Recommencez");
                }
             }// while
 
             // calculating the number of shares
            decimal nbParts;
            if (marie) nbParts = (decimal)nbEnfants / 2 + 2;
            else nbParts = (decimal)nbEnfants / 2 + 1;
            if (nbEnfants >= 3) nbParts += 0.5M;
 
             // taxable income
            decimal revenu = 0.72M * salaire;
 
             // family quotient
            decimal QF = revenu / nbParts;
 
             // search for tax bracket corresponding to QF
            int i;
            int nbTranches = limites.Length;
            limites[nbTranches - 1] = QF;
            i = 0;
            while (QF > limites[i]) i++;
             // tax
            int impots = (int)(coeffR[i] * revenu - coeffN[i] * nbParts);
 
             // the result is displayed
            Console.WriteLine("Impôt à payer : {0} euros", impots);
        }
    }
}
  • 第 7-9 行:数值后缀为 M(Money),表示其类型为 decimal
  • 第16行:
    • Console.ReadLine() 使字符串 C1 具有类型
    • C1.Trim() 去除字符串首尾的空格C1 - 生成字符串 C2
    • C2.ToLower() 将字符串 C2 转换为小写,生成字符串 C3
  • 第 21 行:布尔变量 marie 接收值 true false,取决于关系式 answer=="o"
  • 第 29 行:键盘输入的字符串被转换为整数。如果转换失败,将抛出异常。
  • 第 30 行:布尔变量 OK 根据条件 nbEnfants>=0 取值为 true false
  • 第 55-56 行:不能直接写成 nbEnfants/2。如果 nbEnfants 等于 3,结果将是 3/2,这种整数除法会得到 1 而不是 1.5。因此,我们写成 (decimal)nbEnfants,使除法的一个操作数为实数,从而进行实数除法。

以下是一些示例:

Etes-vous marié(e) (O/N) ? o
Nombre d'enfants : 2
Salaire annuel : 60000
Impôt à payer : 4282 euros
Etes-vous marié(e) (O/N) ? oui
Réponse incorrecte. Recommencez
Etes-vous marié(e) (O/N) ? o
Nombre d'enfants : trois
Réponse incorrecte. Recommencez
Nombre d'enfants : 3
Salaire annuel : 60000 euros
Réponse incorrecte. Recommencez
Salaire annuel : 60000
Impôt à payer : 2959 euros

3.7. 主程序参数

主函数 Main 可以接受一个字符串数组作为参数:String[](或 string[])。该数组包含用于启动应用程序的命令行参数。例如,如果我们使用以下命令(DOS)运行程序 P:


        P arg0 arg1 … argn

Main 函数声明如下:

public static void Main(string[] args)

args[0]="arg0", args[1]="arg1" ... 以下是一个示例:


using System;
 
namespace Chap1 {
    class P10 {
        static void Main(string[] args) {
             // list parameters received
            Console.WriteLine("Il y a  " + args.Length + " arguments");
            for (int i = 0; i < args.Length; i++) {
                Console.Out.WriteLine("arguments[" + i + "]=" + args[i]);
            }
        }
    }
}

要向执行的代码传递参数,请按以下步骤操作:

  • 在 [1] 中:右键单击项目 / 属性
  • 在 [2] 中:[调试] 选项卡
  • 在 [3] 中:输入参数

执行后得到以下结果:

1
2
3
4
5
Il y a  4 arguments
arguments[0]=a0
arguments[1]=a1
arguments[2]=a2
arguments[3]=a3

请注意,该

public static void Main()

Main 方法不期望任何参数时是有效的。

3.8. 枚举

枚举是一种其值域为一组整数常量的数据类型。让我们考虑一个需要管理考试成绩的程序。考试成绩共有五种:FairAssezBienFineTrèsBienExcellent

我们可以为这五个常量定义一个枚举:


        enum Mentions { Passable, AssezBien, Bien, TrèsBien, Excellent };

在内部,这五个常量由连续的整数编码表示,第一个常量对应 0,第二个常量对应 1,依此类推……可以声明一个变量,使其取值范围为该枚举中的值:


            // une variable qui prend ses valeurs dans l'énumération Mentions
Mentions maMention = Mentions.Passable;

变量可以与枚举中的各种可能值进行比较:


            if (maMention == Mentions.Passable) {
                Console.WriteLine("Peut mieux faire");
}

您可以获取枚举的所有值:


             // list of statements in string form
            foreach (Mentions m in Enum.GetValues(maMention.GetType())) {
                Console.WriteLine(m);
}

正如简单类型 int 等同于 System.Int32 一样,简单类型 enum 也等同于 System.Enum。该结构拥有一个静态方法 GetValues,它允许您获取作为参数传递的枚举类型的所有值。该参数必须是 Type 类型的对象,Type 是一个关于数据类型的信息类。 变量 v 的类型可通过 v.GetType() 获取。类型 T 的类型可通过 typeof(T) 获取。因此,此处的 maMention.GetType() 返回枚举类型 Mentions 的 Type 对象,而 Enum.GetValues(maMention.GetType()) 则返回枚举类型 Mentions 的值列表。

如果现在我们编写


             //list of mentions in integer form
            foreach (int m in Enum.GetValues(typeof(Mentions))) {
                Console.WriteLine(m);
}

第 2 行,循环变量为整数类型。随后我们获取了整数形式的枚举值列表。通过 typeof(Mentions) 获取了与数据类型 Mentions 对应的 System.Type 对象。我们也可以写成 maMention.GetType()

以下程序演示了上述内容:


using System;
 
namespace Chap1 {
    class P11 {
        enum Mentions { Passable, AssezBien, Bien, TrèsBien, Excellent };
        static void Main(string[] args) {
             // a variable that takes its values from the Mentions enumeration
            Mentions maMention = Mentions.Passable;
             // variable value display
            Console.WriteLine("mention=" + maMention);
             // test with enumeration value
            if (maMention == Mentions.Passable) {
                Console.WriteLine("Peut mieux faire");
            }
             // list of statements in string form
            foreach (Mentions m in Enum.GetValues(maMention.GetType())) {
                Console.WriteLine(m);
            }
             //list of mentions in integer form
            foreach (int m in Enum.GetValues(typeof(Mentions))) {
                Console.WriteLine(m);
            }
        }
    }
}

结果如下:

mention=Passable
Peut mieux faire
Passable
AssezBien
Bien
TrèsBien
Excellent
0
1
2
3
4

3.9. 向函数传递参数

这里我们关注参数如何传递给函数。请看以下这个静态函数:


        private static void ChangeInt(int a) {
            a = 30;
            Console.WriteLine("Paramètre formel a=" + a);
}

在函数定义的第1行中,a被称为形式参数。它的存在仅是为了定义changeInt函数。它完全也可以被命名为b。现在让我们来看一下该函数的一个用法:


        public static void Main() {
            int age = 20;
            ChangeInt(age);
            Console.WriteLine("Paramètre effectif age=" + age);
}

在此第 3 行语句 ChangeInt(age) 中,age 是实际参数,它将把自己的值传递给形式参数 a。我们关注的是形式参数如何获取实际参数的值。

3.9.1. 按值传递

以下示例说明,默认情况下,函数参数采用值传递,即实际参数的值会被复制到对应的形式参数中。这两个参数是相互独立的实体。如果函数修改了形式参数,实际参数的值将保持不变。


using System;
 
namespace Chap1 {
    class P12 {
        public static void Main() {
            int age = 20;
            ChangeInt(age);
            Console.WriteLine("Paramètre effectif age=" + age);
        }
        private static void ChangeInt(int a) {
            a = 30;
            Console.WriteLine("Paramètre formel a=" + a);
        }
    }
}

结果如下:

Paramètre formel a=30
Paramètre effectif age=20

实际参数 age 的值 20 已被复制到形式参数 a 中(第 10 行)。随后该值被修改(第 11 行)。实际参数保持不变。此模式适用于函数输入参数。

3.9.2. 按引用传递

在引用传递中,有效参数与形式参数是同一个实体。如果函数修改了形式参数,有效参数也会随之改变。在 C# 中,两者都必须在前面加上关键字 ref

以下是一个示例:


using System;
 
namespace Chap1 {
    class P12 {
        public static void Main() {
             // example 2
            int age2 = 20;
            ChangeInt2(ref age2);
            Console.WriteLine("Paramètre effectif age2=" + age2);
        }
        private static void ChangeInt2(ref int a2) {
            a2 = 30;
            Console.WriteLine("Paramètre formel a2=" + a2);
        }
    }
}

以及性能结果:

Paramètre formel a2=30
Paramètre effectif age2=30

实际参数已跟随形式参数的修改。此模式适用于函数的输出参数。

3.9.3. 使用 out 关键字进行引用传递

考虑前面的示例,其中变量 age2 在调用 changeInt 之前不会被初始化:


using System;
 
namespace Chap1 {
    class P12 {
        public static void Main() {
             // example 2
            int age2;
            ChangeInt2(ref age2);
            Console.WriteLine("Paramètre effectif age2=" + age2);
        }
        private static void ChangeInt2(ref int a2) {
            a2 = 30;
            Console.WriteLine("Paramètre formel a2=" + a2);
        }
    }
}

编译此程序时,会出现以下错误:

    Use of unassigned local variable 'age2'

我们可以通过为 age2 赋初始值来解决这个问题。你也可以将关键字 ref 替换为关键字 out。这样就表明该参数仅为输出参数,因此无需初始值:


using System;
 
namespace Chap1 {
    class P12 {
        public static void Main() {
             // example 3
            int age3;
            ChangeInt3(out age3);
            Console.WriteLine("Paramètre effectif age3=" + age3);
        }
        private static void ChangeInt3(out int a3) {
            a3 = 30;
            Console.WriteLine("Paramètre formel a3=" + a3);
        }
    }
}

结果如下:

Paramètre formel a3=30
Paramètre effectif age3=30