文章目录
一、实例
1、各种形状的图形
在屏幕上输出各种形状的图形(文字),诸如圆形,长方形,三角形等;用一个数组存储这些不同的形状,并进行一些操作
class shape{
public: virtual void draw()=0;
};
class rectangle:public shape{
float w,h;
public:rectangle(float ww, float hh){
w=ww; h=hh;
}
void draw(){
cout<<“A rectangle. Width="<<w<<“ length=“<<h<<endl;
}
};
class circle:public shape{
float r;
public:circle(float rr) {
r=rr;
}
void draw(){
cout<<“A circle. Radius=“<<r<<endl;
}
};
//......
int main(){
shape *ptr[4];
ptr[0]=new rectangle(2,3);
ptr[1]=new circle(5);
//……
drawshapes(ptr,4);
//……
}
void drawshapes(shape* p[],int n) {
for(int i=0;i<n;i++)
p[i]->draw();
}
二、面向对象的特点
封装
将数据和基于数据的操作包装在一起,构成一个不可分割的独立实体,**数据被保护在类的内部,尽可能地隐藏内部的细节,只保留一些对外的接口使之与外部发生联系。**用户无需知道对象内部的细节,但可以通过包裹在数据外面的已经授权的操作来与这个封装的对象进行交流。
示例中,每一种图形都独立封装起来。以圆形为例,半径是私有成员,用户不能访问这一属性,只能调用draw方法。
继承
继承是使用已存在的类来建立新类。子类的定义可以增加新的数据或功能,也可以复用父类的功能,提高了开发的效率。
示例中定义了抽象类shape,以此为基础定义了不同的形状,这些子类都继承自shape,都拥有draw方法。同时我们又分别覆写了draw方法,使得每个子类对draw方法有各自不同的实现。
我们总结各种形状的共有特征并进行抽象,得到shape类,所有形状的公共方法为draw,模块依赖于这个固定的抽象体,因此对于修改是封闭的。同时通过这个抽象体shape可以派生新的类,对于扩展是开放的。
多态
同一操作作用于不同的对象,可以有不同的解释,产生不同的执行效果。
利用虚函数与基类指针指向派生类对象,或基类对象引用派生类对象结合起来就可以实现多态性。
在示例中,drawshape函数通过父类的引用调用对象的draw方法,实际被调用的却是具体的子类的方法,从而相同的代码能够依据运行时对象的不同表现出不同的行为。
三、库和类
关于库
数组的保存
数组需要一块保存数组元素的空间。这块空间需要在执行时动态分配。
数组的下标可以由用户指定范围。因此,对每个数组还需要保存下标的上下界。
保存这个数组的三个部分是一个有机的整体,因此可以用一个结构体把它们封装在一起。
数组操作
给数组分配空间
给数组元素赋值
取某一个数组元素的值
由于这个数组的存储空间是动态分配的,因此,还必须有一个函数去释放空间
这里是array库的头文件:
#ifndef _array_h//这里是定义array库
#define _array_h
//可指定下标范围的数组的存储
struct IntArray
{ int low;
int high;
int *storage;
};
//根据low和high为数组分配空间。分配成功,返回值为true,否则为false
bool initialize(IntArray &arr, int low, int high);
//设置数组元素的值
//返回值为true表示操作正常,返回值为false表示下标越界
bool insert(const IntArray &arr, int index, int value);
//取数组元素的值
//返回值为true表示操作正常,为false表示下标越界
bool fatch(const IntArray &arr, int index, int &value);
//回收数组空间
void cleanup(const IntArray &arr);
#endif
这里是array库的实现文件:
#include "array.h"
#include <iostream>
using namespace std;
//根据low和high为数组分配空间。分配成功,返回值为true,否则为false
bool initialize(IntArray &arr, int low, int high)
{arr.low = low;
arr.high = high;
arr.storage = new int [high - low + 1];
if (arr.storage == NULL) return false; else return true;
}
//设置数组元素的值,返回值为true表示操作正常,返回值为false表示下标越界
bool insert(const IntArray &arr, int index, int value)
{if (index < arr.low || index > arr.high) return false;
arr.storage[index - arr.low] = value;
return true;
}
//取数组元素的值,返回值为true表示操作正常,为false表示下标越界
bool fatch(const IntArray &arr, int index, int &value)
{if (index < arr.low || index > arr.high) return false;
value = arr.storage[index - arr.low] ;
return true;
}
//回收数组空间
void cleanup(const IntArray &arr) { delete [ ] arr.storage; }
这里是array库的应用:
#include <iostream>
using namespace std;
#include "array.h"
int main()
{ IntArray array; //IntArray是array库中定义的结构体类型
int value, i;
//初始化数组array,下标范围为20到30
if (!initialize(array, 20, 30)) { cout << "空间分配失败" ; return 1;}
for (i=20; i<=30; ++i) {
cout << "请输入第" << i << "个元素:";
cin >> value;
insert(array, i, value); }
while (true) {
cout << "请输入要查找的元素序号(0表示结束):";
cin >> i;
if (i == 0) break;
if (fatch(array, i, value)) cout << value << endl;
else cout << "下标越界\n";
}
cleanup(array); //回收array的空间
return 0;
}
array库的问题
这个数组的使用相当笨拙。每次调用和数组有关的函数时,都要传递一个结构体给它。
我们可能在一个程序中用到很多库,每个库都可能需要做初始化和清除工作。库的设计者都可能觉得initialize和cleanup是比较合适的名字,因而都写了这两个函数。
系统内置的数组在数组定义时就指定了元素个数,系统自动会根据元素个数为数组准备存储空间。而我们这个数组的下标范围要用initialize函数来指定,比内置数组多了一个操作。
当数组使用结束后,还需要库的用户显式地归还空间。
对数组元素的操作不能直接用下标变量的形式表示。
array库的改进:将函数放进结构体
好处:
- 函数原型中的第一个参数不再需要。编译器自然知道函数体中涉及到的low, high和storage是同一结构体变量中的成员
- 函数名冲突的问题也得到了解决。现在函数名是从属于某一结构体,从属于不同结构体的同名函数是不会冲突的。
改进后的头文件:函数瘦身了!(?)
#ifndef _array_h
#define _array_h
struct IntArray{
int low;
int high;
int *storage;
bool initialize(int lh, int rh);
bool insert(int index, int value);
bool fatch(int index, int &value);
void cleanup();
};
#endif
改进后的实现文件:
//与原来的实现有一个变化:函数名前要加限定
bool IntArray::initialize(int lh, int rh){
low = lh;
high = rh;
storage = new int [high - low + 1];
if (storage == NULL) return false;
else return true;
}
改进后的array库的应用:
//函数的调用方法不同。就如引用结构体的成员一样,要用点运算符引用这些函数
int main(){
IntArray array;
int value, i;
if (!array.initialize(20, 30)) { cout << "空间分配失败" ; return 1;}
for (i=20; i<=30; ++i) {
cout << "请输入第" << i << "个元素:";
cin >> value;
array.insert(i, value); }
while (true) {
cout << "请输入要查找的元素序号(0表示结束):";
cin >> i;
if (i == 0) break;
if (array.fatch(i, value)) cout << value << endl;
else cout << "下标越界\n";
}
array.cleanup();
return 0;
}
how it becomes ‘class’:
将函数放入结构体是从C到C++的根本改变
在C中,结构体只是将一组相关的数据捆绑了起来,它除了使程序逻辑更加清晰之外,对解决问题没有任何帮助。
将处理这组数据的函数也加入到结构体中,结构体就有了全新的功能。它既能描述属性,也能描述对属性的操作。事实上,它就成为了和内置类型一样的一种全新的数据类型。
为了表示这是一种全新的概念,C++用了一个新的名称 — 类来表示。
四、类
定义
class 类名{
[private:]//方括号表示可以不写
私有数据成员和成员函数
public:
公有数据成员和成员函数
};
struct 结构体类型名{
[public:]//不写就认为是公有,所以以前一直不写
公有数据成员和成员函数
private:
私有数据成员和成员函数
};
- 结构体是一种特殊的类
- 在类中,未指定访问属性的成员为私有类型
- 在结构体中,未指定访问属性的成员为公有类型
- 类和结构体共存,是由历史原因造成的
class IntArray {
private://里面的变量一般是私有
int low;
int high;
int *storage;
public://可用操作一般写成公有,也可以是私有就你自己用
bool initialize(int lh, int rh);
bool insert(int index, int value);
bool fatch(int index, int &value);
void cleanup();
};
- private 和public的出现次序可以是任意的。也可以反复出现多次。
- 成员还可以被说明为protected
- 访问特性:
数据成员一般是私有的
成员函数一般是公有的
成员函数实现时分解出的小函数是私有的 - 接口和实现分开:
与库设计一样,类的定义写在接口文件中,成员函数的实现写在实现文件中。
//a.h main.cpp a.cpp
某些简单的成员函数的定义可以直接写在类定义中。
在类定义中定义的成员函数被认为是内联函数。
实例
保存有理数:保存一个有理数就是保存两个整数。
有理数类的操作:
加函数
乘函数
创建有理数的函数,用以设置有理数的分子和分母
输出有理数函数
化简函数
访问权限设计:
数据成员是私有的
化简函数是内部调用的函数,与用户无关,因此也是私有的
其它函数都是公有的
定义有理数类:
#ifndef _rational_h
#define _rational_h
#include <iostream>
using namespace std;
class Rational {
private:
int num;
int den;
void ReductFraction(); //将有理数化简成最简形式
public:
void create(int n, int d) { num = n; den = d;}
void add(const Rational &r1, const Rational &r2);
void multi(const Rational &r1, const Rational &r2);
void display() { cout << num << '/' << den;}
};
#endif
有理数类的实现:
#include "Rational.h“
//add函数将r1和r2相加,结果存于当前对象
void Rational::add(const Rational &r1, const Rational &r2){
num = r1.num * r2.den + r2.num * r1.den;
den = r1.den * r2.den;
ReductFraction();
}
void Rational::multi(const Rational &r1, const Rational &r2){
num = r1.num * r2.num;
den = r1.den * r2.den;
ReductFraction();
}
// ReductFraction实现有理数的化简
void Rational::ReductFraction(){
int tmp = (num > den) ? den : num;
for (; tmp > 1; --tmp)
if (num % tmp == 0 && den % tmp ==0){
num /= tmp;
den /= tmp;
break;
}
}
对象的使用
对象的定义
类与对象的关系:类型与变量的关系
对象定义方法:
直接在程序中定义某个类的对象
存储类别 类名 对象列表;
如定义两个IntArray类的对象arr1和arr2,可写成:
IntArray arr1, arr2;
用动态内存申请的方法申请一个动态对象:
Rational *rp;
Rp = new Rational; rp = new Rational[20];
delete Rp;或 delete []rp;
对象的引用
对象名.数据成员名 或 对象指针->数据成员名
arr1.storage 或 rp->num
对象名.成员函数名(实际参数表) 或对象指针->成员函数名(实际参数表)
arr1.insert() 或 rp->add()
- 外部函数不能引用对象的私有成员
实例
有理数类的使用
#include <iostream>
using namespace std;
#include "Rational.h" //使用有理数类
int main(){
int n, d;
Rational r1, r2, r3; //定义三个有理数类的对象
cout << "请输入第一个有理数(分子和分母):";
cin >> n >> d;
r1.create(n,d);
cout << "请输入第二个有理数(分子和分母):";
cin >> n >> d;
r2.create(n,d);
r3.add(r1, r2); //执行r3=r1+r2
r1.display();
cout << " + ";
r2.display();
cout << " = ";
r3.display();
cout << endl;
r3.multi(r1, r2); //执行r3=r1*r2
r1.display();
cout << " * ";
r2.display();
cout << " = ";
r3.display();
cout << endl;
return 0;
}
对象赋值
同类型的对象之间可以互相赋值,如两个有理数对象r1和r2,可以执行
r1 = r2;
当一个对象赋值给另一个对象时,所有的数据成员都会逐位拷贝。上述赋值相当于执行:
r1.num = r2.num
r1.den = r2.den
但这样的话函数的空间事实上是会浪费一些
this指针
五、结构体
在构造函数里面加缺省值,可以省去dummy constructor意思是只需要写这一个构造函数就够了。