电子商务课程复习

电子商务课程复习

第1章 计算机、Internet与Visual C#简介

课后习题

1.1 填空题

  • 计算机只能直接理解其本身的机器语言,这种语言是由1和0构成的。
  • 计算机处理数据时由一组指令控制,这组指令称为计算机程序
  • 本章介绍的3种语言是机器语言、汇编语言高级语言
  • 将高级语言程序变成机器语言程序称为编译器
  • Visual Studio是开发C#程序的集成开发环境(IDE)
  • C语言是作为UNIX操作系统的开发语言而著称的。
  • Web服务分别用XMLSOAP在Internet上标记和发送消息。

2.2 判断题(更正过的)

  • UML主要用于设计面向对象系统。
  • C#是面向对象语言。
  • C#是多个.NET语言之一(还有Visual Basic与Visual C++)。
  • 面向对象编程(针对事情)是比过程式编程更自然的建模方式。
  • 计算机只能直接理解其机器语言。
  • MSIL是公共中间格式,不管原.NET语言是什么,所有.NET程序都要编译成这种格式。
  • .NET框架可以移植到非Windows平台

第2章 Visual C# 2008 Express简介

课后习题

2.1 填空题

  • 可视化编程技术可以创建GUI而不用编写任何代码。
  • 方案是一个或多个工程,一起形成Visual C#程序。
  • 自动隐藏特性在鼠标指针移到窗口区外时隐藏窗口。
  • 工具提示在鼠标指针放到图标上时显示。
  • Solution Explorer窗口可以浏览方案文件。
  • 加号框表示Solution Explorer中的树可以展开
  • 属性窗口的属性按字母顺序类别排序
  • 窗体的Text属性指定窗体标题栏中显示的文本。
  • 工具栏可以直观地将控件加进窗体中。
  • 动态帮助根据当前上下文显示相关帮助文章。
  • TextAlign属性指定文本在卷标边界中的对齐方式。

2.2 判断题(更正过的)

  • 标题栏显示IDE的方式。
  • 图钉图标切换,自动隐藏,而X框关闭窗口。
  • 工具栏图标表示各种菜单命令。
  • 工具箱包括表示控件的图标。
  • 窗体有标题栏,卷标没有(但有卷标文本)。
  • 控制属性可以用属性窗口修改。
  • 图形框通常显示图形。
  • Visual C#文件扩展名为.cs。
  • 窗体背景颜色用BackColor属性设置。

第3章 C#程序简介

简单程序:

1
2
3
4
5
6
7
8
9
10
11
12
13
//console.WriteLine()和console.Write()区别在于最后是否有换行。
using System;
public class Welcome {
public static void Main(string[] args) {
Console.WriteLine("Hello world!");
//使用formating
Console.WriteLine("{0}\n{1}","Welcome to", "C# Programming!");
//读入加转换
Convert.ToInt32(Console.ReadLine());
//输出指定长度,如果需要在左侧则使用{0,-20}表示
Console.WriteLine("{0,4}{1,20:C}", year, acc ount);
}
}

课后习题

3.1 填空题

  • 左花括号( { )开始方法体,右花括号( } )结束方法体。
  • 每条语句以分号结束。
  • if是决策语句。
  • 单行注释以//开始。
  • 空行、空格制表符称为空白符,新行符也是空白符。
  • 关键字保留给C#使用。
  • C#程序从Main方法开始执行。
  • 方法Console.WriteLine()Console.Write()在控制台窗口显示信息。

3.2 判断题(更正过的)

  • 注释不在程序执行时导致操作,而是建档程序和改进其可读性。
  • C#是大小写相关的,所以number和NuMbEr是不同变量。
  • 求余运算符(%)也可以在C#中处理非整数算子。
  • *、/和%同一优先级,而+和-低一级。

第4章 类与对象简介

Main是个特殊方法,在程序执行时自动调用,但大多数方法并不自动调用。

习惯上,方法名从大写字母开始,后续单词首字母大写。

get和set方法的使用:

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
using System;
public class GradeBook
{
private string courseName;

public GradeBook(string name)
{
CourseName = name;
}

// 之后直接使用CourseName获取到其变量
public string CourseName
{
get
{
return courseName;
}
set
{
courseName = value
}
}
/*更好的get和set方法:
*public string CourseName{get; set;}
*称为:自实现属性
*直接写出来,声明+实现一起出现
*/
public void DisplayMessage()
{
Console.WriteLine("Hello\n{0}", CourseName)
}
}

Main方法

1
2
3
4
5
6
7
8
using System;
public class GradeBookTest
{
public static void Main(String[] args)
{
GradeBook myGradeBook = new GradeBook();
}
}

让Main方法执行程序的关键部分是static关键字,表示这个Main方法是静态方法,调用是不必先创建这个类的对象。

可以在需要时声明新的类类型,因此C#称为可扩展语言。

关于using指令

注意上面的程序,这个指令告诉编译器程序使用System名字空间中的类,如Console类。默认情况下,GradeBook类和GradeBookTest类在相同名字空间,一个类使用同一名字空间的另一个类,不需要using指令。

没有显式放进一个名字空间的类隐式放进全局名字空口。

实际上也可以不用using指令,使用类的完全限定类名,包括完全名字空间和类名,System.Console.WriteLine();

GradeBook类带实例变量与属性

GradeBook类的每个实例包含每个实例变量的一个副本。

访问修饰符public与private

用private声明的变量与方法只能在声明这个方法的类中访问。

用private声明的实例变量称为信息隐藏。在GradeBook类中,属性CourseName操纵实例变量courseName。

设置与读取专用实例变量、属性

属性(Property) 是类(class)、结构(structure)和接口(interface)的命名(named)成员。类或结构中的成员变量或方法称为 域(Field)。属性(Property)是域(Field)的扩展,且可使用相同的语法来访问。它们使用 访问器(accessors) 让私有域的值可被读写或操作。

尽管可以定义get和set方法,但C#属性更方便。

上面程序GradeBook类中的CourseName就是属性。属性声明可以有get访问函数、set访问函数或两者皆有。

定义属性后,可以在代码中像变量一样使用。可以用赋值运算符对属性赋值,其执行属性set函数代码。

get和set访问方法

