领悟面向对象就离开发应用不远了(类提高)有你对面向对象的所有疑问,爆肝1w字

2022-12-13 14:08:50 浏览数 (1)

文章目录

  • 前言
  • 一、面向对象
  • 二、使用类
    • 1.类的基础使用
    • 2.运算符重载
    • 3.友元
      • 1.友元函数
      • 2.友元类
      • 3.友元成员函数
  • 三、类继承
    • 基类
      • 派生类
        • 插个访问权限的事情
    • 2.多态 ***公有 *** 继承
      • virtual:
        • 虚函数
        • 基类中
    • 3.抽象基类
    • 4.动态与静态的联编
      • 静态
      • 动态
  • 最后

前言

hello,码神又回来了,首先感谢大家对上篇类的支持,其次就是来还欠下的面向对象了,秋名山路途漫漫,码神始终与你们同在,发车了!

一、面向对象

实际上面向对象是一个范指的概念,其中面向对象一般来说就是代指其中的类,这也是我上一篇为什么写写了一下类的原因,使用好了类,真的就领悟了面向对象。

二、使用类

1.类的基础使用

大致就是我上篇博客所写,这篇我们就注重于提高,当我学会了类,就离领悟面向对象编程不远了(基础)!!

2.运算符重载

首先我们明确一点,运算符重载是c 多态的一种形式,我们来类比与函数重载来看:同名的函数来完成相同的基本操作,即使被利用与不同的数据类型,运算符重载也差不多 例如:运算符“ * ”一样在指针中用来解引用,但是它还是乘法运算符

再看如何定义一个自己的重载运算符 operator ()重载 运算符

代码语言:javascript复制
返回值类型  operator  运算符(形参表)
{
    ....
}

实例:

代码语言:javascript复制
#include <iostream>
using namespace std;
class Complex
{
public:
    double real, imag;
    Complex(double r = 0.0, double i = 0.0) : real(r), imag(i) { }
    Complex operator - (const Complex & c);
};
Complex operator   (const Complex & a, const Complex & b)// 重载
{
    return Complex(a.real   b.real, a.imag   b.imag); //返回一个临时对象
}
Complex Complex::operator - (const Complex & c)
{
    return Complex(real - c.real, imag - c.imag); //返回一个临时对象
}
int main()
{
    Complex a(4, 4), b(1, 1), c;
    c = a   b; //等价于 c = operator   (a,b);
    cout << c.real << "," << c.imag << endl;
    cout << (a - b).real << "," << (a - b).imag << endl; //a-b等价于a.operator - (b)
    return 0;
}

可以重载的太多了,简单就用 带过了。 意义:如果不做特殊处理,C 的 、-、*、/ 等运算符只能用于对基本类型的常量或变量进行运算,不能用于对象之间的运算,运用与对象处理

3.友元

怎么说这个呢?平常不用,但是有一种地方用了更好公有类方法提供了唯一的访问途径,但是有时候不太适用于特定的问题,友元就出现了

1.友元函数

可以看为类的扩展接口,直接用吧:

代码语言:javascript复制
//创建友元函数是将其放入类声明中
class A
{
friend a operator*(double m, const T &t)

}

有一下俩点值得注意的:

  1. 虽然operator*()是在类中声明的,但是不是成员函数,所以不能用成员运算符来调用
  2. 不是成员函数,但是与成员函数的访问权限相同

函数主体也有些不同,不能用::作用域

代码语言:javascript复制
a operator*(double m,const T &t)
{
//主体随意

}

2.友元类

友元类的所有成员函数都是另一个类的友元函数,都可以访问另一个类中的隐藏信息(包括私有成员和保护成员)。 当希望一个类可以存取另一个类的私有成员时,可以将该类声明为另一类的友元类。定义友元类的语句格式如下: friend class 类名;

代码语言:javascript复制
#include <iostream>
using namespace std;
class B
{
private:
int tt;
friend class A;//相当于a为b的友元类,a可以使用b的数据
friend void Show( A& , B& );
 
public:
B( int temp = 100):tt ( temp ){}
 
};
 
