本篇内容参考C#图解教程
本篇内容偏向基础,适合0基础的朋友快速上手,也适合有一定C语言(或其他语言如C++,java)基础的人快速上手C#。同时适合unity引擎的初学者,更加详细的了解C#语言。
本文内容基本涵盖C#基础内容,对于接口,事件等会给出学习资料但文章中不涉及。
1.C#程序的概述
1.1一个简单的C#程序
using System;
namespace HelloWorld
{
class Program
{
static void Main(string[] args)
{
Console.WriteLine("Hello World!");
}
}
}
运行结果:
Image
一个简单的C#程序由一下部分组成:
(1)using命令行(如行1告诉程序使用System这个命名空间的类型)
(2)声明当前程序内容的命名空间()
(3)在命名空间中声明程序所需的类(与C++的类相似)
(4)写出Main方法,程序由Main方法的开头开始执行直到Main方法结束,程序结束
1.2标识符
标识符是一种字符串可以用来命名变量、方法、参数
标识符字符串的规则:
(1)字母和下划线可以出现在标识符的任何位置
(2)数字不可以放在首位
(3)@字符只能出现在标识符的首位
1.3关键字
同C/C++一样不可以将变量、方法、参数命名成关键字 如下是C#的关键字表
1.4空白
程序中的空白指的是没有可视化输出的字符,程序员在代码中使用空白将被编译器忽略,但他可以使代码更加清晰易懂。
空白字符包括空格,制表符(tab),换行符、回车符
1.5语句
C#的语句与C++,JAVA等相似,相信绝大多数人并不是0基础来看C#知识的这部分就跳过。
1.6从程序中输出文本
在C#学习的过程中免不了调试代码,为了可以更加清晰的显示数据,我们需要对变量进行输出所以给出在Console的类,该类包含了输入到输出到控制台窗口的方法。
Write与WriteLine 使用方法形如:
System.Console.Write(“ ”);
System.Console.WriteLine(“ ”);
1.6.2代码WriteLine的使用:
using System;
class Program
{
static void Main()
{
int a = 5;
int b = 10;
Console.WriteLine("a = {0},b = {1}", a, b);
}
}
Console.WriteLine的括号中一共有三个参数,第一个参数和普通的输出字符串类似,只是加入了”{int}”这样的标记,例如”{0}”的意思就是在这个标记所在处插入Console.WriteLine的括号中的第一个参数;”{1}”的意思就是在这个标记处插入Console.WriteLine的括号中的第二个参数。第二个参数是用来控制输出的字符占多少位(类似C++中的setfill()函数)、第三个参数是是数字格式说明符。
以下是标准数字格式说明符
Image
Write与WriteLine的不同
Write在输出不换行
WriteLine输出后换行
1.7为代码添加注释
C#中单行注释 // 与带分隔符号 / / 的注释用法与C/C++一致。
2.类的基本概念
在第二章开始前提一嘴在C#的变量类型与C++基本一致所以不过多赘述,只给出一些定义, C# 中存在一系列基本数据类型,它们包含整数、浮点数、布尔值(即逻辑值,只能是真或假)、字符、字符串。
(1)C# 中,使用类型变量名; 语句声明一个变量。变量遵循先声明,后使用的原则。要使用一个变量,必须先在前面声明它。
(2)C# 中,使用变量名 = 值; 语句为变量赋值。赋值语句的意思是,在计算出值后,将变量名代表的变量修改为这个值。
(3)C#中,使用类型变量名 = 值; 语句声明一个变量,同时给这个变量赋值。
(4)对变量的首次赋值称为赋初值。C# 中遵循先赋初值,后使用的原则。要将变量用于除赋值外的用途,必须保证这个变量已经被赋初值。(在C#中没有想C++一样的)
(5)C#中,使用 ((类型)值) 表达式将值转换为指定类型。这与 C++ 中 C 风格的类型转换语法相同。在语法上 C# 只支持这一种转换方式,意即,C# 不支持 C++ 中 类型(值) 这样的类型转换。
(6)C#中,支持一些 C++ 中不支持的隐式类型转换。String 加上 long时会首先隐式转换为 string,而两个字符串相加意味着字符串的连接。
Image
2.1类的概述
类是一种活动的数据结构其有三大属性封装,继承,多态
类有数据成员,函数成员组成。
数据成员用于存储数据:由字段(相当于C/C++的变量),常量组成
函数成员用于执行代码:方法(相当于C/C++的函数),属性,运算符,析构函数,构造函数,索引器,事件
2.2声明类
类的声明定义新的类的特征和成员,与创建类的实例,但创建实例的模板。类的声明提供下列内容:类的声明,类的特征,类的成员。
Class Student{//关键字 类名
//类的成员声明
}
2.3创建类的实例
声明类只是说明了类的数据成员与函数成员,而并不会实际的开辟空间来存储。想要创建一个类,并为实际的数据分配内存,需要用到以下三点。
1.使用关键字new
2.要分配内存的实例的类型名称
3.使用一对圆括号(圆括号中可能有参数也可能没有参数,这取决于构造函数)
形如:
类名 变量名 = new 类名();
var 变量名 = new 类名();
或
类名 变量名;
变量名 = new 类名();
2.3类中的访问修饰符
2.3.1类中的访问修饰符的概述 访问修饰符一般出现在类中成员声明之前: 语法:
字段:
访问修饰符 类型 标识符;
方法:
访问修饰符 返回类型 方法名( );
访问修饰符一共五种包括 (private) (public) (protected) (internal) (protected internal)
在本文中只介绍前两种剩余的留到C#继承中讲解。
2.3.2private(私有访问)与public(公有访问)
2.3.2.1 private 访问修饰符
私有成员只能从声明它的类的内部访问,其他的类看不见或无法访问它们。
私有访问是默认的访问级别、所以,如果一个成员在声明时不带访问修饰符,那它就是私有成员。还可以使用private访问修饰符显式地将一个成员声明为私有。隐式地声明私有成员和显式地声明在语义上没有不同,两种形式是等价的。
例如,下面的两个声明都指定了private int成员:
int MyInt1; //隐式声明为私有
private int MyInt2; //显式声明为私有
2.3.2.2 public访问修饰符
实例的公有成员可以被程序中的其他对象访问。必须使用public访问修饰符指定公有访问。
形如public int MyInt;
类C1声明了公有和私有的字段和方法
class C1
{
int F1; //隐式私有字段
private int F2; //显式私有字段
public int F3; //公有字段
void DoCalc() //隐式私有方法
{
……
}
public int GetVal() //公有方法
{
……
}
{
Image
2.4从类的内部访问成员
类的成员仅用其他类成员的名称就可以访问它们。
例如,下面的类声明展示了类的方法对字段和其他方法的访问。即使字段和两个方法被为private,类的所有成员还是可以被类的任何方法(或任何函数成员)访问。
class DaysTemp
{
//字段
private int High=75;
private int Low=45;
//方法
private int Get High()
{
return High;//访问私有字段
}
private int Get Low()
{
return Low;//访问私有字段
}
public float Average()
{
return (GetHigh() + GetLow()) / 2;
}
}
2.5从类的外部访问成员
要从类的外部访问实例成员,必须包括变量名称和成员名称,中间用句点(.)分隔。这称为点运算符,后文会详细讨论。
例如,下面代码的第二行展示了一个从类的外部访问方法的示例:
DaysTempmyDt =new DaysTemp();//创建类的对象
float fValue=myDt. Average();//从外部访问
DaysTemp内的两个字段被声明为public,所以可以从类的外部访问它们。
方法Main是类Program的成员。它创建了一个变量和类DaysTemp的对象,并给对象的字段赋值。然后它读取字段的值并打印出来。
class DaysTemp//声明类DaysTemp
{
public int High=75;
public int Low=45;
}
class Program//声明类Program
{
static void Main()
{
DaysTemp temp=new DaysTemp(); //创建对象
temp.High=85;//字段赋值
temp.Low = 60;
Console.WriteLine(“High: {0}”,temp.High);
Console.WriteLine($”Low: {temp.Low}”);
}
}
3.方法
3.1方法的结构(本章内容如果是有C/C++基础的人可以粗略跳过,方法就相当于C++中的函数)
方法是一块具有名称的代码。可以使用方法的名称从别的地方执行代码,也可以把数据方法并接收数据输出。
方法主要有两个部分方法头与方法体;
方法头指定方法的特征,包括:
(1)方法是否返回数据,如果返回,返回什么类型;
(2)方法的名称
(3)哪种类型的数据可以传递给方法或从方法返回,以及如何应对处理这些数据
方法体包含可执行代码的语句序列,执行过程从方法体的第一条语句,一直到整个方法结束。
3.2局部变量与局部常量
局部变量:
局部变量也保存数据。字段通常保存和对象状态有关的数据,而建局部变量经常是用于保存
局部的或临时的计算数据。
形如:变量类型 变量名称 = 可选的初始化语句(想同类型的变量或者值);
(1)局部变量的存在和生存期仅限于创建它的块及其内嵌的块。
(2)从声明它的那一点开始存在,在块完成执行时结束存在。
(3)可以在方法体内任意位置声明局部变量,但必须在使用它们前声明。
局部常量:
局部常量很像局部变量,只是一旦被初始化,它的值就不能改变了。如同局部变量,局部瘤量必须声明在块的内部。
形如:关键字const 变量类型 变量名称 = (必须进行初始化);
(1)在声明时必须初始化。
(2)在声明后不能改变。
相信大部分人有一定的编程基础所以下内容跳过(与C/C++基本一致就不多赘述了)
3.3方法的调用 跟(C/C++)一样,使用方法名称并带上与之对应的方法列表。
3.4返回值
3.5返回语句与void方法
3.6形参与实参
3.7局部函数(从C#7.0开始添加的新标准)
方法块内的代码可以调用另一个方法。如果另一个方法在同一个类内,可以直接使用它的名称并传入所需的参数(参见下一节)进行调用。如果另一个方法在不同的类中、必须通过这个类的一个对象实例调用它。另一个类中的方法必须使用 public访问修饰符声明。、
从C#7.0开始,你可以在一个方法中声明另一个单独的方法。这样可以将嵌入的方法跟其他代码隔离开来、所以它只能在包含它的方法内调用。如果使用恰当,这可以使代码更清晰,更于维护。这些嵌入的方法被称为局部函数。 与局部变量必须在使用之前进行声明不同,你可以在包含方法的任意位置声明局部函数。下面的代码演示了一个 MethodWithlocalFunction 方法,它包含了一个局部函数。
class Program
{
public void MethodWithLocalFunction ()
{
int MyLocalFunction (int zì)//声明局部函数
{
return z1*5;
}
int results= MyLocalFunction (5);//调用局部函数
Console. Writeline($"Results of local function call:{results}\\");
}
static void Main(string[]args)
{
Program myProgram=new Program();
myProgram. MethodWithLocalFunction ();//调用方法
}
}
3.8引用参数(这跟C++中的引用&几乎一致)
使用方法:
(1)使用引用参数时,必须在方法的声明和调用中都使用ref修饰符。
(2)实参必须是变量,在用作实参前必须被赋值。如果是引用类型变量,可以赋值为一个用或null。
引用参数的特征:
(1)不会在栈上为形参分配内存。
(2)形参的参数名将作为实参变量的别名,指向相同的内存位置。
注:由于形参名和实参名指向相同的内存位置,所以在方法的执行过程中对形参体方法完成后依然可见(表现在实参变量上)。记住要在方法的声明和调用上都使用ref关键字。
引用使用展示:
using System;
class Program
{
static void Main()
{
int a=5;
ref int b = ref a;
Console.WriteLine($"a = {a},b = {b}");
b = 10;
Console.WriteLine($"a = {a},b = {b}");
}
}
运行结果:
Image
引用作为方法参数代码展示(这与C++引用几乎一致就不详细解释了):
using System;
class Method
{
public
void swap(ref int x,ref int y)
{
int temp=x;
x = y;
y = temp;
}
}
class Program
{
static void Main()
{
Method way = new Method();
int a = 5;
int b = 10;
Console.WriteLine("a = {0},b = {1}", a, b);
Console.WriteLine("after swap value");
way.swap(ref a,ref b);
Console.WriteLine("a = {0},b = {1}", a, b);
}
}
运行结果:
Image
ref局部变量与ref返回
别名功能(3.8第一个代码)不是ref局部变量功能最常见的用途。实际上,它经常和ref返回功能一般用。ref返回功能提供了一种使方法返回变量引用而不是变量值的方法。这里需要的额外语法使用了ref关键字两次:一次是在方法的返回类型声明之前;另一次是在return关键字之后,被返回对象的变量名之前
代码展示:
using System;
class Score
{
private int score;
public void display()
{
Console.WriteLine($"score = {score}");
}
public ref int get_s()
{
return ref score;
}
public void s()
{
this.score = 5;
}
}
class Program
{
static void Main()
{
Score s1 = new Score();
s1.s();
s1.display();
ref int r_score =ref s1.get_s();
r_score = 10;
s1.display();
}
}
说明我们在此程序中运用,ref返回在类外通过修改r_score的值(按C++理解方式,此时r_score与s1中score有相同的地址)
运行结果:
Image
3.9输出参数(在实际使用中比较少)
输出参数用于从方法体内把数据传出到调用代码,它们的行为与引用参数类似。如同引用参数,输出参数有以下要求。
(1)必须在声明和调用中都使用修饰符。输出参数的修饰符是out而不是ref。
(2)和引用参数相似,实参必须是变量,而不能是其他类型的表达式。这是有道理的,因为方法需要内存位置来保存返回值。
与引用参数类似,输出参数的形参充当实参的别名。形参和实参都是同一块内存位置的名称。显然,在方法内对形参做的任何改变在方法执行完成之后(通过实参变量)都是可见的。
与引用参数不同,输出参数有以下要求。
(1)在方法内部、给输出参数赋值之后才能读取它。这意味着参数的初始值是无关的,而且没有必要在方法调用之前为实参赋值。
(2)在方法内部,在方法返回之前,代码中每条可能的路径都必须为所有输出参数赋值。
因为方法内的代码在读取输出参数之前必须对其写人,所以不可能使用输出参数把数据传入方法。事实上,如果方法中有任何执行路径试图在方法给输出参数赋值之前读取它的值,编译器就会产生一条错误消息。
3.10参数数组
3.10.1参数数组概述
在之前的描述中,一个形参必须严格地对应一个实参。参数数组则不同,它允许特定类型的零个或多个实参对应一个特定的形参。参数数组的重点如下。
(1)在一个参数列表中只能有一个参数数组。
(2)如果有参数数组,它必须是列表中的最后一个。
(3)由参数数组表示的所有参数必须是同一类型。
声明一个参数数组时必须做的事如下。 (1)在数据类型前使用params修饰符。 (2)在数据类型后放置一组空的方括号。
形如:
//方法返回类型 方法名称(修饰符 变量类型[ ] 参数名称)
Int sum( params int[ ] arrry) {
……
}
类型名后面的空方括号指明了参数是一个整数数组。在这里不必在意数组的细节,它们将在后面详细阐述。而现在,你需要了解的内容如下。
(1)数组是一组有序的同一类型的数据项。
(2)数组使用一个数字索引进行访问。
(3)数组是一个引用类型,因此它的所有数据项都保存在堆中。
3.10.2 方法调用
可以使用两种方式为参数数组提供实参。
(1)一个用逗号分隔的该数据类型元素的列表。所有元素必须是方法声明中指定的
ListInts(10, 20, 30);//3个int
(2)一个该数据类型元素的一维数组。
int [] intArray = {1,2,3};
ListInts(intArray);//一个数组变量
注:在这些示例中,没有在调用时使用params修饰符。参数数组中修饰符他参数类型的模式并不相符。
params修饰符的用法总结如下。 (1)在声明中需要修饰符。 (2)在调用中不允许有修饰符。
3.11方法重载(与C++中的函数重载基本一致就不再详细赘述)
一个类中可以有多个同名方法,这叫作方法重载。使用相同名称的每个方法必须有一个和其他方法不同的签名。
(1)方法的名称;
(2)参数的数目;
(3)参数的数据类型和顺序;
(4)参数修饰符。
注:返回类型不是签名的一部分,而我们往往误认为它是签名的一部分。形参的名称也不是签名的一部分。
3.12命名参数与可选参数
命名参数是真的不想说,我感觉在实际的开发过程中没有人回去用(先挖个坑,万一以后真的有需要再来补上)
可选参数(跟构造函数中的参数默认值很像):
所谓可选参数就是可以在调用方法的时候包含这个参数,也可以省略它。
为了表明某个参数是可选的,你需要在方法声明中为该参数提供默认值。指定默认值的语法,和初始化局部变量的语法一样,如下面代码的方法声明所示。
形参b的默认值设置成3;因此、如果在调用方法的时候只有一个参数,方法会使用3作为第二个参数的初始值。
class MyClass
{
public int Cale(int a, int = 3)
{
return a+b;
}
static void Main()
{
MyClass me = new MyClass();
int r0 = mc. Calc(5,6);//使用显式值
int r1 = mc. Calc(5);//为b使用默认值
Console.WriteLine($"{r0},{r1}");
}
}
这段代码产生如下输出: 11,8
对于可选参数的声明,我们需要知道如下几个重要事项。
(1)不是所有的参数类型都可以作为可选参数。
(2)只要值类型的默认值在编译的时候可以确定,就可以使用值类型作为可选参数。只有在默认值是null的时候,引用类型才可以用作可选参数。
3.13递归
略……
4.深入理解类
4,1成员修饰符的顺序
在前面的内容中,你看到字段和方法的声明可以包括如public和private这样的修饰符。
类成员声明语句由下列部分组成:核心声明、一组可选的修饰符和一组可选的特性(attribute用于描述这个结构的语法如下。方括号表示方括号内的成分是可选的。
[特性] [修饰符] 核心声明
可选成分如下。
修饰符:
(1)如果有修饰符,必须放在核心声明之前。
(2)如果有多个修饰符,可以任意顺序排列。
特性
(1)如果有特性,必须放在修饰符和核心声明之前。
(2)如果有多个特性,可以任意顺序排列。
至此,只解释了两个修饰符,即public和private。以后会介绍特性。例如,public static都是修饰符,可以一起修饰某个声明。因为它们都是修饰符,所以可以任何顺序放置。两行代码在语义上是等价的;
public static int MaxVal;
static public int MaxVal;
4.2静态字段
静态字段被类的所有实例共享,所有实例都访问同一内存位置。因此,如果该内存位置的值被一个实例改变了,这种改变对所有的实例都可见。
可以使用static修饰符将字段声明为静态,如:
class D
{
int Mem1; //实例字段
static int Mem 2; //静态字段
}
例如,上面代码声明了类D,它含有静态字段Mem2和实例字段Mem1。Main定义类D的两个实例。静态成员Mem 2是与所有实例分开保存的。并且从实例内部,访问或更新静态字段的语法和访问或更新其他成员字段一样。
因为Mem2是静态的,所以类D的两个实例共享一个Mem2字段。如果Meme2被改变了,个改变在两个实例中都能看到。成员Mem 1没有声明为static,所以每个实例都有自己的副本。
4.3 从类的外部访问静态成员
方法一:
就像实例成员,静态成员也可以使用点运算符从类的外面进行访问,但因为没有实例,所以访问静态成员的方法使用类名 形如
class D {
static int Mem;
……
}
D.Mem = Value; //访问静态成员
代码:
using System;
class A
{
public const int N = 10010;
public static void display()
{
Console.WriteLine($"N = {N}");
}
}
class Program
{
static void Main()
{
Console.WriteLine($"A.N = {A.N}");
A.display();
}
}
运行结果:
Image
方法二:
使用using static声明,在该成员所属的类中包含一个using static声明。 样例如下
using static System.Console;
…………
WriteLine(“$hello world”);
静态成员的生存周期(静态成员的生命期与实例成员的不同):
只有在实例创建之后才产生实例成员,在实例销毁之后实例成员也就不存在了。但是即使类没有实例,也存在静态成员,并且可以访问。
4.4静态函数成员
(1)如同静态字段,静态函数成员独立于任何类实例。即使没有类的实例,仍然可以调态方法。
(2)静态函数成员不能访问实例成员,但能访问其他静态成员。 访问静态函数的方法与访问静态字段一致。
4.5其他静态类成员类型
方法√ 属性√ 构造函数√ 运算符√ 索引器× 事件√ 字段√ 类型√ 常量×
4.6成员常量
特别说明:与C和C++不同,在C#中没有全局常量。每个常量都必须声明在类型内。
成员常量类似前一章所述的局部常量,只是它们被声明在类声明中而不是方法 内,如下面的示例:
class MyClass
{
const int IntVal=100;//定义int类型常量
}
const double PI=3.1416;//错误:不能在类型声明之外声明
成员常量与前面讲的局部常量几乎一致不过是声明在类中。
4.7属性
4.7.1属性概述
属性是代表类实例或类中的数据项的成员。使用属性就像写入或读取一个字段,语法相同。
属性的特征:
(1)属性是命名的类成员。
(2)属性有类型。
(3)属性可以被赋值和读取。
(4)属性不一定为数据存储分配内存!
(5)属性会执行代码。
属性是一组(两个)匹配的、命名的、称为访问器的方法。
(1)set访问器为属性赋值。
(2)get访问器从属性获取值。
4.7.2属性声明和访问器
set和get访问器有预定义的语法和语义。可以把set访问器想象成一个方法,带有理参数,它“设置”属性的值。get访问器没有参数并从属性返回一个值。
set访问器:
(1)拥有一个单独的、隐式的值参,名称为value,与属性的类型相同;
(2)拥有一个返回类型void。
get访问器:
(1)没有参数;
(2)拥有一个与属性类型相同的返回类型;
形如:
Set与get说明:
set访问器中的隐式参数value是一个普通的值参。和其他值参一样,可以用它发送双数据到方法体或访问器块。在块的内部,可以像普通变量那样使用value,包括对它赋值。
get访问器的所有执行路径必须包含一条return语句,它返回一个属性类型的值。
访问器set和get可以以任何顺序声明,并且,除了这两个访问器外,属性上不允许有其他方法
4.7.3属性的示例
下面的代码展示了一个名为C1的类的声明示例,它含有一个名为MyValue的属性。
请注意,属性本身没有任何存储。取而代之,访问器决定如何处理发送进来的数据,以及应将什么数据发送出去。
在代码之前在介绍一下如何使用属性:
要写入一个属性,在赋值语句的左边使用属性的名称。
要读取一个属性,把属性的名称用在表达式中。
例如,下面的代码包含一个名为MyValue的属性的声明。只需使用属性名就可以写入和读取属性,就好像它是一个字段名。
int MyValue//属性声明
{
set{ ...}get{…‥}
}
MyValue= 5;//赋值: 隐式调用set方法
z = MyValue;//表达式: 隐式调用get方法
属性会根据是写入还是读取来隐式地调用适当的访问器。不能显式地调用访问器,因为这样做会产生编译错误。
y = MyValue. get();//错误!不能显式调用get访问器
MyValue. set(5);//错误!不能显式调用set访问器
代码如下:
using System;
class C1
{
private int m_value = 10;
pubilc int Myvalue //属性不分配内存
{
set { m_value = value; }
get { return m_value}
}
}
class Program
{
static void Main()
{
C1 c = new C1();
Console.WriteLine("Myvalue: {0}", c.Myvalue);
c.Myvalue = 20;
Console.WriteLine("Myvalue: {0}", c.Myvalue);
}
}
运行结果:
Image
C#7.0为属性的getter/setter引入了另一种语法(lambma表达式),这种语法使用表达函数体。这种语法只有在访问函数体由一个表达式组成的时候才能使用。(以后再详细介绍)
int MyValue
{
set=>value>100? 100: value;
get => theRealValue ;
}
4.7.4只读和只写属性
要想不定义属性的某个访问器,可以忽略该访问器的声明。
(1)只有get访问器的属性称为只读属性。只读属性能够安全地将一个数据项从类或类的实例中传出,而不必让调用者修改属性值。
(2)只有set访问器的属性称为只写属性。只写属性很少见,因为它们几乎没有实际用途。如果想在赋值时触发一个副作用,应 该使用方法而不是属性。
(3)两个访问器中至少有一个必须定义,否则编译器会产生一条错误消息。
4.7.5属性是用来替代公有字段的方法之一
属性比公有字段更好,理由如下。
(1)属性是函数成员而不是数据成员,允许你处理输入和输出,而公有字段不行。
(2)属性可以只读或只写,而字段不行。
(3)编译后的变量和编译后的属性语义不同。
4.7.6自动属性 这里留一个坑以后再填
4.7.7静态属性
属性也可以声明为static。静态属性的访问器和所有静态成员一样,具有以下特点。
(1)不能访问类的实例成员,但能被实例成员访问。
(2)不管类是否有实例,它们都是存在的。
(3)在类的内部,可以仅使用名称来引用静态属性。
(4)在类的外部,正如前面描述的,可以通过类名或者使用using static结构来引用静态属性。
例如,下面的代码展示了一个类,它带有名为MyVa1ue的自动实现的静态属性。在Main的头三行,即使没有类的实例,也能访问属性。Main的最后一行调用一个实例方法,它从类的内部访问属性。
代码展示:
using System;
using static ConsoleTestApp.Trivial;
namespace ConsoleTestApp
{
class Trivial
{
public static int MyValue { get; set; }
public void PrintValue()
{ Console.WriteLine("Value from inside:{0}", MyValue); }
}
class Program
{
static void Main()
{
Console.WriteLine("Init Value:{0}", Trivial.MyValue);
Trivial.MyValue = 10;
Console.WriteLine("New Value:{O}", Trivial.MyValue);
MyValue = 20;//从类的外部访问,但由于使用了using static,所以没有使用
Console.WriteLine($"New Value:{MyValue}");
Trivial tr = new Trivial();
tr.PrintValue();
}
}
}
4.8构造函数
(跟C++的构造函数基本一致所以接下来就直接给出代码就不详细讲解)
代码:
using System;
class Student
{
string m_name;
int m_id;
public Student()
{
m_name = "NO Student";
m_id = 0;
}
public Student(string name, int id)
{
m_name = name;
m_id = id;
}
public void display()
{
Console.WriteLine($"Name: {m_name} Id: {m_id}");
}
}
class Program
{
static void Main()
{
Student a = new Student();
Student b = new Student("Lijiaqin", 218228);
a.display();
b.display();
}
}
代码:
using System;
class Student
{
string m_name;
int m_id;
public static int number;
public static void fir()
{
number = 1;
}
public Student()
{
m_name = "NO Student";
m_id = 0;
number++;
}
public Student(string name, int id)
{
m_name = name;
m_id = id;
number++;
}
public void display()
{
Console.WriteLine($"Name: {m_name} Id: {m_id} Number: {number}");
}
}
class Program
{
static void Main()
{
Student.fir();
Student a = new Student();
a.display();
Student b = new Student("Lijiaqin", 218228);
b.display();
}
}
4.9析构函数
C#有自己的垃圾回收机制,不需要为类写出析构函数
4.10 readonly修饰符
字段可以用readonly修饰符声明。其作用类似于将字段声明为const,一旦值被设定就不能改变。
(1)const字段只能在字段的声明语句中初始化,而readonly字段可以在下列任意位置设置它的值。
(2)const字段的值必须可在编译时决定,而readonly字段的值可以在运行时决定。这种自由性允许你在不同的环境或不同的构造函数中设置不同的值。
(3)它可以是实例字段,也可以是静态字段,它在内存中有存储位置。
4.11 this关键字
this关键字在类中使用,是对当前实例的引用。它只能被用在下列类成员的代码块中。
(1)实例构造函数。
(2)实例方法
(3)属性和索引器的实例访问器(索引器将在下一节阐述)。
注:因为静态成员不是实例的一部分,所以不能在任何静态函数成员的代码中使用this关键字。更适当地说,this用于下列目的:
(1)用于区分类的成员和局部变量或参数;
(2)作为调用方法的实参。
例如,下面的代码声明了类MyClass,它有一个int字段和一个方法,方法带有一个单独的int参数。方法比较参数和字段的值并返回其中较大的值。唯一的问题是字段和形参的名称相同,都是var1。在方法内使用this关键字引用字段,以区分这两个名称。
class MyClass
{
int var1 =10;
public int ReturnMaxSum ( int var1)
{
Return var1 > this.var1 ? var1 : this.var1; //var1是参数,this.var1是字段
}
}
4.12 索引器
4.12.1索引器的引入
Image
4.12.2索引器的声明
声明索引器的语法如下所示。请注意以下几点。
(1)索引器没有名称。在名称的位置是关键字this。
(2)参数列表在方括号中间。
(3)参数列表中必须至少声明一个参数。
形如:
ReturnType this(关键字)[Type param 1 , ……](参数列表){
get {……}
Set{……}
}
4.12.3索引器和属性的对比
索引器和属性在很多方面是相似的。
(1)和属性一样,索引器不用分配内存来存储。
(2)索引器和属性都主要被用来访问其他数据成员,它们与这些成员关联,并为它们提供取和设置访问。属性通常表示单个数据成员。索引器通常表示多个数据成员。 说明:可以认为索引器是为类的多个数据成员提供get和set访问的属性。通过提供索引器,可以在许多可能的数据成员中进行选择。索引本身可以是任何类型,而不仅仅是数值类型。 关于索引器,还有一些注意事项如下。
(1)和属性一样,索引器可以只有一个访问器,也可以两个都有。
(2)索引器总是实例成员,因此不能被声明为static。
(3)和属性一样,实现get和set访问器的代码不一定要关联到某个字段或属性。这段代码可以做任何事情也可以什么都不做,只要get访问器返回某个指定类型的值即可。
4.12.4索引的set 与get访问器
set:
当索引器被用于赋值时,set访问器被调用,并接受两项数据,如下:
(1)一个名为value的隐式参数,其中持有要保存的数据;
(2)一个或更多索引参数,表示数据应该保存到哪里。
形如:emp[0]="Doe";
set访问器有如下语义: (1)它的返回类型为void。 (2)它使用的参数列表和索引器声明中的相同。 (3)它有一个名为value的隐式参数,值参类型和索引器类型相同。
get:
当使用索引器获取值时,可以通过一个或多个索引参数调用get访问器。索引参数指示获取哪个值。 形如:
string s=emp[0];
get访问器方法体内的代码必须检查索引参数,确定它表示的是哪个字段,如果get是以普通方法的语法书写的。get访问器有如下语义。 (1)它的参数列表和索引器声明中的相同。 (2)它返回与索引器类型相同的值。
4.12.5代码展示:
下面的代码为先前示例中的类Employee声明了一个索引器。
(1)索引器需要读写string类型的值,所以string必须声明为索引器的类型。它必须声明为public,以便从类的外部访问。
(2)3个字段被随意地索引为整数0-2,所以本例中方括号中间名为index的形参必须为int型。在set访问器方法体内,代码确定索引指的是哪个字段,并把隐式变量value的值赋给它。
(3)在get访问器方法体内,代码确定索引指的是哪个字段,并返回该字段的值。
代码1:
class Employee
{
public string LastName;//调用字段0
public string FirstName;//调用字段1
public string CityOfBirth ;//调用字段2
public string this[int index]//索引器声明
{
set//set访问器声明
{
switch(index) {
case 0: LastName = value;
break;
case 1: FirstName = value;
break;
case 2: CityOfBirth = value;
break;
default://(异常以后再聊)
throw new ArgumentOutOfRangeException ("index");
}
}
get//get访问器声明
{
switch(index){
case 0: return LastName;
case 1: return FirstName;
case 2: return CityOfBirth ;
default://(异常)
throw new ArgumentOutOfRangeException ("index");
}
}
}
}
代码2:
class Class 1
{
int Tempo;//私有字段
int Tempi;//私有字段
public int this[int index]//索引
{
get
{
return(0==index? Temp0:Temp 1;)//返回Tempo或Tempi的值
}
set
{
if (0== index)
Temp0=value;//注意隐式变量"value"
else
Tempi=value;//注意隐式变量"value"
}
}
}
class Example
{
static void Main()
{
Class1 a=new Class1();
Console. Writeline("Values--TO:{0},T1:{1}",a[0],a[1]);
a[0]=15;
a[1]=20;
Console.WriteLine($"Values--TO:{a[O]},T1:{a[1]}");}
}
}
代码运行结果:
Values -- T0: 0, T1:0;
Values -- T0: 15, T20;