C#知识补充(一)——ref和out、成员属性、万物之父和装箱拆箱、抽象类和抽象方法、接口

写在前面:

写本系列**(自用)** 的目的是回顾已经学过的知识、记录新学习的知识或是记录心得理解,方便自己以后快速复习,减少遗忘**。C#中某些内容在初学的时候没学明白,在这里巩固一下。所以知识点不会连贯,是零散的。**

一、ref和out

ref和out是函数参数的修饰符。

1、ref和out的使用

ref和out在使用上是相同的,需要在函数中传入ref或者out修饰的参数,只需要直接在函数参数前加上ref/out即可,像这样:

static void ChangeValueRef(ref int value)

同样的,在调用该函数时,传参也需要加上ref:

ChangeValueRef(ref a);

ref和out修饰的参数,相当于引用,在函数内部改变了该参数的值,在函数外部的实参也会被改变,如下所示:

cs 复制代码
class Program
{
    static void ChangeValueRef(ref int value)
    {
        value = 3;
    }

    static void ChangeValue(int value)
    {
        value = 3;
    }

    static void ChangeValueOut(out int value)
    {
        value = 5;
    }

    static void Main(string[] args)
    {
        int a = 1;

        ChangeValue(a);
        Console.WriteLine(a);

        ChangeValueRef(ref a);
        Console.WriteLine(a);

        ChangeValueOut(out a);
        Console.WriteLine(a);
    }
}

2、ref和out的区别

ref传入的变量必须初始化,out不用;out必须在内部赋值,ref不用。并且out无论在外部有没有初始化都视为没有初始化。

如下例所示,声明变量b但是不初始化b,如果将b传入ChangeValueRef()就会报错,但是可以传入函数ChangeValueOut()。

如果将ChangeValueOut()内部的value = 5注释了,也就是不在函数体内对value复制,那么ChangeValueOut()就会报错,ChangeValueRef()不会。

通俗来讲就是ref买票上车,out上车买票。

cs 复制代码
class Program
{
    static void ChangeValueRef(ref int value)
    {
        value = 3;
    }

    static void ChangeValueOut(out int value)
    {
        value = 5;
    }

    static void Main(string[] args)
    {
        int b;
        //ChangeValueRef(ref b);
        ChangeValueOut(out b);
        Console.WriteLine(b);
    }
}

二、成员属性

属性可以解决public、private、protected的局限性。属性可以让成员变量在外部只能获取,不能修改;或者只能修改,不能获取。

1、基本语法

属性的基本语法是:

访问修饰符 类型 属性名

{

get语句块

set语句块

}

其中,get必须有返回值,通过这个属性可以得到私有的成员变量。set语句块中有默认的value关键字,用于表示外部传入的值,value的类型和属性的类型一致。如下所示,定义了一个属性Name,并且在Main函数中通过p.Name获取到了私有的Name值以及修改这个值。

cs 复制代码
class Person 
{
    private string name;
    private int age;
    private int money;
    private bool sex;

    public string Name//属性
    {
        get
        {
            return name;
        }
        set
        {
            name = value;
        }
    }
}

internal class Program
{
    static void Main(string[] args)
    {
        Person p = new Person();
        //使用属性
        p.Name = "Max";
        Console.WriteLine(p.Name);
    }
}

2、用处

可以利用属性实现只能得不能改的效果,这种使用方式在单例模式中十分常见。get和set不能同时使用访问修饰符。

get和set可以只有一个。只有一个时,没必要加访问修饰符,一般是只有一个get, 即只让外部得到但不能改,基本不会出现只有一个set的情况。

这两个例子都如下所示:

cs 复制代码
class Person 
{
    private string name;
    private int age;
    private int money;
    private bool sex;

    public int Money
    {
        get
        {
            return money - 5;
        }
        private set
        {
            money = value + 5;
        }
    }

    public bool Sex
    {
        get
        {
            return sex;
        }
    }
}

