一.相关知识点
重载new和delete
当创建一个new表达式时有两件事发生。首先,使用运算符new分配内存,然后调用构造函数。在delete表达式里,调用析构函数,然后使用运算符delete释放内存。我们永远无法控制构造函数和析构函数的调用(否则我们可能意外地搅乱它们),但可以改变内存分配函数运算符new和delete。
被new和delete使用的内存分配系统是为通用目的而设计的。但在特殊的情形下,它不能满足我们的需要。改变分配系统的原因是考虑效率:我们也许要创建和销毁一个特定的类的非常多的对象以至于这个运算变成了速度的瓶颈。 C++允许重载newdelete来实现我们自己的存储分配方案,所以可以像这样处理问题。
另外一个问题是堆碎片:分配不同大小的内存可能造成在堆上产生很多碎片,以至于很快用完内存。也就是内存可能还有,但由于是碎片,找不到足够大的内存满足我们的需要。通过为特定类创建我们自己的内存分配器,可以确保这种情况不会发生。
在嵌入和实时系统里,程序可能必须在有限的资源情况下运行很长时间。这样的系统也可能要求分配内存花费相同的时间且不允许出现堆内存耗尽或出现很多碎片的情况。由客户定制的内存分配器是一种解决办法,否则程序设计者在这种情况下要避免使用 new和delete,从而失去了C++很有价值的优点。
当重载运算符 new和delete时,记住只改变原有的内存分配方法是很重要的。编译器将用new代替缺省的版本去分配内存,然后为那个内存调用构造函数。所以,虽然编译器遇到 new时会分配内存并调用构造函数,但当我们重载 new时,可以改变的只是内存分配部分。( delete也有相似的限制。)
重载全局new和delete
当全局版本的new和delete不能满足整个系统时,对其重载是很极端的方法。如果重载全局版本,那么缺省版本就完全不能被访问—甚至在这个重载定义里也不能调用它们。
重载的new必须有一个size_t参数。这个参数由编译器产生并传递给我们,它是要分配内存的对象的长度。必须返回一个指向等于这个长度(或大于这个长度,如果我们有这样做的原因)的对象的指针,或如果没有找到存储单元(在这种情况下,构造函数不被调用),返回一个0。然而如果找不到存储单元,不能仅仅返回0,还应该调用new-handler或进行异常处理,通知这里存在问题。
运算符new的返回值是一个void*,而不是指向任何特定类型的指针。它所做的是分配内存,而不是完成一个对象的建立—直到构造函数调用了才完成对象的创建,这是由编译器所确保的动作,不在我们的控制范围内。
运算符delete接受一个指向由运算符new分配的内存的void *。它是一个void*因为它是在调用析构函数后得到的指针。析构函数从存储单元里移去对象。运算符 delete的返回类型是void。
对象放置
重载运算符new还有其他两个不常见的用途。
1) 可能想要在内存的指定位置上放置一个对象。这对于面向硬件的内嵌系统特别重要,在这个系统中,一个对象可能和一个特定的硬件是同义的。
2) 可能想在调用new时,可以从不同的内存分配器中选择。
这两种情形都用相同的机制解决:重载的运算符 new可以带多于一个的参数。像前面所看到的,第一个参数总是对象的长度,它是在内部计算出来的并由编译器传递。但其他参数可以
由我们自己定义:一个放置对象的地址、一个对内存分配函数或对象的引用、或其他方便的任何设置。起先在调用过程中传递额外的参数给运算符 new的方法看起来似乎有点古怪:在关键字
new后是参数表(没有size_t参数,它由编译器处理),参数表后面是正在创建的对象的类名字。
例如:
X* xp = new(a) X ;
将传递a作为第二个参数给运算符 new。当然,这是在这个运算符 new已经声明的情况下才有效的。
二.相关代码
1.
<span style="font-size:18px;"><strong>/*对使用 C的动态内存分配函数创建一个类*/ /*MALCLASS.cpp*/ #include <stdlib.h>//malloc() and free() #include <string.h>//mesmet #include "E:\VC++\7_31_2\allege.h" #include <iostream.h> class obj { int i, j, k; enum{ sz = 100 }; char buf[sz]; public: void initialize() { cout << "initialize obj" << endl; i = j = k = 0; memset(buf, 0 , sz); } void destroy() { cout << "destroying obj" << endl; } }; int main() { obj* Obj = (obj*)malloc(sizeof(obj)); /*这里用户必须决定对象的长度(这也是程序出错原因之一)。因为它是一块内存而不是一 个对象,所以malloc()返回一个void*。 C++不允许将一个void* 赋予任何指针,所以必须映射。 因为malloc()可能找不到可分配的内存(在这种情况下它返回0),所以必须检查返回的指 针以确信内存分配成功。 但最坏的是: Obj->initialize( ) ; 用户在使用对象之前必须记住对它初始化。*/ allegemem(Obj); Obj->initialize(); Obj->destroy(); free(Obj); return 0; }</strong></span>
2.
<span style="font-size:18px;"><strong>/*显示初始化发生的情况*/ /*NEWDEL.cpp*/ #include <iostream.h> class tree { int height; public: tree(int Height) { height = Height; } ~tree() { cout << "*"; } friend ostream& operator << (ostream& os, const tree* t) { return os << "tree height is: " << t->height << endl; } }; int main() { tree* T = new tree(40); cout << T; delete T; return 0; }</strong></span>
3.
<span style="font-size:18px;"><strong>/*因为delete表达式对于不在堆上分配的指针是不安全的, 采取了一些步骤以防止在堆以外的地方创建 String*/ /*STRINGS.H*/ #ifndef STRINGS_H_ #define STRINGS_H_ #include <string.h> #include <iostream.h> class String { char* s; String(const char* S) //为了限制用户使用这个类,主构造函数声明为 private,所以,除了我们以外,没有人可以使用它 { s = new char[strlen(S) + 1]; strcpy(s, S); } String(const String&); void operator=(String&); //另外,拷贝构造函数也声明为 private,但没有定义,以防止任何人使用它,运算符‘ =’也是如此 public: friend String* makeString(const char* S) { return new String(S); } static String* make(const char* S) { return new String(S); } //访问这个函数的方法有两种。为了使用简单,它可以是全局的friend函数(称为makeString())。 //但如果不想“污染”全局名字空间,可以使之为 static成员函数(称为 make())并通过 //String::make()调用它。后一种形式对于更加明显地表示它属于这个类是有好处的。 ~String() { delete s; } operator char*() const { return s; } char* str() const { return s; } friend ostream& operator<<(ostream& os, const String& S) { return os << S.s; } }; #endif</strong></span>
4.
<span style="font-size:18px;"><strong>/*PSTASH.h*/ #ifndef PSTASH_H_ #define PSTASH_H_ class pstash { int quantity; int next; void** storage;//storage是一个 void指针数组 void inflate(int increase); public: pstash() { quantity = 0; storage = 0; next = 0; } ~pstash() { delete storage; } int add(void* element); void* operator[](int index) const; int count() const { return next; } }; #endif</strong></span>
<span style="font-size:18px;"><strong>/*PSTASH.cpp*/ #include "pstash.h" #include <iostream.h> #include <string.h> int pstash::add(void* element) { const InflateSize = 10; if(next >= quantity) { inflate(InflateSize); } storage[next+1] = element; return (next-1);//index number } void* pstash::operator[](int index) const { if(index >= next || index < 0) { return 0; } return storage[index]; } void pstash::inflate(int increase) { const psz = sizeof(void*); void** st = new void*[quantity + increase]; memset(st, 0, (quantity + increase) * psz); memcpy(st, storage, quantity * psz); quantity += increase; delete storage; storage = st; } </strong></span>
<span style="font-size:18px;"><strong>/*PSTEST.cpp*/ #include "pstash.h" #include "E:\VC++\8_4_1\strings.h" #include <fstream.h> #include "E:\VC++\7_31_2\allege.h" int main() { pstash intStash; for(int i = 0; i < 25; ++i) { intStash.add(new int(i)); //伪构造函数形式,所以一个新的 int对象的内存将在堆上创建并且这个int对象被初始化为值i。 } for(int u = 0; u < intStash.count(); ++u) { cout << "intStash[" << u << "] = " << *(int*)intStash[u] << endl; } ifstream infile("pstest.cpp"); allegefile(infile); const bufsize = 80; char buf[bufsize]; pstash stringStash; for(int j = 0; j < 10; ++j) { if(infile.getline(buf, bufsize)) { stringStash.add(makeString(buf)); } } while(infile.getline(buf, bufsize)) { stringStash.add(String::make(buf)); } for(int v = 0; stringStash[v]; ++v) { char* p = *(String*)stringStash[v]; //由运算符[]返回产生的指针必须被映射为 String*以使之具有正确的类型 cout << "stringStash[" << v << "] = " << p << endl; } return 0; } </strong></span>
5.
<span style="font-size:18px;"><strong>/*STACKl1.H*/ #ifndef STACKl1_H_ #define STACKl1_H_ class stack { struct link { void* data; link* next; link(void* Data, link* Next) { data = Data; next = Next; } }*head; public: stack() { head = 0; } ~stack(); void push(void* Data) { head = new link(Data, head); } void* peek() const { return head->data; } void* pop(); }; #endif </strong></span>
<span style="font-size:18px;"><strong>/*STACKl1.cpp*/ #include "stackl1.h" #include <stdlib.h> void* stack::pop() { if(head == 0) { return 0; } void* result = head->data; //使用void指针意味着在堆上创建的对象不能被 stack销毁 link* oldHead = head; head = head->next; delete oldHead; return result; } stack::~stack() { link* cursor = head; while(head) { cursor = cursor->next; delete head; head = cursor; } }</strong></span>
<span style="font-size:18px;"><strong>/*STKTSTl1.cpp*/ #include "stackl1.h" #include "E:\VC++\8_4_1\strings.h" #include <fstream.h> #include "E:\VC++\7_31_2\allege.h" //这个程序没有删除 stack里的指针,stack本身也没有做,所以那块内存丢失了。 int main() { ifstream file("stktstl1.cpp"); allegefile(file); const bufsize = 100; char buf[bufsize]; stack textlines; while(file.getline(buf, bufsize)) { textlines.push(String::make(buf)); } String* s; while((s = (String*)textlines.pop()) != 0) { cout << *s << endl; } }</strong></span>
6.
<span style="font-size:18px;"><strong>/*NEWHANDL.cpp*/ #include <iostream.h> #include <stdlib.h> #include <new.h> //new-handler 函数必须不带参数且具有 void返回值。 while循环将持续分配int对象(并丢掉 //它们的返回地址)直到全部内存被耗尽。在紧接下去对 new调用时,将没有内存可被分配,所 //以调用new-handler。 void out_of_memory() { cerr << "memory exhausted!" << endl; exit(1); } int main() { set_new_handler(out_of_memory); while(1) { new int[1000]; } return 0; }</strong></span>
7.
<span style="font-size:18px;"><strong>/*重载全局new和delete*/ /*GLOBLNEW.cpp*/ #include <stdio.h> #include <stdlib.h> void* operator new(size_t sz) { printf("operator new: %d bytes\n", sz); //用printf()不会进入死锁状态,因为它不调用new来初始化本身。 void* m = malloc(sz); if(!m) { puts("out of memory"); } return m; } void operator delete(void* m) { puts("operator delete"); free(m); } class s { int i[100]; public: s() { puts("s::s()"); } ~s() { puts("s::~s()"); } }; int main() { puts("creating & destroying an int"); int* p = new int(47); delete p; puts("creating & destroying an s"); s* S = new s; delete S; puts("creating & destroying s[3]"); s* SA = new s[3]; delete []SA; return 0; }</strong></span>
8.
<span style="font-size:18px;"><strong>/*为类 framis创建了一个非常简单的内存分配系统。程序开始时在静态数据区域内留出一块存储单元。 这块内存是用于 framis类型对象分配的内存空间。为了决定哪块存储单元已被使用,这里使用了一个 字节 ( bytes )数组,一个字节( byte )代表一块存储单元*/ /*FRAMIS.cpp*/ #include <stddef.h> #include <fstream.h> ofstream out("framis.out"); class framis { char c[10]; static unsigned char pool[]; static unsigned char alloc_map[]; public: enum{ psize = 100 }; framis() { out << "framis()\n"; } ~framis() { out << "~framis()..."; } void* operator new(size_t); void operator delete(void*); }; unsigned char framis::pool[psize * sizeof(framis)]; unsigned char framis::alloc_map[psize] = {0}; void* framis::operator new(size_t) { for(int i = 0; i < psize; ++i) { if(!alloc_map[i]) { out << "using block " << i << " ... "; alloc_map[i] = 1; return pool + (i * sizeof(framis)); } } out << "out of memory" << endl; return 0; } void framis::operator delete(void* m) { if(!m) { return; } unsigned long block = (unsigned long)m - (unsigned long)pool; block /= sizeof(framis); out << "freeing block " << block << endl; alloc_map[block] = 0; } int main() { framis* f[framis::psize]; for(int i = 0; i < framis::psize; ++i) { f[i] = new framis; } new framis; delete f[10]; f[10] = 0; framis* x = new framis; delete x; for(int j = 0; j < framis::psize; ++j) { delete f[j]; } return 0; } </strong></span>
9.
<span style="font-size:18px;"><strong>/*为数组重载new和delete 如果为一个类重载了运算符new和delete,那么无论何时创建这个类的一个对象都将调用这 些运算符。但如果为这些对象创建一个数组时,将调用全局运算符 new()立即为这个数组分配 足够的内存。全局运算符 delete()被调用来释放这块内存。可以通过为那个类重载数组版本的 运算符new[]和delete[]来控制对象数组的内存分配。*/ /*NEWARRY.cpp*/ /*但注意,申请的长度比期望的大 4个 字节。这额外的 4个字节是系统用来存放数组信息的,特别是数组中对象的数量。当用下面的 表达式时,方括号就告诉编译器它是一个对象数组,所以编译器产生寻找数组中对象的数量的 代码,然后多次调用析构函数。*/ #include <new.h> #include <fstream.h> ofstream trace("newarry.out"); class widget { int i[10]; public: widget() { trace << "*"; } ~widget() { trace << "~"; } void* operator new(size_t sz) { trace << "widget::new: " << sz << "bytes" << endl; return ::new char[sz]; } void operator delete(void* p) { trace << "widget::delete" << endl; ::delete []p; } void* operator new[](size_t sz) { trace << "widget::new[]: " << sz << " bytes" << endl; return ::new char[sz]; } void operator delete[](void* p) { trace << "widget::delete[]" << endl; ::delete []p; } }; int main() { trace << "new widget" << endl; widget* w = new widget; trace << "\ndelete widget" << endl; delete w; trace << "\nnew widget[25]" << endl; widget* wa = new widget[25]; trace << "\ndelete []widget" << endl; delete []wa; return 0; }</strong></span>
10.
<span style="font-size:18px;"><strong>/*构造函数调用*/ /*NOMEMORY.cpp*/ #include <iostream.h> #include <new.h> //当程序运行时,它仅打印来自运算符new的信息。因为new返回0,所以构造函数没有被调 //用,当然它的信息不会打印出来。 void my_new_handler() { cout << "new handler called" << endl; } class nomemory { public: nomemory() { cout << "nomemory::nomemory()" << endl; } void* operator new(size_t sz) { cout << "nomemory::operator new" << endl; return 0; } }; int main() { set_new_handler(my_new_handler); nomemory* nm = new nomemory; cout << "nm = " << nm << endl; return 0; }</strong></span>
11.
<span style="font-size:18px;"><strong>/*在一个特定的存储单元里放置一个对象*/ /*PLACEMNT.cpp*/ #include <stddef.h> #include <iostream.h> class X { int i; public: X(int I = 0) { i = I; } ~X() { cout << "X::~X()" << endl; } void* operator new(size_t, void* loc) { return loc; } }; int main() { int l[10]; X* xp = new(l) X(47); xp->X::~X();//显式地调用析构函数 //销毁对象时将会出现两难选择的局面。因为仅有一个版本运算符 delete,所以没有办法说 //“对这个对象使用我的特殊内存释放器”。我们可以调用析构函数,但不能用动态内存机制释放 //内存,因为内存不是在堆上分配的。 return 0; }</strong></span>
三.习题+解答
1. 写一个有构造函数和析构函数的类。在构造函数和析构函数里通过 cout宣布自己。通过这个类自己证明new和delete总是调用构造函数和析构函数。用 new创建这个类的一个对象,用delete销毁它。在堆上创建和销毁这些对象的一个数组。
#include <iostream.h> class what { int i; public: what() { cout << "what::what()" << endl; } ~what() { cout << "what::~what()" << endl; } }; int main() { what* w = new what; delete w; what* v = new what[10]; delete []v; return 0; }
运行结果如下:
2. 创建一个pstash对象,并把练习1的对象用new创建填入。观察当这个对象出了范围和它的析构函数被调用时发生的情况。
#include <iostream.h> #include "E:\VC++\8_4_2\pstash.h" #include "E:\VC++\8_4_2\pstash.cpp" class what { int i; public: what() { cout << "what::what()" << endl; } ~what() { cout << "what::~what()" << endl; } }; int main() { pstash p; for(int i = 0; i < 25; ++i) { p.add(new what); } //对象出了范围会继续分配直至内存耗尽 //p.~pstash();//崩溃 return 0; }
3. 写一个有单个对象版本和数组版本的重载运算符new和delete的类。演示这两个版本的工作情况。
/*使用标准的C库函数中的时间函数来生成简单的 Time类*/ /*CPPTIME.h*/ #ifndef TEXT_H_ #define TEXT_H_ #include <time.h> #include <string.h> #include <iostream> using namespace std; class A { int i; char c; float f; double d; int j; char n; float m; double g; public: A() { i = j = 0; c = n = '0'; f = m = 1.0; d = g = 0.0; cout << "Create A()!" << endl; } A(int I, char C, float F, double D, int J, char N, float M, double G); }; A::A(int I, char C, float F, double D, int J, char N, float M, double G) :i(I),c(C),f(F),d(D),j(J),n(N),m(M),g(G) { cout << "Create A(int I, char C, float F, double D, int J, char N, float M, double G)!" << endl; } class B { A a; int flag; char name; double number; enum{ size = 10 }; int arr[size]; public: friend A; B() { flag = 0; name = '0'; number = 0.0; for(int k = 0,m = 0;k < size; ++k,++m) { arr[k] = m; } cout << "Create B()!" << endl; } B(int Flag, char Name, double Number); void g(); void f() { cout << "f()" << " "; } }; B::B(int Flag, char Name, double Number):flag(Flag),name(Name),number(Number) { cout << "Create B(int Flag, char Name, double Number)!" << endl; } void B::g() { cout << "g()" << " "; } class Time { time_t T; tm local; char Ascii[26]; unsigned char lflag, aflag; void updateLocal() { if(!lflag) { local = *localtime(&T); lflag++; } } void updateAscii() { if(!aflag) { updateLocal(); strcpy(Ascii, asctime(&local)); aflag++; } } public: Time() { mark(); } void mark() { lflag = aflag = 0; time(&T); } const char* ascii() { updateAscii(); return Ascii; } int delta(Time* dt) const { return difftime(T, dt->T); } int DaylightSavings() { updateLocal(); return local.tm_isdst; } int DayOfYear() { updateLocal(); return local.tm_yday; } int DayOfWeek() { updateLocal(); return local.tm_wday; } int Since1900() { updateLocal(); return local.tm_year; } int Month() { updateLocal(); return local.tm_mon; } int DayOfMonth() { updateLocal(); return local.tm_mday; } int Hour() { updateLocal(); return local.tm_hour; } int Minute() { updateLocal(); return local.tm_min; } int Second() { updateLocal(); return local.tm_sec; } }; #endif /*两个私有函数updateLocal( )和 updateAscii( )检查标 记,并有条件地执行更新。*/
#include "E:\VC++\7_31_1\cpptime.h" class why { int i[10]; public: why() { cout << "why::why()" << endl; } ~why() { cout << "why::~why()" << endl; } void* operator new(size_t sz) { cout << "why::new: " << sz << "bytes" << endl; return ::new char[sz]; } void operator delete(void* p) { cout << "why::delete" << endl; ::delete []p; } void* operator new[](size_t sz) { cout << "why::new[]: " << sz << " bytes" << endl; return ::new char[sz]; } void operator delete[](void* p) { cout << "why::delete[]" << endl; ::delete []p; } }; int main() { cout << "new why" << endl; Time start1; why* w = new why; cout << "\ndelete why" << endl; delete w; Time end1; cout << endl; cout << "start1 = " << start1.ascii(); cout << "end1 = " << end1.ascii(); cout << "delta1 = " << end1.delta(&start1) << endl; cout << "\nnew why[25]" << endl; Time start2; why* wa = new why[25]; cout << "\ndelete []why" << endl; delete []wa; Time end2; cout << endl; cout << "start2 = " << start2.ascii(); cout << "end2 = " << end2.ascii(); cout << "delta2 = " << end2.delta(&start2) << endl; return 0; }
4. 设计一个对FRAMIS.CPP进行测试的程序来显示定制的new和delete比全局的new和delete大约快多少。
/*使用标准的C库函数中的时间函数来生成简单的 Time类*/ /*CPPTIME.h*/ #ifndef TEXT_H_ #define TEXT_H_ #include <time.h> #include <string.h> #include <iostream> using namespace std; class A { int i; char c; float f; double d; int j; char n; float m; double g; public: A() { i = j = 0; c = n = '0'; f = m = 1.0; d = g = 0.0; cout << "Create A()!" << endl; } A(int I, char C, float F, double D, int J, char N, float M, double G); }; A::A(int I, char C, float F, double D, int J, char N, float M, double G) :i(I),c(C),f(F),d(D),j(J),n(N),m(M),g(G) { cout << "Create A(int I, char C, float F, double D, int J, char N, float M, double G)!" << endl; } class B { A a; int flag; char name; double number; enum{ size = 10 }; int arr[size]; public: friend A; B() { flag = 0; name = '0'; number = 0.0; for(int k = 0,m = 0;k < size; ++k,++m) { arr[k] = m; } cout << "Create B()!" << endl; } B(int Flag, char Name, double Number); void g(); void f() { cout << "f()" << " "; } }; B::B(int Flag, char Name, double Number):flag(Flag),name(Name),number(Number) { cout << "Create B(int Flag, char Name, double Number)!" << endl; } void B::g() { cout << "g()" << " "; } class Time { time_t T; tm local; char Ascii[26]; unsigned char lflag, aflag; void updateLocal() { if(!lflag) { local = *localtime(&T); lflag++; } } void updateAscii() { if(!aflag) { updateLocal(); strcpy(Ascii, asctime(&local)); aflag++; } } public: Time() { mark(); } void mark() { lflag = aflag = 0; time(&T); } const char* ascii() { updateAscii(); return Ascii; } int delta(Time* dt) const { return difftime(T, dt->T); } int DaylightSavings() { updateLocal(); return local.tm_isdst; } int DayOfYear() { updateLocal(); return local.tm_yday; } int DayOfWeek() { updateLocal(); return local.tm_wday; } int Since1900() { updateLocal(); return local.tm_year; } int Month() { updateLocal(); return local.tm_mon; } int DayOfMonth() { updateLocal(); return local.tm_mday; } int Hour() { updateLocal(); return local.tm_hour; } int Minute() { updateLocal(); return local.tm_min; } int Second() { updateLocal(); return local.tm_sec; } }; #endif /*两个私有函数updateLocal( )和 updateAscii( )检查标 记,并有条件地执行更新。*/
#include <stddef.h> #include "E:\VC++\7_31_1\cpptime.h" class framis { char c[10]; static unsigned char pool[]; static unsigned char alloc_map[]; public: enum{ psize = 100 }; framis() { cout << "framis()\n"; } ~framis() { cout << "~framis()..."; } void* operator new(size_t); void operator delete(void*); }; unsigned char framis::pool[psize * sizeof(framis)]; unsigned char framis::alloc_map[psize] = {0}; void* framis::operator new(size_t) { for(int i = 0; i < psize; ++i) { if(!alloc_map[i]) { cout << "using block " << i << " ... "; alloc_map[i] = 1; return pool + (i * sizeof(framis)); } } cout << "out of memory" << endl; return 0; } void framis::operator delete(void* m) { if(!m) { return; } unsigned long block = (unsigned long)m - (unsigned long)pool; block /= sizeof(framis); cout << "freeing block " << block << endl; alloc_map[block] = 0; } int main() { Time start1; framis* x = new framis; delete x; Time end1; cout << endl; cout << "start1 = " << start1.ascii(); cout << "end1 = " << end1.ascii(); cout << "delta1 = " << end1.delta(&start1) << endl; Time start2; framis* f[framis::psize]; for(int i = 0; i < framis::psize; ++i) { f[i] = new framis; } for(int j = 0; j < framis::psize; ++j) { delete f[j]; } Time end2; cout << endl; cout << "start2 = " << start2.ascii(); cout << "end2 = " << end2.ascii(); cout << "delta2 = " << end2.delta(&start2) << endl; return 0; }
以上代码仅供参考,如有错误请大家指出,谢谢大家~
版权声明:本文为博主原创文章,未经博主允许不得转载。