[干货] 一篇文章完全搞懂面向对象基本概念

62 篇文章 6 订阅
35 篇文章 1 订阅

今天我一个朋友, 突然问我, 在程序入口所用的类中, 为什么字段需要加static…

好家伙, 一听就是没懂啥是面向对象! 我也看了网上的一大堆东西, 啊说啥继承, 多态, 反正我是菜鸟的时候是没听懂这些东西, 后来还是我自己摸索出来的 (嘤嘤嘤QAQ)

好的, 我们使用 C# 来做演示, 大概了解一下类的最基本概念. 来整一个, 数组拓展.

演示代码片:

public class ArrayHelper
{
	public int[] Source;          // 字段
	public static int InsCount = 0;
	public Arrayhelper()           // 构造函数
	{
	    this.Source = new int[]{};
	    InsCount++;
	}
    public ArrayHelper(int[] arr)  // 有参数的构造函数
    {
        this.Source = arr;
	    InsCount++;
    }
    ~ ArrayHelper()                // 析构函数
    {
        InsCount--;
    }
    public void ResetInsCount()
    {
		InsCount = 0;
	}
    public int GetSum()            // 方法
    {
        int sum = 0;
        foreach (var i in Source)
            sum += i;
        return sum;
    }
    public int GetAverage()        // 方法
    {
        return GetSum() / Source.Length;
    }
}

1. 基本概念

1. 啥是对象:

对象, 它的英文是 object, 咱也不懂为啥它被翻译成这种有歧义的词, 没错, 它跟 ‘情人’ 没有任何关系. 意思接近为: “物体”, “事物”, 下面的句子中, ‘对象’ 一词是最接近编程中的 ‘对象’ 的.

  1. 你要帮助的对象是谁?
  2. 我们本次整改所针对的对象, 是厂里干部遇事却不作为的事件.

生活中一切东西都可成为对象, 例如一台电脑, 可以说是一个对象, 它有一些成员(字段, 属性, 方法), 例如所装载的硬件. 而对象都有自身的行为(方法), 例如, 汽车, 有点火这个行为.

2. 编程中的类:

编程中的类, 我们可以理解为模板, 或者生产机器, 通过它我们可以创建一个对象(类的实例化). 一个类产生的对象, 就是这个类的实例.

ArrayHelper helper = new ArrayHelper();   // 实例化 ArrayHelper, helper 就是我们示例化产生的对象

3. 构造函数:

构造函数, 就是这个类在构造对象的时候会调用的函数, 例如刚刚 new ArrayHelper() 的时候, 必定会调用类中的这一片代码:

public Arrayhelper()           // 构造函数
{
    this.Source = new int[]{};
}

我们可以声明多个构造函数, 以使用不同的方式来创建对象.

public ArrayHelper(int[] arr)  // 有参数的构造函数
{
    this.Source = arr;
}

让我们来使用下这个带参数的构造函数:

int[] myArray = new int[] {1, 2, 3};
ArrayHelper helper = new ArrayHelper(myArray);

4. 对象的字段

在我们的实例代码中, 类中有写一个 int[] 类型的 Source 字段. 我们也说过, 类就好比一个模板. 当一个对象创建后, 它就拥有这个字段

当我们实例化之后, 我们就可以通过实例, 来访问新生成的对象的成员:

ArrayHelper helper = new ArrayHelper();
Console.WriteLine(helper.Source);         // 将 helper 的 Source 成员打印出来

这个成员, 是对象所拥有的, 而不是类本身所拥有的, 所以, 下面的代码是错误的:

Console.WriteLine(ArrayHelper.Source);    // 会报错, 因为成员是对象的.

每一个对象都有自己独自的成员, 所以, 不同实例的成员, 值是一定相不同的.

5. 对象的方法

在我们的示例代码中, 类中有写一个返回值为 int 的 GetSum 方法, 同字段一样, 每个对象都有自己独自的方法, 所以调用后, 返回值是不一定相同的.

