数据抽象——对象与类
一、数据抽象与封装
1.结构体存在的问题
- 必需知道数据的表示,数据表示发生变化将影响操作,不安全(内存溢出、数组越界),不易维护,无扩展能力(公开的易产生耦合关系)。
- 通过过程抽象操作数据:数据类型的定义与操作的定义是分开的,二者之间没有显式的联系,数据表示仍然是公开的,可以不通过函数来操作数据。
2.面向对象设计
数据抽象:数据的使用者只需要知道对数据所能实施的操作以及这些操作之间的关系,而不必知道数据的具体表示。
数据封装:指把数据及其操作作为一个整体来进行描述。数据的具体表示对使用者是不可见的,对数据的访问只能通过封装体所提供的对外接口(操作)来完成。
数据抽象与封装是面向对象程序设计的基础。 抽象数据类型:ADT。
栈封装实例:
//新关键字:class
//注意分号
const int STACK_SIZE=100;
class Stack
{
private:
int top;
int buffer[STACK_SIZE];
public:
Stack() { top = -1; }
bool push(int i);
bool pop(int &i);
};
bool Stack::push(int i)
{
if (top == STACK_SIZE-1)
{
cout << “Stack is overflow.\n”;
return false;
}
else
{
top++; buffer[top] = i;
return true;
}
}
bool Stack::pop(int &i)
{
if (top == -1)
{
cout << “Stack is empty.\n”;
return false;
}
else
{
i = buffer[top]; top--;
return true;
}
}
Stack st; // 会自动地去调用st.Stack()对st进行初始化。
int x;
st.push(12); // 把12放进栈st。 push(st,12);
st.pop(x); // 把栈顶元素退栈并存入变量x。
st.top = -1; // Error 外界无法访问数据,只能通过对象访问操作
st.top++; // Error 数据仅对象自己能够访问
st.buffer[st.top] = 12; // Error 可访问性可以控制
由于数据封装对外界屏蔽实现细节(数据、实现细节),因此可在对Stack依赖的模块无需改动的情况下,改动Stack内部实现。
3.为什么要封装
提高软件开发效率和保证软件质量的几个基本的程序设计手段: 抽象(复杂度控制)、封装(信息隐蔽)、模块化(组织大型程序)、软件复用、可维护性。
二、类
1.类(Class)
对象是由数据(数据成员、成员变量、实例变量、对象的局部变量等)及能对其实施的操作(成员函数、方法、消息处理过程等)所构成的封装体,它属于值的范畴。
类描述了对象的特征(数据和操作),它属于类型的范畴(对象的类型) 。
对象构成了面向对象程序的基本计算单位,而对象的特征则由相应的类来描述。类是对象的集合。
C++的类是一种用户自定义类型,定义形式如下:
class <类名> { <成员描述> } ;
其中,类的成员包括:数据成员(成员变量、属性)、成员函数(操作、消息响应机制),类成员标识符的作用域为整个类定义范围。
class Date
{ public:
void set(int y, int m, int d) //成员函数
{ year = y;
month = m;
day = d;
}
bool is_leap_year() //成员函数
{ return (year%4 == 0 && year%100 != 0) ||
(year%400==0);
}
private:
void print() //成员函数
{ cout << year << "." << month << "." <<day;
}
private:
int year,month,day; //数据成员
};
2.数据成员
数据成员指类的对象所包含的数据,它们可以是常量和变量。数据成员的说明格式与非成员数据的声明格式相同,例如:
class Date //类定义
{
......
private: //访问控制说明
int year,month,day; //数据成员说明
};
说明数据成员时不允许进行初始化(某些静态数据成员除外)。例如:
class A
{
int x=0; //Error
const double y=0.0; //Error
......
};
数据成员的类型可以是任意的C++类型(void除外)。在说明一个数据成员的类型时,如果未见到相应的类型定义或相应的类型未定义完,则该数据成员的类型只能是这些类型的指针或引用类型(静态成员除外)。例如:
class A; //A是在程序其它地方定义的类,这里是声明。
class B
{ A a; //Error,未见A的定义。
B b; //Error,B还未定义完。
A *p; //OK
B *q; //OK
A &aa; //OK
B &bb; //OK
};
注意,生成对象时要预先分配内存,故:
A.h中:
class A{private:B b;}
B.h中:
class B{private:A a;}//报错
解决方案:
不方便给出A或B的定义,A a或B b其中之一改为A* a或B*b(指针永远为4个字节)
3.成员函数
成员函数描述了对类定义中的数据成员所能实施的操作。成员函数的定义可以放在类定义中,例如:
class A
{
...
(inline) void f() {...} //默认inline,建议编译器按内联函数处理。
};
成员函数的定义也可以放在类定义外,增强可读性。一般放在cpp文件中,但不能再为inline。例如:
class A
{
...
void f(); //声明
};
void A::f() { ... } //需要用类名受限,区别于全局函数。
类成员函数名是可以重载的(析构函数除外),它遵循一般函数名的重载规则。
4.类成员的访问控制
在C++的类定义中,可以用访问控制修饰符public,private或protected来描述对类成员的访问限制。 例如:
class A
{
public:
int x; //访问不受限制。
void f();
private: //只能在本类和友元的代码中访问,成员一般私有。
int y;
void g();
protected: //只能在本类、派生类和友元的代码中访问(继承中用到)。
int z;
void h();
};
- public函数决定类的功能,private函数供自己调用。
- public、private可多处使用,不写默认private(struct默认public)。
- 一般来说,类的数据成员和在类的内部使用的成员函数应该指定为private,只有提供给外界使用的成员函数才指定为public。
- 具有public访问控制的成员构成了类与外界的一种接口(interface)。操作一个类的对象时,只能通过访问对象类中的public成员来实现。
- protected类成员访问控制具有特殊的作用(继承)。