欢迎来到Cefler的博客😁
🕌博客主页:那个传说中的man的主页
🏠个人专栏:题目解析
🌎推荐文章:题目大解析2
目录
- 👉🏻类、对象、类成员简介
- 👉🏻语句详解
- foreach语句
- checked和unchecked语句
- 👉🏻字段、属性、索引器、常量
- ⭐️字段
- 只读字段(readonly)
- ⭐️ 属性
- 属性声明
- 实时动态计算
- 静态属性和实例属性
- 属性与字段的关系
- ⭐️索引器
- ⭐️常量
- 常量的声明
- 各种“只读”的应用场景
- 👉🏻参数
- ⭐️传值传参
- ⭐️输出参数
- ⭐️数组参数
- ⭐️ 具名参数
- ⭐️可选参数
- ⭐️扩展方法(this修饰):为目标数据类型追加方法
- 👉🏻委托
- 什么是委托?
- 委托声明
- 委托的一般使用
- 👉🏻事件
- 什么是事件?
- 事件的应用
👉🏻类、对象、类成员简介
对象
也叫实例,是类经过实例化后得到的内存中的实体
依照类,我们可以创建对象,这就是实例化
而使用new操作符
我们就可以创建类的实例
类的三大成员🚗
1.属性:存储数据,组合起来表示类或对象当前的状态
2.方法:由C语言中的函数进化而来,表示类或对象能做什么
3.事件:类或对象通知其它类或对象的机制,为c#所持有
重在属性的类:Entity Framework
重在方法的类:Math,Console
重在事件的类:Timer
静态成员和实例成员🚗
1.静态成员:在语义上表示它是类的成员
2.实例成员(非静态):语义上表示它是对象的成员
通俗来说,实例成员必须new出一个实例才存在,而静态成员无需new即可访问
c# 派生谱系
👉🏻语句详解
foreach语句
语法:
foreach(数据类型 变量名 in 数组名)
{
//语句块;
}
foreach 循环用于列举出集合中所有的元素,foreach 语句中的表达式由关键字 in 隔开的两个项组成。
in 右边的项是集合名,in 左边的项是变量名,用来存放该集合中的每个元素。
该循环的运行过程如下:每一次循环时,从集合中取出一个新的元素值。放到只读变量中去,如果括号中的整个表达式返回值为 true,foreach 块中的语句就能够执行。
一旦集合中的元素都已经被访问到,整个表达式的值为 false,控制流程就转入到 foreach 块后面的执行语句。
这里变量名的数据类型必须与数组的数据类型相兼容。
在 foreach 循环中,如果要输出数组中的元素,不需要使用数组中的下标,直接输出变量名即可。
foreach 语句仅能用于数组、字符串或集合类数据类型。
static void Main()
{
int[] arr = new int[5] { 1, 2, 3, 4, 5 };
foreach(int input in arr)
{
Console.WriteLine(input);
}
}
checked和unchecked语句
checked 和 unchecked 语句指定整型类型算术运算和转换的溢出检查上下文。 当发生整数算术溢出时,溢出检查上下文将定义发生的情况。 在已检查的上下文中,引发 System.OverflowException;如果在常数表达式中发生溢出,则会发生编译时错误。 在未检查的上下文中,会通过丢弃任何不适应目标类型的高序位来将操作结果截断。 例如,在加法示例中,它将从最大值包装到最小值。
详见:checked 和 unchecked 语句
👉🏻字段、属性、索引器、常量
⭐️字段
什么是字段?
- 字段(field)是一种表示与对象或类型(结构体与类)关联的变量
- 字段是类型的成员,旧称“成员变量”
- 与对象相关联的叫“实例字段”
- 与类型关联的字段称为“静态字段”,被static修饰
字段属于类,写在类里,语句写在函数里
class Fun
{
public int a;
public static int b;//静态成员
}
字段的声明
- 尽管字段声明带有封号,但它不是语句
- 字段的名字一定是名词
字段的初始值
- 无显示初始化时,字段获得其类型的默认值,所以字段“永远都不会被未初始化”
- 实例字段初始化时的时机——对象创建时
- 静态字段初始化的时机——类型被加载时
只读字段
- 实例只读字段
- 静态只读字段
静态字段/函数(被static修饰),实例对象无法访问到,要想访问到,只能通过类名.字段名/函数名才行
非静态字段/函数,实例对象可以访问到
📗在构造函数或非静态函数中引用当前类的非静态字段要用this
关键字,若是静态则就是类名.静态字段名
public int score;//非静态字段
public static int amount;//静态字段
public Student()//创建一个构造函数
{
Student.amount++;
this.score++;
}
这里总而言之:
类名能够访问的:静态字段/函数
实例对象能够访问的:非静态字段/函数
✏️用静态字段打印0~10
class Class1
{
static void Main()
{
for (int i = 0; i < 10; i++)
{
Student stu = new Student();//实例化对象,每次实例化都会调用Student中的构造函数
}
Student.PrintAmount();
}
}
class Student
{
public static int amount;
public Student()//创建一个构造函数
{
Student.amount++;
}
public static void PrintAmount()
{
Console.WriteLine(Student.amount);
}
}
实例构造和静态构造的区别
无非就是构造函数的public和static的区别,但主要区别在于,实例构造每次调用类该构造函数都会被调用一次,但静态构造只被调用一次后就不会再被调用了,所以静态构造适合初始化一些数据,并且希望后期不会修改。
只读字段(readonly)
修饰词readonly
:当被其修饰后,字段只能被修改一次(即初始化),且只能在类中或构造函数中初始化
若在外界中初始化会报错。
⭐️ 属性
什么是属性?
- 属性(propety):是一种用于访问对象或类型的特征的成员,特征反映了状态
- 属性是字段的自然扩展
- 从命名上看,field更偏向于实例对象在内存当中的布局,而propety偏向于反映现实世界对象的特征
- 对外:暴露数据,数据可以是存储在字段里的,也可以是动态计算出来的
- 对内:保护字段不被非法值"污染"
- 属性是由Get/Set方法对进化而来
- 属性也是个语法糖:只是隐藏在背后
一般向外暴露数据用属性是因为Get方法可以作为数据的输出,Set方法可以防止数据被恶意篡改
MyGet/Set👇🏻
class Class1
{
static void Main()
{
Student stu1 = new Student();
stu1.SetAge(95);
int s1 = stu1.GetAge();
stu1.SetAge(98);
int s2 = stu1.GetAge();
stu1.SetAge(200);//这里肯定会异常
int s3 = stu1.GetAge();
Console.WriteLine("{0},{1},{2}", s1, s2, s3);
}
class Student
{
private int age;//private即私有化,只能在当前类中使用
public int GetAge()
{
return this.age;
}
public void SetAge(int val)
{
if(val>0&&val<124)
{
this.age = val;
}
else
{
throw new Exception("GetAge fail,val err");//抛出异常
}
}
}
}
尝试用异常捕获优化上述代码
try
{
Student stu1 = new Student();
stu1.SetAge(95);
int s1 = stu1.GetAge();
stu1.SetAge(98);
int s2 = stu1.GetAge();
stu1.SetAge(200);//这里肯定会异常
int s3 = stu1.GetAge();
Console.WriteLine("{0},{1},{2}", s1, s2, s3);
}
catch(Exception e)
{
Console.WriteLine(e.Message);
}
使用private修饰时,命名的规范一般是首写字母为小写。
官方的Get/Set👇🏻
class Class1
{
static void Main()
{
try
{
Student stu = new Student();
stu.nald = 85;
int s1 = stu.nald;
Console.WriteLine(s1);
}
catch(Exception e)
{
Console.WriteLine(e.Message);
}
}
class Student
{
private int age;//private即私有化,只能在当前类中使用
public int nald//属性,属性名随便改
{
get
{
return this.age;
}
set
{
if(value>=0&&value<=124)//value为特定的上下文关键字
{
this.age = value;
}
else
{
throw new Exception("age err");
}
}
}
}
}
属性声明
权限 特性 数据类型 属性名
{get;set;
}
如果没有set,该属性就只能读不能改
属性快速声明:
1.vs软件内输 prop 连按2下tab键
public int MyProperty { get; set; }
- propfull 连按2下tab键
private int myVar;
public int MyProperty
{
get { return myVar; }
set { myVar = value; }
}
3.ctrl+r+e:自动封装字段
实时动态计算
class Class1
{
static void Main()
{
Student stu = new Student();
stu.Age = 24;
Console.WriteLine(stu.CanWork);
}
class Student
{
private int age;
public int Age
{
get { return age; }
set { age = value; }
}
private bool canwork;
public bool CanWork//实时动态计算
{
//get { return canwork; }
get
{
if(this.age>=0&&this.age<=124)
{
return true;
}
else
{
return false;
}
}
}
}
}
静态属性和实例属性
也就是public和static的区别,静态属性反映当前类的状态,实例属性反映创建的实例对象的状态
属性与字段的关系
- 一般来说,它们都用于表示实体(对象或类)的状态
- 属性大多数情况下是字段的包装器(wrapper)
- 建议:永远使用属性(而不是字段)来暴露数据。即字段永远private或protected
⭐️索引器
什么是索引器?
索引器(indexer)是这样一种成员,它使对象能够用与数组相同的方式(用下标)进行索引
-
使用索引器可以用类似于数组的方式为对象建立索引。
-
get 取值函数返回值。 set 取值函数分配值。
-
this 关键字用于定义索引器。
-
value 关键字用于定义由 set 访问器分配的值。
-
索引器不必根据整数值进行索引;由你决定如何定义特定的查找机制。
-
索引器可被重载。
-
索引器可以有多个形参,例如当访问二维数组时。
索引器的声明🧊
声明语法:
element-type this[int index]
{
// get 访问器
get
{
// 返回 index 指定的值
}
// set 访问器
set
{
// 设置 index 指定的值
}
}
索引器快速声明:输入indexer后快速按下两下tab键
Console.WriteLine()快速写:输入cw后快速按下两下tab键
浅试索引器
static void Main()
{
Student stu = new Student();
stu.name[0] = "张三";
stu.name[1] = "李四";
Console.WriteLine(stu.name[0]);
Console.WriteLine(stu.name[1]);
}
class Student
{
public string[] name = new string[10];
public string this[int index]
{
get
{
return name[index];
}
set
{
name[index] = value;
}
}
}
索引器还有以字符串为下标访问的,这些我们暂且不涉及
索引器和数组的区别🥥
-
索引器的索引值(Index)类型不限定为整数:
用来访问数组的索引值(Index)一定为整数,而索引器的索引值类型可以定义为其他类型。 -
索引器允许重载
一个类不限定为只能定义一个索引器,只要索引器的函数签名不同,就可以定义多个索引器,可以重载它的功能。 -
索引器不是一个变量
索引器没有直接定义数据存储的地方,而数组有。索引器具有Get和Set访问器。
索引器和属性的区别🥥
- 索引器以函数签名方式 this 来标识,而属性采用名称来标识,名称可以任意
- 索引器可以重载,而属性不能重载。
- 索引器不能用static 来进行声明,而属性可以。索引器永远属于实例成员,因此不能声明为static。
参考文章👉🏻:C# 索引器使用总结
⭐️常量
什么是常量?
常量(constant)是表示常量值(即可以在编译时计算的值)的类成员
常量隶属于类型而不是对象,即没有所谓的“实例对象”(“实例对象”的角色由只读实例字段来担当)
常见的常量有👇🏻
常量的声明
注意!:
对于自定义的类和结构体是不能用const修饰的,而我们常见的int,float等可以被const修饰
各种“只读”的应用场景
- 为了提高程序可读性和执行效率——常量
- 为了防止对象的值被改变——只读字段
- 向外暴露不允许修改的数据——只读属性(静态或非静态),功能与常量有一些重叠
- 当希望成为常量的值其类型不能被常量声明接受时(类/自定义结构体)——静态只读字段
👉🏻参数
⭐️传值传参
传值参数我们就不多赘述,在c语言中我们知道若是一般的传值操作只能改变形参而不能改变实参
在c#中只有用ref或out修饰后才能获得传址的效果。
而c#中引用类型的变量本身不用ref或out修饰就可以达到传址的效果,举例👇🏻
class Class1
{
static void Main()
{
Student stu = new Student() { Name ="Jack"};
fun(stu);
Console.WriteLine(stu.Name);
}
static void fun(Student stu)
{
stu.Name = "TOM";
}
class Student
{
public string[] name = new string[10];
public string Name { get; set; }
}
}
这里name作为数组名为传址操作,故而可以影响实参
从哈希代码上来看,二者都是同一对象👇🏻
那如果对引用类型ref修饰传参呢?
class Class1
{
static void Main()
{
Student stu = new Student() { Name ="Jack"};
Console.WriteLine("{0},{1}",stu.Name, stu.GetHashCode());
Console.WriteLine("_____________________________");
fun(ref stu);
Console.WriteLine("{0},{1}", stu.Name, stu.GetHashCode());
}
static void fun(ref Student stu)
{
stu = new Student() { Name = "Tom" };
Console.WriteLine("{0},{1}", stu.Name, stu.GetHashCode());
}
class Student
{
public string[] name = new string[10];
public string Name { get; set; }
}
}
引用类型不加ref和加ref的区别
- 不加ref:实参和形参所指向的内存地址不一样,但这两个不一样的地址中却存储着相同的地址,是我们这个实例在堆内实际存在的地址
- 加ref:此时实参和形参所指向的内存地址就是同一地址
语义上ref和out的区别
ref为了“改变”,"out"为了输出
⭐️输出参数
static void Main()
{
string input = Console.ReadLine();
double a;
bool res = double.TryParse(input, out a);//如果输出成功返回true
if(res)
{
Console.WriteLine(a);
}
else
Console.WriteLine("input err");
}
⭐️数组参数
我们常规的数组传参👇🏻
class Class1
{
static void Main()
{
int[] arr = new int[3] { 1, 2, 3 };
int res=Calculate(arr);
Console.WriteLine(res);
}
static int Calculate(int[] arr)
{
int sum = 0;
foreach(var x in arr)
{
sum += x;
}
return sum;
}
}
但是这样我们还得提前创建好一个数组,有没有更简便的呢?
params修饰🚗
params修饰后,无需创建数组,直接传数据,编译器会在中间自动生成一个数组
⭐️ 具名参数
class Class1
{
static void Main()
{
Fun(age: 18, name: "小李");
}
static void Fun(string name,int age)
{
Console.WriteLine("{0},{1}",name,age);
}
}
具名参数好处
- 增加了代码可读性
- 不受参数传参的顺序限制
⭐️可选参数
- 参数因为具有默认值而变得”可选“
- 不推荐使用可选参数
static void Fun(string name ="小胡",int age = 24)//已经有默认值了
{
Console.WriteLine("{0},{1}",name,age);
}
⭐️扩展方法(this修饰):为目标数据类型追加方法
- 方法必须是共有的,静态的,即被public、static修饰
- 必须是形参列表中的第一个,由this修饰
- 必须由一个静态类(一般类名为SomeTypeExtension)来统一收纳对SomeType类型的扩展方法(再强调,必须存在静态类中!才可以扩展方法)
class Class1
{
static void Main()
{
double x = 3.14159;
double y = x.Round(4);
Console.WriteLine(y);
}
}
static class DoubleExtention
{
public static double Round(this double input,int digit)
{
double res = Math.Round(input, digit);
return res;
}
}
👉🏻委托
什么是委托?
- 委托(delegate)是函数指针的升级版
- 一切皆地址
- 变量(数据)是以某个地址为起点的一段内存中所存储的值
- 函数(算法)是以某个地址为起点的一段内存中所存储的一组机器语言指令
- 直接调用:通过函数名来调用函数,CPU通过函数名直接获得函数所在地址并开始执行->返回
- 间接调用:通过函数指针来调用函数,CPU通过函数指针存储的值获得函数所在地址并开始执行->返回
- 委托的使用
- Action委托
- Func委托
Action委托🍡 (无参无返回值)
class Class1
{
static void Main()
{
Class1 obj = new Class1();
Action action = new Action(obj.Print);//现在我们将action这个委托执行了obj.Print这个方法,使得我们待会可以间接调用
obj.Print();//直接调用
action.Invoke();//间接调用
action();//更简便的间接调用,模仿函数指针
}
public void Print()
{
Console.WriteLine("Hello World");
}
}
Func委托🍡
Class1 obj = new Class1();
Func<int, int, int> func1 = new Func<int, int, int>(obj.Add);
Func<int, int, int> func2 = new Func<int, int, int>(obj.Sub);
int x = 100, y = 200, z = 0;
z = func1.Invoke(x, y);//func1(x,y)也行
Console.WriteLine(z);
z = func2(x, y);
Console.WriteLine(z);
}
public void Print()
{
Console.WriteLine("Hello World");
}
public int Add(int a,int b)
{
return a + b;
}
public int Sub(int a, int b)
{
return a - b;
}
委托是一种类,为了便于理解,可以认为其参数是方法(即函数)
可以认为委托这个类型,就是一个函数指针类型,专门接受函数的地址。
委托声明
委托是一种类(class),类是数据类型所以委托也是一种数据类型
自定义委托🍳
语法
访问权限 delegate 返回值类型 委托名(参数,参数,……);
delegate说明我要创建一个委托了。
class Class1
{
static void Main()
{
Class1 obj = new Class1();
Mydelegate mydelegate = new Mydelegate(obj.Add);
int res = mydelegate(10, 14);
Console.WriteLine(res);
}
public delegate int Mydelegate(int a, int b);//自定义委托
public int Add(int a, int b)
{
return a + b;
}
}
注意:委托与所封装的方法的类型必须一致对齐
委托的一般使用
🌈 把方法当作参数传给另一个方法
模板方法
借用指定的外部方法来产生结果
- 相当于”填空题“
- 常位于代码中部
- 委托有返回值
class Class1
{
static void Main()
{
ProductFactory productFactory = new ProductFactory();
WrapFactory wrapFactory = new WrapFactory();
Func<Product> func1 = new Func<Product>(productFactory.MakePizza);
Box box1 = wrapFactory.WrapProduct(func1);
Console.WriteLine(box1.Product.Name);
}
}
class Product //产品
{
public string Name { get; set; }
}
class Box //箱子
{
public Product Product { get; set; }//得到产品
}
class WrapFactory//产品包装厂
{
public Box WrapProduct(Func<Product>getProdunct)
{
Box box = new Box();
Product product = getProdunct.Invoke();
box.Product = product;
return box;
}
}
class ProductFactory//产品加工厂
{
public Product MakePizza()
{
Product product = new Product();
product.Name = "Pizza";
return product;
}
}
上述模板方法思路总结:
即创建了4个类
- Product:这里存储着产品的属性,也就是产品的名字
- Box :这里存储着产品
- ProductFactory:这里主要是加工产品
- WrapFactory :包装产品放入Box中
而后ProductFactory中创建了一个专门加工产品的函数
WrapFactory 中创建了一个专门包装产品的函数。
这中间的关系就是我在专门包装产品的函数通过委托调用了加工产品的函数去加工产品
回调(callback)方法
调用指定的外部方法
- 相当于”流水线“
- 常位于代码末尾
- 委托无返回值
委托这块实在hold不住了😖
未完待续……
👉🏻事件
什么是事件?
事件在类中声明且生成,且通过使用同一个类或其他类中的委托与事件处理程序关联。包含事件的类用于发布事件。这被称为 发布器
(publisher) 类。其他接受该事件的类被称为 订阅器
(subscriber) 类。事件使用 发布-订阅(publisher-subscriber) 模型。
-
发布器(publisher) 是一个包含事件和委托定义的对象。事件和委托之间的联系也定义在这个对象中。发布器(publisher)类的对象调用这个事件,并通知其他的对象。
-
订阅器(subscriber) 是一个接受事件并提供事件处理程序的对象。在发布器(publisher)类中的委托调用订阅器(subscriber)类中的方法(事件处理程序)。
发生->响应5个动作:
1.我有一个事件
2.一个人或一群人关心这个事件
3.我的这个事件发生了
4.关心这个事件的人被依次通知
5.被通知的人根据拿到的事件信息(又称”事件数据“,“事件参数”,“通知”)对事件进行响应
事件的应用
事件模型的五个组成部分
- 事件的拥有者
- 事件的成员
- 事件的响应者
- 事件处理器——本质上是一个回调方法
- 事件订阅——把事件处理器与事件相关联在一起,本质上是一种以委托类型为基础的“约定”
注意🗣:
- 事件处理器是方法成员
- 挂接事件处理器的时候,可以使用委托实例,也可以直接使用方法名,这是一个“语法糖”
- 事件处理器对事件的订阅不是随意的,匹配与否由声明事件时所使用的委托类型来检测
- 事件可以同步调用也可以异步调用
小知识补充🪷:图标扳手是属性,小方块是方法,闪电是事件。类的三大成员就是它们了
- 属性:存储数据
- 方法:做事情,实现逻辑运算
- 事件:在某种情况下进行通知
实例1🍺
class Class1
{
static void Main()
{
Timer timer = new Timer();//timer:事件的拥有者
timer.Interval = 1000;//1秒
Boy boy = new Boy();//boy:事件的响应者
timer.Elapsed += boy.Action;//timer.Elapsed:事件 +=:订阅符号 boy.Action:事件处理器
timer.Start();//打开钟表
Console.ReadLine();
}
class Boy//事件的关心者/响应者
{
internal void Action(object sender, ElapsedEventArgs e)
{
Console.WriteLine("Jump");
}
}
}
效果如下👇🏻:
实例2🍺
class Class1
{
static void Main()
{
Form form = new Form();//form:事件拥有者
Controller controller = new Controller(form);//controller:事件响应者
form.ShowDialog();
}
class Controller
{
private Form form;
public Controller(Form form)//构造函数
{
if(form!=null)
{
this.form = form;
this.form.Click += this.FormClicked;// form.Click:事件本身
}
}
private void FormClicked(object sender, EventArgs e)//事件处理器
{
this.form.Text = DateTime.Now.ToString();
}
}
}
实例3🍺
class Class1
{
static void Main()
{
MyForm form = new MyForm();//form:既是事件拥有者也是响应者
form.Click += form.Action;
form.ShowDialog();
}
class MyForm : Form
{
internal void Action(object sender, EventArgs e)
{
this.Text = DateTime.Now.ToString();
}
}
}
事件拥有者2通俗易懂就是谁调用了这个事件
实例4🍺
class Class1
{
static void Main()
{
MyForm form = new MyForm();//form:响应者
form.ShowDialog();
}
class MyForm : Form
{
private TextBox textBox;
private Button button;//button:事件拥有者
public MyForm()
{
this.textBox = new TextBox();
this.button = new Button();
this.Controls.Add(this.button);
this.Controls.Add(this.textBox);
this.button.Click += this.ButtonClicked;//+=:事件的订阅,button.Click:事件
this.button.Text = "Click";
this.button.Top = 100;
}
private void ButtonClicked(object sender, EventArgs e)//事件处理器
{
this.textBox.Text = "Hello World";
}
}
}
如上便是本期的所有内容了,如果喜欢并觉得有帮助的话,希望可以博个点赞+收藏+关注🌹🌹🌹❤️ 🧡 💛,学海无涯苦作舟,愿与君一起共勉成长