c++面向对象复习

C++复习总结

面向对象程序的特点

  1. 抽象性:抽取本质特性(共性),加一简单描述。抽象是面向对象方法的核心

  2. 封装性:内部细节对外隐藏,通过接口进行控制

    1. 数据和行为的包装
    2. 信息隐藏
  3. 继承性:复用共性,简化描述

    1. 一般类:包含共性;
    2. 特殊类:在一般类的基础上,增添每个具体类的个性;
    3. 继承:特殊类的对象拥有一般类的全部属性和操作;
    4. 多继承:一个类可以继承多个一般类的特性。
  4. 多态性:一个接口,多种方式。多态性也是面向对象程序设计的重要特性之一

    1. 静态多态性:在编译过程中确定同名操作的具体对象
    2. 在程序运行过程中才确定操作所针对的具体对象
  5. 联编:把一条消息和一个对象的方法相结合的过程,即调用成员函数

    1. 消息:(对类的成员函数的)调用
    2. 方法:成员函数
    3. 静态联编:在编译阶段完成联编
    4. 动态联编:在程序运行阶段完成联编

类和对象

格式:

class //<类名>
{
private:
	//<私有成员函数和数据成员的说明>
protected:
	//<保护成员函数和数据成员的说明>
public:
	//<公有成员函数和数据成员的说明>
};

举个例子:

//CTime.hpp
class CTime{
private:
    int hour;
    int minute;
    int second;
public:
    void setTime(int hour = 0,int minute = 0,int second = 0);
    CTime(int hour = 0,int minute = 0,int second = 0);

    friend ostream& operator<<(ostream& os,const CTime& time){
        os << setw(2) << stefill('0') << time.hour << ":";
        os << setw(2) << stefill('0') << time.minute << ":";
        os << setw(2) << stefill('0') << time.second;
        return os;
    }
};

//CTime.cpp
void CTime::setTime(int hour,int minute,int second){
    hour = (h>=0 && h<=24>) ? h : 0;
    minute = (m>=0 && m<=60>) ? m : 0;
    second = (s>=0 && s<=60>) ? s : 0;
}

CTime::CTime(int hour,int minute,int second){
    this->setTime(hour,minute,second);
}

void main(){
    CTime t1(8,10,1),t2(11,30,1);
    CTime* pTime = &t1;//指针指向t1

    cout << t1 << endl;
    cout << t2 << endl;
    cout << *pTime << endl;
}

输出:
08:10:01
11:30:01
08:10:01

成员函数

对象的行为抽象为成员函数,又称为方法

数据成员

对象的属性抽象为数据成员,又称成员变量

对象

类和对象的关系相当于普通数据类型与其变量的关系。

格式:
<对象名>.<公有数据成员名>
<对象名>.<公有成员函数名(实参表)>

举个例子:
rectangle r1(10, 10, 20, 20);
r1.area( );

注意:只有用public定义的公有成员才能使用圆点操作符访问

构造函数和析构函数

//CTime.hpp
CTime(int hour = 0,int minute = 0,int second = 0);
~CTime();

//CTime.hpp
CTime::CTime(int hour,int minute,int second){
    this->setTime(hour,minute,second);
    cout << *this << ",constrctor..." << endl;
}

CTime::~CTime(){
cout << *this << ",destrctor..." << endl;
}

int main(){
    CTime t1(8,20),t2(11,30);
    CTime* pTime = &t1;//指针指向t1

    cout << t1 << endl;
    cout << t2 << endl;
    cout << *pTime << endl;
}

输出:
08:20:00,constrctor...
11:30:00,constrctor...
08:20:00
11:30:00
08:20:00
11:30:00,destrctor...
08:20:00,destrctor...

构造函数的两种方法

//推荐
CTime::CTime(int hour,int minute,int second){
    this->setTime(hour,minute,second);
    cout << *this << ",constrctor..." << endl;
}
//第二种方法
CTime::CTime(int hour,int minute,int second):hour(hour),minute(minute),second(second){}

拷贝构造函数

若没定义拷贝构造函数时,系统默认浅拷贝,存在潜在危险,即当数据类型为指针类型的时候,两个对象的指针会指向同一内存区域。

拷贝构造函数调用场合:

  1. 用对象初始化对象
  2. 函数参数传递(此时参数为一个对象)

举个例子:

CTime::CTime(CTime& t1){
    this->hour = t1.hour;
    this->minute = t1.minute;
    this->second = t1.second;
}

