例1: C++ 中的空类默认产生哪些类成员函数?
对于一个空类,编译器默认产生4个成员函数:默认构造函数、 析构函数、 拷贝构造函数和赋值函数。
例2: structure是否能够领有constructor / destructor及成员函数?如果能够, 那么structure和class还有区别么?
区别是class中变量默认是private, struct中的变量默认是public。struct能够有构造函数,析构函数,之间也能够继承,等等。 C++中的struct其实和class意义一样, 惟一不同的就是struct外面默认的访问控制是public, class中默认的访问控制是 private。 C++中存在struct关键字的惟一意义就是为了让C程序员们有个归属感, 是为了让C++编译器兼容以前用C开发的我的项目。
例3: 现有以下代码, 则编译时会产生谬误的是?
struct Test{ Test(int) {} Test() {} ~Test() {} void fun() {}};int main(){ Test a(1); a.fun(); Test b(); b.fun(); return 0;}
Test b()
这个语法等同于申明了一个函数,函数名为b, 返回值为Test, 传入参数为空。然而实际上,代码作者是心愿申明一个类型为Test,变量名为b的变量,应该写成Test b;
, 但程序中这个谬误在编译时是检测不进去的。出错的是b.fun()
,它是编译不过来的。
例4: 上面程序的打印出的后果是什么?
#include <iostream>using namespace std; class Base{public: Base(int i) : m_j(i), m_i(m_j) {} Base() : m_j(0), m_i(m_j) {} ~Base() {} int get_i() const { return m_i; } int get_j() const { return m_j; } private: int m_i; int m_j;};int main(){ Base obj(98); cout << obj.get_i() << endl; cout << obj.get_j() << endl; return 0;}
本题想要失去的后果是"98,98"。 然而成员变量的申明是先m_i, 而后是m_j;初始化列表的初始化变量程序是依据成员变量的申明程序来执行的, 因而m_i会被赋予一个随机值。更改一下成员变量的申明程序能够失去料想的后果。 如果要失去 "98,98"的输入后果,程序须要批改如下
int m_j;int m_i;
补充变量未初始化的值
- 未初始化的全局数组为0;
- 未初始化的部分数组为随机值;
- 初始化局部的全局数组与部分数组,初始化局部为初始化值,未初始化局部都为0;(不论全局还是部分)
- 全局变量未初始化:
#include <iostream>using namespace std; int i;char c;float f;double d;int* p;int main(){ cout << i << endl; // 0 cout << c << endl; // '\0' cout << f << endl; // 0 cout << d << endl; // 0 cout << p << endl; // 0 return 0;}
- 局部变量未初始化为随机数。
#include <iostream>using namespace std; int main(){ int i; char c; float f; double d; int* p; cout << i << endl; cout << c << endl; cout << f << endl; cout << d << endl; cout << p << endl; return 0;}
类的成员变量未初始化的值
- 对象在全局作用域或为动态部分对象时,类的内置成员变量被初始化为0.
- 对象在部分作用域定义时,类的内置成员变量不被初始化为0。
对于类类型成员依照其本身的默认构造函数进行初始化。
<span style="color:red;">如果类显式提供了带参数的构造函数,则编译器不会再为其生成空参数的构造函数。这时候就不能用空参数来定义类类型变量</span>
class A{public: int value; A (int i):value(i){}};int main(){ A a; // error return 0;}
参考代码:
#include <iostream>using namespace std; class A{public: int i; float f; double d; char c; int* p; void print() { cout << i << endl; cout << c << endl; cout << f << endl; cout << d << endl; cout << p << endl; cout << "-------------------" << endl; }};A a0;int main(){ a0.print(); A a; a.print(); return 0;}
例5: MFC类库中,CObject类的重要性不言自明。在CObject的定义中,咱们看到一个乏味的景象,即CObject的析构函数是虚构的。为什么MFC的编写者认为虚构的析构函数是必要的?
#include <iostream>using namespace std; class Base{public: Base() { cout << "Base" << endl; } ~Base() { cout << "~Base" << endl; }};class Derived : public Base{public: Derived() { cout << "Derived" << endl; } ~Derived() { cout << "~Derived" << endl; }};int main(){ Base *b = new Derived(); delete b; return 0;}
咱们将一个父类的指针指向子类的对象,当咱们应用delete b
去开释b指向的内存,会调用父类的析构函数(动态联编),然而却不会调用子类的析构函数。
如果子类对象在构造函数内申请了一块堆内存,最初根据上述状况则会造成内存透露,咱们在 ~Base() { cout << "~Base" << endl; }
前加上 virtual
, delete b
会先调用子类析构,再调用父类析构,天然就把子类申请的内存开释掉啦!
补充常识: C++动态联编和动静联编
依据: https://blog.csdn.net/erlian1... 所批改
联编是指一个程序本身彼此关联的一个过程。 依照联编所进行的阶段不同,可分为动态联编和动静联编两种。
动态联编: 动态联编是指在程序编译链接阶段进行联编。这种联编又称为晚期联编,这是因为这种联编工作是在程序运行之前实现的。
编译时所进行的联编又称为动态束定。束定是指确定所调用的函数与执行该函数代码之间的关系。 其长处是效率高,但灵活性差。
上面来看一个动态联编的程序例题:
#include <iostream>using namespace std;class Point{ public: Point(double i,double j)//基类的构造函数 { x=i;y=j; } double Area() const//定义的常成员函数 { return 0.0; }private: double x,y;};class Rectangle:public Point//私有继承的派生类{public: Rectangle(double i,double j,double k,double l);//申明派生类的构造函数 double Area() const { return w*h; }private: double w,h;};Rectangle::Rectangle(double i,double j,double k,double l):Point(i,j)//派生类构造函数的函数体{ w=k; h=l;} void fun(Point &s)//定义的类外函数{ cout<<s.Area()<<endl;}int main(){ Rectangle rec(3.5,15.2,5.0,28.0);//定义的派生类的带参数的对象 fun(rec);//调用函数fun()}
输入的后果为:0
程序剖析:从输入的后果来看,该程序执行了Point::Area()的后果。派生类Rectangle的对象rec作为函数fun()的实参,而该函数的形参是类Point()的对象的援用s。在程序编译阶段,对象援用s所执行的Area()操作被联编到Point类的函数上。因而在执行函数fun()中的s.Area()操作时,返回值为0。
通过对象指针进行的一般成员函数的调用,仅仅与指针的类型无关,而与此刻指针正指向什么对象无关。要想实现当指针指向不同对象时执行不同的操作,就必须将基类中相应的成员函数定义为虚函数,进行动静联编。
动静联编
动静联编是指在程序运行时进行的联编,这种联编又称为早期联编。 动静联编的长处是灵活性强,但效率低。
动静联编要求在运行时解决程序中的函数调用与执行该函数代码间的关系,又称为动静束定。在上述的例题中,因为是动态联编,函数fun()中的参数s所援用的对象被联编到类Point上。那么履行动静联编,则s所援用的对象将被联编到类Rectangle上。由此可见,对于同一个对象的援用,采纳不同的联编形式将会被联编到不同类的对象上,即不同联编能够抉择不同的实现,这便是多态性。实际上是对于函数fun()的参数的多态性抉择。联编是一种重要的多态性抉择。
实现动静联编须要同时满足以下三个条件:
- 必须把动静联编的行为定义为类的虚函数。
- 类之间应满足子类型关系,通常体现为一个类从另一个类私有派生而来。
- 必须先应用基类指针指向子类型的对象,而后间接或者间接应用基类指针调用虚函数。
上述的例题能够改为:
#include <iostream>using namespace std;class Point{ public: Point(double i,double j)//基类的构造函数 { x=i;y=j; } virtual double Area() const//定义的虚函数 { return 0.0; }private: double x,y;};class Rectangle:public Point//私有继承的派生类{public: Rectangle(double i,double j,double k,double l);//申明派生类的构造函数 virtual double Area() const//派生类的虚函数 { return w*h; }private: double w,h;};Rectangle::Rectangle(double i,double j,double k,double l):Point(i,j)//派生类构造函数的函数体{ w=k; h=l;} void fun(Point &s)//定义的类外函数{ cout<<s.Area()<<endl;}int main(){ Rectangle rec(3.5,15.2,5.0,28.0);//定义的派生类的带参数的对象 fun(rec);//调用函数fun()}
输入的后果为:140.
程序剖析:该程序中阐明了虚函数,fun()函数的对象援用参数s被动静联编,该函数体内调用的Area()函数是在运行中确定的,而不是在编译时确定的,因而在运行时,实参rec为类rectangle的对象,于是Area()函数被确定为类Rectangle的Area()函数。
例6: 析构函数能够为virtual型,构造函数则不能。那么为什么构造函数不能为虚呢?
虚函数采纳一种虚调用的方法。虚调用是一种能够在只有局部信息的状况下工作的机制,特地容许咱们调用一个只晓得接口而不晓得其精确对象类型的函数。然而如果要创立一个对象,你势必要晓得对象的精确类型,因而构造函数不能为虚。
例7: 如果虚函数是十分无效的,咱们是否能够把每个函数都申明为虚函数?
不行,这是因为虚函数是有代价的:因为每个虚函数的对象都必须保护一个虚表,因而在应用虚函数的时候都会产生个零碎开销。如果仅是个很小的类,且不想派生其余
类,那么基本没必要应用虚函数。
例8: 析构函数能够是内联函数吗?
析构函数能够是内联函数。
例9: 重载和笼罩有什么不同?
override是指派生类重写基类的虚函数, overload约定成俗地被翻译为 “重载",是指编写一个与已有函数同名,然而参数表不同的函数。 例如一个函数既能够接管整型数作为参数,也能够接管浮点数作为参数。 重载不是一 种面向对象的编程, 而只是一种语法规定, 重载与多态没有什么间接关系。
例10: 在C++中, 你不应该从以下哪个抛出异样?
A. Constructor (构造函数)B. Destructor (析构函数)C. Virtual function (虚办法)D. None of the above (以上答案都不对)
构造函数中抛出异样是有肯定必要的,试想如下状况:
构造函数中 有两次new操作,第一次胜利了,返回了无效的内存,而第二次失败,此时因为对象结构尚未实现,析构函数是不会调用的,也就是delete语句没有被执行,第一次new出的内存就悬在那儿了(产生内存泄露), 所以异样处理程序能够将其裸露进去。
构造函数中遇到异样是不会调用析构函数的, 一个对象的父对象的构造函数执行结束,不能称之为结构实现,对象结构是不可分割的,要么齐全胜利, 要么齐全失败,C++保障这一点。 对于成员变量,C++遵循这样的规定,即会从异样的产生点依照成员变量的初始化的逆序开释成员。 举例来说,有如下初始化列表:
A:: A() : ml(), m2(), m3(), m4(), m5() () {};
假设m3的初始化过程中抛出异样,则会依照m2,ml的顺序调用这两个成员的析构函数。 在{}之间产生的未捕获异样,最终会导致在栈的开解时析构所有的数据成员。
解决这样的问题, 应用智能指针是最好的, 这是因为auto_ptr
成员是一个对象而不是指针。 换句话说,只有不应用原始的指针,那么就不用放心构造函数抛出异样而导致资源泄露。
所以在C++中,资源泄露的问题个别都用RAII(资源获取即初始化)的方法:把须要关上/敞开的资源用简略的对象封装起来(这种封装能够同时有多种用途,比方暗藏底层API细节,以利于移植)。 这能够省去很多的麻烦。
如果不必RAII,即便以后构造函数里获取的货色在析构函数里都开释了,如果某天对类有改变,要新减少一种资源,构造函数里个别能适当地获取,但记不记得要在析构函数里相应地开释呢?失误的比例很大。 如果思考到构造函数里抛出异样,就更简单了。 随着我的项目的不断扩大和工夫的推移,这些细节不可能都记得住,而且,有可能会由他人来施行这样的改变。
- C++中告诉对象结构失败的惟一办法,就是在构造函数抛出异样
- 当对象产生局部结构异样时,曾经结构结束的子对象将会逆序地被析构(即异样产生点后面的对象; 而还没有开始构建的子对象将不会被结构了(即异样产生点前面的对象, 当然它也就没有析构过程了; 还有正在构建的子对象和对象本人自身将进行持续构建(即出现异常的对象),并且它的析构是不会被执行的。
析构函数抛异样
Effective C++倡议,析构函数尽可能地不要抛出异样。 构想如果对象出了异样, 当初异样解决模块为了保护零碎对象数据的一致性,防止资源透露,有责任开释这个对象的资源,调用对象的析构函数,可当初如果析构过程又再出现异常,那么请问由谁来保障这个对象的资源开释呢?而且这新呈现的异样又由谁来解决呢?不要遗记后面的一个异样目前都还没有解决完结,因而陷入了一个矛盾之中,或者说处于有限的递归嵌套之中。 所以C++规范就做出了这种假如。