• C++之面向对象编程20170912


    /******************************************************************************************************************/

    一、C++面向对象编程_访问控制和继承

    1.继承关系

    class Person {

    private:

    static int cnt;  

    char *name;   

    int age;

    public:

    static int getCount(void);

    }

    class Student : public Person {

    //定义Student类,继承了Person

    };

    2.访问控制

    private 外界不可见,不能直接访问

    protected 外界不可见,不能直接访问;子类可以访问

    public 外界可以直接访问

    3.调整访问权限

    派生类内部可以调整继承过来的成员(派生类可见的父类成员)的访问权限,可以提升或降低权限

    使用using来实现,例:

    class Father {

    private:

             int money;

    protected:

             int room_key;

    }

    class Son : public Father {

    private:

             int toy;

             //using Father::it_skill;

    public:

             using Father::room_key;//使用using来调整权限为public

    }

    4.不同继承方式(类型)

    主要包括三种继承类型:public ,protected,private//公有继承,保护继承,私有继承

    继承产生的权限结果见图

    基类private成员派生类不可见(不可见 权限自然不会被改变)

    公有继承,基类成员访问权限不变

    保护继承,基类成员访问权限全变为protected

    私有继承, 基类成员访问权限全变为private

    1).无论哪种继承方式,在派生类内部使用父类时并无差别

    2). 不同的继承方式,会影响这两方面:

    外部代码(类外)对派生类的使用(一般是创建对象使用)、

    派生类的子类(派生类的子类怎么使用派生类里面的成员)

    5.覆写

    子类可以覆写从父类继承来的成员函数

    6.派生类对象的空间分布

    1).

    class Student : public Person {

    private:

             int grade;

             void setGrade(int grade) {this->grade = grade;}

             int getGrade(void) {return grade;}

    public:

             void printInfo(void)

             {

                       cout<<"Student ";

                       Person::printInfo();//调用父类的打印函数  

    }

    };

    2).派生类对象内存空间分部

    派生类对象内存空间=父类内存空间+自己的独特的属性

    //类似在基类内存空间后面追加一份属于派生类自己独特属性的空间

    3).(向上)转型

    void test_func(Person &p)

    {

             p.printInfo();

    }

    int main(int argc, char **argv)

    {

             Person p("lisi", 16);

             Student s;

             s.setName("zhangsan");

             s.setAge(16);

             test_func(p);

             test_func(s); /* 等同于 Person &p = (s里面的Person部分);

    p引用的是"s里面的Person部分",所以test_func里使用的是person的printInfo函数*/

             s.printInfo();//

    return 0;

    }

    派生类是基类的一种,所以 基类=派生类 时 等同于派生类里面的基类部分赋值给基类

    /******************************************************************************************************************/

    二、C++面向对象编程_多重继承

    1.多重继承

    class Sofabed : public Sofa, public Bed {

    //不写public默认是私有继承

    };

    2.多个基类中有相同成员函数的情形

    1).可以指定成员函数具体是属于哪个基类的

    s.Sofa::setWeight(100);

    2).抽出相同的成员形成基类的父类

    同时使用虚拟继承,来保证基类共用其父类的同一成员,从而保证派生类使用的是唯一成员(只使用了一个成员,派生类所占内存中只有该成员只占一份空间)

    class Sofa : virtual public Furniture

    {

    };

    class Bed : virtual public Furniture

    {

    };

    class Sofabed : public Sofa, public Bed

    {

    };

    int main(int argc, char **argv)

    {

             Sofabed s;

             s.watchTV();

             s.sleep();

             s.setWeight(100);

            

             return 0;

    }

    尽量避免使用多重继承,这样会使得程序更加复杂,更容易出错

    /******************************************************************************************************************/

    三、C++面向对象编程_再论构造函数

    1.构造顺序

    先父后儿:

    1)先调用基类的构造函数,

    先虚拟继承的基类后一般继承的基类(相同类型的,先继承哪个就先调用哪个构造函数)

    注意,虚拟继承的基类,构造函数只执行一次(虚拟基类中,只使用一份内存,所以只执行一次)

    2)自身

    先对象成员,后自己的

    2.派生类调用基类的有参构造函数

    在派生类的有参构造函数中使用:号加上类名(参数)

    class LeftRightSofabed : public Sofabed, virtual public LeftRightCom {

    private:

             Date date;

             Type type;

    public:

             LeftRightSofabed()

    {

    cout <<"LeftRightSofabed()"<<endl;

    }

             LeftRightSofabed(char *str1, char *str2, char *str3) : Sofabed(str1), LeftRightCom(str2), date(str3)//派生类的有参构造函数中使用:号加上类名(参数)

    {

    cout <<"LeftRightSofabed()"<<endl;

    }

    };

    3.类中对象的构造函数的调用

    在派生类的有参构造函数中使用:号加上对象名(参数)

    class LeftRightSofabed : public Sofabed, virtual public LeftRightCom {

    private:

             Date date;

             Type type;

    public:

             LeftRightSofabed()

    {

    cout <<"LeftRightSofabed()"<<endl;

    }

    LeftRightSofabed(char *str1, char *str2, char *str3) : Sofabed(str1), LeftRightCom(str2), date(str3)

    {//派生类的有参构造函数中使用:号加上对象名(参数)

    cout <<"LeftRightSofabed()"<<endl;

    }

    };

    /******************************************************************************************************************/

    四、C++面向对象编程_多态

    1.直接传入

    没有转型,实现不了对应的状态,即无法实现多态

    class Human

    {

    public:

             void eating(void) { cout<<"use hand to eat"<<endl; }

    };

    class Englishman : public Human

    {

    public:

             void eating(void) { cout<<"use knife to eat"<<endl; }

    };

    class Chinese : public Human

    {

    public:

             void eating(void) { cout<<"use chopsticks to eat"<<endl; }

    };

    void test_eating(Human& h)

    {

             h.eating();

    }

    int main(int argc, char **argv)

    {

             Human h;

             Englishman e;

             Chinese c;

             test_eating(h);

             test_eating(e);

             test_eating(c);//执行结果全部调用基类的函数,没实现多态

             return 0;

    }

    2.引入虚函数

    基类函数名加上virtual表示虚函数,基类对应的函数可以加也可以不加,同时派生类对应的函数可以加也可以不加,已经是虚函数的属性了,此时在向上转型中派生类实现的与基类一样的函数就可以被调用(一般来说也就是实现了覆写),否则在向上转型中调用的都是基类的(即没有加virtual的情况)。

    因此,析构函数要加上virtual,加上后在向上转型中,即基类指针指向派生类的对象时(多态性),如果删除该转换后的基类指针;就会调用该指针指向的派生类析构函数(因为是虚函数),而派生类的析构函数又自动调用基类的析构函数,这样整个派生类的对象完全被释放。如果析构函数不被声明成虚函数,则编译器实施静态绑定,在删除转换后的基类指针时,只会调用基类的析构函数而不调用派生类析构函数,这样就会造成派生类对象析构不完全。

    注意,派生类的对象当然可以调用自己的成员函数不管是否有virtual。

    向上转型:子对象转换为父对象(基类指针可以指向派生类的对象),转换时,如果代码中有执行基类没有的成员函数,则编译就会报错,所以是安全的。

    class Human

    {

    private:

             int a;

    public:

             virtual void eating(void)

    {

    cout<<"use hand to eat"<<endl;

    }

    };

    class Englishman : public Human

    {

    public:

             void eating(void) { cout<<"use knife to eat"<<endl; }

    };

    class Chinese : public Human

    {

    public:

             void eating(void) { cout<<"use chopsticks to eat"<<endl; }

    };

    void test_eating(Human& h)

    {

             h.eating();

    }

    int main(int argc, char **argv)

    {

             Human h;

             Englishman e;

             Chinese c;

             test_eating(h);

             test_eating(e);//执行结果调用派生类的函数,实现多态

             test_eating(c);//执行结果调用派生类的函数,实现多态

             return 0;

    }

    内部实现机制

    静态联编:非虚函数,在编译时就确定了调用哪一个

    动态联编:

    1).类里面有虚函数,则其对象里有指针,指向虚函数表,子类对象里由于继承也有这个指针,指向虚函数表

    2).调用函数的时候,找到指针指向的虚函数表,然后调用里面的虚函数

    所以使用虚函数时,对象占用的空间会变大

    见下图:

     

    3.虚函数注意事项

    1).函数参数使用对象的指针或者引用,才有多态

    传值时,无多态(强制转换为基类类型,只剩下基类部分,就没有指针了,静态联编,调用的就只能是基类了)

    2).只有类的成员函数才能声明为虚函数

    3).静态成员不能是虚函数

    4).内联函数不能是虚函数

    5).构造函数不能是虚函数

    6).析构函数一般都声明为虚函数

    这样才可以先释放派生类的(调用自己的清理函数),再调用基类的析构(清理)函数,而不是只释放基类的

    class Human

    {

    private:

             int a;

    public:

             virtual void eating(void) { cout<<"use hand to eat"<<endl; }

             virtual ~Human() { cout<<"~Human()"<<endl; }

    };

    class Englishman : public Human

    {

    public:

             void eating(void) { cout<<"use knife to eat"<<endl; }

             virtual ~Englishman() { cout<<"~Englishman()"<<endl; }

    };

    class Chinese : public Human

    {

    public:

             void eating(void) { cout<<"use chopsticks to eat"<<endl; }

             virtual ~Chinese() { cout<<"~Chinese()"<<endl; }

    };

    7).重载函数(函数参数不同),不可以设为虚函数

    重载已经是多态了,所以不可以设置为虚函数了。

    或者可以理解为,多态是相同的调用方法,可以调用到不同类里面实现的函数,而重载函数参数不同,已经不是相同的调用方法了,所以不能也需要设为虚函数

    8).覆写(覆盖)函数(函数参数,返回值都相同),可以设置为虚函数

    9).函数参数都相同,返回值不同时,不可以设置为虚函数,有个例外:

    当返回值是本类指针或引用时,可以设置为虚函数

    /******************************************************************************************************************/

    五、C++面向对象编程_类型转换

    1.隐式类型转换:

    double d = 100.1;

    int i = d;  // double转为int

    char *str = "123";

    int *p = str; // char *转为int *

    2.显式类型转换:

    兼容c的类型转换,同时具有新的转换特性,

    如下的各种转换语句的意思都是把expression转换成type-id类型的对象,

    1).reinterpret_cast<type_id>(expression)//重新解析转换 强制类型转换

    //是模版函数

    //相当于c风格的用小括号()实现的强制类型转换

    int *p = reinterpret_cast<int *>(str2); // char *转换为int * ,相当于C风格的()

    无法转换const或volatile属性的变量(不能转换只读的为可读可写),不然会编译报错

    2).const_cast<type_id>(expression)//模版函数

    用来去除原来类型的const或volatile属性

    char *str2 = const_cast<char *>(str); //转换const型变量使用到

    int *p = reinterpret_cast<int *>(str2); // char *转换为int * ,相当于C风格的()

    3).dynamic_cast<type_id>(expression)//动态类型转换

    该运算符把expression转换成type-id类型的对象。

    I、Type-id必须是类的指针、类的引用或者void *;

    如果type-id是类指针类型,那么expression也必须是一个指针;

    如果type-id是一个引用,那么expression也必须是一个引用。

    例子:

    void test_eating(Human& h)

    {//Human& h传进来的由程序来决定的,不能事先确定,这个确定过程是动态的,所以称为动态转换 

    Englishman *pe;

             Chinese    *pc;

             h.eating();

             /* 想分辨这个"人"是英国人还是中国人? */

             if (pe = dynamic_cast<Englishman *>(&h))//指针的转换

    /*根据指针找到虚函数表找到类信息从而知道对象是否属于某一个类(虚函数表中有类信息以及继承信息)           

             cout<<"This human is Englishman"<<endl;

             if (pc = dynamic_cast<Chinese *>(&h))

                       cout<<"This human is Chinese"<<endl;

    /*根据指针找到虚函数表找到类信息从而知道对象是否属于某一个类,同时虚函数表里面除了类信息还有继承信息,所以也能知道类属于哪个父类*/

    }

    所以动态类型转换只能用在含有虚函数的类里面,即用于多态的场合

    II、动态转换可以转换指针也可以转换引用

    如果一个引用不能指向一个实体就没有存在的必要了,引用也不能用来作判断,所以会导致程序崩溃,

    所以动态转换经常使用指针而不是引用

    III、主要用于类层次间的上行转换(派生类对象转为基类对象)和下行转换(基类对象转为派生类对象),还可以用于类之间的交叉转换

    在类层次间进行上行转换时,dynamic_cast和static_cast的效果是一样的;

    在进行下行转换时,dynamic_cast具有类型检查的功能,比static_cast更安全

    进行上下行转换是通过虚函数表的类继承信息来判断,转换有可能成功或失败

    4).static_cast<type_id>(expression)

    //转换在编译时由编译器决定的,转换是事先确定的,所以是静态转换,所以不能转换时编译器会报错

    Expression //待转换的对象

    type_id //转换后的类型

    返回转换后的变量

    该运算符把expression转换为type-id类型,

    但运行时没有类型检查来保证转换的安全性。

    例子:

    Human h;

             //Englishman e;

             //Chinese c;

             Guangximan g;

             Englishman *pe;

             pe = static_cast<Englishman *>(&h);//可以转换但不安全

             //Englishman *pe2 = static_cast<Englishman *>(&g);//不能转换

            

    Chinese *pc = static_cast<Chinese *>(&g);//可以转换

    使用场景:

    I、用于类层次结构中基类和子类之间指针或引用的转换。

    II、进行上行转换(把子类的指针或引用转换成基类表示)是安全的(转换时,如果代码中有执行基类没有的成员函数,则编译就会报错,所以是安全的);

    III、进行下行转换(把基类指针或引用转换成子类指针或引用)时,由于没有动态类型检查,所以是不安全的。

    IV、用于基本数据类型之间的转换,如把int转换成char,把int转换成enum:这种转换的安全性也要开发人员来保证。

    V、把void指针转换成目标类型的指针(不安全!!)

    VI、把任何类型的表达式转换成void类型。

    注意:static_cast不能转换掉expression的const、volitale、或者__unaligned属性。

  • 相关阅读:
    高级人力资源管理最喜欢的工具;笔迹分析测试的六大好处
    我与时尚MM的那些事儿
    当幸福来敲门
    perl 模板
    一些R函数【自己使用过,保存】
    关于异步加载、缓存图片、软引用等
    android线程同步
    现半透明的popupwindow
    android中的MotionEvent 及其它事件处理
    android客户端从服务器端获取json数据并解析
  • 原文地址:https://www.cnblogs.com/yuweifeng/p/7511517.html
Copyright © 2020-2023  润新知