1
String name = gradeBook.CourseName;

用户不能直接操纵实例变量courseName,因为它是专用的。

1
gradeBook.CourseBook = "OS";

这时文本“OS”传入隐式参数value,执行set访问方法。注意value在set访问方法中隐式声明和初始化,在set方法体中声明局部变量value会发生编译错误。set方法完成任务后不返回任何数据。

声明顺序不做要求。

在公用属性中,get方法可以是公用的,set方法可以是专用的。

值类型与引用类型

C#语言的类型分为两类——值类型与引用类型。C#的简单类型都是值类型。值类型的变量(如int)只包含这个类型的值。例如:int count = 7表示:int类型的变量count,只能包含像7这样的整数。

引用类型的变量(也称为引用)包含存储变量所引用数据的内存地址。这个变量在程序中称为引用一个对象。即引用类型(GradeBook)的变量(gradeBook)包含该类型对象的引用(内存地址)。

引用类型实例变量默认初始化为null。string是个引用类型实例变量。

值为null的字符串变量不是空串。空串表示””或string.Empty。null表示引用的不是对象。空串是不含字符的string对象。

用构造函数初始化对象

默认情况下,编译器在没有显式提供构造函数的类中提供一个没有变元的默认构造方法,因此任何类都有函数。默认构造方法不修改实例变量的默认值。如果声明类的任何构造函数,则C#不对这个类创建默认构造函数。

构造函数名必须与类名相同。与方法不同,构造函数不指定返回类型(连void也没有)。

浮点数与decimal类型

float、double和decimal可以在内存中存储实数。float和double类型称为浮点类型,它们与decimal的主要区别在于:decimal变量精确存储有限范围的实数,而浮点数只存储实数的近似值,但取值范围大得多。

简单类型 有效位
float 单精度浮点数 7
double 双精度浮点数 15~16 精度是float的2倍
decimal 28~29 7.33M或7.33m 需要double变量2倍内存

整数直接赋值给上面3种类型,隐式转换成这些类型。

格式项{0:C}将数据格式化为金额值:$3.3

课后习题

4.1 填空题

  • 房子的蓝图就像对象的类。
  • 运算符new创建关键字右边所指定的对象。
  • 默认情况下,没有显式在名字空间中声明的类隐式放在全局名字空间。
  • 类的每个对象维护属性复制时,表示属性的字段也称为实例变量。
  • Convert方法ToDecimal返回decimal值。
  • Console方法ReadLine读取字符,直到遇到新行符,然后用string返回这些字符(不包含新行符)。
  • 对于自实现属性,编译器自动生成专用实例变量及其get与set方法。

4.2 判断题

  • 方法和属性体用左右花括号定界。
  • 实例变量默认初始化,局部变量不是。
  • 源代码中的实数值称为浮点数直接输,默认类型为float。

第5章 控制语句:第一部分

算法

解决问题的过程(procedure)称为算法(algorithm),包括:

  • 执行的操作(action)
  • 执行的操作的顺序(order)

伪代码

伪代码是人为的非正式语言,帮助程序员开发算法。

控制结构

程序中的语句按编写的顺序一条一条地执行,称为顺序执行。

指定下一个执行的语句不是紧邻其后的语句,这称为控制转移。

结构化编程更清晰、更易调试与修改并且不容易出错。

研究表明,所有程序都可以只用三种控制结构,即顺序结构、选择结构和重复结构。

顺序结构是C#内置的,除非另外指定,计算机总是按编写的顺序一条一条地执行。

选择结构有三种,称为选择语句。

if语句称为单选择语句,if…else语句称为双选择语句,switch语句称为多选择语句。

C#提供四种重复结构,称为重复语句(也称为迭代语句或循环),重复语句有while、do…while、for与foreach。

控制语句小结:C#只有三种控制语句,分别是顺序结构、选择结构(三种)和重复语句(四种)。

C#提供条件运算符(? :),可以代替if…else语句。是C#中唯一的三元运算符(ternary operator),操作数和条件运算符一起形成条件表达式(conditional expression)。

垂悬else问题:C#编译器总是将else与最接近的if相关联,除非用花括号指定。(就近原则)

放在花括号中的一组语句称为

简单类型间的显式和隐式转换

1
2
3
double average;
int total,gradeCounter;
average = (double)total / gradeCounter;

这里使用double类型转换符称为显式转换,这是的计算式浮点数除以整数,要保证操作数的数据类型一致,C#将int操作数提升为double(提升操作,也称为隐式转换)。

C#支持复合赋值运算符

分别为:+=,-=,*=,/=和%=

也支持自增自减,前置和后置

C#也是强类型语言,即要求所有变量都具有类型。

课后习题

5.1 填空题

  • 将一组指令重复特定次数称为计数器控制或确定重复。
  • 事先不知道一组指令的重复次数,可以用标记(记号,标志或哑元)值终止重复。

5.2 判断题

  • 指定计算机程序执行语句的顺序称为程序控制。

第6章 控制语句:第二部分

for循环语句

1
2
3
4
for( int counter = 1; counter <= 10; counter++, total += counter )
{
statement/empty statement;
}

switch多选择语句

1
2
3
4
5
6
7
8
9
10
switch(grade / 10)
{
case 9:
case 10:
++aCount;
break;
default:
++bCount;
break;
}

break语句

continue语句在循环结构中执行时跳过该结构体的其余语句,进入下一轮循环。

逻辑运算符

有&&(条件与)、||(条件或)、&(布尔逻辑与)、|(布尔逻辑或)、^(布尔逻辑异或)和!(逻辑非)。

只有逻辑非是一元运算符。

短路求值:

1
(gender == 'F') && (++age >= 65)

将在gender不等于“F”时立即停止(整个表达式为假)。

布尔逻辑(&和|)不进行短路求值,所以如果用布尔表达式++age>=会执行,age值会发生改变。

课后习题

6.1 填空题

  • 通常,for语句用于计数器控制循环,while语句用于标记控制循环。

6.2 判断题

  • switch选择结构的每个case中可以使用break语句退出当前case结构,也可以使用return语句。

第7章 方法详述

分治法(divide and conquer):经验表明,要开发和维护大程序,最好的办法是从更容易管理的小块和小组件开始。

