zoukankan      html  css  js  c++  java
  • 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属性。

  • 相关阅读:
    168. Excel Sheet Column Title
    171. Excel Sheet Column Number
    264. Ugly Number II java solutions
    152. Maximum Product Subarray java solutions
    309. Best Time to Buy and Sell Stock with Cooldown java solutions
    120. Triangle java solutions
    300. Longest Increasing Subsequence java solutions
    63. Unique Paths II java solutions
    221. Maximal Square java solutions
    279. Perfect Squares java solutions
  • 原文地址:https://www.cnblogs.com/yuweifeng/p/7511517.html
Copyright © 2011-2022 走看看