《程序员面试宝典》学习记录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++中存在struct关键字的唯一意义就是为了让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 = '\0';
    }
    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)合法。单个参数的构造函数如果不添加explicit关键字,会定义一个隐含的类型转换;添加explicit关键字会消除这种隐含转换。
考点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\n");
    }
    virtual void fun()
    {
        printf("2\n");
    }
};
class B : public A
{
public:
    void foo()
    {
        printf("3\n");
    }
    void fun()
    {
        printf("4\n");
    }
};
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
关键字,基类的函数将被隐藏(注意别与重载混淆)。
(2)如果派生类的函数与基类的函数同名,并且参数也相同,但是基类函数没有virtual
关键字。此时,基类的函数被隐藏(注意别与覆盖混淆)。

小结: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)若开发一个项目,那么在设计阶段就要确定“错误处理的策略”。



  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值