C#代码包装

代码包装的三种常见途径是方法、类和名字空间。

方法(函数、过程)可以将程序模块化,用户定义方法。分治,复用。

静态方法,静态变量和Math类

所有Math方法都是静态的。

1
Console.WriteLine( Math.Sqrt( a + b * c ) )

Math

静态常量

1
2
Math.PI
Math.E

这些变量在Math类中声明,修饰符为public与const。任何用关键字const声明的变量都是常量,声明之后不能改变数值。

前面提到,属性的变量也称为实例变量,类的每个对象在内存中有这个变量的不同实例。但静态变量,类的每个对象在内存中没有这个变量的不同实例。类的对象包含静态变量时,这个类的所有对象共享静态变量的同一备份。

为什么Main方法声明为static?

1
2
3
public static void Main(string args[]){
...
}

Main方法声明为static是执行环境可以调用Main方法而不必创建类的实例。

string args[]可以省略,public也可以省略,void也可替换为int

用字符串接合组装字符串

C#中生成string对象时可以用运算符+(或复合赋值运算符+=)将小字符串组装成大字符串。

1
2
double result = 9.35000;
Console.WriteLine("Maximum is: " + result);

控制台输出为:Maximum is: 9.35

布尔值也会隐式转换成string。

关于方法的声明与使用

可以用三种方式调用方法。

方法调用堆栈云激活记录

堆栈是后进先出(LIFO)数据结构。

程序调用方法是,被调的方法要知道如何放回调用者,因此把调用者的返回地址压入程序执行堆栈(也称方法调用堆栈)。

程序执行堆栈还包含程序执行期间每个方法调用使用的局部变量。这个数据存放成程序执行堆栈的一部分,称为方法调用的激活记录( activation record)或堆栈帧( stack frame)。进行方法调用时,这个方法调用的激活记录压进程序执行堆栈。方法返回调用者时,这个方法调用的激活记录出栈,程序不再知道这些局部变量。如果局部变量保存对象引用,是具有该对象引用的程序中唯一变量,则包含这个局部变量的激活记录出栈时,程序不能再访问这个对象,最终会在“内存回收”时从内存中删除。
当然,计算机的内存量有限,因此只能用一定量的内存存储程序执行堆栈中的激活记录。如果发生的方法调用太多,程序执行堆栈无法存储其激活记录,则会发生堆栈溢出错误。

变元提升与类型转换

方法调用的另一个重要特性是变元提升——隐式地将变元值变成方法参数所需要的类型。

1
Console.WriteLine( Math.Sqrt( 4 ) );

虽然Sqrt方法要求double变元,但也可以使用整型值,打印数值2.0。方法声明的参数表使C#将int值4转换成double值4.0,然后传入Sqrt方法。

隐式转换要求满足升级规则,否则必须显式转换。

所有简单类型都可以隐式转换为object类型。

Implicit

.NET框架类库

许多预定义类组成相关类的名字空间。名字空间组成.NET框架类库(即FCL)。

1
using System;

程序可以使用System名字空间的类名,不必用完全限定名。

C#的一个强大之处是FCL的名字空间中有许多类。

FCL

随机数生成

可以用随机数生成器对象产生随机byte、int与double值。

1
2
3
4
5
6
7
8
9
Random randomNumbers = new Random();
// Next方法产生值:0<=randomValue<=+2147483646
int randomValue = randomNumber.Next();
// 返回0、1、2、3、4、5。变元6称为比例因子,表示Next要产生的数值的个数
int randomValue = randomNumber.Next( 6 );
// 返回1、2、3、4、5、6
int randomValue = 1 + randomNumber.Next( 6 );
// 返回1、2、3、4、5、6
int randomValue = randomNumber.Next( 1, 7 );

Next返回的值实际上是伪随机数——通过复杂数学计算产生的数值序列。用当前时间作为种子。

比例缩放与移动随机数

1
2
返回2581114
int number = 2 + 3 * randomNumber.Next( 5 )

随机数可重复性与测试/调试

要保证运行和调试是随机数一致

1
Random randomNumbers = new Random( seedValue );

seedValue变元(int类型)提供随机数计算的种子。如果每次使用相同的seedValue,则Random对象产生相同的随机数列。

案例

1
2
3
4
5
6
7
8
private enum Status { CONTINUE, WON, LOST }

private enum DiceNames
{
SNAKE_EYES = 2,
...
TREY = 3
}

声明作用域

  • 参数声明的作用域是声明所在的方法体。

  • 局部变量声明的作用域从声明点开始,到声明所在块结束为此。

  • for语句首部初始化部分出现的局部变量声明的作用域是for语句体和首部的其他表达式。

  • 类的方法、属性、字段的作用域是整个类体。这就使类的非静态方法与属性可以使用类的任何方法、属性、字段,不管声明顺序如何。同样,静态属性与方法可以使用类的任何静态成员

方法重载

一个类中可以声明同名方法,只要其参数集不同(参数个数、类型和顺序),称为方法重载。

区别重载方法

编译器根据签名区别重载方法,签名是方法名和参数个数、类型与顺序地组合。

重载方法的返回类型

1
2
3
4
5
6
7
8
9
public int Square( int x )
{
return x * x;
}

public double Square( int y )
{
return y * y;
}

上面2个方法签名相同,返回值类型不同,会产生编译错误。

递归

1
2
3
4
5
6
7
8
public static long Factorial( long number )
{
// base case
if(number <= 1)
return 1;
else
return number * Factorial( number -1 );
}

按值与按引用传递变元

参数声明中使用关键字ref可以按引用将变量传递给方法——被调方法可以修改调用者的原变量。ref关键字用于调用方法中已经初始化的变量。

在参数前面加上关键字out可以建立输出参数,告诉编译器这个变元按引用传入被调方法,被调方法对调用者的原变量赋值。如果方法中没有在每个执行路径上对输出参数赋值,则编译器报错。

一个方法只能通过return语句向调用者返回一个值,但指定多个输出参数(ref或out)可以返回多个值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public void ReferenceAndOutParameters()
{
int y = 5; // initialize y to 5
int z; // declare z, but does not initilize it

SquareRef(ref y);
SquareOut(out z);
}