class A
{
private:
int value;
friend void Show( A& , B& );
 
public:
A(int temp = 200 ):value ( temp ){}
 
void Show( B &b )
{
  cout << value << endl;
  cout << b.tt << endl; 
}
};
 
void Show( A& a, B& b )
{
cout << a.value << endl;
cout << b .tt << endl;
}
 
int main()
{
A a;
B b;
a.Show( b );
Show( a, b );
      return 0;
}

相当于破坏了c 中封装数据的特性,所以一般也不用

3.友元成员函数

友元成员函数声明和定义的顺序必须满足一定要求

代码语言:javascript复制
#include <iostream>  
#include <string>  
using namespace std;  
  
class Person; //声明 
class Student  
{  
public:  
    void ShowInf(Person& PersonObject);  
};  
  
class Person  
{  
    friend void Student::ShowInf(Person& PersonObject);  
private:  
    int age;  
    string name;  
public:  
    Person(string name, int age)  
    {  
        this->name = name;  
        this->age = age;  
    }  
    Person(Person& PersonObject)  
    {  
        this->name = PersonObject.name;  
        this->age = PersonObject.age;  
    }  
    Person operator = (Person PersonObject)  
    {  
        this->age = PersonObject.age;  
        this->name = PersonObject.name;  
    }  
};  
  
void Student::ShowInf(Person& PersonObject)  
{  
    cout << PersonObject.name << "的年龄为" << PersonObject.age << endl;  
}  
  
int main()  
{  
    Person PersonObject("张三", 19);  
    Student StudentObject;  
    StudentObject.ShowInf(PersonObject);  
}  

用Student类中的ShowInf()成员函数访问Person类中的私有成员name和age

三、类继承

好了开始下一个重点继承,内容有点多,建议收藏 继承什么,像二代一样,儿子继承父亲,这里的基类就像父亲,派生类好比于儿子。 在好比于我们用的数据库,一般来说现成的数据库不容许个人修改,但是为了提高重用性,C 提供了类继承来实现扩展类和修改类,就像继承一笔财产肯定比白手起家要快的多,类的继承有2个概念:

基类

我们这里用国乒来举例子,还是刘国梁的队伍,但是这次由于训练人数变多,球桌不够用了,下面看代码

代码语言:javascript复制
#include<iostream>
#include<string>
using namespace std;
class TableTennisPlayer //记录是否有球桌,及名字
{
private:
	string firstname;
	string lastname;
	bool hasTable;
public:
	TableTennisPlater(const string & fn="none",const string &ln="none",bool ht=flase);
	coid Name() const;
	bool HasTable() const{return hasTable;};
	void ReseTable(bool v) {hasTable =v ;};
}

TableTennisPlayer :: TableTennisPlayer (const string &fn,const string &ln,bool ht):firstname(fn),lastname(ln),hasTable(ht)
{}//构造函数
void TableTennisPlayer::Name() const
{
	cout<<lastname<<","<<firstname;
}
//主函数
int main()
{
	TableTennisPlayer player1("ma long","zhang ji ke",true);
	TableTennisPlayer player2("liu guo liang","wang tao",false);
	player1.Name();
	if(player1.HasTable())
		cout<<":hasn't table";
	else
		cout<<":has table";
	return 0;
}

派生类

现在码神也组织了一支乒乓球队想要和国乒选手一起打一把,但是总不能从零开始吧,所以此刻派生类出来了,首先将RatedPlayer类声明从TableTennisPlayer类中派生而来 先看下用法

代码语言:javascript复制
class RatedPlayer : public TableTennisPlayer
{
}

这里RatedPlayer称为 TableTennisPlayer的派生类,注意一下俩点

  1. 派生类对象储存了基类的数据成员(继承了基类的实现)
  2. 派生类还继承了基类的接口 当然派生类和其他类的使用方法大致一样:可以有自己的构造函数,可以添加额外的数据成员和成员函数
插个访问权限的事情

