词条 | NEW |
释义 | 1 标准模板库NEW是STL ( Standard Template Library,标准模板库,惠普实验室开发的一系列软件的统称。惠普实验室开发)软件数据的一种。从堆中划分一块区域,动态创建一个类型的数据,最后返回该区域的指针。该数据类型可以是标准数据类型,也可以是用户自定义类型。 概念 1、数据模板从堆中划分一块区域,动态创建一个类型的数据,最后返回该区域的指针。该数据类型可以是标准数 据类型,也可以是用户自定义类型。数据使用完后,应调用delete运算符来释放动态申请的内存(在堆中)。 2、英文翻译为新的意思。 ◎ VBNew 关键字引入 New 子句,该子句创建一个新的对象实例。New 子句必须指定一个可以用来创建实例的已定义类。可以在声明语句或赋值语句中使用 New。执行该语句时,它将调用指定类的构造函数,传递您提供的所有参数: Dim Obj As Object Obj = New SomeClass("String required by constructor") ' ... Dim MyLabel As New Label() 由于数组是类,因此 New 可以创建新的数组实例: Dim MyArray As Integer() MyArray = New Integer() {0, 1, 2, 3} 如果内存不足,无法创建新的实例,公共语言运行库将引发 OutOfMemoryException 错误,即内存溢出。 ◎ C++(1)new可用来生成动态无名变量, 如 int *p=new int; int *p=new int [10]; //动态数组的大小可以是变量或常量;而一般直接声明数组时,数组大小必须是常量 又如: int *p1; double *p2; p1=new int(12); p2=new double [100]; l 分别表示动态分配了用于存放整型数据的内存空间,将初值12写入该内存空间,并将首地址值返回指针p1; l 动态分配了具有100个双精度实型数组元素的数组,同时将各存储区的首地址指针返回给指针变量p2; 对于生成二维及更高维的数组,应使用多维指针。 以二维指针为例 int **p=new int* [row]; //row是二维数组的行,p是指向一个指针数组的指针 for(int i=0; i<row; i++) p[i]=new int [col]; //col是二维数组的列,p是指向一个int数组的指针 删除这个二维数组 for(int i = 0; i < row; i++) delete []p[i]; //先删除二维数组的列 delete []p; (2)使用完动态无名变量后应该及时释放,要用到 delete 运算符 delete p; //释放单个变量 delete [ ] p;//释放数组变量(不论数组是几维) 相比于一般的变量声明,使用new和delete 运算符可方便的使用变量。 ◎ C#new在C#中有三种用法: (1)new运算符:用于创建对象和调用构造函数。 (2)new修饰符:用于隐藏基类成员的继承成员。 (3)new约束:用于在泛型声明中约束可能用作类型参数的参数的类型。 ◎ new运算符1.用于创建对象和调用构造函数 例:Class_TestMyClass=newClass_Test(); 2.也用于为值类型调用默认的构造函数 例:intmyInt=newint(); myInt初始化为0,它是int类型的默认值。该语句的效果等同于:intmyInt=0; 3.不能重载new运算符。 4.如果new运算符分配内存失败,则它将引发OutOfMemoryException异常。 ◎ new修饰符使用new修饰符显式隐藏从基类继承的成员。若要隐藏继承的成员,请使用相同名称在派生类中声明该成员,并用new修饰符修饰它。 请看下面的类: public class MyClass { public int x; public void Invoke(){} } 在派生类中用Invoke名称声明成员会隐藏基类中的Invoke方法,即: public class MyDerivedC:MyClass { new public void Invoke(){} } 但是,因为字段x不是通过类似名隐藏的,所以不会影响该字段。 通过继承隐藏名称采用下列形式之一: 1.引入类或结构中的常数、指定、属性或类型隐藏具有相同名称的所有基类成员。 2.引入类或结构中的方法隐藏基类中具有相同名称的属性、字段和类型。同时也隐藏具有相同签名的所有基类方法。 3.引入类或结构中的索引器将隐藏具有相同名称的所有基类索引器。 4.在同一成员上同时使用new和override是错误的。 注意:在不隐藏继承成员的声明中使用new修饰符将生成警告。 ◎ 详细分析“new”是C++的一个关键字,同时也是操作符。关于new的话题非常多,因为它确实比较复杂,也非常神秘。 ◎ new的过程当我们使用关键字new在堆上动态创建一个对象时,它实际上做了三件事:获得一块内存空间、调用构造函数、返回正确的指针。当然,如果我们创建的是简单类型的变量,那么第二步会被省略。假如我们定义了如下一个类A: class A { int i; public: A(int _i) :i(_i*_i) {} void Say() {cout<<"i="<<i<<endl;} }; //调用new: A* pa = new A(3); 那么上述动态创建一个对象的过程大致相当于以下三句话(只是大致上): A* pa = (A*)malloc(sizeof(A)); pa->A::A(3); return pa; 虽然从效果上看,这三句话也得到了一个有效的指向堆上的A对象的指针pa,但区别在于,当malloc失败时,它不会调用分配内存失败处理程序new_handler,而使用new的话会的。因此我们还是要尽可能的使用new,除非有一些特殊的需求。 ◎ new的三种形态到目前为止,本文所提到的new都是指的“new operator”或称为“new expression”,但事实上在C++中一提到new,至少可能代表以下三种含义:new operator、operator new、placement new。 new operator就是我们平时所使用的new,其行为就是前面所说的三个步骤,我们不能更改它。但具体到某一步骤中的行为,如果它不满足我们的具体要求 时,我们是有可能更改它的。三个步骤中最后一步只是简单的做一个指针的类型转换,没什么可说的,并且在编译出的代码中也并不需要这种转换,只是人为的认识 罢了。但前两步就有些内容了。 new operator的第一步分配内存实际上是通过调用operator new来完成的,这里的new实际上是像加减乘除一样的操作符,因此也是可以重载的。operator new默认情况下首先调用分配内存的代码,尝试得到一段堆上的空间,如果成功就返回,如果失败,则转而去调用一个new_hander,然后继续重复前面 过程。如果我们对这个过程不满意,就可以重载operator new,来设置我们希望的行为。例如: class A { public: void* operator new(size_tsize) { printf("operator new calledn"); return ::operator new(size); } }; A* a = new A(); 这里通过::operator new调用了原有的全局的new,实现了在分配内存之前输出一句话。全局的operator new也是可以重载的,但这样一来就不能再递归的使用new来分配内存,而只能使用malloc了: void* operator new(size_t size) { printf("global newn"); return malloc(size); } 相应的,delete也有delete operator和operator delete之分,后者也是可以重载的。并且,如果重载了operator new,就应该也相应的重载operator delete,这是良好的编程习惯。 new的第三种形态——placement new是用来实现定位构造的,因此可以实现new operator三步操作中的第二步,也就是在取得了一块可以容纳指定类型对象的内存后,在这块内存上构造一个对象,这有点类似于前面代码中的“p- >A::A(3);”这句话,但这并不是一个标准的写法,正确的写法是使用placement new: #include <new.h> void main() { char s[sizeof(A)]; A* p = (A*)s; new(p) A(3); //p->A::A(3); p->Say(); } 对头文件<new>或<new.h>的引用是必须的,这样才 可以使用placement new。这里“new(p) A(3)”这种奇怪的写法便是placement new了,它实现了在指定内存地址上用指定类型的构造函数来构造一个对象的功能,后面A(3)就是对构造函数的显式调用。这里不难发现,这块指定的地址既 可以是栈,又可以是堆,placement对此不加区分。但是,除非特别必要,不要直接使用placement new ,这毕竟不是用来构造对象的正式写法,只不过是new operator的一个步骤而已。使用new operator地编译器会自动生成对placement new的调用的代码,因此也会相应的生成使用delete时调用析构函数的代码。如果是像上面那样在栈上使用了placement new,则必须手工调用析构函数,这也是显式调用析构函数的唯一情况: p->~A(); 当我们觉得默认的new operator对内存的管理不能满足我们的需要,而希望自己手工的管理内存时,placement new就有用了。STL中的allocator就使用了这种方式,借助placement new来实现更灵活有效的内存管理。 ◎ 处理内存分配异常正如前面所说,operator new的默认行为是请求分配内存,如果成功则返回此内存地址,如果失败则调用一个new_handler,然后再重复此过程。于是,想要从operator new的执行过程中返回,则必然需要满足下列条件之一: l 分配内存成功 l new_handler中抛出bad_alloc异常 l new_handler中调用exit()或类似的函数,使程序结束 于是,我们可以假设默认情况下operator new的行为是这样的: void* operator new(size_t size) { void* p = null while(!(p = malloc(size))) { if(null == new_handler) throw bad_alloc(); try { new_handler(); } catch(bad_alloc e) { throw e; } catch(…) {} } return p; } 在默认情况下,new_handler的行为是抛出一个bad_alloc异常,因此 上述循环只会执行一次。但如果我们不希望使用默认行为,可以自定义一个new_handler,并使用std::set_new_handler函数使其 生效。在自定义的new_handler中,我们可以抛出异常,可以结束程序,也可以运行一些代码使得有可能有内存被空闲出来,从而下一次分配时也许会成 功,也可以通过set_new_handler来安装另一个可能更有效的new_handler。例如: void MyNewHandler() { printf(“New handler called!n”); throw std::bad_alloc(); } std::set_new_handler(MyNewHandler); 这里new_handler程序在抛出异常之前会输出一句话。应该注意,在 new_handler的代码里应该注意避免再嵌套有对new的调用,因为如果这里调用new再失败的话,可能会再导致对new_handler的调用, 从而导致无限递归调用(没有尝试过)。 在编程时我们应该注意到对new的调用是有可能有异常被抛出的,因此在new的代码周围应该注意保持其事务性,即不能因为调用new失败抛出异常来导致不正确的程序逻辑或数据结构的出现。例如: class SomeClass { static int count; SomeClass() {} public: static SomeClass* GetNewInstance() { count++; return new SomeClass(); } }; 静态变量count用于记录此类型生成的实例的个数,在上述代码中,如果因new分配内存失败而抛出异常,那么其实例个数并没有增加,但count变量的值却已经多了一个,从而数据结构被破坏。正确的写法是: static SomeClass* GetNewInstance() { SomeClass* p = new SomeClass(); count++; return p; } 这样一来,如果new失败则直接抛出异常,count的值不会增加。类似的,在处理线程同步时,也要注意类似的问题: void SomeFunc() { lock(someMutex); //加一个锁 delete p; p = new SomeClass(); unlock(someMutex); } 此时,如果new失败,unlock将不会被执行,于是不仅造成了一个指向不正确地址的指针p的存在,还将导致someMutex永远不会被解锁。这种情况是要注意避免的。(参考:C++箴言:争取异常安全的代码) ◎ STL的内存分配与traits技巧在《STL原码剖析》一书中详细分析了SGI STL的内存分配器的行为。与直接使用new operator不同的是,SGI STL并不依赖C++默认的内存分配方式,而是使用一套自行实现的方案。首先SGI STL将可用内存整块的分配,使之成为当前进程可用的内存,当程序中确实需要分配内存时,先从这些已请求好的大内存块中尝试取得内存,如果失败的话再尝试 整块的分配大内存。这种做法有效的避免了大量内存碎片的出现,提高了内存管理效率。 为了实现这种方式,STL使用了placement new,通过在自己管理的内存空间上使用placement new来构造对象,以达到原有new operator所具有的功能。 template <class T1, class T2> inline void construct(T1* p, const T2& value) { new(p) T1(value); } 此函数接收一个已构造的对象,通过拷贝构造的方式在给定的内存地址p上构造一个新对 象,代码中后半截T1(value)便是placement new语法中调用构造函数的写法,如果传入的对象value正是所要求的类型T1,那么这里就相当于调用拷贝构造函数。类似的,因使用了 placement new,编译器不会自动产生调用析构函数的代码,需要手工的实现: template <class T> inline void destory(T* pointer) { pointer->~T(); } 与此同时,STL中还有一个接收两个迭代器的destory版本,可将某容器上指定范 围内的对象全部销毁。典型的实现方式就是通过一个循环来对此范围内的对象逐一调用析构函数。如果所传入的对象是非简单类型,这样做是必要的,但如果传入的 是简单类型,或者根本没有必要调用析构函数的自定义类型(例如只包含数个int成员的结构体),那么再逐一调用析构函数是没有必要的,也浪费了时间。为 此,STL使用了一种称为“type traits”的技巧,在编译器就判断出所传入的类型是否需要调用析构函数: template <class ForwardIterator> inline void destory(ForwardIterator first, ForwardIterator last) { __destory(first, last, value_type(first)); } 其中value_type()用于取出迭代器所指向的对象的类型信息,于是: template<class ForwardIterator, class T> inline void __destory(ForwardIterator first, ForwardIterator last, T*) { typedef typename __type_traits<T>::has_trivial_destructor trivial_destructor; __destory_aux(first, last, trivial_destructor()); } //如果需要调用析构函数: template<class ForwardIterator> inline void __destory_aux(ForwardIterator first, ForwardIterator last, __false_type) { for(; first < last; ++first) destory(&*first); //因first是迭代器,*first取出其真正内容,然后再用&取地址 } //如果不需要,就什么也不做: tempalte<class ForwardIterator> inline void __destory_aux(ForwardIterator first, ForwardIterator last, __true_type) {} 因上述函数全都是inline的,所以多层的函数调用并不会对性能造成影响,最终编译 的结果根据具体的类型就只是一个for循环或者什么都没有。这里的关键在于__type_traits<T>这个模板类上,它根据不同的T类 型定义出不同的has_trivial_destructor的结果,如果T是简单类型,就定义为__true_type类型,否则就定义为 __false_type类型。其中__true_type、__false_type只不过是两个没有任何内容的类,对程序的执行结果没有什么意义,但在编译器看来它对模板如何特化就具有非常重要的指导意义了,正如上面代码所示的那样。__type_traits<T>也是特化了的一系列模 板类: struct __true_type {}; struct __false_type {}; template <class T> struct __type_traits { public: typedef __false _type has_trivial_destructor; …… }; template<> //模板特化 struct __type_traits<int> //int的特化版本 { public: typedef __true_type has_trivial_destructor; …… }; …… //其他简单类型的特化版本 如果要把一个自定义的类型MyClass也定义为不调用析构函数,只需要相应的定义__type_traits<T>的一个特化版本即可: template<> struct __type_traits<MyClass> { public: typedef __true_type has_trivial_destructor; …… }; 模板是比较高级的C++编程技巧,模板特化、模板偏特化就更是技巧性很强的东西, STL中的type_traits充分借助模板特化的功能,实现了在程序编译期通过编译器来决定为每一处调用使用哪个特化版本,于是在不增加编程复杂性的 前提下大大提高了程序的运行效率。更详细的内容可参考《STL源码剖析》第二、三章中的相关内容。 ◎ 带有“[]”的new和delete我们经常会通过new来动态创建一个数组,例如: char* s = new char[100]; …… delete s; 严格的说,上述代码是不正确的,因为我们在分配内存时使用的是new[],而并不是简单的new,但释放内存时却用的是delete。正确的写法是使用delete[]: delete[] s; 但是,上述错误的代码似乎也能编译执行,并不会带来什么错误。事实上,new与new[]、delete与delete[]是有区别的,特别是当用来操作复杂类型时。假如针对一个我们自定义的类MyClass使用new[]: MyClass* p = new MyClass[10]; 上述代码的结果是在堆上分配了10个连续的MyClass实例,并且已经对它们依次调 用了构造函数,于是我们得到了10个可用的对象,这一点与Java、C#有区别的,Java、C#中这样的结果只是得到了10个null。换句话说,使用 这种写法时MyClass必须拥有不带参数的构造函数,否则会发现编译期错误,因为编译器无法调用有参数的构造函数。 当这样构造成功后,我们可以再将其释放,释放时使用delete[]: delete[] p; 当我们对动态分配的数组调用delete[]时,其行为根据所申请的变量类型会有所不 同。如果p指向简单类型,如int、char等,其结果只不过是这块内存被回收,此时使用delete[]与delete没有区别,但如果p指向的是复杂 类型,delete[]会针对动态分配得到的每个对象调用析构函数,然后再释放内存。因此,如果我们对上述分配得到的p指针直接使用delete来回收, 虽然编译期不报什么错误(因为编译器根本看不出来这个指针p是如何分配的),但在运行时(DEBUG情况下)会给出一个Debug assertion failed提示。 ◎ 为对象调用析构函数要回答这个问题,我们可以首先看一看new[]的重载。 class MyClass { int a; public: MyClass() { printf("ctorn"); } ~MyClass() { printf("dtorn"); } }; void* operator new[](size_t size) { void* p = operator new[](size); printf("calling new[] with size=%d address=%pn", size, p); return p; } // 主函数 MyClass* mc = new MyClass[3]; printf("address of mc=%pn", mc); delete[] mc; 运行此段代码,得到的结果为:(VC2005) calling new[] with size=16 address=003A5A58 ctor ctor ctor address of mc=003A5A5C dtor dtor dtor 虽然对构造函数和析构函数的调用结果都在预料之中,但所申请的内存空间大小以及地址的 数值却出现了问题。我们的类MyClass的大小显然是4个字节,并且申请的数组中有3个元素,那么应该一共申请12个字节才对,但事实上系统却为我们申 请了16字节,并且在operator new[]返后我们得到的内存地址是实际申请得到的内存地址值加4的结果。也就是说,当为复杂类型动态分配数组时,系统自动在最终得到的内存地址前空出了 4个字节,我们有理由相信这4个字节的内容与动态分配数组的长度有关。通过单步跟踪,很容易发现这4个字节对应的int值为0x00000003,也就是 说记录的是我们分配的对象的个数。改变一下分配的个数然后再次观察的结果证实了我的想法。于是,我们也有理由认为new[] operator的行为相当于下面的伪代码: template <class T> T* New[](int count) { int size = sizeof(T) * count + 4; void* p = T::operator new(size); *(int*)p = count; T* pt = (T*)((int)p + 4); for(int i = 0; i < count; i++) new(&pt) T(); return pt; } 上述示意性的代码省略了异常处理的部分,只是展示当我们对一个复杂类型使用new[] 来动态分配数组时其真正的行为是什么,从中可以看到它分配了比预期多4个字节的内存并用它来保存对象的个数,然后对于后面每一块空间使用 placement new来调用无参构造函数,这也就解释了为什么这种情况下类必须有无参构造函数,最后再将首地址返回。类似的,我们很容易写出相应的delete[]的实 现代码: template <class T> void Delete[](T* pt) { int count = ((int*)pt)[-1]; for(int i = 0; i < count; i++) pt.~T(); void* p = (void*)((int)pt – 4); T::operator delete(p); } 由此可见,在默认情况下operator new[]与operator new的行为是相同的,operator delete[]与operator delete也是,不同的是new operator与new[] operator、delete operator与delete[] operator。当然,我们可以根据不同的需要来选择重载带有和不带有“[]”的operator new和delete,以满足不同的具体需求。 2 时尚鞋品牌New New中文名: 新风潮 英文名: New New Shoes 韩国品牌New New Shoes做为国内女性时尚新锐,在近几年中,以其独特的市场营销理念,及优质的产品品质,成为国内鞋类的领导者之一。New New Shoes的风格简约明快、高贵典雅,继承韩国女性注重展示自然美和曲线美的设计理念,现以其品牌的独特魅力,引进韩国一级生产技术及整个生产供货流程,选用最优质的上等面料,在中国本土加工生产。 公司旗下拥有韩国独具创意的设计师、具有丰富设计经验的制版师、具有资深技术的生产技术管理人员,在国内实行严格监督管理,以确保国内最优的质量、最好的产品而且占有绝对的价格优势。现与有着多年OEM代工合作的广州华芳贸易有限公司,时尚鞋网共同合作开发国内市场,并与08年获得欧州著名游艇生产商Pierre青睐,结成深度战略合作。 产品在进入国内市场不到3年的时间里,多次受到多家主流媒体轮翻报到推荐,包括瑞丽女性,太平洋女人,onlylady女人志,E风尚等,网络搜索人气每日聚增。产品品质口碑相传,积累了一批年轻女性的追棒。一直定位亲民线路的价格策略,现每日都有大量来自世界各国的定单,New New Shoes 正在加速迈向品牌国际化的步伐 3 泰国明星名称:Chaiyapol Pupart(查亚鹏·普帕特) 昵称:New 职业:演员 出生日期:1990年2月 27日 国籍:泰国,比利时身高: 180 体重: 69 爱好:踢足球,游泳MSN 音乐 走暹罗海洋之旅 教育: 中学: Satit Patumwan示范学校(2005 - 2007) 目前参加朱拉隆功大学一年级体育科学 电视剧 新凤凰血 as Chanok with Son & vill (Ch.5 2008) 饰 查诺 世袭家族 as Patiya/Pu with Mos & vill (Ch.5 2009) 饰 布 爱在路上 as Sithisak with Bie & vill (Ch.5 2010) 饰 迪沙 撒旦的玫瑰 Bee & Captain 音乐MV "Thok Loom Ruk" Waii -------------------------------------------------------------------------------------------------------------------------------------- 魔兽1区血色十字军NEW公会 会长:轻声叹息 进度:8/13 |
随便看 |
百科全书收录4421916条中文百科知识,基本涵盖了大多数领域的百科知识,是一部内容开放、自由的电子版百科全书。