C# visual studio 2022 学习2

类成员:

  1.字段成员 字段只是类中声明的一个变量,用来在对象中存储信息。

(1).静态字段 使用static关键字修饰的就是静态字段,静态字段属于类而不属于某个类实例,对它的访问使用“类名.静态字段名” ,不能使用“对象名.静态字段名”的格式。 静态字段通常用来保存当前类的实例个数

例3-2:编写一个类,使用静态字段计算类的实例数量。
public class theclass
{ public theclass()
  {
      count++;  //计数器增1
  }
   public void showcount()
  {
     Console.WriteLine(“实例数为:” + count );
   }
   private static int count=0; //显式赋初值
}
public class Test
{
    public static void Main()
   {
      theclass class1=new theclass();
      class1.showcount();
      theclass class2 =new theclass();
      class2.showcount();
    }
}

(2).只读字段    当字段声明中包括readonly修饰符时,该字段成为只读字段。只能在声明只读字段的同时赋初值。其他任何时候都不允许为只读字段赋值。 


public class Class1{
public readonly string str1=@”I am str1”;}
执行下面代码:
Class1 c1=new Class1();
Console.WriteLine(c1.str1);
//c1.str1=“Change readonlyfiled”;错误
如果在类定义中显式添加一个构造函数,可以在构造函数中改变只读字段的值。
Public class Class1
{Public readonly string str1=@”I am str1”;
  public Class1(){ str1=“initialized by constructor!”;}
}

2.方法成员 方法成员的本质就是在类中声明的函数,描述类能够“做什么”。

参数传递

C#编程语言支持的参数传递方式包括:

 1.值传递:方法中的变量是传入变量的一个拷贝,方法中对形参做的修改,不会影响方法外面的实参。    

(1) 对于值类型数据,值传递就是传递了变量的值。    

(2) 对于引用类型数据,值传递传递的是引用的值,即方法中的形参和方法外的实参将指向同一对象。因此,通过形参也能修改对象的实际内容。  

  • 总结1.(1)(2)就是:

简单类型(int ,double,char),enum类型,struct类型,string类型——>都是值类型

class——>引用类型 

注意:string比较特殊,属于值类型。这里把s2看做实参,s看做形参。

string s = "abc";
            string s2 = s;
            s = "jc";
            Console.WriteLine(s);
            Console.WriteLine(s2);
  • 代码例子:

test1类中有两个同名不同参的方法,这叫重载;

因为是test2是类,所以是引用类型的值传递,改变实参;

