山寨:不是最好的,是最适合我们的!欢迎体验山寨 中文版MSDN

Blog @ Blog

当华美的叶片落尽,生命的脉络才历历可见。 -- 聂鲁达

常用链接

统计

积分与排名

BBS

Blog

Web

最新评论

[转]谈谈c++的初始化工作

关于c中的初始化相关部分,如指针,如全局变量与局部变量默认初始化的区别,如静态变量的默认初始化,就跳过。我们从类开始。
    初始化是非常重要的工作,因为你的类(确切说是对象,程序)的执行过程就是一系列状态变换,而初态不正确,就不可能到达正确解了。
    面向对象的c++中的初始化工作,是由构造函数来完成的,在其他场景可能称为构造器。这是大家都明白的。但是,展开来,或许您还未必清楚,如,如何设计好的默认初始化,哪些成员变量只有唯一的初始化形式,组合与继承的初始化,资源浅拷贝问题,无名对象的问题,特殊需要的初始化(实例对象须唯一化)等等。我将在vc7.0上调试程序,每次调试一个,谈一个问题,试图给您解释清楚。愿于您有所帮助。

    这次就说说好的初始化过程与静态成员的初始化。
    不管程序员如何,面向对象的c++中初始化工作是必须的!!你写了一个类,没有写构造函数,但是,系统会“暗暗的”给你一个系统默认的构造函数,在实例化对象的时候它就会工作---要知道,一旦你自己定义了构造函数,系统就不会再提供默认构造函数。
    问题是,我们应该定义自己的构造函数。否则,系统多半是无法达到正确的初始状态的!
    定义好的构造函数,应该是给出多版本的构造函数,作好安全检查工作。我们下面给出一个例子,由c++缔造者的例子改动迩来。
  
     需要一个类,日期Date,它有成员变量day,month,year,执行一些相关操作。如何进行初始化工作?我们或许会见到下面的代码:

 

    //
    class Date {
        int d
,m,y;
    public:
        Date(int dd
=0,int mm=0,int yy=0)
        {
           d
=dd;
           m=mm;
           y=yy;
        }

     //
    }
;
    //

 

    这样的程序没有语法错误,可以工作,但不是正确工作。下面这个语句会怎么样呢?
     
    Date oneday(-2,10,2002);
    作简单的检查,如下面的代码部分。也是于事无补的。如对下面的语句仍然是无能为力的:

    

Date oneday(29,2,1981);

    //
    class Date {
        int d
,m,y;
    public:
        Date(int dd
=0,int mm=0,int yy=0)
        {
          if(dd>
=0&&yy>=0&&mm>=0&&m<=31){//???
              d
=dd;
              m=mm;
              y=yy;
           }
           //else ???
        }

     //
    }
;
    //

    更何况,我们可能会需要用string来初始化,用char *指针来初始化:
    string s="29/2/1981";
    char *p="29/2/1981";

    应该怎么办呢?我想你有必要好好审视你的初始化工作了!!!

     我们来看一个设计实例:

Date oneday(29,2,1981);

    //
    class Date {
        int d
,m,y;
    public:
        Date(int dd
=0,int mm=0,int yy=0)
        {
          if(dd>
=0&&yy>=0&&mm>=0&&m<=31){//???
              d
=dd;
              m=mm;
              y=yy;
           }
           //else ???
        }

     //
    }
;
    //

     我们来看看实现部分:

 

//date.cpp
#include 
"date.h"
#using <mscorlib.dll>

//静态成员的初始化
Date Date::default_date(
4,feb,1981);

Date::~Date(void)
{
}
//详尽的初始化工作的例子
Date::Date(int dd
, Month mm, int yy)
{
       //(
1
 if(dd
==0) dd=default_date.day();//test d=default_date.day()
 if(mm==0) mm=default_date.month();//test m=default_date.month()
 if(yy==0) yy=default_date.year();//test y=default_date.year()
         int max;

 switch(mm)
 {
 case feb:
  max
=28+leapyear(yy);
  break;
 case apr:case jun:case sep:case nov:
  max
=30;
  break;
 case jan:case mar:case may:case jul:case aug:case oct:case dec:
  max
=31;
  break;
 default:
  throw Bad_date()
;
 }
 if(dd<
1||max<dd||yy<0) throw Bad_date();

 y
=yy;
 m=mm;
 d=dd;
}

void Date::set_default(int d
, Month m, int y)
{
 Date::default_date
=Date(d,m,y);
}

int Date::day(void) const
{
 return d
;
}

Month Date::month(void) const
{
 return m
;
}

int Date::year(void) const
{
 return y
;
}
//测试函数
void Date::Test(void)
{
 std::cout<<
"\n This is a test using class Date. \n"
  <<
" The date is(day/month/year) :"<<d<<"/"<<m<<"/"<<y
          <<std::endl
;
 std::cout<<"\n Thank you! \n\n";
}

 

    这里,有几个需要注意的,就是:
    (1)构造函数的版本
         Date(int dd=0, Month mm=Month(0), int yy=0);
         Date(string s) { /* 省去内容*/}
         Date(char *p) { /*省去内容*/}
    (2)静态成员提供默认的值
         //静态成员变量
 static Date default_date;
         //及接口
         static void set_default(int d, Month m, int y);
    (3)异常管理
        //异常类(默认构造函数,因为我们只是抛出异常,甚至没有标志)
 class Bad_date{};
    (4)构造函数中较好的算法
   
    这些都是我们初始化工作交好的保证!
    用下面的文件程序测试,可得结果:

//fmain.cpp
#include 
"date.h"

void main()
{
    Date oneDay
;
 oneDay.Test();
}
/*结果:

 This is a test using class Date.
 The date is(day/month/year) :
4/2/1981

 Thank you!

Press any key to continue
*/

 

下面回到实现程序文件date.cpp,看(1)部分的代码。我后面注释了三行的代码。如果我用注释的代码换掉程序中的代码,您觉得会出现什么结果?
我们首先来看上次遗留的问题。
把(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

     为什么(上次异常是三个,这次是六个,可以解释吗)?怎么办?

posted on 2007-08-08 16:40 isabc 阅读(649) 评论(0)  编辑 收藏 引用 所属分类: C++基础


只有注册用户登录后才能发表评论。
网站导航: 博客园   IT新闻   BlogJava   知识库   博问   管理


广告信息(免费广告联系)

中文版MSDN:
欢迎体验