nju

1. 抽象和封装

  • 抽象: 数据的使用者只需要知道数据能实施的操作以及这些操作之间的关系,不必知道数据的具体表示形式。
  • 封装:将数据及其操作作为一个整体来进行实现。数据的具体表示对使用者是不可见的,对数据的访问只能通过封装体所提供的对外接口来完成。
  • 案例
    比如,栈的使用,首先不能直接在程序中定义,一不小心手误就会出错;其次,采用面向过程的想法:
    struct Stack{}; void push(Stack &s);
    当我在其他地方定义一个void f(Stack &s), 会破坏Stack的数据;数据表示仍是公开的,可以对数据进行操作。

所以引入面向对象。
public对外的接口, private隐藏的内容,外不可见
protect面向继承

把程序构造成由若干个对象组成,每个对象是由一些数据以及对这些数据所能实施的操作构成的封装体。
一个雷所描述的对象的特征可以继承。

这里特征指private中的成员。对象所定义的操作即是其接口

2. 面向对象程序的执行过程

对象构成了面向对象程序的基本计算单位
对象间的消息传递是引起面向对象程序进行计算的唯一形式。

从程序外部向程序中的某个对象发送第一条消息标志着程序的开始。该对象在处理这条消息的过程中又向其他对象发送消息,引起进一步的计算。当第一条消息处理结束后则程序的一次计算结束。消息处理可以分为同步和异步,这里显然只涉及到同步。

3. 对比

提高软件开发效率和保证软件质量的几个基本程序设计手段

  • 抽象(复杂度控制)
  • 封装(信息隐藏)
  • 模块化(组织大型程序)
  • 软件复用(延长开发周期)
  • 可维护性(延长软件寿命)
  • 软件模型的自然渡(缩小解题空间与问题空间的语义间隙)

    最后一个方面可以思考到,实际上求解问题都可以划分各个状态。即通过状态空间来解题,所以可以看出来基于对象/类的解题方式与问题空间有很好的对应。

面向过程是什么?强调的是过程即功能,数据和操作分离,数据没有保护,功能特定限定不可扩展。最后,与问题空间没啥关系。
最大的高手是能说出一堆东西还能找到准确的分类。

4. 面向对象的基本内容

对象-值,类-类型。
多态:某一论域中的一个元素存在多种解释:

  • 一名多用:函数名、操作符重载
  • 类属性:
    • 一函数可以对多种类型数据进行操作?
    • 一个类型可以描述多种类型的数据

绑定:确定对多态元素的某个使用是多态元素的哪种形式?

  • 静态绑定
  • 动态绑定

面向对象程序特有的多态(继承)

  1. 对象类型的多态: 子类对象可以属于子类,也可以属于父类
  2. 对象标识的多态:父类的引用或指针可以引用或指向父亲对象,也可子类对象
  3. 消息的多态:发给的消息可以发给子类,即,一个消息可以有多种解释(父类与子类有不同解释)

多态带来的好处:

  • 易于实现程序高层代码的复用 ?
  • 使得程序扩充变得容易(只要增加底层的具体实现)
  • 增强语言的可扩充性(操作符重载)

对象构成了面向对象的基本计算单位,而对象的特征则是由相应的类来描述。因此,程序中首先要定义类。
注意:在C++中,也允许在结构和联合中定义函数,但成员的访问控制与类不同。

结构和联合的成员默认为public,是否可以改我不知道?

访问控制就是public,private,protected.

public:公开
private:本类和友元的代码中访问,默认方式
protected:只能在本类、派生类和友元的代码中访问

友元函数什么意思?

访问控制设置的建议

  1. 类的数据成员和在类的内部使用的成员函数应该指定为private,只有提供给外界使用的成员函数才指定为public. 具有public访问控制的成员构成了类与外界的一种接口,在一个类的外部只能访问该类接口中的成员

数据类型定义时的注意

  1. 在说明一个数据成员的类型时,如果未见到相应类型的定义,或相应的类型未定义完,则该数据成员的类型只能是这些类型的指针或引用类型。

    因为实际类型是无法解释代码的。而指针类型是现在符号表中定义,然后再寻找答案的。

  2. 成员函数的实现可在内外定义,在内外定义要用类名受限,区别于全局函数。

  3. 类成员函数名是可以重载的,它遵循一般函数名的重载规则。

对象

对象是动态运行中构造,类型是静态的形式。

对象可以分为全局对象、局部对象和成员对象?