例1(构造函数、析构函数和拷贝构造函数)

CTime::CTime(int hour,int minute,int second){
    this->setTime(hour,minute,second);
    cout << *this << ",constrctor..." << endl;
}

CTime::~CTime(){
cout << *this << ",destrctor..." << endl;
}

CTime::CTime(CTime& t1){
    this->hour = t1.hour;
    this->minute = t1.minute;
    this->second = t1.second;
    cout << *this << ",copy constrctor..." << endl;
}

void demo_copy_constryctor(CTime t1){
    cout << t1 << ",demo_copy_constrctor..." << endl;
}

int main(){
    CTime t1(8);
    CTime t2 = t1;

    t2.setTime(11,30);
    cout << "t2:" << t2 <<endl;
    demo_copy_constryctor(t2);
}

输出
08:00:00,constrctor... //t1初始化
08:00:00,copy constrctor... //用t1初始化t2
t2:11:30:00 //cout << "t2:" << t2 <<endl;
11:30:00,copy constrctor... //调用函数,在函数内部初始化形参t1,用t2初始化t1
11:30:00,demo_copy_constrctor... //demo_copy_constryctor(t2);
11:30:00,destrctor... //函数调用完,局部变量t1被调用完释放
11:30:00,destrctor... //函数执行完,释放t2,栈,先释放后入栈的
08:00:00,destrctor... //释放t1

例2(没有拷贝构造函数的风险)

class CDog{
public:
    char* name;
    CDog(char* name);
    friend ostream& operator<<(ostream& os,const CDog& dog1){
        os << "I'am a dog, name is " << dog1.name;
        return os;
    }
};

CDog::CDog(char* name){
    this->name = name;
}

void main(){
    using namespace N2_CDOG;
    char name1[] = "Lao Hei";
    CDog dog1(name1) ,dog2 = dog1;

    dog2.name[0] = 'H';
    cout << "dog1:" << dog1 << endl;
    cout << "dog2:" << dog2 << endl;
}

输出
dog1:I'am a dog, name is Hao Hei
dog2:I'am a dog, name is Hao Hei

分析:因为CDog dog1(name1),name1的值为数组的地址,dog2 = dog1,dog1.name和dog2.name指向同一个内存空间,所以dog1和dog2的name都同时被修改

this指针

this指针是指向当前对象的特殊指针。

每个非静态的成员函数都有一个this指针函数的参数,当通过一个对象调用成员函数时,编译器将把当前对象的地址传递给this指针。

//我们的代码
void CTime::showTime(){
    cout << hour << ':' << minute << ':' << second << endl;
}

//编译器处理的代码
void CTime::showTime(CTime* const this){
    cout << this->hour << ':' << this->minute << ':' << this->second << endl;
}

int main(){
    CTime t1;
    t1.showTime();//我们的代码
    t1.showTime(&t1);//编译器处理的代码
}

静态成员

静态成员属于类,而不属于任何一个对象

私有静态数据成员只能在类内引用,公有或保护静态数据成员可以在类外通过类名引用。

静态数据成员

静态数据成员必须要在类外进行初始化

声明:
static <数据类型> <静态成员名>

初始化:
<类型标识符> <类名>::<静态数据成员名>=<值>

访问:
通过对象访问:person.count
通过类名访问:CPerson::count(推荐)

静态成员函数

访问静态数据成员

区别非静态成员函数,静态成员函数没有this指针,因为类的静态成员函数只有一个运行实例

静态函数成员可以直接引用该类的静态成员,但不能直接引用非静态数据成员

公有静态函数成员可以通过类名或对象名来调用

class CPerson{
private:
    char m_strNmae[20];
    long m_ID;
    double m_wage;
    static int m_nCount;//静态成员变量,表示一创建对象的数量
    static double m_nTotalWage;//所有雇员的工资总额
public:
    CPerson(const char* strName = nullptr, long ID = 0, double wage = 0);

    friend ostream& operator<<(ostream& os,const CPerson& person){
        os << "(" << person.m_strName << "," << person.m_ID << "," << person.m_wage;
        os << "Count:" << CPerson::m_nCount << ",Total Wage:" << CPerson::m_nTotalWage;
        return os;
    }
};

int CPerson::m_nCount = 0;
double CPerson::m_nTotalWage = 0;

