您的位置:首页 > 编程语言 > C#

C#学习辛苦路 点点滴滴的总结 我的笔记

2009-09-23 18:20 435 查看
c#笔记

类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类

第一部分 基础知识

类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类

一综合理论知识:

1c#代码执行过程:

1首先转换成中间语言MSIL(任何语言都可以转换成MSIL)

2利用JIT编译器(just in time)将中间语言代码编译,我们只要关注用我们熟悉的语言写程序,其他的工作完全交给系统了

二c#基本语法:

1c#中的空格

c#中空格被忽略,只能识别一个空格,多个空格会被忽略,所以一行中可以有多个空格

2c#中的注意事项

一个语句之后必须有分号

3注释



4占位符的使用

Console.Writeline("{0}{1}",mystring,
myInteger);

每个占位符用包含在花括号中的一个整数来表示,整数以0开始,每次递增1,占位符的总数应等于列表中指定的变量数,该列表用逗号分隔开,跟在字符串后面,

表示用后面的变量代替

例二

Console.WriterLine("sourceVar
val:{0}",sourceVar);

这里注意占位符的位置

5转义符号的应用

1

myString="This
string has a/nline break";

输入为

This
string has a

line
break

(程序中/n前面和后面都没有空格就可以实现转义符的功能)

2

常用转义字符表:

/' 单引号

/"
双引号

// 反斜杠

/0 空

/a 警告(产生鸣响)

/b 退格

/f 换页

/n
换行

/r 回车

/t
水平制表符

/v 垂直制表符

6变量的命名

1

字母,下划线,@都可以作为开头,要特别注意@,这是新增加的

2

严格区分大小写

3

c#中一般采用受单词大写的方法来定义类名,变量名,例如Age firstName WinterOfDiscoutent

7常用数据类型

整数部分

sbyte 8 -128
127

byte 8 0 255

short 16 -32768 32767

ushort 16 0 65535

int 32 -2147483648 2147483647(10位,负的21亿到正21亿)

uint 32 0 4294967295

long 64 -9223372036854775808 9223372036854775807(19位)

ulong 64 0 18446744073709551615(20位)

要会记忆:

short
int和long都是有符号的整数

ushort
uint和ulong是无符号的整数

上面放在一起记忆,byte和sbyte单独记忆,注意byte是无符号的整数

小数部分

float

double

decimal

(位数太多,略)

另外三个

Char 一个Unicode字符,0 65535

bool 只有连个值 true false

string 字符串,其字符个数没有上限,因为字符串可以使用可变大小的内存

(char和ushort都是表示0到65535)

8 C#支持的类型

C#
支持两种类型:“值类型”和“引用类型”。

值类型

?
变量直接包含它们自己的数据

?
局部变量总是放在栈(stack)中

引用类型

?
变量间接指向它们的数据

?
局部变量指向堆(heap)中的对象

值类型 包括:

1简单类型(如 char、int 和 float)

2用户自定义类型:

枚举类型enum

结构类型struct。

其区别是简单类型可以有字面表达式,例如42

引用类型 包括:

类 (Class) 类型、

接口类型interface、

委托类型delegate(有的书籍称代表)

数组类型[]array。

值类型与引用类型的区别在于值类型的变量直接包含其数据,而引用类型的变量则存储对象引用。对于引用类型,两个变量可能引用同一个对象,因此对一个变量的操作可能影响另一个变量所引用的对象。对于值类型,每个变量都有自己的数据副本,对一个变量的操作不可能影响另一个变量

9标识符起名的规则:

局部变量、局部常量、非公有实例域、函数参数使用camelCase规则;

其他类型的标识符使用PascalCase规则。

camelCase规则(第一个单词的首字母小写,其余单词的首字母大写)

PascalCase规则(所有单词的首字母大写)

尽量不要使用缩写。

Message,而不要使用msg。

不要使用匈牙利命名法。

public
sealed class GrammarHelper

{ ...

public
QualifiedSymbol Optional(AnySymbol symbol)

{
... }

private
AnyMultiplicity optional =

new
OptionalMultiplicity();

}

10 @-c#新引进的

int
@int = 42; 在关键字前加@来使它可以用作变量名:

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

11编程风格

较正规的编程风格

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

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

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

一行一个语句

分号前不要有空格

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

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

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

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; //不推荐

找错

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段程序的错误:第一个错误是标识符不能以数字开头;第二个错误是不能用下划线作标识符。

12操作符

1基础知识

分类

一元操作符

二元操作符

三元操作符

三元操作符只有一个 ?:

括号 (x)

访问成员 x.y

函数调用 f(x)

访问数组(不是元素) a[x]

自增 x++

自减 x--

调用构造函数 new

获得类名 typeof

获得尺寸 sizeof(不安全的)

数值检查 (un)checked

2
is 操作符

3
as 操作符

13操作符的优先级

14连接

规则1

除了赋值操作符外的其他二元操作符都是左连接的。

x+y+z 应理解为 (x+y)+z

规则2

赋值操作符和?: 操作符是右连接的。

x=y=z 应理解为 x=(y=z)

x+=y+=z 应理解为 x+=(y+=z)

a?b:c?d:e 应理解为 a?b:(c?d:e

三类型转换

1隐式转换

对于隐式转换 规则:任何类型A,只要其取值范围完全包含在类型B的取值范围内,

就可以隐式转换为类型B

由于char和ushort数字范围一样,因此可以将char隐式转换成ushort,但是

反过来就不行,即不能将ushort隐式转换成char

转换表



2显示转换

第一种方法

利用 (变量类型)这种方式实现 例子:



显示转换要注意溢出,如果要转换的值过大,超出类型的表示范围,则会出现溢出而导致错误

第二种方法

利用Convert命令

这种方法有一定的限制 规则:在字符串向double转换的时候,字符串必须是数字的有效表达方式

,该数字必须是不会溢出的数。

数的表达方式:首先是一个可选符号(加号或减号),然后是零位或多位数字,一个圆点后跟一位或多位数字,接着是一个可选的e或E,例如

-1.323e-43

转换表



复杂点的例子



四枚举类型

1

我们知道连续 和离散 的感念

对于double,其就可以看作是连续的

而bool就是离散的,其只有两个值true false

枚举就是给变量一个范围,这个范围是离散的值组成的集合

它是一个用户声明的值类型

enum
Suit

{

Clubs, Diamonds, Hearts, Spades

}

//Suit表示一副牌,它有4个花色:梅花(Clubs),方块(Diamonds),红心(Hearts),//黑桃(Spades)

sealed
class Example

{

static
void Main()

{

...

Suit lead = Spades; //错误

...

Suit trumps = Suit.Clubs; //正确

枚举的声明可以出现在类声明的相同地方。

枚举的声明包括名字、访问权限、内在的类型和枚举的成员。

枚举中声明的常量的范围是定义它们的枚举,换言之,下面的例子是错误的:

Suit
trumps = Clubs;

Clubs必须被限制为Suit的一个成员,就如下面:

Suit
trumps = Suit.Clubs;

枚举的注意点

枚举值缺省为int

?
你可以选择任一内在的整数类型

?
但不能是字符型

enum
Suit : int //内在类型是int,可以省略

{

Clubs,

Diamonds,

Hearts = 42, //成员的取值缺省为前一个成员取值+1,但可以自己赋初值

Spades, //最后一个分号是可选的

};//可以有结尾分号

枚举类可以显式的声明它的内在类型是sbyte, byte, short,
ushort, int, uint, long, ulong。

如果一个枚举类没有显式声明它的内在类型,则缺省为int。

成员的取值必须和枚举声明的内在类型相同,并且必须在内在类型的范围之内

(例如,你不能让成员的取值为负数,而枚举的内在类型是uint)。

如果成员没有被赋值, 那么它的取值是前一个成员取值+1,第一个成员的缺省值是1。

枚举的成员的取值可以有相同的取值。

最后一个枚举成员可以使用一个结尾分号,这使得你将来可以很方便地加入更多的成员。

枚举成员的访问权限隐含为public。

2枚举的计算

枚举的计算和整数计算一样

using
system

enum
Weekday

{

Sunday,Monday,Wednesday,Thurday,Friday,Sunday

};

class
Test

{

public
static void Main()

{

Weekday
day1 = Weekday.Sunday;

Weekday day2 = Weekday.Saturday;

Weekday day3 = day1 + 6;

}

}

3枚举的转换

五数组类型

六结构类型

七语句

throw语句

throw语句抛出错误

检查先前定义的条件时非常有用

表达式的类型必须是System.Exception或是它的派生类

string
DaySuffix(int days)

{

if
(days < 0 || days > 31)

{

throw new

ArgumentOutOfRangeException("days");

}

...

}

语句列表和块语句

static
void Main()

{

F();

G();

{

H();

I();

}

}

标记语句和 goto 语句

static
void Main(string[] args)

{

if (args.Length == 0)

goto done;

Console.WriteLine(args.Length);

done:

Console.WriteLine("Done");

}

局部常数声明

static
void Main()

{

const float pi = 3.14f;

const int r = 123;

Console.WriteLine(pi * r * r);

}

局部变量声明

static
void Main()

{

int a;

int b = 2, c = 3;

a =
1;

Console.WriteLine(a + b + c);

}

表达式语句

static
int F(int a, int b)

{

return a + b;

}

static
void Main()

{

F(1, 2);
// Expression statement

}

if 语句

static
void Main(string[] args)

{

if (args.Length == 0)

Console.WriteLine("No args");

else

Console.WriteLine("Args");

}

switch 语句

static
void Main(string[] args)

{

switch (args.Length)

{

case 0:

Console.WriteLine("No args");

break;

case
1:

Console.WriteLine("One arg ");

break;

default:

int n = args.Length;

Console.WriteLine("{0} args", n);

break;

}

}

while 语句

static
void Main(string[] args)

{

int i = 0;

while (i < args.Length)

{

Console.WriteLine(args[i]);

i++;

}

}

do mhile 语句

static
void Main()

{

string s;

do

{

s
= Console.ReadLine();

}
while (s != "Exit");

}

for 语句

static
void Main(string[] args)

{

for
(int i = 0; i < args.Length; i++)

Console.WriteLine(args[i]);

}

foreach 语句

tatic
void Main(string[] args)

{

foreach (string s in args)

Console.WriteLine(s);

}

break 语句

static
void Main(string[] args)

{

int i = 0;

while
(true)

{

if
(i == args.Length)

break;

Console.WriteLine(args[i++]);

}

}

continue 语句

static
void Main(string[] args)

{

int i = 0;

while (true)

{

Console.WriteLine(args[i++]);

if
(i < args.Length)

continue;

break;

}

}

return 语句

static
int F(int a, int b)

{

return a + b;

}

static
void Main()

{

Console.WriteLine(F(1, 2));

return;

}

throw 语句和 try 语句

static
int F(int a, int b)

{

if (b
== 0)

throw new Exception("Divide by
zero");

return
a / b;

}

static
void Main()

{

try

{

Console.WriteLine(F(5, 0));

}

catch(Exception e)

{

Console.WriteLine("Error");

}

}

checked 和 unchecked 语句

static
void Main()

{

int x = Int32.MaxValue;

Console.WriteLine(x + 1); // Overflow

checked

{

Console.WriteLine(x + 1); // Exception

}

unchecked

{

Console.WriteLine(x + 1); // Overflow

}

}

lock 语句

static
void Main()

{

A a =
...;

lock(a)

{

a.P = a.P + 1;

}

}

using
statements

static
void Main()

{

using (Resource r = new Resource())

{

r.F();

}

}

八异常处理

类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类

第二部分 类的基础知识

类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类

一综合

1 类的修饰符

public 表示不限制对类的访问

protected 表示只能从所在类和所在类的派生自类进行访问

internal 表示只有所在类才能访问

private 只有系统定义的库或者包才能访问

abstract 抽象类,不允许建立类的实例

sealed 密封类
不允许被继承

2 类的成员

成员常量

字段

属性

方法

事件

索引

构造函数

析构函数

3 this关键字的使用

二命名空间

三装箱和拆箱

四变量

静态变量与非静态变量(又称实例变量)

变量的类型:

静态static variables

非静态 instance variables

数组元素 array elements

值参数 value parameters

引用参数 reference parameters

输出参数 output parameters

局部变量 local variables

局部变量是指在一个独立的程序块中声明的变量,如for switch
using等

它只在该范围内有效

局部变量 和其他变量不一样 系统不会给它指明一个默认值

五属性与字段(有的教材叫域)

1字段

字段类似c++中的简单成员变量例如:

class
A

{

public int x;

publicstring y;

private flort z;

}

2静态字段和非静态字段

1

和前面讲的讲台变量和非静态变量的用法 原理
一样

若将一个字段说明为静态的,无论建立多少类的实例,内存中只有一个静态数据的拷贝

当这个类的第一个实例被初始化时,静态字段就被初始化了,以后再次建立实例的时候

不再对其初始化,所有的类的实例共享一个静态字段副本

与静态字段相反,非静态字段在类每次进行实例化的时候,每个实例都有一份单独的拷贝

下面例子显示了一个 Color 类,

它包含三个分别名为 redPart、bluePart 和 greenPart 的内部实例字段

和四个分别名为 Red、Blue、Green 和 White 的静态字段

class
Color

{

public
static Color Red = new Color(0xFF, 0, 0);

public
static Color Blue = new Color(0, 0xFF, 0);

public
static Color Green = new Color(0, 0, 0xFF);

public
static Color White = new Color(0xFF, 0xFF, 0xFF);

internal
ushort redPart;

internal
ushort bluePart;

internal
ushort greenPart;

public
Color(ushort red, ushort blue, ushort green)

{

redPart
= red;

bluePart
= blue;

greenPart
= green;

}

}

2

readonly的相关知识

3属性 get set

1概述

属性的引进,体现了对象的封装性:不直接操作了的数据内容,而是通过访问其对其访问

利用get 和set 对属性进行读 写

只有set访问器
表明属性的值只能进行设置吧不能读出

只有get访问器 表明属性的值只读
不能改写

同时具有set和get访问器
表明属性的值 读写 都是允许的

get
要和 return 结合使用 来读取属性的值

set
要和 value 结合使用来设置属性的值

2 get 语句举例:

?
必须返回一个有确定类型的值

?
功能上就像一个 “get 函数”

struct
Time

{

...

public int Hour

{

get

{

return hour;

}

...

}

private int hour, minute, second;

}

Time lunch = new Time();

...
Console.WriteLine(lunch.Hour);

//请注意,get和set不是关键字

当读一个属性的时候,属性的get语句自动运行。

get语句必须返回一个有确定类型的值。在上面的例子中,Time结构类有一个整型属性Hour,所以它的get语句必须返回一个整型值。

属性的返回值不能是void(从这里可以推断出字段的类型也不能是void)。这就意味着get语句必须包含一个完整的return语句(retun;这种形式是错误的)。

get语句可以在retun语句前包含任何其他的语句(比如,可以检查变量的类型),但return语句不能省略。

注意,get和set不是关键字,所以你可以在任何地方包括get/set语句中声明一个局部变量、常量的名字是get或set,但最好不要这样做

3 set 语句举例:

?
是通过value 标识符来进行赋值的

?
可以包含任何语句(甚至没有语句)

struct
Time

{

...

public int Hour

{

...

set
{

if (value < 0 || value > 24)

throw new
ArgumentException("value");

hour = value;

}

}

private int hour, minute, second;

}

Time lunch = new Time();

...

lunch.Hour = 12;

当写一个属性的时候,属性的set语句自动运行。

在上面的例子中,Time结构类有一个整型属性Hour,所以赋给这个属性的值必须是一个整型值。例如:

lunch.Hour
= 12;

把一个整型值12赋给了lunch的Hour属性,这个语句会自动调用属性的set语句。

set语句是通过value标识符来获得属性的赋值的。例如,如果12被赋给了Hour属性,那么vaue的值就是12。

注意的是value不是一个关键字。value只是在set语句中才是一个标识符。你可以在set语句外的任何语句声明value为一变量的名字。

例如:

public int Hour

{

get
{ int value; ... }//正确

set
{ int value; ... }//错误

}

4只读属性

?
只读属性只有get语句

?
任何写操作都会导致错误

?
就像一个只读字段

struct
Time

{

...

public int Hour

{

get

{

return hour;

}

}

private int hour, minute, second;

}

Time
lunch = new Time();

...

lunch.Hour
= 12; //错误

...

lunch.Hour
+= 2;//错误

一个属性可以不必同时声明get语句和set语句。你可以只声明一个get语句。在这种情况下,属性是只读的,任何写的操作都会导致错误。例如,下面的语句就会导致一个错误:

lunch.Hour
= 12;

因为Hour是只读属性。

但要注意的是,属性必须至少包括一个get或set语句,一个属性不能是空的:

public
int Hour { }//错误

5只写属性

? 只写属性只能有set 语句

?
任何读操作都是错误的

struct
Time

{

...

public int Hour

{

set {

if (value < 0 || value > 24)

throw new
OutOfRangeException("Hour");

hour = value;

}

}

private int hour, minute, second;

}

Time
lunch = new Time();

...

Console.WriteLine(lunch.Hour);
//错误

...

lunch.Hour
+= 12;//错误

一个属性可以不必同时声明get语句和set语句。你可以只声明一个set语句。在这种情况下,属性是只写的,任何读的操作都会导致错误。例如,下面的语句就会导致一个错误:

Console.WriteLine(lunch.Hour);

因为Hour是只写属性。

而下面的例子则看上去好像是对的:

lunch.Hour
+= 2;

这句语句的实际运作是这样的:

lunch.Hour
= lunch.Hour + 2;

它执行了读的操作,因此是错误的。因此,像+=这种复合型的赋值操作符既不能用于只读属性,也不能用于只写属性。

6综合举例

在下面的示例中,Button 类定义一个 Caption 属性。

public
class Button

{

private
string caption;

public
string Caption

{

get

{

return
caption;

}

set

{

caption
= value;

Repaint();

}

}

}

可读取并写入的属性(如 Caption)同时包含 get 和 set 访问器。

当读取属性值时调用 get 访问器;当写入属性值时则调用 set 访问器。

在 set 访问器中,属性的新值是通过一个名为 value 的隐式参数来赋值的。

属性声明相对直接一些,但是属性的实际值在它们被使用时才可见。

例如,读取和写入 Caption 属性的方式可以与读取和写入字段相同:

Button
b = new Button();

b.Caption
= "ABC"; // set; causes
repaint

string
s = b.Caption; // get

b.Caption
+= "DEF"; // get & set;
causes repaint

六方法(函数)

1基本语法

C#不支持全局函数

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

无源文件和头文件之分

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

函数不能有结尾分号

sealed class Methods

{

void Inline()

{
...

}

void
Error()

{
...

}; //错误,函数不能有结尾分号

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

sealed
class Methods

{

...

};//可以有结尾分号

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

sealed
class Methods

{

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

}

2函数重载

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

函数名和参数称为标识

标识必须唯一

返回值类型不是标识

和C++与Java一样,C#允许一个类声明两个以上的同名函数,只要参数的类型或个数不同。这就是重载。

但是,一个类不能包含标识为相同的实例函数和静态函数

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一样,返回值的类型不是标识的一部分,不能被用作重载的标准,例如:

sealed
class AlsoIllegal

{

int
Random() { ... }

double
Random() { ... }//错误

3ref和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型参数

}

ref和out修饰符可以是一个函数的标识。但是你不能同时重载ref和out型参数。

ref和out修饰符在某种意义上是“安全的“,因为只有ref型实参才能传递给ref型函数参数,

只有out型实参才能传递给out型函数参数。

但是,当调用函数的时候,你会非常容易忘记ref和out修饰符,所以最好不要重载ref和out型参数。

例如:

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修饰符

}

}

4方法中的参数

1

总说:

值参数

引用参数ref 用ref修饰

输出参数out 用out修饰

数组行参数 用params修饰

2

值参数:

没有被ref 或 out修饰的函数参数是一个值型参数。

值型参数只有在该参数所属的函数被调用的时候才存在,并且用调用时所传递的实参的值来进行初始化。

当函数调用结束时,值型参数不复存在

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

int
arg; // arg没有被赋初值

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

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

Method(42);

Method(21
+ 21);

3

引用型参数

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

没有发生复制

实参必须预先被赋值

实参必须是一个变量类型

实参和函数参数都要有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); //错误,引用型参数的实参不能是常量

4

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型参数的实参不能是常量

5

ref与out区别

ref:

static
void Main()

{

int arg = 0; //ref必须要初始化,在用,而out不用初始化

Console.Write(arg);

Method(ref arg);

Console.Write(arg);

}

out:

static void Main()

{

int arg;

Method(out arg); //没有初始化

Console.Write(arg);

}

6

参数数组

参数数组用 params 修饰符声明。

一个给定的方法只能有一个参数数组,而且它必须始终是最后一个指定的参数。

参数数组的类型总是一维数组类型。

using System;

class Test

{

static
void F(params int[] args) {

Console.WriteLine("#
of arguments: {0}", args.Length);

for
(int i = 0; i < args.Length; i++)

Console.WriteLine("\targs[{0}]
= {1}", i, args[i]);

}

static
void Main() {

F();

F(1);

F(1,
2);

F(1,
2, 3);

F(new
int[] {1, 2, 3, 4});

}

}

显示了带数目可变的 int 参数的方法 F,以及对此方法的若干个调用。输出为:

# of arguments: 0

# of arguments: 1

args[0]
= 1

# of arguments: 2

args[0]
= 1

args[1]
= 2

# of arguments: 3

args[0]
= 1

args[1]
= 2

args[2]
= 3

# of arguments: 4

args[0]
= 1

args[1]
= 2

args[2]
= 3

args[3]
= 4

5静态成员与非静态成员

若将类中的某个成员声明为static

该成员成为静态成员

类中的成员要么是静态,要么是非静态的

非静态成员为实例专用,只有被实例化了

非静态成员才有意义

静态成员在内存中只有一个区域,而非静态成员与类的实例联系在一起,每当创建一个实例,系统会为创建 的实例创建一个非静态成员

这样在内存区域中有多个副本

5静态与非静态方法

理解这个静态方法
可以联想 Console.WriteLine() 这个方法就是静态的

七委托

八索引

九事件

十自动内存管理

类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类

第三部分 OOP技术

类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类类

一构造函数与析构函数

二接口

一个接口定义了一个协议。一个实现了一个接口的类或结构必须符合它的协议。一个接口可以从多个基本接口继承,而一个类或结构也可以实现多个接口。

接口可以包含方法、属性、事件和索引。一个接口不能包含常数、域、操作符、构造函数、静态构造函数或类型,也不能包括任何类型的静态成员。所有接口成员隐含的都有公共访问。接口成员声明中包含任何修饰符是错误的. 接口成员不能被用abstract, public, protected, internal, private, virtual, override, 或 static 修饰符声明

//
用接口用了这么久,今天才知道接口也可以声明属性,索引器和事件。

//真是罪过~~

//提示:在VS中,把光标定位在下面一行的ITest中,按alt+shit+F10,可以自动实现接口

interface ITest

{

//声明事件

event Mydelegate
MyEvent;

//声明属性

string Str { set; get; }

//声明索引器

string this[int i] { set; get; }

//声明方法

void test(int t);

}

//注意代理要声明在命名空间下,接口外

public delegate
void Mydelegate(string str);

接口自己不为它所定义的成员提供执行程序。接口只是指定必须被实现这个接口的类或接口提供的成员。

1.1 接口声明

一个接口声明是一个类型声明 (§9.5) 它声明了新的接口类型。

interface-declaration:

attributesopt
interface-modifiersopt
interface identifier
interface-baseopt
interface-body ;opt


一个接口声明由下面的方式组成:一个可选的属性集合, 跟着一个可选的接口修饰符集合, 跟着关键词interface 和一个命名接口的标识符,还可以跟着一个可选的接口基本说明,跟着一个接口主体,最后可以选择跟一个分号

1.1.1 接口修饰符\interface-modifier:

new

public

protected

internal

private

对于相同的修饰符在一个接口声明中出现多次是错误的。

new 修饰符是在嵌套接口中唯一被允许存在的修饰符。它说明用相同的名称隐藏一个继承的成员 public, protected, internal和private 修饰符控制接口的访问能力。根据发生接口声明的上下文,只有这些修饰符中的一些会被允许

1.1.2 基本接口

一个接口可以从零或多个接口继承,那些被称为这个接口的显式基本接口。当一个接口有比零多的显式基本接口时,那么在接口的声明中的形式为,接口标识符后面跟着由一个冒号和一个用逗号分开的基本接口标识符列表。

interface-base:

: interface-type-list

一个接口的显式基本接口必须可访问。例如,在一个公共接口的基本接口中指定一个私有接口是错误的。

1、C#中的接口是单独于类来定义的。这和 C++模型是对立的,在 C++中接口实际上就是抽象基类。

  2、接口和类都能够继承多个接口。

  3、而类能够继承一个基类,接口根本不能继承类。

一个接口直接或间接地从它自己继承是错误的。

接口方法

接口方法使用接口方法声明(interface-method-declaration)来声明:

interface-method-declaration:

attributesopt newopt return-type
identifier (formal-parameter-listopt);


接口方法声明中的属性(attributes), 返回类型(return-type), 标识符(identifier), 和 形式参数列表(formal-parameter-lis)t 与一个类的方法声明中的那些有相同的意义。一个接口方法声明不允许指定一个方法主体,而声明通常用一个分号结束。

C#中的接口

1.都是“虚的”不能被实例化,这也是接口中为什么不能包含字段--成员变量的原因2.正因为接口是虚的,所以接口内的索引,属性,时间等只能有声明,而不能在接口内实现,具体如何实现是派生接口或者派生类的事.

3.都具有模板的性质,如果一个接口或者类从某一个接口继承,它将自动具有被集成者的特征(包括索引,属性,函数,实践等).

4.接口支持多重继承,而C#中,类之支持单一继承,接口实际表示的是一种承载能力,

对接口成员的访问

对接口方法的调用和采用索引指示器访问的规则与类中的情况也是相同的。如果底层成员的命名与继承而来的高层成员一致,那么底层成员将覆盖同名的高层成员。但由于接口支持多继承,在多继承中,如果两个父接口含有同名的成员,这就产生了二义性(这也正是C#中取消了类的多继承机制的原因之一),这时需要进行显式的定义:interface ISequence

{

 int Count { get; set; }

}

interface IRing

{

 void Count(int i) ;

}

interface IRingSequence: ISequence,
IRing { }

 class CTest

{

  void Test(IRingSequence rs)

{

   //rs.Count(1) ; 错误, Count 有二义性

   //rs.Count = 1; 错误, Count 有二义性

   ((ISequence)rs).Count = 1; // 正确

   ((IRing)rs).Count(1) ; // 正确调用IRing.Count

  }

}

//上面的例子中,前两条语句rs
.Count(1)和rs .Count = 1会产生二义性,从而导致编译时错误,

//因此必须显式地给rs 指派父接口类型,这种指派在运行时不会带来额外的开销。

//再看下面的例子:

interface IInteger

{

 void Add(int i) ;

}

interface IDouble

{

 void Add(double d) ;

}

interface INumber: IInteger,
IDouble {}

 class CMyTest

{

 void Test(INumber Num)

{

   //
Num.Add(1) ; 错误

   Num.Add(1.0) ; // 正确

   ((IInteger)n).Add(1)
; // 正确

   ((IDouble)n).Add(1)
; // 正确

  }

}

//调用Num.Add(1) 会导致二义性,因为候选的重载方法的参数类型均适用。

//但是,调用Num.Add(1.0) 是允许的,

//因为1.0 是浮点数参数类型与方法IInteger.Add()的参数类型不一致,

//这时只有IDouble.Add 才是适用的。

//不过只要加入了显式的指派,就决不会产生二义性。

三继承

四多态

C#的四个基本技巧

发布:dxy 字体:[增加
减小] 类型:转载

1.如果可能尽量使用接口来编程

  .NET框架包括类和接口,在编写程序的时候,你可能知道正在用.NET的哪个类。然而,在这种情况下如果你用.NET支持的接口而不是它的类来编程时,代码会变得更加稳定、可用性会更高。请分析下面的代码:

private void LoadList (object [] items, ListBox l)

{

 for (int i = 0; i < items.Length;i++)

  l.Items.Add (items[i].ToString ());

}

  这个函数从一个可为任何对象的数组中加载ListBox,这段代码被限定为只能使用数组。假想过些时候你发现那些对象存在数据库中,或别的集合中。那么你需要修改程序来使用不同的集合类型。如果你用ICollection接口来写那段程序,你就不用修改那段程序了,对于任何实现ICollection接口的类型它都能很好的工作:

private void LoadList (ICollection items,ListBox l)

{

  foreach (object o in items)

  l.Items.Add (o.ToString ());

}

  ICollection被数组和所有System.Collection中的集合实现。此外,多维数组也支持ICollection接口。如果那还不够的话,数据库.NET类同样支持ICollection接口。用接口写的这个函数不用需改就可以才许多中情况下使用。

  2. 使用属性代替原始数据

  因为属性已经成为语言本身的元素,所以声明数据元素时它的作用域等级没有必要大于private。因为代码本身会把属性看成数据元素,你并没有失去使用简单数据类型的便利性 。相反它会使你的代码更加灵活功能更加强大。属性使你的数据元素封装性更好。属性可以让你使用lazy evaluation来返回数据。lazy evaluation的意思是当用户请求时才计算它的值,而不是一直保留着它。

  最后,属性可以是virtual也可以是abstract。你也可以在接口中定义属性。

  这里还有维护方面的因素应当注意:尽管操作两者的方法是一样的,但是你把一个数据元素变成属性,那么原先客户端的程序便不能访问服务端的新版本程序了。实际上对于在Web service中你想实现序列化的值你可以把它们变成属性来使用:

private int TheMonth = 0;

[XmlAttribute ("Month")]

public int Month

{

 get {

  return TheMonth;

 }

 set {

  TheMonth = value;

 }

}

  简单通过属性就可以使你的所有数据元素私有化。

  3. 在Producer/Consumer 的Idiom中使用Delegate

  当你生成一个实现producer idiom类的时候,使用deletate来通知consumer。这种方法相对于用接口更加灵活。Delegate是多点传送的,所以不用加额外的代码你就何以支持多用户。相对于用接口这样做可使类之间的耦合性降低。

  下面的类处理键盘输入并把它传给所有的registered listeners:

public class KeyboardProcessor

{

private OnGetLine theFunc = null;

public OnGetLine OnGetLineCallback {

 get {

  return theFunc;

 }

 set {

  theFunc = value;

 }

}

public void Run (){

// Read input.

// If there is any listeners, publish:

string s;

do {

 s = Console.ReadLine ();

 if (s.Length == 0)

  break;

 if (theFunc != null){

  System.Delegate [] funcs =theFunc.GetInvocationList();

  foreach (OnGetLine f in funcs) {

   try {

    f (s);

   } catch (Exception e) {

    Console.WriteLine

    ("Caught Exception: {0}", e.Message);

   }

  }

 }

} while (true);

}

  任何数目的listeners都可注册到producer,它们所要做的只是提供一个特定的函数:deletate。

  4. 注意初始化顺序

  C#中对于一些变量声明加入了initializer的概念。它们在构造函数之前被执行,实际上变量在基类的构造函数执行前之前被初始化。

  所以,在初始化变量的时候不要用基类中的数据,因为它们还没有被构造。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: