C++基础之面向对象高级

发布于:2025-06-21 ⋅ 阅读:(167) ⋅ 点赞:(0)

一、继承 inherit

1.1 基本概念

继承是在一个已经存在的类的基础上新建一个类,新建的类拥有已经存在的类的特性。主要体现的是代码复用的思想。
● 已经存在的类被称为“基类(Base Class)”或“父类”
● 新建的类被称为“派生类”或“子类(Sub Class)”
基类和派生类是相对的,一个类即可能是基类又可能是派生类,取决于对比的类。

1.2 函数隐藏

通常派生类和基类都有做出一些差异化,其中函数隐藏就是一种修改基类函数的方式,另外也可以通过增加内容等方法做出差异化。

#include <iostream>

using namespace std;

class Father
{
private:
    string name="张";
public:
    

    
    string get_name()
    {
        return name;
    }
    void work()
    {
        cout<<"我是一个老师"<<endl;
    }

};

class Son:public Father
{

public:
//注意:基类的private成员子类可以继承,但是能否对其进行访问取决于基类是否开放了接口
//    void set_name(string name)
//    {
//        this->name=name;
//    }
    //若子类不满意父类的某个成员函数,可进行函数隐藏(保持函数签名相同以覆盖)
    void work()
    {
       cout<<"我是一个程序员"<<endl;
    }

};

int main()
{
   Son s;
   cout<<s.get_name()<<endl;
   s.work();

    return 0;
}

1.3 构造函数

1.3.1 继承中构造函数的限制

C++中规定,派生类无法继承父类的构造函数,但派生类的任意一个构造函数都必须直接或间接调用基类的任意一个构造函数。

默认情况下,编译器会为每个类增加一个无参构造函数,同时会在派生类的无参构造函数中调用基类的无参构造函数(因此1.2代码可以正常运行)。

如果基类中只有一个有参构造函数,则子类调用不到基类的无参构造函数,会报错

如果使用之前的知识解决上面的问题,可以给基类增加无参构造函数或给基类的有参构造函数增加参数默认值。

#include <iostream>

using namespace std;

class Father
{
private:
    string name;
public:

 //方法1-->增加无参构造函数
//    Father():name("张"){}
 //方法2-->给父类有参构造函数增加默认值
    Father(string name="张"):name(name){}



    string get_name()
    {
        return name;
    }
    void work()
    {
        cout<<"我是一个老师"<<endl;
    }

};

class Son:public Father
{

public:

    void work()
    {
       cout<<"我是一个程序员"<<endl;
    }

};

int main()
{
   Son s;
   cout<<s.get_name()<<endl;

    return 0;
}

1.3.2 透传构造

如果编译器默认调用不到基类的构造函数,则可以手动在派生类的构造函数中调用基类的构造函数。
#include <iostream>

using namespace std;

class Father
{
private:
    string name="张";
public:

   Father(string name):name(name){}

    string get_name()
    {
        return name;
    }
    void work()
    {
        cout<<"我是一个老师"<<endl;
    }

};

class Son:public Father
{

public:

    //透传
    Son():Father("张"){}

     //透传
    Son(string name):Father(name){}

    void work()
    {
       cout<<"我是一个程序员"<<endl;
    }

};

int main()
{
   Son s;
   cout<<s.get_name()<<endl; //张
   s.work();

   Son s2("王");
    cout<<s2.get_name()<<endl; //王

    return 0;
}

1.3.3 委托构造

#include <iostream>

using namespace std;

class Father
{
private:
    string name="张";
public:

   Father(string name):name(name){}

    string get_name()
    {
        return name;
    }

    void work()
    {
        cout<<"我是一个老师"<<endl;
    }

};

class Son:public Father
{

public:

    //委托
    Son():Son("王"){}

     //透传
    Son(string name):Father(name){}

    void work()
    {
       cout<<"我是一个程序员"<<endl;
    }

};

int main()
{
   Son s;
   cout<<s.get_name()<<endl; //王
   s.work();

   Son s2("王");
    cout<<s2.get_name()<<endl;//王

    return 0;
}

但是委托构造要避免闭环

1.3.4 继承构造

这是C++11的新特性,继承构造不是构造函数能继承,而是表现出类似于继承的特性,本质上是透传构造。

#include <iostream>

using namespace std;

class Father
{
private:
    string name="张";
public:
   Father():name("张"){};
   Father(string name):name(name){}

    string get_name()
    {
        return name;
    }

    void work()
    {
        cout<<"我是一个老师"<<endl;
    }

};

class Son:public Father
{

public:

  using Father::Father;
//有了这句话以后,编译器会帮我们自动添加以下的代码
//    Son():Father(){}
//    Son(string name):Father(name){}

    void work()
    {
       cout<<"我是一个程序员"<<endl;
    }

};

int main()
{
   Son s;
   cout<<s.get_name()<<endl; //张
   s.work();

   Son s2("王");
    cout<<s2.get_name()<<endl;//王

    return 0;
}

 1.4 对象的创建与销毁