void SquareRef(ref int x)
{
x = x * x;
}

void SquareOut(out int x)
{
x = 10;
}

课后习题

7.1 填空题

  • 数据只能从堆栈增加和取出。
  • 将控制从被调方法返回调用者的三种方法是:returnreturn expression方法右花括号处
  • 程序执行堆栈包含程序执行期间每次调用方法时局部变量的内存。这个数据存放在程序执行堆栈中,称为方法调用的活动记录堆栈顶

第8章 数组

前面说过,类型可以分为两类——值类型和引用类型。数组是引用类型。

我们通常意义上的数组实际上是内存中数组实例的引用。

数组的元素可以是值类型或引用类型。

1
2
3
4
5
6
7
8
9
a=5;
b=6;
// 索引应为非负整数或整型表达式
c[a + b] +=2;
// 将元素c[11]加2

// 用数组的Length属性确定数组长度
// 注意数组的Length属性不能改变,因为它没有set方法
c.Length

数组索引值应为int、uint、long、ulong类型或可以隐式升级为这些类型的值。

声明与创建数组

1
2
3
4
5
int[] array1 = new int[12];

string[] array2;
array2 = new string[12];
// 每个元素接收默认值, 数组简单类型元素为0, 布尔元素为false, 引用为null

改变数组长度

虽然数组是定长实体,但可以用静态Array方法Resize改变数组长度。这个方法创建指定长度的新数组,将原数组内容复制到新数组,并将收到的变量设置为引用新数组的第一个

1
2
int[] array3 = new int[5];
Array.Resize( ref array3, 10 );

如果新数组比旧数组小,则后面的内容被截尾。

使用数组初始化器

逗号分隔的表达式清单(称为初始化清单),放在花括号中:

1
int[] array4 = { 10, 20, 30, 40, 50};

编译器遇到包括初始化清单的数组声明时,首先计算清单中的初始化器个数,确定数组长度,然后在“幕后”建立相应的new操作。

声明常量全部用大写字母。

用数组元素作为计数器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
using System;

public class RollDie
{
public static void Main( string[] args )
{
Random randomNumbers = new Random();
int[] frequency = new int[ 7 ];

for(int roll = 1; roll <= 6000; roll++)
++frequency[ randomNumber.Next(1, 7) ];

Console.writeLine("{0}{1, 10}", "Face", "Frequency");
for(int face = 1; face < frequency.Length; face++)
Console.writeLine("{0, 4}{1, 10}", face, frequency[face]);
}
}

运行结果:

1
2
3
4
5
6
7
Face Frequency
1 956
2 981
3 1001
4 1030
5 1035
6 997

foreach语句

foreach语句只能访问数组元素,不能修改元素。否则编译错误。

隐式类型局部变量

C#提供了隐式类型局部变量特性,是编译器可以根据变量初始化值类型推出局部变量的类型。

1
2
3
var 7; // 编译器推出变量x类型为int,因为编译器认为7之类的整数是int类型
var -123.45; // double
var array5 = new[] (32, 27, 64, 18, 95, 14);

将数组与数组元素传入方法

指定不带方括号的数组名。

1
2
3
4
5
6
7
8
9
10
// 方法声明
void ModifyArray(double[] a)
{
...
}

// 方法调用语句
double[] array6 = new double[24];
ModifyArray( array6 );
// 接收double数组的引用, a与array6是相同对象

按值与按引用传递数组

C#中“存储”数组之类对象的变量并不实际存储对象本身,而是存储对象的引用(即存储对象的计算机内存地址)。

多维数组

二维的多维数组也称为数值表,把信息放在行和列中。

c#支持两种二维数组——矩形数组和齿状数组。

矩形数组

1
int[ , ] array7 = { {1, 2}, {3, 4} }

编译器在每行的初始化器个数不同时产生一个错误,因为矩形数组的每一行要长度相同。

齿状数组

1
2
3
4
5
int[][] jagged = {
new int[] {1,2},
new int[] {3},
new int[] {4, 5, 6}
};

数组jagged本身是三个元素的数组,各引用一个int值的一维数组。

注意矩形数组与齿状数组的数组生成表达式差别。jagged数组后面是两组方括号,表示这是个int数组的数组。在数组初始化器中,C#要求用new关键字创建每一行的数组对象。

用数组生成表达式创建二维数组

1
2
3
4
5
6
7
8
9
int[,] array8;
array8 = new int[3, 4]; // 3行4列
// 齿状数组不能如此
int[][] array9 = new int[2][5]; // error
// 而是
int[][] a;
a = new int[2][]; // 2行
a[0] = new int[5];
a[1] = new int[3];

游长变元表(Variable-length argument lists)

可以创建接收任意个变元的方法。

一维数组型变元前面加上关键字params在方法参数表中,表示这个方法接收任意个变元,类型为数组元素类型。params修饰符的这个用法只能在参数表最后一个项目中出现。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public static double Average(params double[] numbers)
{
double total = 0.0;

foreach(double d in numbers)
total += d;

return total / numbers.Length;
}

public static void Main(String[] args)
{
double d1 = 10.0;
double d2 = 10.0;
double d3 = 10.0;

Console.WriteLine( Average(d1, d2) ); // 15.0
Console.WriteLine( Average(d1, d2, d3) ); // 20.0
}

使用命令行变元

1
2
3
4
5
6
7
8
9
10
public class ArgTest{
public static void Main(String[] args)
{
if(args.Length == 0)
Console.WriteLine("The number of arg is 0!");
else
foreach(var arg in args)
Console.WriteLine(arg);
}
}

使用:

1
2
3
4
$ ArgTest.exe 1 2 3
1
2
3

课后习题

8.1 填空题

  • 清单与数值表可以存放在数组中。
  • 命令行变元存放在strings数组,通常称args中。

第9章 LINQ与泛型集合简介

一组预包装数据结构——.NET框架集合类。

LINQ(language Integrated Query,语言集成查询表达式)。利用LINQ可以编写与SQL查询相识的查询表达式,从各种数据源,不限于数据库,还有数组和list驱动信息,选择符合一组条件的元素,称为筛选。

声明式编程:LINQ指定的不是读取结果所要的步骤,而是所选元素要满足的条件

