一个程序的本质就是”数据+算法“,以下四种成员均是用来表示”数据“。
字段的本质是一种变量,作用是用来存储数据的,字段是为一个对象或者类型存储数据的。和方法体中的局部变量不同,方法体中的局部变量是方法运行时,帮助其方法暂时地存储数据,而字段帮助对象或类型存储数据。当一个对象或类型拥有多个字段的时候,这些字段的值就能组合起来表示这个对象或类型当前的状态。 所以简单的来说,字段用来为对象或类型存储数据,多个字段的组合能表达对象或类型的状态。
字段本质是变量,而且是对象或类型的成员,所以一般被称作”成员变量“
”实例字段“ 与 ”静态字段“ 的区别就在于 ”实例字段“ 的组合表示与其关联的对象的状态;而静态字段则表示与其关联的类型的状态(为某个数据类型保存数据)。
将类实例化之后能对其可以公开访问的数据类型(实例字段)进行成员访问从可以用来表示这个实例的状态。
实例字段为实例存储数据,数据的组合表示此实例当前的状态。
class Program
{
static void Main(string[] args)
{
Student student1 = new Student();//创建一个Student的实例student1
student1.Age = 40;
student1.Score = 90;//实例字段存储数据的组合表示当前实例的状态
Student student2 = new Student();//创建一个Student的实例student2
student1.Age = 40;
student1.Score = 40;//实例字段存储数据的组合表示当前实例的状态
}
}
class Student //声明一个“类”
{
//为Student声明"数据类型",创建成员,public为公开访问
public int Age;
public int Score;
//为Student声明"静态字段"成员,public为公开访问,static表示静态
public static int AvarageAge;
public static int AvarageScore;
public static int Amount;
public Student() //构造函数
{
//为构造函数添加一个逻辑,每次引用Student创建实例的时候Amount都会+1
Student.Amount++;
}
public static void ReportAmount()//创建一个静态方法
{
Console.WriteLine(Amount);
}
}
静态字段和静态方法都可以在引用类创建实例的时候被访问。
引用这个类创建实例的时候方可访问类中的静态字段、静态方法和实例字段。静态字段可以不用创建实例,可以直接调用方法。如:
构造函数:构造函数 ,是一种特殊的方法。主要用来在创建对象时初始化对象, 即为对象成员变量赋初始值,总与new运算符一起使用在创建对象的语句中。特别的一个类可以有多个构造函数 ,可根据其参数个数的不同或参数类型的不同来区分它们 即构造函数的重载。
声明字段的语法格式:
字段(field)是类的成员,但并不是一个语句,所以声明字段的时候要写在 “类体” 里面而不是 ”方法体或方法体“ 里面(那就变成了 ”局部变量“ 了)。
在字段声明的语法中带 ”opt“ 下标的都是可选的或者可忽略的。
(字段修饰符):可一个或者多个,但必须是有意义的修饰符组合,如: private / public / public static;public 和 private 不可同时使用等。
其中字段被 ”readonly“(只读修饰符)修饰的字段叫 ”只读字段“,那么就只能读取字段中的值而不能修改。
readonly(只读修饰符):只读字段初始化是为了不让被初始化之后得值被改变或被重新赋值
只读实例字段的初始化只有一次机会,那就是在只读实例字段变量的构造器(构造函数)中对其进行初始化(也可以在声明时就进行初始化),在创建实例的时候给构造函数传值然后对只读实例字段进行初始化。静态实例字段初始化事件的发生是在对象被创建时(实例化时)。输出值为 ”1“
class Program
{
static void Main(string[] args)
{
Student student1 = new Student(1);
// student1.ID = 3; //这里会报错,不能再对只读实例字段初始化后的的变量再进行赋值
Console.WriteLine(student1.ID);//输出结果为”1“
}
}
class Student //声明一个“类”
{
public readonly int ID; //只读实例字段
public Student(int id) //构造函数
{
this.ID = id;
}
}
class Program
{
static void Main(string[] args)
{
Console.WriteLine(Brush.DefultColor.R);
Console.WriteLine(Brush.DefultColor.G);
Console.WriteLine(Brush.DefultColor.B);
//输出三个0
Brush.DefultColor = new Color() { R=255,G=255,B=255};
//这里会报错“无法对静态只读字段进行赋值”
}
}
struct Color //创建一个构造器
{
public int R;
public int G;
public int B;
}
class Brush //创建一个类,其中有静态只读实例,并且在实例化的时候就进行了显式初始化
{
public static readonly Color DefultColor = new Color() { R = 0, G = 0, B = 0 };
}
------------------------------写法一样,同样会报错
class Brush //创建一个类,其中有静态只读实例,并且在实例化的时候就进行了显式初始化
{
public static readonly Color DefultColor;
static Brush() //构造函数 (前面的 public 改成 static 变成静态构造函数)
{
Brush.DefultColor = new Color() { R = 0, G = 0, B = 0 };
}
}
(数据类型):byte、short、int、long、float、double、bool、char等。
(变量声明器):可以是 ”变量名“ 也可以是 ”变量名加上变量初始化器(也就是一个赋值符号加上一个初始值)“。
tips:在声明一个字段时对其进行显示初始化和在构造函数中对其进行初始化是一样的。建议声明字段的时候就对其进行显示初始化,方便日后分辨。
==================================================================================================================================================
① 从命名上看,字段(field)更偏向于对实例对象在内存中的布局,而属性(property)更偏向于反映现实世界中对象的特征;
② 从功能上看就是当用 字段(field)来表示/储存一个对象或类型的特征的状态/数据时,字段(field)内保存什么值你就只能访问到什么值; 但当你用属性(property)表示状态的时候不仅能访问到存储的值而且能额外提供一个帮助你实时、动态地计算对象或类型当前的某个特征。
③ 在对数据或特征的保护上看,使用 字段(field)向外暴露数据来表示对象或类型特征时容易被输入的非法值“污染”,使用 属性(property)可以在其中添加逻辑限制来保护字段不被非法值“污染”。
class Program
{
static void Main(string[] args)
{
Student student1 = new Student();
student1.Age = 40;
Student student2 = new Student();
student1.Age = 3200;
//此处年龄为3200,但是在人类年龄里属于非法值,可还是打印出来了,那么这个时候平均年龄就被输入的非法值污染并且输出了
Student student3 = new Student();
student1.Age = 38;
int 平均年龄 = (student1.Age + student2.Age + student3.Age) / 3;
Console.WriteLine(平均年龄);
}
}
class Student //声明一个“类”
{
public int Age; //声明一个字段来表示学生的年龄
}
所以用字段(field)来表示对象状态的时候非常不安全,所以就有了属性(property)来对表示对象状态特征的值进行保护,属性(property)其实是由 Get&Set 方法演变而来的:
在使用属性(property)来表示对象的状态时,对字段进行了私有化,外部无法对其访问,而是通过加上逻辑限制和实时(动态)计算得出来的值来表示对象的状态,那么就对对象或类型的特征表示起到了很好的保护作用(在给 “age” 加了一层私有访问再通过 Get&Set 方法计算得出值 的着一层皮之后,这里输入了一个非法值,运行抛出异常,对年龄特征的合理性有了一个很好的保护)。
属性(property)实际是从 Get&Set 的方法演变而来的,输入值时使用Set方法,获取值时使用Get方法。
抛出异常之后,可以使用 Try&Catch 方法来抓住异常防止程序直接崩溃,从而增强程序的容错性:
class Program
{
static void Main(string[] args)
{
try
{
Student student1 = new Student();
student1.SetAge(22);
Student student2 = new Student();
student2.SetAge(46);
Student student3 = new Student();
student3.SetAge(3200);
int 平均年龄 = (student1.GetAge() + student2.GetAge() + student3.GetAge()) / 3;
Console.WriteLine(平均年龄);
Console.ReadKey();
}
catch (Exception 异常)
{
//这里会输出异常信息,即“输入了错误的值”
Console.WriteLine(异常.Message);
Console.ReadKey();
}
}
}
class Student //声明一个“类”
{
private int age; //声明一个私有字段来表示学生的年龄
public int GetAge()
{
return age;//需要有返回值,GetAge()的时候得到年龄age
}
public void SetAge(int value)
{
if (value>=0&&value<=100)
{
age = value;
}
else
{
throw new Exception("输入了错误的值");
}
}
}
上面是用的传统的 Get&Set 对 字段(field)进行私有访问保护的编写,下面则是在C#中用Get&Set对属性(property)这种成员的编写法:
class Student //声明一个“类”
{
private int age; //声明一个私有字段来表示学生的年龄
public int GetAge()
{
return Age;//需要有返回值,GetAge()的时候得到年龄age
}
public void SetAge(int value)
{
if (value >= 0 && value <= 100)
{
Age = value;
}
else
{
throw new Exception("输入了错误的值。");
}
}
----------------------------------------------------------------------------------
private int age; //声明一个私有字段来表示学生的年龄
public int Age //下面的getter和setter是VS2019在Ctrl R+E自动生成
{
get => age;
set
{
//在set后面加上if逻辑限制value
if (value >= 0 && value <= 100)
{
age = value;
}
else
{
throw new Exception("输入了错误的值。");
}
}
}
-----------------------------------------------------------------------------------
private int age; //声明一个私有字段来表示学生的年龄
public int Age
{
get
{
return age;
}
set
{
if (value >=0&&value<=100)
{
age = value;
}
else
{
throw new Exception("输入了错误的值。");
}
}
}
}
以上三种写法均可。那么对字段(field)用属性(property)进行包装或是直接自动生成编写出属性(property)进行包装之后,Student的实例就可以直接访问Age这个属性成员对年龄直接进行赋值,提高代码可读性。
在属性声明的语法中带 ”opt“ 下标的都是可选的或者可忽略的。
(属性修饰符):可一个或者多个,但必须是有意义的修饰符组合,如: private / public / public static;public 和 private 不可同时使用等。
(数据类型):byte、short、int、long、float、double、bool、char等。
:属性的名字,因为属性(properry)是类的成员,所以叫member-name。
需要注意的是:属性的声明当中完整的会getter和setter两个都有,但也会有只有getter和只有setter的声明。
① 只有getter的属性从外部来看只能读取而不能赋值,叫做只读属性。
② 只有setter的属性从外部来看只能赋值而不能读取,叫做只写属性(一般不用)。
字段(field)有实例字段和静态字段,那么属性(property)除了有上面的实例属性同样也有静态属性,tips:属性和支持字段的代码片段(propfull)
class Program
{
static void Main(string[] args)
{
try
{
//静态字段和静态属性均可不用创建实例,类装载了可直接访问
Student.Amount = 100;
Console.WriteLine(Student.Amount);
Console.ReadKey();
}
catch (Exception 异常)
{
//这里会输出异常信息,即“输入了错误的值”
Console.WriteLine(异常.Message);
Console.ReadKey();
}
}
}
class Student //声明一个“类”
{
//propfull,完整的声明,注意属性名称大小写
private static int amount;
//将属性改成静态属性时,上面的私有字段也需要改成静态字段
public static int Amount
{
get { return amount; }
set
{
if (value<=0&&value>=100)
{
amount = value;
}
else
{
throw new Exception("总和必须为正整数或零");
}
}
}
}
(静态数据成员是在类装载的时候就分配了内存空间,而类的成员变量是在生成对象时分配内存空间。所以只要类装载了,就可以用类名直接访问静态数据成员)
class Student //声明一个“类”
{
//propfull,注意属性名称大小写
private static int amount;
//将属性改成静态属性时,上面的私有字段也需要改成静态字段
public static int Amount
{
get { return amount; }
private set{ amount = value;} //私有setter
}
public void AnyMethod()
{
amount = 333;
}
}
这种属性不属于只读属性,删掉setter之后是无法编译的,他只是外部不允许访问setter,但是在当前类中能访问到。在特殊场景中有可能会用到。
大多数情况下,属性是字段的包装器(wrapper),两者都是用于表示对象或类型的状态的;但有的时候属性的值是动态计算而得出的,并非一个固定的值(参考属性的主动调用和被动调用)。
建议:永远使用属性(property)而不是字段(field)来暴露数据,即字段永远都是 ”private“ 和 ”protected“ 来对字段进行声明并且进行包装。
=========================================================================
索引器(indexer):索引器为类的一种成员,它使得对象可以像数组一样被索引,使得像数组那样对对象使用下标。它提供了通过索引方式方便地访问类的数据信息的方法。一般情况下拥有索引器这种成员的类型一般都是 ”集合类型” 。
namespace IndexerExample
{
class Program
{
static void Main(string[] args)
{
try
{
Student studen1 = new Student();
studen1["数学"] = null;
var 数学成绩 = studen1["数学"];
Console.WriteLine("数学成绩=" + 数学成绩);
Console.ReadKey();
}
catch (Exception ex)
{
Console.WriteLine(ex.Message); Console.ReadKey();
}
}
}
class Student //声明一个“类”
{
//声明一个字段,字典类型<键,值>
private Dictionary 成绩 = new Dictionary();
//声明索引器使用index代码片段
public int? this[string 科目]//索引返回值类型为int?,表示成绩,使用字符串类型string对“科目”进行索引
{
get
{ /* 在此处返回指定的索引 */
if (this.成绩.ContainsKey(科目)) //用“科目”这个key对应到“成绩”里去索引
{
return this.成绩[科目]; //如果这个“成绩”里有与这个key对应的则返回对应的“成绩”
}
else
{
return null;
}
}
set
{ /* 将指定的索引设置为此处的值 */
if (value.HasValue==false)
{
throw new Exception("输入值不能为空。");
}
//用“科目”这个科目key对应到“成绩”里去索引
if (this.成绩.ContainsKey(科目))
{
//如果这个“成绩”里有与这个科目key对应的则对“成绩”进行赋值
this.成绩[科目] = value.Value;
}
else
{
//如果这个“成绩”里没有与这个科目key对应的则对“成绩”增加一个key并且把value传进去
this.成绩.Add(科目, value.Value);
}
}
}
}
}
=========================================================================
1、什么是常量(const)
(1)表示常量值,即可以在编译时计算的值,是类中成员的一种
(2)常量隶属于类型而不是对象,即没有“实例常量”,非要创建自定义”实例常量“,那么只能通过创建只读实例字段来实现,但是创建自定义常量的时候最好确定使用范围。还要注意区分“成员常量”和“局部常量”
成员常量:类中的成员常量在类装在之后可直接访问
局部常量:声明局部常量的方法是通过创建只读字段来实现的,其实声明方法也类似于成员常量,只不过作用范围会有所变化
==================================================================================
1、为了提高程序的可读性和执行效率——使用常量
2、为了防止对象的值被修改——使用只读字段(只有声明的时候有机会对他进行初始化,等价于在构造函数中对他进行初始化,用构造函数代码表较多)
3、向外界暴露不允许修改的数据时——使用只读属性(静态或者非静态),功能上会与常量有一些重叠
注:常量在编译的时候,编译器会拿常量的值代替常量的标识符,但只读属性没有这个功能
4、当希望成为常量的值其类型不能被常量声明接受时(类类型/结构体类型)——使用静态只读字段
注:将类类型和结构体类型设为常量时不能用const进行修饰,而需要使用static readonly进行修饰;const只能对值类型进行修饰