C-sharp面向对象

面向对象两个重要概念:类和对象

面向对象三大特征:封装、继承和多态

类和对象

例子,我们定义一个Person类代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace demo
{
internal class Person // 类首
{
// 类体
//成员变量
public string name;
public int age;
//成员方法
public void Speak()
{
Console.WriteLine("我是{0},我今年{1}岁", name, age);
}
}
}

然后在main方法中使用这个类,即在Program.cs文件中输入代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace demo
{
class Program
{
static void Main(string[] args)
{
Person person = new Person(); // 实例化对象
person.name = "tom";
person.age = 19;
person.Speak();
Console.ReadLine();
}
}
}

类的成员

类是一种引用数据类型。类的成员有:

  • 数据成员
  • 方法成员

构造方法

  • 构造方法名和类名相同
  • 构造方法没有返回值类型
  • 构造方法名前的权限修饰符通常使用public

构造方法的重载,例子:

在Person类中代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace demo
{
internal class Person // 类首
{
// 类体
//成员变量
public string name;
public int age;
//成员方法
public Person() // 定义无参构造方法
{
Console.WriteLine("无参构造方法调用了");
}
public Person(string name) // 定义有参构造方法
{
Console.WriteLine("有参构造方法调用了,name={0}",name);
}
public void Speak()
{
Console.WriteLine("我是{0},我今年{1}岁", name, age);
}
}
}

在Program.cs中编写代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace demo
{
class Program
{
static void Main(string[] args)
{
Person person1 = new Person(); // 实例化对象(无参构造方法)
Person person2 = new Person("tom"); // 有参构造方法
Console.ReadLine();
}
}
}

