面向对象三大特性

一,封装

面向对象的精髓在于封装,面向对象要求数据应该尽可能被封装,越多的东西被封装,就越少的人可以看到他,而越少的人可以看到他,就有越大的弹性去改变他。因此,越多的东西被封装,改变那些东西的能力就越大。这就是推崇封装的原因,改变事物而只影响有限客户。通过C++提供的访问控制符来控制成员的访问权限:

  • private:只能由该类中的函数或其友元函数访问。在类外不能访问,该类的对象也不能访问。
  • protected:可以被该类中的函数、子类的函数或其友元函数访问。在类外不能访问,该类的对象也不能访问。
  • public:可以被该类中的函数、子类的函数或其友元函数访问,也可以由该类的对象访问。

二,继承

2.1,C函数库的局限性

除非厂商提供了库函数的源码,否则无法根据自己的需求对库函数进行修改。

使用继承带来的优点:

  • 面向对象编程的主要目的之一是提供可重用的代码,类继承提供了比修改源码更好的方法,不需要访问源码就可以派生出类。尤其是当项目比较庞大时,重用经过测试的代码比重新编写代码要好的多。
  • 使用继承与多态机制,可以很方便的对系统的功能进行扩展。

2.2,如何实现继承?

自定义一个基类Person,定义一个派生类Student

class Person{
private:
    string name;
public:
    Person():name("default"){
        cout<<"Person default constructor."<<endl;
    }
    Person(const string &name):name(name){
        cout<<"Person constructor."<<endl;
    }
    void show(){
     cout<<"name: "<<name<<endl;
   }
};
class Student : public Person{
private:
    string number;
public:
    Student():number("000000"){
        cout<<"Student default constructor."<<endl;
    }
    Student(const string &name, const string &number):Person(name), number(number){
        cout<<"Student constructor."<<endl;
    }
    void show(){
     Person::show();
     cout<<"number: "<<number<<endl;
   }
};

C++提供了三种类型的继承,分别是public、private、protected:

  • public:基类的public与protected属性在派生类中不变。
  • private:基类的public与protected属性在派生类中变成private。
  • protected:基类的public与protected属性在派生类中变成protected。
  • private与protected之间的区别只在基类派生的类中才会体现出来。派生类的成员可以直接访问基类的保护成员,但不能访问基类的私有成员。

2.3,派生类构造函数调用顺序

在派生类构造函数中没有显示调用基类的构造函数,创建派生类对象时,将使用基类的默认构造函数。下面在Student派生类构造函数中显示调用基类的构造函数。

Student(const string &name, const string &number):Person(name), number(number){
        cout<<"Student constructor."<<endl;
    }

创建派生类对象时,首先创建基类对象。派生类构造函数通过成员初始化列表调用基类的构造函数,然后再初始化派生类新增的数据成员。

2.4,派生类析构函数调用顺序

派生类的析构函数被执行时, 执行完派生类的析构函数后, 自动调用基类的析构函数。

#include <iostream>
using namespace std;

class Base
{
public:
    ~Base()
    {
        cout<<"Base Destroy."<<endl;
    }
};

class Derived : public Base
{
public:
    ~Derived()
    {
        cout<<"Derived Destroy."<<endl;
    }
};

void main()
{
    Derived obj;
}

输出结果:

Derived Destroy.
Base Destroy.

2.5,派生类与基类之间的特殊关系

  • 派生类对象可以使用基类的非私有方法。
  • 基类指针或引用在不进行显示转换的情况下指向派生类对象,但基类指针或引用只能调用基类方法。
  • 派生类对象是一个特殊的基类对象,任何使用基类对象的地方都可使用派生类对象替换。

三,多态

3.1,什么是多态?

多态(polymorphism)的字面意思是多种表现形式,多态性可以简单地概括为”一个接口,多种方法”,程序在运行时才决定调用的函数,换句话说,方法的行为应取决于调用方法的对象,它是面向对象编程领域的核心概念。多态的目的是为了实现接口重用,也就是说,不论传递过来的究竟是那个类的对象,函数都能够通过同一个接口调用到对应于各自对象的实现方法。

3.2,使用多态解决分不清对象类型的问题

class Person{
protected:
    string name;
public:
    Person():name("default"){
    }
    Person(const string &name):name(name){
    }
    void show(){
        cout<<"name: "<<name<<endl;
    }
};
class Student : public Person{
protected:
    string number;
public:
    Student():number("000000"){
    }
    Student(const string &name, const string &number):Person(name), number(number){
    }
    void show(){
        cout<<"name: "<<name<<endl;
        cout<<"number: "<<number<<endl;
    }
};
void test(Person &p){
    p.show();
}
int main(){
    Person p("person");
    Student s("student", "11041722");
    test(p);
    test(s);
    return 0;
}

