C++语法基础(9)——类和对象

类和对象

面向对象

从过程化到面向对象

  • 过程化程序设计:每个步骤用语句代替;适合解决小问题;
  • 面向对象程序设计:解决问题寻找帮手;可以解决大问题;
  • 对象与变量:都是程序中解决问题的工具;后者是语言已经设计好的工具,前者是程序员自己创建的工具

C++语法基础(9)——类和对象_第1张图片

  • 代码重用:已有的工具能给其他程序员使用
  • 实现隐藏:工具的使用者不需要知道工具的内部实现方式
  • 继承:在已有工具的基础上加以扩展,形成一个功能更强的工具
  • 多态:对不同对象发出同一指令,不同对象有不同行为

DoubleArray库的设计与实现

要求
设计一个库,提供动态实型数组服务,该数组满足两个要求

  • 可以任意指定下标范围
  • 下标范围可在运行时确定
  • 使用下标变量时会检查下标的越界

数组的存储

  • 数组需要一块保存数组元素的空间,大小未知。这块空间需要在执行时动态分配。
  • 数组的下标可以由用户指定范围。因此,对每个数组还需要保存下标的上下界。
  • 保存这个数组的三个部分是一个有机的整体,因此可以用一个结构体把它们封装在一起。

数组操作

  • 给数组元素赋值
  • 访问数组元素的值
  • 给数组元素分配空间(由于这个数组的存储空间是动态分配的,因此,还必须有一个函数去释放空间)

头文件

#ifndef _DoubleArray_h
#define _DoubleArray_h

struct DoubleArray //可指定下标范围的数组的存储
{
	int low;
	int high;
	double *storage;
};

bool initialize(DoubleArray &arr, int low, int high); //根据low和high为数组分配空间
bool insert(const DoubleArray &arr, int index, double value); //设置数组元素的值
bool fatch(const DoubleArray &arr, int index, double &value); //访问数组元素的值
void cleanup(const DoubleArray &arr); //回收数组空间
#endif

实现文件

#include "DoubleArray.h"
#include 
using namespace std;

bool initialize(DoubleArray &arr, int low, int high)
{
	arr.low = low;
	arr.high = high;
	arr.storage = new double[high - low + 1];
	if (arr.storage == NULL)
		return false;
	else return true;
}

void cleanup(const DoubleArray &arr)
{ delete [] arr.storage; }

bool insert(const DoubleArray &arr, int index, double value)
{
	if (index < arr.low || index > arr.high)
		return false;
	arr.storage[index - arr.low] = value;
	return true;
}

bool fatch(const DoubleArray &arr, int index, double &value)
{
	if (index < arr.low || index > arr.high)
		return false;
	value = arr.storage[index - arr.low];
	return true;
}

应用

#include 
using namespace std;
#include "DoubleArray.h"
int main()
{
	doubleArray array;
	double value;
	int low, high, i;

	cout << "请输入数组的下标范围:";
	cin >> low >> high;

	if (!initialize(array, low, high)) {
		cout << "空间分配失败";
		return 1;
	}
	for(i=low;i<=high;++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);
	
	return 0;
}

将函数放入结构体

DoubleArray库的缺点

  • 不能直接用下标变量访问
  • 需要用户程序关心数组空间问题
    • 需要调用initialize函数申请空间
    • 需要调用cleanup函数归还空间
  • 函数名冲突
    • 一个程序中可能用到很多库,每个库都可能需要初始化和清除工作。库的设计者都可能觉得initialize和cleanup是比较合适的名字,因而都写了这两个函数。
  • 使用相当笨拙

将函数放入结构体

  • 函数原型中的第一个参数不再需要。编译器自然知道函数体中涉及到的low,high和storage是同一结构体变量中的成员
  • 函数名冲突的问题也得到了解决。函数名是从属于某一结构体,从属于不同结构体的同名函数是不会冲突的

改进后的头文件

#ifndef _DoubleArray_h
#define _DoubleArray_h

struct DoubleArray
{
	int low;
	int high;
	double *storage;

	bool initialize(int lh,int rh);
	bool insert(int index,double value);
	bool fatch(int index, double &value);
	void cleanup();
};
#endif

改进后的DoubleArray函数的实现

bool DoubleArray::initialize(int lh, int rh)
{
	low = lh;
	high = rh;
	storage = new double[high - low + 1];
	if (storage == NULL) return false;
	else return true;
}

改进后的DoubleArray库的使用