CPerson::CPerson(const char* strName,long ID,double wage){
    strcpy(this->m_nstrName,strName);
    this->m_ID = ID;
    this->m_wage = wage;

    CPerson::m_nCount++;
    CPerson::m_nTotalWage += this->m_wage;
}

void main(){
    CPerson person1("LiMing",1101051,3000.0);
    cout << person1 << endl;

    CPerson person2("HanMeimei",1101052,5000,0);
    cout << person2 << endl;
}

输出
("LiMing",1101051,3000.0) Count:1,Total Wage:3000
("HanMeimei",1101052,5000,0) Count:2,Total Wage:8000

如果需要修改员工工资?

void CPerson::setWage(double wage){
    CPerson::m_ntotalWage += wage - this->m_wage;
    this->m_wage = wage;
}

原则:

  1. 尽量少使用全局性的数据
  2. 全局性数据的操作尽量单一

组合类

将一个已定义的类的对象作为另一个类的数据成员。

构造顺序:

  • 由内而外,先构造内嵌类,再构造组合类
  • 内嵌类的构造顺序:按内嵌对象在组合类中的定义顺序
<类名>::<类名>(形参表):对象成员1(形参表),对象成员2(形参表),…
{ 
    //类的初始化程序体
}

析构函数的调用顺序与构造函数相反

拷贝构造函数:

C::C(C &c):a(c.a)
{
    ...
}
Line::Line(Line &line):start(line.start),end(line.end)
{
    ...
}

例3(组合类的构造函数、析构函数和拷贝构造函数)

//CPoint.hpp
class CPoint
{
private:
    int x, y;
public:
    CPoint(int x = 0, int y = 0);
    CPoint(const CPoint& p);
    ~CPoint();

    friend ostream& operator<<(ostream& os, const CPoint& p) {
        os << "(" << p.x << "," << p.y << ")";
        return os;
    }
};

//CPoint.cpp
CPoint::CPoint(int x, int y) {
    this->x = x;
    this->y = y;
    cout << *this << ",constryctor..." << endl;
}

CPoint::CPoint(const CPoint& p) {
    this->x = p.x;
    this->y = p.y;
    cout << *this << ",copy,constryctor..." << endl;
}

CPoint::~CPoint() {
    cout << *this << ",destryctor..." << endl;
}

//CLine.hpp
class CLine {
private:
    CPoint start, end;
public:
    //CLine(const CPoint start, const CPoint end);
    CLine(const CPoint& start, const CPoint& end);
    CLine(const CLine& line);
    ~CLine();

    friend ostream& operator<<(ostream& os, const CLine& l) {
        os << l.start << "-->" << l.end;
        return os;
    }
};

//CLine.cpp
CLine::CLine(const CPoint& start, const CPoint& end) :end(end), start(start) {
    cout << *this << ",Line consttructor.." << endl;
}

CLine::CLine(const CLine& line) :start(line.start), end(line.end) {
    cout << *this << ",Line copy consttructor.." << endl;
}

CLine::~CLine() {
    cout << *this << ",Line desttructor.." << endl;
}

//main
int main() {
    CPoint p1, p2(2, 2);
    CLine line1(p1, p2), line2 = line1;

    cout << "p1:" << p1 << endl;
    cout << "p2:" << p2 << endl;
    cout << "line1:" << line1 << endl;
    cout << "line2:" << line2 << endl;
}

输出
(0,0),constryctor...
(2,2),constryctor...
(0,0),copy,constryctor...
(2,2),copy,constryctor...
(0,0)-->(2,2),Line consttructor..
(0,0),copy,constryctor...
(2,2),copy,constryctor...
(0,0)-->(2,2),Line copy consttructor..
p1:(0,0)
p2:(2,2)
line1:(0,0)-->(2,2)
line2:(0,0)-->(2,2)
(0,0)-->(2,2),Line desttructor..
(2,2),destryctor...
(0,0),destryctor...
(0,0)-->(2,2),Line desttructor..
(2,2),destryctor...
(0,0),destryctor...
(2,2),destryctor...
(0,0),destryctor...

思考:
如果将CLine(const CPoint& start, const CPoint& end);
换成CLine(const CPoint start, const CPoint end);
运行结果?

