类和对象
一、过程性编程和面向对象编程概述
-
面向过程:面向过程会把事情拆分成: 一个个函数和数据(用于方法的参数) 。然后按照一定的顺序,执行完这些方法(每个方法看作一个过程),等方法执行完就完成了,比较注重过程。
-
面向对象:面向对象会把事物抽象成对象的概念,就是说这个问题里面有哪些对象,然后给对象赋一些属性和方法,然后让每个对象去执行自己的方法,然后解决问题。比较注重对象。三大基本特性:封装,继承,多态。
1.接口
接口是个共享框架,供两个系统交换使用;
-
如用户可能是您,而程序可能是字符处理器。使用字符处理器。不能直接将词传到计算机内存中,必须同程序提供的接口交互。敲打键盘字符显示到计算机屏幕上,点击鼠标时,计算机对输入的段落进行处理。程序接口将您的意图转换为存储在计算机中的具体信息。
-
类的公共接口,公众(public)是使用类的程序,交互系统由类对象组成,而接口由编写类的人提供的方法组成。接口让程序员能够编写与类对象交换的代码,从而让程序能够使用类对象。
-
例如:要计算string对象中包含多少个字符,无需打开对象,而只需用string类提供的
size()
方法。类设计禁止公共用户直接访问,但公众可以使用方法size()
。方法size()
是用户和string类对象之间得公共接口的组成部分。 -
方法
getline()
是istream类的公共接口的组成部分,使用cin的程序不是直接与cin对象内部交换读取一行输入,而是使用getline()
。
二、类概念
1.概述
类是一种将抽象转换为用户定义类型的c++工具,它将数据表示和操纵数据的方法组合成一个整洁的包。
2.定义类
类是用户自定义的类型,如果程序中要用到类,必须提前说明,或者使用已存在的类(别人写好的类、标准库中的类等),C++语法本身并不提供现成的类的名称、结构和内容。首先了解下面的知识点:
class默认是私有的, 数据私有 、方法公有 ,用户就可以借助 公有方法 间接的操作私有数据。
例:
#include <iostream>
using namespace std;
class Person
{
private:
int m_mony;
protected:
int m_age;
public:
void show()
{
m_mony=100;
m_age=12;
cout<<"年龄:"<<m_age<<",金钱:"<<m_mony<<endl;
}
};
int main()
{
//用类 去 实例化 一个对象(就是用Person定义一个变量)
Person lucy;
//cout<<"钱:"<<lucy.m_money<<endl;//err 内的外部不可访问
//cout<<"年龄:"<<lucy.m_age<<endl;//err 内的外部不可访问
lucy.show();//ok 公有的类的外部可用访问
//private protected虽然是私有、保护的 类外不可访问 但是用户可以借助 public公 有的方法
//间接的访问私有、保护的数据
}
3.访问类的成员
方法一:
使用点号(.)
来访问成员变量和成员函数:
#include <iostream>
using namespace std;
class Person
{
public:
char *name;
int age;
float score;
void showPerson()
{
cout<<name<<"年龄:"<<age<<",成绩:"<<score<<endl;
}
};
int main()
{
Person ob;//创建对象,通过点(.)访问类成员
ob.name="小明";
ob.age=12;
ob.score=99.9;
ob.showPerson();
return 0;
}
方法二:
使用对象指针,有了对象指针后,可以通过箭头->
来访问对象的成员变量和成员函数。
#include <iostream>
using namespace std;
class Person
{
public:
char *name;
int age;
float score;
void showPerson()
{
cout<<name<<"年龄:"<<age<<",成绩:"<<score<<endl;
}
};
int main()
{
Person stu;
Person *pt=&stu;
pt->name="小明";
pt->age=12;
pt->score=99.9;
pt->showPerson();
return 0;
}
当然,也可以在堆上创建对象,这时需要使用new关键字:
Person *pt= new Person;
但是最后必须使用delete释放:
delete pt;
4.类的成员变量和成员函数
-
成员变量:类的成员变量和普通变量一样,也有数据类型和名称,占用固定长度的内存。但是,在定义类的时候不能对成员变量赋值,因为类只是一种数据类型或者说是一种模板,本身不占用内存空间,而变量的值则需要内存来存储。
-
成员函数:类的成员函数也和普通函数一样,都有返回值和参数列表,它与一般函数的区别是:成员函数是一个类的成员,出现在类体中,它的作用范围由类来决定;而普通函数是独立的,作用范围是全局的,或位于某个命名空间内。
实例:
class Person
{
public:
//三个成员变量
char *name;
int age;
float score;
void showPerson()//成员函数
{
cout<<name<<"年龄:"<<age<<",成绩:"<<score<<endl;
}
};
上面的成员函数是放在类中声明的,也可以将放在外面声明
例:
#include <iostream>
using namespace std;
class Person
{
public:
//三个成员变量
char *name;
int age;
float score;
void showPerson();
};
void Person::showPerson()//定义函数
{
cout<<name<<"年龄:"<<age<<",成绩:"<<score<<endl;
}
注释:
-
在类体中直接定义函数时,不需要在函数名前面加上类名。
-
在类外定义成员函数时,就必须在函数名前面加上类名予以限定。
::
被称为作用域限定符),用来连接类名和函数名,指明当前函数属于哪个类。 -
成员函数必须先在类体中作原型声明,然后在类外定义,也就是说类体的位置应在函数定义之前。
5.内联方法
在类体中和类体外定义成员函数是有区别的:
-
在类体中定义的成员函数会自动成为内联函数,在类体外定义的不会。当然,在类体内部定义的函数也可以加
inline
关键字,但这是多余的,因为类体内部定义的函数默认就是内 -
若希望将函数定义在类体外部,又希望它是内联函数,那么可以在定义函数时加 inline 关键字。
-
当然你也可以在函数声明处加
inline
,不过这样做没有效果,编译器会忽略函数声明处的inline
。
实例:
class Person
{
public:
//三个成员变量
char *name;
int age;
float score;
void showPerson();//函数声明
};
inline void Person::showPerson()//定义函数
{
cout<<name<<"年龄:"<<age<<",成绩:"<<score<<endl;
}
-
在类体外定义
inline
函数的方式,必须将类的定义和成员函数的定义都放在同一个头文件中(或者同一个源文件中),否则编译时无法进行嵌入(将函数代码的嵌入到函数调用出)内联函数的特殊规则要求在每个使用它们的文件中都对其进行定义。 -
确保内联定义对多文件程序的所有文件都可用的,最简单的方法是:将内联定义放在定义类的头文件中。
二、构造函数
1.构造函数
1.构造函数
在C++中,有一种特殊的成员函数,它的名字和类名相同,没有返回值,不需要用户显式调用(用户也不能调用),而是在创建对象时自动执行。这种特殊的成员函数就是构造函数。
例:
#include <iostream>
using namespace std;
class Person
{
private:
char *m_name;
int m_age;
float m_score;
public:
//声明构造函数
Person();//无参构造
Person(char *name,int age,float score);//有参构造
void showPerson();//声明普通成员函数
};
Person::Person()//定义无参构造函数
{
cout<<"无参构造"<<endl;
}
Person::Person(char *name, int age, float score)//定义有参构造函数
{
m_name=name;
m_age=age;
m_score=score;
cout<<"有参构造"<<endl;
}
void Person::showPerson()//定义普通成员函数
{
cout<<m_name<<"年龄:"<<m_age<<",成绩:"<<m_score<<endl;
}
int main()
{
//创建对象方法1:
Person ob1("lucy",12,95.9);
ob1.showPerson();
//创建对象方法2:
Person *pt= new Person("Tom",16,99.9);
pt->showPerson();
return 0;
}
注:不能将类成员名用作构造函数的参数名。解决方案有两种:
1.在数据成员名中使用m_后缀。
class Persoon
{
private:
char *m_name;
int m_age;
float m_score;
...
}
2.在成员名中使用后缀_
class Person
{
private:
char name_;
int age_;
flaot score_;
...
}
2.默认构造函数
2.默认构造函数
如果用户自己没有定义构造函数,那么编译器会自动生成一个默认的构造函数,只是这个构造函数的函数体是空的,也没有形参,也不执行任何操作。比如上面的 Person类,默认生成的构造函数如下:
Person(){}
1.定义默认构造函数有两种方式:
1.给已有构造函数的所有参数提供默认值:
Person(const string & co="Error",int n=0,double pr=0.0;}
2.通过函数重载来定义另一个构造函数:没有参数的构造函数。:
Person(){}
2.注:一个类必须有构造函数,要么用户自己定义,要么编译器自动生成。一旦用户自己定义了构造函数,不管有几个,也不管形参如何,编译器都不再自动生成。
3.使用构造函数
3.使用构造函数
c++提供两种使用构造函数的方式。
1.显示的调用构造函数:
Person ob1=Person("lucy",12,95.9);
2.隐式地调用构造函数
Person ob1("lucy",12,95.9);
4.构造函数重载
介绍:和普通成员函数一样,构造函数是允许重载的。一个类可以有多个重载的构造函数,创建对象时根据传递的实参来判断调用哪一个构造函数。
构造函数的调用是强制性的,一旦在类中定义了构造函数,那么创建对象时就一定要调用,如果有多个重载的构造函数,那么创建对象时提供的实参必须和其中的一个构造函数匹配。
例:
#include <iostream>
using namespace std;
class Person
{
private:
char *m_name;
int m_age;
float m_score;
public:
Person();
Person(char *name,int age,float score);
void setname(char *name);
void setage(int age);
void setscore(float score);
void showPerson();
};
Person::Person()
{
m_name=NULL;
m_age=0;
m_score=0;
}
Person::Person(char *name, int age, float score)
{
m_name=name;
m_age=age;
m_score=score;
}
void Person::setname(char *name)
{
m_name=name;
}
void Person::setage(int age)
{
m_age=age;
}
void Person::setscore(float score)
{
m_score=score;
}
void Person::showPerson()
{
if(m_name==NULL || m_age<=0)
{
cout<<"成员变没有初始化"<<endl;
}
else
{
cout<<m_name<<"年龄:"<<m_age<<"成绩:"<<m_score<<endl;
}
}
int main()
{
Person ob1("lucy",12,95.9);
ob1.showPerson();
Person *pt= new Person();
pt->showPerson();
pt->setname("Tom");
pt->setage(16);
pt->setscore(99.9);
pt->showPerson();
return 0;
}
注:有参构造Person(char *, int, float)为各个成员变量赋值,无参构造Person()将各个成员变量的值设置为空,它们是重载关系。根据Person()创建对象时不会赋予成员变量有效值,所以还要调用成员函数 setname()、setage()、setscore() 来给它们重新赋值。
5.构造函数初始化列表
构造函数的一项重要功能是对成员变量进行初始化,为了达到这个目的,可以在构造函数的函数体中对成员变量一一赋值,还可以采用初始化列表。
例:
using namespace std;
class Person
{
private:
char *m_name;
int m_age;
float m_score;
public:
Person(char *name,int age,float score);
void showPerson();
};
Person::Person(char *name,int age,float score):m_name(name),m_age(age),m_score(score)
{
cout<<"有参构造"<<endl;
}
void Person::showPerson()
{
cout<<m_name<<"年龄:"<<m_age<<"成绩:"<<m_score<<endl;
}
int main()
{
Person ob("Tom",12,99.9);
ob.showPerson();
return 0;
}
注:
1.定义构造函数时并没有在函数体中对成员变量一一赋值,其函数体为空(当然也可以有其他语句),而是在函数首部与函数体之间添加了一个冒号:,后面紧跟m_name(name), m_age(age), m_score(score)语句,这个语句的意思相当于函数体内部的m_name = name; m_age = age; m_score = score;语句,也是赋值的意思。
2.初始化成员列表(参数列表)只能在构造函数使用。
三、析构函数
-
析构函数的名称也很特殊:在类名前加上
~
,如:~Person()
; -
析构函数可以没有返回值和声明类型。
-
与构造函数不同的是,析构函数没有参数,不能被重载。
-
若用户没有定义,编译器会自动生成一个默认的析构函数,类只能有一个析构函数。
-
若构造函数使用new来分配内存,则析构函数将使用delete来释放这些内存。
实例:
#include <iostream>
using namespace std;
class Person
{
private:
const int m_len;//数组长度
int *m_arr;//数组指针
int *m_p;//指向第i个元素的指针
int *ar(int i);//获取第i个元素的指针
public:
Person(int len);//有参构造
~Person();//析构函数
void input();//输入元素
void show();//显示数组元素
};
Person::Person(int len):m_len(len)//有参构造
{
if(len>0){
m_arr=new int[len];
}
else
{
m_arr=NULL;
}
}
Person::~Person()//析构函数
{
delete[]m_arr;
}
void Person::input()//普通成员函数
{
for(int i=0;m_p=ar(i);i++)
cin>>*ar(i);
}
void Person::show()//普通成员函数
{
for(int i=0;m_p=ar(i);i++)
{
if(i=m_len-1)
cout<<*ar(i)<<endl;
else
cout<<*ar(i)<<", ";
}
}
int *Person::ar(int i)//定义一个函数返回值为int
{
if(!m_arr || i<0 || i>=m_len)
return NULL;
else
return m_arr+i;
}
int main()
{
int n;
cout<<"input array length:";
cin>>n;
Person *pt=new Person(n);
cout<<"Input"<<n<<"numbers:";
pt->input();
cout<<"Elements:";
pt->show();
delete pt;
return 0;
}
注:
ar()
函数只在类的内部使用,所以将它声明为 private 属性;m_len
变量不允许修改,所以用const
进行了限制,这样就只能使用初始化列表来进行赋值。
四、const成员函数
在类中,若不希望有些数据被修改,可以使用const关键字
加以限定。const能用来修饰成员变量和成员函数。
-
修饰成员变量和普通cosnt变量的用法相似,只需要在声明时加上const关键字,例如:
const int age;
-
const 成员函数可以使用类中的所有成员变量,但是不能修改它们的值,这种措施主要还是为了保护数据而设置的,const 成员函数也称为常成员函数。
实例:
#include <iostream>
using namespace std;
class Person
{
private:
char *m_name;
int m_age;
float m_score;
public:
Person(char *name,int age,float score);//声明构造函数
void showPerson();//声明普通成员函数
//声明const成员函数,三个成员函数只是为了获得成员变量的值
char *getname()const;
int getage()const;
float getscore()const;
};
Person::Person(char *name,int age,float score):m_name(name),m_age(age),m_score(score)
{}
void Person::showPerson()
{
cout<<m_name<<"年龄:"<<m_age<<",成绩:"<<m_score<<endl;
}
char * Person::getname() const
{
return m_name;
}
int Person::getage() const
{
return m_age;
}
float Person::getscore()const
{
return m_score;
}
int main()
{
Person ob("Tom",12,99.9);
ob.showPerson();
}
注:
-
函数开头的
const
用来修饰函数的返回值,表示返回值是 const 类型,也就是不能被修改,例如const char * getname()。
-
函数头部的结尾加上 const 表示常成员函数,这种函数只能读取成员变量的值,而不能修2.改成员变量的值,例如
char * getname() const。
-
当成员变量类型符前用
mutable
修饰时可以修改。
1.常对象
在 C++ 中,const 也可以用来修饰对象,称为常对象。一旦将对象定义为常对象之后,就只能调用类的 const 成员(包括 const 成员变量和 const 成员函数)。
例:
#include <iostream>
using namespace std;
class Person
{
private:
char *m_name;
int m_age;
float m_score;
public:
Person(char *name,int age,float score);//声明构造函数
void showPerson();//声明普通成员函数
//声明const成员函数
char *getname()const;
int getage()const;
float getscore()const;
};
Person::Person(char *name,int age,float score):m_name(name),m_age(age),m_score(score)
{}
void Person::showPerson()
{
cout<<m_name<<"年龄:"<<m_age<<",成绩:"<<m_score<<endl;
}
char * Person::getname()const
{
return m_name;
}
int Person::getage()const
{
return m_age;
}
float Person::getscore()const
{
return m_score;
}
int main()
{
const Person ob("Tom",12,99.9);// 常对象
//ob.showPerson();error
cout<<ob.getname()<<"年龄:"<<ob.getage()<<",成绩:"<<ob.getscore()<<endl;
}
五、对象数组
- 对象数组:每一个数组元素都是对象的数组,也就是说,若一个类有若干个对象,我们把这一系列的对象用一个数组来存放。
- 对应数组元素是对象,不仅具有的数据成员,而且还有函数成员。本质是数组 只是数组的每个元素是类的对象。
实例1:
用只有一个参数的构造函数给对象数组赋值 。
#include <iostream>
using namespace std;
class Data
{
private:
int x;
public:
Data(int n)
{
x=n;
}
int getx()
{
return x;
}
};
int main()
{
Data arr[3]={12,13,14};//用只有一个参数的构造函数给对象数组进行赋值,三个对象
for(int i=0;i<=2;i++)
cout<<"第"<<i+1<<"个对象是: "<<"arr["<<i<<"]"<<" = "<<arr[i].getx()<<endl;
return 0;
}
实例2:
用不带参数和带一个参数的构造函数给对象数组赋值。
#include <iostream>
using namespace std;
class Data
{
private:
int x;
public:
Data()
{
x=9;
}
Data(int n)
{
x=n;
}
int getx()
{
return x;
}
};
int main()
{
Data arr1[3]={12,13,14};//三个对象均用只有一个参数的构造函数给对象数组进行赋值
Data arr2[3]={11};//第一对象调用有一个参数的构造函数赋值,后两个对象调用无参的构造函数赋默认值
for(int i=0;i<=2;i++)
cout<<"第"<<i+1<<"个对象是: "<<"arr["<<i<<"]"<<" = "<<arr1[i].getx()<<endl;
for(int i=0;i<=2;i++)
cout<<"第"<<i+1<<"个对象是: "<<"arr2["<<i<<"]"<<" = "<<arr2[i].getx()<<endl;
return 0;
}
实例3:
用带有多个参数的构造函数给对象数组赋值。
#include <iostream>
#include<cmath>
using namespace std;
class Data
{
private:
double real;
double imag;
public:
Data(double r=0.0,double i=0.0):real(r),imag(i)
{}
double pt()
{
double t;
t=real*real+imag*imag;
return sqrt(t);
}
};
int main()
{
Data ob[3]={
Data(1.3,1.4),
Data(2.2,3.2),
Data(3.3,4.4),
};
for(int i=0;i<=2;i++)
cout<<"ob["<<i<<"]:"<<ob[i].pt()<<endl;
return 0;
}
六、作用域
-
在类中定义的名称(如数据成员名和类成员名)的作用域都为整个类,作用域为整个类的名称只在该类中是已知的,在类外是不可知的,可以在不同类中使用相同的类成员名而不会引起冲突。
-
类作用域意味着不能从外部直接访问类的成员,公有成员函数也是如此,要调用公有成员函数,必须通过对象:
Person ob("Tom",12,99.9);ob.show();
-
在定义成员函数时,必须使用作用域解析运算符:
void Person::show(double x){...}
。 -
在类声明或成员函数定义中,可以使用未修饰的成员名称,构造函数名称在被调用时,才能被识别,因为它的名称与类名相同。
-
在其他情况下,使用类成员名时,必须根据上下文使用直接成员运算符
(.)
,间接成员运算符(->)
或作用域解析运算符(::)
。
实例:
下面代码演示如何访问具有类作用域的标识符。
class IK
{
private:
int fuss;
public:
IK(int f=9)
{
fuss=f;
}
void ViewIK()const;
};
void IK::ViewIK()const
{
cout<<fuss<<endl;
}
...
int main()
{
IK * pik= new IK;
IK ee=IK(8);
ee.ViewIK();
pik->ViewIK();
...
}
1.作用域为类的常量
使符号常量的作用域为类很有用,如类声明可能使用字面值30来指定数组的长度,由于该常量对于所有对象来说都是相同的,因此创建一个由所有对象共享的常量是个好主意。例:
class Data
{
private:
const int Months=12;
double const[Months];
...
}
但这是行不通的,因为声明类只是描述了对象的形式,并没有创建对象。有两种方法解决:
- 通过枚举,在类声明中声明枚举作用域为整个类,因此可以通过用枚举为整型常量提供作用域为整个类的符合名称。
- 使用关键字static。
- 项目3
例:
//方法1:
clss Person
{
private:
enum {Months = 12};
double costs[Months];
...
//用这种方式声明枚举并不会创建类数据成员。也就是说,所有对象中都不包括枚举。
//方法2:使用关键字static.
class Person
{
private:
static const int Months=12;
double const[Months];
//这将创建一个名为Months的常量。该常量将与其他静态变量存储在一起,而不是存储在对象中。因此,只有一个Months常量,被所有Person对象共享。
2.作用域内枚举(C++11)
enum egg {Small,Medium,Large,Jumbo};
enum t_shirt {Small,Medium,Large,Jumbo};
egg中Small
和t_shirt中Small
位于相同的作用域内,将产生冲突。
避免这种问题,C++11提供了一种新枚举:其枚举量的作用域为类。枚举的声明类似如下:
enum class egg {Small,Medium,Large,Jumbo};
enum class t_shirt {Small,Medium,Large,Jumbo};
使用关键字struct代替class也可以,无论使用哪种方式都需要使用枚举名来限定枚举量:
egg choice = egg::Large;
t_shirt Floyd = t_shirt::Large;
在有些情况下,常规枚举将自动转换为整型,如将其赋给int
变量或用于比较表达式时,但作用域内枚举不能隐式地转换为整型:
enum egg_old {Small,Medium,Large,Jumbo};//未分类
enum class t_shirt {Small,Medium,Large,Jumbo};//分类
egg_old one = Medium;//未分类
t_shirt rolf=t_shirt::Large;//分类
int king=one;//未分类的隐式类型转换
int ring = rolf;//不允许
if(king<Jumbo)//允许
cout<<"Jumbo converted to int before comparison."<<endl;
if(king<t_shirt::Medium)
cout<<"Not allowed:<not deffined for scoped enum."<<endl;
//但在必要时,可进行显示类型转换:
int Frodo=int (t_shirt::Small);
枚举用某种低层整型类型表示,包含枚举的结构的长度可能随系统而异。对于作用域内枚举,c++11消除了这种依赖性。默认情况下,c++11作用域内枚举的底层类型为int,另一种语法,可用于做出不同的选择:
enum class:short pizza(Small,Medium,Large,XLarge);
:short
将底层类型指定为short
,底层必须为整型,在c++11中也可以使用这种语法来指定常规枚举的底层类型。如果没指定,编译器选择的底层将随实现而异。
七、抽象数据类型
抽象数据类型(ADT)
是将数据对象、数据对象之间的关系和数据对象的基本操作封装在一起的一种表达方式,它和工程中的应用是一致的,只向外界提供关键信息,并隐藏其后台的实现细节,即只表现必要的信息而不呈现细节,数据抽象是一种依赖于接口和实现分离的编程(设计)技术。
1.访问标签强制抽象
在 C++ 中,我们使用访问标签来定义类的抽象接口。一个类可以包含零个或多个访问标签:
-
使用公共标签定义的成员都可以访问该程序的所有部分。
-
一个类型的数据抽象视图是由它的公共成员来定义的。
-
使用私有标签定义的成员无法访问到使用类的代码。私有部分对使用类型的代码隐藏了实现细节。
-
访问标签出现的频率没有限制。每个访问标签指定了紧随其后的成员定义的访问级别。指定的访问级别会一直有效,直到遇到下一个访问标签或者遇到类主体的关闭右括号为止。
1.数据抽象的好处
-
类的内部受到保护,不会因无意的用户级错误导致对象状态受损。类实现可能随着时间的推移而发生变化,以便应对不断变化的需求,或者应对那些要求不改变用户级代码的错误报告。
-
如果只在类的私有部分定义数据成员,编写该类的作者就可以随意更改数据。如果实现发生改变,则只需要检查类的代码,看看这个改变会导致哪些影响。如果数据是公有的,则任何直接访问旧表示形式的数据成员的函数都可能受到影响。
3.方法
-
抽象把代码分离为接口和实现,在设计组件时,必须保持接口独立于实现。
-
如果改变底层实现,接口也将保持不变。
-
在这种情况下,不管任何程序使用接口,接口都不会受到影响,只需要将最新的实现重新编译即可。
实例1:
#include <iostream>
using namespace std;
class Person
{
private:
int total;//对外隐藏的数据
public:
Person(int i=0)
{
total=i;
}
void arr(int num)//对外接口
{
total+=num;
}
int gettotal()//对外接口
{
return total;
}
};
int main()
{
Person ob;
ob.arr(10);
ob.arr(20);
ob.arr(30);
cout<<"total="<<ob.gettotal()<<endl;
return 0;
}
实例2:
通过使用栈,使用抽象数据类型方式存储数据,即总是从堆顶添加或删除数据。
- C++程序使用栈来管理自动变量。当新的自动变量被生成后,它们被添加到堆顶;消亡时,从栈中删除它们。
- 栈的特征:首先,栈存储了多个数据项(该特征使得栈成为一个容器:一种更为同用的抽象)。
栈由可对它执行的操作来描述:
-
可创建空栈。
-
可将数据添加到堆顶(压入)。
-
可从栈顶删除数据项(弹出)。
-
可查看栈是否填满。
-
可查看栈是否为空。
-
可将上述描述转换为一个类声明,其中公有成员函数提供了表示栈操作的接口,而私有数据负责存储栈数据。
-
私有部分必须表明数据存储的方式,例如:使用常规的数组、动态分配数组或更高级的数据结构(如链表)。
-
公有接口应隐藏数据表示,而以通用术语来表达。如创建栈、压入等。
-
#include <iostream>
#include<cctype>
using namespace std;
typedef unsigned long Item;
class Stack
{
private:
enum{MAX=10};
Item items[MAX];
int top;
//私有部分,栈使用数组实现,而公有部分隐藏这一点。使用动态数组来替代数组,而不会改变类的接口。意味修改栈的实现后,不需要重新编写使用栈的程序,只需重新编译栈代码,并将其用已有的程序代码链接即可。
public:
//接口是冗余的,因为pop()和push()返回有关栈状态的信息(满或空),而不是void类型。
Stack()//确保所有栈被创建都为空。
{
top=0;
}
bool isempty()const
{
return top==0;
}
bool isfull()const
{
return top==MAX;
}
bool push(const Item & item)
{
if(top<MAX)
{
items[top++]=item;
return true;
}else
{
return false;
}
}
bool pop(Item & item)
{
if(top>0)
{
item = items[--top];
return true;
}
}
};
int main()
{
Stack st;
char ch;
unsigned long po;
cout<<"please enter A to add a purchase oreder,\n"
<<"p to process a PO,or Q quit.\n";
while(cin>>ch && toupper(ch) !='Q')
{
while(cin.get() !='\n')
continue;
if(!isalpha(ch))
{
cout<<'\a';
continue;
}
switch(ch){
case 'A':
case 'a':cout<<"Enrter a PO number to add:";
cin>>po;
if(st.isfull())
cout<<"stack already full\n";
else
st.push(po);
break;
case 'P':
case 'p': if(st.isempty())
cout<<"stack already empty\n";
else
{
st.pop(po);
cout<<"PO #"<<po<<"popped\n";
}
break;
}
cout<<"please enter A to add a purchase order,\n"
<<"p to porcess a PO ,or Q to quit.\n";
}
cout<<"Bye\n";
return 0;
}