指定式编程:指定执行任务所有的步骤,面向对象是指示式编程的子集。

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
using System;
using System.Linq;
using System.Collections.Generic;

class LINQWithArray
{
public static void Main(string[] args)
{
int[] values = {2, 9, 5, 0, 3, 7, 1, 4, 8, 5};

var filtered =
from value in values
where value > 4
select value;

Display(filtered); // 9 5 7 8 5

var sorted =
from value in values
orderby value
select value;

Display(sorted); // 0 1 2 3 4 5 7 8 9

var sortedAndfiltered =
from value in values
where value > 4
orderby value
// orderby value desending 都是降序
select value; //

Display(sortedAndfiltered); // 9 8 7 5 5
}

// IEnumerable<int>是个接口,描述的对象可迭代
public static void Display(IEnumerable<int> results)
{
foreach(var element in results)
Console.WriteLine(" {0}", element);
}
}

用泛型方法显示LINQ查询结果

泛型方法,指定一个类型参数表。

1
2
3
4
5
6
7
8
9
10
11
public static void Display<T>(IEnumerable<T> results)
{
foreach(T element in results)
Console.WriteLine(element);
}

Employee[] employees;
int[] array;

Display(employees);
Display(array);

T是类型变元的占位符。调用泛型方法时,编译器根据调用中的变元指定每个类型表示的类型。

T可以在参数表和方法体中多次出现,也可以作为返回值。

集合简介

.NET框架类库提供了几个类(称为集合),可以存储相关对象集合。

List

1
2
3
4
5
6
7
8
9
10
11
12
using System;
using System.Collection.Generic;

public class ListCollection
{
List<string> items = new List<string>();

items.Add("red");
items.Insert(0, "yellow"); // yellow, red
items.Add("yellow"); // yellow, red, yellow
items.Remove("yellow"); // red, yellow
}

用LINQ查询泛型集合

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
using System;
using System.Linq;
using System.Collection.Generic;

public class LINQListCollection
{
List<string> items = new List<string>();

items.Add("aQua");
items.Add("RusT");
items.Add("yElLow");
items.Add("rEd");

// List<string> items = new List<string>{"aQua", "RusT", "yElLow", "rEd"};

var startWithR =
from item in items
let uppercasedString = item.ToUpper()
where uppercasedString.StartsWith("R")
orderby uppercasedString
select uppercasedString;

foreach(var item in startWithR)
Console.Write("{0} ", item); // RED RUST

items.Add("rUby");
items.Add("SaFfRon");

foreach(var item in startWithR)
Console.Write("{0} ", item); // RED RUBY RUST
}

注意查询只创建一次,但对结果迭代得到两个不同的列表。这里演示了LINQ的延迟执行——查询只在访问结果时才执行,而不是在定义查询时执行。一次创建,多次执行,数据改变,返回结果。

课后习题

9.1 填空题

  • 集合类专门用于存储对象组和提供组织、存储与读取对象的方法。

第10章 再论类与对象

1
string.Format("{0:D2}:{1:D2}:{2:D2}", hour, minute, second);

Format()返回格式化字符串,而不是在控制台显示。

用this引用访问当前对象成员

每个对象都可以用关键字this引用自己。调用特点对象的非静态方法时,方法体隐式用关键字this引用这个对象的实例变量和其他成员。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class SimpleTime
{
private int hour;

public SimpleTime(int hour)
{
this.hour = hour;
}

public string BuildString()
{
this.ToString();
ToString();
}

public string ToString()
{
reutrn string.Format("0:D2", this.hour);
}
}

索引器

类可以用关键字this定义属性式的类成员,称为索引器,可以像数组一样用索引访问元素清单。索引器的好处是可以定义整数索引和非整数索引。

索引器在类中像属性一样定义,用关键字this定义,不能用static修饰。

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
public class Box
{
private string[] names = {"length", "width", "height"};
private double[] dimensions = new double[3];

public Box(double length, double width, double heght)
{
dimensions[0] = length;
dimensions[1] = width;
dimensions[2] = height;
}

public double this[int index]
{
get
{
if( (index<0) || (index>=dimensions.Length) )
return -1;
else
return dimensions[index];
}
set
{
if( (index>=0) || (index<dimensions.Length) )
dimensions[index] = vlaue;
}
}

public double this[string name]
{
get
{
int i=0;
while((i<names.Length) && (name.ToLower()!=names[i]))
++i;
return (i == naems.Length) ? -1 : dimensions[i];
}
set
{
int i=0;
while((i<names.Length) && (name.ToLower()!=names[i]))
++i;
if(i != names.Length)
dimensions[i] = value;
}
}
}

BoxTest.cs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class BoxTest
{
public static void Main(String[] args)
{
Box box = new Box(30, 30 ,30);
// box[0] 30
// box[1] 30
// box[2] 30
box[0]=10;
box["width"]=20;
// box[0] 10
// box[1] 20
// box[2] 30
}
}

索引器可以重载。

重载构造函数

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
public class Time
{
private int hour;
private int minute;

public Time() : this(0, 0, 0){}

public Time(int h) : this(h, 0, 0){}

public Time(int h, int m)
{
SetTime(h ,m);
}

public Time(Time time) : this(time.Hour, time.Minute){}

public SetTime(int h, int m)
{
Hour = h;
Minute = m;
}

public int Hour
{
get
{
return hour;
}
// 这个类外不能使用set方法
private set
{
hour = ((value>=0 && value<24) ? value : 0);
}
}

public int Minute
{
...
}
}

这里的this引用用法称为构造函数初始化器,易维护。

默认与无参数构造函数

注意上面Time无参构造函数,显式初始化Time对象,为每个参数传递三个参数0。如果省略无参构造函数,则无法用表达式new Time()创建对象,会发生编译错误。

合成

类可以引用其他类的对象,作为成员。这个功能称为合成,也称为“有”(has-a)关系。

Employee类中引用其他对象

1
2
3
4
5
6
public class Employee
{
private Date birthDate;
private Date hireDate;
...
}

内存回收与析构函数

Garbage Collection and Destructors

如果管理资源的对象失去所有引用,而还没有显式释放资源,则程序不能再访问和释放这个资源。这就产生了资源泄露。

