【C#学习4】面向对象编程

本文深入解析C#中的属性、构造函数、继承、接口、抽象类等面向对象编程核心概念,通过实例演示如何利用这些特性提升代码的模块化和重用性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

属性

定义

  • 定义属性需要名字和类型
  • 属性包含两个块 get块和set块
  • 访问属性和访问字段一样,当取得属性的值的时候,就会调用属性中的get块,所以get块需要一个返回值,类型就是属性的类型;当我们去给属性设置值的时候,就会调用属性中的set块,我们可以在set块中通过value访问到我们设置的值。
public int MyIntProp
{
	get{ 
	     get code
	 }
	 set{
	     set code
	 }
}

实例:
定义一个MyProperty类

namespace _016_属性的定义
{
    class MyProperty
    {
        //定义属性,get和set块可以只有一个或者两个都有
        public int MyIntProperty
        {
            get
            {
                Console.WriteLine("属性中的get块被调用");
                return 100; //get块里需要一个返回值
            }
            set
            {
                Console.WriteLine("属性中的set块被调用");
                Console.WriteLine("在set块中访问value的值:" + value);
            }
        }
    }
}

调用

namespace _016_属性的定义
{
    class Program
    {
        static void Main(string[] args)
        {
            MyProperty myProperty = new MyProperty();

            myProperty.MyIntProperty = 200; //给属性设置值的时候,调用set块
            Console.WriteLine(myProperty.MyIntProperty); //访问的时候,调用get块

            Console.ReadKey();
        }
    }
}

通过属性来访问字段

我们习惯上把字段设置为私有的,这样外界就不能修改字段的值,之后我们可以通过定义属性来设置和取得字段中的值

private int age;
public int Age
 {
     get { return age; }
     set{
         //通过set方法在设置值之前做一些校验的工作
         if (value>0)
         {
             age = value;
         }
     }
 }

其他效果

  • 设置属性的只读或者只写
 private string name;
 public string Name 
	 {
	     get { return name; }
	 }
  • 属性的访问修饰符
  private string name;
  public string Name //也可以叫做get set 方法
  {
      //如果在get或者set前面加上private,表示这个块只能在类内部调用,也就是这个属性只读/只写
      get { return name; }
      private set { name = value; }
  }
  • 自动实现的属性
//编译器会自动给我们提供一个字段/数据成员,不用我们定义
public string Sex { get; set; } 

面向对象编程

定义

  • 为了让编程更加清晰,把程序中的功能进行模块化划分,每个模块提供特定的功能,而且每个模块都是孤立的,这种模块化编程提供了非常大的多样性,大大增加了重用代码的机会。
  • 面向对象编程也叫做OOP编程。简单来说面向对象编程就是结构化编程,对程序中的变量结构划分,让编程更清晰。

  • 类创建的变量叫做对象。 类实际上是创建对象的模板,每个对象都包含数据,并提供了处理和访问数据的方法。
  • 类定义了类的每个对象(称为实例)可以包含什么数据和功能。

类的定义和声明

类中的数据和函数称为类成员,包含:

  • 数据成员:数据成员是包含类的数据–字段,常量和事件的成员。

  • 函数成员:提供了操作类中数据的某些功能。(方法,属性,构造方法和终结器(析构方法),运算符和索引器)。

  • 实例

定义一个Customer类

namespace _015_面向对象编程_类
{
    class Customer //定义了一个新的类叫Customer类
    {
        //数据成员:包含2个字段
        public string name;
        public int age;

        //函数成员:定义了一个方法
        public void show()
        {
            Console.WriteLine("名字:" + name);
            Console.WriteLine("年龄:" + age);
        }
    }
}

定义一个Vector3类

namespace _015_面向对象编程_类
{
    class Vector3
    {
        //编程规范上,习惯把所有字段的访问修饰符设置为private,只可以在类内部访问,不可以通过对象访问
        private float x,y,z;