#include 
using namespace std;
#include "array.h"
int main()
{
	doubleArray array;
	double value;
	int low, high, i;

	cout << "请输入数组的下标范围:";
	cin >> low >> high;

	if (!array.initialize(low, high)) {
		cout << "空间分配失败";
		return 1;
	}
	for(i=low;i<=high;++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;
}

将函数放入结构体的意义
将函数放入结构体是从C到C++的根本改变

  • C中的结构体
    • 一组相关的数据
    • 它除了使程序逻辑更加清晰之外,对解决问题没有任何帮助
  • C++中的结构体
    • 数据+处理数据的函数
    • 成为了和内置类型一样的一种新的数据类型

类定义

类定义的格式

class 类名 {
	private:
		私有数据成员和成员函数
	public:
		公有数据成员和成员函数
	};

如果某个成员前面没有访问范围说明符,则该成员默认地被认为是私有成员。

  • 私有成员(private)
    • 只能由类的成员函数调用
    • 私有成员被封装在一个类中,类的用户是看不见的
  • 公有成员(public)
    • 类的用户可以调用的信息,是类对外的接口

DoubleArray类的定义

class DoubleArray
{
private:
	int low;
	int high;
	double *storage;
public:
	bool initialize(int lh,int rh);
	bool insert(int index,double value);
	bool fatch(int index, double &value);
	void cleanup();
};
  • 数据成员和成员函数
    • 根据所需保存的信息设计数据成员
    • 根据行为设计成员函数
  • 访问特性
    • 数据成员一般是私有的
    • 成员函数一般是公有的
    • 成员函数实现时分解出的小函数是私有的
  • 接口和实现分开
    • 类的定义写在接口文件中
    • 成员函数的实现写在实现文件中
    • 某些简单的成员函数的定义可以直接写在类定义中
    • 在类定义中定义的成员函数被认为是内联函数

设计一个类SampleCLass

  • 需要一个整型变量作为搜索的关键字
  • 需要一个字符串变量作为储存的主要内容
  • 需要一个公有的分裂函数,输入两个该类的指针,将原来的字符串分成前后两个部分分别存入到两个指针指向的对象中,关键字分别变为原来的1/3与2/3.
class SampleClass
{
    int key;
    string value;
public:
    void split(SampleClass *,SampleClass *);
}

有理数类的设计C++语法基础(9)——类和对象_第2张图片

头文件

#ifndef _rational_h
#define _rational_h
#include 
using namespace std;

class Rational {
private:
	int num;
	int den;
	void ReductFraction(); //将有理数化简成最简形式
public:
	void create(int n, int d) {num=n; den=d;ReductFraction();}
	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"
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();
}
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;
		}
}

创建一个类,功能是打印出一句“point”

#include 
using namespace std;

class point {
public:
    //请补充代码,完成show函数的定义,功能是打印出一句“point”。
    void show() {
        cout << "point" << endl;
    }
};

int main() {
    point p1;
    point *p;
    p = &p1;
    p1.show(); //通过对象p1访问show函数
    p->show();  //通过指针p访问show函数
    return 0;
}

使用对象

对象的定义

  • 类与对象的关系:类型与变量的关系

  • 怎样定义一个变量,就可以怎样定义一个对象

  • 直接在程序中定义某个类的对象

    • 存储类别 类名 对象列表
    • DoubleArray arr1, arr2, *ap;
    • static DoubleArray array[10];
  • 申请动态对象

    • Rational *rp;
    • rp = new Rational;
    • rp = new Rational[20];
    • delete rp;delete [] rp;

对象的使用

对象的使用是使用它的成员

公有成员可以被任何函数使用
私有成员只能被自己的成员函数使用

成员的引用

arr1.storage //对象名.数据成员名
rp->num //对象指针->数据成员名
arr1.insert(5, 3.7) //对象名.成员函数名(实际参数表)
rp->add(r1,r2) //对象指针->成员函数名(实际参数表)

有理数类的使用

#include 
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指针

每个成员函数都有一个隐藏的指向本类型的指针形参this,指向控制对象

void create(int n, int d)
{
	num = n;
	den = d;
}

void create(Rational *this, int n, int d)
{
	this->num = n;
	this->den = d;
}
  • 通常,在写成员函数时可以省略this,编译时会自动加上它们。
  • 如果在成员函数中要把对象作为整体来访问时,可以显式地使用this指针。
class SampleClass
{
    int a;
public:
    SampleClass add()
    {
        /*
        这个函数实现了将变量a增加1的功能
        最后返回增加1前的对象(不要求地址相同)
        */
        SampleClass temp=*this;
		a++;
		return temp;
    }
}

构造与析构

  • 对象能赋初值吗 可以
  • 对象如何赋初值 ——> 将如何赋初值的过程写成一个函数
  • 对象消亡时如何善后——> 将善后的过程写成一个函数

构造函数和析构函数

  • 构造函数和析构函数是特殊的成员函数
  • 构造函数
    • 赋初值的过程
    • 定义对象时自动调用
  • 析构函数
    • 对象消亡时的扫尾工作
    • 对象消亡时自动调用

构造函数

