C++ note (2)
Eiaton

数据抽象和类 Ⅰ

基本概念

抽象

Data Abstraction:只关心该数据“是什么”“如何使用”,而不关心其如何运作。
Control Abstraction:只关心该行为能实现什么,而不关心其具体实现方法。

抽象数据类型

在程序中,称被抽象的数据,为抽象数据类型(Abstract Data Type, ADT)

一种ADT应具有:

  1. 说明部分:描述数据值的特征和作用于这些数据之上的操作,用户仅需明白其说明,无需知晓内部实现。
  2. 实现部分。

抽象数据类型转化

DATE设计为一种数据类型。

  • 内部包含年月日等数据以及在这些数据上可进行的操作。
  • 用户利用DATE就可以定义多个变量。
  • 用户可调用每个变量中公开的操作,但无法直接访问每个变量中被隐藏的内部数据。
  • 用户也无需关心变量中各操作的具体实现。
  • 于是DATE就是一种封装好的数据类型。这就达到了信息隐藏和封装的目的。

结构体 类 对象

C 中的结构体

C 数组允许定义可存储相同类型数据项的变量,结构是 C 编程中另一种用户自定义的可用的数据类型,它允许您存储不同类型的数据项。
结构用于表示一条记录,我们继续以上面的日期为例,我们可能会关心:

  • Year
  • Month
  • Day

但C语言中的 struct 只能包含变量,
不能包括实现ADT当中操作的函数

1
2
3
4
5
6
7
8
9
10
11
12
13
struct Date {
int year;
int month;
int date;
} date;

void set (int, int, int);
int getYear();
int getMonth();
int getDay();
void print();
void increment();
void decrement();

C++ 对象&类

新增面向对象编程

1
2
3
4
5
class classname {
Access specifiers:
Date members/variables;//变量
Member fn() {}//方法
};//分号结束一个类

类的示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Date {

private:
int year;
int month;
int day;

public:
void set(int, int, int);
int flag;
int getYear() const;
int getMonth() const;
int getDay() const;
void print() const;
void increment();
void decrement();
};

{}中列出类的成员。类的成员包括:

  • 数据成员:一般说来,数据成员是需要隐藏的对象;即外部的程序是不能直接访问这些数据的,应该通过函数成员来访问这些数据。所以一般情况下,数据成员通过关键字 private 声明为私有成员(private member)。
  • 函数成员:通过关键字 public 声明为公有成员(public member)。外部程序可以访问共有成员,但无法访问私有成员。
  • 对于类的使用者(用户代码),只需要获得DATE.h,即可调用类对象的公有函数访问其内部的数据成员。使用者无法直接访问私有成员,无需知晓公有函数的内部实现。

结构体 v.s. 类

Cstruct 只能包含变量,而 C++class 还可以包含函数。set() 是用来处理成员变量的函数,在 C 中,我们将它放在了 struct Date 外部,和成员变量是分离的;而在 C++ 中,我们将它放在了 class Date 内部,使它和成员变量聚集在一起,看起来更像一个整体。

对象

通过结构体定义出来的变量传统上叫变量,而通过类定义出来的变量有了新的名称,叫做对象(Object)。

1
Date date;

成员函数

类的成员函数指,把定义和原型写在类定义内部的函数,就像类定义中的变量一样。

类成员函数是类的一个成员,它可以操作类的任意对象,可以访问对象中的所有成员。

成员函数可以定义在类定义内部,或者单独使用范围解析运算符 :: 来定义。在类定义中定义的成员函数把函数声明为内联的,即便没有使用 inline 标识符。

1
2
3
4
5
6
7
8
9
10
class Date {

public:
void set(int y, int m, int d) {
year = y;
month = m;
day = d;
}
......
};
1
2
3
4
5
6
7
8
9
10
11
12
class Date {

public:
void set(int, int, int);
......
};

void Date::set(int y, int m, int d) {
year = y;
month = m;
day = d;
}

预处理命令

在 DATE.cpp 文件开头需要加入预处理命令

#include "DATE.hpp"

这是因为在 DATE.cpp 中要用到用户自定义的标识符 DATE ,而它的定义在 DATE.hpp 中。

在 DATE.hpp 中,各函数原型是在 {} 中的。根据标识符的作用域规则,它们的作用范围仅在类定义中,而不包括 DATE.cpp 。因此在 DATE.cpp 中需要利用作用域解释运算符 :: 来指明这里的函数是类 DATE 里的成员函数。

DATE.cpp 中有时还包括 DATE 内部要使用到的函数,例如daysInMonth。

  • 这种函数并非对外公开供用户使用,因此可以将其声明为私有成员。

  • 若在该函数中没有涉及该类的数据成员,则无需将它们声明为类的成员。

调用成员函数和成员变量是在对象上使用点运算符(.),这样它就能操作与该对象相关的数据

1
2
date.flag = 1;
date.set(2022,1,30);

类的静态成员

静态(static)成员是类的组成部分但不是任何对象的组成部分

  • 通过在成员声明前加上保留字 static 将成员设为static(
    • 在数据成员的类型前加保留字 static 声明静态数据成员;
    • 在成员函数的返回类型前加保留字 static 声明静态成员函数)
  • static成员遵循正常的公有/私有访问规则。
  • C++ 程序中,如果访问控制允许的话,可在类作用域外直接(不通过对象)访问静态成员(需加上类名和 ::
  • 静态数据成员具有静态生存期,是类的所有对象共享的存储空间,是整个类的所有对象的属性,而不是某个对象的属性。
  • 与非静态数据成员不同,静态数据成员不是通过构造函数进行初始化,而是必须在类定义体的外部再定义一次,且恰好一次,通常是在类的实现文件中再声明一次,而且此时不能再用 static 修饰。
  • 静态成员函数不属于任何对象
  • 静态成员函数没有 this 指针
  • 静态成员函数不能直接访问类的非静态数据成员,只能直接访问类的静态数据成员
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    //date.hpp
    class Date {

    private:
    static int count;
    ...

    public:
    static void getCount();
    ...
    };
    1
    2
    3
    4
    5
    6
    7
    8
    //date.cpp

    int Date::count = 0;
    //必须在类外定义体的外部再定义一次

    void Date::getCount() {
    return count;
    }