C#语言知识点
C#的传参方式
1、值传递(与java一样)
①操作值类型:将内存中的值复制一份“副本”传递给形参,所以形参改变不会影响实参。
②操作引用类型:将对象在内存中的地址复制一份“副本”传给形参,虽然是副本,但是指向一样,所以形参也指向了相同的地址,故而形参改变好像是改变了实际参数。
2、引用传递
形参与实参之前,都要加ref关键字。需要注意的是,实参在使用之前必须赋值,否则编译器会报错。此时不再复制实参在栈中的副本,而是将实参在栈中的地址传给形参,也就是实参与形参共用栈中的值。此时在方法中对形参所做的任何操作,都会影响实参。
①值类型:由于a与x,b与y指向了栈中相同的地址,在Add方法中对形参x与y的操作,会影响实参a与b的值。
copy/**
*@Author Arc
*2022.06.13
*/
class Program
{
static void Main(String[] args)
{
int a = 4;
int b = 5;
Add(ref a, ref b);
Console.WriteLine($"a={a},b={b}");
//输出a=5,b=6
}
public static void Add(ref int x, ref int y)
{
x += 1;
y += 1;
}
}
②引用类型:这时Add方法中形参animal指向的不再是实参a在栈中的副本的地址,而是直接与a指向了栈中相同的位置。
copyclass Program
{
static void Main(String[] args)
{
Animal a = new Animal() { Age = 1 };
Add(ref a);
Console.WriteLine($"a.Age={a.Age}");
//输出animal.age=11
}
public static void Add(ref Animal animal)
{
animal.Age += 1;
}
}
class Animal
{
public int Age { get; set; }
}
3、输出参数
用法:
(1)形参与实参之前,都要加out关键字。
(2)输出参数主要是用于函数需要有多个返回值时,作为返回值使用。
与引用变量相同的是:
(1)输出参数也不复制实参在栈中的副本,而是将实参在栈中的地址传给形参。在这点上,输出参数与引用参数相同。
(2)实参必须是可以赋值的变量,而不能是常亮。
与引用参数不同的是:
(1)实参在使用之前不必赋值。
(2)事实上,在使用之前对实参的赋值没有任何意义,因为在调用方法的最开始,便会将其值抹去,使其成为未赋值的变量。
(3)在方法返回之前,必须对out参数进行赋值。
由以上特点所决定的是,输出参数无法向方法中传递信息,其唯一作用便是,当一个参数需要返回多个值时,作为返回值返回。
copy/**
*@Author Arc
*2022.06.13
*/
class Program
{
static void Main(String[] args)
{
int a;
Add(1, 2, out a);
Console.WriteLine(a);
//输出3
}
public static bool Add(int x, int y, out int result)
{
result = x + y;
return true;
}
}
数据类型的转换
在C艹中与java一样有两种数据类型转换,即自动类型转换和强制类型转换。此外C#提供了Covert类,其中各种方法用于转换不同的数据类型。
Convert.ToInt32() 转换为整型(int)
Convert.ToChar() 转换为字符型(char)
Convert.ToString() 转换为字符串型(string)
Convert.ToDateTime() 转换为日期型(datetime)
Convert.ToDouble() 转换为双精度浮点型(double)
Conert.ToSingle() 转换为单精度浮点型(float)
C#中的多维数组交错数组的区别,即[,]与[][]的区别
多维数组
多维数组声明,在声明时必须指定数组的长度, type [lenght ,lenght ,lengh, ... ]
copyint [,] test1 = new int [3,3];
或者声明时赋值,系统判断长度:
copyint [,] test1 = {
{1,2,3},
{1,2,3},
{1,2,3},
};
交错数组
声明时,至少要指定第一维的长度,格式为 type [ ] [ ] [ ] ...
copyint [][] test1 = new int[5][]; int [][] test1 = new int[][];
//注意,此的声明方式是错的
或者声明时即赋值,由系统推断长度
copyint [][] test1 = {
new int[] {1,2,3,4},
new int[] {1,2,3},
new int[] {1,2}
};
多维数组与交错数组 二者的相同、区别
两者声明时,都必须指定长度,多维数组必须指定每一维的长度,而交错数组需要至少需要指定第一维的长度。
多维数组声明时,符号是这样的 [ , , , , ],逗号在 方括号 [ ] 中,每一维长度用逗号分隔。而交错数组每一维独立在 [ ]中
当你想指定数组长度时,只能在等号右侧指定,int [,] test1 = new int [3,3] 是正确的 ;int [6,4] test1 = new int [6,4] 是错误的;
下面以代码形式说明
copy/**
*@Author Arc
*2022.06.13
*/
int [,] test1 = {
{1,2,3,4},
{1,2,3},
{1,2}
};
//这样是错的,长度必须一致
int [,] test1 = new int [4,5] {
{1,2,3,4,5},
{1,2,3},
{1,2,3}
};
//这样也是错误的,长度必须一致,必须为每一个位置赋值
这一点C#与C语言有所区别,C语言可以不全赋值,没有赋值的位置系统默认为0。
下面的方法是正确的
copy//多维数组
int [,] numbers = new int[3,2] {{1,2},{3,4},{5,6}};
int [,] numbers = new int[,] {{1,2},{3,4},{5,6}};
//交错数组(数组的数组)
int [][] numbers = new int[2][] {new int{2,3,4},new int{5,6,7,8,9}};
int [][] numbers = new int[][] {new int{2,3,4},new int{5,6,7,8,9}};
int [][] numbers = {new int{2,3,4},new int{5,6,7,8,9}};
面向对象部分
重载overload
重载是函数的重载,两个同名函数,参数类型或个数不同,构成重载关系,需注意,返回值不作为函数重载的判断条件。多用于子类继承了父类的函数,需要不同的实现。
隐藏hide
当子类与父类有完全一样的方法时,称“子类隐藏了父类的同名方法”,如下:
copy/**
*@Author Arc
*2022.06.13
*/
class Parent{
public void HideF(){
Console.WriteLine("Praent.HideF");
}
}
class Child : Parent{
public void HideF(){
Console.WriteLine("Child.Hidef");
}
}
当重名时调用哪个方法取决于谁(子类还是父类对象)调用:
copyChild c = new Child();
c.Hidef();
//Child.Hidef
Parent p = new Parent();
p.Hidef();
//Praent.HideF
下面调用的是子类还是父类?
copyChild c = new Child();
Parent p;
p = c;
p.HideF();
//Parent.Hidef
//如果需要调用子类方法则需要转换类型
((Child)p).HideF();
//Child.Hidef
虽然能输出结果,但是不符合C#的语法规范,修改后如下:
copyclass Child : Parent{
public new void HideF(){
Console.WirteLine("Child.Hidef");
}
}
new告诉编译器,子类隐藏了父类的同名方法,提供自己的新版本,如果还想调用父类的方法,可以使用base关键字:
copybase.HideF();
//Parent.Hidef
重写override与虚方法
为了达成“如果父类变量引用了子类对象则调用子类的方法,父类变量引用父类对象调用父类方法”,可以在父类的同名方法前面加上关键字virtual,表示这是虚方法,子类可以重写这个:即在子类同名方法前面加上override,表示进行了重写。
copyclass Parent{
public virtual void OverrideF(){
Console.WirteLine("Parent.OverrideF");
}
}
class Child{
public override void OverrideF(){
Console.WirteLine("Child.OverrideF");
}
}
解决引用的问题:
copyChild c = new Child();
Parent p;
p = c;
p.OverrideF();
//Child.OverrideF
以上示例说明,当父类的方法是虚方法时,子类重写了同名方法,通过父类变量去调用,调用谁的取决于父类变量是哪个对象的真实引用。
多态
多态有两个必要条件,一是有继承,二是父类引用指向子类的对象。
copy/**
*@Author Arc
*2022.06.13
*/
//宠物类
class Animal{
//参数是一个父类类型,但能接收传过来的子类对象,也就是“父类引用指向子类的对象”
public void shower(Animal a){
}
public void eat(){
System.out.println("吃东西");
}
public void helpEat(Animal a){
//调用具体对象的eat方法,但是每个动物不同,就需要自身继承了eat方法后进行拓展,也就是重写
a.eat();
}
}
委托delegate
委托类型的变量可以接受一个函数的地址,也就是将方法以变量的形式传递,以方法的形式执行。可以看成一个容器,将某个具体的函数装入,就可以把这个容器当函数用。但是委托实际上不是函数的容器,是一个派生自Delegate的类。
课本示例:
copypublic class Math{
public int Add(int x , int y){
return x + y;
}
}
//定义一个委托类型
public delegate int MathDelegateAdd(int value1 , int value2);
MathDelegateAdd dgt;
Math obj = new Math();
dgt = obj.Add;
Console.WirteLine(dgt(1,2));
//输出3
上述例子不是所有函数都能委托给agt对象,只有有两个int类型参数的,且返回值也是int的才可以(废话),一个委托对应着一种函数的signature。
委托不仅可以代表一个函数,更可以代表一堆函数批量执行(委托的组合和分解):
copy//参数为string,无返回的委托类型
delegate void MyDelegate(string s);
//定义类
class MyClass{
public static void Hello(){
Console.WriteLine("你好,{0}",s);
}
public static void GoodBye(){
Console.WriteLine("再见,{0}",s);
}
}
class Program{
static void Main(string args[]){
MyDelegate a,b,c,d;
a = MyClass.Hello;
Console.WriteLine("调用委托变量 a:");
a("a");
b = MyClass.GoodBye;
Console.WriteLine("调用委托变量 b:");
b("b");
c= a + b;
Console.WriteLine("调用委托变量 c:");
c("c=a+b"); //顺序调用两个方法
d = c - a;
Console.WriteLine("调用委托变量 d:");\
d("d=a-c") //只调用GoodBye
}
}
事件
事件在类中声明且生成,且通过使用同一个类或其他类中的委托与事件处理程序关联。
主要特点:一对多关联,即一个事件源,多个响应者。.NET Framework的事件处理机制是基于多路委托。
组成一个事件的五个部分:事件的拥有者、事件拥有者的某个事件成员、事件订阅器、事件响应者以及事件响应者的成员。
简单来说事件实质就是:事件的拥有者(Event source)的某个事件成员(Event,成员)被调用导致订阅该事件的事件响应者(Event Subcriber)调用事件处理成员(Event Handler,成员)被触发(本质上就是一个回调函数被调用)。
事件的作用:事件与属性类似本质是一个包装器,目的是为了更好的保护类内部的委托类型成员,事件只能被+=或-=,在这一过程中实际上操作的是事件拥有者的委托类型成员,从而保护类内部的委托类型成员不被外部随意修改。
copy//定义一个委托
public delegate void MyMultiDelegate(int value);
//事件发布者
public class Publisher{
public MyMultiDelegate handers; //事件响应者清单
}
//事件响应类
public class Subscriber{
//事件处理函数
public void MyMethod(int i){
Console.WriteLine(i);
}
}
事件响应代码:
copystatic void Main(string args[]){
Publisher p = new Publisher();
//两个事件响应者
Subscriber s1 = new Subscriber();
Subscriber s2 = new Subscriber();
//直接调用Delegate类的静态方法组合成多个委托
p.handlers = System.Delegate.Combine(p.handlers,new MyMultiDelegate(s1.MyMethod)) as MyMultiDelegate;
p.handlers = System.Delegate.Combine(p.handlers,new MyMultiDelegate(s2.MyMethod)) as MyMultiDelegate;
//调用+=运算符组合委托
p.handlers += new MyMultiDelegate(s1.MyMethod));
p.handlers += new MyMultiDelegate(s2.MyMethod));
//最简单的写法
p.handlers += s1.MyMethod;
p.handlers += s2.MyMethod;
p.handlers(10);
}
执行最后一句,调用两个事件响应s1和s2的事件响应函数,输出两个整数。
为了限制事件的激发只能由事件源对象引发,引入event关键字
copypublic delegate void MyMultiDelegate(int value);
public class Publisher{ //事件发布者类
public event MyMultiDelegate handlers;
public void FireEvent(){ //激发事件
handlers(10);
}
}
public class Subscriber{ //事件响应者
public void MyMethod(int i){ //事件处理函数
Console.WirteLine(i);
}
}
模拟事件响应:
copystatic void Main(string args[]){
Publisher p = new Publisher();
//两个事件响应者
Subscriber s1 = new Subscriber();
Subscriber s2 = new Subscriber();
//声明为事件的委托无法直接调用Combine方法
//这两句不能用
//p.handlers = System.Delegate.Combine(p.handlers,new MyMultiDelegate(s1.MyMethod)) as MyMultiDelegate;
//p.handlers = System.Delegate.Combine(p.handlers,new MyMultiDelegate(s2.MyMethod)) as MyMultiDelegate;
//必须用+=来追加委托
p.handlers += new MyMultiDelegate(s1.MyMethod));
p.handlers += new MyMultiDelegate(s2.MyMethod));
//声明为事件的委托方法也不能直接调用,无法编译
//p.handlers += s1.MyMethod;
//p.handlers += s2.MyMethod;
只能通过类的公有方法间接引发事件
p.FireEvent();
Console.ReadKey();
}
//10
//10
结论:多路委托允许在事件源对象之外激发事件
Console类的几个方法
Console.readkey():监听键盘事件,可以理解为按任意键执行。
Console.read():读取键盘输入的第一个字符,返回ASCII值。回车退出
Console.readline():读取所有字符,返回字符串。回车退出
Console.Write():控制台输出,不换行。
Console.Writeline():控制台输出,换行。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步