如何知道哪个是构造函数呢? 构造函数的名字必须与类名相同

  • 构造函数特点
    • 可以有任意类型的参数
    • 不允许写返回类型
    • 可以重载
    • 每个类必须有构造函数。如果没有定义构造函数,编译器自动生成一个空的构造函数

DoubleArray类的构造函数

DoubleArray(int lh, int rh)
{
	low = lh;
	high = rh;
	storage = new double[high - low + 1];
}

构造函数的使用

  • 定义对象时,必须指定构造函数的实际参数
  • 类名 对象名 (实际参数表);
  • DoubleArray array(20, 30);

Rational类的构造函数

Rational(int n1, int n2)
{
	num = n1;
	den = n2;
	ReducFraction();
}
  • 定义对象时
  • Rational r(2,7);

动态对象的初始化

  • 在类型后面用一个圆括号指出它的实际参数表
  • 为一个动态的DoubleArray数组指定下标范围为20到30,可用下列语句:
  • p = new DoubleArray(20, 30);
  • 为一个动态的Rational对象指定初值是1/5,可用下列语句:
  • p = new Rational(1, 5);

析构函数

  • 析构函数在销毁对象时,完成一些善后工作,由编译系统自动调用
  • 析构函数与构造函数名字相同,但它前面必须加一个波浪号(~)
  • 析构函数没有参数,没有返回值,也不能重载
  • 定义类时没有定义析构函数,编译系统会自动生成一个缺省的空析构函数

有构造和析构函数的DoubleArray类

class DoubleArray{
	int low;
	int high;
	double *storage;
public:
	DoubleArray(int lh, int rh):low(lh), high(rh);
	DoubleArray(const DoubleArray &);
	bool insert(int index, double value);
	bool fatch(int index, double &value);
	~DoubleArray() {delete [] storage;}
};

DoubleArray类的使用

#include 
using namespace std;
#include "DoubleArray.h"
int main()
{
	doubleArray array(20,30);
	double value;
	int i;
	
	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";
	}

	return 0;
}

运算符重载

基本概念

  • 什么是运算符重载
    • 使系统内置的运算符可以用于类类型
    • 例如:类A的对象a1、a2、a3,可以执行a3=a1+a2;
    • 运算符重载的限制
  • 不是所有的运算符都能重载
    • 重载不能改变运算符的优先级和结合性
    • 重载不能改变运算符的操作个数
    • 不能创建新的运算符
  • 运算符重载的方法
    • 写一个函数解释某个运算符在某个类中的含义
    • 重载函数命名 operator@
    • 重载 "+"运算符,该重载函数名为operator+
    • 重载赋值运算符,函数名为operator=
  • 运算符重载函数原型
    • 运算符的重载不能改变运算符的运算对象数
    • 形式参数个数(包括成员函数的隐式指针this)及类型 与运算符的运算对象相符
    • 返回值 与运算结果值类型一致
  • 成员函数vs全局函数
    • 大多数运算符的重载函数可以写成成员函数,也可以写成全局函数
    • 重载成全局函数 函数原型与运算符与完全相符 最好将此函数设为友员函数
    • 重载成成员函数 形式参数个数比运算符对象数少1。这是因为成员函数有一个隐含的参数this 隐含参数this是第一个形式参数

重载实例

重载成成员函数

class Rational {
private:
	int num;
	int den;
	void ReductFraction();
public:
	Rational(int n = 0, int d = 1)
	{
		num = n;
		den = d;
	}
	Rational operator+(const Rational &r1) const;
	Rational operator*(const Rational &r1) const;
	bool operator==(const Rational &r1) const;
	void display() const {
		cout << num << '/' << den;
	}
};
Rational Rational::operator+(const Rational &r1) const
{
	Rational tmp;
	tmp.num = num*r1.den + r1.num*den;
	tmp.den = den* r1.den;
	tmp.ReductFraction();
	return tmp;
}

Rational Rational::operator*(const Rational &r1) const
{
	Rational tmp;
	tmp.num = num*r1.num;
	tmp.den = den*r1.den;
	tmp.ReductFraction();
	return tmp;
}

bool Rational::operator==(const Rational &r1) const
{
	return num == r1.num && den == r1.den;
}

重载成全局函数

class Rational {
	friend Rational operator+(const Rational &r1, const Rational &r2);
	friend Rational operator*(const Rational &r1, const Rational &r2);
	friend bool operator==(const Rational &r1, const Rational &r2);
private:
	int num;
	int den;
	void ReductFraction();
public:
	Rational(int n = 0, int d = 1)
	{
		num = n;
		den = d;
	}
	
	void display() const {
		cout << num << '/' << den;
	}
};
Rational operator+(const Rational &r1, const Rational &r2)
{
	Rational tmp;
	tmp.num = r2.num*r1.den + r1.num*r2.den;
	tmp.den = r2.den* r1.den;
	tmp.ReductFraction();
	return tmp;
}

