C#知识点整理(ASP.NET4.5 工信出版社)


C#的传参方式

1、值传递(与java一样)

①操作值类型:将内存中的值复制一份“副本”传递给形参,所以形参改变不会影响实参。

②操作引用类型:将对象在内存中的地址复制一份“副本”传给形参,虽然是副本,但是指向一样,所以形参也指向了相同的地址,故而形参改变好像是改变了实际参数。

2、引用传递

形参与实参之前,都要加ref关键字。需要注意的是,实参在使用之前必须赋值,否则编译器会报错。此时不再复制实参在栈中的副本,而是将实参在栈中的地址传给形参,也就是实参与形参共用栈中的值。此时在方法中对形参所做的任何操作,都会影响实参。

①值类型:由于a与x,b与y指向了栈中相同的地址,在Add方法中对形参x与y的操作,会影响实参a与b的值。

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指向了栈中相同的位置。

 

class 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参数进行赋值。 由以上特点所决定的是,输出参数无法向方法中传递信息,其唯一作用便是,当一个参数需要返回多个值时,作为返回值返回。

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, … ]

int [,] test1 = new int [3,3];

或者声明时赋值,系统判断长度:

int [,] test1 = {
            {1,2,3},
            {1,2,3},
            {1,2,3},
        };

交错数组

声明时,至少要指定第一维的长度,格式为 type [ ] [ ] [ ] …

int [][] test1 = new int[5][]; int [][] test1 = new int[][];    
//注意,此的声明方式是错的

或者声明时即赋值,由系统推断长度

int [][] 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] 是错误的;

下面以代码形式说明

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。

下面的方法是正确的

//多维数组
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

当子类与父类有完全一样的方法时,称“子类隐藏了父类的同名方法”,如下:

class Parent{
    public void HideF(){
        Console.WriteLine("Praent.HideF");
    }
}

class Child : Parent{
    public void HideF(){
        Console.WriteLine("Child.Hidef");
    }
}

当重名时调用哪个方法取决于谁(子类还是父类对象)调用:

Child c = new Child();
c.Hidef();
//Child.Hidef
Parent p = new Parent();
p.Hidef();
//Praent.HideF

 下面调用的是子类还是父类?

Child c = new Child();
Parent p;
p = c;
p.HideF();
//Parent.Hidef

//如果需要调用子类方法则需要转换类型
((Child)p).HideF();
//Child.Hidef

 虽然能输出结果,但是不符合C#的语法规范,修改后如下:

class Child : Parent{
    public new void HideF(){
        Console.WirteLine("Child.Hidef");
    }
}

 new告诉编译器,子类隐藏了父类的同名方法,提供自己的新版本,如果还想调用父类的方法,可以使用base关键字:

base.HideF();
//Parent.Hidef

重写override与虚方法

为了达成“如果父类变量引用了子类对象则调用子类的方法,父类变量引用父类对象调用父类方法”,可以在父类的同名方法前面加上关键字virtual,表示这是虚方法,子类可以重写这个:即在子类同名方法前面加上override,表示进行了重写。

class Parent{
    public virtual void OverrideF(){
        Console.WirteLine("Parent.OverrideF");
    }
}
class Child{
    public override void OverrideF(){
        Console.WirteLine("Child.OverrideF");
    }
}

解决引用的问题:

Child c = new Child();
Parent p;
p = c;
p.OverrideF();
//Child.OverrideF

以上示例说明,当父类的方法是虚方法时,子类重写了同名方法,通过父类变量去调用,调用谁的取决于父类变量是哪个对象的真实引用。

多态

多态有两个必要条件,一是有继承,二是父类引用指向子类的对象。

class Animal{
    //参数是一个父类类型,但能接收传过来的子类对象,也就是“父类引用指向子类的对象”
    public void shower(Animal a){
    
    }
    
    public void eat(){
        System.out.println("吃东西");
    }
    
    public void helpEat(Animal a){
        //调用具体对象的eat方法,但是每个动物不同,就需要自身继承了eat方法后进行拓展,也就是重写
        a.eat();
    }
}

委托delegate

委托类型的变量可以接受一个函数的地址,也就是将方法以变量的形式传递,以方法的形式执行。可以看成一个容器,将某个具体的函数装入,就可以把这个容器当函数用。但是委托实际上不是函数的容器,是一个派生自Delegate的类。

课本示例:

public 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。

委托不仅可以代表一个函数,更可以代表一堆函数批量执行(委托的组合和分解):

//参数为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,成员)被触发(本质上就是一个回调函数被调用)。

事件的作用:事件与属性类似本质是一个包装器,目的是为了更好的保护类内部的委托类型成员,事件只能被+=或-=,在这一过程中实际上操作的是事件拥有者的委托类型成员,从而保护类内部的委托类型成员不被外部随意修改。

//定义一个委托
public delegate void MyMultiDelegate(int value);

//事件发布者
public class Publisher{
    public MyMultiDelegate handers;        //事件响应者清单
}
//事件响应类
public class Subscriber{
    //事件处理函数
    public void MyMethod(int i){
        Console.WriteLine(i);
    }
}

事件响应代码:

static 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关键字

public 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);
    }
}

模拟事件响应:

static 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():控制台输出,换行。

原创文章,作者:,如若转载,请注明出处:https://blog.ytso.com/273285.html

(0)
上一篇 2022年7月10日
下一篇 2022年7月10日

相关推荐

发表回复

登录后才能评论