输出
(0,0),constryctor...
(2,2),constryctor...
(0,0),copy,constryctor...
(2,2),copy,constryctor...
(0,0),copy,constryctor...
(2,2),copy,constryctor...
(0,0)-->(2,2),Line consttructor..
(2,2),destryctor...
(0,0),destryctor...
(0,0),copy,constryctor...
(2,2),copy,constryctor...
(0,0)-->(2,2),Line copy constructor..
p1:(0,0)
p2:(2,2)
line1:(0,0)-->(2,2)
line2:(0,0)-->(2,2)
(0,0)-->(2,2),Line destructor..
(2,2),destryctor...
(0,0),destryctor...
(0,0)-->(2,2),Line destructor..
(2,2),destryctor...
(0,0),destryctor...
(2,2),destryctor...
(0,0),destryctor...

友元

声明:
friend class <友元类名>

如果A为B的友元类,则在B中声明friend class A,则A类的所有成员函数都成为B类的友元函数,都可以访问B类的私有和保护成员。

  1. 友元类的声明同样可以在类声明中的任何位置
  2. 友元类的所有成员函数都成为友元函数
  3. 友元关系不能传递
  4. 友元关系是单向的
//--------A.hpp--------
class B;//声明B是来自外部
class A{
private:
    double x;
public:
    A(double x=0);

    friend B;
    friend void absA(A& objA);
    friend ostream& operator<<(ostream& os, const A& objA){
        os << "A{x = " << objA.x << "}";
        return os;
    }
};

//--------A.cpp--------
A::A(double x){
    this->x = x;
}

//--------myFun.hpp--------
void absA(A& objA);

//--------myFun.cpp--------
void absA(A& objA){
    if(objA.x < 0>){
        objA.x = -objA.x;
        //一般情况,不能在函数内部直接访问objA.x,因为x为类A的私有成员变量
        //但是由于A中声明了absA()函数为A的友元函数,所以可以直接访问私有成员变量
    }
}

//--------B.hpp--------
#include "A.hpp"//需要引入A的头文件
class B{
public:
    double getX(A& objA);
    void setX(A& objA ,double x = 0);
    //因为类A中声明了B是A的友元,所以在类B中可以直接访问A的私有成员变量
};

//--------B.cpp--------
double getX(A& objA){
    return objA.x;
}

void setX(A& objA ,double x = 0){
    objA.x = x;
}

//--------main--------
int main(){
    A objA1(-5);
    B objB1;

    cout << objA1 << endl;
    absA(objA1);
    cout << objA1 << endl;
    objB1.setX(objA1, 100);
    cout << "A.x = " << objB1.getX(objA1) << endl;
}

输出
A{x = -5}
A{x = 5}
A.x = 100

常对象和常对象成员

常对象

使用const关键字声明的对象称为常对象。

常对象的声明形式为:
const <类名> <对象名>
<类名> const <对象名>

声明常对象的同时,也要进行初始化,该对象以后不能再被更新。

常成员函数

使用const 关键字声明的函数称为常成员函数。

常成员函数声明的形式为:
<类型标识符> <函数名>(参数表) const;

常成员函数不能更新对象的数据成员,也不能调用该类中没有用const修饰的成员函数。

继承与派生

派生类的一般声明语法为:

class <派生类名>:[继承方式] <基类名>
{
    //派生类成员声明;
}

其中,继承方式关键字为private、public和protected,系统的默认值为私有继承(private)。

public:基类成员的访问权限在派生类中保持不变
private:基类成员所有公有、保护成员在派生类中成为私有成员
protected:基类成员所有公有、保护成员在派生类中成为保护成员

  • 保持已有类的特性而构造新类的过程称为继承
  • 在已有类的基础上新增自己的特性而产生新类的过程称为派生
  • 被继承的已有类称为基类(或父类)
  • 派生出的新类称为派生类(或子类)

举个例子:

class CPoint {
private:
    double x,y;
public:
    CPoint(double x = 0,double y = 0);
    double getX();
    double getX();
    void setX(double x = 0);
    void setY(double y = 0);
};

class CCircle : public CPoint {
private:
    double r;
public:
    CCircle(double r);
    void setR(double r);
    void setCenter(double x = 0,double y = 0);
    double getR();
    double getUpperLeftX();
    double getUpperLeftY();
};

void CCircle::setCenter(double x,double y){
    this->setX(x);
    this->setY(y);
}

double CCircle::getR(){
    return this->r;
}

double CCircle::getUpperLeftX(){
    return this->getX() - this->r;
}

double CCircle::getUpperLeftY(){
    return this->getX() + this->r;
}