Rational operator*(const Rational &r1,const Rational &r2)
{
	Rational tmp;
	tmp.num = r2.num*r1.num;
	tmp.den = r2.den*r1.den;
	tmp.ReductFraction();
	return tmp;
}

bool Rational::operator==(const Rational &r1,const Rational &r2)
{
	return r2.num == r1.num && r2.den == r1.den;
}

有理数类的使用

int main()
{
	Rational r1(1,6), r2(1,6), r3;

	r3 = r1 + r2;
	r1.display(); cout << "+"; r2.display();
	cout << "="; r3.display(); cout << endl;
	r3 = r1 * r2;
	r1.display(); cout << "*"; r2.display();
	cout << "="; r3.display(); cout << endl;

	return 0;
}

随堂测验

//题目描述:
//补全Int类,使得程序的输出为:
//1
//2
//3
//4
//5
#include 
using namespace std;
class Int
{
    static int addtime;

public:
    Int(int x) {}
    friend int operator+(const Int &r1, const Int &r2)
    {
        return ++addtime;
    }
};
int Int::addtime = 0;

int main()
{
    Int a(1), b(1), c(1);
    cout << a + a << endl;
    cout << b + b << endl;
    cout << b + c << endl;
    cout << c + c << endl;
    cout << a + a << endl;
}

继承与多态

组合

  • 将对象作为类的数据成员
  • 组合表示一种聚集关系,是一种部分和整体(is a part of)的关系
  • 通常需要用初始化列表去初始化对象成员

实例
C++语法基础(9)——类和对象_第3张图片

class Complex{
	friend Complex operator+(const Complex &x, const Complex &y);
	friend istream& operator>>(istream &is, Complex &obj);
	friend ostream& operator<<(ostream &os, const Complex &obj);

private:
	Rational real; //实部
	Rational imag; //虚部

publicComplex(int r1 = 0, int r2 = 1, int i1 = 0, int i2 = 1): real(r1,r2),imag(i1,i2){}
	Complex(Rational r1, Rational r2): real(r1), imag(r2) {}
};
Complex operator+(const Complex &x, const Complex &y)
{	return Complex(x.real + y.real, x.imag + y.imag);}

istream & operator>>(istream &is, Complex &obj)
{
	cout << "请输入实部";
	is >> obj.real;
	cout << "请输入虚部";
	is >> obj.imag;
	return is;
}

ostream & operator<<(ostream &os, const Complex &obj)
{
	os << '(' << obj.real << "+" << obj.imag << "i" << ')';
	return os;
}
int main()
{
	Complex x1,x2,x3;

	cout << "请输入x1:\n";
	cin >> x1;
	cout << "请输入x2: \n";
	cin >> x2;

	x3 = x1 + x2;
	
	cout << x1 << "+" << x2 << "=" << x3 << endl;
	return 0;
};
  • 用于组合的类也可以定义在类内。设有类Linklist,在其内定义了类Node,如果在Linklist外要使用类Node来声明对象x,应该写作:
  • Linklist::Node x;

继承

  • 支持软件的重用,基类代码被派生类重用
  • 可以反映事物之间的层次关系,基类和派生类是“一般和特殊”的关系,派生类是一类特殊的基类
  • 支持软件的增量开发,通过类的功能扩展整个软件的功能
  • 利用多继承对概念进行组合
class 派生类名: 派生方法 基类名
{
	//派生类新增的数据成员和成员函数
}
  • 派生方法 决定基类成员在派生类中的访问特性
  • 公有派生 public 基类的公有成员在派生类也是公有的
  • 私有派生 pivate 基类的公有成员在派生类是私有的

派生实例

class base{
	int x;
public:
	void setx(int k);
};

class derived1:public base {
	int y;
public:
	void sety(int k);
};

此时Derived1有x,y两个数据成员和setx,sety两个成员函数。其中x不可访问,y是私有的,setx()和sety()均是公有的。
C++语法基础(9)——类和对象_第4张图片

  • 包含成员 protected 一类特殊的私有成员,可以被派生类的成员函数访问
  • 保护派生 基类的公有成员和保护成员在派生类中都是保护成员
public继承 protected继承 private继承
基类public成员 public protected private
基类protected成员 protected protected private
基类private成员 不可访问 不可访问 不可访问

注:不可访问指派生类不可访问基类成员,private则指基类成员作为派生类的private成员

多态性

  • 多态性有两种实现方式:编译时的多态性运行时的多态性
  • 运算符重载和函数重载属于编译时的多态性
  • 虚函数属于运行时的多态性

C++语法基础(9)——类和对象_第5张图片

  • 便于系统功能的扩展
  • 扩展系统功能通常需要扩展主程序

通过定义新的类使得在主程序不变的情况下系统功能得到扩展。

你可能感兴趣的:(C++与数据结构笔记,c++,开发语言)