最后,还有自动属性,自动属性外部不能改,如果类中有一个特征是只希望外部能得到不能改(能改不能得),又没什么特殊处理,那么可以直接使用自动属性。

cs 复制代码
class Person 
{
    private string name;
    private int age;
    private int money;
    private bool sex;

    public float Height
    {
        get;
        private set;
    }
}

三、万物之父和装箱拆箱

1、万物之父

万物之父是object,是所有类的基类,是一种引用类型。可以用里氏替换原则, 用object容器装所有的对象。object可以用来表示不确定的类型,作为函数参数类型。

(1)里氏替换原则

父类Father可以直接装载子类Son,并将Father "as" 为Son从而调用Son内的方法。如下所示

cs 复制代码
class Father
{

}

class Son : Father
{
    public void Speak()
    {

    }
}

internal class Program2
{
    
    static void Main(string[] args)
    {
        Father f = new Son();

        if(f is Son)
        {
            (f as Son).Speak();
        }

    }
}

(2)object

object就是所有类型的父对象,可以利用里氏替换原则用object容器装所有的对象。如下,可以利用object装载引用类型Son;装载值类型如整数、浮点数;装载string类型;装载数组。

cs 复制代码
internal class Program2
{
    
    static void Main(string[] args)
    {
        //引用类型
        object o = new Son();
        //o = f;
        if(o is Son)
        {
            (o as Son).Speak();
        }

        //值类型
        object o2 = 1f;
        float fl = (float)o2;

        //string
        object str = "12334";
        string str2 = str.ToString();
        //string str2 = str as string;

        //数组
        object arr = new int[10];
        int[] ar = arr as int[];

        //装箱
        object v = 3;
        //拆箱
        int intValue = (int)v;
    }
}

使用object声明的数组可以装载任意的类型,如下,函数的参数为object数组,那么可以传入任意类型:

cs 复制代码
internal class Program2
{
    
    static void Main(string[] args)
    {
        TestFun(1, 2, 3, 4f, "1234", new Son());
    }

    static void TestFun(params object[] array)
    {

    }
}

2、装箱拆箱

发生条件:用object存值类型(装箱),再把object转为值类型(拆箱)。使用object的好处是,在不确定类型时可以方便参数的存储和传递。坏处是,装箱拆箱存在内存迁移,增加性能消耗。

(1)装箱

装箱:把值类型用引用类型存储,栈内存会迁移到堆内存中。

(2)拆箱

拆箱:把引用类型存储的值类型取出来,堆内存会迁移到栈内存中。

cs 复制代码
    static void Main(string[] args)
    {
        //装箱
        object v = 3;
        //拆箱
        int intValue = (int)v;
    }

四、抽象类和抽象方法

1、抽象类

被抽象关键字abstract修饰的类,特点是:不能被实例化、可以包含抽象方法、继承抽象类必须重写其抽象方法。

如下所示,我们定义了一个抽象类Thing,类Water继承了抽象类Thing。Thing是不能被实例化,也就是不能new的,但是Water可以。

cs 复制代码
abstract class Thing
{
    public string name;
}

class Water :Thing
{

}

internal class Program3
{
    static void Main(string[] args)
    {
        //Thing t = new Thing(); 抽象类不能被实例化

        Thing t = new Water(); //可以遵循里氏替换原则
    }
}

2、抽象函数

抽象函数又叫纯虚方法,用abstract关键字修饰。只能在抽象类里声明,没有方法体。不能是私有的,因为抽象函数继承后必须实现,用override来重写。

如下所示,有抽象类Fruits。在Fruits中有抽象函数public abstract void Bad();,由于Bad()子类必须重写,所以不能是私有的,只能是公共or保护的。Fruits中public virtual void Test()是虚函数,可以自行选择是否重写。

cs 复制代码
abstract class Fruits
{
    public string name;

    //子类需要重写,不能是私有的
    public abstract void Bad();

    public virtual void Test()
    {
        //可以选择是否写逻辑
    }
}

