zoukankan      html  css  js  c++  java
  • 《程序猿面试宝典》学习记录6

    印象笔记同步分享:《程序猿面试宝典》学习记录6


    《程序猿面试宝典》学习记录6

    第10章 面向对象

    10.1 面向对象的基本概念

    考点1:面向对象三大特性
    1)封装性:封装,也就是把客观事物封装成抽象的类。而且类能够把自己的数据和方法仅仅让可信的类或者对象操作,对不可信的进行信息隐藏。(开闭原则)
    在C++中类中成员的属性有:public, protected。 private。这三个属性的訪问权限依次减少。
    2)继承性:继承是指这样一种能力:它能够使用现有类的全部功能,并在无需又一次编写原来的类的情况下对这些功能进行扩展。

    (里氏代换)interface
    3)多态性:多态性(polymorphisn)是同意你将父对象设置成为和一个或很多其它的他的子对象相等的技术,赋值之后,父对象就能够依据当前赋值给它的子对象的特性以不同的方式运作。简单的说,就是一句话:同意将子类类型的指针赋值给父类类型的指针。实现多态,有二种方式,覆盖,重载。


    覆盖。是指子类又一次定义父类的虚函数的做法。


    重载。是指同意存在多个同名函数,而这些函数的參数表不同(也许參数个数不同,也许參数类型不同。也许两者都不同)。

    virtual
    考点2:C++中空类默认产生哪些类成员函数?
    对于一个空类,编译器默认产生4个成员函数:
    1)默认构造函数
    2)析构函数
    3)拷贝构造函数
    4)赋值函数

    10.2 类和结构

    考点1:结构能否够有构造函数、析构函数及成员函数?假设能够。那么结构和类还有什么差别吗?
    差别是class中变量默认是private,struct中的变量默认是public。

    struct能够有构造函数、析构函数。之间也能够继承甚至是多重继承,等等。C++中的struct事实上和class意义一样,唯一不同就是struct里面默认的訪问控制是public。class中默认訪问控制是private。C++中存在structkeyword的唯一意义就是为了让C程序猿有个归属感,是为了让C++编译器兼容曾经用C开发的项目。
    考点2:准确理解结构struct

    struct Test
    {
        Test(int){}
        Test(){}
        void fun(){}
    };
    int main()
    {
        Test a(1);
        a.fun();
        Test b(); //这里和上面不一样这个相当于声明一个函数,函数名为b。返回值为Test。传入參数为空,作者的目的应该是声明一个类型Test。变量为b的变量,所以改为 Test b 下一句才不会错误
        b.fun();//所以这个会错误发生
        return 0;
    }
    

    构造函数:Test a(1)表示存在默认拷贝构造函数 Test b 表示存在默认无參数的构造函数

    10.3 成员变量

    考点1:成员变量的初始化

    class test
    {
        private:
        int a;                普通成员
        const int b;          常量成员
        static int c;         静态成员
        static const int d;   静态常量成员
        int &e;               引用类型成员
    }
    

    记住以下几个原则:
    1)常量成员(注意没有静态常量成员)和引用类型成员仅仅能用成员初始化列表对成员变量初始化
    2)静态成员和静态常量成员因为是类共同拥有的,不是属于某一个对象的。因此不能在构造函数中初始化3)静态成员(注意没有静态常量成员)必须在类外初始化
    4)引用变量必须初始化才干使用
    5)仅仅有静态常量成员才干在类中直接赋值初始化
    6)初始化列表初始化变量顺序依据成员变量的声明顺序来运行。而和在程序中赋值先后顺序无关

    class test
    {
        int a=1;                     错误。对象还没有构造,尚未分配内存空间
        int a;
        const b;
        static int c = 1;            错误。不能够再声明时初始化
        static int c;
        const static int d = 1;      唯有静态常量成员才干在类中直接赋值初始化
        int &e;                      引用类型必须用成员初始化列表
        public:
            test(int _e):b(1),e(_e)  引用初始化必须为左值
            test(int _e):b(1),e(_e){}
    };
    int test::c=1;
    const int test::d=1;
    

    考点2:C++成员变量初始化顺序

    class A  
    {  
    private:  
        int n1;  
        int n2;  
    
    public:  
        A():n2(0),n1(n2+2){}  
    
        void Print(){  
            cout << "n1:" << n1 << ", n2: " << n2 <<endl;    
        }  
    };  
    
    int main()  
    {  
    
        A a;  
        a.Print();  
    
        return 1;  
    }
    

    输出结果为:n1:1874928131 n2:0
    成员变量在使用初始化列表初始化时。与构造函数中初始化成员列表的顺序无关。仅仅与定义成员变量的顺序有关。由于成员变量的初始化次序是依据变量在内存中次序有关,而内存中的排列顺序早在编译期就依据变量的定义次序决定了。


    改动为:

    A()  
    {  
        n2 = 0;  
        n1 = n2 +2;  
    }
    

    这样输出就是n1:2 n2:0
    考点3:区分于常量成员和静态常量成员
    以下这个类声明正确吗?为什么?

    class A 
    {
        const int Size = 0;
    };
    

    解析:这道程序题存在着成员变量问题。常量必须在构造函数的初始化列表里初始化或者将其设置成static。

    正确的程序例如以下:

    class A
    {
        A()
        {
            const int Size = 1;
        }
    };
    或者:
    class A
    {
        static const int Size = 1;
    };
    

    10.4 构造函数和析构函数

    考点1:构造函数与析构函数有什么特点?
    构造函数与析构函数差别:
    1)构造函数和析构函数是在类体中说明的两种特殊的成员函数。


    构造函数的功能是在创建对象时。使用给定的值来将对象初始化。


    析构函数的功能是用来释放一个对象的。

    在对象删除前,用它来做一些清理工作。它与构造函数的功能正好相反。
    2)构造函数能够有一个參数也能够有多个參数,构造函数能够重载,即定义多个參数个数不同的函数;可是析构函数不指定数据类型,也没有參数,且一个类中仅仅能定义一个析构函数。不能重载。
    3)程序不能直接调用构造函数,在创建对象时系统自己主动调用构造函数;析构函数能够被调用,也能够由系统调用。析构函数能够内联
    析构函数会被自己主动调用的两种情况:①当这个函数结束时,对象的析构函数被自己主动调用。②当一个对象使用new运算符被动创建时候,在使用delete运算符释放它,delete将会自己主动调用析构函数。


    考点2:为什么虚拟的析构函数是必要的?

    保证了在不论什么情况下,都不会出现因为析构函数未被调用而导致的内存泄露。
    考点3:析构函数能够为 virtual 型。构造函数则不能,为什么?
    虚函数採用一种虚调用的办法。

    虚调用是一种能够在仅仅有部分信息的情况下工作的机制,特别同意我们调用一个仅仅知道接口而不知道其准确对象类型的函数。可是假设要创建一个对象,你势必要知道对象的准确类型,因此构造函数不能为 virtual。
    考点4:假设虚函数是很有效的,我们能否够把每一个函数都声明为虚函数?
    不行。这是由于虚函数是有代价的:由于每一个虚函数的对象都必须维护一个 v 表,因此在使用虚函数的时候会产生一个系统开销。假设仅是一个非常小的类,且不行派生其它类。那么根本不是必需使用虚函数。
    考点5:显式调用析构函数

    #include <iostream>using namespace std;
    
    class MyClass
    {
    public:
        MyClass()
        {
            cout << "Constructors" << endl;
        }
    
        ~MyClass()
        {
            cout << "Destructors" << endl;
        }
    };
    
    int main()
    {
        MyClass* pMyClass =  new MyClass;
        pMyClass->~MyClass();
        delete pMyClass;
    
        return 0;
    }
    

    执行结果:
    结果:
    Constructors
    Destructors //这个是显示调用的析构函数
    Destructors //这个是delete调用的析构函数
    总结:
    new的时候,事实上做了三件事。一是:调用::operator new分配所需内存。

    二是:调用构造函数。

    三是:返回指向新分配并构造的对象的指针。
    delete的时候,做了两件事,一是:调用析构函数,二是:调用::operator delete释放内存。

    10.5 拷贝构造函数和赋值函数

    考点1:编写类string的构造函数、析构函数、赋值函数
    已知类的原型为:

    class String
    {
    public:
        String(const char *str = NULL);             普通构造函数
        String(const String &other);                拷贝构造函数
        ~String(void);                              析构函数
        String & operate = (const String &other);   赋值函数
    private:
        char *m_data;                               用于保存字符串
    };
    析构函数:
    String::~String(void)
    {
        delete[] m_data;
    }
    构造函数:
    String::String(const char *str)
    {
        if(str==NULL)
        {
            m_data = new char[1];//若能加NULL,推断更好
            *m_data = '';
        }
        else
        {
            int length = strlen(str);
            m_data = new char[length+1];
            strcpy(m_data,str);
        }
    }
    拷贝构造函数:
    String::String(const String &other)
    {
        int length = strlen(other.m_data);
        m_data = new char[length+1];
        strcpy(m_data,other.m_data);
    }
    赋值函数:
    String & String::operate = (const String &other)
    {
        if(this == &other) //检查自复制
        {
            return *this;
        }
        delete [] m_data;  //释放原有的资源
        int length = strlen(other.m_data); //分配新内存资源。并复制内容
        m_data = new char[length+1];
        strcpy(m_data,other.m_data);
        return *this;//返回本对象的引用
    }
    

    考点2:区分默认构造函数、默认拷贝构造函数、默认析构函数、默认赋值函数

    A()                        默认构造函数
    A(const A&)                默认拷贝构造函数
    ~A                         默认析构函数
    A& operator = (const A &)  默认赋值函数
    CTemp a(b);                复制构造函数,C++风格的初始化
    CTemp a = b;               复制构造函数,只是这样的风格仅仅是为了与C兼容
    CTemp a;
    a = b;                     赋值函数
    

    区分:不同之处在于:赋值运算符处理两个已有对象,即赋值前B应该是存在的;复制构造函数是生成一个全新的对象。即调用复制构造函数之前B不存在。拷贝构造函数是构造函数。不返回值,而赋值函数须要返回一个对象自身的引用,以便赋值之后的操作。
    考点3:拷贝构造函数深入理解
    1)拷贝构造函数里能调用private成员变量吗?
    拷贝构造函数其时就是一个特殊的构造函数,操作的还是自己类的成员变量。所以不受private的限制。
    2)下面函数哪个是拷贝构造函数,为什么?

    X::X(const X&);             拷贝构造函数
    X::X(X);            
    X::X(X&, int a=1);          拷贝构造函数
    X::X(X&, int a=1, int b=2); 拷贝构造函数
    

    对于一个类X, 假设一个构造函数的第一个參数是下列之中的一个:

    a) X&    b) const X&   c) volatile X&    d) const volatile X&
    

    且没有其它參数或其它參数都有默认值,那么这个函数是拷贝构造函数.
    3)一个类中存在多于一个的拷贝构造函数吗?
    类中能够存在超过一个拷贝构造函数。

    class X 
    { 
        public:       
        X(const X&);      // const 的拷贝构造
        X(X&);            // 非const的拷贝构造
    };
    

    4)怎样防止默认拷贝发生?
    声明一个私有拷贝构造函数。

    甚至不必去定义这个拷贝构造函数,这样由于拷贝构造函数是私有的。假设用户试图按值传递或函数返回该类对象。将得到一个编译错误。从而能够避免按值传递或返回对象。
    5)拷贝构造函数一定要使用引用传递呢。我上网查找了很多资料,大家的意思基本上都是说假设用值传递的话可能会产生死循环。
    6)理解默认拷贝构造函数
    请看以下一段程序:

    #include<iostream>using namespace std;
    
    class B
    {
    private:
        int data;
    public:
        B()
        {
            cout<<"defualt constructor"<<endl;
        }
    
        ~B()
        {
            cout<<"destructed "<<endl;
        }
    
        B( int i) : data(i)
        {
            cout<<"constructed by parameter"<<data<<endl;
        }
    };
    
    B Play( B b )
    {
        return b;
    }
    
    int main ()
    {
        B temp = Play(5);
        return 0;
    }
    

    1)该程序输出结果是什么?为什么会有这种输出?
    2)B(int i):data(i),这样的使用方法的专业术语叫什么?
    3)Play(5),形參类型是类,而5是个常量。这样写合法吗?为什么?
    (1)输出结果例如以下:

    constructed by parameter   在Play(5)处,5通过隐含的类型转换调用了B::B( int i )
    destructed                 Play(5) 返回时,參数的析构函数被调用
    destructed                 temp的析构函数被调用。temp的构造函数调用的是编译器生存的拷贝构造函数。这个须要特别注意
    

    2)待參数的构造函数。冒号后面的是成员变量初始化列表(member initialization list)
    3)合法。单个參数的构造函数假设不加入explicitkeyword,会定义一个隐含的类型转换;加入explicitkeyword会消除这样的隐含转换。
    考点4:怎样理解浅拷贝和深拷贝?
    浅拷贝
    指的是在对象复制时。仅仅对对象中的数据成员进行简单的赋值,默认拷贝构造函数运行的也是浅拷贝。

    大多情况下“浅拷贝”已经能非常好地工作了,可是一旦对象存在了动态成员,那么浅拷贝就会出问题了。

    class Rect
    {
    public:
        Rect()      // 构造函数。p指向堆中分配的一空间
        {
            p = new int(100);
        }
        ~Rect()     // 析构函数,释放动态分配的空间
        {
            if(p != NULL)
            {
                delete p;
            }
        }
    private:
        int width;
        int height;
        int *p;     // 一指针成员
    };
    
    int main()
    {
        Rect rect1;
        Rect rect2(rect1);   // 复制对象
        return 0;
    }
    

    这个代码执行会出现这种错误。
    在执行定义rect1对象后。因为在构造函数中有一个动态分配的语句,因此执行后的内存情况大致例如以下:

    在使用rect1复制rect2时。因为运行的是浅拷贝。仅仅是将成员的值进行赋值。这时 rect1.p = rect2.p,也即这两个指针指向了堆里的同一个空间,例如以下图所看到的:

    当然,这不是我们所期望的结果,在销毁对象时。两个对象的析构函数将对同一个内存空间释放两次,这就是错误出现的原因。我们须要的不是两个p有同样的值,而是两个p指向的空间有同样的值,解决的方法就是使用“深拷贝”。
    深拷贝:在“深拷贝”的情况下,对于对象中动态成员。就不能只简单地赋值了,而应该又一次动态分配空间

    class Rect
    {
    public:
        Rect()      // 构造函数,p指向堆中分配的一空间
        {
            p = new int(100);
        }
        Rect(const Rect& r)
        {
            width = r.width;
            height = r.height;
            p = new int;    // 为新对象又一次动态分配空间
            *p = *(r.p);
        }
        ~Rect()     // 析构函数,释放动态分配的空间
        {
            if(p != NULL)
            {
                delete p;
            }
        }
    private:
        int width;
        int height;
        int *p;     // 一指针成员
    };
    


    此时rect1的p和rect2的p各自指向一段内存空间,但它们指向的空间具有同样的内容,这就是所谓的“深拷贝”。

    10.6 多态的概念

    考点1:理解多态性
    多态性能够简单地概括为“一个接口,多种方法”,程序在执行时才决定调用的函数,它是面向对象编程领域的核心概念。

    多态(polymorphisn)。字面意思多种形状。
    多态的作用是什么呢?
    封装能够使得代码模块化,继承能够扩展已存在的代码。他们的目的都是为了代码重用

    而多态的目的则是为了接口重用。也就是说。不论传递过来的到底是那个类的对象。函数都能够通过同一个接口调用到适应各自对象的实现方法。
    考点2:覆盖(override)和重载(overload)差别?
    C++多态性是通过虚函数来实现的,虚函数同意子类又一次定义成员函数,而子类又一次定义父类的做法称为覆盖(override),或者称为重写。(这里我认为要补充。重写的话能够有两种。直接重写成员函数和重写虚函数,仅仅有重写了虚函数的才干算作是体现了C++多态性)而重载则是同意有多个同名的函数。而这些函数的參数列表不同,同意參数个数不同。參数类型不同。或者两者都不同。编译器会依据这些函数的不同列表,将同名的函数的名称做修饰,从而生成一些不同名称的预处理函数。来实现同名函数调用时的重载问题。

    但这并没有体现多态性。
    考点3:多态与非多态的差别?
    多态与非多态的实质差别就是函数地址是早绑定还是晚绑定。假设函数的调用,在编译器编译期间就能够确定函数的调用地址,并生产代码,是静态的,就是说地址是早绑定的。而假设函数调用的地址不能在编译器期间确定。须要在执行时才确定。这就属于晚绑定。
    考点4:多态的使用方法

    #include<iostream>using namespace std;
    
    class A
    {
    public:
        void foo()
        {
            printf("1
    ");
        }
        virtual void fun()
        {
            printf("2
    ");
        }
    };
    class B : public A
    {
    public:
        void foo()
        {
            printf("3
    ");
        }
        void fun()
        {
            printf("4
    ");
        }
    };
    int main(void)
    {
        A a;
        B b;
        A *p = &a;
        p->foo();
        p->fun();
        p = &b;
        p->foo();
        p->fun();
        return 0;
    }
    

    第一个p->foo()和p->fuu()都非常好理解,本身是基类指针。指向的又是基类对象,调用的都是基类本身的函数,因此输出结果就是1、2。


    第二个输出结果就是1、4。

    p->foo()和p->fuu()则是基类指针指向子类对象。正式体现多态的使用方法,p->foo()因为指针是个基类指针。指向是一个固定偏移量的函数,因此此时指向的就仅仅能是基类的foo()函数的代码了。因此输出的结果还是1。而p->fun()指针是基类指针。指向的fun是一个虚函数,因为每一个虚函数都有一个虚函数列表。此时p调用fun()并非直接调用函数,而是通过虚函数列表找到相应的函数的地址,因此依据指向的对象不同,函数地址也将不同。这里将找到相应的子类的fun()函数的地址。因此输出的结果也会是子类的结果4。
    笔试的题目中另一个另类測试方法。

    B *ptr = (B *)&a;  ptr->foo();  ptr->fun();
    

    问这两调用的输出结果。

    这是一个用子类的指针去指向一个强制转换为子类地址的基类对象。结果,这两句调用的输出结果是3,2。


      并非非常理解这样的使用方法,从原理上来解释,因为B是子类指针。尽管被赋予了基类对象地址。可是ptr->foo()在调用的时候,因为地址偏移量固定,偏移量是子类对象的偏移量,于是即使在指向了一个基类对象的情况下,还是调用到了子类的函数,尽管可能从始到终都没有子类对象的实例化出现。而ptr->fun()的调用,可能还是因为C++多态性的原因,因为指向的是一个基类对象。通过虚函数列表的引用,找到了基类中fun()函数的地址。因此调用了基类的函数。由此可见多态性的强大,能够适应各种变化。不论指针是基类的还是子类的,都能找到正确的实现方法。
    考点5:隐藏规则
    这里“隐藏”是指派生类的函数屏蔽了与其同名的基类函数。规则例如以下:

    (1)假设派生类的函数与基类的函数同名。可是參数不同。

    此时,不论有无virtual
    keyword,基类的函数将被隐藏(注意别与重载混淆)。
    (2)假设派生类的函数与基类的函数同名,而且參数也同样,可是基类函数没有virtual
    keyword。

    此时,基类的函数被隐藏(注意别与覆盖混淆)。

    小结:1、有virtual才可能发生多态现象
    2、不发生多态(无virtual)调用就按原类型调用
    #include<iostream>using namespace std;
    class Base
    {
    public:
        virtual void f(float x)
        {
            cout<<"Base::f(float)"<< x <<endl;
        }
        void g(float x)
        {
            cout<<"Base::g(float)"<< x <<endl;
        }
        void h(float x)
        {
            cout<<"Base::h(float)"<< x <<endl;
        }
    };
    class Derived : public Base
    {
    public:
        virtual void f(float x)
        {
            cout<<"Derived::f(float)"<< x <<endl;   //多态、覆盖
        }
        void g(int x)
        {
            cout<<"Derived::g(int)"<< x <<endl;     //隐藏
        }
        void h(float x)
        {
            cout<<"Derived::h(float)"<< x <<endl;   //隐藏
        }
    };
    int main(void)
    {
        Derived d;
        Base *pb = &d;
        Derived *pd = &d;
        // Good : behavior depends solely on type of the object
        pb->f(3.14f);   // Derived::f(float) 3.14
        pd->f(3.14f);   // Derived::f(float) 3.14
        // Bad : behavior depends on type of the pointer
        pb->g(3.14f);   // Base::g(float)  3.14
        pd->g(3.14f);   // Derived::g(int) 3 
        // Bad : behavior depends on type of the pointer
        pb->h(3.14f);   // Base::h(float) 3.14
        pd->h(3.14f);   // Derived::h(float) 3.14
        return 0;
    }
    1)函数Derived::f(float)覆盖了Base::f(float)。

    2)函数Derived::g(int)隐藏了Base::g(float),而不是重载。 3)函数Derived::h(float)隐藏了Base::h(float),而不是覆盖。

    考点6:纯虚函数
    1)定义
    纯虚函数是在基类中声明的虚函数。它在基类中未定义。但要求不论什么派生类都要定义自己的实现方法。在基类中实现纯虚函数的方法是在函数原型后加“=0”
    virtual void funtion()=0
    2)引入原因
    1、为了方便使用多态特性,我们经常须要在基类中定义虚拟函数。
    2、在非常多情况下,基类本身生成对象是不合情理的。比如,动物作为一个基类能够派生出老虎、孔雀等子类,但动物本身生成对象明显不合常理。


    为了解决上述问题。引入了纯虚函数的概念,将函数定义为纯虚函数(方法:virtual ReturnType Function()= 0;),则编译器要求在派生类中必须予以重写以实现多态性。同一时候含有纯虚拟函数的类称为抽象类,它不能生成对象。这样就非常好地攻克了上述两个问题。
    补充:相似概念
    1)多态性
    指同样对象收到不同消息或不同对象收到同样消息时产生不同的实现动作。C++支持两种多态性:编译时多态性,执行时多态性。
    a、编译时多态性:通过重载函数实现
    b、执行时多态性:通过虚函数实现。
    (宏、内联函数、模板都能够再编译时候解析,可是虚函数是在执行时才干确定的)
    2)虚函数
    虚函数是在基类中被声明为virtual,并在派生类中又一次定义的成员函数,可实现成员函数的动态覆盖(Override)
    3)抽象类
    包括纯虚函数的类称为抽象类。因为抽象类包括了未定义的纯虚函数,所以不能定义抽象类的对象。

    10.7 友元

    考点1:了解友元
    在封装中C++类数据成员大多情况是private属性;可是假设接口採用多參数实现肯定影响程序效率;然而这时候假设外界须要频繁訪问这些私有成员,就不得不须要一个既安全又理想的“后门”——友元关系;
    C++中提供三种友元关系的实现方式,友元函数、友元成员函数、友元类。
    友元函数
    :既将一个普通的函数在一个类中说明为一个friend属性;其定义(大多数会訪问该类的成员)应在类后。
    友元成员函数:既然是成员函数,那么肯定这个函数属于某个类,对了就是由于这个函数是另外一个类的成员函数。有时候由于我们想用一个类通过一个接口去訪问另外一个类的信息,然而这个信息仅仅能是被它授权的类才干訪问;那么也须要用friend去实现;这个概念仅仅是在声明的时候稍有变化;
    友元类:友元类声明会将整个类说明成为还有一个类的友元关系;和之前两种的差别是集体和个人的差别。友元类的全部成员函数都能够是还有一个类的友元函数;
    友元须要注意的地方:
    1)友元能够訪问类的私有成员
    2)仅仅能出如今类定义内部,友元声明能够在类中的不论什么地方,一般放在类定义的開始或结尾。
    3)友元能够是普通的非成员函数。或前面定义的其它类的成员函数,或整个类。


    4)类必须将重载函数集中每个希望设为友元的函数都声明为友元。
    5)友元关系不能继承,基类的友元对派生类的成员没有特殊的訪问权限。假设基类被授予友元关系,则仅仅有基类具有特殊的訪问权限。

    该基类的派生类不能訪问授予友元关系的类。
    考点2:深入了解友元类
    当一个类B成为了另外一个类A的“朋友”时,那么类A的私有和保护的数据成员就能够被类B訪问。我们就把类B叫做类A的友元
    友元不是成员函数。可是能够訪问类中的私有成员。友元的作用在于提高程序的执行效率,可是它破坏了类的封装性和隐藏性,使得非成员函数能够訪问类的私有成员。
    友元类能够通过自己的方法来訪问把它当做朋友的那个类的全部成员。

    可是我们应该注意的是。我们把类B设置成了类A的友元类,可是这并不会是类A成为类B的友元。

    说白了就是:甲愿意把甲的秘密告诉乙,可是乙不见得愿意把乙自己的秘密告诉甲。
    如果我们要设计一个模拟电视机和遥控器的程序。大家都之道。遥控机类和电视机类是不相包括的,并且,遥控器能够操作电视机,可是电视机无法操作遥控器,这就比較符合友元的特性了。
    使用友元类须要注意:
    1) 友元关系不能被继承
    2) 友元关系是单向的,不具有交换性。若类B是类A的友元。类A不一定是类B的友元,要看在类中是否有对应的声明。

    有点像恋爱中单相思

    3) 友元关系不具有传递性

    若类B是类A的友元,类C是B的友元,类C不一定是类A的友元。相同要看类中是否有对应的申明
    考点3:写一个程序,设计一个点类Point。求出两个点之间的距离

    class Point
    {
    private:
    float x;
    float y;
    public:
    point(float a = 0.0f,float b = 0.0f):x(a,b){};
    friend float distance(point& left, Point& right)
    };
    float distance(Point& left, Point& right)
    {
        return ((left.x-right.x)^2+(left.y-right.y)^2)^0.5
    }
    

    10.8 异常

    考点1:抛出异常
    1)析构函数应该从不抛出异常。假设析构函数中须要运行可能会抛出异常的代码,那么就应该在析构函数内部将这个异常进行处理,而不是将异常抛出去。
    原因:在为某个异常进行栈展开时,析构函数假设又抛出自己的未经处理的还有一个异常。将会导致调用标准库 terminate 函数。

    而默认的terminate 函数将调用 abort 函数,强制从整个程序非正常退出。
    2)构造函数中能够抛出异常。可是要注意到:假设构造函数由于异常而退出,那么该类的析构函数就得不到运行。所以要手动销毁在异常抛出前已经构造的部分。虚方法也能够抛出异常。
    考点2:C++之父Bjarne Stroustrup的建议:
    1)当局部的控制可以处理时。不要使用异常。
    2)使用“资源分配即初始化”技术区管理资源
    3)尽量少用try_catch语气块,而是使用“资源分配即初始化”技术
    4)假设构造函数内错误发生,通过抛出异常来指明
    5)避免在析构函数中抛出异常
    6)保持普通程序代码和异常处理代码分开
    7)小心通过new分配的内存在发生异常时,可能造成内存泄露。


    8)假设一个函数可能抛出某种异常。那么我们调用它时。就要假定它一定会抛出该异常,即要进行处理。
    9) 要记住。不是全部的异常都继承自exception类。


    10)编写的供别人调用的程序库,不应该结束程序,而应该通过抛出异常,让调用者决定怎样处理(由于调用者必需要处理抛出的异常)。
    11)若开发一个项目。那么在设计阶段就要确定“错误处理的策略”。



  • 相关阅读:
    C#接口入门学习
    消息队列接收时报错:对消息队列系统的访问被拒绝
    给某做测试的好友的建议
    在不同的Sql Server 数据库服务器(不同机器)导数据。
    如何让开发人员更好测试?
    存储过程初探
    语音报警.NET开发初探
    vs2010下Siverlight开发环境安装
    C# HttpWebRequest 从google服务器获取google的PageRank PR值
    创建进程API CreateProcess Demo
  • 原文地址:https://www.cnblogs.com/mthoutai/p/7095750.html
Copyright © 2011-2022 走看看