这里我一开始每个writeline后面都用了readline所以只出一个结果,只有我不断输入值,才会更新输出,事实上只用在静态Main里面(C#是用Main哦)最后一句放readline即可。

using System;

namespace cs
{
    public class test1
    {
        public void valdiliver(int x)
        {
            x += 5;
            Console.WriteLine(x);
           // Console.ReadLine();
        }
        public void valdiliver(test2 test)
        {
            test.str += "changed!";
            Console.WriteLine(test.str);
            //Console.ReadLine();
        }
    }
    public class test2
    {
        public string str = "this string";
    }
    public class Solution
    {
        static void Main(string[] args)//这是C#的Main方法
        {
            test1 a = new test1();
            test2 b = new test2();
            int c = 1;
            Console.WriteLine(c);
           // Console.ReadLine();
            a.valdiliver(c);
            Console.WriteLine(c);
            //Console.ReadLine();

            Console.WriteLine(b.str);
           // Console.ReadLine();
            a.valdiliver(b);
            Console.WriteLine(b.str);
            Console.ReadLine();
        }

    }
}

2.地址传递:方法中的变量是传入变量的一个引用,方法中对形参做的修改,也会影响方法外面的实参。    

(1) 引用参数ref:由调用方法初始化参数值。

很多情况下,我们要使用参数的引用传递。引用传递是传递变量的地址,使得形参和实参指向同一内存空间,方法中对于形参的修改,实际上就是对于实参的修改。 由调用方法初始化参数值。实参、形参中ref不能省 。   

(2)输出参数 out:被调用方法初始化参数值,可以不用初始化就作为参数传递给方法。

中级特性

面向对象的程序设计的三个核心概念:封装、继承、多态

1.封装

2.继承:

(1)有这四种方式:父调父;子调父:子类对象可以调父类所有方法;父调子;子调子

看一些例子:

  • 子调父的方法

如果儿子有跟父亲同样的方法名,要调父类的那个方法,用base.方法名;如果子类没有,就直接用方法名。

class Car
{
    public Car()
    { }
    protected void f() { Console.WriteLine("aaa"); }
}
class TrashCar : Car
{
    public TrashCar() { }
    void f() { Console.WriteLine("bbb"); }
    public void f1() { base.f(); f(); }//这里前面调父,后面调子
}
class MyApp
{
    static void Main()
    {
        TrashCar myCar = new TrashCar();
        myCar.f1();
    }
}

using System;

namespace cs
{
    public class test1
    {
        public void f()
        {
            Console.WriteLine("test1.....");
        }
    }
    public class test2:test1
    {
        public void f()
        {
            Console.WriteLine("test2.....");
        }
    }
    public class Solution
    {
        static void Main(string[] args)//这是C#的Main方法
        {
            test1 a = new test1();
            a.f();

            

            Console.ReadLine();
        }

    }
}

object声明对象:由于C#中System.Object类是所有类的祖先,所以可以用Object类型的引用指向所有类型的对象。

Object a = new fu();
Object b=new zi();//或者a=new zi;
a.f();
b.f();//报错

3.多态:一个函数可以从父类和子类的入口进去、多态是最大范围的对同名函数的控制

(1)声明用父类,给实例new时用子类,实际去的是父类的入口;

这个叫做继承时的多态

public class fu
{
    public void f() { Console.WriteLine("fu"); }
}
public class zi:fu
{
    public void f() { Console.WriteLine("zi"); }
}
static void Main(string[] args)
{
    fu []a = new fu[3];
    a[0]= new fu();
    a[1]=new zi();
    a[0].f();
    a[1].f();
    Console.ReadLine();
}
//输出
//fu
//fu

(2)(静态)编译时的多态:是通过重载完成的,重载(函数名相同,参数不同)。

这个就是类里写了两个同名同返回的函数,只是参数不同,所以调用函数时就根据参数不同调,而动态多态就是父类和子类各有同名同返回同参,完全一样。

(3)(动态)运行时的多态:是通过虚函数完成的。父类方法加virtual,子类方法加override表示将该方法覆盖;父类子类都加virtual就没用了

public class fu
{
    public virtual void f() { Console.WriteLine("fu"); }
}
public class zi:fu
{
    public override void f() { Console.WriteLine("zi"); }
}
static void Main(string[] args)
{
    fu []a = new fu[3];
    a[0]= new fu();
    a[1]=new zi();
    a[0].f();
    a[1].f();
    Console.ReadLine();
}
//输出
//fu
//zi
fu a = new fu();
fu b=new zi();
a.f();
b.f();
//fu zi本来用父类声明和子类给实例,从父类入口入,但是这里还是被覆盖了

所以可见覆盖和重载的区别:

相同点:       都涉及两个同名的方法。 不同点: 1.类层次 (1).重载涉及的是同一个类的两个同名方法; (2).覆盖涉及的是子类的一个方法和父类的一个方法,这两个方法同名。 2.参数和返回值      (1).重载的两个方法具有不同的参数,可以有不同返回值类型;      (2).覆盖的两个方法具有相同的参数,返回值类型必需相同。

就近法则:有父子孙三级,各级都有同名同返回同参的方法;

在子类方法中访问成员(成员变量、成员方法)满足这个顺序

  • 先子类局部范围找
  • 然后子类成员范围找
  • 然后父类成员范围找,如果父类范围还没有找到则报错。
 public class fu
 {
     public virtual void f() { Console.WriteLine("fu"); }
 }
 public class zi:fu
 {
     public override void f() { Console.WriteLine("zi"); }
 }
 public class sun : zi { 
     public override void f() { Console.WriteLine("sun"); }
 }

 static void Main(string[] args)
 {
     sun b= new sun();
     b.f();//显然输出sun
     Console.ReadLine();
 }
public class fu
{
    public virtual void f() { Console.WriteLine("fu"); }
}
public class zi:fu
{
    public override void f() { Console.WriteLine("zi"); }
}
public class sun : zi { 
    //public override void f() { Console.WriteLine("sun"); }
}

static void Main(string[] args)
{
    sun b= new sun();
    b.f();//输出zi
    Console.ReadLine();
}

new:

若覆盖时没有使用virtual和override关键字,则称子类的方法隐藏了父类的方法。此时编译器报警告。若要消除掉警告,可以使用new修饰符。

不加New的话,会有警告

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace cs1
{
    internal class Program
    {
        public class fu
        {
            public void f() { Console.WriteLine("fu"); }
        }
        public class zi:fu
        {
            public new void f() { Console.WriteLine("zi"); }
        }
        public class sun : zi { 
            public new void f() { Console.WriteLine("sun"); }
        }

        static void Main(string[] args)
        {
            fu a = new fu();
            a.f();//输出fu
            sun b= new sun();
            b.f();//输出sun
            Console.ReadLine();
        }
    }
}

但要永远遵循“父声明,子给实例,从父类入口进,即调父类方法”

sealed:在虚方法上加这个修饰符;比如子某个方法sealed了,那么孙就不能再对这个方法重写(即改为虚方法)。

例:记得加virtual和override

public class fu
{
    public virtual void f() { Console.WriteLine("fu"); }
}
public class zi:fu
{
    public sealed override void f() { Console.WriteLine("zi"); }
}
public class sun : zi { 
    public override void f() { Console.WriteLine("sun"); }
}

抽象类与接口

抽象类就是把要变成抽象类的类class前面加一个abstract

接口定义:

接口成员访问方式是public,但是不能写出这个修饰符,即不允许public void f();

也不能声明出构造方法,即不能出现实例构造函数void f(){};

正确的:

 interface IA//A表示这个接口的名字,I表示这是一个接口
  {
      void f();
  }

接口上的二义性:例子

using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace cs1
{
    interface IA//A表示这个接口的名字,I表示这是一个接口
    {
        void f();
    }
    interface IB
    {
        void f();
    }
    class A: IA,IB { 
        public void f() { Console.WriteLine("接口IA"); } //类中重写接口中的方法必须用public,不然做不了
        void IB.f() { Console.WriteLine("接口IB"); }//显式调用IB中的方法

    }
    class Program
    {
         static void Main(string[] args) {
            IA a=new A();//不需要as了
            IB b = new A(); //声明接口是调用哪个的关键
            a.f();//输出接口IA
            b.f();//输出接口IB
            Console.ReadLine();
        }
    }
}

总结:声明对象时的接口很重要,“继承”接口的类A中的方法更重要

但声明时更重要,实验过了,如果声明是用IA,A类中是IB.f(),那就调public f()

如果是IA.f(),先调IA.f()。如果是IB.f(),那么只有用IB接口声明对象才能调到IB接口。

new哪个类也可以改变接口怎么写

using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace cs1
{
    interface IA//A表示这个接口的名字,I表示这是一个接口
    {
        void f();
    }
    interface IB
    {
        void f();
    }
    class A: IA,IB { 
        public void f() { Console.WriteLine("接口IA"); } //类中重写接口中的方法必须用public,不然做不了
        void IB.f() { Console.WriteLine("接口IB"); }//显式调用IB中的方法

    }
    class B : IA,IB {
        public void f() { Console.WriteLine("类B走的接口IA"); } //类中重写接口中的方法必须用public,不然做不了
        void IB.f() { Console.WriteLine("类B走的接口IB"); }
    }
    class Program
    {
         static void Main(string[] args) {
            IA a=new A();
            IA b = new B(); 
            a.f();//输出接口IA
            b.f();//输出类B走的接口IA
            Console.ReadLine();
        }
    }
}

实际问题:同学A还是同学B收作业问题

接口中一个方法就是收作业方法,两个同学作为类继承这个接口,是A还是B收作业再看

需要做一个走这个接口的通道(也可以像上面的例子中直接用a.f()还是b.f())

为什么要这个接口?因为工作中如果多写一个类多写一个方法很冗余,或者有的类不需要继承这个接口,然后你再写一个接口把这个工程都改了,能行吗

有一个Person类为什么要?就是Person类可以是A同学B同学类的父亲类,这样的话我就不用再改变new A() 还是new B() 时的语句,直接再Person类中搞(太难了不写了)

using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace cs1
{
    interface collecthomework
    {
        void f();//收作业的方法
    }
    class A: collecthomework{ 
        public void f() { Console.WriteLine("接口收作业,A收"); } //类中重写接口中的方法必须用public,不然做不了
    
    }
    class B : collecthomework {
        public void f() { Console.WriteLine("接口收作业,B收"); } //类中重写接口中的方法必须用public,不然做不了
    }
    class Program
    {
        static void docollecthomework(collecthomework work)//执行这个通道需要搞一个做这个接口的通道
        {
            work.f();
        }
         static void Main(string[] args) {
            collecthomework a= new A();
            docollecthomework (a);
            Console.ReadLine();
        }
    }
}

可以感觉到接口类似取代了基础的父类,而且比父子之间调的更便利,只要同名畅通无阻调(老师说:要学会丢弃旧方法,用新方法,比如子类的override重写和接口,都是很好的方法)

接口中的方法少些带参的

上一个例子的docollecthomework函数如果函数参数是接口数组,就很牛,因为类似子类的方法全部都在这个接口数组之下,答案都在这个数组里啊

计算不同图形的面积的例子:

public class MyApp
{
    public static double SumAreas(ICalAreaAndVolumn[] array)//这个
    {
        double tot = 0.0;
        for (int i = 0; i < array.Length; i++)
        { tot += array[i].GetArea(); }
        return tot;
    }
    public static void Main()
    {
        Sphere sp = new Sphere(2);
        Cylinder cd = new Cylinder(2, 10);
        Cone cn = new Cone(2, 20);
        ICalAreaAndVolumn[] array = { sp, cd, cn };
        Console.WriteLine("total arears = {0}", SumAreas(array));
    }
}

  • 13
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值