首先派生类不能直接访问基类的私有成员,而必须通过基类的方法进行访问,具体的说就是派生类的构造函数必须使用基类的构造函数,其他的类比

代码语言:javascript复制
RatedPlayer :: RatedPlayer(unsigned int r,const &fn,const string &ln,bool ht):TableTennisPlayer(fn,ln,ht)
{
	rating =r;
}

下面是我画的图

有多种形式,这里就不一一赘述了,可以自行了解,有如下要点:

  1. 首先构造基类对象
  2. 将基类信息传递给基类对象
  3. 派生类构造函数应该初始化派生类新增的数据成员 流程就像我上张图片一样,程序首先调用基类构造,再调用派生类构造。基类构造函数负责初始化继承的数据成员,派生类构造函数主要初始化新增的数据成员。 析构:首先调用派生类析构函数,然后再调用基类析构函数

2.多态 ***公有 *** 继承

当需要同一个方法再派生类和基类中行为是不同的,或者说方法的行为应取决于调用该方法的对象——多态,多种状态

  1. 在派生类中重新定义基类
  2. 虚方法 下面看一个例子: Brass 支票账户信息:客户姓名 账号 当前结余 可以执行的操作:创建账户 存款 取款 显示信息

BrassPlus包含Brass的信息以及:透支上限 透支贷款利率 显示透支总额 如果学过高中数学,我们可以说是Brass是BrassPlus的子集,所以我们现在开发俩个类

代码语言:javascript复制
class Brass
{
private:
	enum {MAx=35};
	char fullname[MAX];
	long acctNum;
	double balance;
public:
	Brass(const char *s="Nullbody",long an=-1; double bal=0.0);
	void Deopsit(double amt);
	virtual void Withdraw(double amt);
	double Balance() const;
	virtual void ViewAcct() const;
	virtual ~Brass(){};
}
 
class BrassPlus : public Brass
{
private:
	double maxLoan;
	double rate;
	double owesBank;
public:
	BrassPlus(const char *s="nullbody",long ml=500,double r=0.1);
	virtual void ViewAcct() const;
	virtual void Withdraw(double amt);
	void reseMax(double m){maxLoan=m;}
	void ReseRate(double r){rate=r;}
	void ReseOwes(){owesBank=0;}
};
  1. BrassPlus类在Brass类基础上添加了3个私有数据成员和3个公有成员函数。
  2. Brass类和BrassPlus类都声明了 ViewAcct()和Withdraw()方法,但是BrassPlus对象和Brass对象的这些方法的行为是不同的。
  3. Brass类在声明ViewAcct()和Withdraw()方法时候使用了virtual关键字。类比于虚函数
  4. Brass类还声明一个虚拟析构函数,虽然不执行任何操作。 注意:如果在派生类中重新定义基类的方法,通常将基类方法声明为虚的,好处是程序将根据对象类型而不是引用或者指针的类型来选择方法版本,算是一个惯例吧 函数实现我就不写了,不然篇幅太长了,感谢!秋名山上有你

还要注意,基类和派生类中所题,派生类访问基类数据是构造函数访问基类的公有数据,记住!

还是不放心,再提一下虚virtual吧

virtual:

virtual在英文中表示“虚”、“虚拟”的含义。c 中的关键字“virtual”主要用在两个方面:虚函数与虚基类。下面将分别从这两个方面对virtual进行介绍。

虚函数

虚函数源于c 中的类继承,是多态的一种。在c 中,一个基类的指针或者引用可以指向或者引用派生类的对象。同时,派生类可以重写基类中的成员函数。这里“重写”的要求是函数的特征标(包括参数的数目、类型和顺序)以及返回值都必须与基类中的函数一致。

基类中

可以在基类中将被重写的成员函数设置为虚函数,其含义是:当通过基类的指针或者引用调用该成员函数时,将根据指针指向的对象类型确定调用的函数,而非指针的类型。 这样就可以将基类与派生类的同名方法区分开,实现多态。

3.抽象基类