        //为字段提供set方法,来设置字段的值
        public void setX(float x)
        {
            //如果我们直接在方法内部访问同名的变量的时候,优先访问形参
            //使用this.表示访问的是类的字符或者方法
            this.x = x;
        }
        public void setY(float y)
        {
            this.y = y;
        }
        public void setZ(float z)
        {
            this.z = z;
        }
        public float Length()
        {
            return (float)Math.Sqrt( Math.Pow(x, 2) + Math.Pow(y, 2) + Math.Pow(z, 2) );
        }

    }
}

调用

namespace _015_面向对象编程_类
{
    class Program
    {
        static void Main(string[] args)
        {
            //要使用一个类的话,需要先引入它的命名空间

            Customer customer1; //声明一个对象(变量),也称之为实例化
            customer1 = new Customer(); //对对象进行初始化,需要用new + 类名
            //使用类声明的对象中的变量,还有方法
            customer1.name = "China";
            customer1.age = 70;
            customer1.show();

            Vector3 v1 = new Vector3();
            v1.setX(1);
            v1.setY(1);
            v1.setZ(1);
            Console.WriteLine("\n" + v1.Length());

            Console.ReadKey();
        }
    }
}

PS:实参和形参

方法中的参数分为实际参数和形式参数,实际参数被称为实参,是在调用方法时传递的参数;形式参数被称为形参,是在方法定义中所写的参数。

public int Add(int a,int b) //a 和 b 是形式参数
{
    return a+b;
}
public void Print()
{
    Add(3,4); //调用 Add 方法时传递的参数 3 和 4 即为实际参数。
}

构造函数

自行为类声明一个对象(变量),分为有参和无参

定义一个Vector3类

namespace _015_面向对象编程_类
{
    class Vector3
    {
        private float x, y, z,length;

        //构造函数,我们可以使用有参的或者无参的构造函数进行初始化,之后编译器不会为我们自动提供构造函数
        public Vector3() //无参的构造函数
        {
            Console.WriteLine("Vector3类的构造函数被调用了");
        }
        public Vector3(float x, float y, float z) //有参的构造函数
        {
            this.x = x;
            this.y = y;
            this.z = z;
            length = Length();
            Console.WriteLine(length);
        }
        public float Length()
        {
            return (float)Math.Sqrt( Math.Pow(x, 2) + Math.Pow(y, 2) + Math.Pow(z, 2) );
        }
	}
}

调用

namespace _015_面向对象编程_类
{
    class Program
    {
        static void Main(string[] args)
        {
            //构造函数
            Vector3 v1 = new Vector3();
            Vector3 v2 = new Vector3(1, 1, 1);

            Console.ReadKey();
        }
    }
}

继承

实现继承

表示一个类型派生于一个基类型,它拥有该基类型所有的成员字段和函数。在实现继承中,派生类型采用基类的每个函数的实现代码,除非在派生类型的定义中指定重写某个函数的实现代码。在需要给现有的类型添加功能,或许多相关的类型共享一组重要的公共功能时,这种类型的继承非常有用。

class MyDerivedClass:MyBaseclass
{
	functions and data members here
}
  • 实例:

父类(基类) Enemy

namespace _017_继承
{
    class Enemy
    {
        private float hp;
        private float speed;

        public float Hp
        {
            get { return hp; }
            set  { hp = value; }
        }
        public float Speed
        {
            get { return speed; }
            set { speed = value; }
        }
        public void AI()
        {
            Console.WriteLine("这是Enemy类公有的AI方法");
        }
    }
}

子类(派生类)Boss

class Boss:Enemy
    {
        public void Attack()
        {
            Console.WriteLine("Boss正在进行攻击");
        }
    }