#include <iostream>

using namespace std;

class Value
{
private:
    string name;
public:
    Value(string name):name(name){
        cout<<name<<"构造函数"<<endl;
    }
    ~Value()
    {
        cout<<name<<"析构函数"<<endl;
    }


};

class Father
{
public:
    static Value s_value;
    Value value=Value("Father成员变量");

    Father()
    {
        cout<<"Father的构造函数"<<endl;

    }
    ~Father()
    {
        cout<<"Father的析构函数"<<endl;

    }


};
Value  Father::s_value=Value("Father静态成员变量");

class Son:public Father
{
public:
    static Value s_value;
    Value value=Value("Son成员变量");

     Son()
    {
        cout<<"Son的构造函数"<<endl;

    }
    ~ Son()
    {
        cout<<"Son的析构函数"<<endl;

    }


};

Value  Son::s_value=Value("Son静态成员变量");

int main()
{
  cout<<"主函数开始"<<endl;



    Son* s=new Son;
    cout<<"调用s的功能"<<endl;
    delete s;


  cout<<"主函数结束"<<endl;
    return 0;
}

 

规律:

1. 对象创建与销毁流程相反(对称)。

2. 静态成员变量的生命周期是程序的整个运行周期。

3. 同类型的代码或内存开辟都是基类优先,销毁都是派生类优先。

 1.5 多重继承

1.5.1 基本使用

C++支持多继承,即一个派生类可以有多个基类。

多重继承的派生类对于每个基类的关系都可以看做是一个单一继承。

#include <iostream>

using namespace std;

class Sofa
{

public:
    void sit()
    {
        cout<<"坐在沙发上"<<endl;
    }

};

class Bed
{
public:
    void lay()
    {
        cout<<"躺在床上"<<endl;
    }

};

class Sofabed:public Sofa,public Bed
{
    
};

int main()
{
  
    Sofabed sb;
    sb.sit();
    sb.lay();
            
    
    return 0;
}

1.5.2 二义性 之 重名成员

在多重继承时,如果多个基类之间拥有相同名称的成员时,通过派生类对象调用这些重名成员时,会出现二义性问题。

解决方法:在调用重名成员前使用 基类名称:: 来显式调用。

#include <iostream>

using namespace std;

class Sofa
{

public:
    void sit()
    {
        cout<<"坐在沙发上"<<endl;
    }
    void position()
    {
         cout<<"放在客厅"<<endl;
    }

};

class Bed
{
public:
    void lay()
    {
        cout<<"躺在床上"<<endl;
    }
    void position()
    {
         cout<<"放在卧室"<<endl;
    }

};

class Sofabed:public Sofa,public Bed
{


};

int main()
{

    Sofabed sb;
    sb.sit();
    sb.lay();
    
//  sb.position(); 出现二义性错误!!
    
    //使用作用域限定符
    sb.Bed::position();
    sb.Sofa::position();
    
    return 0;
}

1.5.3 二义性 之 菱形继承

菱形继承也成为钻石继承,指的是一个派生类有多个基类,这多个基类又有一个共同的基类。
解决方法1:作用域限定符
#include <iostream>

using namespace std;
class Furniture
{
public:
    void func()
    {
        cout<<"我是家具"<<endl;
    }
    
};

class Sofa:public Furniture
{


};

class Bed:public Furniture
{


};

class Sofabed:public Sofa,public Bed
{


};

int main()
{

    Sofabed sb;
    sb.sit();
    sb.lay();

//    sb.func();
    //方法一
    sb.Bed::func();
    sb.Sofa::func();
   
    
    return 0;
}

解决方法2:虚继承 

 虚继承底层原理与编译器相关,当Sofa类和Bed类使用virtual继承Furniture类时,Sofa类的对象和Bed类的对象都会多一个隐藏指针,这个指针指向了Furniture类的一个表——虚基类表,查询这个表可以找到Furniture的函数调用位置。

真正的虚继承定义是Sofa与SofaBed、Bed与SofaBed的继承关系,此时SofaBed对象就会拥有两个继承来的隐藏虚基类表指针,在SofaBed对象调用Furniture的相关函数时,通过隐藏成员指针查询的调用位置进行比对来避免二义性问题。

         

        

 

#include <iostream>

using namespace std;
class Furniture
{
public:
    void func()
    {
        cout<<"我是家具"<<endl;
    }
};

class Chair:public Furniture
{

};

class Sofa:virtual public Furniture
{


};
class Bed:virtual public Furniture
{

};
class Sofabed:public Sofa ,public Bed
{

};

int main()
{
    Sofabed sb;
    Sofa s;
    Bed b;
    Chair c;
    Furniture f;
    cout<<sizeof(f)<<endl; //1
    cout<<sizeof(s)<<" "<<sizeof(b)<<endl;//4  4
    cout<<sizeof(sb)<<endl;//8
    sb.func();//我是家具


    return 0;
}