不能实例化的基类被称为抽象基类, 这样的基类只有一个用途, 那就是从它派生出其他类。 在 C 中,要创建抽象基类,可声明纯虚函数。

代码语言:javascript复制
#include <iostream>
using namespace std;
class Fish
{
public:
	// define a pure virtual function Swim
	virtual void Swim() = 0;
};

class Tuna :public Fish
{
public:
	void Swim()
	{
		cout << "Tuna swims fast in the sea! " << endl;
	}
};

class Carp :public Fish
{
	void Swim()
	{
		cout << "Carp swims slow in the lake!" << endl;
	}
};

void MakeFishSwim(Fish& inputFish)
{
	inputFish.Swim();
}

int main()
{
	// Fish myFish; // Fails, cannot instantiate an ABC
	Carp myLunch;
	Tuna myDinner;

	MakeFishSwim(myLunch);
	MakeFishSwim(myDinner);

	return 0;
}

main( )的第 1 行被注释掉了, 原因是它表明, 编译器不允许您创建抽象基类( ABC)Fish 的实例。编译器要求您创建具体类(如 Tuna)的对象,这与现实世界一致。第 7 行声明了纯虚函数 Fish::Swim( ),这迫使 Tuna 和 Carp 必须分别实现 Tuna::Swim( )和 Carp::Swim( )。第 27~30 行实现了 MakeFishSwim(Fish&),这表明虽然不能实例化抽象基类,但可将指针或引用的类型指定为抽象基类。抽象基类提供了一种非常好的机制,让您能够声明所有派生类都必须实现的函数。如果 Trout 类从Fish 类派生而来,但没有实现 Trout::Swim( ),将无法通过编译。 抽象基类常被简称为 ABC。

4.动态与静态的联编

联编:将源代码中的函数调用解释为执行特定的函数代码块被称为函数名联编,因为有个函数重载,所以还比较复杂,此刻的编译器查看参数及函数名,在编译过程中的联编称为静态联编,当出现虚函数时,过程更加复杂,编译器必须生成能够在程序运行时选择的正确的虚方法的代码,这又 称为动态联编

静态

静态联编对函数的选择是基于指向对象的指针或者引用的类型。其优点是效率高,但灵活性差。

代码语言:javascript复制
#include<iostream>

class A

{public:

     voidf()
    {cout<<"A"<<"";}

};

classB:publicA

{public:

 	void f()
 	{cout<<"B"<<endl;}

};

int main()

{
	A*pa=NULL;

	Aa;Bb;

	pa=&a;pa->f();

	pa=&b;pa->f();
//结果为A A
}

程序的运行结果可以看出,通过对象指针进行的普通成员函数的调用,仅仅与指针的类型有关,而与此刻指针正指向什么对象无关。要想实现当指针指向不同对象时执行不同的操作,就必须将基类中相应的成员函数定义为虚函数,进行动态联编。

动态

动态联编对成员函数的选择是基于对象的类型,针对不同的对象类型将做出不同的编译结果。C 中一般情况下的联编是静态联编,但是当涉及到多态性和虚函数时应该使用动态联编。动态联编的优点是灵活性强,但效率低。 值得注意的是 只能通过指向基类的指针或基类对象的引用来调用虚函数,其格式为:指向基类的指针变量名->虚函数名(实参表)或基类对象的引用名.虚函数名(实参表) 实现动态联编需要同时满足以下三个条件:

① 必须把动态联编的行为定义为类的虚函数。

② 类之间应满足子类型关系,通常表现为一个类从另一个类公有派生而来。

③ 必须先使用基类指针指向子类型的对象,然后直接或者间接使用基类指针调用虚函数。

代码语言:javascript复制
#include<iostream>

classA

{
public:

Virtual voidf()//虚函数

{cout<<"A"<<"";}};

classB:publicA

{
public:

Virtual voidf()//虚函数

{
	cout<<"B"<<endl;}
};

int main()

{ 
	A*pa=NULL;

	Aa;Bb;

	pa=&a;

	pa->f();

	pa=&b;

	pa->f();
//结果为A B
}

0 人点赞