class Program
    {
        static void Main(string[] args)
        {
            //继承:父类里面所有的数据成员和函数成员都会继承到子类里面
            Boss boss = new Boss();
            boss.AI(); //继承父类的方法
            boss.Attack();

            //可以用父类声明对象,然后用子类构造(实例化),反之子类声明的对象不可以使用父类构造
            Enemy enemy;
            enemy = new Boss();
            //enemy.Attack(); //此时的对象仍是父类类型,需要强制类型转换为子类类型
            Boss boss1 = (Boss)enemy;
            boss1.Attack();

            Console.ReadKey();
        }
    }

接口继承

表示一个类型只继承了函数的签名,没有继承任何实现代码。在需要指定该类型具有某些可用的特性时,最好使用这种类型的继承。

虚方法

把一个基类函数声明为virtual,就可以在任何派生类中重写该函数:

class Enemy
   {
       public virtual void Move()
       {
           Console.WriteLine("这是Enemy类公有的Move方法");
       }
   }

在派生类中重写另外一个函数时,要使用override关键字显示声明:

class Boss:Enemy
   {
       public override void Move()
       {
           //base.Move();
           Console.WriteLine("Boss正在移动");
       }
   }
}
class Program
    {
        static void Main(string[] args)
        {
            Boss boss = new Boss();
            boss.Move(); //调用子类中重写的方法

            Enemy enemy = new Enemy();
            enemy.Move(); //调用父类中的虚方法

            Console.ReadKey();
        }
    }

隐藏方法

如果签名相同的方法在基类和派生类中都进行了声明,但是该方法并没有分别声明virtual和override,这是派生类就会隐藏基类方法(可以使用new关键字进行声明)
一个方法的签名即为一个方法的定义,例如返回值、参数、方法名等。

class Enemy
{
 	public void AI()
        {
            Console.WriteLine("这是Enemy类公有的AI方法");
        }
}
class Boss:Enemy
    {
        public new void AI()
        {
            Console.WriteLine("这是Boss的AI方法");
        }
    }
class Program
    {
        static void Main(string[] args)
        {
			Boss boss = new Boss();
			boss.AI();    //This is AI from Boss!
			
			Enemy boss = new Boss();
			boss.AI();    //This is AI from Enemy!
            Console.ReadKey();
        }
    }

隐藏和重写的区别:
隐藏:只是将父类中的方法给隐藏了,实际这个方法还存在。
重写:将原先父类中的方法完全重写了,原先的方法是不存的了。
使用子类构造的时候,重写访问的是子类的,隐藏访问的是父类的

Enemy enemy = new Enemy(); //都是父类的方法
enemy.Move(); 
enemy.AI(); 

Enemy enemy = new Boss();
enemy.Move(); //重写
enemy.AI(); //隐藏

一般不去使用隐藏方法,因为很容易引起方法调用的混乱。

this 和base关键字的作用

  • this:可以访问当前类中定义的字段、属性和方法;另外当方法的参数跟字段重名的时候,使用this可以表明访问的是类中的字段
  • base:可以调用父类中的公有方法和字段

有没有this和base关键字都可以访问,但是加上关键字后IDE-VS编译器会给出提示,把所有可以调用的字段和方法罗列出来方便选择

抽象类

C#允许把类和函数声明为abstract。抽象类不能实例化,但可以包含普通函数和抽象函数,抽象函数就是只有函数定义没有函数体,定义的时候需要使用关键字"abstract",派生类中必须重写基类所有的虚方法。

实例:
基类Bird:

abstract class Bird //当类中存在抽象方法的时候,这个类也要声明为抽象的
    {
        private float speed;
        public void Eat()
        {
        }
        public abstract void Fly(); //定义一个虚方法
    }

派生类Crow:

class Crow:Bird //继承一个抽象类后,必须去实现抽象方法
    {
        public override void Fly() //关键字override重写
        {
            Console.WriteLine("乌鸦在飞");
        }
    }
class Program
    {
        static void Main(string[] args)
        {
            Bird bird = new Crow(); //我们可以通过抽象类去声明对象,但是不可以构造
            bird.Fly();
            Console.ReadKey();
        }
    }