下面是演示:

int[] array1 = new[] {1, 2, 3};
int[] array2 = new[] {1, 2, 3, 4, 5};
ArrayHelper helper1 = new ArrayHelper(array1);
ArrayHelper helper2 = new ArrayHelper(array2);
Console.WriteLine(helper1.GetSum());       // 结果是6
Console.WriteLine(helper2.GetSum());       // 结果是15

同样, 属于对象的方法, 是不能通过类名来调用的, 以下代码是错误的:

Console.WriteLine(ArrayHelper.GetSum());    // 报错, 因为成员是对象的

6. 类的字段:

类也是可以有成员的, 它们独属于某个对象, 它们属于这个类. 这种成员, 被称为静态成员. 例如我们示例代码中的 InsCount 字段.

静态成员是可以被所有实例所访问的, 例如构造函数里面我们指定了为 InsCount 的值增加1, 这就意味着, 我们每实例化一个对象, 这个类的 InsCount 的值都将增加1;

访问类的静态成员, 需要通过类名来访问, 毕竟这个静态成员是属于这个类的. 不可以用实例名来访问, 因为不独属于任何一个实例.

正确示例:

Console.WriteLine(ArrayHelper.InsCount);     // 打印类的 InsCount 字段

错误示例:

ArrayHelper helper = new ArrayHelper();
Console.WriteLine(helper.InsCount);        // 报错, 因为成员属于类而不是对象

7. 类的方法:

静态方法, 同静态字段一样, 属于整个类, 所有对象可访问, 只能通过类名而无法通过对象名来访问.

例如我们的示例代码中的 ResetInsCount 函数.

正确示例:

ArrayHelper.ResetInsCount();

错误示例:

ArrayHelper helper = new ArrayHelper();
helper.ResetInsCount();

8. 析构函数:

析构函数与构造函数相对应, 析构函数将在对象被释放的时候执行, 例如我们代码片中的析构函数:

~ ArrayHelper()
{
	InsCount--;
}

即, 每当一个对象被释放, InsCount 的值都会减少1. 配合构造函数来看, 造成的结果就是, InsCount 字段将始终与当前的实例数量保持一致.

9. this 关键字:

有时候啊, 你可能写函数的参数名, 写了个跟成员一模一样的名字, 结果你想给实例成员赋值的时候, 发现实际上确是给方法的参数赋值了.

在一个类的非静态方法中, 想要访问实例的成员, 你可以用 this 来修饰以避免歧义.

下面是使用了 this 关键字的示例代码

public class TestObj
{
	int value;
    public void SetValue(int value)
    {
        this.value = value;
    }
    public int GetValue()
    {
        return this.value;
    }
}

10. 成员访问限制:

成员有访问限制, 常用的有 公共的(public), 私有的(private), 受保护的(protected), 如果不写修饰符, 那么默认访问限制就是private, 这个我得用一个比较长的代码片来演示了.

修饰符特征
public公有的, 无论谁都可以访问
internal内部的, 位于同一程序集的可以进行访问
protected受保护的, 除了这个类的成员, 子类也可以进行访问
private私有的, 仅有这个类的成员可以访问
using System;

namespace Null.Tutorial
{
    class TestObj{
    {
        static int insCount = 0;      // 没有写访问限制修饰符, 但是默认是private
        public int[] Source;
        public TestObj(int[] source)
        {
            this.Source = source;
            insCount++;
        }
        ~TestObj()
        {
            insCount--;
        }
        public int GetSum()
        {
            int sum = 0;
            foreach(var i in Source)
                sum += i;
            return sum;
        }
        public static int GetInstanceCount()
        {
            return insCount;
        }
    }
    class Program
    {
    	public static void Main(string[] args)
    	{
	        int[] myArr = new int[] {1, 2, 3};
	        TestObj objIns = new TestObj(myArr);
	        Console.WriteLine(objIns.insCount);    // 报错, 因为在类之外访问私有成员
	        Console.WriteLine(objIns.GetSum());    // 正常访问, 因为是public
	        Console.WriteLine(TestObj.GetInstanceCount());   // 正常访问, 因为是public
	    }
    }
}

11. 属性语法糖

C# 还支持属性这种东西. 其实是语法糖. 属性类似于字段, 举个例子, 你就懂了.

class TestObj
{
	int field3, field4, field5, field6;
    public int Field1 { get; set; }
    public int Field2 { get; private set; }
    public int Field3 => field3
    public int Field4 { get => field4; set => field4 = value; }
    public int Field5
    {
        get
        {
            return field5;
        }
        set
        {
            field5 = value;
        }
    }
    public int Field6 { get => field6; }
}

上面的代码中, Field1 ~ Field5 都是属性.

