dynamic_cast 类指针动态转换的时候有警告信息怎么回事?

fire222 2005-11-30 03:26:38
父类 base
子类 son

base *p=new son;
son *q=dynamic_cast <son *> (p);
还有就是,传递指针的时候指针指的是子类,传进了函数之后我用dynamic_cast在转回子类类型的
指针就报错,而且是编译的时候不报错,运行的时候报!!

我郁闷呀!使用dynamic_cast的时候我该注意什么呀,我觉得自己的用法和MSND上的一样呀!
...全文
139 4 打赏 收藏 转发到动态 举报
写回复
用AI写文章
4 条回复
切换为时间正序
请发表友善的回复…
发表回复
freemme 2005-12-04
  • 打赏
  • 举报
回复
为什么需要转换,直接使用就可以了啊,这正是体现了c++的多态性
oyljerry 2005-12-04
  • 打赏
  • 举报
回复
打开编译器的rtti
fire222 2005-12-04
  • 打赏
  • 举报
回复
哦,说错了,传递的是基类的指针,进了函数之后用dynamic_cast在转换成子类的指针。
不知道这样的转换是不是正确,我在程序中调试的时候有警告!
菜牛 2005-11-30
  • 打赏
  • 举报
回复
传递指针的时候指针指的是子类,传进了函数之后我用dynamic_cast在转回子类类型的
指针