int main(){
    CCircle c1(100);
    c1.setCenter(200,200);

    cout << "(" << c1.getX() << "," << c1.getY() << ")";
    cout << "R = " << c1.getR() << endl;
    cout << "UpperLeft(" << c1.getUpperLeftX() << "," << c1.getUpperLeftY() << ")" << endl;
}

输出
(200,200) R = 100
UpperLeft(100,300)

注意:在本例中,组合类的方式优于继承的方式

同名覆盖

派生类可以对基类的成员重新定义。

举个例子:

class A{
public:
    void show(){ cout << "A::show" << endl; }
};

class B:public A{
    void show(){ cout << "B::show" << endl; }
    void display(){ show(); }
    //如何在B的对象中调用A的成员函数show()
    //void display(){ A::show(); }
};

int main(){
    A a;
    B b;
    a.show();
    b.show();
    b.display();
}

输出
A::show
B::show
B::show

派生类的构造函数和析构函数

  • 基类的构造函数、析构函数不能被继承,故派生类需要调用基类的构造函数对基类进行实例化。
  • 编译时,先生成基类构造函数的调用代码,再生成派生类构造函数的调用代码。
  • 基类构造函数的调用:
    • 隐式调用:派生类未指定基类构造函数,则调用基类默认的构造函数。
    • 显式调用:派生类指定基类构造函数,派生类将参数传递给基类的构造函数。
  • 析构过程与构造过程相反。
  • 拷贝构造函数:C::C(C &c1):B(c1){ ... }

构造函数的显示调用

<派生类名>::<派生类名>(<形参声明>):<基类名>(<参数表>)
{
    //派生类构造函数的函数体;
}
  • <形参声明>部分参数传递给基类构造函数
  • 基类有多个构造函数时,编译器根据<参数表>来确定调用哪一个基类构造函数

举个例子:

class CPoint{
protected:
    int x,y;
public:
    CPoint(int x=0,int y=0){
        this->x=x;
        this->y=y;
        cout<<"point constructor:"<<'['<<x<<","<<y<<']'<<endl;
    };

    ~CPoint(){
        cout<<"point destructor:"<<'['<<x<<","<<y<<']'<<endl;
    };
};

class CCircle{
protected:
    int radius;
public:
    CCircle(int x=0,int y=,int r=0):CPoint(x,y) {
        radius = r;
        cout<<"circle constructor:"<<'['<<radius<<']'<<'['<<x<<","<<y<<']'<<endl;
    };

    ~CCircle(){
        cout<<"circle destructor:"<<'['<<radius<<']'<<'['<<x<<","<<y<<']'<<endl;
    };
};

class CCylinder{
protected:
    int height;
public:
    CCylinder(int x=0,int y=,int r=0,int h=0):CCylinder(x,y,r) {
        height = h;
        cout<<"cylinder constructor:"<<'['<<height<<']'<<'['<<radius<<']'<<'['<<x<<","<<y<<']'<<endl;
    };

    ~CCylinder(){
        cout<<"cylinder destructor:"<<'['<<radius<<']'<<'['<<x<<","<<y<<']'<<endl;
    };
};

int main(){
    CCylinder cylinder(200,300,100,400);
}

输出
point constructor:[200,300]
circle constructor:[100][200,300]
cylinder constructor:[400][100][200,300]
cylinder destructor:[400][100][200,300]
circle destructor:[100][200,300]
point destructor:[200,300]

多继承

  • 单继承:一个派生类只有一个直接基类
  • 多继承一个派生类有多个直接基类

多继承定义形式:

class <派生类名>:[继承方式] 基类名1 ,…, [继承方式] 基类名n
{
	派生类成员声明;
};

多继承派生类的构造方法:

<派生类名>(<形参声明>):<基类名1>(<参数表1>) ,…, <基类名n>(<参数表n>)
{
	派生类构造函数体;
};

举个例子:

class Base1{
private:
    int a;
public:
    Base1(int x){
        a=x;
        cout<<"Base1 Constructor!"<<endl;
    }
    int getA(){ retrun a; }
};

class Base2{
private:
    int b;
public:
    Base2(int x){
        b=x;
        cout<<"Base2 Constructor!"<<endl;
    }
    int getB(){ retrun b; }
};

//定义顺序决定基类构造函数的调用顺序
class Deriver : public Base1, public Base2{
private:
    int c;
public:
    Derived(int x,int y,int z) : Base2(z), Base1(y) {
        c=x;
        cout<<"Derived Constructor!"<<endl;
    }
    void show(){
        cout<<getA()<<','<<getB()<<','<<c<<endl;
    }
};

