从结构体走向对象
本文能够让你大致理解面向对象的一些概念, 但是以下一切内容均不适用于参加OO课程的考试, 如果爆零, 后果自负 .
部分语法的介绍
C++语言中, 定义结构体变量时, 类型中的struct可以省略, 如"struct 结构体名 变量名"可以简写为"结构体名 变量名 ". 以下均为简写形式.
Java语言中, 所有内置的大写开头的类型 都是类, 所有对象都是"引用变量" , 可以理解为不能做运算的指针 .
(指针运算指, 数组首项地址+1等于第二项地址的这种运算.)
如: 1 2 3 4 5 6 int a = 0 ;Integer b = 0 ;Integer c = 0 ;
上面的代码可以简单地理解为, a是一个int类型变量, 内容是0.
b是一个指向Integer变量的指针, 他指向的值是0.
c同理.
因此, b==c 判断的是b和c是否指向同一个对象. 判断b和c相等应该用b.equals(c).
这篇推送只讲概念, 不需要完全理解代码是什么意思, 具体语法部分都做了注释. 之后大概 还会有推送来讲语法. 如果那里有代码不明白什么意思欢迎联系我或者后台给我留言.
类
啥是类? 最简单的来说, 就是 C 语言中的结构体.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 public class Student { String name; int ID; }
在最开始非面向对象的编程中, 我们经常会用多个变量来描述同一对象的多个属性: 比如定义一个名字变量, 一个学号变量. 后来, 随着计算机科学的发展, 编程语言中支持了 结构体 这样一个特性, 能够让我们把 描述同一个东西不同属性 的多个变量统一管理. 结构体这一个概念, 到了面向对象的语言中, 结构体发展为了类的概念.
可以把类理解为C语言中的结构体+方法 .
方法
在很多情况下, 我们之前编写的一些函数是针对某一个结构体 提供的. 比如, 链表的删除某个节点的函数就可以认为是针对某个节点定义的, 效果是删除这个节点后面的一个节点 .
我们可以把这样的函数定义为一个全局的函数, 把一个结构体变量作为参数传入这个函数:
1 2 3 node* remove (node *now) { ... }
但是, 如果这样自定义我们就会在别的任何地方都没法再次定义一个 remove 函数了. 这个只针对 node* 的函数占据了一个全局的名字 (或者说, 符号). 无疑, 这样的实现非常 不优雅 . 因此, 我们更应该把进和 node* 这一个变量相关的函数定义成一个 node* 的 成员函数 , 也叫 方法 .
1 2 3 4 5 6 7 8 9 10 11 struct node { int data; node *; node* remove () { return this ->next; } };
如果这样定义remove函数, 就不会污染全局的作用域, 同时也不用我们显示的声明参数, 只需要如下调用:
即可自动把 a 或 a的地址 作为 this指针 参数传入给 remove 函数.
因此, 方法就是针对某一个类的函数 , 为了方便我们把它放到类里面 . 构造一个对象的函数是一个特殊的成员函数, 叫做类的构造函数.
继承
其实, 编程语言演进的过程就是一代代程序程序员偷懒的过程 . 自从有了结构体的方法这东西之后, 人们就在想如果好几个结构体都有相同的方法, 能不能直接重复用一下?
比如对于如下几个结构体:
1 2 3 4 5 6 7 8 struct shirt { double price = 9.15 ; void printPrice () ; }; struct trousers { double price = 2.33 ; void printPrice () ; };
这两个结构体都分别有一个printPrice函数, 内容也都是一样的, 但是要写两次, 非常的不优雅 , 于是人们引入了继承的概念:
May there be inheritance!
1 2 3 4 5 6 7 8 9 10 11 12 13 14 class Clothes { double price; void printPrice () { } } class Shirt extends Clothes { double price = 9.15 ; } class Trousers extends Clothes { double price = 2.33 ; }
这样, printPrice 函数只需要写一次. 从某个类继承, 或者说派生出来的类, 会拥有父类的所有属性以及方法. 子类也可以对应的重写这些方法.
同时, 对于以上的代码中, 显然 Clothes 类和别的类有一个很大的区别. 我们会创建一个 Shirt 类的对象, 但是我们不会执行 new Clothes(). Clothes 类存在的意义在于提供给别人继承, 我们不会实例化这个类, 因此我们把这个类定义为抽象类(abstract class) :
1 2 3 4 5 6 7 abstract class Clothes { double price; void printPrice () { System.out.println(this .price); } }
一个抽象类的意义是提供给别人来继承. 代码中的 abstract 标记了这是一个抽象类.
多态
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 abstract class Animal { void bark () { System.out.println("bark" ); } } class Dog extends Animal { void bark () { System.out.println("Woof" ); } } class Cat extends Animal { void bark () { System.out.println("Meow~" ); } }
上述代码中, Animal类规定了继承他的类要实现 bark() 方法. 那么, 对于如下的代码, 会输出什么呢?
1 2 3 4 5 Animal a = new Dog ();a.bark();
上面说到, Java中类的变量都是引用变量, 因此上面代码可以不严谨的理解为: 定义一个 指向Animal类型的指针 a,
在这里, a 是一个Animal类型的引用, 指向一个Dog对象. 但是, 尽管他是 Animal 类型的引用, 调用 a.bark()时仍然调用的是 Dog 的 bark() 方法.
多态, 狭义上指同一个名字(符号)指代多个物体. 在上面代码中, 如果不知道a指向的类型是什么, 调用 a.bark() 有三种可能的情况. 这就利用了多态的性质.
例子以及面向对象的好处.
在C语言中, 由于没有这些特性, 极大地存在着代码冗余重复的现象, 如: printf 函数用于格式化并向控制台输出内容, sprintf 用于格式化并向字符串写入内容, fprintf 用于格式化并向文件写入内容.
上面的三个函数, 都完成了格式化这一个步骤, 但是代码被编写了三次. 如果需要向网络连接中格式化并写入内容, 则又需要重复一遍格式化的操作. 面向对象就能很好地解决这个问题.
如, 如果我们要向一个文件写入内容:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 PrintWriter out = new PrintWriter ( new BufferedWriter ( new OutputStreamWriter ( new FileOutputStream ("filename.txt" ) ) ) );
上面代码中, 几个类的构造函数分别为:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 FileOutputStream(String name) OutputStreamWriter(OutputStream out) BufferedWriter(Writer out) PrintWriter(Writer out)
可以看到, FileOutputStream 接受文件名作为参数, 单纯负责向文件中写入内容.
OutputStreamWriter 接受任何 OutputStream 对象, 用于转换写入内容的编码.
BufferedWriter 接受一个 Writer, 用于缓存即将写入 Writer 的内容.
PrintWriter 接受一个 Writer, 用于格式化输入, 写入 Writer 中.
高内聚, 低耦合的设计模式就体现在了这里: 一个 OutputStream 类只需要实现 write 方法, 只能写入二进制字节数据, 而一个 Writer 负责处理编码问题, 可以写入字符串, 负责把写入的字符串编码为二进制字节数据, 而 BufferedWriter 则负责缓存上层写入的内容, 也同样只提供了写入字符串的方法. PrintWriter 负责提供格式化的方法, 可以格式化并写入 int double char string 等多种类型.
这样的设计, 使得功能的拓展变得很方便, 比如我们要向一个自定义的东西中写入数据, 完全可以只实现一个 OutputStream 而复用 OutputStreamWriter, BufferedWriter, PrintWriter 等很多和写入数据有关的类.
所以, 这样的实际模式大概是被不断加需求改需求的产品经理逼出来的
感谢 ☁️学长,
(如果可以的话, 能关注一下这个公众号吗)