  • Field1 是开放(public)获取值与设置值的属性, 用法跟字段一致.
  • Field2 是开放(public)获取值, 但设置值是私有的(private)属性, 即, 在类外面可以直接获取值, 但无法设置.
  • Field3 对应私有字段 field3, 它的获取权限是 public, 设置权限是 private
  • Field4 对应私有字段 field4, 获取权限是 public, 设置权限是 private
  • Field5 是属性的完整写法, 其中包含 get 访问器与 set 访问器, 在没有指定个别访问修饰符时, 它们的权限与整个属性的权限一致.
  • Field6 是与私有字段 field6 相对应的, 没有指定 set 访问器的属性, 这意味着它只支持获取值, 不允许设置值, 但是类之内的成员可以直接通过对 field6 进行设置来实现更改属性值.

12. 对象引用

C# 和 Java 中, 有值类型和引用类型, 事实上, 一个 “存储” 对象的字段, 它包含的是这个对象的引用. 这个的话, C++ 程序员肯定熟悉的一批, 可不就是指针嘛.

Object obj = new Object();    // 创建一个对象, 并使用 obj 字段来存储对象引用
Console.WriteLine(obj);       // 通过这个对象引用, 可以直接对对象进行操作.
obj = null;

最后一句, 对 obj 字段赋值 null, 肯定会有人以为, 执行完之后, 我们刚刚创建的对象就嗝屁了, 但其实是, 仅仅是这个字段的值为null了.

引用类型的字段就像一个指针, 它有自己的值, 这个值表示对象的内存地址(也有可能是句柄啥的), 总之有这个引用, 就能够找到对象所在的位置. 而给这个字段赋值为null, 也就是改变了这个指针的方向, 让它指向 0x0000, 但事实上对象还存在着.

但是呢, .NET 和 JVM 都有着 GC(Garbage Collection), 即垃圾回收机制. 有一个个步骤来回收这些没用的对象, 到最终, 这些不被引用的托管对象(即被运行时管理的对象)都会被销毁.

还有一个好玩的例子:

int[] myArray = new int[] {1, 2, 3};
int[] another = myArray;
another[0] = 100;
Console.WriteLine(myArray[0]);       // 结果是100

其原理就是, myArray 为最开始创建的数组的对象引用, 我们又将这个引用赋值给 another, 那么 another 跟 myArray 的值一样, 指向我们最开始创建的对象. 即, 通过 another来对数组更改, 其实跟通过 myArray 来更改无异. 因为它们表示同一个对象.

13. 非托管对象

注意, 对象的内存是非常大的, 所以如果不好好管理, 很容易造成内存泄漏. 但对于 .NET 和 Java, 这个问题小了一点, 因为大部分类型都可以被 GC 给清理掉.

但是, 还是存在一些非托管对象的, 例如用户自己分配的内存, 以及 MemoryStream 之类的, 这些内存不会被 GC 清理掉, 用户需要自己管理, 一般这种包含非托管内存的类都会继承 IDisposable 接口, 用户可以调用它们的 Dispose 方法来清理掉这个对象的非托管内存.

2. 一些基本认知:

1. 静态与非静态成员访问:

1. 非静态成员可直接访问静态成员:

这个的话, 稍微想想就能理解. 例如一个厂子里刚造出一辆车, 这个车一看就知道是哪个牌子的, 所以可以轻易的找到这个厂子, 也就是说使用这个厂子里的零配件也是非常简单的, 即非静态成员(实例的成员)可直接访问静态成员(类的成员).

2. 静态成员无法直接访问为静态成员:

同样是汽车的例子, 这个车已经离开厂子了, 如果厂子要查看这个车的一些零配件损坏程度, 肯定是不大方便的, 因为你不知道这个车在哪, 即静态成员(类的成员)无法直接访问非静态成员(实例成员).

3. 通过对象引用来访问非静态成员:

静态成员来访问非静态成员, 肯定有办法访问, 例如这辆车在离开厂子前留下个联系方式, 这样厂子就可以找到这辆车. 下面的代码片是一个例子, 必须有对象引用才可以对其操作.

public class TestObj
{
    static List<TestObj> instances = new List<TestObj>();
    private SelfValue = 0;
    public TestObj()
    {
        instances.Add(this);
    }
    public int Value => SelfValue;
    public static ResetAllInstanceValue()
    {
        foreach (var ins in instances)
            instances.SelfValue = 0;
    }
}

喏, 这就是静态方法操作实例成员的示例. 只要你有这个对象的引用, 就可以对这个对象进行操作

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
  本书是一部独具特色的面向对象技术著作。书中结合代码示例生动透彻地讲述了面向对象思想的精髓,让读者真正学会以对象方式进行思考。此外,本书还讨论了各种与面向对象概念密切相关的应用主题,包括XML、UML建模语言、持久存储、分布式计算和客户/服务器技术等。   本书内容精炼,示例简单明了,适合各层次面向对象开发人员阅读,也是高校相关专业面向对象课程的理想教学参考书。 第1章 面向对象概念介绍 1 1.1 过程式程序设计与OO程序设计 2 1.2 从过程式开发转向面向对象开发 4 1.2.1 过程式程序设计 5 1.2.2 OO程序设计 5 1.3 对象到底是什么 5 1.3.1 对象数据 5 1.3.2 对象行为 6 1.4 类到底是什么 9 1.4.1 类是对象模板 9 1.4.2 属性 11 1.4.3 方法 11 1.4.4 消息 11 1.5 使用UML完成类图建模 12 1.6 封装和数据隐藏 12 1.6.1 接口 12 1.6.2 实现 13 1.6.3 接口/实现范型的一个实际例子 13 1.6.4 接口/实现范型的模型 14 1.7 继承 15 1.7.1 超类和子类 16 1.7.2 抽象 16 1.7.3 is-a关系 17 1.8 多态 18 1.9 组合 20 1.9.1 抽象 21 1.9.2 has-a关系 21 1.10 小结 21 1.11 本章使用的示例代码 21 1.11.1 TestPerson示例:C#.NET 21 1.11.2 TestPerson示例:VB.NET 22 1.11.3 TestShape示例:C#.NET 23 1.11.4 TestShape示例:VB.NET 25 第2章 如何以对象方式思考 27 2.1 了解接口与实现之间的区别 28 2.1.1 接口 29 2.1.2 实现 29 2.1.3 接口/实现示例 29 2.2 设计接口时使用抽象思维 33 2.3 尽可能为用户提供最小接口 35 2.3.1 确定用户 35 2.3.2 对象行为 36 2.3.3 环境约束 36 2.3.4 明确公共接口 36 2.3.5 明确实现 37 2.4 小结 37 2.5 参考书目 38 第3章 高级面向对象概念 39 3.1 构造函数 39 3.1.1 何时调用构造函数 39 3.1.2 构造函数中有什么 40 3.1.3 默认构造函数 40 3.1.4 使用多个构造函数 41 3.1.5 构造函数的设计 44 3.2 错误处理 44 3.2.1 忽略问题 45 3.2.2 检查问题并中止应用 45 3.2.3 检查问题并尝试恢复 45 3.2.4 抛出异常 45 3.3 作用域概念 47 3.3.1 局部属性 48 3.3.2 对象属性 49 3.3.3 类属性 50 3.4 操作符重载 51 3.5 多重继承 52 3.6 对象操作 53 3.7 小结 54 3.8 参考书目 54 3.9 本章使用的示例代码 54 3.9.1 TestNumber示例:C#.NET 54 3.9.2 TestNumber示例:VB.NET 55 第4章 类剖析 57 4.1 类名 57 4.2 注释 58 4.3 属性 59 4.4 构造函数 60 4.5 访问方法 62 4.6 公共接口方法 63 4.7 私有实现方法 64 4.8 小结 64 4.9 参考书目 64 4.10 本章使用的示例代码 65 4.10.1 TestCab示例:C#.NET 65 4.10.2 TestCab示例:VB.NET 66 第5章 类设计指导原则 68 5.1 真实世界系统建模 68 5.2 明确公共接口 69 5.2.1 最小公共接口 69 5.2.2 隐藏实现 69 5.3 设计健壮的构造函数(和析构函数) 70 5.4 在类中设计错误处理 70 5.4.1 对类建立文档以及使用注释 71 5.4.2 构建类要以合作为出发点 71 5.5 设计时充分考虑重用 72 5.6 设计时充分考虑到可扩展性 72 5.6.1 名字要有描述性 72 5.6.2 抽出不可移植的代码 72 5.6.3 提供一种复制和比较对象的方法 73 5.6.4 让作用域尽可能小 73 5.6.5 类应当对自己负责 74 5.7 设计时充分考虑可维护性 75 5.7.1 使用迭代 76 5.7.2 测试接口 76 5.8 使用对象持久性 78 5.9 小结 79 5.10 参考书目 79 5.11 本章使用的示例代码 79 5.11.1 TestMath示例:C#.NET 79 5.11.2 TestMath示例:VB.NET 80 第6章 利用对象实现设计 81 6.1 设计指导原则 81 6.1.1 完成适当的分析 84 6.1.2 建立工作陈述 84 6.1.3 收集需求 84 6.1.4 开发用户界面的原型 85 6.1.5 明确类 85 6.1.6 确定各个类的职责 85 6.1.7 确定类如何相互合作 85 6.1.8 创建类模型来描述系统 85 6.2 案例研究:blackjack示例 86 6.2.1 使用CRC卡 87 6.2.2 明确blackjack类 88 6.2.3 明确类的职责 90 6.2.4 UML用例:明确协作关系 95 6.2.5 第一轮CRC卡 98 6.2.6 UML类图:对象模型 99 6.2.7 建立用户界面原型 100 6.3 小结 101 6.4 参考书目 101 第7章 掌握继承和组合 102 7.1 重用对象 102 7.2 继承 103 7.2.1 一般化和特殊化 105 7.2.2 设计决策 105 7.3 组合 107 7.4 为什么封装在OO中如此重要 109 7.4.1 继承如何削弱封装 109 7.4.2 多态的一个详细例子 111 7.4.3 对象职责 111 7.5 小结 115 7.6 参考书目 115 7.7 本章使用的示例代码 115 7.7.1 TestShape示例:C#.NET 115 7.7.2 TestShape示例:VB.NET 117 第8章 框架与重用:使用接口和抽象类实现设计 119 8.1 代码:重用还是不重用 119 8.2 什么是框架 119 8.3 什么是契约 121 8.3.1 抽象类 122 8.3.2 接口 124 8.3.3 集成 125 8.3.4 编译器的证明 127 8.3.5 建立契约 128 8.3.6 系统插入点 130 8.4 一个电子商务例子 130 8.4.1 电子商务问题 130 8.4.2 非重用的方法 131 8.4.3 一个电子商务解决方案 133 8.4.4 UML对象模型 133 8.5 小结 137 8.6 参考书目 137 8.7 本章使用的示例代码 138 8.7.1 TestShape示例:C#.NET 138 8.7.2 TestShape示例:VB.NET 140 第9章 构建对象 143 9.1 组合关系 143 9.2 分阶段构建 144 9.3 不同类型的组合 146 9.3.1 聚集 146 9.3.2 关联 146 9.3.3 结合使用关联和聚集 148 9.4 避免依赖性 148 9.5 基数 149 9.5.1 多个对象关联 151 9.5.2 可选关联 151 9.6 集成示例 152 9.7 小结 152 9.8 参考书目 153 第10章 用UML创建对象模型 154 10.1 什么是UML 154 10.2 类图的结构 155 10.3 属性和方法 156 10.3.1 属性 156 10.3.2 方法 157 10.4 访问指示 157 10.5 继承 158 10.6 接口 159 10.7 组合 160 10.7.1 聚集 160 10.7.2 关联 161 10.8 基数 161 10.9 小结 162 10.10 参考书目 163 第11章 对象和可移植数据:XML 164 11.1 可移植数据 164 11.2 XML 165 11.3 XML与HTML 166 11.4 XML和面向对象语言 166 11.5 两个公司间共享数据 167 11.6 用DTD验证文档 168 11.7 将DTD集成到XML文档 170 11.8 使用层叠样式表 175 11.9 小结 177 11.10 参考书目 177 第12章 持久对象:串行化和关系数据库 178 12.1 持久对象基础 178 12.2 将对象保存到平面文件 179 12.2.1 串行化文件 180 12.2.2 再谈实现和接口 182 12.2.3 方法如何保存 183 12.3 串行化过程中使用XML 183 12.4 写到关系数据库 186 12.5 加载驱动程序 189 12.5.1 建立连接 189 12.5.2 SQL语句 190 12.6 小结 192 12.7 参考书目 192 12.8 本章使用的示例代码 192 12.8.1 Person类示例:C#.NET 193 12.8.2 Person类示例:VB.NET 195 第13章 对象与因特网 197 13.1 分布式计算的演进 197 13.2 基于对象的脚本语言 197 13.3 JavaScript验证示例 200 13.4 Web页面中的对象 202 13.4.1 JavaScript对象 202 13.4.2 Web页面控件 204 13.4.3 声音播放器 205 13.4.4 电影播放器 205 13.4.5 Flash 206 13.5 分布式对象和企业 206 13.5.1 公共对象请求代理体系结构(CORBA) 207 13.5.2 Web服务定义 210 13.5.3 Web服务代码 213 13.5.4 Invoice.cs 214 13.5.5 Invoice.vb 215 13.6 小结 216 13.7 参考书目 216 第14章 对象和客户/服务器应用 217 14.1 客户/服务器方法 217 14.2 专有方法 217 14.2.1 串行化对象代码 218 14.2.2 客户代码 218 14.2.3 服务器代码 220 14.2.4 运行专有的客户/服务器示例 222 14.3 非专有方法 223 14.3.1 对象定义代码 223 14.3.2 客户代码 224 14.3.3 服务器代码 225 14.3.4 运行非专有的客户/服务器示例 227 14.4 小结 228 14.5 参考书目 228 14.6 本章使用的示例代码 228 14.6.1 客户/服务器示例——VB.NET:对象定义代码 228 14.6.2 客户/服务器示例——VB.NET:客户代码 229 14.6.3 客户/服务器示例——VB.NET:服务器代码 230 第15章 设计模式 232 15.1 为什么研究设计模式 232 15.2 Smalltalk的模型/视图/控制器 233 15.3 设计模式的不同类型 234 15.3.1 创建型模式 235 15.3.2 结构型模式 239 15.3.3 行为型模式 241 15.4 反模式 242 15.5 小结 243 15.6 参考书目 243 15.7 本章使用的示例代码 243 15.7.1 C#.NET 244 15.7.2 VB.NET 247 索引 250
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值