子类Apple继承了父类Fruits,那么Apple必须重写抽象方法,否则就会报错。使用public override void Bad()。但是对于虚方法Test(),可以自行选择是否实现。

cs 复制代码
class Apple : Fruits
{
    //实现抽象方法
    public override void Bad()
    {
        
    }

    //可以选择是否实现
    public override void Test()
    {
        base.Test();
    }
}

如果还有类继承了Apple,抽象方法和虚方法都可以选择继续重写下去:

cs 复制代码
class SuperApple : Apple
{
    //可以选择继续重写下去
    public override void Bad()
    {
        base.Bad();
    }

    public override void Test()
    {
        base.Test();
    }
}

五、接口

1、概念

接口是行为的抽象规范,它也是一种自定义类型。关键字是interface。

接口声明的规范是:不能包含成员变量,只包含方法、属性、索引器、事件;成员不能被实现,成员可以不写访问修饰符,不能是私有的;接口不能继承类,但是可以继承另一个接口。

接口的使用规范是:类可以继承多个接口,类继承接口后,必须实现接口中所有成员。

2、实现

interface IFly { } 即可定义一个接口,接口的命名规范是帕斯卡命名法并在前面加个I。成员方法、属性等都不能实现,如下所示:

cs 复制代码
    interface IFly 
    {
        public void Fly();//protected也可以

        string Name
        {
            get; //也不能有语句块,属性也不能被实现
            set;
        }
    }

现有一类Person同时继承了类型Animal和接口IFly,它必须实现接口中的所有内容。而接口继承接口时,不需要实现,待类继承接口后,类自己去实现所有内容。

cs 复制代码
    class Animal
    {

    }

    class Person : Animal, IFly
    {
        public string Name 
        {
            get
            {
                return "1";
            }
            set
            {
            }
        }        

        public virtual void Fly()
        {
            
        }
    }

    static void Main(string[] args)
    {
        IFly f = new Person();//能父类装子类,不能new自己
    }

3、显式实现

如果一个类继承了多个接口,并且接口中有同名函数时,还是按之前的实现的话,就会让两个接口只有一种行为表现。

因此需要采用显式实现:void IAtk.Atk()和void ISuperAtk.Atk(),使用显式实现时,继承接口的类也可以继续有同名函数public void Atk(),如下例所示:

cs 复制代码
    interface IAtk
    {
        void Atk();
    }

    interface ISuperAtk
    {
        void Atk();
    }

    class Player : IAtk, ISuperAtk
    {
        /*
        public void Atk()
        {
            
        }
        */

        //显式实现
        void IAtk.Atk()
        {
            
        }

        void ISuperAtk.Atk()
        {

        }

        public void Atk()
        {

        }
    }
相关推荐
Scott9999HH8 小时前
【IIoT流量实战】蒸汽管道阀门全关却仍有流量?用 Python 实现涡街信号 FFT 频谱分析与温压全补偿积算网关,深度拆解靠谱的涡街流量计厂家硬核技术标准
开发语言·python
EIP低代码平台8 小时前
EIP 低代码平台 - 角色维护
低代码·c#·权限·工作流·netcore
码智社9 小时前
AES加密原理详解及Java实现加解密实战
java·开发语言
AI云海9 小时前
python 列表、元组、集合和字典
开发语言·python
克里斯蒂亚诺更新9 小时前
ruoyi角色权限的对应关系
笔记
萧瑟余晖10 小时前
JDK 26 新特性详解
java·开发语言
马优晨10 小时前
Freemarker 完整讲解(后端 Java 模板引擎)
java·开发语言·freemarker·freemarker 完整讲解·freemarker模板引擎
a11177612 小时前
2FA 验证码生成器(github登录验证 app)
笔记·学习
我的xiaodoujiao12 小时前
API 接口自动化测试详细图文教程学习系列32--Allure测试报告2
python·学习·测试工具·pytest
人邮异步社区12 小时前
怎么把C语言学到精通?
c语言·开发语言