int main(){
    Derived obj(1,2,3);
    obj.show();
}

输出
Base1 Constructor!
Base2 Constructor!
Derived Constructor!
2,3,1

虚基类

二义性问题的产生:被继承的基类有同名函数

class Base1{
public:
    int a;
    void set(int a){ this->a = a; }
};

class Base2{
public:
    int a;
    void set(int a){ this->a = a; }
};

//定义顺序决定基类构造函数的调用顺序
class MultiDeri : public Base1, public Base2{
public:
    int get(){ return this->a; }
};

int main(){
    Derived obj(1,2,3);
    obj.show();
}

ERROR!!!

二义性问题(菱形缺陷):
父类没有同名成员,但有共同的爷类

class A{
public:
    int a;
};

class B : public A{
public:
    int b;
};

class C : public A{
public:
    int c;
};

class D : public B,public C{
public:
    int d;
};

解决方式:

  1. 作用域限定符,d1.B::a,d1.C::a。
  2. 虚基类派生,对同名成员只保留一个副本。虚基类是一种派生方式,不是一种类。

虚基类及其构造函数的调用

  • 基类名称前加virtual即为虚基类
  • 调用虚基类的派生类的构造函数时,首先要调用虚基类的构造函数
  • 在多层次继承中,虚基类的构造函数只能调用一次
  • 虚基类的构造函数由最派生类(创建对象的类)调用,最派生类的非虚基类对虚基类的构造函数的调用将被忽略
  • 隐式调用,最派生类自动调用虚基类的默认构造函数
  • 若虚基类没有默认构造函数,虚基类的所有直接或间接派生类都必须显式调用
class A{
private:
    int a;
public:
    A(int a): a(a){
        cout<<"A constructor:"<<a<<endl;
    }
    ~A(){
        cout<<"A destroyed."<<endl;
    }
};

class B1 : public virtual A{
private:
    int b1;
public:
    B1(int a,int b1): A(a){
        this->b1=b1;
        cout<<"B1 constructor:"<<b1<<endl;
    }
    ~B1(){
        cout<<"B1 destroyed."<<endl;
    }
};

class B2 : public virtual A{
private:
    int b2;
public:
    B2(int a,int b2): A(a){
        this->b2=b2;
        cout<<"B2 constructor:"<<b2<<endl;
    }
    ~B1(){
        cout<<"B2 destroyed."<<endl;
    }
};

class C : public B1,public B2{
public:
    int c;
    C(int a,int b1,int b2,int c) : B1(a,b1),B2(a,b2),A(a){
        this->c=c;
        cout<<"C constructor:"<<c<<endl;
    }
    ~C(){
        cout<<"C destroyed."<<endl;
    }
};

int main(){
    C c1(1,2,3,4);
}

输出:
A constructor:1
B1 constructor:2
B2 constructor:3
C constructor:4
C destroyed.
B2 destroyed.
B1 destroyed.
A destroyed.

多态与虚函数

  • 静态多态(编译时多态):通过重载实现
  • 动态多态(运行时多态):通过虚函数实现

基类指针指向派生类对象

  • c++允许一个基类对象的指针指向其派生类的对象,但不允许派生类对象的指针指向其基类对象。
  • 将一个基类对象的指针指向其派生类的对象,通过该指针只能访问派生类中从基类继承的共有成员,不能访问派生类中新增的成员,除非通过强制类型转换将基类指针转换为派生类指针。
class A {
private:
	int a;
public:
	void setA(int i) { a = i; }
	void showA() { cout << "a = " << a << endl; }
};

class B :public A {
private:
	int b;
public:
	void setB(int i) { b = i; }
	void showB() { cout << "B = " << b << endl; }
};

int main() {
	A a, * pa;
	B b, * pb;
	//通过基类指针pa访问B中从A继承的公有成员
	pa->setA(100);
	pa->showA();
	pb = (B*)pa;//积累指针强制转化为派生类指针
	//不能通过基类指针pa访问派生类自己定义的成员
	pb->setB(200);
	pb->showB();
	//以下语句错误
	//pb = &a;//不能将 "A *" 类型的值分配到 "B *" 类型的实体
	//pa->setB(50);//类 "A" 没有成员 "setB"
	//pa->showB();//类 "A" 没有成员 "showB"
}

输出
a = 100
b = 200

原理解析

思路:用基类指针指向派生类对象,通过基类指针来统一调用不同派生类的成员,从而实现多态。