密封类和密封方法

C#允许把类和方法声明为sealed。对于类,这表示不能继承该类;对于方法表示不能重写该方法。 当防止重写某些类导致代码混乱的时候可以使用。

sealed class BaseClass //密封类无法被继承
{
	public sealed void Move() //密封方法不能被重写
	{
	}
}

派生类的构造函数

在子类中调用父类的默认构造函数(会先调用父类的,然后是子类的)。
无参的base()可以不写,因为默认会调用父类中的默认构造函数(调用有参的要写)

namespace _018_派生类的构造方法
{
    class BaseClass
    {
        private int x;
        public BaseClass() //无参构造函数
        {
            Console.WriteLine("Base Class无参构造函数");
        }
        public BaseClass(int x) //有参构造函数
        {
            this.x = x;
            Console.WriteLine("x赋值完成");
        }
    }
}
class DerivedClass:BaseClass
    {
        private int y;
        public DerivedClass() :base()//调用父类中无参的构造函数。如果我们没有在父类中创建,默认会调用父类中的无参构造函数
        {
            Console.WriteLine("这个是DerivedClass无参的构造函数");
        }
        public DerivedClass(int x,int y) : base(x) //调用父类中有参的构造函数
        {
            this.y = y;
            Console.WriteLine("y赋值完成");
        }
    }
class Program
    {
        static void Main(string[] args)
        {
            DerivedClass derivedClass1 = new DerivedClass();
            DerivedClass derivedClass2 = new DerivedClass(1,2);

            Console.ReadKey();
        }
    }

访问修饰符

修饰符,用来修饰类型或者成员的关键字。修饰符可以指定方法的可见性。
在这里插入图片描述
其他修饰符
在这里插入图片描述
static可以修饰字段或者方法,修饰字段的时候,表示这个字段是静态的数据,叫做静态字段或者静态属性,修饰方法的时候,叫做静态方法,或者静态函数
使用static修饰的成员,只能通过类名访问。 当我们构造对象的时候,对象中只包含了普通的字段,不包含静态字段。

class DerivedClass:BaseClass
    {
        public static int z=1; 
         public static void TestMothod()
        {
            Console.WriteLine("静态方法");
        }
    }
class Program
    {
        static void Main(string[] args)
        {
        	//只能通过类访问静态字段或方法
            Console.WriteLine(DerivedClass.z); 
            DerivedClass.TestMothod();
            Console.ReadKey();
        }
    }

定义和实现接口

定义一个接口在语法上跟定义一个抽象类完全相同,但不允许提供接口中任何成员的实现方法。注:

  • 一般情况下,接口只能包含方法,属性,索引器和事件的声明。
  • 接口不能有构造函数,也不能有字段,接口也不允许运算符的重载。
  • 接口定义中不允许声明成员的修饰符,接口成员都是公有的。

定义接口

namespace _019_定义和实现接口
{
    interface IFly //关键字interface,以I开头
    {
    	//接口中不能实现函数
        void MethodA(); //方法只能够有签名,没有结构体
        void MethodB(); //默认都是公有的
    }
}

实现接口

class Bird : IFly
    {
        //实现接口
        public void MethodA(){
        }
        public void MethodB(){
        }
    }

Trick:

  • 抽象类和接口都不能实例化(构造),两者的方法都只有函数定义,没有函数体
  • 虚函数以重写之后的为准,就算调用基类的也是一样
  • 构造函数声明的时候,构造函数里的东西就被调用了
  • 在抽象类中,不一定所有的方法都是抽象方法,抽象类不需要实现
  • 在赋值的时候,值类型和引用类型要区分清楚
Public abstract Animal //在抽象类中,不一定所有的方法都是抽象方法,抽象类不需要实现
{
	public abstract void Eat();
	public void Sleep()
	{
	}
}

关于接口实现方式:隐式实现/显式实现

地址

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值