前言
面向对象是老生常谈的话题了,但仍有小白和萌新不懂,那今天我们就来学学面向对象的基础知识。
因为小农擅长C#语言,所以以下例子使用C#,但是语法内容简单,不会影响阅读。
首先,什么是对象呢?
什么?你连对象是什么都不知道? 请你离开·······别,别,别,留下,留下······
啥不懂的我们一起学嘛!
面向对象编程
万物皆对象
其实,万物皆对象,你看到的、听到的一切事物都是对象。
对象是一个自包含的实体,用一组可识别的特性和行为来标识。–《大话设计模式》
而面向对象编程,顾名思义就是针对对象编程。
既然聊到面向对象编程那就不得不提及类了,那什么是类呢?
物以类聚
图书馆整理书籍时,会将书籍进行分类,比如计算机类、外语类、外国小说、武侠小说等等。
当我们需要寻找《天龙八部》时,我们可以到武侠小说类里面去寻找。
而在编程中,类的定义
类就是具有相同的属性和功能的对象的抽象的集合
好了,官方的话说完了,我们来“造人”,造个女朋友出来。
所以我们建一个名为女朋友的类。
现实 | 计算机中 | C#代码 |
---|---|---|
女朋友 | 类 | class GrilFriend { } |
class GrilFriend
{
}
创建实例(喜欢的对象自己New)
我们想让她跟我们问好,要如何做呢?
我们可以在类里面写一个说话的方法。
class GrilFriend
{
public string Speak()
{
return "早上好!帅气的程序猿";
}
}
我们把她放到主函数中,让她说话。但是女朋友得是一个真实的实例,实例就是一个真实的对象
可不能是一个类,所以我们要创建实例。
static void Main (string[] args) {
GrilFriend grilFriend = new GrilFrend();
Console.WriteLine(grilFriend .Speak ());
}
这就是创建实例,使用new 关键字来创建,上述代码就是创建了一个女朋友的实例,并使用控制台输出。
输出结果:
构造方法(帮女朋友取名字)
她需要有个名字,一般是父母帮她起,但是在这,你可以自己起!
名字是生下来就取的,所以我们希望在创建实例的时候,它能自己就有名字了。
所以我们需要有个方法,在创建实例的时候就调用给她取名,没错,这就是构造方法。
构造方法,又叫构造函数,其实就是对类进行初始化。构造方法与类同名,无返回值,也不要void,在New的时候就可以用了。–《大话设计模式》
我们来给女朋友起个名字···唔··叫什么好呢? 我这么屌丝,会过贫苦的生活,
所以她应该要很坚强才行,就叫王坚强吧!
我们定义一个字符变量name
,用与存储名称,然后定义一个字符参数name
,
用于构造的时候传递名称进来。
class GrilFriend
{
private string name="";
public GrilFriend (string name)
{
this.name=name;
}
public string Speak()
{
return "我是"+name+",早上好!帅气的程序猿 ";
}
}
static void Main(string[] args)
{
GrilFriend grilFriend=new GrilFrend ("王坚强");
Console.WriteLine(grilFriend.Speak ());
}
输出结果:
就这样,你的女朋友王坚强就出世啦!!恭喜恭喜!
重载(名字没想好,能不能不取名?)
有的朋友表示不服
这名字也太难听了,宁愿没有呢,换一个···
唔···换啥呢,没想好,对了···你这样子弄,我不起名字,难道我女朋友就不能出生了吗?
按目前的代码,如果写new GrilFriend ()
是会报错的,
因为你必须取名,所以你不想起名字的话,我们可以运用重载
方法重载提供了创建同名的多个方法的能力,但是这些方法需使用不同的参数类型。–《大话设计模式》
class GrilFriend
{
private string name="";
public GrilFriend(string name)
{
this.name=name;
}
public string Speak()
{
return "我是" + name + ",早上好!帅气的程序猿 ";
}
public GrilFriend()
{
this.name="无名氏"
}
}
这样再重新新建就不会报错了。
属性
你会发现,我们可以给女朋友起名,但是如果我们想在主函数读取女朋友的名字,你会发现读取不到,
因为name
是变量是私有的,然后我们很容易的想到,将它设为public
,可是这样不是谁都能知道它的名字了吗?
如果哪天你发现大家都能知道你女朋友的名字,认识你女朋友,你一定会觉得自己
绿了!~
~~这显然不是我们期望的,有什么解决办法吗?
有的小伙伴就想到了,我们可以设置一个设置值方法,再设置一个获取值的方法,写入方法是private
,读取是public
private void Set(string name)
{
this.name=name;
}
public string Get()
{
return this.name;
}
可是女朋友后续还会有很多特性,例如身份证、年龄、身高等。各个都这么写,实在是太麻烦了,
于是,属性的作用就体现出来了。
属性是一个方法或一对方法,但在调用它的代码看来,它是一个字段,即属性适合于以字段的方式使用方法调用的场合。–《大话设计模式》
在C#中,它长这个样子
private string name="";
public string Name
{
get {
return name;
}
set {
name = value;
}
}
通过get,set访问器我们就可以控制这个字段的访问了,成功保护女朋友的隐私,
如果我们只让读取,我们可以只设置get访问器,不用set访问器。
private string name="";
public string Name
{
get {
return name;
}
}
然后我们就可以在主函数中通过对对象的属性进行访问获取名称并输出结果。
class Program
{
static void Main(string[] args)
{
GrilFriend girlFriend =new GrilFriend ("王坚强");
Console.WriteLine(girlFriend.Name);
}
}
输出结果:
三大特性
相信很多小伙伴都听过面向对象三大特性了,什么?没听过?
请你··········坐下,听我一一道来。
面向对象有三大特性,分别是封装,继承,多态。
封装
把每个对象相关信息,都给对象包装好,让我们调用的时候只用调用它,而不用通过其他的对象来完成它的相关操作,这个特性就叫封装。
例如我有这么个需求,我出门老是不记得我锁门了没还有就是钱包钥匙等小东西老是忘带,要怎么解决呢?
对了,将这些东西(变量)用一个包(类)装(封装)起来不就好了嘛··
于是我买了一个小背包,将这些小玩意全都装起来,每次我只要判断我是否有带包就可以了,这样大大提高了效率和安全性。
在程序中也是如此,我们需要对一个类进行调用,
希望通过它的实例就能访问到它的所有属性,而不是在其他类中(你不希望你的钱包从别人包里拿出来吧?),我们就可将其封装,便于调用。
继承
当一个人去世,他的个人财产将会由法定继承人或者遗嘱继承人来继承,
一般来说,是自己的直系亲属,这是现实的继承,那么面向对象的继承是什么样的呢?
现在有这么个问题,妈妈也有跟你问侯的方法,那我们要怎么实现呢?
初始思维,肯定是按照上面的再走一遍。
class Mom{
private string name = "";
public string Name
{
get {
return name;
}
}
public Mom(string name) {
this.name = name;
}
public string Speak () {
return "我是" + name + ",早上好!儿砸";
}
public Mom() {
this.name = "无名氏";
}
}
可是,你这做法让家族人多的怎么活·········
还有七大姑八大姨们呢~~难道加一个写一遍??
虽说有CV(Ctrl +C,Ctrl+V)大法,但是如果要改或者新增呢?顶不住了吧····
所以我们把两个类相同的方法和属性,提取到一个类中,叫做Woman类,
然后,再让Mom和GirlFriend去继承这个类。
public class Woman {
protected string name = "";
public string Name {
get {
return name;
}
}
public Woman (string name) {
this.name = name;
}
public Woman () {
this.name = "无名氏";
}
}
注意,“:”这个符号代表继承的意思,在Java中,使用extends 。
注意,name
字段的关键字更改为protected
为了让子类也能继承到,如果是private
,则无法继承是会报错无此参数的。
如果有对这些关键字不懂的小伙伴,请点击 C#中public、private、protect的区别
class Mom: Woman {
public Mom(string name):base(name)
{
}
public string Speak () {
return "早上好!儿砸 ";
}
public Mom() : base () {
}
}
class GrilFriend: Woman {
public GrilFriend(string name):base(name)
{
}
public string Speak () {
return "我是" + name + ",早上好!帅气的程序猿 ";
}
public GrilFriend() : base () {
}
}
这样,代码就简洁了很多,如果不用继承,要修改里面的功能,就必须在所有重复的地方修改,所以,继承让代码实现了共享。
将功能代码放在公共类,后续再新增七大姑八大姨类我们的代码量也会大大减少。
多态
现实是,我们的家族并不止2个人,往往还有妈妈,姐姐等。如果大家都问好,相信会出现以下代码局面
static void Main (string[] args) {
GirlFriend girlFriend = new GirlFriend ("王坚强");
Sister sister = new Sister ("姐姐");
Mom mom = new Mom ("妈妈");
Console.WriteLine (girlFriend.Speak ());
Console.WriteLine (sister.Speak ());
Console.WriteLine (mom.Speak ());
}
输出结果:
显然,这样实在是太笨重了,有没有更好的解决办法呢?
我们观察一下代码,
Console.WriteLine (girlFriend.Speak ());
Console.WriteLine (sister.Speak ());
Console.WriteLine (mom.Speak ());
我们发现:这3行代码就是不同的对象调用同一个方法,但是通过自己的方式来实现问候。
那可不可以用一个“通用的对象”,当我们拿一个对象初始化它,它就转化成那个对象,然后调用的就是它的问候方法呢?
就比如说 我定义一个 “万能对象”,然后将妈妈对象实例化后赋值给它,
调用它的问候方法就变成妈妈的问候方法,然后拿姐姐这个对象赋值给它,它就会调用姐姐的呢?
这个“通用的对象”就是我们的Woman类啦。
首先,我们将speak方法提取到Woman类中,并定义初始的问候方法。
public class Woman {
····省略
public string Speak () {
return "早上好!";
}
}
但是,3个类和Woman
类中都有Speak方法,当我们调用的时候,是程序是怎么知道我们调用的是父类还是子类的方法呢?所以这时候我们就要用到重写,即Override
。
首先我们将Woman
类中的Speak方法声明为可重写的,使用virtual
关键字,将该方法定义为虚方法
为了让子类能完全替代父类去执行speak方法。
public class Woman {
····省略
public virtual string Speak () {
return "";
}
}
然后我们在子类中重写该方法
class Sister : Woman {
public Sister (string name):base(name)
{
}
public override string Speak () {
return "我是" + name + ",早上好!弟弟 ";
}
public Sister () {
}
}
class GirlFriend :Woman{
public GirlFriend (string name):base(name)
{
}
public override string Speak () {
return "我是" + name + ",早上好!帅气的程序猿 ";
}
public GirlFriend () {
}
}
class Mom : Woman {
public Mom (string name):base(name)
{
}
public override string Speak () {
return "我是" + name + ",早上好!儿砸";
}
public Mom () {
}
}
然后我们就可以定义一个Woman的数组,将实例化后的GirlFriend
、Sister
、Mom
实例放入数组中,
static void Main (string[] args) {
Woman[] arrayhuman = new Woman[3];
arrayhuman[0] = new GirlFriend ("王坚强");
arrayhuman[1] = new Sister ("姐姐");
arrayhuman[2] = new Mom ("妈妈");
}
然后我们使用foreach
循环调用输出类中的Speak
方法
static void Main (string[] args)
{
省略···
foreach (Woman human in arrayhuman) {
Console.WriteLine (human.Speak ());
}
}
输出结果:
由于多态性,程序会在运行时寻找human是什么对象,然后调用指定的重写方法。
多态,正如字面意思一样,就是“多种状态”,除了这种继承类重写,接口的不同实现方式也为多态,
准确的说多态性是允许你将父对象设置成为一个或更多的他的子对象相等的技术。
多态的原理是当方法被调用时,无论对象是否被转换为其父类,都只有位于对象继承链最末端的方法实现会被调用。也就是说,虚方法是按照其运行时类型而非编译时类型进行动态绑定调用的。–《大话设计模式》
本文是在参考《大话设计模式》(强力推荐)基础上加上个人理解编写完成。如有错误,请指正,谢谢。
作者信息
2年crud码农一枚,如果本文对你有所帮助,请点赞,谢谢。