欢迎来到Cefler的博客
博客主页:那个传说中的man的主页
个人专栏:题目解析
推荐文章:题目大解析2
对象
也叫实例,是类经过实例化后得到的内存中的实体
依照类,我们可以创建对象,这就是实例化
而使用new操作符
我们就可以创建类的实例
类的三大成员
1.属性:存储数据,组合起来表示类或对象当前的状态
2.方法:由C语言中的函数进化而来,表示类或对象能做什么
3.事件:类或对象通知其它类或对象的机制,为c#所持有
重在属性的类:Entity Framework
重在方法的类:Math,Console
重在事件的类:Timer
静态成员和实例成员
1.静态成员:在语义上表示它是类的成员
2.实例成员(非静态):语义上表示它是对象的成员
通俗来说,实例成员必须new出一个实例才存在,而静态成员无需new即可访问
c# 派生谱系
语法:
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 语句指定整型类型算术运算和转换的溢出检查上下文。 当发生整数算术溢出时,溢出检查上下文将定义发生的情况。 在已检查的上下文中,引发 System.OverflowException;如果在常数表达式中发生溢出,则会发生编译时错误。 在未检查的上下文中,会通过丢弃任何不适应目标类型的高序位来将操作结果截断。 例如,在加法示例中,它将从最大值包装到最小值。
详见:checked 和 unchecked 语句
什么是字段?
字段属于类,写在类里,语句写在函数里
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
:当被其修饰后,字段只能被修改一次(即初始化),且只能在类中或构造函数中初始化
若在外界中初始化会报错。
什么是属性?
一般向外暴露数据用属性是因为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; }
private int myVar;
public int MyProperty
{
get { return myVar; }
set { myVar = value; }
}
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的区别,静态属性反映当前类的状态,实例属性反映创建的实例对象的状态
什么是索引器?
索引器(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访问器。
索引器和属性的区别
参考文章: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);
}
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;
}
}
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.被通知的人根据拿到的事件信息(又称”事件数据“,“事件参数”,“通知”)对事件进行响应
事件模型的五个组成部分
注意:
小知识补充:图标扳手是属性,小方块是方法,闪电是事件。类的三大成员就是它们了
- 属性:存储数据
- 方法:做事情,实现逻辑运算
- 事件:在某种情况下进行通知
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");
}
}
}
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();
}
}
}
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";
}
}
}
如上便是本期的所有内容了,如果喜欢并觉得有帮助的话,希望可以博个点赞+收藏+关注❤️ ,学海无涯苦作舟,愿与君一起共勉成长