文章目录
C++基础——类和对象
(一)面向对象
1. 从过程化到面向对象(上)
知识点
抽象过程:计算机的工作建立在抽象的基础上
计算机层面的抽象:从计算机硬件,到机器语言,再到高级语言
当出现一个抽象的任务时,需要程序员在机器模型与实际问题模型之间建立联系,按照计算机的结构去思考
-
过程化程序设计:每个步骤用语句代替;适合解决小问题;
-
面向对象程序设计:解决问题寻找帮手;每个帮手就是一个对象,可以解决大问题;
2. 从过程化到面向对象(下)
对象与变量:都是程序中解决问题的工具;后者是语言已经设计好的工具,前者是程序员自己创建的工具
面向对象的程序设计为程序员提供了创建工具的功能
解决问题过程:
- 考虑需要的工具;
- 创建、收集工具;
- 设计利用工具解决问题的过程
3. 面向对象的程序设计的特点
代码重用:已有的工具能给其他程序员使用
实现隐藏:工具的使用者不需要知道工具的内部实现方式
继承:在已有工具的基础上加以扩展,形成一个功能更强的工具
多态:对不同对象发出同一指令,不同对象有不同行为
(二)类
将函数放入结构体是从C到C++的根本改变!
类定义与声明:
class ClassName
{
private:
//私有数据成员和成员函数
public:
//公有数据成员和成员函数
};
私有成员(private):只能由类的成员函数调用,私有成员被封装在一个类中,类的用户是看不见的
公有成员(public):类的用户可以调用的信息,是类对外的接口
private和public的出现次序可以是任意的,可以反复出现多次,成员还可以被声明为protected(此声明在之后的章节会进行解释)
类的设计:
数据成员和成员函数:
根据所需保存的信息设计数据成员
根据行为设计成员函数
访问特性:
数据成员一般是私有的
成员函数一般是公有的
成员函数实现时分解出的小函数是私有的
在实际工程中,我们可以将接口和实现分开:
- 类的定义写在接口文件中
- 成员函数的实现写在实现文件中
- 某些简单的成员函数的定义可以直接写在类定义中
- 在类定义中定义的成员函数被认为是内联函数
举例:有理数类的设计
(三)使用对象
类与对象的关系:类型与变量的关系
对象定义
直接在程序中定义某个类的对象,各种定义方式与行为与各种数据类型相似。
//存储类别 类名 对象列表;
DoubleArray arr1, arr2;
static DoubleArray array[10];
Rational *rp;
rp = new Rational;
delete rp;
rp1 = new Rational[20];
delete [] rp1;
对象的使用
对象的使用是使用它的成员:公有成员可以被任何函数使用,私有成员只能被自己的成员函数使用。
成员的引用:
/*
对象名.数据成员名
对象指针->数据成员名
对象名.成员函数名(实际参数表)
对象指针->成员函数名(实际参数表)
*/
arr1.storage = 0;
rp->num = 1;
arr1.insert(5, 3.7);
rp->add(r1, r2);
每个成员函数都有一个隐藏的指向本类型的指针形参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指针。*this 代表当前变量
(四)构造与析构
变量可以在定义时赋初值,对象能赋初值吗?怎么赋?
将赋初值的过程写成一个函数,让计算机在定义对象时执行该函数,这个函数称为构造函数
同理,有时在对象消亡前也有些工作要做,可将该过程写成一个函数,让计算机在对象消亡时执行该函数,这个函数称为析构函数
构造函数和析构函数是特殊的成员函数
1. 构造函数
构造函数说明了定义对象时,如何为对象赋初值,由系统在定义对象时自动调用。构造函数的名字必须与类名相同
如果没有给类定义构造函数,编译系统会自动生成一个缺省的构造函数。它只为对象分配存储空间,空间中的内容为随机数。
构造函数可以有任意类型的参数,也可以不带参数,但不能具有返回类型。构造函数可以重载。
class SampleClass
{
public:
SampleClass(/*...*/){/*...*/}
}
不带参数的构造函数称为默认的构造函数,一般每个类应该有一个缺省的构造函数。
对象定义的一般形式:类名 对象名(实际参数表);
实际参数表必须与某个构造函数的形式参数表相对应,除非这个类有一个构造函数是没有参数的,那么可以用:类名 对象名;
2. 析构函数
(五)运算符重载
什么是运算符重载?
使系统内置的运算符可以用于类类型
例如:类A的对象a1、a2、a3,可以执行
a3 = a1 + a2;
运算符重载的方法
运算符重载就是写一个函数解释某个运算符在某个类中的含义
关键问题
如何使编译器能自动找到重载的这个函数
解决方案
函数名必须要体现出和某个被重载的运算符的联系。
C++中规定,重载函数名为 operator@
. 其中,@为要重载的运算符
- 重载“+”运算符,该重载函数名为
operator+
- 重载赋值运算符,函数名为
operator=
函数原型 运算符的重载不能改变运算符的运算对象数、形式参数个数(包括成员函数的隐式指针this)及类型与运算符的运算对象相符
返回值与运算结果值类型一致
成员函数vs全局函数
大多数运算符的重载函数可以写成成员函数,也可以写成全局函数
重载成全局函数 函数原型与运算符与完全相符 最好将此函数设为友员函数
重载成成员函数 形式参数个数比运算符的运算对象数少1。这是因为成员函数有一个隐含参数this,this是第一个形式参数
举例
为rational类增加“+”和“*”以及“==”比较的重载函数,用以替换现有的add和multi函数
方案一:重载成成员函数
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()
{
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() { cout << num << '/' << den; }
};
函数实现
Rational operator+(const Rational &r1, const Rational &r2)
{
Rational tmp;
tmp.num = r1.num * r2.den + r2.num * r1.den;
tmp.den = r1.den * r2.den;
tmp.ReductFraction();
return tmp;
}
Rational operator*(const Rational &r1, const Rational &r2)
{
Rational tmp;
tmp.num = r1.num * r2.num;
tmp.den = r1.den * r2.den;
tmp.ReductFraction();
return tmp;
}
其他函数实现略
重载后有理数类的使用
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;
}
(六)继承与多态
组合:对象作为类的数据成员
聚集关系,是一种部分和整体的关系,即has-a的关系
组合的目的:简化创建新类的工作,让创建新类的程序员不需要过多地关注底层的细节,可以在更高的抽象层次上考虑问题,有利于创建功能更强的类
知识点
c++派生类定义的格式如下:
class 派生类名:继承方式 基类名{
新增加的成员声明;
};
继承方式可以是public、private和protected,大多数是public,protected是一类私有成员,但是派生类可以访问
派生类中基类成员的访问特性
public继承 | protected继承 | private继承 | |
---|---|---|---|
基类public成员 | public | protected | private |
基类protected成员 | protected | protected | private |
基类private成员 | 不可访问 | 不可访问 | 不可访问 |
注:不可访问指派生类不可访问基类成员,private则指基类成员作为派生类的private成员
多态性有两种实现方式:编译时的多态性(静态联编,在编译的时候就知道是哪个函数)和运行时的多态性(动态联编)
运算符重载和函数重载属于编译时的多态性
虚函数属于运行时的多态性