![面向对象的程序设计c++第三章类与对象_第1页](http://file4.renrendoc.com/view12/M02/15/00/wKhkGWW_u86AUynOAADsmKg28kM034.jpg)
![面向对象的程序设计c++第三章类与对象_第2页](http://file4.renrendoc.com/view12/M02/15/00/wKhkGWW_u86AUynOAADsmKg28kM0342.jpg)
![面向对象的程序设计c++第三章类与对象_第3页](http://file4.renrendoc.com/view12/M02/15/00/wKhkGWW_u86AUynOAADsmKg28kM0343.jpg)
![面向对象的程序设计c++第三章类与对象_第4页](http://file4.renrendoc.com/view12/M02/15/00/wKhkGWW_u86AUynOAADsmKg28kM0344.jpg)
![面向对象的程序设计c++第三章类与对象_第5页](http://file4.renrendoc.com/view12/M02/15/00/wKhkGWW_u86AUynOAADsmKg28kM0345.jpg)
版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领
文档简介
面向对象程序设计
第三章类和对象杨卫东左峥嵘华中科技大学自动化学院2017秋作业1版本,OpenCV库第3章类和对象
类与对象的根本概念构造函数与析构函数对象数组与对象指针向函数传递对象对象的赋值和复制静态成员友元类的组合常类型1、类的定义类与对象的根本概念类的定义格式:class类名类头{ private: 数据成员或成员函数的说明 protected:类体 数据成员或成员函数的说明 public: 数据成员或成员函数的说明};<各成员函数的实现(定义局部)>类的定义类的定义格式分为说明局部和实现局部说明局部是用来描述该类中的成员,包括数据成员的说明和成员函数的说明,成员函数是用来对数据成员进行操作的,又称为“方法〞。实现局部是用来定义〔编写〕各种成员函数的,以描述这些成员函数是如何实现对数据成员的操作。总之,说明局部将告诉编程者这个类是“做什么〞的,而实现局部是告知“怎么做〞的。类的说明局部由类头和类体两局部组成类头由关键字class和类名组成,用来向编译系统声明定义了一个新的类型。类名是由编程者启用的一种标识符,一般用‘C’(Class)开头的字符串作为类名,以便与对象名、函数名区别。类体是对类的组织形式进行具体的描述。由访问限制符〔private、protected、public〕和数据成员、成员函数组成,整个类体用一对大括号包围起来,完整地表达对类的描述。class允许隐藏内部成员。它是依靠类定义中的三个访问限制符public、private、protected来确定隐藏的程度,它们将类体划分成三大局部:以public:开头的程序局部称为公有局部。以private:开头的程序局部称为私有局部。以protected:开头的程序局部称为保护局部。类的定义只是定义了某种类的组织形式,即定义了一个新的class类型,相当于ANSIC中的结构体定义。编译系统并不给class类型的每个数据成员分配内存空间
访问限制符2.访问限制符public、private、protected访问限制符将类体分为三个局部,每一局部都可以有数据成员和成员函数,也可以只有数据成员或成员函数,但不同的访问限制符规定了该局部所具有的访问权限。Public访问限制符指定的为公有局部是透明的,它的数据成员和成员函数是开放的,既可以由本类的成员函数访问,也可有程序的其他局部直接访问。例如允许该类的对象去直接访问它的公有局部即编写成“对象名.公有成员名〞的形式。Private访问限制符指定的为私有局部象一个黑盒子,完全是隐藏的,它只能由本类的成员函数访问,不允许程序其他局部访问,例如不允许该类的对象去直接访问它的私有局部,即不允许编写成“对象名.私有成员〞的形式。访问限制符Protected访问限制符指定的保护局部是半透明的,它可由本类成员函数或它的派生类成员函数直接访问,但也不允许程序其他局部直接访问它。保护局部主要用于类的继承。访问限制符访问限制符使用的一般原那么通常总是将数据成员指定为私有的以实现数据隐藏,这些数据成员是用来描述该类对象的属性,编程者无法直接访问它们而隐藏起来。将成员函数指定为公有的,作为该类对象访问私有数据成员的一个接口界面,即对象访问私有数据成员的一条消息通路提供给外界使用。因此私有数据只能通过公有成员函数访问,从而隐藏了处理这些数据的实现细节,使得类对数据的描述和类提供给外部世界来处理数据的界面这两件事情互相独立,这就给出了面向对象的重要特性,使得一个类的用户唯一需要做的事情就是访问类的界面。Date类的描述数据成员定义格式类的定义与结构体的定义一样,仅仅定义了类的组织形式,给出了一个新的class类型,编译系统并不给类的每个数据成员分配具体的内存空间。数据成员既可以放在公有局部称为公有数据成员,又可以放在私有或保护局部称为私有或保护数据成员。定义格式都是:<类型>数据成员名;说明数据成员只有<数据类型>说明,而无<存储类>说明,所以将类的数据成员说明为auto、register和extern型都是无意义的在类体中不允许对所定义的数据成员进行初始化classCounter{public:...private: unsignedvalue=0;//error};数据成员的类型可以是根本数据类型(char、int、...、double等)和复杂数据类型(数组、指针、引用、...、结构变量等),或已经定义了的类对象。当另一个类的对象作为这个类的成员时,称为“对象成员〞,这另一个类必须预先定义好。classN{public:...};//不能采用先声明,后定义的形式。〞classN;〞classM{public:...private:Nn;//n是N类的对象};Tpoint.cpp成员函数的定义成员函数(MemberFunction)定义格式:说明:所有成员函数都必须在类体内用函数原型加以声明;成员函数的定义(方法的实现局部)既可在类体外定义,也可在类体内定义。在类体外定义成员函数时,必须用类名加作用域运算符’::’来指明所定义的成员函数属于哪个类;在类体内定义的成员函数,其函数头可不写[<类名>::]。在类体内定义成员函数时,编译系统自动地将成员函数当作内联函数处理,其函数头前不必写关键字〞inline〞。例如Counter类中的Increment()、Decrement()和ReadValue()都是内联函数。假设是类体外定义的成员函数,必须用关键字〞inline〞指明它为内联函数。在定义了类的对象后,才可以用如下格式调用:对象名.成员函数名(实参表);例如:Counterc1,c2;c1.Increment();c2.Decrement();成员函数与普通函数一样,可设置参数的默认值(缺省值Default)ch3-cpp-complex.dsw对象的定义对象的定义格式:在定义好称之为“类名〞的一个类以后,就可以用如下格式定义它的对象:<存储类><类名><对象名表>;<对象名表>中可以有一个或多个对象名,假设为多个对象名时用逗号分隔开。一般对象名:Dated1,d2;对象数组名:Dated[31];对象指针名:Date*ptr;对象引用名:Dated1,&d2=d1;对象的定义指向对象的指针对象指针的定义格式:<存储类><类名>指针名;例如:Counterc1,c2,*pc;这里所说的<存储类>是指针变量本身的存储类型,与ANSIC标准一样,有extern型、static型、auto型等。而auto的说明经常缺省,extern在定义时不写,声明时必须写。例中只定义了一个对象指针pc,但pc并没有定向,还必须通过初始化或赋值操作将已存在的对象地址赋给它。即pc=&c1;另外,在C++中对象指针可作为另一个类的成员,此时的对象指针是没有<存储类>说明的。只要一个类已经声明〔可以没有定义〕,那么指向该类的对象指针或该类对象的引用都可作为另一个类的成员。构造函数与析构函数1〕构造函数2〕析构函数3〕重载构造函数和拷贝构造函数构造函数与析构函数对象的初始化与构造函数“类〞是一种抽象的数据类型。要使用这个类还必须用“类〞说明它的对象,每个对象中的数据成员还必须赋初值,即对象的初始化对象的初始化由构造函数完成变量,存储空间…MyImg.cpp构造函数与析构函数构造函数是一种特殊的成员函数,以类名作为函数名不能指定返回类型和显式的返回值,连void都不能写,即无返回类型可以有任意类型的参数对象定义时被编译系统自动调用:假设某类定义了构造函数,那么每当该类的对象创立时,编译系统为它的对象分配内存空间,并自动调用构造函数初始化所创立的对象,从而确保每个对象自动地初始化构造函数为公有,用户不能显示调用。构造函数只完成新对象的初始化工作,不执行对象的主要任务。即在类创立新对象时,为对象分配内存空间,给数据成员赋初值可以类内定义,亦可类外定义complex.dsw构造函数与析构函数构造函数每个类可定义多个构造函数,实现构造函数的重载,以适应创立对象时,对象的参数具有不同个数的情况。实际应用中,通常需要给每个类定义构造函数,如未定义,编译系统自动生成一个默认的构造函数,如:complex::complex(){}〔tpoint1.cpp)用默认构造函数创立对象时,假设创立的是全局对象或静态对象,那么对象的所有数据成员都初始化为零(数)或空(字符串)采用构造函数对数据成员赋初值的形式形式1:直接创立对象类名对象名[(实参表)];如:complexcp(1.0,2.0);形式2:通过指针和new来创立对象时类名*指针变量名=new类名[(实参表)];如:complex*pt=newcomplex(1.0,2.0);构造函数的重载构造函数之间以它们所带参数的个数和类型的不同而区分说明无参构造函数创立对象时,应该用语句〞Datadate1;〞,不能用〞Datadata1();〞只要定义了一个构造函数,系统将不再提供默认的构造函数;类中可包含多个构造函数,但对于对象而言,建立对象时只执行其中的一个。Complex.cpp带有默认参数的构造函数假设构造函数在类的声明外定义,那么默认参数需在类内声明构造函数原型时指定;假设构造函数的全部参数都指定了默认值,那么在定义对象时可以指定1个或多个实参,也可以不给出实参,这时的构造函数也属于默认构造函数,不能再声明无参数的默认构造函数;假设构造函数的全部参数都指定了默认值,那么不能再定义重载构造函数;???Complex(doubler=0.0,doublei=0.0);Complex(doubler);用成员初始化列表对数据成员初始化不在函数体内用赋值语句对数据成员初始化,而在函数首部实现的。带有成员初始化列表的构造函数的一般形式:类名::构造函数名([参数表)][:(成员初始化列表)]成员初始化列表的一般形式:数据成员名1(初始值1),数据成员名2(初始值2),…构造函数可使用构造初始化表对数据成员进行初始化classA{ inti; charc;public://下面为构造函数
A(inti1,charc1){i=i1;imag=i;} A(inti1,charc1):i(i1),c(c1){} };为什么使用初始化列表使用初始化列表主要是基于性能问题,对于内置类型,如int,float等,使用初始化类表和在构造函数体内初始化差异不是很大,但是对于类类型来说,最好使用初始化列表某些情况下必须使用初始化列表常量成员,因为常量只能初始化不能赋值,所以必须放在初始化列表里面引用类型成员,引用必须在定义的时候初始化,并且不能重新赋值,所以也要写在初始化列表里面成员变量的初始化顺序成员是按照他们在类中出现的顺序进行初始化的,而不是按照他们在初始化列表出现的顺序初始化的析构函数析构函数的作用:执行清理任务,释放空间析构函数也是一种特殊的成员函数和构造函数名相同,但其前有波浪号~无参数,无返回值,不能重载,因此类中只有一个析构函数当撤销对象,编译系统会自动调用析构函数,程序员不能显示调用类只有一个,假设未显式定义,编译系统自动生成默认的析构函数 classcomplex{ doublereal; doubleimag;public: complex(doubler=0.0,doublei=0.0) {real=r;imag=i;} //定义一个析构函数 ~complex(){cout<<“析构\n〞;}};析构函数它是构造函数的配对物,与构造函数一样是与类同名的成员函数,并在函数名前加上一个’~’以便与构造函数相区别。
count.cpp中的析构函数~Counter()。析构函数没有参数和返回值,一个类只能有一个析构函数,不能被重载,实现与构造函数相反的操作;
如果编程者没有给类定义析构函数,那么编译系统将生成一个什么也不做的缺省析构函数。例如,Complex类的缺省析构函数为:
Complex::~Complex(){}//空函数编译系统自动调用构造函数的次序和调用析构函数的次序是相反的;析构函数调用对象生存期结束时:假设对象在一个函数体内定义,那么当这个函数结束时,该对象将释放,析构函数自动调用;当对象是使用new运算符动态创立时,在使用delete运算符释放它时才调用析构函数。Debug…list.cpp对象的使用访问类对象成员的方法通过对象名和对象选择符访问对象中的成员一般形式:对象名.数据成员名或对象名.成员函数名[(实参表)]通过指向对象的指针访问对象中的成员对象名->数据成员名或对象名->成员函数名[(实参表)]通过对象的引用访问对象中的成员引用对象名.数据成员名或引用对象名.成员函数名[(实参表)]私有数据成员的访问通过公用成员函数进行访问重载构造函数与一般成员函数一样,C++运行重载构造函数,重载的方法一样
classA{ //……public: A();//重载构造函数
A(int); A(int,char); A(float,char); //…… };拷贝构造函数拷贝构造函数是一种特殊的构造函数它用于根据存在对象建立新对象,它能把存在对象的值逐域拷贝到新对象中。拷贝构造函数的特点:1〕该函数只有一个参数,其形参是同类对象的引用2〕每个类都有一个拷贝构造函数。假设未显式地定义它,编译系统也会自动生成。拷贝构造函数的定义形式:
classname(constclassname&ob){//拷贝构造函数的定义体}
其中,ob是用来初始化另一个对象的引用classpoint{ intx,y; public: point(inta,intb){x=a;y=b;} point(constpoint&p){x=p.x;y=p.y;}};调用拷贝构造函数的方法代入法类名对象名2(对象1)Point
p2(p1);赋值法对象2
=对象1;Point
p2=p1;调用拷贝构造函数的3种情况用类的一个对象去初始化该类的另一个对象Pointp2(p1);Pointp2=p1;当函数的形参是类的对象,在调用函数进行形参和实参结合时Voidfun1(Pointp){p.print();}Intmain(){Pointp1(10,20);fun1(p1);return0;}调用拷贝构造函数的3种情况当构造函数的返回值是类的对象,在函数调用完毕将返回值〔对象〕带回函数调用处时Pointfun2(){Pointp1(10,20);returnp1;}Intmain(){Pointp2;p2=fun2();return0;}对象数组与C的变量数组是相同的——即用类来声明对象的数组#include"iostream.h“classexam{ intx;public: voidset_x(intn) { x=n; } intget_x() { returnx; }};对象数组voidmain(){ examob[4];//对象数组的定义和使用
for(inti=0;i<4;i++) ob[i].set_x(i); for(i=0;i<4;i++) cout<<ob[i].get_x()<<endl;}当进行了上述定义之后,如果数组各元素要求不同的初始值,就可以使用初始化表进行赋值如:examob1[3]={1,2,3}; examob2[2]={exam(4),exam(5)};也可以进行强制类型转换如:ob1[0]=exam(6);对象指针实体对象与指针实体对象占有实在的空间〔含有自己的各种属性值〕;对象指针为指向实体对象的间接变量;指针在各种编译器下存放统一的地址。使用实体对象: examob;ob.get_x();指针对象: exam*ps=newexam; ps->get_x();指针变量必须指向实体变量后才能使用!!对象指针实体对象使用“.〞运算符操作其成员,而对象指针使用“->〞〔箭头运算符〕来使用其所指向对象的成员。对象指针最常用的是引用对象数组如:examob[4],*pe; pe=ob;pe++; //对象指针使用pe->get_x(); //对象数组对象指针C++提供的一个特殊对象指针this指针是一种隐含指针,每个成员函数都有一个this指针,用来标记成员函数操作哪个对象的数据成员。this是为了实现代码共享C++和C一样:函数的作用是减少代码开销。C++的类的对象占有自己的内存空间;但并不是每个对象有自己的代码,一个类的所有对象共用类的代码!classstack{ chardata[100];char*top;public: voidpush(charc) {//……}charpop(){//……}};Stacks1;Stacks2;代码区thisvoidpush(charc)thischarpop()s1对象data*tops2对象data*top数据区s1.pop();s2.pop();this是隐含的指针〔可以省略〕charstack::pop() {return*(--top);}或者是 {return*(--this->top);}编译器工作机理C++编译器把s1.pop()变成pop(&s1)编译器把声明charstack::pop()变成 charpop(stack*this)向函数传递对象对象可以作为参数传递给函数,其方法与传递其他类型的数据相同。向函数传递对象有三种方法:1〕传值——进行实参和形参对象的拷贝2〕传址——把实参的地址传递给形参指针,通过指针操作实参 3〕传引用——形参生成为实参的“别名〞,通过形参别名来操作实参,其作用和指针一样传值——参数传值是实参数值的拷贝,而不是参数本身传址——传递对象的地址传引用——形参为对象的别名举例fun_para.cpp静态成员--静态数据成员类中成员〔成员函数和数据成员〕前用“static〞修饰即成为静态成员特征:静态成员不管类创立了多少个对象,静态成员只有一个拷贝!(编译器要特殊处理——预先生成)举例:Student.cppclassstudent{public:staticintcount;//……};intstudent::count=0;//……无论student创立多少个实例,count将严格只存放一个拷贝。由于这种静态类成员独立于任何类对象存在,用::定义,而无需引用对象。static成员和整个程序作业一样持久,但作用域仅限于此类。另外,它的访问也是受控的〔公有、私有、保护的〕count
st1
st2
st3静态数据成员的初始化静态数据成员的初始化在类外进行,并在定义对象之前,一般在主函数main()之前,初始化的格式:数据类型类名::静态数据成员名=初始值不管它是公有的、私有的还是保护的,都必须在所有函数以外进行初始化,否那么将产生连接(linking)错误静态数据成员的访问独立于对象之外,采用:类名::静态数据成员名也可通过对象名访问:对象名.静态对象数据成员名对象指针->静态数据成员名静态数据成员也可设定访问限制,私有的静态数据成员不能被类外直接访问.静态成员函数用static修饰的成员函数即是静态成员函数静态成员函数只可以引用属于该类的静态数据成员或静态成员函数类外代码用类名和作用域分辨符调用,无需引用具体实例,甚至类实例可以不存在。私有静态成员函数不能被类外部的函数和对象访问#include<iostream.h>classstudent{private:staticintcount;public:student(){++count;}~student(){--count;}staticintgetCount(){returncount;}};intstudent::count=0;静态成员函数与非静态成员函数的区别静态成员是类的一局部,而不是对象的一局部;非静态成员函数有this指针,静态函数没有;静态函数只能访问本类中的静态数据成员,不能直接访问非静态数据成员,非静态函数均可访问.静态成员函数可通过对象名访问该对象的非静态成员为什么要使用静态成员函数在建立任何对象之前,通过静态成员函数对静态数据成员进行处理编译系统将静态成员函数限定为内部连接,与现行文件相连接的其他文件中的同名函数不会与该函数发生冲突,维护了该函数的平安性.假设定义了向量类Vector和矩阵类Matrix,各类都隐藏了私有数据成员,并提供了完整的操作函数。为简单起见,假设向量有3个元素,下标为0、1、2。而矩阵包含3行*3列=9个元素,下标都是0、1、2。假设要定义一个矩阵乘向量的函数multiply(),首先想到的方法是将它定义成外部函数,由Vector类和Matrix类共享它,此时在Vector类和Matrix类定义中,必须分别提供通过下标来访问各自元素的成员函数elem()。classVector{doublev[3];public://读向量v的第i号元素doubleelem(inti){returnv[i];}
void
set(inti,doublex){v[i]=x;}};classMatrix{doublem[3][3];public://读矩阵m的第i行第j列的元素doubleelem(inti,intj)
{returnm[i][j];}};
矩阵m[3][3]乘向量v[3]的运算法那么如下:m00m01m02v0m00v0+m01v1+m02v2m10m11m12v1=m10v0+m11v1+m12v2m20m21m22v2m20v0+m21v1+m22v2其结果矩阵(向量)r的第i行应为:r[i]=Σ2j=0m[i][j]*v[j];i=0,1,2假设将multiply()定义为外部函数,那么可写为:Vectormultiply(constMatrix&m,constVector&v){Vectorr;
doublex[3];for(inti=0;i<3;i++){for(intj=0;j<3;j++)
调用Matrix类的elem()n2=32x[i]+=m.elem(i,j)*v.elem(j);r.set(i,x[i]);n2=32(Vector类)returnr;}n=3(Vector类)
由于multiply()为非成员函数,不能直接访问Matrix类对象m和Vector类对象v的私有数据成员,只有通过各自的成员函数Vector::elem(inti)和Matrix::elem(inti,intj)访问。每当调用一次multiply()函数,Vector类的elem()要调用n2+n=3*(3+1)=12次,式中n为向量的元素个数。Matrix类的elem()要调用n2=32=9次,从而产生很大的调用开销,直接影响了程序的运行效率,特别当n增加时,调用次数将大幅度增加。如果把multiply()定义成友元函数,可以有限制地实现对类的私有局部和保护局部的直接访问,不必再定义各自的成员函数elem()。友元函数友员给予别的类或非成员函数访问私有成员权利。在此类中用关键字friend声明,公有或私有区声明都行。函数作为友元:
classt1{private:intdata;friendvoidfriend_t1(t1fri);public:t1(){data=12;} voiduse_friend();};voidfriend_t1(tifri)//外部函数
{fri.data=10;}友员不是成员——不能用this指针访问它。
voidt1::use_friend(){t1fri;this->friend_t1(fri);//
这是不正确的!
::friend_t1(fri); //
这是合法的!
}voidmain(){t1fri,fri1;fri.friend_t1(fri); //
这也是不正确的!
friend_t1(fri);}友元成员和友元类一个类〔或类的成员〕可以作为别的类的友元classx{public:voidf();private:inti;};classy{friendvoidx::f();inti;public://……};voidx::f(){yyi;yi.i=10;}classx{public:voidf();private:inti;};classy{friendx;inti;public://……};voidx::f(){yyi;yi.i=10;}友元关系是单向的,不具有交换性友元关系不具有传递性类A是类B的友元类B是类C的友元
类A是类C的友元类的组合--对象成员当一个类的对象作为另一个类的成员时,该对象称为对象成员,或子对象,这另一个类称为容器类(ContainerClass),这种方法称为组合技术。对象成员的初始化当创立容器类的对象时,对象成员所需的参数初始化机制由容器类的构造函数提供,这是在它的构造函数头内,为成员对象指定一个初始化参数表来完成的,其格式为:容器类〔构造函数〕名(参数表):成员对象名1(参数表1),成员对象名2(参数表2),...其中参数表1,参数表2,...为成员对象所属类相对应的构造函数参数表。classX{
类名1对象成员1;类名2对象成员2;//……};对于上面的类,其构造函数一般为X::X(参数表0):对象成员1(参数表1),对象成员2(参数表2)//……foo.cpp#include<iostream.h>classfoo{inti;public:foo(){i=0;}};classbar{inti;public:bar(intx){i=x;}intget(){returni;}};classSnafu{foof;barb1;barb2;public://空函数
Snafu():[f(),]b1(1),b2(2){}voidread1(){cout<<"b1ofobj="<<b1.bar::get()<<endl;}voidread2(){cout<<"b2ofobj="<<b2.bar::get()<<endl;}};voidmain(){Snafuobj;obj.read1();obj.read2();}该程序的输出结果:b1ofobj=1b2ofobj=2成员对象b1和b2的初始化参数表应放在所属类的构造函数Snafu()的头部,并用冒号隔开,各成员对象的初始化参数表b1(1)和b2(2)间用逗号隔开,每个成员对象只能在初始化参数表中出现一次。而成员对象f的构造函数foo()没有参数,所以成员对象f不必写到初始化参数表中,可以缺省。初始化过程当创立容器类的对象时,例如:Snafuobj;编译系统自动地先调用成员对象所属类相应的构造函数,执行初始化参数表b1(1)和b2(2)所规定的任务,然后再调用容器类的构造函数Snafu()。而调用成员对象构造函数的顺序取决于成员对象在容器类中定义的先后顺序,而不按初始化参数表的排列顺序。撤消容器类对象时,先为容器类对象调用析构函数,然后再调用对象成员的析构函数。
容器类必须有一个构造函数,以便提供一个对象成员的初始化参数表b1(1)和b2(2),尽管该构造函数体为空函数。编译系统不为容器类提供默认的构造函数。必须在容器类中定义一些公有成员函数,用来访问对象成员中的私有数据成员,在容器类的这些成员函数体内,调用对象成员所属类的公有成员函数去访问对象成员的私有数据成员,在访问表达式中用对象成员所属的类名加作用运算符指明该成员函数,其格式为:容器类对象成员名.对象成员所属的类名::
成员函数名(实参表);或容器类的对象指针成员->对象成员所属的类名::
成员函数名(实参表);
#include<iostream.h>classPoint{public:Point(intxi,intyi){x=xi;y=yi;}Point(Point&p){x=p.x;y=p.y;}~Point(){}intXcoord(){returnx;}intYcoord(){returny;}private:intx,y;};classCircle{public:Circle(intx1,inty1,intr):center(x1,y1){radius=r;}intGetRad(){returnradius;}intGetcenterX(){returncenter.Point::Xcoord();}intGetcenterY(){returncenter.Point::Ycoord();}private:Pointcenter;intradius;};
voidmain(){Pointpnt(160,180),*bp;Circlespotlight(320,240,40),*dp;bp=&pnt;cout<<"(1)pnt'sX="<<bp->Xcoord()<<"\tY="<<bp->Ycoord()<<endl;dp=&spotlight;cout<<"(2)spotlight'sX="<<dp->GetcenterX()<<"\tY="<<spotlight.GetcenterY();cout<<"\tradius="<<dp->GetRad()<<endl;}该程序的输出结果:(1)pnt'sX=160Y=180(2)spotlight'sX=320Y=240radius=40
对象的赋值和复制对象间赋值(=)是一个拷贝过程浅拷贝实现对象间数据元素的一一对应复制深拷贝当被复制的对象数据成员是指针类型时,不是复制该指针成员本身,而是将指针所指的对象进行复制系统提供的拷贝(如默认拷贝构造函数等)只能实现浅拷贝,深拷贝必须自定义拷贝当类中有指针数据成员时,并且为其开辟了空间,浅拷贝会出现问题!程序使用了系统默认的拷贝构造函数 Studentstu2(stu); 默认拷贝构造函数完成stu到stu2的逐位复制〔浅拷贝〕浅拷贝在对象释放时会出现问题拷贝问题原因 二个对象name指向同样内存,stu1调用析构函数后,name指向内存释放〔delete〕,那么stu2调用析构函数释放谁呢?问题解决 自定义拷贝构造函数〔或赋值运算符〕,自定义完成过程即为深拷贝!对象的赋值:调用的是C++自带的operator=()或者用户重载的等号操作函数。对象初始化:使用缺省构造函数或缺省复制构造函数。拷贝前拷贝后stu1的name字符数组元素占用的内存nameage
stu1nameage
stu1stu1和stu2的name指针指向同样的字符数组元素占用的内存!nameage
stu2逐位拷贝二者相等拷贝拷贝拷贝前拷贝后stu1的name字符数组元素占用的内存nameage
stu1nameage
stu1nameage
stu2把指针指向内容也复制对象数组对象数组在ANSIC中,把具有相同结构类型的结构变量,有序地集合起来便组成了结构数组。在ANSIC++中,与此类似将具有相同class类型的对象有序地集合在一起便组成了对象数组,对于一维对象数组也称为“对象向量〞,因此对象数组的每个元素都是同种class类型的对象。定义格式为:<存储类><类名>对象数组名[元素个数]。。。[={初始化列表}];其中<存储类>是对象数组元素的存储类型,与变量一样有extern型、static型和auto型等,该对象数组元素由<类名>指明所属类,与普通数组类似,方括号内给出某一维的元素个数。对象向量只有一个方括号,二维对象数组有两个方括号,如此类推。#include<iostream.h>classPoint{
intx,y;public:
Point(void){x=y=0;}
Point(intxi,intyi)
{x=xi;y=yi;}
Point(intc){x=y=c;}
voidPrint() {staticinti=0; cout<<"P"<<i++<<"("<<x<<","<<y<<")\n"; }};
voidmain(){
PointTriangle[3]={Point(0,0),Point(5,5),Point(10,0)};
intk=0;
cout<<"输出显示第"<<++k<<"个三角形的三顶点:\n";
for(inti=0;i<3;i++)
{
Triangle[i].Print();
}
Triangle[0]=Point(1);
Triangle[1]=6;//CallPoint(6)
Triangle[2]=Point(11,1);
cout<<"输出显示第"<<++k<<"个三角形的三顶点:\n";
for(i=0;i<3;i++)
{
Triangle[i].Print();
}PointRectangle[2][2]={Point(0,0),Point(0,6)
,Point(16,6),Point(16,0)};cout<<"输出显示一个矩形的四顶点:\n";for(i=0;i<2;i++) for(intj=0;j<2;j++) Rectangle[i][j].Print();cout<<"输出显示45度直线上的三点:\n";PointLine45[3]={0,1,2};for(i=0;i<3;i++)
Line45[i].Print();PointPtArray[3];cout<<"输出显示对象向量PtArray的三元素:\n";for(i=0;i<3;i++)
PtArray[i].Print();}
该程序的输出结果:输出显示第1个三角形的三顶点:P0(0,0)P1(5,5)P2(10,0)输出显示第2个三角形的三顶点:P3(1,1)P4(6,6)P5(11,1)输出显示一个矩形的四顶点:P6(0,0)P7(0,6)P8(16,6)P9(16,0)输出显示45度直线上的三点:P10(0,0)P11(1,1)P12(2,2)输出显示对象向量PtArray的三元素:P13(0,0)P14(0,0)P15(0,0)
对象数组的初始化当对象数组所属类含有带参数的构造函数时,可用初始化列表按顺序调用构造函数初始化对象数组的每个元素。如上例中:
PointTriangle[3]={Point(0,0),Point(5,5),Point(10,0)};PointRectangle[2][2]={Point(0,0), Point(0,6),Point(16,6),Point(16,0)};也可以先定义后给每个元素赋值,其赋值格式为:
对象数组名[行下标][列下标]=构造函数名(实参表);例如:Rectangle[0][0]=Point(0,0);Rectangle[0][1]=Point(0,6);Rectangle[1][0]=Point(16,6);Rectangle[1][1]=Point(16,0);假设对象数组所属类含有单个参数的构造函数时;如上例中〞Point(intc);〞,该构造函数置x和y为相同的值。那么对象数组的初始化可简写为: PointLine45[3]={0,1,2}; PointTriangle[3]={0,//CallPoint(0) 5,//CallPoint(5) Point(10,0)};PointTriangle[3]={Point(0,0),Point(5,5),Point(10,0)};Triangle[0]=Point(0,0);Triangle[1]=Point(0,0);Triangle[2]=Point(0,0);???对象数组创立时假设没有初始化列表,其所属类中必须定义无参数的构造函数,在创立对象数组的每个元素时自动调用它。如上例中在执行“PointPtArray[3];〞语句时,调用Point(void),初始化对象数组PtArray[]的每个对象为(0,0)。如果对象数组所属类含有析构函数,那末每当建立对象数组时,按每个元素的排列顺序调用构造函数,每当撤消数组时,按相反的顺序调用析构函数。#include<iostream.h>#include<string.h>classPersonal{ charname[20];public: Personal(char*n) {strcpy(name,n); cout<<name<<"sayshello!\n"; } ~Personal(void) {cout<<name<<"saysgoodbye!\n";}};voidmain(){ cout<<"创立对象数组,调用构造函数:\n"; Personalpeople[3]={"Wang","Li","Zhang"}; cout<<"撤消对象数组,调用析构函数:\n";}该程序的输出结果为:创立对象数组,调用构造函数:Wangsayshello!Lisayshello!Zhangsayshello!撤消对象数组,调用析构函数:Zhangsaysgoodbye!Lisaysgoodbye!Wangsaysgoodbye!对象的存储类与根本数据类型的变量一样,对象在定义时也必须指明它的存储类。自动(auto型)对象、外部(extern型)对象、静态(static)对象和动态对象(又称堆对象,即使用new和delete运算符创立和撤消的对象)。不同存储类的对象,其作用域和生存期也不相同。生存期与对象所在的存储区域密切相关,存储区域有程序代码区(CODEarea)、数据区(DATAarea)、堆区(HEAParea)和堆栈区(STACKarea)等。对应的生存期为静态生存期、动态生存期和局部生存期等
MEMORY
CODEarea程序代码区
静态存储区
存放外部、静态对象
堆区(Heap)DATAarea
动态存储区
自动对象、形参
堆栈区
STACKarea
对象的存储区域
对应的生存期为静态生存期、动态生存期和局部生存期等。静态生存期:具有静态生存期的对象,只要程序一开始运行或者运行到其定义点时它就存在,程序结束时才消失。它存放在数据区中分配固定的内存空间,当没有初始化时,编译系统自动将其数据成员设置为零(对数值型)或空(字符串或指针)。象外部(extern)型对象、静态(static)型对象都具有静态生存期。动态生存期:使用new和delete运算符创立和撤消的对象(包括变量),以及调用ANSIC标准函数库中的malloc()和free()函数创立和撤消的变量。具有动态生存期。它们存放在内存的堆中,由new运算符(或malloc()函数所创立的变量)为其分配内存空间那么生存期开始,当用delete运算符(或free()函数撤消该变量)撤消它时,生存期结束。局部生存期:自动对象和函数的形参具有局部生存期,它们存放在内存的堆栈区,假设没有初始化,其数据成员为随机值。对象与根本数据类型的变量不同的是,假设类提供了构造函数,每当创立该类的对象时,都将自动调用构造函数来实现每个新对象的初始化,每当撤消该类对象时都将自动调用析构函数(假设该类提供了析构函数)或默认的析构函数,以完成对象存储空间的自动回收。自动对象:与自动变量类同在函数体或程序块内定义的对象称为自动对象。定义它时就创立并自动调用构造函数来实现初始化,当程序退出定义它的作用域时,便自动调用析构函数或默认的析构函数撤消它。内部静态对象:与内部静态变量类同在函数体或程序块内定义的静态对象称为内部静态对象。当程序执行到它的定义点时便创立它,并自动调用构造函数来实现初始化,当程序结束时便自动调用析构函数或默认的析构函数撤消它。它的作用域和可见性一致,但与存在期不一致。外部对象和外部静态对象:它们都是全局型的对象,一旦程序启动,按它们定义的先后次序创立,并依次调用构造函数进行初始化,当程序结束时按相反的次序调用析构函数撤消它们。#include<iostream.h>#include<string.h>classA{charstring[50];public:A(char*st);~A();};A::A(char*st){strcpy(string,st);cout<<string<<"被创立时调用构造函数!"<<endl;}A::~A(){cout<<string<<"被撤消时调用析构函数!"<<endl;}voidfun(){cout<<"在fun()函数体内:\n"<<endl; AFunObj("fun()函数体内的自动对象
FunObj");
staticAInStaObj("内部静态对象
InStaObj");}fun_static.dswvoidmain(){AMainObj("主函数体内的自动对象
MainObj");cout<<"主函数体内,调用fun()函数前:\n";fun();cout<<"\n主函数体内,调用fun()函数后:\n";}staticAExStaObj("外部静态对象
ExStaObj");AGblObj("外部对象GblObj");
该程序的输出结果:外部静态对象ExStaObj被创立时调用构造函数!外部对象GblObj被创立时调用构造函数!主函数体内的自动对象MainObj被创立时调用构造函数!主函数体内,调用fun()函数前:在fun()函数体内:
fun()函数体内的自动对象FunObj被创立时调用构造函数!内部静态对象InStaObj被创立时调用构造函数!fun()函数体内的自动对象FunObj被撤消时调用析构函数!
主函数体内,调用fun()函数后:主函数体内的自动对象MainObj被撤消时调用析构函数!内部静态对象InStaObj被撤消时调用析构函数!外部对象GblObj被撤消时调用析构函数!外部静态对象ExStaObj被撤消时调用析构函数!自动对象的作用域仅在定义它的函数体或程序块内,其作用域范围小,生存期也短。内部静态对象的作用域虽然在定义它的函数体或程序块内,其作用域范围小,但生存期与作用域不一致却较长,从定义点开始一直到程序结束,在作用域以外虽然存在但不可见。外部静态对象的作用域与外部静态变量类同,是定义该对象所在的整个源文件,从定义点开始到文件结束,作用域局限在根源程序。但其生存期都是全局的。外部对象是在某个源文件中定义,而它的作用域却是包含该源文件的整个源程序,其生存期与作用域都是全局的。对象创立,构造-顺序,析构-逆序动态对象:如前所述,全局变量、静态数据、常量存放在全局数据区(DataArea),所有类成员函数和非成员函数的代码存放在代码区(CodeArea),为调用函数而分配的局部变量、函数参数、返回数据、返回地址等存放在堆栈区(StackArea),余下的内存空间是自由存储区称之为堆区(HeapArea)。动态对象的创立:在C++中,可用new运算符动态地创立对象,并为该对象在内存堆中分配存储空间,取代了ANSIC中的malloc()函数,这类对象称为动态对象,其定义格式为: new<类型>(初值表) ①或 new<类型> ②它说明在堆(heap)中动态建立了一个由<类型>所指定的对象。第①种形式由圆括号包围的“初值表〞给出被创立对象的初始值。也可采用不赋初始值的第②种形式,但必须给它赋值后才能参加运算和操作,这种形式经常用来定义动态对象数组。这里所说的<类型>包含所有的根本数据类型和派生类型(或称复杂的数据类型、构造类型),以及用户定义的class类型。new为任意类型的对象在堆(Heap)中分配一块所需的存储空间,并返回该存储空间的首地址。如果堆中没有足够的存储空间或分配出错时返回空(NULL)指针。因此与使用malloc()函数一样,必须检测其返回值不为空指针,在预先定义了一个同类型的指针后,这种形式便于应用if语句,检测其返回值是否为空指针,可写为:if((指针名=new类型)==NULL){...}例中:if((p=newPoint)==NULL){...}顺便指出,在以后程序中该指针名用来代替所创立的动态对象名,该动态对象本身是匿名的。由于:简化if(p==NULL)〔即if(p==0)〕if(!p)简化if(p!=NULL)〔即if(p!=0)〕if(p)一般格式可写为:if(!(指针名=new类型)){出错处理操作;}if(指针名=new类型){创立成功的处理操作;}有时,也可直接采用初始化操作创立新的动态对象,其格式为:
类型*指针名=new类型(初值表);例如:int*pi=newint;Date*pd=newDate;用new也可定义一个动态对象数组,new的返回值是数组第1个元素的地址,其格式为:
<类型>*指针变量名=new<类型>[元素个数];例如:Point*pt=newPoint[3];
即创立了具有3个元素的Point类型的(动态)数组pt,对单个对象也可以采用这种格式,只不过元素个数为1。例如:Point*px=newPoint[1];用这种格式创立对象时,只是给对象分配了内存空间,不能对它赋初值。因此在使用前,还必须用赋值操作对其赋值。#include<iostream.h>#include<string.h>classB{charname[80];doubleb;public:staticintcount;B(char*s,doublen){strcpy(name,s);b=n; cout<<"调用一般构造函数,count:"<<++count<<endl;}B()
{cout<<"调用无参数构造函数,count:"<<++count<<endl;}~B(){cout<<"调用析构函数撤消对象"<<name<<"后,c
温馨提示
- 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
- 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
- 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
- 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
- 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
- 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
- 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。
最新文档
- 2025年无菌包装用包装材料项目规划申请报告模范
- 2025年健康护理产品购销合同书样本
- 2025年新股权分配策划协议
- 2025年环境有害生物防治合同
- 2025年健身房个人教练聘请合同范本
- 2025年子女抚养费用分担策划协议
- 2025年共同研发知识产权合同
- 2025年合作双方产品协议范本
- 2025年全年图书选购合作协议书样本
- 2025年公园景观照明设备定期维护服务申请协议
- 房产中介公司薪酬制度
- 成人氧气吸入疗法护理标准解读-2
- JBT 2231.3-2011 往复活塞压缩机零部件 第3部分:薄壁轴瓦
- 旅游学概论(郭胜 第五版) 课件 第1、2章 旅游学概述、旅游的产生与发展
- 高一文理分科分班后第一次家长会市公开课一等奖省赛课获奖
- 2024年江西生物科技职业学院单招职业适应性测试试题库(典优)
- 13.2《致大海》课件高中语文选择性必修中册
- 2024年长沙电力职业技术学院单招职业技能测试题库及答案解析
- 商场收银主管个人工作总结
- 部编版语文三年级下册第一单元大单元整体学习任务设计(表格式)
- 中职生心理健康教育全套教学课件
评论
0/150
提交评论