输出结果:

name: person
name: student

程序分析:

对象p与s分别是基类和派生类的对象,而函数test的形参是Person类的引用。按照类继承的特点,编译器把Student类对象看做是一个Person类对象。我们想利用test函数达到的目的是,传递不同类对象的引用,分别调用不同类的重载了的show成员函数,但是程序的运行结果却出乎人们的意料,编译器分不清传进来的是基类还是派生类对象,无论是基类对象还是派生类对象调用的都是基类的show成员函数。

使用多态解决上面的问题

为了要解决上述不能正确分辨对象类型的问题,c++提供了一种叫做多态性(polymorphism)的技术来解决问题。对于上面的程序,这种能够在编译时就能够确定哪个重载的成员函数被调用的情况被称做静态联编,而系统在运行时,能够根据其类型确定调用哪个重载的成员函数的能力,称为多态性或叫动态联编。下面使用多态技术解决上面的问题,动态联编正是解决多态问题的方法。 把基类中的show成员函数声明为虚函数:

class Person{
protected:
    string name;
public:
    Person():name("default"){
    }
    Person(const string &name):name(name){
    }
    virtual void show(){
        cout<<"name: "<<name<<endl;
    }
};
class Student : public Person{
protected:
    string number;
public:
    Student():number("000000"){
    }
    Student(const string &name, const string &number):Person(name), number(number){
    }
    virtual void show(){
        cout<<"name: "<<name<<endl;
        cout<<"number: "<<number<<endl;
    }
};
void test(Person &p){
    p.show();
}
int main(){
    Person p("person");
    Student s("student", "11041722");
    test(p);
    test(s);
    return 0;
}

输出结果:

name: person
name: student
number: 11041722

3.3,虚函数

虚函数是实现多态的重要机制,下面声明与定义虚函数:

class Person{
protected:
    string name;
public:
    Person():name("default"){}
    Person(const string &name):name(name){}
    virtual void show(){
        cout<<"name: "<<name<<endl<<endl;
    }
};

class Student : public Person{
protected:
    string number;
public:
    Student():number("000000"){}
    Student(const string &name, const string &number):Person(name), number(number){}
    virtual void show(){
        cout<<"name: "<<name<<endl;
        cout<<"number: "<<number<<endl<<endl;
    }
};

int main(){
    Person p("person");
    Student s("student", "11041722");
    p.show();
    s.show();
    return 0;
}

虚函数的特点:

  • 如果方法是通过引用或指针而不是对象调用的,它将确定使用哪一种方法。如果没有使用关键字virtual,程序将根据引用类型或指针类型选择方法。如果使用了关键字virtual,程序将根据指针或引用实际指向的对象的类型来选择方法。
  • 在基类的方法声明中使用关键字virtual可使该方法在基类以及所有的派生类中是虚的。

注意:

关键字virtual只用于类声明的方法原型中,而没有用于方法的定义中。

3.4,虚函数的工作原理

通常,编译器处理虚函数的方法是:给每个对象添加一个隐藏成员。隐藏成员中保存了一个指向函数地址数组的指针。这种数组称为虚函数表(virtual function table)。虚函数表中存储了为类对象进行声明的虚函数的地址。例如,基类对象包含一个指针,该指针指向基类中所有虚函数的地址表。派生类对象将包含一个指向独立地址表的指针。如果派生类提供了虚函数的新定义,该虚函数表将保存新函数的地址。如果派生类没有重新定义虚函数,该虚函数表将保存基类中同名虚函数的地址。如果派生类定义了新的虚函数,则该函数的地址也将被添加到虚函数表中。调用虚函数时,程序将通过对象中的vptr指针,找到虚函数表,然后在虚函数表中查找要调用的函数的地址。

3.5,虚函数带来的额外开销

使用虚函数时,在内存与执行速度方面都有一定的开销。虽然非虚函数的效率比虚函数高,但是不具有动态编联功能。

  • 每个对象都将增大,增大量为存储隐藏成员(是一个指针)的空间。
  • 对于每一个类,编译器都将创建一个虚函数表。
  • 对于每个函数调用,都需要执行一项额外的操作,即到表中查找地址。

3.6,使用虚函数应注意的问题

  • 构造函数不能是虚函数。创建派生类对象时,将调用派生类的构造函数,派生类的构造函数将调用基类的构造函数,这种顺序不同于继承机制。派生类不继承基类的构造函数,所以将类的构造函数声明为虚的没什么意义。
  • 析构函数应当是虚函数,除非类不用作基类。
  • 友元不能是虚函数,友元不是类成员,而只有成员函数才能是虚函数。
  • 重新定义将隐藏方法。重新定义继承的方法并不是重载。如果重新定义派生类中的函数,无论参数列表是否相同,该操作将隐藏所有的同名基类方法。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值