属性

  • 属性是一种与当前对象关联的、主要用于访问类中某一数据成员的机制
  • 为了防止数据被其他人随意修改,把字段声明为私有访问权限(封装性

属性包括属性头和属性体。属性头(属性名),属性体(getset

例子,在Person类中输入代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace demo
{
internal class Person // 类首
{
// 类体
//声明字段
private string name;
private int age;

// 声明属性
// 通过快捷键自动生成的get和set
public string Name { get => name; set => name = value; }
public int Age { get => age; set => age = value; }

//public string Name
//{
// get { return name; } // 读操作
// set { name = value; } // 写操作
//}
//public int Age
//{
// get { return age; }
// set
// {
// if (age<0)
// {
// Console.WriteLine("年龄输入不合法!");
// }
// else
// {
// age = value;
// }
// }
//}
//// 自动属性
//public char Gender { get; set; }


//成员方法
public void Speak()
{
Console.WriteLine("我是{0},我今年{1}岁", Name, Age);
}
}
}

其中快速生成get和set的快捷键为Ctrl R E,自动生成的代码中的=>是Lambda表达式,可自行查看。

关于C#自动属性可自行查看。

属性和方法都属于类的方法成员,都可以完成对类体中数据成员的访问。不同之处为:

  • 属性名后不需要(),方法名后必须有()
  • 属性不能指定参数,方法可以指定参数
  • 属性不能使用void类型,方法可以使用void类型

修饰符

访问修饰符(C# 编程指南)C#的类修饰符和成员修饰符

类的常用修饰符:

  • public 表示访问权限不受限制
  • abstract 表示这是一个抽象类
  • sealed 表示这是一个密封类(不能被继承)

类的成员常用修饰符:

  • public 表示公有成员,访问不受限制
  • private 表示私有成员,只能在成员所在类内访问。(类成员的默认访问权限)
  • protected 表示受保护成员,只能在成员所在类内和子类访问
  • static 表示静态成员变量,只能使用类名访问该成员。不用static修饰的成员称为实例成员,需要使用类的实例(即对象)进行访问

继承

菜鸟教程

继承是通过已有类创建新类的机制。

  • 被继承的已有类称为超类(super class,又称基类、父类)
  • 继承得到的新类称为扩展类(expansion class,又称子类、派生类)

在C#的继承中:

  • 使用冒号:来表示继承关系

  • C#对类的继承是单继承

  • C#对接口的实现是多继承(类似Java的单继承多实现)

  • 超类要求写在接口列表前面

例子,我们先定义一个Person父类,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace demo02
{
class Person
{
// 类体
//声明字段
private string name;
private int age;

// 声明属性
// 通过快捷键自动生成的get和set
public string Name { get => name; set => name = value; }
public int Age { get => age; set => age = value; }

//成员方法
public void Speak()
{
Console.WriteLine("我是{0},我今年{1}岁", Name, Age);
}
}
}

然后创建Student类,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace demo02
{
class Student:Person // Student类继承了Person类
{

}
}

然后在main方法中输入代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace demo02
{
class Program
{
static void Main(string[] args)

Student student = new Student();
student.Name = "tom";
student.Age = 19;
student.Speak();
Console.ReadLine();
}
}
}

可以看到Student类以及可以继承Person类的成员变量和方法了。

关于构造方法的继承,例子如下:

先在父类Person中添带参构造方法如下:

1
2
3
4
5
6
public Person(string name,int age)
{
Name = name;
Age = age;
Console.WriteLine("调用了父类有参构造函数");
}

然后在子类Student类中添加代码如下:

1
2
3
4
5
6
7
class Student:Person    // Student类继承了Person类
{
public Student(string name,int age) : base(name, age)
{
Console.WriteLine("调用了子类的有参构造函数");
}
}

然后在main方法中执行代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace demo02
{
class Program
{
static void Main(string[] args)
{
Student student = new Student("tom",19);
student.Speak();
Console.ReadLine();
}
}
}

结果为:

(具体可自行查看)

抽象类和抽象方法

使用abstract关键字作为修饰符的类和方法即为抽象类和抽象方法

  • 抽象类本身不能被实例化,即不能直接创建抽象类的对象
  • 抽象类只能作为父类,用于继承
  • 当某个抽象类被继承得到的子类作为非抽象类后,该子类可以正常进行实例化操作
  • 抽象方法只有方法头,没有方法体。即没有方法的具体实现
  • 每个抽象方法的方法声明以分号;结束

抽象类和抽象方法的关系:

  • 抽象类可以不包含抽象方法
  • 如果某个类中包含了抽象方法,则该类必须声明为抽象类
  • 当继承抽象类的子类是非抽象类时,则必须在子类中为抽象父类中的抽象方法提供具体的实现
  • 在实现抽象方法时,需要在方法头使用override关键字

例子:

我们声明一个抽象类Animal,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace demo03
{
abstract class Animal
{
public abstract void Bark(); // 抽象方法
public void Sleep() // 非抽象方法
{
Console.WriteLine("睡着了");
}
}
}

然后在创建其子类Dog,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace demo03
{
class Dog : Animal
{
public override void Bark() // 实现抽象方法
{
Console.WriteLine("狗再叫");
}
}
}

(在子类中重写抽象方法快捷键:Alt+Enter

执行代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace demo03
{
class Program
{
static void Main(string[] args)
{
Dog dog = new Dog();
dog.Bark();
Console.ReadLine();
}
}
}

sealed密封类

sealed(C# 参考)

sealed关键字来修饰类、实例方法和属性作用如下:

  • 类的声明使用sealed修饰,表示该类不允许被继承
  • 方法声明使用sealed修饰,表示方法重写了父类的方法,但不允许再被其子类重写
  • 属性声明使用sealed修饰,表示属性重写了父类的属性,但不允许再被其子类重写

接口

菜鸟教程

  • 接口是完全的抽象成员的集合,即接口体中只能包含抽象成员(抽象方法、抽象属性等)
  • 接口默认的成员默认为公有、抽象成员,因此接口的成员声明前不能添加publicabstract关键字
  • 一个类可以实现多个接口,接口的实现使用冒号:
  • 使用interface关键字声明接口

(单继承多实现)

例子:

创建一个接口ISleepable,代码如下:(接口命名一般在前面加I)

1
2
3
4
5
6
7
8
9
10
11
12
13
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace demo04
{
interface ISleepable
{
void Sleep();
}
}

然后创建People类来实现ISleepable接口,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace demo04
{
class PeoPle : ISleepable
{
public void Sleep()
{
Console.WriteLine("人可以睡觉");
}
}
}

执行代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace demo04
{
class Program
{
static void Main(string[] args)
{
People people = new People();
people.Sleep();
Console.ReadLine();
}
}
}

再来一个例子:有三类人员教师、学生和程序员,他们都有姓名、年龄等属性,都可以通过Sleep方法睡觉,其中教师可以教学。

我们分别创建Teacher、Student、Coder类,并让其继承父类People。

People类代码为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace demo04
{
class People : ISleepable
{
private string name;
private int age;

public string Name { get => name; set => name = value; }
public int Age { get => age; set => age = value; }

public void Sleep()
{
Console.WriteLine("{0}睡觉了",Name);
}
}
}

创建ITeach接口,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace demo04
{
interface ITeach
{
void Teach();
}
}

创建Teacher类代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace demo04
{
class Teacher : People, ITeach // 继承People类,实现ITeach接口
{
public void Teach()
{
Console.WriteLine("{0}开始教课了", Name);
}
}
}

然后分别创建Student类和Coder类,关键部分代码如下:

1
2
3
4
5
6
namespace demo04
{
class Student:People
{
}
}
1
2
3
4
5
6
namespace demo04
{
class Coder:People
{
}
}

执行代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace demo04
{
class Program
{
static void Main(string[] args)
{
Teacher teacher = new Teacher();
Student student = new Student();
Coder coder = new Coder();
teacher.Name = "老师";
student.Name = "学生";
coder.Name = "程序员";
teacher.Sleep();
teacher.Teach(); // 实现了ITeach接口,即可以调用Teach方法
student.Sleep();
coder.Sleep();
Console.ReadLine();
}
}
}

例子3:两个接口中的抽象方法同名的情况

创建两个接口IMyInterface1和IMyInterface2,关键部分代码如下:

1
2
3
4
5
6
7
namespace demo04
{
interface IMyInterface1
{
void Print();
}
}
1
2
3
4
5
6
7
namespace demo04
{
interface IMyInterface2
{
void Print();
}
}

然后创建MyClass类,来实现这两个接口,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace demo04
{
class MyClass : IMyInterface1, IMyInterface2
{
void IMyInterface1.Print()
{
Console.WriteLine("实现了IMyInterface1中的Print方法");
}
void IMyInterface2.Print()
{
Console.WriteLine("实现了IMyInterface2中的Print方法");
}

}
}

Alt+Enter,选择显示实现接口

然后执行代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace demo04
{
class Program
{
static void Main(string[] args)
{
IMyInterface1 interface1 = new MyClass(); // 多态
IMyInterface2 interface2 = new MyClass(); // 多态
interface1.Print();
interface2.Print();
Console.ReadLine();
}
}
}

多态

菜鸟教程

多态是同一个行为具有多个不同表现形式或形态的能力。

多态性意味着有多重形式。在面向对象编程范式中,多态性往往表现为"一个接口,多个功能"。

多态性可以是静态的或动态的。在静态多态性中,函数的响应是在编译时发生的。在动态多态性中,函数的响应是在运行时发生的。

在 C# 中,每个类型都是多态的,因为包括用户定义类型在内的所有类型都继承自 Object。

多态:子类对象父类声明

多态主要表现为:方法重载和方法重写(override)

  • 方法重载:在一个类中包含名字相同参数类型不同的多个方法
  • 方法重写:如果父类提供的功能不能满足要求,则可以在子类中重新定义
    • 在父类中如果想让某个方法被子类重写,可以使用virtual关键字将方法声明为虚方法,然后就可以在子类中使用override关键字重写该方法
    • 在C#中方法默认是非虚拟的即不能被重写(若要重写需要virtual关键字)
    • virtual不能和static同时使用
    • virtual不能和private同时使用
    • 重写方法的名称、参数、类型、返回值必须与原虚方法完全一样

例子:创建一个父类Animal,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace demo05
{
class Animal
{
public virtual void Bark()
{
Console.WriteLine("动物在叫");
}
}
}

然后创建其子类Dog类和Cat类,关键部分代码如下:

1
2
3
4
5
6
7
class Dog:Animal
{
public override void Bark()
{
Console.WriteLine("狗在叫");
}
}
1
2
3
4
5
6
7
class Cat:Animal
{
public override void Bark()
{
Console.WriteLine("猫在叫");
}
}

执行代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace demo05
{
class Program
{
static void Main(string[] args)
{
// 里氏替换原则
Animal animal1 = new Dog(); // 多态
animal1.Bark();
Animal[] arrAnimla = { new Dog(), new Cat()};
for (int i = 0; i < arrAnimla.Length; i++)
{
arrAnimla[i].Bark();
}

//// 不使用多态
//Animal animal = new Animal();
//Dog dog = new Dog();
//Cat cat = new Cat();
//animal.Bark();
//dog.Bark();
//cat.Bark();
Console.ReadLine();

}
}
}