公共语言运行环境(CLR)进行自动内存管理,用内存回收单元(Garbage Collection)释放对象不再需要的内存,其他对象可以使用这个内存。

对象失去所有引用时,成为可析构对象。每个对象有个特殊成员,称为析构函数。

析构函数由内存回收单元调用,在内存回收单元释放对象内存之前用于进行对象的终止整理工作。

静态类成员

static Class Members

某些情况下,类的所有对象要共享某个变量的一个副本。这时就要使用静态变量。静态变量表示类信息,类的所有对象共享同一数据。静态变量的作用域是类体。

  • 类的公用静态成员访问方法:类名和点号运算符Math.PI
  • 类的专用静态成员只能通过类的方法和属性访问。

如果没有Employee类的对象,则成员count任然可以引用,弹药通过静态属性Count,如Employee.CountC#的字符串对象创建后不能修改。

声明为静态的方法不能直接访问非静态类成员,因为即使类对象不存在,也可以调用静态方法。静态方法也不能使用this引用,this引用要引用特定类对象。

只读实例变量

readonly Instance Variables

C#提供了readonly关键字,可以指定对象的实例变量不能修改,想修改会发生错误。

构造函数可以多次对readonly实例变量赋值。

声明为const的成员要在编译时赋值,常量成员的值不能在编译时确定要用readonly关键字声明。如果构造函数不初始化readonly声明的成员,则编译器产生一个警告。

1
2
3
4
5
6
7
8
9
public class Increment
{
private readonly int INCREMENT;

public Increment(int incrementValue)
{
INCREMENT = incrementValue;
}
}

软件复用性

Software Reusability

类库,快速程序开发(RAD),FCL,CLR

数据抽象与封装

Data Abstraction and Encapsulation

信息隐藏:类通常对类的客户隐藏其实现细节。

数据抽象:描述类的功能而不管其实现细节。

int、double与char类的类型,还有队列都是抽象数据类型(ADT)。抽象数据类型(ADT)实际上包含两个概念,即数据表达和该数据允许的操作。

internal Access

目前为止我们所定义的类,只能用两个访问修饰符声明:public与internal。顶级类。C#支持嵌套类——定义在其他类中的类。除了public与internal,类还可以声明为private或protected。如果类声明没有访问修饰符,默认internal访问。声明为internal的方法。实例变量和其他成员可以让同一汇编中的所有代码访问,而其他汇编中的代码则不行。同一汇编中,internal相当于public。

Class View and Object Browser

使用Class View窗口

使用Object Browser

对象初始化器

Object Initializers

创建对象并在同一语句中将其属性初始化。

1
2
3
4
5
6
7
8
9
class ObjectInitialzerTest
{
static void Main(string[] args)
{
Time aTime = new Time{Hour=14, Minutre=145, Second=12};

Time anotherTime = new Time{Minute=45};
}
}

代理delegate

代理对象保存一个方法的引用。代理可以把方法看成数据,通过代理可以将方法赋值给一个变量,将这个方法与其他方法相互传递。

代理类型只是描述一组具有特定参数和特定返回类型的方法。

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
using System;
using System.Collections.Generic;

class Delegates
{
// 委托一个接收int并返回布尔值的函数
public delegate bool NumberPredicate(int number);

static void Main(string[] args)
{
int[] numbers = {1, 2, 3, 4, 5, 6, 7, 8, 9 ,10};

// 创建NumberPredicate委托类型的实例
NumberPredicate evenPredicate = IsEven;

List<int> evenNumbers = FilterArray(numbers, evenPredicate);
// evenNumber: 2 4 6 8 10

List<int> oddNumbers = FilterArray(numbers, IsOdd);
// oddNumber: 1 3 5 7 9

private static list<int> FilterArray(int[] intArray, NumberPredicate predicate)
{
List<int> result = new List<int>();
foreach(int item in inArray)
{
if(predicate(item))
result.Add(item);
}
return result;
}

private static bool IsEven(int number)
{
return (number%2 == 0);
}

private static bool IsOdd(int number)
{
return (number%2 == 1);
}
}
}

由于IsEven方法的签名与NumberPredicate代理的签名匹配,因此可以用NumberPredicate类型的变量引用IsEven方法。

Lambda Expressions

lambda表达式首先是个参数表。

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
using System;
using System.Collections.Generic;

class Lambda
{
public delegate bool NumberPredicate(int number);

static void Main(string[] args)
{
int[] numbers = {1, 2, 3, 4, 5, 6, 7, 8, 9 ,10};

NumberPredicate evenPredicate = number => (number%2 == 0);

evenPredicate(4); // True

List<int> evenNumbers = FilterArray(numbers, evenPredicate);
// evenNumber: 2 4 6 8 10

List<int> oddNumbers = FilterArray(numbers, (int number) => (number%2 == 1));
// evenNumber: 1 3 5 7 9

List<int> numberOver5 = FilterArray(numbers, number => {return number>5; } );
// 6 7 8 9 10

private static list<int> FilterArray(int[] intArray, NumberPredicate predicate)
{
List<int> result = new List<int>();
foreach(int item in inArray)
{
if(predicate(item))
result.Add(item);
}
return result;
}
}
}

参数表后面是lambda运算符(=>,读作go to)和一个表示函数体的表达式。

没有指定返回类型,返回类型可以从返回值或代理的返回值推定。

同样,lambda表达式定义的方法要与代理类型定义的方法有相同签名。

匿名类型

Anonymous Types可以创建简单类,用于存储数据,不必编写类定义。

匿名类型的属性都是公用和不可变的。匿名类型是只读的,创建对象之后不能修改属性值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
using System;
using System.Collections.Generic;

class Lambda
{
public delegate bool NumberPredicate(int number);

static void Main(string[] args)
{
var bob1 = new {Name = "Bob Smith", Age = 37};
bob.ToString(); // {Name = Bob Smith, Age = 37}
var bob2 = new {Name = "Bob Smith", Age = 37};
bob1.Equals(bob2); // True
// 两者属性值相同,声明顺序相同
}
}

LINQ中的匿名类型

1
2
3
var names = 
from e in employees
select new {e.FirstName, Last = e.LastName};

课后习题