问题:这样只能调用基类成员,不能调用派生类成员。

A a, *pa;//pa为基类指针
B b, *pb;//pb为派生类指针
pa = &b;//基类指针pa指向派生类对象b
pb = (B*)pa;//基类指针签字转化为派生类指针
pa = &a;//错误:派生类只能不能指向基类

虚函数

  • 基类指针指向派生类对象,只能调用基类成员。
  • 基类指针指向派生类对象,通过虚函数,可调用派生类成员。
    • 此时,多态形成,运行时多态,动态多态。
    • 同一个指针,可调用不同对象的同名方法,得到不同相应,对同一消息做出不同相应。
    • 为同一类继承结构中所有类的同一行为提供统一接口。
  • 虚函数声明形式如下:
    • virtual<函数类型><函数名>(<形成声明>);
    • 声明为虚函数,意味着该成员函数在派生类中可能被重新定义。
class A {
public:
	virtual void show() { cout << "A::show" << endl; }
};

class B :public A {
public:
	void show() { cout << "B::show" << endl; }
};

class C :public A {
public:
	void show() { cout << "C::show" << endl; }
};

int main() {
	A a, * pa;
	B b;
	C c;
	pa = &a; pa->show();
	pa = &b; pa->show();
	pa = &c; pa->show();
}

输出
A::show
B::show
C::show

class CAnimal {
public:
	virtual void run() {}
};

class CPerson :public CAnimal {
public:
	void run() { cout << "Person:Run on 2 legs." << endl; }
};

class CDog :public CAnimal {
public:
	void run() { cout << "Dog:Run on 4 legs." << endl; }
};

class CBug :public CAnimal {
public:
	void run() { cout << "Person:Run on 6 legs." << endl; }
};

void animalRun(CAnimal* pa) {
	pa->run();
}

int main() {
	CPerson person1;
	CDog dog1;
	CBug bug1;
	animalRun(person1);
	animalRun(dog1);
	animalRun(bug1);
}

输出
Person:Run on 2 legs.
Dog:Run on 4 legs.
Person:Run on 6 legs.

虚构造函数、虚析构函数

  • 构造函数不能被定义为虚函数,虚函数需要通过对应的虚指针vtable来调用。
  • 析构函数建议全部定义为虚函数,释放对象时,只有定义为虚析构函数才能保证先释放派生类再释放基类。
class Base {
public:
	Base() { cout << "Base constructor." << endl; }
	virtual ~Base()  { cout << "Base destructor." << endl; }
};

class Derived {
public:
	Derived() { cout << "Derived constructor." << endl; }
	virtual ~Derived() { cout << "Derived destructor." << endl; }
};

int main() {
	Base* pBase = new Derived;
	delete pBase;
}

输出
Base constructor.
Derived constructor.
Derived destructor.
Base destructor.

纯虚函数与抽象类

  • 有时在基类中无法给出基类中虚函数的实现代码,这时可以把虚函数声明为纯虚函数,
    • virtual<函数类型><函数名>(<形参声明>)=0;
  • 纯虚函数没有函数体,及提供一个统一的接口。
  • 含有纯虚函数的类称为抽象类,抽象类不能实例化。
class CAnimal{
    virtual void run() = 0;
};
class CShape {
protected:
	double s;
public:
	CShape() { s = 0; }
	virtual double area() = 0;
};

class  CCircle :public CShape {
private:
	double r;
public:
	CCircle(double r) { this->r = r; }
	double area() { return 3.14 * r * r; }
};

int main() {
	CShape* pShape;
	CCircle circle(3);
	pShape = &circle;
	cout << "area = " << pShape->area() << endl;
}

纯虚函数与抽象类语法规则

  • 若必须定义为抽象类,而不合适的函数可作为纯虚函数,可将析构函数定义为纯虚函数。
  • 为了通过编译,必须在内外实现该纯虚函数。(有的编译器不用实现)
class CShape {
protected:
	double s;
public:
	CShape() { s = 0; }
	virtual double area() { return 0; }
	virtual ~CShape() = 0;
};
CShape::~CShape() {};

重载

int my_abs(int val) {
	return (val < 0) ? -val : val;
}
int my_abs(float val) {
	return (val < 0) ? -val : val;
}
int main() {
	int i = 100;
	float f = -125.78f;
	cout << my_abs(i) << endl;
	cout << my_abs(f) << endl;
}

输出
100
125.78