1.6 权限

1.6.1 三种权限修饰符

  • private
  • protected
  • public

三种权限修饰符修饰成员的访问区别如下: 

在类内

派生类内

全局(例如主函数中)

private

√

×

×

protected

√

√

×

public

√

√

√

#include <iostream>

using namespace std;
class Father
{
private:
    string str1="私有成员";
protected:
    string str2="保护成员";
public:
    string str3="共有成员";
   void test()
    {
       cout<<str1<<endl;
       cout<<str2<<endl;
       cout<<str3<<endl;
    }
};

class Son:public Father
{
public:
   void test2()
    {
//        cout<<str1<<endl; 无法访问
        cout<<str2<<endl;
        cout<<str3<<endl;

    }
};

int main()
{

  Father f;
  f.test();
  Son s;
  s.test2();

//  cout<<f.str1<<endl; 无法访问
//  cout<<f.str2<<endl;  无法访问
  cout<<f.str3<<endl;


    return 0;
}

1.6.2 公有继承

公有继承下,基类的private成员可以被派生类继承,但是无法直接访问。基类的protected和public成员继承到派生类仍然作为派生类的protected和public的成员(权限不变)。

#include <iostream>

using namespace std;
class Father
{
private:
    string str1="私有成员";
protected:
    string str2="保护成员";
public:
    string str3="共有成员";
   void test()
    {
       cout<<str1<<endl;
       cout<<str2<<endl;
       cout<<str3<<endl;
    }
};

class Son:public Father
{
public:

//    str1继承了,但无法直接访问
//    str2继承了,并作为Son的protected成员-->类内和派生类内访问
//    str3继承了,并作为Son的public成员-->均可访问


};

class Grandson:public Son
{
public:
    Grandson()
    {
        cout<<str2<<endl;
        cout<<str3<<endl;
    }
};

int main()
{

  Son s;
  Grandson g;


//  cout<<s.str2<<endl;  无法访问
  cout<<s.str3<<endl;


    return 0;
}

1.6.3 保护继承

保护继承下,基类的private成员可以被派生类继承,但是无法直接访问。基类的protected和public成员继承到派生类都变成派生类的protected成员。
#include <iostream>

using namespace std;
class Father
{
private:
    string str1="私有成员";
protected:
    string str2="保护成员";
public:
    string str3="共有成员";
   void test()
    {
       cout<<str1<<endl;
       cout<<str2<<endl;
       cout<<str3<<endl;
    }
};

class Son:protected Father
{
public:

//    str1继承了,但无法直接访问
//    str2继承了,并作为Son的protected成员-->类内和派生类内访问
//    str3继承了,并作为Son的protected成员-->类内和派生类内访问


};

class Grandson:public Son
{
public:
    Grandson()
    {
        cout<<str2<<endl;
        cout<<str3<<endl;
    }
};

int main()
{

  Son s;
  Grandson g;


//  cout<<s.str2<<endl;  无法访问
//  cout<<s.str3<<endl;  无法访问


    return 0;
}

1.6.4 私有继承

私有继承下,基类的private成员可以被派生类继承,但是无法直接访问。基类的protected和public成员继承到派生类都变成派生类的private成员。
#include <iostream>

using namespace std;
class Father
{
private:
    string str1="私有成员";
protected:
    string str2="保护成员";
public:
    string str3="共有成员";

};

class Son:private Father
{
public:

//    str1继承了,但无法直接访问
//    str2继承了,并作为Son的private成员-->只能在Son类内访问
//    str3继承了,并作为Son的private成员-->只能在Son类内访问
    Son()
    {
         cout<<str2<<endl;
         cout<<str3<<endl;
    }

};

class Grandson:public Son
{
public:
    Grandson()
    {
        
       // cout<<str2<<endl; 无法访问
       // cout<<str3<<endl; 无法访问
    }
};

int main()
{

  Son s;
  Grandson g;


//  cout<<s.str2<<endl;  无法访问
//  cout<<s.str3<<endl;  无法访问


    return 0;
}

二、多态 polymorphism

2.1 函数覆盖 override

函数覆盖也被称为函数重写,函数覆盖是多态的触发条件之一。

函数覆盖与函数隐藏相似,最大的区别是基类被覆盖的函数需要使用virtual修饰,被virtual修饰的函数是虚函数

虚函数有以下性质:

● 虚函数具有传递性,基类的虚函数可以把派生类新覆盖的函数变成虚函数。

● 成员函数可以设置为虚函数,静态成员函数不能设置为虚函数。

● 构造函数不能设置为虚函数,析构函数可以设置为虚函数。

● 如果函数声明与定义分离,只需要使用virtual修饰声明即可。
●  在 C++ 1 1 中 , 可以 使用 override 关键字 验证 是否 覆盖 成功 。


微信公众号

今日签到

点亮在社区的每一天
去签到