精华内容
下载资源
问答
  • 引用类型不一样,我们在声明guy的时候把anders赋给它,前面说过,引用类型包含的是只 想堆上数据区域地址的引用,其实就是把anders的引用也赋给guy了,因此这二者从此指向了同一块内存区域,既然是指向同一块区域...
  • 一张表则是一个计数数组,第i个数组元素是上述第i个项的出现次 数。这些所有项的计数值的初始值都是0。 在读取购物篮时,我们检查购物篮中的每个项并将其名称转换为一个整数。然后,将该整数作为计数数组的下标...
  • 一张表则是一个计数数组,第i个数组元素是上述第i个项的出现次 数。这些所有项的计数值的初始值都是0。 在读取购物篮时,我们检查购物篮中的每个项并将其名称转换为一个整数。然后,将该整数作为计数数组的下标...
  • <div><h2>从一个需求谈起 在我之前的项目中,曾经遇到过这样一个需求,编写一个级联选择器&#...动态规划方案通常会使用一个数组来建立一张表,用于存放被分解成众多子问题的解。当算法执行完毕ÿ...
  • 一、概述 视图(View)是从一个或多个(其他视图...视图一经定义便存储在数据库中,与其相对应的数据并没有像一样在数据库中另外存储一份,通过视图看到的数据只是存放在基表中的数据。对视图的操作与对表的操作

    一、概述

    视图(View)是从一个或多个表(其他视图)中导出的表,其结构和数据是建立在对表的查询基础之上的。所以视图不是真实存在的基础表,而是一张虚表。视图所对应的数据并不实际地以视图结构存储在数据库中,而是存储在视图所引用的表中。

    视图一经定义便存储在数据库中,与其相对应的数据并没有像表一样在数据库中另外存储一份,通过视图看到的数据只是存放在基表中的数据。对视图的操作与对表的操作一样,可以对其进行查询、修改(有一定的限制)和删除。

    当对视图中的数据进行修改时,相应的基表的数据也要发生变化,同时,如果基表的数据发生变化,则这种变化也可以自动地反映到视图中。


    二、视图的特点

    1.视点集中,减少对象大小
    视图让用户能够着重于他们所需要的特定数据或所负责的特定要求,如用户可以选择特定行或特定列。

    2.从异构源组织数据
    可以在连接两个或多个表的复杂查询的基础上创建视图,这样可以将单个表显示给用户。

    3.隐藏数据的复杂性,简化操作
    视图向用户隐藏了数据库设计的复杂性,这样如果开发者改变数据库设计,不会影响到用户与数据库交互。另外,用户可将经常使用的连接查询、嵌套查询或联合查询定义为视图。

    4.简化用户权限的管理
    可以将视图的权限授予用户,而不必将基表中某些列的权限授予用户,这样就简化了用户权限的定义。


    三、视图的四种类型

    1.关系视图:
    关系视图(relational view)基本上就是经过存储的查询,可以将它的输出看作是一个表。它就是基于关系数据的存储对象。

    2.内嵌视图:
    又称为嵌套查询,是嵌入到父查询中的查询,能够在任何可以使用表名称的地方使用。

    3.对象视图(不演示):
    为了迎合数据库中对象类型而将关系表投射到特定数据类型的虚拟对象表中,视图的每行都是带有属性、方法和唯一标识(OID)的对象实例。

    4.物化视图(不演示):
    就是在数据库中查询结果存储在视图中,并支持查询重写、刷新、提交等特性的视图。


    4、视图的创建及管理

    1. 关系视图

      创建语法:

      CREATE [OR REPLACE] [FORCE] VIEW
      view_name [(alias[, alias]...)] 
      AS select_statement 
      [WITH CHECK OPTION [CONSTRAINT constraint]] 
      [WITH READ ONLY];

      查看视图(可以通过User_views,All_views,Dba_views来查询视图信息):

      --案例:
      select * from user_views;--查看当前用户创建的视图

      创建普通关系视图【可对视图执行DML操作】:

      --使用scott身份登录,创建视图emp表的简单视图
      create view view_emp as select * from emp;
      select * from view_emp;
      update view_emp set sal=3200 where sal=3000
      delete view_emp where rownum=1;

      创建只读视图:

      --特点:顾名思义,只读,不能执行其他DML操作
      create or replace view view_emp as select * from emp where sal>=3000 with read only;

      创建检查视图:

      --特点:执行DML操作时,自动检测是否满足创建视图时所建立的where条件,如果不满足,直接出错
      create or replace view view_emp as select * from emp where sal>=3000 with check option;

      创建复杂视图:

      --特点: 复杂视图是指包含函数、表达式或分组数据的视图,主要目的是为了简化查询
      create or replace view view_emp as select count(*) 人数,avg(sal+nvl(comm,0)) 平均工资,deptno 部门编号 from emp group by deptno;

      创建强制视图:

      /*
      特点:正常情况下,如果基表不存在,创建视图就会失败。但是可以使用FORCE选项强制创建视图(前提是创建视图的语句没有语法错误),
      但此时该视图处于失效状态,调用会出错,直到这个基表已经存在
      */
      create or replace FORCE  view view_test as select * from myemp;

      视图上的DML语句有如下限制:

      ①.只能修改一个底层的基表。
      ②.如果修改违反了基表的约束条件,则无法更新视图。
      ③.如果视图包含连接操作符、DISTINCT 关键字、集合操作符、聚合函数或 GROUP BY 子句,则将无法更新视图。
      ④.如果视图包含伪列或表达式,则将无法更新视图。

    2. 内嵌视图

      概述:
      内嵌视图就是嵌入到父查询中的查询,能够在任何可以使用表名称的地方使用。
      内嵌视图又称为嵌套查询。
      嵌视图可以出现在SELECT语句的FROM子句中,以及INSERT INTO、UPDATE甚至是DELETE FROM语句中。
      内嵌视图是临时的,它只存在于父查询的运行期间。

      --案例:
      select * from (select e.*,rownum rn from emp e) tab where rn>=5 and rn<=10;
      --其中: select e.*,rownum rn from emp e就是一个内嵌视图,临时有效

    展开全文
  • 一张表上最多只能创建一个聚集索引,因为真实数据的物理顺序只能有一种。 "聚集"指实际的数据行和相关的键值都保存在一起 聚簇索引的二级索引:叶子节点不会保存引用的行的物理位置,而是保存了行的...

    摘抄和整理,非原创!!!

    官方定义:

    在《数据库原理》一书中是这么解释聚簇索引和非聚簇索引的区别的:
    聚簇索引的叶子节点就是数据节点,
    而非聚簇索引的叶子节点仍然是索引节点,只不过有指向对应数据块的指针。

     

    聚集索引:表数据按照索引的顺序来存储的,也就是说索引项的顺序与表中记录的物理顺序一致。对于聚集索引,叶子结点即存储了真实的数据行,不再有另外单独的数据页。 在一张表上最多只能创建一个聚集索引,因为真实数据的物理顺序只能有一种。

    "聚集"指实际的数据行和相关的键值都保存在一起

    聚簇索引的二级索引:叶子节点不会保存引用的行的物理位置,而是保存了行的主键值

     

    注意:数据的物理存放顺序与索引顺序是一致的,即:只要索引是相邻的,那么对应的数据一定也是相邻地存放在磁盘上的,如果主键不是自增id,那么可以想象,它会干些什么,不断地调整数据的物理地址、分页,当然也有其他一些措施来减少这些操作,但却无法彻底避免。但,如果是自增的,那就简单了,它只需要一页一页地写,索引结构相对紧凑,磁盘碎片少,效率也高。

     

    非聚集索引:表数据存储顺序与索引顺序无关。对于非聚集索引,叶结点包含索引字段值及指向数据页数据行的逻辑指针,其行数量与数据表行数据量一致

    MyISAM的B+Tree的叶子节点上的data,并不是数据本身,而是数据存放的地址主索引和辅助索引没啥区别,只是主索引中的key一定得是唯一的

     

    聚簇索引是对磁盘上实际数据重新组织以按指定的一个或多个列的值排序的算法。特点是存储数据的顺序和索引顺序一致。一般情况下主键会默认创建聚簇索引,且一张表只允许存在一个聚簇索引。

     

    因此,MYSQL中不同的数据存储引擎对聚簇索引的支持不同就很好解释了。下面,我们可以看一下MYSQL中MYISAM和INNODB两种引擎的索引结构。

    如原始数据为:




     

    MyISAM引擎的数据存储方式如图:



     

    MYISAM是按列值与行号来组织索引的。它的叶子节点中保存的实际上是指向存放数据的物理块的指针。从MYISAM存储的物理文件我们能看出,MYISAM引擎的索引文件(.MYI)和数据文件(.MYD)是相互独立的。

    而InnoDB按聚簇索引的形式存储数据,所以它的数据布局有着很大的不同。它存储数据的结构大致如下:



     

    注:聚簇索引中的每个叶子节点包含主键值、事务ID、回滚指针(rollback pointer用于事务和MVCC)和余下的列(如col2)。

    INNODB的二级索引与主键索引有很大的不同。InnoDB的二级索引的叶子包含主键值,而不是行指针(row pointers),这减小了移动数据或者数据页面分裂时维护二级索引的开销,因为InnoDB不需要更新索引的行指针。其结构大致如下:



     

    INNODB和MYISAM的主键索引与二级索引的对比:



     

    InnoDB的的二级索引的叶子节点存放的是KEY字段加主键值。因此,通过二级索引查询首先查到是主键值,然后InnoDB再根据查到的主键值通过主键索引找到相应的数据块。而MyISAM的二级索引叶子节点存放的还是列值与行号的组合,叶子节点中保存的是数据的物理地址。所以可以看出MYISAM的主键索引和二级索引没有任何区别,主键索引仅仅只是一个叫做PRIMARY的唯一、非空的索引,且MYISAM引擎中可以不设主键。

     

     为了更形象说明这两种索引的区别,我们假想一个表如下图存储了4行数据。其中Id作为主索引,Name作为辅助索引。图示清晰的显示了聚簇索引和非聚簇索引的差异。

     


     

    对于聚簇索引存储来说,行数据和主键B+树存储在一起,辅助键B+树只存储辅助键和主键,主键和非主键B+树几乎是两种类型的树。对于非聚簇索引存储来说,主键B+树在叶子节点存储指向真正数据行的指针,而非主键。

    InnoDB使用的是聚簇索引,将主键组织到一棵B+树中,而行数据就储存在叶子节点上,若使用"where id = 14"这样的条件查找主键,则按照B+树的检索算法即可查找到对应的叶节点,之后获得行数据。若对Name列进行条件搜索,则需要两个步骤:第一步在辅助索引B+树中检索Name,到达其叶子节点获取对应的主键。第二步使用主键在主索引B+树种再执行一次B+树检索操作,最终到达叶子节点即可获取整行数据。

    MyISM使用的是非聚簇索引,非聚簇索引的两棵B+树看上去没什么不同,节点的结构完全一致只是存储的内容不同而已,主键索引B+树的节点存储了主键,辅助键索引B+树存储了辅助键。表数据存储在独立的地方,这两颗B+树的叶子节点都使用一个地址指向真正的表数据,对于表数据来说,这两个键没有任何差别。由于索引树是独立的,通过辅助键检索无需访问主键的索引树。

    为了更形象说明这两种索引的区别,我们假想一个表如下图存储了4行数据。其中Id作为主索引,Name作为辅助索引。图示清晰的显示了聚簇索引和非聚簇索引的差异。

     



     

    我们重点关注聚簇索引,看上去聚簇索引的效率明显要低于非聚簇索引,因为每次使用辅助索引检索都要经过两次B+树查找,这不是多此一举吗?聚簇索引的优势在哪?

    1 由于行数据和叶子节点存储在一起,这样主键和行数据是一起被载入内存的,找到叶子节点就可以立刻将行数据返回了,如果按照主键Id来组织数据,获得数据更快。

    2 辅助索引使用主键作为"指针" 而不是使用地址值作为指针的好处是,减少了当出现行移动或者数据页分裂时辅助索引的维护工作,使用主键值当作指针会让辅助索引占用更多的空间,换来的好处是InnoDB在移动行时无须更新辅助索引中的这个"指针"。也就是说行的位置(实现中通过16K的Page来定位,后面会涉及)会随着数据库里数据的修改而发生变化(前面的B+树节点分裂以及Page的分裂),使用聚簇索引就可以保证不管这个主键B+树的节点如何变化,辅助索引树都不受影响。

    展开全文
  • Java实现多态的机制

    2017-11-21 19:04:39
    在JVM中,对象的引用其实是指向一个句柄(handle)的指针,该句柄包含一对指针,一个指针指向一张表格,实际该表格也有两个指针(一个指针指向包含了对象的方法另外一个指向对象类型,表明该对象所属的类型);...

        在JVM中,对象的引用其实是指向一个句柄(handle)的指针,该句柄包含一对指针,一个指针指向一张表格,实际上该表格也有两个指针(一个指针指向包含了对象的方法表,另外一个指向对象类型,表明该对象所属的类型);另外一个指针指向一块从java堆中分配的内存空间,该空间用于存贮对象数据。

        Java通过将子类对象引用赋值给父类对象的引用变量来实现动态方法的调用,当把子类对象赋值给父类引用变量的时候,通过父类引用变量只能引用被子类重写的方法。

    展开全文
  • 第一章 1.多态:不同对象去完成同一行为时,可以展现出不同的形态。在运行时,可以通过指向基类的指针或...另外,用一张虚函数(virtual table)存储所有指向虚函数的指针,并在表头附加一个该类的type_info对象,

    http://www.roading.org/develop/cpp/%E3%80%8A%E6%B7%B1%E5%BA%A6%E6%8E%A2%E7%B4%A2c%E5%AF%B9%E8%B1%A1%E6%A8%A1%E5%9E%8B%E3%80%8B%E7%AC%94%E8%AE%B0%E6%B1%87%E6%80%BB.html

    第一章 关于对象

    1.多态:不同对象去完成同一行为时,可以展现出不同的形态。在运行时,可以通过指向基类的指针或引用,来调用实现派生类中的方法。

    作用:在面向对象的程序设计中,使用多态能够增强程序的可扩充性,即类型需要修改或增加功能时,只需改动或增加较少的代码,提高开发效率。

    2.C++对象模型
    所有的非静态数据成员存储在对象本身中。所有的静态数据成员、成员函数(包括静态与非静态)都置于对象之外。另外,用一张虚函数表(virtual table)存储所有指向虚函数的指针,并在表头附加上一个该类的type_info对象,在每个对象中则保存一个指向虚函数表的指针。如下图:
    在这里插入图片描述
    函数存放在只读代码区的.text段内,虚函数表存放在.rodata段内。

    3.多态运转原理:
    存在虚函数的类都有一个一维的虚函数表叫做虚表,每一个类的对象有一个指向虚表的虚指针。虚表是和类对应的,虚表指针是和对象对应的。 父类和子类相对应的虚函数在虚表中的位置相同。

    一个指针或引用之所以支持多态,是因为指针和引用并不引发“与类型有关的内存操作”,只影响他们所指向的内存的“解释方式”。
    “指针类型”会教导编译器如何解释某个特定地址中的内存内容及其大小。

    指向子类型对象的父类型指针,把子类型对象当成了父类型来解释,但当其通过虚指针调用虚函数时,使用的是子对象的虚指针,并且父类和子类相对应的虚函数在虚表中位置相同,因此调用了子对象的虚函数。
    编译的时候就决定了,普通函数直接调用这个函数的地址,而如果是虚函数,则从这个虚表里取地址调用。

    4.面向对象和基于对象:
    C++通过类的指针和引用来支持多态,这种设计风格就被称为“面向对象”;
    不使用继承和多态的编程风格就被称为基于对象object-based(OB),执行更快且空间更紧凑,但没有弹性。

    第二章 构造函数语意学

    NRV(命名返回值named return value)优化

    X foo() 
    { 
        X xx; 
        if(...) 
            return xx; 
        else 
            return xx; 
    }
    

    形如foo(),所有return 指令传回相同的对象,编译器会自动对其做NRV优化,方法是以一个参数result取代返回对象:

    void  foo(X &result)
    {
        result.X::X();
        if(...)
        {
            //直接处理result
            return;
        }
        else
        {
            //直接处理result
            return;
        }
    }
    

    对于一句类似于X a = foo()这样的代码,NRV优化后的代码相较于原代码节省了一个临时对象的空间(省略了xx),同时减少了两次函数调用(减少xx对象的默认构造函数和析构函数,以及一次拷贝构造函数的调用,增加了一次对a的默认构造函数的调用)。

    成员函数初始化队列:

    在构造函数中对于对象成员的初始化发生在初始化队列中——或者我们可以把初始化队列直接看做是对成员的定义,而构造函数体中进行的则是赋值操作。
    初始化队列运行在前(初始化顺序按class中成员声明顺序,而不是初始化队列顺序),构造函数体在后。
    编译器实际上会一一操作初始化队列,按顺序在构造函数体内安插初始化操作,并在原来的显式代码之前。

    第三章 Data语意学

    1.多态 内存布局
    在这里插入图片描述每一个 有虚函数 的类有一个虚函数表,虚函数表存储虚函数指针,虚函数指针指向该类的虚函数,该类的每一个对象有一个虚函数表指针vptr,指向该虚函数表。

    在这里插入图片描述多重继承情况下,有多少个基类就有多少个虚函数表指针,前提是基类要有虚函数才算上这个基类。当子类有多出来的虚函数时,添加在第一个虚函数表中。
    https://blog.csdn.net/qq_36359022/article/details/81870219

    第四章 函数语意学

    1.虚函数底层实现:
    http://www.roading.org/develop/cpp/c%E4%B9%8B%E8%99%9A%E5%87%BD%E6%95%B0virtual-member-functions.html
    简单概括:
    单一继承中,无论基类指针指向的对象实际是什么类型,该指针都能通过虚指针(基类及其继承类中虚指针名称相同),以及该对象对应的虚函数表中的索引,调用到想要调用的某个虚函数,因为在不同类中,相对应的虚函数索引相同。

    多重继承中,有多个虚指针、虚函数表,对于 第二个或后续基类 的处理 需要调整this指针,所以比较复杂;

    虚拟继承中,虚基在派生类中也存在虚指针,存在多个虚函数表,更复杂。

    3.成员函数实际上通过携带类对象的this指针,转化为非成员函数,和非成员函数具有相同的效率。

    4.指向函数的指针
    指向非虚拟函数的指针,输出为函数地址;
    指向虚拟函数的指针,输出为该虚拟函数在虚拟函数表中的索引值。

    第五章 构造、析构、拷贝语意学

    1.类设计原则:
    1)即使是一个抽象基类,如果它有非静态数据成员,也应该给它提供一个带参数的构造函数,来初始化它的数据成员。
    2)不要将析构函数设计为纯虚的,这不是一个好的设计。
    3)真的必要的时候才使用虚函数,不要滥用虚函数。
    4)不能决定一个虚函数是否需要 const ,那么就不要它。
    5)决不在构造函数或析构函数中使用虚函数机制。

    2.POD数据类型
    Plain Old Data(简旧数据类型) : 能直接以其二进制形式与 C 库交互的数据类型,比如可以直接使用memmove、memcopy等C函数进行复制。
    详细介绍

    struct Point {
        float x,y,z;
    };
    

    概念上来讲,对于一段这样的C++代码,编译器会为之合成一个默认构造函数、拷贝构造函数、析构函数、赋值操作符。然而实际上编译器会分析这段代码,并给Point贴上Plain OId Data标签。编译器在此后对于Point的处理与在C中完全一样,也就是说上述的函数都不会被合成。可见概念上应当由编译器合成的函数,并不一定会合成,编译器只有在必要的时候才会合成它们。由此一来,原本在观念上应该调用这些函数的地方实质上不会调用,而是用其它的方法来完成上面的功能,比方复制操作会用bitwise copy(位拷贝)。

    3.类的显式列表初始化

    struct N{
        int x,y,z;
    };
    //或者
    class N{
    public:
        int x,y,z;
    };
    int main(){
        N a={1,2,3};
    	return 0;
    }
    

    限制:1)只有成员都是public,且没有构造函数时才能使用,2)只能指定常量。

    4.虚拟继承的构造函数
    在这里插入图片描述
    根据c++ 语法,Point 的初始化应有most-derived class来施行。也就是说当Vertex3d为most-derived class的时候,应当由它的构造函数来调用Point的构造函数初始化Point子对象,Vertex3d的子对象的构造函数对于Point的调用则应当抑制。如果没有抑制会怎么样?当我们定义Vertex3d cv;时,Vertex3d的构造函数中调用Point的构造函数、而随之调用它的子对象,Point3d和Vertex的构造函数中也调用了Point的构造函数。先不说,对于同一个子对象进行三次初始化是否有效率,更重要的是,这将不可避免的带来错误。由Vertex3d指定的子对象Point的值,会被覆盖掉。

    编译器通常使用一个条件变量来表示是否为most-derived class,各构造函数根据这个条件变量来决定是否调用虚基类的构造函数,因此通过控制这个条件变量,就可以抑制非most-derived class调用虚基类的构造函数。当然也有其它的方法来做同样的事。

    假设该体系每个类中都有一个虚函数size()返回当前类的大小,在每一个构造函数中都放置一个size()。
    定义一个PVertex对象,前述的5个构造函数每次调用的size()都被决议为当前正在执行的构造函数所对应的类 的size()函数实例,而不全是PVertex::size()。为什么?这就要引出 vptr的初始化操作 以及 构造函数的执行算法。

    构造函数的执行算法
    1)在派生类构造函数中,所有虚基类 以及 上一层基类 的构造函数被调用。(递归)
    2)上述完成后,对象的vptr被初始化,指向相关的vtable。(某些情况可省略)
    3)如果有成员函数初始化队列,则在构造函数体内扩展开来。因为vptr已经初始化完成,所以虚成员函数可以被调用。
    4)最后,执行构造函数体内程序员提供的代码。

    其中,vptr并不是一定要被设定,可以省略,除了两种情况:
    1)当一个完整的对象被构造起来。比如声明Point3d,则Point3d的构造函数必须设定其ptr。
    2)当一个子对象构造函数调用了一个虚函数(直接调用或间接调用)时。

    所以说,在类的构造函数初始化列表里调用该类的虚拟函数是安全的,因为调用时的vptr已经被编译器正确设定好。

    对象复制语意学

    设计一个类,并考虑到要以一个对象指定给另一个对象时,有三种选择:

    • 什么都不做,采用编译器提供默认行为(bitwise copy或者由编译器合成一个)。
    • 自己提供一个赋值运算符操作。
    • 明确拒绝将一个对象指定给另一个对象。(设置为private不定义,或者=delete)

    对于第三点,只要将赋值操作符声明为private,且不定义它就可以了。对于第二点,只有在第一点的行为不安全或不正确,或你特别想往其中插入点东西的时候。

    编译器的默认行为:如果没有特殊情况,用bitwise copy(展现bitwise copy语意),否则合成赋值运算符。

    不使用bitwise copy的四种特殊情况:

    • 类包含有定义了copy assignment operator的class object成员。
    • 类的基类有copy assignment operator。
    • 类声明有任何虚函数的时候(问题同样会出现在由继承类对象向基类对象拷贝的时候)。
    • 当class继承体系中有虚基类时。

    在虚拟继承情况下,copy assignment operator会遇到一个不可避免的问题,virtual base class sub-object的复制行为会发生多次,与前面说到的在虚拟继承情况下虚基类被构造多次是一个意思,不同的是在这里不能抑制非most-derived class 对virtual base class 的赋值行为。

    安全的做法是把虚基类的赋值放在最后,避免被覆盖。或者直接不在虚基中声明数据。

    对象析构语意学

    只有在基类拥有析构函数,或者object member拥有析构函数的时候,编译器为类合成析构函数,否则都被视为不需要。如果程序提供了析构函数,则编译器会扩展析构函数,在我们提供的代码后面调用对基类或对object member的析构。

    析构的顺序正好与构造相反:

    • 本身的析构函数被执行。
    • 如果member class object拥有析构函数,则以声明的相反顺序调用member object 的析构函数。
    • 如果对象内含vptr,则重设vptr 指向适当的基类的虚函数表。
    • 如上一层非虚基类有析构函数,则以声明相反的顺序被调用。
    • 如果当前类是 most-derived class,那么以构造的相反顺序调用虚基类的析构函数。

    析构函数实现策略:维护两份析构函数实例

    • 对于完整对象实例:总是设定好vptr,并调用虚基类析构函数。
    • 基类子对象实例:除非析构函数中调用了虚函数,否则不会设定vptr。

    第6章 执行期语意学

    6.1对象的构造和析构

    一般而言,构造函数被安插在对象的定义处,而析构函数被安插在对象生命周期结束前:

    // Pseudo C++ Code  
    {  
        Point point;  
        // point.Point::Point() 一般被安插在这儿  
        ...  
        // point.Point::~Point() 一般被安插在这儿 
    }
    

    当代码有一个以上的离开点的时候,析构函数则必须放在对象被构造之后的每一个离开点(return)之前。

    因此,尽可能将对象定义在接近要使用的地方,可以减少不必要的构造对象和析构对象的代码被插入到自己的代码当中。把所有对象定义放在函数或区段起始处不是好的习惯。

    全局对象

    C++中所有的全局对象(类的对象)都放在程序的data段中,如果显式指定一个值,则对象以该值为初值,否则内存内容为0)。其构造函数要到程序启动才会实施(静态初始化)。

    局部静态变量

    下面一段代码:

    const Matrix&  identity()
    {  
        static Matrix mat_identity;  
        // ...  
        return mat_identity;  
    }
    

    因为静态语意保证了 mat_identity 在整个程序周期都存在,而不会在函数 identity()退出时被析构,所以:

    • mat_identity的构造函数只能被施行一次,虽然identity()可以被调用多次。
    • mat_identity 的析构函数只能被施行一次,虽然identity()可以被调用多次。

    那么 mat_identity的构造函数和析构函数到底在什么时候被调用?答案是:mat_identity的构造函数只有在第一次被调用时在被施行,而在整个程序退出之时按构造相反的顺序析构局部静态对象。

    对象数组(Array of Objects)

    对于定义一个普通的数组,例如:

    Point knots[ 10 ];
    

    实际上背后做的工作则是:

    1. 分配充足的内存以存储10个Point元素;
    2. 为每个Point元素调用它们的默认构造函数(不论是合成的还是显式定义的,如果没有则调用有默认值的其他构造函数,如果不适配则无法运行)。编译器一般以一个或多个函数来完成这个任务。当数组的生命周期结束的时候,则要逐一调用析构函数,然后回收内存,编译器同样一个或多个函数来完成任务。
    3. 如果初始化了前几个对象,则先用给出的初值初始化前几个对象,剩下的对象调用默认构造函数。

    6.2 new和delete运算符

    一个看起来很简单的new expression运算,其实暗含一些步骤,像这样的一次简单运用:Point3d *p=new Point3d;实际上包含着两个步骤:

    1. 调用一个合适的operator new函数分配足够的内存,默认使用malloc分配,分配失败会抛出异常;
    2. 调用合适的构造函数初始化这块内存。
    //Point3d *p=new Point3d;等价于
    Point3d* p;
    if (p = operator new(sizeof(Point3d)))
        p = Point3d::Point3d( p );
    

    delete运算符类似,但如果p指针值为0,C++会要求delete不要有操作:

    //delete p;等价于
    if (p !=0 ){
    	Point3d::~Point3d( p );
        operator delete(p);
    }
    

    但p并不会因此自动清除为0,仍为原来的地址,但成为了野指针——指向了一块“垃圾内存”,或者说指向了一块不应该读写的内存。避免野指针的好方法是,当一个指针变为野指针的时候,马上赋值为NULL,其缘由在于,你可以很容易的判断一个指针是否为NULL,却难以抉择其是否为野指针。

    由此可见:new expression和operator new完全不是一回事,但关系不浅——operator new 为new expression分配内存。

    摘录一下 《C++ primer》关于对比new expression 和 operator new的一小段话:

    标准库函数 operator new和 operator delete 的命名容易让人误解。与其他operator 函数(如 operator=)不同,这些函数没有重载new或delete expression,实际上,我们不能重定义new或delete expression的行为。

    我们能够重载的只有new expression 中第一步的operator new函数。

    针对数组的new语意

    Point *p=new Point3d[10];
    p[1];//p + sizeof(Point);
    

    将基类指针指向派生类数组,很容易出问题。

    因为派生类和基类对象的大小是通常不一样的,对基类指针使用下标操作符时,编译器按基类对象大小寻址,而不是我们需要的派生类。

    Placement Operator new 的语意 (布局new)

    placement operator new 是重载 operator new 的一个标准、全局的版本,它不能够被自定义的版本代替。

    用来在指定地址上构造对象,要注意的是,它并不分配内存,仅仅是 对指定地址调用构造函数。其调用方式如下:

    Point *pt=new(p) Point;
    

    它的实现方式异常简单,传回一个指针即 可:

    void* operator new(void *p ,size_t){//size_t被忽略
        return p;
    }
    

    代替了 普通 operator new 的分配内存,因为地址已经给出,所以不需要自己分配内存。 之后自动执行就是 new 表达式的第二步:调用合适的构造函数初始化这块内存。

    通过一个placement operator new构建的一个对象,如果你使用delete来撤销对象,那么其内 存也被回收,如果想保存内存而析构对象,好的办法是显示调用其析构函数。

    使用条件:指针 p 必须指向相同类型的类对象,或者是一块 新内存,足够容纳该类型的对象。如下:

    Point *p= new Point;
    char *p = new char[ sizeof( Point )];
    

    临时对象

    T operator+( const T&, const T&);//存在+重载
    T a, b;
    T c= a + b ;
    

    上面的程序片段实际上只执行三次构造函数,没有执行拷贝构造,没有临时变量。理论上是因为实施了NRV优化,将c作为额外参数提供给了operator+函数,直接在函数内部修改了c。

    但是对于一个情况非常类似的赋值操作语句c = a + b,却有很大的差别,那个临时变量是不能省的。

    不能忽略临时对象,反而导致如下过程:(有临时对象的构造、析构,还有赋值运算符)

    // Pseudo C++ code  
    // T temp = a + b;  
    T temp;  
    a.operator+( temp, b ); 	// @1 
    	
    // c = temp  
    c.operator =( temp ); 		// @2  
    temp.T::~T();
    

    在代码@1处,表明以拷贝构造函数或NRV方式将结果保存的临时对象中,而不是c中,因为运算符并不为其外加参数调用一个析构函数(它期望一块新的内存)。

    对于一个没有出现目标对象的表达式a + b,那么产生一个临时对象来存储运算结果,则是非常必要的。

    所以:T c = a + b总是比c = a + b更有效率。

    原因可以简单概括为:运算符函数并不为外加参数调用析构函数,期望新的内存,所以前者(内存新鲜)可以使用NRV优化,而后者需要使用赋值运算符。

    临时对象的生命周期

    对于一个没有出现目标对象的表达式a + b;,产生一个临时对象来存储运算结果,是非常必要的。比如

    string s1("hello "), s2("world "),s3("by Adoo");
    std::cout<<s1+s2+s3<<std::endl;
    

    C++标准规定

    • 临时性对象的摧毁应当作为造成产生这个临时对象的完整表达式的最后一个步骤

      完整表达式:不是另一个表达式 子表达式 的表达式。

    但有两个例外:

    • 凡含有表达式执行结果的临时性对象,应该保存到Object的初始化操作完成为止。

    • 如果临时性对象被绑定与一个引用,临时对象将残留,直至被初始化的引用的生命结束,或直到临时对象的scope结束——视哪一种情况先达到,对应于这种情况:

    string s1("hello ");
    const string &s=s1+"world";
    

    第七章

    http://www.roading.org/develop/cpp/%E6%A8%A1%E6%9D%BF%E4%BA%8C%E4%BA%8B.html

    http://www.roading.org/develop/cpp/eh-rtti.html

    1.当catch的参数是基类时,可以捕获其派生类异常。

    //异常类型为exVertex,派生自exPoint
    catch(exPoint p){
    	//do something
    	throw;
    }
    catch(exPoint &rp){
    	//do something
    	throw;
    }
    

    如果参数是引用类型,则会有多态性质;如果是普通基类,则会被裁剪为基类对象。
    如果函数体内修改该捕获对象且继续抛出,对引用类型的修改会被繁殖到下一个catch子句;而非引用类型的修改会被抛弃。

    exVertex errVer;
    //...
    mumble(){
    	//...
        if(mumble_cond){
            //...
            throw errVer;
        }
    }
    

    被抛出的errVer是全局对象的复制品,所以catch子句中对异常对象的改变是局部性的,不会影响到errVer。只有在一个catch子句评估完毕并且知道他不会再抛出异常后,真正的异常对象才会被摧毁。

    7.3执行期类型识别(Runtime Type Identification RTTI)


    downcast(向下转换)

    意为 将基类转换至其派生类中的某一个。

    需要downcast的情况:指向派生类对象的基类指针,需要使用派生类的非虚函数,此时需要先把基类指针转换为派生类指针,再调用函数。

    downcast引发的问题:如果downcast不正确的使用,比如:被转换的指针并不是指向该派生类对象,而是指向其他派生类对象或基类对象,则也会被转换且不会报错,后续使用会造成问题。

    由此,产生了dynamic_cast

    dynamic_cast运算符

    dynamic_cast运算符,用于将 基类指针或引用安全地转换为派生类的指针或引用。

    通过在执行期(因此,只对支持多态的类适用)查询指向对象的类型来判断转换是否安全:

    如果指针的转换是不安全的,则传回0;

    如果引用的转换是不安全的,因为无法将0传回引用,所以会抛出bad_cast exception。

    typeid运算符

    对类型或表达式使用,返回const type_info&,用以获取类型信息。

    所有类型都可以使用typeid:

    对于多态类型,执行期查询虚函数表;非多态类型,直接静态取得。

    Point3d x;
    Point *p=&x;
    typeid(*p).name();//返回Point3d类型,通过查询虚函数表
    

    因此,可以先使用typeid判断对象类型是否一样,再决定是否转换,以代替dynamic_cast运算符。

    展开全文
  • 引用一张太平洋首页的一部分,像这部分数据数据表的设计和后台的设计要怎么做才能在降低开发难度、读取速度和编辑人员操作简便程度中取得平衡。PS:数据库是sql server 我现在的做法是直接另外,文字、链接...
  • 《C专家编程》:再论指针(八)

    千次阅读 2016-05-31 16:32:14
    千万不要忘了,当你把一个手指指向别人... 我们知道多维数组虽然看起来其存储结构是一张表,但是其实系统是决不允许程序按这种方式进行存储数据的。其单个元素的存储与引用都是以线性形式排列在内存中。如下图一所示:
  • 2009达内SQL学习笔记

    2010-02-10 19:46:58
    函数一般在数据上执行,它给数据的转换和处理提供了方便。不同的DBMS提供的函数不同。 函数可能会带来系统的不可移植性(可移植性:所编写的代码可以在多个系统运行)。 加入注释是一个使用函数的好习惯。 大多数...
  • 日期类型 date 7字节 用于存储中的日期和时间数据,取值范围是公元前4712年1月1日至公元9999年12月31日,7个字节分别表示世纪、年、月、日、时、分和秒 二进制数据类型 row 1~2000字节 可变长二进制数据,在具体...
  • excel的使用

    2012-11-25 17:06:01
    首先打开“工具”菜单,单击“宏”命令中的“Visual Basic编辑器”,进入Visual Basic编辑环境,在“工程-VBAobject”栏中的当前表上单击鼠标右键,选择“插入”-“模块”,在右边栏创建下面的函数rrr,代码如下: ...
  • 用EXCEL套打表格

    2013-08-06 12:18:08
    然后在相应单元格里输入数据、文字或公式引用(如图),记住千万不要设置单元格边框,这样一张表格就做好了。 4.页面设置 单击菜单栏中的“文件/页面设置…/页边距”,用尺子量取原表格纸中表格边框到纸张左边和上边...
  • relation 的,也即关系,有一对一、一对多、多对多这三种情况一对一 relation一对一关系意味着两张表,一张表有另外一张表的id引用(或者外键)。在model对象里面就是说,两个model,是一对一的比如,我们有两张表,...
  • Apache Hive: 是基于Hadoop的一个数据仓库工具,可以将结构化的数据文件映射为一张数据库,通过类SQL语句快速实现简单的MapReduce统计,不必开发专门的MapReduce应用,十分适合数据仓库的统计分析 笔记 Hive篇 ...
  • 21.对数据库的一张表进行操作,同时要对另一张表进行操作,如何实现? 答:将操作多个表的操作放入到事务中进行处理 22.TCP/IP 建立连接的过程?(3-way shake) 答:在TCP/IP协议中,TCP协议提供可靠的连接服务,采用三...
  • 人力资源管理软件(完全免费)

    热门讨论 2011-03-08 11:36:20
    提供个税工具,应发金额、所得税、应发任输一个数据自动计算另外2个 灵活生成各类统计报表,可定制方案 查询非常方便,支持组合条件查询 支持员工数据导入,支持初始工资数据导入,启用非常方便(见dll目录下导入...
  • 千里马酒店前台管理系统V7使用手册

    热门讨论 2011-06-16 14:09:38
    在状态栏的输入域(快捷键F8切换),您可以输入房号、姓名、帐号来打开一张客单,并按在住、预订、离店的优先次序来智能判断。所有的功能均可在命令行执行,例如预订1208房可以输入“res1 rmno=1208”,详细的功能...
  • 4.5 鼠标放到图片会显示另外一张图片 4.6 鼠标形状定义大全 4.7 鼠标移入移出时颜色变化 4.8 跟随鼠标的文字 4.9 跟随鼠标的彩色文字 4.10 跟随鼠标的魔法文字 4.11 跟随鼠标的星星 4.12 跟随鼠标的旋转背景 4.13 ...
  • 新版Android开发教程.rar

    千次下载 热门讨论 2010-12-14 15:49:11
    � 丰富的数据业务,将导致数据流量的显著增加 。 � 手机来源增加,价格更为低廉。 对软件开发者的影响 � 因为 Android 移动软件平台抱持开放互通的观念,势必吸引不少自由软件的拥护者。 � 开发方向有三个重点 :...
  • 我在新的数据上努力恢复原出问题的现象,却一直没有重现,于是恢复原数据,加载后立即重现。并注意到,当DSL端口激活时,主机复位。仔细比较两种数据的差别,发现出现主机复位问题的数据中DSL板配置了MNT/MLT端口,...
  • 光流是针对2D图像来说的,如果说一个图片流到另外一个图片,都是2D的物体移动,那就用光流来做。 如果是3D的物体流动,那我们就用场景流(Scene Flow),场景流在传统的方法就是使用的是SGBM, 利用的是双目成像...
  • Linux 操作系统基础教程 清华大学信息学院计算机系 目 录 前言....................................................................................数据量,而一个完整的发行版本大概都是 1Gbit 左右的数据量...

空空如也

空空如也

1 2
收藏数 24
精华内容 9
关键字:

引用另外一张表上数据