构造函数重载

class Box {
private:
	double height, width, depth;
public:
	Box();
	Box(double h);
	Box(double h, double w);
	Box(double h, double w, double d);
	//超级构造函数
	//Box(double h = 0, double w = 0, double d = 0);
};

int main() {
	Box box1;
	Box box2(1.0);
	Box box3(1.0, 2.0);
	Box box4(1.0, 2.0, 3.0);
}

重载规则

  • 重载:函数名相同,参数类型不同
  • 以下重载非法:
    • 返回类型不同:
      • long fun(int);
      • float fun(int);
    • 不能利用引用重载
      • void fun(int&);
      • void fun(int);
  • const可用于重载,以下合法:
    • void fun();
    • void fun() const;

运算符重载

运算符重载的一般形式:

<函数类型>operator<运算符>(<形参表>);
{
    //<函数体>
}

运算符函数原型:

  • 输出流运算符函数原型
    • ostream& operator<<(ostream& os, <操作对象>)
  • 加法运算符函数原型
    • <返回类型> operator+(<操作对象>,<操作对象>)
  • 自增运算符函数原型
    • 前缀:<返回类型> operator++(<操作对象>)
    • 后缀:<返回类型> operator++(<操作对象>,int)
  • 下标运算符函数原型
    • <返回类型>& operator[](<操作对象>,int)

普通形式函数形式重载

class CComplex {
private:
	double r, i;
public:
	CComplex(double r = 0, double i = 0);
	virtual ~CComplex();
	friend CComplex operator+(CComplex c1, CComplex c2);
	//...输出流重载
};

CComplex operator+(CComplex c1, CComplex c2) {
	CComplex CTemp;
	CTemp.r = c1.r + c2.r;
	CTemp.i = c1.i + c2.i;
	return CTemp;
}

int main() {
	CComplex c1(1, 1), c2(2, 2);
	CComplex c3 = c1 + c2;
	cout << c1 << endl;
	cout << c2 << endl;
	cout << c3 << endl;
}

输出
(1,1)
(2,2)
(3,3)

成员函数形式重载

class CComplex1 {
private:
	double r, i;
public:
	CComplex1(double r = 0, double i = 0);
	virtual ~CComplex1();
	CComplex1 operator+(CComplex1 c2);
	//...输出流重载
};


CComplex1 CComplex1::operator+(CComplex1 c2) {
//实际:CComplex1 CComplex1::operator+(CComplex1* const this, CComplex1 c2)
	CComplex1 CTemp;
	CTemp.r = this->r + c2.r;
	CTemp.i = this->i + c2.i;
	return CTemp;
}

int main() {
	CComplex1 c1(1, 1), c2(0.1, 0.1);
	CComplex1 c3 = c1 + c2;
	cout << c1 << endl;
	cout << c2 << endl;
	cout << c3 << endl;
}

输出
(1,1)
(0.1,0.1)
(1.1,1.1)

自增运算符重载

class Counter {
private:
	int value;
public:
	Counter() { value = 0; }
	Counter operator++();//前缀运算符
	Counter operator++(int);//后缀运算符
	void display() {
		cout << "value:" << value << endl;
	}
};

Counter Counter::operator++() {
	value++;
	return *this;
}

Counter Counter::operator++(int) {
	Counter temp;
	temp.value = this->value++;
	return temp;
}

int main() {
	Counter obj1, obj2;

	obj2 = obj1++;
	obj1.display();
	obj2.display();

	obj2 = ++obj1;
	obj1.display();
	obj2.display();
}

输出
value:1
value:0
value:2
value:2

下标运算符重载

class Interger {
private:
	int* array;
	int len;
public:
	Interger(int len) {
		this->len = len;
		array = new int[len];
	}
	int& operator[](int i);//重载运算符[]
	~Interger() { delete[]array; }
};

int& Interger::	operator[](int i) {
	if (i<0 || i>len - 1) {
		cout << "错误:数据越界!" << endl;
	}
	return array[i];
}

int main() {
	Interger array(10);
	int i = 0;

	for (i = 0; i < 10; i++) {
		array[i] = i + 1;
		cout << array[i] << " ";
	}
	cout << ":i = " << i << endl;
	cout << array[i] << endl;
}

输出
1 2 3 4 5 6 7 8 9 10 : i = 10
错误:数据越界!

posted @ 2022-12-07 10:17  零零六七  阅读(9)  评论(0)    收藏  举报