对象创建的两种方式

  • 直接,实体变量,使用实体,在静态区或栈中

    静态区好像不可能? 前面不说了吗?是这个意思吗?

  • 间接,动态,使用指针
    在堆中
    1
    2
    3
    4
    5
    6
    7
    8
    9
    A *p;
    p=new A; //创建
    *p, p->
    delete p;

    A *q;
    q=new A[100];
    q[i] // *(q+i)
    delete []q;

  • 使用参数进行赋值
    同类型一样,同类对象可以进行赋值,取对象地址之类的事情
    Data b;

  • 使用对象作为参数
    使用对象做实参,不想函数在调用是创建一个大的元素又不想被修改
    const Data &d

    感觉上参数实际上就是一个地址

  • 使用对象作为返回值

    务必注意一个事,如果想返回的是一个新对象返回值用Data;否则容易把参数中,原本函数中的值给改变。最重要的是自己到底是想还是不想被改变

  • 每个对象,this指针

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    // 面向对象编程
    class A{
    public:
    void g(int i){
    x=i;
    // 相当于this.x=i;
    }
    private:
    int x;
    }

    // 面向过程编程
    struct A{
    int x;
    }
    void g(A *const this,int i){
    this->x=i;
    }

    a.g(1); 等价于 g(&a,1);

突然想到了,Python中编程,当初想要将 pub(x,y)变为x(y)的过程? 具体怎么做还得看一下相关书上的东西。


  • 对象初始化
    特有的东西,init(), 自动被调用,很好!
    可重载
    动态数组 默认 A();
    常量和引用不能被初始化,为什么?

    一旦被赋值了,那对象还要它干什么

初试话顺序按类定义顺序,很显然

在构造函数中开辟的空间需要自己在析构函数中解决,特别是动态申请的空间。

  • 成员对象
    数据成员可以是另一个类
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    class A
    { int x;
    public:
    A() { x = 0; }
    A(int i) { x = i; }
    };
    class B
    { A a;
    int y;
    public:
    B() { y = 0;} //调用A的默认构造函数对a初始化。
    B(int i) { y = i; } //调用A的默认构造函数对a初始化。
    B(int i, int j): a(j) { y = i; } //调用A(int)对a 初始化。
    };
    B b0; //b0.y初始化为0,b0.a.x初始化为0
    B b1(1); //b1.y初始化为1,b1.a.x初始化为0
    B b2(1,2); //b2.y初始化为1,b2.a.x初始化为2

析构的顺序跟执行顺序相反。

容易想到,析构函数 先调用本身类,再调用成员对象
应用场景:复杂的窗口调用关系时

  • 拷贝构造函数
    A(const A&a){}

    正如其名,在需要拷贝,即值传递的时候用到
    比如说,构造函数就自定义的,那么拷贝构造函数?
    ?默认构造函数是什么?-> 隐式拷贝构造函数

对于隐式拷贝构造函数:

  1. 非对象成员逐个赋值
  2. 对象成员优先选择构造函数,否则隐式

    这里注意,当成员构造函数中申请了一片空间,可能会同时指向一片空间

p=new int[5]; 这样会指向一个空间吗?首先,要看它的第一步动作,如果是

对于自定义拷贝构造函数,注意成员对象默认构造函数,否则显式指定

这里比较麻烦,

  1. 定义对象
    A a,b; b=a;
  2. 对象作为值参数和返回值
  • const

void f(const Date& d){
d.get_day(); //不可以,无法知道实际上是否进行了数据成员改变,所以统一禁止
}

因此,将类的成员函数分为两类:

  • 获取对象状态
    int get_day() const {}; //不能改变数据成员的值, 这下f中d.get_day()ok了
  • 改变对象状态

  • 同类对象共享数据(全局变量不行,不安全!)

    问题如何引出? static还有什么用?

采用静态对象,类直接可以获取。static int x; void f() {x++; }
// 工厂模式,进行计数, 类可直接访问,只有一个备份
应用:实现对某类对象的计数

  • 静态成员函数
    静态成员函数只能访问类的静态成员。
    静态成员函数没有隐藏的this

    静态都可以通过对象和类两种方式来访问

在一些“纯”面向对象程序设计语言中,把类看作对象,用元类(meta class)来描述.
静态成员属于“类对象”

  • 友元

    暴露的一个超级大的接口,为了应对其他方面的不足。
    目的是为了提高在类的外部对类的数据成员的访问效率。

友元可以是全局函数、其它的类或其它类的某些成员函数。
class A
{
? // 感觉是public
friend void func();
friend class B;
friend void C::f();
private:
int x;
}

友元关系具有不对称性。
友元也不具有传递性
友元是数据保护和数据访问效率之间的一种折中方案

todo
用类来实现矩阵和向量类型
multipy的实现

类作为模块

  • 类是一个自然的模块划分单位
  • .h文件中存放的是类的定义
  • .cpp文件中是类成员函数的实现

一个模块一般有两个文件

耦合性:模块与模块之间的依赖关系

过程: 结构式

面向对象:降低耦合

Demeter法则,一个类的成员函数除了访问自身类结构的直接子结构外,不能以任何方式依赖于任何其他类的结构。
每个成员函数只应向某个有限集合中的对象发送消息。
仅与你的直接朋友联系。