zoukankan      html  css  js  c++  java
  • C++/Java实现中的new[]

    看到一篇中文文章《C/C++, Java: Java的new[]与C++的new[]》很有意思。

    public class Test extends JPanel {
    private static final long serialVersionUID = 4767050156491994899L;
    public static void main(String[] args) {
    AnimApp[] array = new AnimApp[3]; // 没有执行构造函数, 在这里只是申请了空间.
    array[0] = new AnimApp(3); // 构造一个对象
    }
    }

    class AnimApp {
    public AnimApp(int times) {
    System.out.println("class AnimApp");
    }
    }

    文章提到:

    Java中的定义一个数组, 然后用new[], 这时并没有执行构造函数, 只是申请了一段内存空间, 与C++中的allocator<T>.allocate(size)(因为allocator<T>的类型在运行时确定, 所以不是指定空间的字节数, 而是用元素个数, 每个元素的大小allocator中有记录)相似. 然后在构造一个对象的时候, 即如上new AnimApp(3)时, 才真正的构造对象, 似allocator<T>.construct(param).

    但在C++中, new[]操作符(默认的)就会去先申请空间, 接着执行构造函数, 申请了多少个对象的空间, 执行多少次(每个对象一次), 所以想为无无参构造函数的类使用默认的new[]来定义一个数组是不行的, 这点与Java不同, 他是即申请空间, 同时也要构造对象, Java只是申请一段空间, 对于空间中的每个对象, 得自己显示的用new ClassName(param)来构造.


    这两步让我想到了Symbian下提到的"两段构造"技术。即一个对象的生成分为两个阶段:1)为对象分配空间.2)调用对象的构造函数为分配的空间初始化。

    在这两个过程中,都可能发生失败,特别是构造函数发生异常的时候,第一阶段分配的内存空间就会失去句柄,导致内存泄露(Memory Leak)。由于Symbian下的C++在C++标准化之前(至少是没有标准化的异常处理的),所以Symbian提出了自己的处理模式——”两段构造“。

    参考《Symbian OS异常三步曲之三:两段构造》如下

    一、两段构造的格式:

    编写一个类时,将构造代码分为两部分:
    1、  一个基本的不会发生异常退出的构造函数
    这个构造函数将被new操作符调用,它隐式的调用基类的构造函数,另外还可以调用那些不会发生异常退出的函数,也可以以默认值或传入构造函数的参数来初始化成员变量。
     
    2、  一个类方法(通常称为ConstructL())
    只要通过new操作符分配并构造的对象被压入了清除栈,该方法就可以单独调用了,它将继续完成对象的构造过程,这里面可以安全的执行那些可能发生异常退出的操作。即使发生了异常,由于之前已经将对象指针入栈,清除栈可以调用类的析构函数来释放所有已经成功分配的资源,并回收分配给对象本身的内存。

    相关的参考代码如下

    class CExample : public CBase
    {
    public:
    static CExample* NewL();//静态函数
    static CExample* NewLC();//静态函数
    ~CExample();//析构函数必须public,否则delete不能调用
    private:
    CExample();//绝对不能异常
    ConstructL();//将构造时所有可能异常的代码放在这里
    }
    CExample* CExample::NewLC()
    {
    CExample* me = new(ELeave) CExample();
    CleanupStack::PushL(me);
    me->ConstrutL();
    return me;
    }
    CExample* CExample::NewL()
    {
    CExample me = CExample::NewLC();
    CleanupStack::Pop(me);
    return me;
    }

    当然,NewL()和NewLC()函数是可以接受参数的,并可以通过参数来初始化对象。这些参数可以传递给简单构造函数,也可以传递给ContructL(),或者同时传递给两者。

    ————————————————————我是分割线——————————————

    对于Java这种与C++不同之处的原因要归为它们之间的实现不同。

    1)Java的数组不是集合,它只能保存同种类型的多个原始类型或者对象的引用。数组保存的仅仅是对象的引用,而不是对象本身。

    2)数组本身就是对象,Java中对象是在堆中的,因此数组无论保存原始类型还是其他对象类型,数组对象本身是在堆中的。

    3)在Java中,官方推荐的声明方式是Object []array,并且不能指定长度,只有new的时候才指定长度。

    4)对象类型数组中的引用被默认初始化为null。如:Object [] myobj= new Object [10]; 相当于从myobj[0]到myobj[9]都这样被自动初始化为myCar[i] = null;并没有调用构造函数。在myobj[1] = new Object的时候才调用。

    至于Java为什么要选择这种方式,则是其刻意避免指针(容易犯错),符号都是引用类型。

  • 相关阅读:
    记录:将图片数据生成 tfrecords 文件并在训练使用时读取
    记录:EM 算法估计混合高斯模型参数
    记录:Ubuntu 18.04 安装 tensorflow-gpu 版本
    记录:tf.saved_model 模块的简单使用(TensorFlow 模型存储与恢复)
    记录:TensorFlow 中的 padding 方式
    mybatis list映射
    idea使用插件mybatis-generator-plus生成mapper文件(mysql亲测可用)
    Element-UI树形表格
    Mysql5.7版本ERROR 1055问题
    为DISTINCT、GROUP BY和ORDER BY效率问题提提速
  • 原文地址:https://www.cnblogs.com/westfly/p/2360442.html
Copyright © 2011-2022 走看看