10.1 填空题

  • 最低权限原则要求代码只能分配完成指定任务所需的访问量。
  • 编译器将类库工程监理在所谓动态链接库的汇编中。

10.2 判断题

  • lambda表达式必须返回一个值。

第11章 面向对象编程:继承

基类和派生类

Base Classes and Derived Classes

继承形成了树状层次结构。

并非每个类关系都是继承关系。“有”关系,类的成员是其他类对象的引用。

protected成员

protected介于公用与专用之间,基类的protected成员可以让基类成员和派生类成员访问。所有非专用基类成员在派生类中保持原访问修饰符。

基类和派生类的关系

C#中除object以外的每个类都扩展现有类。object是唯一没有基类的类。

1
2
3
4
5
6
7
8
9
// CommissionEmployee类显式扩展(继承自)object类(FCL中的System.Object)
public class CommissionEmployee : object
{
...
public override string ToString()
{
...
}
}

构造函数不继承,但隐式调用object类的构造函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class BaseCommissionEmployee : CommissionEmployee
{
private decimal baseSalary;

// 构造函数初始化器和关键字base调用基类构造函数
public BaseCommissionEmployee(string name, decimal sales, decimal salary): base(name, sales)
{
BaseSalary = salary;
}

public decimal BaseSalary{get; set}

public virtual decimal Earnings()
{
...
}
}

关键字virtual与abstract表示派生类中可以覆盖基类方法。

使用父类private实例变量

1
2
3
4
5
6
7
8
public class BaseCommissionEmployee : CommissionEmployee
{
...
public override decimal Earnings()
{
return BaseSalary + base.Earnings();
}
}

派生类的构造函数

Constructors in Derived Classes

派生类构造函数在执行自己的任务之前要显示(通过base引用和构造函数初始化器)或隐式(调用基类默认构造函数或无参构造函数)调用直接基类的构造函数。链中最后一个调用的构造函数总是object类的构造函数。

继承和软件工程

Software Engineering with Inheritance

Object类

object

第12章 多态、接口与运算符重载

多态举例

程序通过基类变量调用方法时,发生多态——执行时,根据引用对象的类型调用这个方法的正确派生类版本。

程序通过向下转换技术显式将基类引用转换为派生类类型,程序可以调用基类中没有的派生类方法。

1
2
3
4
5
6
7
8
9
10
11
12
public class PolymorphismTest
{
public static void Main(string[] args)
{
BaseCommissionEmployee baseCommissionEmployee = new BaseCommissionEmployee("Bob", 5000, 300);

CommissionEmployee commissionEmployee = baseCommissionEmployee;

CommissionEmployee.ToString();
CommissionEmployee.Earnings();
}
}

抽象类与方法

定义不实例化为任何对象的类,抽象类。抽象基类。

抽象类用关键字abstract声明。抽象类通常包含一个或几个抽象方法。

抽象属性声明形式如下:

1
2
3
4
5
public abstract int property
{
get;
set;
}

构造函数和静态方法不能声明为abstract。派生类不能覆盖静态方法。

不能实例化抽象基类的对象,但可以用抽象基类声明变量,保存这些抽象类派生的任何具体类的对象引用。程序通常用这种变量多态操纵派生类对象。可以用抽象基类名调用抽象基类中声明的静态方法。

as运算符进行向下转换

1
var employee = currentEmployee as BaseCommissionEmployee;

sealed方法与类

基类中声明为sealed的方法不能在派生类中覆盖,声明为static和private的方法隐含sealed,因为派生类中不能覆盖(但派生类可以声明与基类中专用方法同名的新方法)。同时声明override与sealed的派生方法可以覆盖基类方法。

声明为sealed的类不能作为基类(不能扩展)。

创建与使用接口

Creating and Using Interfaces

接口定义和标准化人与系统交互的方式。

软件对象也是通过接口通信。

所有接口成员隐式声明为public与abstract。

每个接口可以扩展一个或多个其他接口。

实现这个接口的具体类要声明接口中指定了签名的每个接口成员。实现接口而不实现其所有成员的类是个抽象类,要声明为abstract,每个未实现的接口成员要包含一个abstract声明。

和公用抽象类一样,接口通常为公用类型,因此通常在单独文件中声明,文件名与接口名相同,文件扩展名为.cs。

声明接口IPayable

1
2
3
4
5
// IPayable.cs
public interface IPayable
{
decimal GetPaymentAmount();
}

创建Invoice类

1
2
3
4
5
6
7
8
9
// Invoice.cs
public class Invoice : IPayable
{
...
public decimal GetPaymentAmount()
{
...
}
}

修改Employee类,实现IPayable接口

1
2
3
4
5
6
7
// Employee.cs
public abstract class Employee : IPayable
{
public string Name {get; private set;}
...
public abstract decimal GetPaymentAmount();
}

.NET框架类库公共接口

interface

运算符重载

使运算符适用于类对象,这个过程称为运算符重载。

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
// ComplexNumber.cs
public class ComplexNumber
{
public double Real {get; private set;}
public double Imaginary {get; private set;}

public ComplexNumber(double a, double b)
{
Real = a;
Imaginary = b;
}

public override string ToString()
{
return string.Format( "{0} {1} {2}i",
Real, (Imaginary < 0 ? "-":"+"), Math.Abs(Imaginary))
}

public static ComplexNumber operator +(ComplexNumber x, ComplexNumber y)
{
reutrn new ComplexNumber(x.Real+y.Real, x.Imaginary+y.Imaginary);
}
}

// OperatorOverloading.cs
public class ComplexTest
{
public static void Main(string[] args)
{
ComplexNumber x, y;
x = new ComplexNumber(2, 4);
y = new ComplexNumber(4, -2);

x+y; // (2+4i)+(4-2i)=(6+2i)
}
}

第13章 异常处理

异常就是程序执行期间发生的问题。

异常处理使应用程序能够解决异常。异常处理可实现清晰,健壮和更多容错的程序。

异常处理概述

Exception Handling Overview

程序员可以决定处理任何异常——所有异常,某种类型的所有异常或相关类型的所有异常。

例子:除数为0不用异常处理

其他信息(称为堆栈跟踪)显示异常名称和导致异常的执行路径。

