我们首先来看上次遗留的问题。 把(1)中的代码换为注释部分,或许您一时还认识不到会有什么发生,但最终是通不过的,调试抛出异常,信息如下: 未处理的“System.Runtime.InteropServices.SEHException”类型的异常出现在 TestInit.exe 中
其他信息:外部组件发生异常。
This application has requested the Runtime to terminate it in an unusual way. Please contact the application's support team for more information. Press any key to continue
我想,您回头再细看的话,就会明白为什么如此了(我们写程序一定要追问到底:)。
我们今天要谈的是,一些变量只有唯一的初始化形式,通过例子,告诉您要特别注意。然后,我们就一步一步,来看资源浅拷贝的问题。我相信初学c++的同学,会对“拷贝函数”有些疑问,它就是为了解决上述问题的;但事实上,还有一个隐藏的地方,今天我也想给您指出。 这些程序,可是我特意设计的哦。希望可以很方便的认识问题所在,与解决之道。
首先,看第一个例子。在类中,这两类变量: e.g. Name &name; //引用 const int ID; //常量 它们的初始化形式是唯一的。而且必须由您来初始化。 看下面的程序: //human.h #pragma once
class Name { char *name; public: //... }; class Human { Name &name; const int ID;//每个人都唯一的标志号 //... public: Human(void); ~Human(void); //... }; //human.cpp #include "human.h" #using <mscorlib.dll>
//默认的构造函数 Human::Human(void) { }
Human::~Human(void) { }
写一个主文件测试。 但调试出错,错误信息文件为: /*---------------------------------------------------------------------------- //Human:error file ------ 已启动生成:项目:TestInit, 配置:Debug Win32 ------
正在编译... Human.cpp Human.cpp(5) : error C2758: “Human::name” : 必须在构造函数基/成员初始值设定项列表中初始化 e:\NET\Small_code\TestInit\Human.h(13) : 参见“Human::name”的声明 Human.cpp(5) : error C2758: “Human::ID” : 必须在构造函数基/成员初始值设定项列表中初始化 e:\NET\Small_code\TestInit\Human.h(14) : 参见“Human::ID”的声明 fmain.cpp Date.cpp 正在生成代码...
生成日志保存在“file://e:\NET\Small_code\TestInit\Debug\BuildLog.htm”中 TestInit - 2 错误,0 警告
---------------------- 完成 ---------------------
生成:0 已成功, 1 已失败, 0 已跳过 -------------------------------------------------------------------------------- */ 因为这里涉及的是仅仅的c++语法,我就不多费口舌了,如何改正,希望您能动手试试,一定要动手,不要想当然哦~~~ 当然,如果您是爱问题的人,我想您可以这样深究一下:设计c++语言时,为什么诸如int类型成员变量能提供默认初始化,而它们却不能;从编译角度,刻意给它们提供如int类型般的初始化会有什么困难和问题?
下面详细谈什么是资源浅拷贝问题。沿袭c的习惯,c++对系统自分配的资源进行统一管理,但是,用户申请的资源,则有用户来释放。 比如:
userType *p=new userType(/*---*/); //... delete p; //delete释放一般是不可忘的
单独的变量或许对您来说是不成问题的。但在类中,这些情况就变的相当复杂。处理不好,您的系统要么就是因为内存泄露而运行不下去,而要么就是异常频频发生。 我们先来看一些c++的默认操作: //... class OneClass { int _value; public: OneClass(int _val=0):_value(_val) {} ~OneClass() {}
//... };
//you may use in this way: OneClass oneObj(7); OneClass anotherObj; anotherObj=oneObj;//(1) //... //int Compare(OneClass one,OneClass two); int k=Compare(oneObj,anotherObj);//(2) //...
在本程序的场景下,上面的代码是可以完好工作的,但您清楚(1)与(2)系统都作了什么了吗?您是否知道,如果您的初始化工作做的不好的话,即使,就沿用上面的初始化习惯,您的程序很容易就崩溃了呢。 答案是,(1)语句执行时,默认的,系统试图把oneObj的资源全部copy给anotherObj,但用户申请的资源(new来的:),却传入的是地址;(2)语句的默认形参传递遵循同样的规则。 当然这与java与c#是不同的,因为java与c#的对象是引用类型。而c++,除非您强行定义为引用类型的,它就不是。
我们来看下面的例子,第一遍我建议您只看程序,不要往下看,看您能否发现什么问题。
//human.h #pragma once
#define NULL 0
class Name { char *name; public: Name(char *_name=NULL):name(_name) {} ~Name() { }
char *getName(){ return name;}
}; class Human { Name *name;// int ID; //唯一化标志 public: Human(int id=0,char *_name=NULL); ~Human(void);
int getID()const { return ID;} Name *getName() { return name;} };
//human.cpp #include "human.h" #using <mscorlib.dll>
Human::Human(int id,char *_name):ID(id) { name=new Name(_name);//初始化:指针 }
Human::~Human(void) { delete name;//析构时释放资源 }
//fmain.cpp #include <iostream> #include "human.h"
void main() { //测试程序 try{ Human lily(11100120,"lily"); Human lucy=lily; } catch(...) {//如果有any异常 std::cout<<"\n Unknown Exception...\n"; } }
//请回头看程序,您觉得一切都好吗?
事实上,调试过程中,等三个异常忽略后,就会得到下面的结果: /* After three exceptions occured you get :
Unknown Exception... Press any key to continue
*/ 为什么? 看这几行代码: Human lily(11100120,"lily"); Human lucy=lily; 虽然一开始,我就给了小例子,形式一样,那个没问题。何以这个就不行了呢?因为类的定义不同。 由c++工作的机理,这行 Human lucy=lily; 是把lily的资源拷贝给lucy(lucy是不调用构造函数的),可是,因为其中的name是用户申请的资源,并不能把它也拷贝过去,而是直接传了地址。这样,您知道吗,lucy.name和lily.name的地址是一样的。 于是,当一个的析构函数调用后,name所指向的资源已被释放掉了的。而另外一个类的析构函数又去释放,问题来了---程序崩溃了! 这就是浅拷贝问题---“浅”的不完全的拷贝:)。 找到原因,我们就办法。解决的办法是,这份工作自己来做! 写一个拷贝赋值操作(public):
形式为: className &operator=(className &obj){ /*...*/} 您看下面的解决办法和结果: /*
If you add ...in class Human(public):
Human &operator=(Human &human){ if(this!=&human){ ID=human.getID(); name=new Name(human.getName()->getName()); return *this; } } OK,and you get:
Press any key to continue
That is what we want! */
下面的例子,是拷贝函数的问题,在上面的基础上,我改动了一下程序的结构, 定义了一个名空间。 具体的问题分析我留给下次,您就有机会细细的看了。您是否一切都清楚了? 您可以解决这个问题吗?
//human.h #pragma once #using <mscorlib.dll> namespace Humanbeing { #define NULL 0
class Name { char *name; public: Name(char *_name=NULL):name(_name) {} ~Name() { }
char *getName(){ return name;}
}; class Human { Name *name; // int ID; //唯一的标志 public: Human(int id=0,char *_name=NULL):ID(id) { name=new Name(_name);//申请资源 } ~Human(void) { delete name;//释放资源 }
//拷贝赋值操作 Human &operator=(Human &human){ if(this!=&human){ ID=human.getID(); name=new Name(human.getName()->getName()); return *this; } } int getID()const { return ID;} Name *getName() { return name;} };
//名空间里的函数 bool IsSameMan(Human one,Human another) { if(one.getID()==another.getID()) return true; else return false; }
}
//测试文件 #include <iostream> #include "human.h" void main() { using namespace Humanbeing; try{ Human lily(11100120,"lily"); Human lucy=lily;
if(IsSameMan(lucy,lily)) { std::cout<<"\n They are the same one.\n"; }else std::cout<<"\n No,they're not the same one.\n";
} catch(...) { std::cout<<"\n Unknown Exception...\n"; } }
调试结果呢,是连续六个异常后,出现: After six exceptions occured you get :
They are the same one.
Unknown Exception... Press any key to continue
为什么(上次异常是三个,这次是六个,可以解释吗)?怎么办?

|