????
主体:(一) 一、C++概述 (一) 发展历史 1980年,Bjarne Stroustrup博士开始着手创建一种模拟语言,能够具有面向对象的程序设计特色。在当时,面向对象编程还是一个比较新的理念,Stroustrup博士并不是从头开始设计新语言,而是在C语言的基础上进行创建。这就是C++语言。 1985年,C++开始在外面慢慢流行。经过多年的发展,C++已经有了多个版本。为次,ANSI和ISO的联合委员会于1989年着手为C++制定标准。1994年2月,该委员会出版了第一份非正式草案,1998年正式推出了C++的国际标准。 (二) C和C++ C++是C的超集,也可以说C是C++的子集,因为C先出现。按常理说,C++编译器能够编译任何C程序,但是C和C++还是有一些小差别。 例如C++增加了C不具有的关键字。这些关键字能作为函数和变量的标识符在C程序中使用,尽管C++包含了所有的C,但显然没有任何C++编译器能编译这样的C程序。 C程序员可以省略函数原型,而C++不可以,一个不带参数的C函数原型必须把void写出来。而C++可以使用空参数列表。 C++中new和delete是对内存分配的运算符,取代了C中的malloc和free。 标准C++中的字符串取代了C标准C函数库头文件中的字符数组处理函数。 C++中用来做控制态输入输出的iostream库替代了标准C中的stdio函数库。 C++中的try/catch/throw异常处理机制取代了标准C中的setjmp()和longjmp()函数。 二、关键字和变量 C++相对与C增加了一些关键字,如下: typename bool dynamic_cast mutable namespace static_cast using catch explicit new virtual operator false private template volatile const protected this wchar_t const_cast public throw friend true reinterpret_cast try bitor xor_e and_eq compl or_eq not_eq bitand 在C++中还增加了bool型变量和wchar_t型变量: 布尔型变量是有两种逻辑状态的变量,它包含两个值:真和假。如果在表达式中使用了布尔型变量,那么将根据变量值的真假而赋予整型值1或0。要把一个整型变量转换成布尔型变量,如果整型值为0,则其布尔型值为假;反之如果整型值为非0,则其布尔型值为真。布儿型变量在运行时通常用做标志,比如进行逻辑测试以改变程序流程。 #include iostream.h int main() { bool flag; flag=true; if(flag) cout<类型,wchar_t也是字符型,但是是那些宽度超过8位的数据型。许多外文字符集所含的数目超过256个,char字符型无法完全囊括。wchar_t数据型一般为16位。 标准C++的iostream库中包括了可以支持宽字符的和对象。用wout替代cout即可。 #include iostream.h int main() { wchar_t wc; wc='b'; wout<类型)。 三、强制转换 有时候,根据表达式的需要,某个数据需要被当成另外的数据型来处理,这时,就需要强制编译器把变量或常数由声明时的转换成需要的型。为此,就要使用强制转换说明,格式如下: int* iptr=(int*) &table; 表达式的前缀(int*)就是传统C风格的强制转换说明(typecast),又可称为强制转换说明(cast)。强制转换说明告诉编译器把表达式转换成指定的型。有些情况下强制转换是禁用的,例如不能把一个结构转换成其他任何型。数字型和数字型、指针指针之间可以相互转换。当然,数字型和指针型也可以相互转换,但通常认为这样做是不安全而且也是没必要的。强制转换可以避免编译器的警告。 long int el=123; short i=(int) el; float m=34.56; int i=(int) m; 上面两个都是C风格的强制转换,C++还增加了一种转换方式,比较一下上面和下面这个书写方式的不同: long int el=123; short i=int (el); float m=34.56; int i=int (m); 使用强制转换的最大好处就是:禁止编译器对你故意去做的事发出警告。但是,利用强制转换说明使得编译器的型检查机制失效,这不是明智的选择。通常,是不提倡进行强制转换的。除非不可避免,如要调用malloc()函数时要用的void型指针转换成指定指针。 四、标准输入输出流 在C语言中,输入输出是使用语句scanf()和printf()来实现的,而C++中是使用来实现的。 #include iostream.h main() //C++中main()函数默认为int型,而C语言中默认为void型。 { int a; cout<>a; /*输入一个数值*/ cout<类来实现的,cin和cout是这些的实例,他们是在C++语言的外部实现。 在C++语言中,有了一种新的注释方法,就是‘//’,在该行//后的所有说明都被编译器认为是注释,这种注释不能换行。C++中仍然保留了传统C语言的注释风格/*……*/。 C++也可采用格式化输出的方法: #include iostream.h int main() { int a; cout<>a; cout<指针调用多个函数的场合,其中有些函数不需要函数指针声明中的所有参数。看下面的例子: int fun(int x,int y) { return x*2; } 尽管这样的用法是正确的,但大多数C和C++的编译器都会给出一个警告,说参数y在程序中没有被用到。为了避免这样的警告,C++允许声明一个无名形参,以告诉编译器存在该参数,且调用者需要为其传递一个实际参数,但是函数不会用到这个参数。下面给出使用了无名参数的C++函数代码: int fun(int x,int) //注意不同点 { return x*2; } (二) 函数的默认参数 C++函数的原型中可以声明一个或多个带有默认值的参数。如果调用函数时,省略了相应的实际参数,那么编译器就会把默认值作为实际参数。可以这样来声明具有默认参数的C++函数原型: #include iostream.h void show(int=1,float=2.3,long=6); int main() { show(); show(2); show(4,5.6); show(8,12.34,50L); return 0; } void show(int first,float second,long third) { cout<类型不能完全相同,这样这些函数就可以相互区别开来。而这在C语言中是不允许的。 1.参数个数不同 #include iostream.h void a(int,int); void a(int); int main() { a(5); a(6,7); return 0; } void a(int i) { cout<a; for(int i=1;i<=10;i++) //C语言中,不允许在这里定义变量 { static int a=0; //C语言中,同一函数块,不允许有同名变量 a+=i; cout<<::a<< <size; int *array=new int[size]; for(int i=0;idt; if(dt>0 && dt<4) { const Date& bd=getdate(dt); cout<类的设计,构造函数和析构函数 是编程人员表达自定义数据型的C++机制。它和C语言中的结构似,C++支持数据抽象和面向对象的程序设计,从某种意义上说,也就是数据型的设计和实现。 一、的设计 1.的声明 class 名 { private: //私有 ... public: //公有 ... }; 2.的成员 一般在C++中,所有定义的变量和函数都是的成员。如果是变量,我们就叫它数据成员如果是函数,我们就叫它成员函数。 3.成员的可见性 private和public访问控制符决定了成员的可见性。由一个访问控制符设定的可访问状态将一直持续到下一个访问控制符出现,或者声明的结束。私有成员仅能被同一个中的成员函数访问,公有成员既可以被同一中的成员函数访问,也可以被其他已经实例化的中函数访问。当然,这也有例外的情况,这是以后要讨论的友元函数。 中默认的数据型是private,结构中的默认型是public。一般情况下,变量都作为私有成员出现,函数都作为公有成员出现。 中还有一种访问控制符protected,叫保护成员,以后再说明。 4.初始化 在声明一个的对象时,可以用圆括号()包含一个初始化表。 看下面一个例子: #include iostream.h class Box { private: int height,width,depth; //3个私有数据成员 public: Box(int,int,int); ~Box(); int volume(); //成员函数 }; Box::Box(int ht,int wd,int dp) { height=ht; width=wd; depth=dp; } Box::~Box() { //nothing } int Box::volume() { return height*width*depth; } int main() { Box thisbox(3,4,5); //声明一个对象并初始化 cout<类中没有private成员和protected成员时,也没有虚函数,并且不是从其他中派生出来的,可以用{}来初始化。(以后再讲解) 5.内联函数 内联函数和普通函数的区别是:内联函数是在编译过程中展开的。通常内联函数必须简短。定义的内联函数有两种方法:一种和C语言一样,在定义函数时使用关键字inline。如: inline int Box::volume() { return height*width*depth; } 还有一种方法就是直接在声明的内部定义函数体,而不是仅仅给出一个函数原型。我们把上面的函数简化一下: #include iostream.h class Box { private: int height,width,depth; public: Box(int ht,int wd,int dp) { height=ht; width=wd; depth=dp; } ~Box(); int volume() { return height*width*depth; } }; int main() { Box thisbox(3,4,5); //声明一个对象并初始化 cout<类中,函数名和名相同的函数称为构造函数。上面的Box()函数就是构造函数。C++允许同名函数,也就允许在一个中有多个构造函数。如果一个都没有,编译器将为该产生一个默认的构造函数,这个构造函数可能会完成一些工作,也可能什么都不做。 绝对不能指定构造函数的型,即使是void型都不可以。实际上构造函数默认为void型。 当一个的对象进入作用域时,系统会为其数据成员分配足够的内存,但是系统不一定将其初始化。和内部数据型对象一样,外部对象的数据成员总是初始化为0。局部对象不会被初始化。构造函数就是被用来进行初始化工作的。当自动型的对象离开其作用域时,所站用的内存将释放回系统。 看上面的例子,构造函数Box()函数接受三个整型擦黑素,并把他们赋值给立方体对象的数据成员。 如果构造函数没有参数,那么声明对象时也不需要括号。 1.使用默认参数的构造函数 当在声明对象时,如果没有指定参数,则使用默认参数来初始化对象。 #include iostream.h class Box { private: int height,width,depth; public: Box(int ht=2,int wd=3,int dp=4) { height=ht; width=wd; depth=dp; } ~Box(); int volume() { return height*width*depth; } }; int main() { Box thisbox(3,4,5); //初始化 Box defaulbox; //使用默认参数 cout<类中可以有多个构造函数。这些构造函数必须具有不同的参数表。在一个中需要接受不同初始化值时,就需要编写多个构造函数,但有时候只需要一个不带初始值的空的Box对象。 #include iostream.h class Box { private: int height,width,depth; public: Box() { //nothing } Box(int ht=2,int wd=3,int dp=4) { height=ht; width=wd; depth=dp; } ~Box(); int volume() { return height*width*depth; } }; int main() { Box thisbox(3,4,5); //初始化 Box otherbox; otherbox=thisbox; cout<类的对象离开作用域时,析构函数将被调用(系统自动调用)。析构函数的名字和名一样,不过要在前面加上 ~ 。对一个来说,只能允许一个析构函数,析构函数不能有参数,并且也没有返回值。析构函数的作用是完成一个清理工作,如释放从堆中分配的内存。 我们也可以只给出析构函数的形式,而不给出起具体函数体,其效果是一样的,如上面的例子。但在有些情况下,析构函数又是必需的。如在中从堆中分配了内存,则必须在析构函数中释放 主体:(三)转换 C++的内部数据型遵循隐式转换规则。假设某个表达市中使用了一个短整型变量,而编译器根据上下文认为这儿需要是的长整型,则编译器就会根据转换规则自动把它转换成长整型,这种隐式转换出现在赋值、参数传递、返回值、初始化和表达式中。我们也可以为提供相应的转换规则。 对一个建立隐式转换规则需要构造一个转换函数,该函数作为的成员,可以把该的对象和其他数据型的对象进行相互转换。声明了转换函数,就告诉了编译器,当根据句法判定需要转换时,就调用函数。 有两种转换函数。一种是转换构造函数;另一种是成员转换函数。需要采用哪种转换函数取决于转换的方向。 一、转换构造函数 当一个构造函数仅有一个参数,且该参数是不同于该的一个数据型,这样的构造函数就叫转换构造函数。转换构造函数把别的数据型的对象转换为该的一个对象。和其他构造函数一样,如果声明的对象的初始化表同转换构造函数的参数表相匹配,该函数就会被调用。当在需要使用该的地方使用了别的数据型,便宜器就会调用转换构造函数进行转换。 #include iostream.h #include time.h #include stdio.h class Date { int mo, da, yr; public: Date(time_t); void display(); }; void Date::display() { char year[5]; if(yr<10) sprintf(year,0%d,yr); else sprintf(year,%d,yr); cout<tm_mon+1; yr=tim->tm_year; if(yr>=100) yr-=100; } int main() { time_t now=time(0); Date dt(now); dt.display(); return 0; } 本程序先调用time()函数来获取当前时间,并把它赋给time_t对象;然后程序通过调用Date转换构造函数来创建一个Date对象,该对象由time_t对象转换而来。time_t对象先传递给localtime()函数,然后返回一个指向tm结构(time.h文件中声明)的指针,然后构造函数把结构中的日月年的数值拷贝给Date对象的数据成员,这就完成了从time_t对象到Date对象的转换。 二、成员转换函数 成员转换函数把该的对象转换为其他数据型的对象。在成员转换函数的声明中要用到关键字operator。这样声明一个成员转换函数: operator aaa(); 在这个例子中,aaa就是要转换成的数据型的说明符。这里的型说明符可以是任何合法的C++型,包括其他的。如下来定义成员转换函数; Classname::operator aaa() 名标识符是声明了该函数的型说明符。上面定义的Date并不能把该的对象转换回time_t型变量,但可以把它转换成一个长整型值,计算从2000年1月1日到现在的天数。 #include iostream.h class Date { int mo,da,yr; public: Date(int m,int d,int y) {mo=m; da=d; yr=y;} operator int(); //声明 }; Date::operator int() //定义 { static int dys[]={31,28,31,30,31,30,31,31,30,31,30,31}; int days=yr-2000; days*=365; days+=(yr-2000)/4; for(int i=0;i类的转换 上面两个例子都是C++对象和内部数据对象之间的相互转换。也可以定义转换函数来实现两个对象之间的相互转换。 #include iostream.h class CustomDate { public: int da, yr; CustomDate(int d=0,int y=0) {da=d; yr=y;} void display() { cout<转换构造函数 operator CustomDate(); //成员转换函数 void display() { cout<转换函数 cd.display(); dt = cd; //调用转换构造函数 dt.display(); return 0; } 这个例子中有两个CustomDate和Date,CustomDate型日期包含年份和天数。 这个例子没有考虑闰年情况。但是在实际构造一个时,应该考虑到所有问题的可能性。 在Date里中具有两种转换函数,这样,当需要从Date型变为CustomDate型十,可以调用成员转换函数;反之可以调用转换构造函数。 不能既在Date中定义成员转换函数,又在CustomDate里定义转换构造函数。那样编译器在进行转换时就不知道该调用哪一个函数,从而出错。 四、转换函数的调用 C++里调用转换函数有三种形式:第一种是隐式转换,例如编译器需要一个Date对象,而程序提供的是CustomDate对象,编译器会自动调用合适的转换函数。另外两种都是需要在程序代码中明确给出的显式转换。C++强制转换是一种,还有一种是显式调用转换构造函数和成员转换函数。下面的程序给出了三中转换形式: #include iostream.h class CustomDate { public: int da, yr; CustomDate(int d=0,int y=0) {da=d; yr=y;} void display() { cout<转换发生的情形 上面的几个例子都是通过不能型对象之间的相互赋值来调用转换函数,还有几种调用的可能: 参数传递 初始化 返回值 表达式语句 这些情况下,都有可能调用转换函数。 下面的程序不难理解,就不分析了。 #include iostream.h class CustomDate { public: int da, yr; CustomDate() {} CustomDate(int d,int y) { da=d; yr=y;} void display() { cout<类的构造函数前面有一个explicit修饰符。如果不加上这个关键字,那么在需要把CustomDate对象转换成Tester对象时,编译器会把该函数当作转换构造函数来调用。但是有时候,并不想把这种只有一个参数的构造函数用于转换目的,而仅仅希望用它来显式地初始化对象,此时,就需要在构造函数前加explicit。如果在声明了Tester对象以后使用了下面的语句将导致一个错误: ts=jd; //error 这个错误说明,虽然Tester中有一个以Date型变量为参数的构造函数,编译器却不会把它看作是从Date到Tester的转换构造函数,因为它的声明中包含了explicit修饰符。 七、表达式内部的转换 在表达式内部,如果发现某个型和需要的不一致,就会发生错误。数字型的转换是很简单,这里就不举例了。下面的程序是把Date对象转换成长整型值。 #include iostream.h class Date { int mo, da, yr; public: Date(int m,int d,int y) { mo=m; da=d; yr=y; } operator long(); }; Date::operator long() { static int dys[]={31,28,31,30,31,30,31,31,30,31,30,31}; long days=yr; days*=365; days+=(yr-1900)/4; //从1900年1月1日开始计算 for(int i=0;i转换的对象可以转换成某个数字型,或者表达式调用了作用于某个的重载运算符时,就会发生隐式转换。运算符重载以后再学习。 主体:(四)私有数据成员和友元 一、私有数据成员的使用 1.取值和赋值成员函数 面向对象的约定就是保证所有数据成员的私有性。一般我们都是通过公有成员函数来作为公共接口来读取私有数据成员的。某些时候,我们称这样的函数为取值和赋值函数。 取值函数的返回值和传递给赋值函数的参数不必一一匹配所有数据成员的型。 #include iostream.h class Date { int mo, da, yr; public: Date(int m,int d,int y) { mo=m; da=d; yr=y; } int getyear() const { return yr; } void setyear(int y) { yr = y; } }; int main() { Date dt(4,1,89); cout<类中的数据。这样有利于软件的设计和维护。比如,改变Date内部数据的形式,但仍然用修改过的getyear()和setyear()来提供访问接口,那么使用该就不必修改他们的代码,仅需要重新编译程序即可。 2.常量成员函数 注意上面的程序中getyear()被声明为常量型,这样可以保证该成员函数不会修改调用他的对象。通过加上const修饰符,可以使访问对象数据的成员函数仅仅完成不会引起数据变动的那些操作。 如果程序声明某个Date对象为常量的话,那么该对象不得调用任何非常量型成员函数,不论这些函数是否真的试图修改对象的数据。只有把那些不会引起数据改变的函数都声明为常量型,才可以让常量对象来调用。 3.改进的成员转换函数 下面的程序改进了从Date对象到CustomDate对象的成员转换函数,用取值和赋值函数取代了使用公有数据成员的做法。(以前的程序代码在上一帖中) #include iostream.h class CustomDate { int da,yr; public: CustomDate() {} CustomDate(int d,int y) { da=d; yr=y; } void display() const {cout<类外的其他函数读取,但是有时候会允许一些特殊的函数直接读写其私有数据成员。 关键字friend可以让特定的函数或者别的的所有成员函数对私有数据成员进行读写。这既可以维护数据的私有性,有可以保证让特定的或函数能够直接访问私有数据。 1.友元 一个可以声明另一个为其友元,这个友元的所有成员函数都可以读写它的私有数据。 #include iostream.h class Date; class CustomDate { int da,yr; public: CustomDate(int d=0,int y=0) { da=d; yr=y; } void display() const {cout<类的所有成员函数有权访问CustomDate的私有成员。因为Date转换函数需要知道CustomDate的每个数据成员,所以真个Date都被声明为CustomDate的友元。 2.隐式构造函数 上面程序对CustomDate的构造函数的调用私有显示该需要如下的一个转换构造函数: CustomDate(Date& dt); 但是唯一的一个构造函数是:CustomDate(int d=0;int y=0); 这就出现了问题,编译器要从Date对象构造一个CustomDate对象,但是CustomDate中并没有定义这样的转换构造函数。不过Date中定义了一个成员转换函数,它可以把Date对象转换成CustomDate对象。于是编译器开始搜索CustomDate,看其是否有一个构造函数,能从一个已存在的CustomDate的对象创建新的CustomDate对象。这种构造函数叫拷贝构造函数。拷贝构造函数也只有一个参数,该参数是它所属的的一个对象,由于CustomDate中没有拷贝构造函数,于是编译器就会产生一个默认的拷贝构造函数,该函数简单地把已存在的对象的每个成员拷贝给新对象。现在我们已经知道,编译器可以把Date对象转换成CustomDate对象,也可以从已存在的CustomDate对象生成一个新的CustomDate对象。那么上面提出的问题,编译器就是这样做的:它首先调用转换函数,从Date对象创建一个隐藏的、临时的、匿名的CustomDate对象,然后用该临时对象作为参数调用默认拷贝构造函数,这就生成了一个新的CustomDate对象。 3.预引用 上面的例子中还有这样一句 class Date; 这个语句叫做预引用。它告诉编译器,Date将在后面定义。编译器必须知道这个信号,因为CustomDate中引用了Date,而Date里也引用了CustomDate,必须首先声明其中之一。 使用了预引用后,就可以声明未定义的的友元、指针和引用。但是不可以使用那些需要知道预引用的的定义细节的语句,如声明该的一个实例或者任何对该成员的引用。 4.显式友元预引用 也可以不使用预引用,这只要在声明友元的时候加上关键自class就行了。 #include iostream.h class CustomDate { int da,yr; public: CustomDate(int d=0,int y=0) { da=d; yr=y; } void display() const {cout<类都设为另一个的友元,只需挑出需要访问当前私有数据成员的成员函数,将它们设置为该的友元即可。这样的函数称为友元函数。 下面的程序限制了CustomDate数据成员的访问,Date中只有需要这些数据的成员函数才有权读写它们。 #include iostream.h class CustomDate; class Date { int mo,da,yr; public: Date(const CustomDate&); void display() const {cout<类的构造函数创建了一个匿名CustomDate对象,然后用该对象创建了一个Date对象。这种用法在C++中是经常出现的。 7.非成员的友元函数 有时候友元函数未必是某个的成员。这样的函数拥有对象私有数据成员的读写权,但它并不是任何的成员函数。这个特性在重载运算符时特别有用。 非成员的友元函数通常被用来做为之间的纽带。一个函数如果被两个同时声明为友元,它就可以访问这两个的私有成员。下面的程序说明了一个可以访问两个私有数据成员的友元函数是如何将在两个之间架起桥梁的。 #include iostream.h class Time; class Date { int mo,da,yr; public: Date(int m,int d,int y) { mo=m; da=d; yr=y;} friend void display(const Date&, const Time&); }; class Time { int hr,min,sec; public: Time(int h,int m,int s) { hr=h; min=m; sec=s;} friend void display(const Date&, const Time&); }; void display(const Date& dt, const Time& tm) { cout << dt.mo << '/' << dt.da << '/' << dt.yr; cout << ' '; cout << tm.hr << ':' << tm.min << ':' << tm.sec; } int main() { Date dt(2,16,97); Time tm(10,55,0); display(dt, tm); return 0; } 主体:(五)析构函数和this指针 一、析构函数 前面的一些例子都没有说明析构函数,这是因为所用到的在结束时不需要做特别的清理工作。下面的程序给出了一新的Date,其中包括一个字符串指针,用来表示月份。 #include iostream.h #include string.h class Date { int mo,da,yr; char *month; public: Date(int m=0, int d=0, int y=0); ~Date(); void display() const; }; Date::Date(int m,int d,int y) { static char *mos[] = { January,February,March,April,May,June, July,August,September,October,November,December }; mo=m; da=d; yr=y; if(m!=0) { month=new char[strlen(mos[m-1])+1]; strcpy(month, mos[m-1]); } else month = 0; } Date::~Date() { delete [] month; } void Date::display() const { if(month!=0) cout<动态分配了内存,然后从内部数组中把月份的名字拷贝给字符串指针month。 析构函数在删除month指针时,可能会出现一些问题。当然从这个程序本身来看,没什么麻烦;但是从设计一个的角度来看,当Date用于赋值时,就会出现问题。假设上面的main()修改为“ int main() { Date birthday(8,11,1979); Date today; today=birthday; birthday.display(); return 0; } 这会生成一个名为today的空的Date型变量,并且把birthday值赋给它。如果不特别通知编译器,它会简单的认为的赋值就是成员对成员的拷贝。在上面的程序中,变量birthday有一个字符型指针month,并且在构造函数里用new运算符初始化过了。当birthday离开其作用域时,析构函数会调用delete运算符来释放内存。但同时,当today离开它的作用域时,析构函数同样会对它进行释放操作,而today里的month指针是birthday里的month指针的一个拷贝。析构函数对同一指针进行了两次删除操作,这会带来不可预知的后果。 如果假设today是一个外部变量,而birthday是一个自变量。当birthday离开其作用域时,就已经把对象today里的month指针删除了。显然这也是不正确的。 再假设有两个初始化的Date变量,把其中一个的值赋值给另一个: Date birthday(8,11,1979); Date today(12,29,2003); today=birthday; 问题就更复杂了,当这两个变量离开作用域时,birthday中的month的值已经通过赋值传递给了today。而today中构造函数用new运算符给month的值却因为赋值被覆盖了。这样,birthday中的month被删除了两次,而today中month却没有被删除掉。 二、重载赋值运算符 为了解决上面的问题,我们应该写一个特殊的赋值运算符函数来处理这问题。当需要为同一个的两个对象相互赋值时,就可以重载运算符函数。这个方法可以解决的赋值和指针的释放。 下面的程序中,中的赋值函数用new运算符从堆中分配了一个不同的指针,该指针获取赋值对象中相应的值,然后拷贝给接受赋值的对象。 在中重载赋值运算符的格式如下: void operator = (const Date&) 后面我们回加以改进。目前,重载的运算符函数的返回型为void。它是总的成员函数,在本程序红,是Date的成员函数。它的函数名始终是operator =,参数也始终是同一个的对象的引用。参数表示的是源对象,即赋值数据的提供者。重载函数的运算符作为目标对象的成员函数来使用。 #include iostream.h #include string.h class Date { int mo,da,yr; char *month; public: Date(int m=0, int d=0, int y=0); ~Date(); void operator=(const Date&); void display() const; }; Date::Date(int m, int d, int y) { static char *mos[] = { January,February,March,April,May,June, July,August,September,October,November,December }; mo = m; da = d; yr = y; if (m != 0) { month = new char[strlen(mos[m-1])+1]; strcpy(month, mos[m-1]); } else month = 0; } Date::~Date() { delete [] month; } void Date::display() const { if (month!=0) cout<类加入了一个重载运算符函数,这个程序和上面的一个程序是相同的。赋值运算符函数首先取得所需的数据,然后用delete把原来的month指针所占用的内存返还给堆。接着,如果源对象的month指针已经初始化过,就用new运算符为对象重新分配内存,并把源对象的month字符串拷贝给接受方。 重载的Date赋值运算符函数的第一个语句比较了源对象的地址和this指针。这个操作取保对象不会自己给自己赋值。 三、this指针 this指针是一个特殊的指针,当的某个非静态的成员函数在执行时,就会存在this指针。它指向的一个对象,且这个对象的某个成员函数正在被调用。 this指针的名字始终是this,而且总是作为隐含参数传递给每一个被声明的成员函数,例如: void Date::myFunc(Date* this); 实际编程时函数的声明不需要包含这个参数。 当程序中调用某个对象的成员函数时,编译器会把该对象的地址加入到参数列表中,感觉上就好象函数采用了上面所示的声明,并且是用如下方式来调用的: dt.myFunc(& dt); 静态成员函数不存在this指针。 当调用某个对象的成员函数时,编译器把对象的地址传递给this指针,然后再调用该函数。因此,成员函数你对任何成员的调用实际上都隐式地使用了this指针。 1.以this指针作为返回值 使用this指针可以允许成员函数返回调用对象给调用者。前面的程序中重载赋值运算符没有返回值,因此不能用如下的形式对字符串进行赋值: a=b=c; 为了使重载的赋值机制也能这样方便,必须让赋值函数返回赋值的结果,在这里就是目标对象。当赋值函数执行时,其返回值也恰好是this指针所指的内容。 下面的程序对前面那个程序进行了修改,让重载赋值运算符返回了一个Date对象的引用。 #include iostream.h #include string.h class Date { int mo,da,yr; char *month; public: Date(int m=0, int d=0, int y=0); ~Date(); void operator=(const Date&); void display() const; }; Date::Date(int m, int d, int y) { static char *mos[] = { January,February,March,April,May,June, July,August,September,October,November,December }; mo = m; da = d; yr = y; if (m != 0) { month = new char[strlen(mos[m-1])+1]; strcpy(month, mos[m-1]); } else month = 0; } Date::~Date() { delete [] month; } void Date::display() const { if (month!=0) cout<指针 在应用程序中,如果数据结构里有指向自身型的成员,那么使用this指针会提供更多的方便。下面的程序中建立了一个ListEntry的链表。 #include iostream.h #include string.h class ListEntry { char* listvalue; ListEntry* preventry; public: ListEntry(char*); ~ListEntry() { delete [] listvalue; } ListEntry* PrevEntry() const { return preventry; }; void display() const { cout< name; if (strncmp(name, end, 3) == 0) break; ListEntry* list = new ListEntry(name); if (prev != 0) prev->AddEntry(*list); prev = list; } while (prev != 0) { prev->display(); ListEntry* hold = prev; prev = prev->PrevEntry(); delete hold; } return 0; } 程序运行时,会提示输入一串姓名,当输入完毕后,键入end,然后程序会逆序显示刚才输入的所有姓名。 程序中ListEntry含有一个字符串和一个指向前一个表项的指针。构造函数从对中获取内存分配给字符串,并把字符串的内容拷贝到内存,然后置链接指针为NULL。析构函数将释放字符串所占用的内存。 成员函数PrevEntry()返回指向链表前一个表项的指针。另一个成员函数显示当前的表项内容。 成员函数AddEntry(),它把this指针拷贝给参数的preventry指针,即把当前表项的地址赋值给下一个表项的链接指针,从而构造了一个链表。它并没有改变调用它的listEntry对象的内容,只是把该对象的地址赋给函数的参数所引用的那个ListEntry对象的preventry指针,尽管该函数不会修改对象的数据,但它并不是常量型。这是因为,它拷贝对象的地址this指针的内容给一个非长常量对象,而编译器回认为这个非常量对象就有可能通过拷贝得到的地址去修改当前对象的数据,因此AddEntry()函数在声明时不需要用const。 主体:(六)对象数组和静态成员 一、对象数组 的对象和C++其他数据型一样,也可以为其建立数组,数组的表示方法和结构一样。 #include iostream.h class Date { int mo,da,yr; public: Date(int m=0,int d=0, int y=0) { mo=m; da=d; yr=y;} void display() const { cout<类对象数组和默认构造函数 在前面已经说过,不带参数或者所有参数都有默认值的构造函数叫做默认构造函数。如果中没有构造函数,编译器会自动提供一个什么都不做的公共默认构造函数 。如果当中至少有一个构造函数,编译器就不会提供默认构造函数。 如果当中不含默认构造函数,则无法实例化其对象数组。因为实例花对象数组的格式不允许用初始化值来匹配某个构造函数的参数表。 上面的程序中,main()函数声明了一个长度为2的Date对象数组,还有一个包含初始化值的单个Date对象。接着把这个初始化的Date对象赋值给数组中第一个对象,然后显示两个数组元素中包含的日期。从输出中可以看到,第一个日期是有效日期,而第二个显示的都是0。 当声明了某个的对象数组时,编译器会为每个元素都调用默认构造函数。 下面的程序去掉了构造函数的默认参数值,并且增加了一个默认构造函数。 #include class Date { int mo, da, yr; public: Date(); Date(int m,int d,int y) { mo=m; da=d; yr=y;} void display() const { cout <类对象数组和析构函数 当对象离开作用域时,编译器会为每个对象数组元素调用析构函数。 #include iostream.h class Date { int mo,da,yr; public: Date(int m=0,int d=0,int y=0) { mo=m; da=d; yr=y;} ~Date() {cout<类的成员声明为静态的。静态成员只能存在唯一的实例。所有的成员函数都可以访问这个静态成员。即使没有声明的任何实例,静态成员也已经是存在的。不过当中声明静态成员时并不能自动定义这个变量,必须在定义之外来定义该成员。 1.静态数据成员 静态数据成员相当于一个全局变量,的所有实例都可以使用它。成员函数能访问并且修改这个值。如果这个静态成员是公有的,那么的作用域之内的所有代码(不论是在的内部还是外部)都可以访问这个成员。下面的程序通过静态数据成员来记录链表首项和末项的地址。 #include iostream.h #include string.h class ListEntry { public: static ListEntry* firstentry; private: static ListEntry* lastentry; char* listvalue; ListEntry* nextentry; public: ListEntry(char*); ~ListEntry() { delete [] listvalue;} ListEntry* NextEntry() const { return nextentry; }; void display() const { cout<name

16,472

社区成员

发帖
与我相关
我的任务
社区描述
VC/MFC相关问题讨论
社区管理员
  • 基础类社区
  • Web++
  • encoderlee
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告

        VC/MFC社区版块或许是CSDN最“古老”的版块了,记忆之中,与CSDN的年龄几乎差不多。随着时间的推移,MFC技术渐渐的偏离了开发主流,若干年之后的今天,当我们面对着微软的这个经典之笔,内心充满着敬意,那些曾经的记忆,可以说代表着二十年前曾经的辉煌……
        向经典致敬,或许是老一代程序员内心里面难以释怀的感受。互联网大行其道的今天,我们期待着MFC技术能够恢复其曾经的辉煌,或许这个期待会永远成为一种“梦想”,或许一切皆有可能……
        我们希望这个版块可以很好的适配Web时代,期待更好的互联网技术能够使得MFC技术框架得以重现活力,……

试试用AI创作助手写篇文章吧