堆栈跟踪中的每一行“ at”都表示发生异常时正在执行的特定方法中的一行代码。该信息说明异常的起源,以及达到该点之前进行了哪些方法调用。

当Convert方法ToInt32接收到不代表有效整数的字符串时,将发生FormatException。即使发生异常并且已打印堆栈跟踪,程序也可能继续执行。在这种情况下,应用程序可能会产生错误的结果。

使用异常处理

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
// DivideByZeroTest.cs
using System;
using System.Windows.Forms;

namespace DivideByZeroTest
{
public partial class DivideByZeroTestForm : Form
{
public DivideByZeroTestForm()
{
InitializeComponent();
}

// 从用户那里获得2个整数
// 将分子除以分母
private void divideButton_click(object sender, EventArgs e)
{
outputLabel.Text = ""; // 清除标签OutputLabel

try
{
int numerator = Convert.ToInt32( numeratorTextBox.Text );
int denominator = Convert.ToInt32( denominatorTextBox.Text );
int result = numerator / denominator;
outputLabel.Text = result.ToString();
}
catch(FormatException)
{
MessageBox.Show( "You must enter two integers.",
"Invalid Number Format", MessageBoxButtons.OK,
MessageBoxIcon.Error );
}
catch ( DivideByZeroException divideByZeroExceptionParameter )
{
MessageBox.Show( divideByZeroExceptionParameter.Message,
"Attempted to Divide by Zero", MessageBoxButtons.OK,
MessageBoxIcon.Error );

}
}
}
}

DivideByZeroTest.cs

另一种有效的方法:

Int32.TryParse方法将字符串转换为int值。该方法需要两个参数-一个是要解析的字符串,另一个是要将转换后的值存储在其中的变量。如果成功解析了字符串,则该方法返回true。如果无法转换字符串,则将值0分配给第二个参数。

Enclosing Code in a try Block(用try块界定代码)

try块包含可能引发异常的代码以及发生异常时被跳过的代码。

catch块至多只能有一个参数。

Catching Exceptions(捕获异常)

当try块中发生异常时,相应的catch块将捕获该异常并进行处理。一个try块之后必须至少有一个catch块。catch块指定一个异常参数,该参数表示catch块可以处理的异常。catch块也可以不指定异常类型或标识符,这种catch块(称为通用catch子句),捕获所有异常类型。

未捕获异常

未捕获的异常(或未处理的异常)是没有匹配的catch块的异常。

Termination Model of Exception Handling(异常处理的终止模型)

当程序或CLR中调用的方法检测到问题时,该方法或CLR会引发异常。发生异常的点称为抛出点。如果try块中发生异常,则try块立即终止,程序控制将立即转移到后面第一个异常参数类型与所有抛出类型匹配的catch块中。处理异常后,程序控制在最后一个catch块之后恢复。这称为异常处理的终止模型

.NET Exception层次

C#中的异常处理机制只能抛出和捕获Exception类(命名空间System)及其派生类的对象。

其他.NET语言(如C++)引发的异常可以通过常规catch子句捕获。

SystemException类

Exception类是.NET异常类层次结构的基类。从Exception派生的一个最重要的类是SystemException。CLR产生SystemException,可以在程序执行期间的任何时候发生。

如果程序尝试访问超出范围的数组索引,则CLR会引发IndexOutOfRangeException类型的异常。尝试使用空引用会导致NullReferenceException。

catch块可以使用基类类型来捕获相关异常的层次结构。指定异常类型参数的catch块可以捕获所有异常。仅当基类和所有派生类的处理行为相同时,此技术才有意义。

确定方法抛出什么异常(Determining Which Exceptions a Method Throws)

finally块

程序经常动态地请求和释放资源。操作系统通常会阻止多个程序处理文件。因此,该程序应关闭文件(即释放资源),以便其他程序可以使用它。如果未关闭文件,则会发生资源泄漏。

处理资源时经常会发生异常。无论程序是否遇到异常,该程序都应在不再需要该文件时将其关闭。C#提供了finally块,无论是否发生异常,该块都可以保证执行
这使得finally块非常适合从相应的try块中释放资源。

try块中的局部变量无法在相应的finally块中访问,因此必须在try块之前声明必须在两者中访问的变量。

using语句

1
2
3
4
using(ExampleObject e = new ExampleObject())
{
e.SomeMethod{};
}

可以简化取得资源、在try块中使用资源和在相应finally中释放资源的代码。文件处理程序,保证文件不再使用时正确关闭。

using语句代码等效于

1
2
3
4
5
6
7
8
9
10
11
{
ExampleObject e = new ExampleObject();
try{
e.SomeMethod();
}
finally
{
if ( e != null )
( ( IDisposable ) e ).Dispose();
}
}

Exception Properties

类Exception的属性用于制定错误消息,指示捕获到的异常。属性Message存储与Exception对象关联的错误消息。属性StackTrace包含一个表示方法调用堆栈的字符串

发生异常时,程序员可能会使用其他错误消息或指示新的异常类型。原始异常对象存储在InnerException属性中。

类异常提供其他属性:

  • HelpLink指定描述问题的帮助文件的位置。
  • Source指定引起异常的应用程序或对象的名称。
  • TargetSite指定异常发生的方法。

最近调用的方法出现在堆栈的顶部。第一种方法(Main)出现在底部。

StackTrace表示抛出点处方法调用堆栈的状态。内部异常信息包括内部异常堆栈跟踪。

用户定义异常类(User-Defined Exception Classes)

用户定义的异常类应直接或间接从名称空间System的Exception类派生。

用户定义的异常应定义三个构造函数:

  • 无参数构造函数
  • 接收字符串参数的构造函数(错误消息)
  • 接收字符串参数和Exception参数的构造函数(错误消息和内部异常对象)

第14章 图形用户界面概念:第一部分

图形用户界面(GUI)允许用户在视觉上与程序进行交互。

GUI控件是可以在屏幕上显示信息或使用户能够与应用程序交互的对象。

controller

Windows Forms

Form是出现在计算机桌面上的图形元素; 它可以是对话框,窗口或MDI窗口。组件是实现IComponent接口的类的实例,该接口定义了组件必须实现的行为,例如如何加载组件。诸如按钮或标签的控件在运行时具有图形表示。

评论