版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领
文档简介
1VC++面向对象与可视化程序设计清华大学计算机系2第1章C++基础知识31.1关于C++ C++是既适合于作为系统描述语言,也适合于编写应用软件的既面向对象又面向过程的一种混合型程序设计语言,它是在C语言的基础之上发展起来的。1.2一个简单的C++程序4一个简单的C++程序
下面通过一个非常简单的用C++编写的例子,让读者了解一下C++的基本输出操作。其功能是在屏幕上显示“Welcome!”,其程序代码如下:#include"iostream"usingnamespacestd;intmain() //main函数,程序入口{ charstr_greet[]="Welcome!";//定义一个数组并初始化 cout<<str_greet<<endl; //在屏幕上输出字符串内容return0;} 创建基于空项目的C++应用程序C/C++编写的程序在程序结构上基本是相同的,都是以main为入口;不同:C++以iostream作为标准IO头文件,C以stdio.h作为标准IO头文件;C++中采用“<<”作为标准输出,C是用printf来实现。6cout是C++中的标准输出流(通常为控制台,即屏幕),这句话把一串字符串(本例中为“Welcome!”)插入输出流(控制台输出)中。cout在声明在头文件iostream中,所以要想使用cout必须将该头文件包括在程序开始处。早些的实现将标准库功能定义在全局空间里,声明在带.h后缀的头文件里,c++标准为了和C区别开,也为了正确使用命名空间,规定头文件不使用后缀.h。使用.h,相当于在c中调用库函数,使用的是全局命名空间,也就是早期的c++实现;当使用<iostream>的时候,该头文件没有定义全局命名空间,必须使用namespacestd;这样才能正确使用cout。7namespace是指标识符的各种可见范围。C++标准程序库中的所有标识符都被定义于一个名为std的namespace中。使用方法是:命名空间std封装的是标准函数库的名称,标准函数库为了和以前的头文件区别,一般不加".h"
usingnamespacestd;1.3C++中的变量和数据类型81.3.1变量的初始化(1)数字值变量的初始化在C++中,数值变量的初始化可以在括号内进行,其格式如下:数据类型变量名(初值);比如下面的例子中,语句“intb(10);”就是这种定义模式。#include<iostream>usingnamespacestd;intmain(){inta=3; //初始值为3intb(10); //初始值为10intresult; //不确定初始值result=a-b;cout<<result;return0;} 9(2)非数字值变量的初始化针对字符串,C++提供一个string类来支持字符串的操作,该类并不是一个基本的数据类型,但在一般的使用中与基本数据类型很相似。用户如果需要声明和使用字符串类型的变量,需要引用头文件<string>,如下面例子所示:#include<iostream>#include<string>usingnamespacestd;intmain(){stringtest_string;test_string="TestString***China***";cout<<test_string<<endl;test_string="TestString***Beijing***";cout<<test_string<<endl;return0;} 10(3)常量的初始化C++通过使用const关键字来完成常量定义的:constintx=5;constchary='n';111.3.2C++的输入与输出操作(1)输入操作C++中的标准输入是采用“cin>>”这个格式来完成。它后面必须跟一个变量以便存储读入的数据。例如:intx; //声明一个整型变量xcin>>x; //将输入值存储在这个变量x中
也可以利用cin输入多个数据,如:cin>>x>>y;等同于:cin>>x;cin>>y;
在以上两种情况下用户都必须输入两个数据,分别给变量x和y。输入时两个变量之间可以使用空格符、tab符或回车。12
值得注意的是,使用cin和>>操作符来读取字符串,例如:cin>>test_string;
由于“cin>>”只能读取一个单词,一旦碰到任何空格,读取操作就会停止,这样就无法输入一个英文句子,要解决此问题,可以使用C++的getline函数,对于字符串的读入,更建议使用getline来进行。
13下面是一个读取字符串的例子:#include<iostream>#include<string>usingnamespacestd;intmain(){stringtest_string;cout<<"你在哪个学校上学?";getline(cin,test_string);cout<<"Hello"<<test_string<<"\n";cout<<"你喜欢什么专业?";
getline(cin,test_string);cout<<"我喜欢"<<test_string<<"专业\n";return0;}改成cin>>test_string;试试看(2)输出操作
输出用“<<”进行操作,比如:cout<<"Hello!"<<"VC++"<<"ObjectOrientedProgramming";初学者要注意输出字符串和输出变量的区别。请观察如下的代码:cout<<"abc"; //打印字符串abc到屏幕上cout<<abc; //把变量abc存储的内容输出到屏幕上
15上面这一行语句将会打印
Hello!VC++ObjectOrientedProgramm
到屏幕上。值得注意的是,cout操作不会产生换行,因此,当需要换行的时候,可以输出一个换行符“\n”,如:cout<<"Hello!\n";此外还可以用操作符endl来换行,例如:cout<<"Hello!"<<endl;cout<<"VisualC++"<<endl;将会输出:Hello!.VisualC++(3)C++中的输入输出流C++中把数据之间的传输操作抽象地称作流。在C++中,流可以表示数据从内存传送到某个载体或者设备中,叫做输出流;也可以表示数据从某个载体或者设备传送到内存缓冲区变量中,叫做输入流。数据在不同的设备之间传送后不一定会消失,广义地讲,也可以把与数据传送有关系的事务叫做流,例如,可以把文件变量叫做流,有时候,流还可以代表要进行传送的数据的结构、属性和特性,用一个名字来表示,叫做流类;而用流代表输入设备和输出设备,叫作流的对象。17(a)输出流cout在C++中预先定义了一个输出流的成员函数put,其用法如下:cout.put(charch)或cout.out(constcharch)下面的代码,体现put函数的应用://#include"stdafx.h"#include<iostream>usingnamespacestd;intmain(){charch1='M',ch2='N';cout<<'M'<<'.'<<'N'<<endl;cout.put(ch1).put('.').put(ch2).put('\n');cout.put('M').put('.').put('N').put('\n');return0;}上述代码的输出结果是三个M.N,值得说明的是,cout对象可以连续调用put函数,比如代码:cout.put(ch1).put('.').put(ch2).put('\n');cout.put('M').put('.').put('N').put('\n');就是连续调用了put函数。18我们还可以使用write()函数输出字符串,其函数调用格式如下:cout.write(constchar*str,intn)其中参数str是指向字符对象的指针,参数n表示要输出的字符对象的字节数。下面通过一个简单的例子介绍write()函数的应用。#include<iostream>usingnamespacestd;intmain(){charstr1[]="ABCDEFGHIJ";charstr2[]="abcdefghij";cout.write(str1,10).put('\n');cout.write(str2,strlen(str2)).put('\n');cout.write("HELLOWORLD",5)<<endl;return0;}从cout.write("HELLOWORLD",5)<<endl;可以看出,利用write()函数可实现对字符串的部分或全部的输出,而其中的第一个参数,既可以是指向字符对象的指针,也可以是字符型数组的首地址(数组名),也可以直接给出字符串常量,用起来还是很方便的。19其输出结果为:ABCDEFGHIJabcdefghijHELLO(b)输入流cin
在C++的标准输入流中,可以使用cin的成员函数get()、getline()和read()等。下面介绍get()函数的使用,请参见下面的代码:#include<iostream>usingnamespacestd;intmain(){intn=0;charch;cout<<"请输入一个字符串:\n";while((ch=cin.get())!='\n'){cout<<ch;n++;}cout<<"\n总字符个数是"<<n<<endl;return0;}20
当cin被用来输入字符串时,常与函数getline一起使用:cin.getline(charbuffer[],intlength,chardelimiter='\n');其中:buffer:用来存储输入的地址。函数是从当前的流开始,读取size-1个字符,或者是遇到delim指定的分隔符就结束;length:一个缓存buffer的最大容量;delimiter:用来判断用户输入结束的字符,它的默认值是换行符('\n')。21下面的例子显示了如何使用cin.getline来输入字符串:#include<iostream>usingnamespacestd;intmain(){charstr[50];cout<<"你在哪一所学校?";cin.getline(str,50);cout<<"Hello"<<str<<".\n";cout<<"你学什么专业?";cin.getline(str,50);cout<<"我喜欢"<<str<<"专业\n";return0;}下面的代码展现其应用:#include<iostream>usingnamespacestd;intmain(){charch[30];cout<<"请输入一个字符串:\n";cin>>ch;cout<<"所输入的字符串是"<<ch<<endl;
cin.getline(ch,30,'!');cout<<"遇!之前的字符是:"<<ch<<endl;
cin.getline(ch,30);cout<<"遇!之后的字符是"<<ch<<endl;return0;}请输入一个字符串:HelloVisual!C++所输入的字符串是Hello遇!之前的字符是:Visual遇!之后的字符是C++
值得注意的是,对于cin的输入,它在提取数据时,遇到第一个空格就会停止,因此,第一个输出是“Hello”,然后第二个输出语句,从当前的结束位置继续往后读取,当遇到“!”时结束,于是就输出“Visual”,最后剩下的输出语句,就输出剩下的字符串,一直到回车结束,或29个字符为止,于是就输出“C++”。如下面的代码用的是“!”标识结束,函数把读取的字符串写入buf地址开始的一段连续存储空间,并在结束位置自动加上截止符“\0”。read()函数的用法很简单,其原型如下:cin.read(c,count);
其功能是从字符串流中读取count个字符到c数组中。下面举个简单的例子说明其应用。#include<iostream>usingnamespacestd;intmain(){charc[10];intcount=3;cout<<"请输入一个字符串\n";
cin.read(c,count);c[count]='\0';cout<<c<<endl;cout<<c[count+1]<<endl;return0;}上述代码中从读入的字符串中提取前三个字符并输出。23(3)字符串流
标准头文件<sstream>定义了一个叫做stringstream的类,使用这个类可以对基于字符串的对象进行操作,这对将字符串与数值互相转换非常有用。例如,如果我们想将一个字符串转换为一个整数,可以这样写:#include<sstream>stringmy_string("12345");intmy_int;stringstream(my_string)>>my_int;cout<<my_int;
上述代码定义了一个字符串类型的对象my_string,并赋初值为"12345",然后接着定义了整型变量my_int,最后调用stringstream类的构造函数,并以字符串变量my_string为参数。这段代码执行之后变量my_int存储的是数值12345。24下面是一个完整的实例体验此应用。#include<iostream>#include<sstream>usingnamespacestd;intmain(){stringstr;floata=0.0;intb=0;cout<<"a=?";getline(cin,str);stringstream(str)>>a;cout<<"b=?";getline(cin,str);stringstream(str)>>b;cout<<"a*b="<<a*b<<endl;return0;}25在此代码的执行过程中,输入了变量a和b的值。但不同于从标准输入中直接读取数值,这里使用函数getline从标注输入流cin中读取字符串对象(str),然后再从这个字符串对象中提取数值a和b。(4)字符串和其它数据类型的转换将字符串内容转换成数字型变量的功能在数据处理中经常会用到。例如一个字符串的内容可能是"1234",如何转换为一个整数?因此,函数库cstdlib提供了3个有用的函数:atoi:将字符串string转换为整型int;atol:将字符串string转换为长整型long;atof:将字符串string转换为浮点型double;所有这些函数接受一个参数,返回一个指定类型的数据(int、long或float)。这三个函数与cin.getline一起使用来获得用户输入的数值,比传统的cin>>方法更可靠。26下面的例子体现其应用:#include<iostream>#include<cstdlib>usingnamespacestd;intmain(){charstr[50];doublea;intb;cout<<"a=?";cin.getline(str,50);a=atof(str);cout<<"b=?";cin.getline(str,50);b=atoi(str);cout<<"a*b="<<a*b;return0;}27此代码的执行过程中,如果对第一个str输入字符串11,对第二个str输入字符串22,那么转换成整型后分别赋给变量a和b,相乘后输出结果为242。1.4动态内存分配目前我们所接触到的程序,所声明的变量、数组和其他对象所必需的内存空间都是确定的。但如果我们需要内存大小为一个变量,其数值只有在程序运行时才能确定,那该如何处理呢?可以采用C++提供的动态内存分配方法来处理,为此C++集成了操作符new和delete。28(1)操作符new和new[]
用操作符new开辟动态内存空间,关键字new后面跟一个数据类型,并跟一对可选的方括号“[]”,里面为要求的元素数。其结果返回一个指向内存块起始位置的指针。其形式为:p=newtype //给一个单元素的数据类型分配内存或者p=newtype[elements] //给一个数组分配内存29例如:int*p;p=newint[10];
上述代码的操作结果,操作系统分配了可存储10个int元素的内存空间,返回指向这块空间起始位置的指针并将它赋给p。因此,现在p指向一块可存储10个整型元素的合法的内存空间。
这种分配数组空间的做法与定义一个普通的数组思路是不同的,定义普通的数组,数组的长度是常量,而采用动态内存分配,数组的长度可以常量,也可以是变量,其长度大小在程序执行过程中确定。
动态内存分配通常由操作系统控制,由于在多任务的环境中,它可以被多个应用所共享,因此,极端情况内存有可能被用光。如果这种极端情况情况发生,操作系统将无法在遇到操作符new时再分配所需的内存,那么这个时候将返回一个无效指针(nullpointer)。因此,为了程序的可靠性,建议在使用new之后要检查返回的指针是否为空(null),如下例所示:int*p;p=newint[10];if(p==NULL){cout<<"errorassigningmemory.Takemeasures.";};31(2)删除操作符delete
既然动态分配的内存只是在程序运行的某一具体阶段才有用,那么一旦它不再被需要时就应该被释放,以便回收内存,免得发生极端情况,内存被用光。操作符delete的使用形式如下:deleteP; //删除给单个元素分配的内存或delete[]p;//删除多元素(数组)的内存分配32下面是一个应用例子:#include<iostream>usingnamespacestd;intmain(){charinput[10];inti,n;long*j;cout<<"希望开几个元素的数组?";cin.getline(input,10);i=atoi(input);j=newlong[i];if(j==NULL) exit(1);for(n=0;n<i;n++){cout<<"输入数据:";cin.getline(input,10);j[n]=atol(input);}cout<<"输入内容如下:";for(n=0;n<i;n++)cout<<j[n]<<"\n";delete[]j;return0;}此代码可以记录用户希望输入的任意多个数字,它的实现主要是通过动态地向系统申请用户要输入的数字所需的空间。1.5“类”是什么?类(class)是一种将数据和函数组织在同一个结构里的逻辑方法。定义类的关键字为class,其功能与C语言中的struct类似,不同之处是class可以包含函数,而不像struct只能包含数据元素。341.5.1类的定义class类名:基类名{
private:
私有成员数据及函数;
protected:
保护成员数据及函数;public:
公共成员数据及函数;}[类的对象声明];35从类的定义可以看到,一个类含有私有、保护和公共三部分。缺省时在类中定义的项都是私有的。private部分的数据和函数只能被该类本身声明的函数存取;protected部分的成员除可以被本类中的成员函数访问外,还可以被本类派生的类的成员函数访问,因此用于类的继承;public部分的成员可以被本类以外的函数访问,是类与外部的接口。在定义一个class成员的时候没有声明其允许范围,这些成员将被默认为private范围。如:CRectangle的类:classCRectangle{intx,y;public:voidset_values(int,int);intarea(void);}rect;
上述代码定义的CRectangle类及其对象变量rect。含:两个private的整型变量x和y,两个函数的原型,并没有函数体(函数体需另行定义)36
在C++中,“对象”是声明为“类”类型的一个数据项,是类的实际变量。这个定义的类的变量就被称为对象。对象有时也称为类的实例(Instance)。37
由此可见,类是程序中的一个静态的概念,而对象是程序中的一个动态的概念。在C++中有两种方法可以定义类的对象:第一种是在定义类的同时直接定义类的对象,即在定义类的右大括号“}”后直接写出属于该类的对象名表列。如:class类名{
成员变量表列;
成员函数表列;}对象名表列;
第二种是在定义好类后,再定义类的对象,其一般格式如下:
类名对象1[,对象2,…];在C++中通常也把类的成员函数称为类的方法;39类的方法成员函数的原型一般在类的定义中声明,其语法与声明普通的函数所用的语法完全相同;方法的具体实现,可以在类的定义内部完成(这种方式定义的类的方法有时也称为类的内联函数),也可以在类的定义之外进行,而且方法的具体实现既可以和类的定义放在同一个源文件中,也可以放在不同的源文件中。方法的具体实现和普通函数的具体实现只是在函数的头部有略微不同的格式。一般来说,如果类的方法的定义是在类的外部实现的,则在定义方法时必须把类名放在方法名之前,中间用作用域运算符(“::”)隔开,其一般形式如下所示:
类名::方法名这样,即使几个类中的方法名相同,也可以用这种形式把它们区分开来。在C++中通常也把类的成员函数称为类的方法。成员函数的原型一般在类的定义中声明,在类的定义中声明其成员函数的语法与声明普通的函数所用的语法完全相同。方法的具体实现,可以在类的定义内部完成(这种方式定义的类的方法有时也称为类的内联函数),也可以在类的定义之外进行,而且方法的具体实现既可以和类的定义放在同一个源文件中,也可以放在不同的源文件中。41一个简单的类的定义classCRectangle{
intx,y;public:voidset_values(int,int);intarea(void);}rect;42这个例子定义了一个CRectangle类和该class类型的对象变量rect。这个类有4个成员:两个整型变量(x和y),在private部分(没写private的就是缺省);两个函数,set_values()和area(),在public部分,这里只包含了函数的原型。
类是面向对象程序设计最基本的单元,在设计面向对象程序时,首先要以类的方式描述实际待解决的问题,也就是将问题所要处理的数据定义成类的私有或公共类型的数据,同时将处理问题的方法定义成类的私有或公有的成员函数。对象成员的引用:
对象名.成员名用此方式引用对象rect的任何public成员,例如:rect.set_value(10,20);my_area=rect.area();
但不能直接引用x或y,因为它们是该class的private成员,它们只能够在该class的其它成员中被引用。4344下面的例子计算矩形的面积。#include<iostream>usingnamespacestd;classCRectangle{intx,y;public:voidset_values(int,int);intarea(void){return(x*y);}};voidCRectangle::set_values(intm,intn){x=m;y=n;}intmain(){CRectanglerecta,rectb;recta.set_values(10,20);rectb.set_values(30,40);cout<<"rectaarea:"<<recta.area()<<endl;cout<<"rectbarea:"<<rectb.area()<<endl;return0;}将set_values函数体的定义放在class定义之外,而将area函数体的定义放在calss定义之内。在class内部直接定义完整的函数和把具体实现放在class外部的区别在于,直接定义时,编译器会自动将函数作为inline(内联函数,后续介绍)考虑,而在class外部定义时,函数只是一般的class成员函数。45调用函数recta.area()与调用rectb.area()所得到的结果是不一样的。这是因为每一个classCRectangle的对象都拥有它自己的变量x和y,以及它自己的函数set_value()和area()。在这个具体的例子中,我们讨论的class是CRectangle,有两个实例,或称对象recta和rectb,每一个有它自己的成员变量和成员函数。
方法的具体实现和普通函数的具体实现只是在函数的头部有略微不同的格式。
如果类的方法的定义是在类的外部实现的,则在定义方法时必须把类名放在方法名之前,中间用作用域运算符(“::”)隔开,其一般形式如下所示:类名::方法名如:voidCRectangle::set_values(intm,intn)
46即使几个类中的方法名相同,也可以用这种形式把它们区分开下面的代码在程序中定义一个名为angle的类,然后求sin函数在60度时候的值。#include<iostream>#include<cmath>usingnamespacestd;constdoubleANG_TO_RAD=0.0174532925;//定义弧度和度之间的转换比例classangle //定义类angle{doublevalue; //类angle的私有数据成员public: //类angle的公共成员函数voidSetValue(double);doubleGetSine(void);}deg; //声明类angle的对象deg4748voidangle::SetValue(doublea)//成员函数SetValue{ value=a; }
doubleangle::GetSine(void) //成员函数GetSine{ doubletemp; temp=sin(ANG_TO_RAD*value); returntemp;}
intmain(){deg.SetValue(60.0);//给类angle的成员value赋值
cout<<"Thesineoftheangleis:";cout<<deg.GetSine()<<endl; //输出正弦值return0;}类也可以嵌套声明,例如:
classMy_student
{classboy //嵌入类boy,作为类My_student的成员之一{ charboy_name[20]; intboy_age;}my_boy_student;
classgirl //嵌入类girl,作为类My_student的成员之一{ chargirl_name[20]; intgirl_age;}my_girl_student;public: voidstudent_input(void); voidstudent_output(void);};49#include<iostream>usingnamespacestd;classOuter{public:classInner
{public:voidFun();
};public:Innerobj_;voidFun()
{cout<<"Outer::Fun...."<<endl;obj_.Fun();
}};50一个类嵌套的例子51voidOuter::Inner::Fun(){cout<<"Inner::Fun..."<<endl;}voidFun(){classLocalClass
{public:intnum_;voidInit(intnum)
{num_=num;
}voidDisplay()
{cout<<"num_"<<num_<<endl;
}
};LocalClasslc;lc.Init(10);lc.Display();}intmain(){Outerm;m.Fun();Outer::Inneri;i.Fun();Fun();return0;}1.5.2内联函数内联函数是指那些定义在类体内的成员函数,即该函数的函数体放在类体内,类的方法也可以声明和定义成内联函数;52内联函数在调用时不像一般的函数那样要转去执行被调用函数的函数体,执行完成后再转回调用函数中,执行其后语句,而是在调用函数处用内联函数体的代码来替换,这样会提高运行速度。因此内联函数主要是解决程序的运行效率问题;值得注意的是,内联函数一定要在调用之前定义,且内联函数无法递归调用。内联函数的两种定义方式当在函数的外部定义时,把关键字inline加在函数定义之前。例如:下面的程序段中定义的类angle的SetValue方法被定义成内联函数。classangle //定义类angle{private:doublevalue;public:voidSetValue(double);};inlinevoidangle::SetValue(doublex)//定义内联函数{ value=x;}53(2)把函数原型声明和方法的定义合并,放入类定义中例如,下面的程序段在声明类angle的SetValue方法后,紧接着就定义该方法的具体实现。classangle //定义类angle{private:doublevalue; //定义私有数据成员public:
voidSetValue(doublex) //定义内联函数{ value=x;}};54#include<iostream>usingnamespacestd;classCRectangle{intx,y;public:voidset_values(int,int);intarea(void){return(x*y);}};voidCRectangle::set_values(inta,intb){x=a;y=b;}intmain(){CRectanglerect;rect.set_values(5,6);cout<<"rectarea:"<<rect.area()<<endl;return0;}551.6构造函数和析构函数C++中有几类特殊的成员函数,这些函数决定了如何建立、初始化、拷贝及删除对象。构造函数和析构函数是其中最重要的两种。56和一般的成员函数一样,构造函数和析构函数既可以在类的内部声明和定义;也可以在类的内部声明,在类的外部定义。如果一个类含有构造函数,则在建立该类的对象时就要调用它;而如果一个类含有析构函数,则在销毁该类的对象时调用它。1.6.1构造函数对象(object)在生成过程中通常需要初始化变量或分配动态内存,以便我们能够操作,或防止在执行过程中返回意外结果。构造函数是一种特殊的成员函数,它主要用来为对象分配内存空间,对类的数据成员进行初始化并执行对象的其他内部管理操作。构造函数的特点:构造函数的名字和它所在的类名相同,当定义该类的对象时,构造函数完成对此对象的初始化。它可以接收参数并允许重载。当一个类含有多个构造函数时,编译程序为了确定调用哪一个构造函数,需要把对象中使用的参数和构造函数的参数表进行比较,此过程与在普通的函数重载中进行选择的过程相同#include<iostream>#definePI3.1415926usingnamespacestd;classCVolume{intr,h;public:CVolume(int,int);doublevolume(void){return(PI*r*r*h);}};
CVolume::CVolume(inta,intb){r=a; h=b;}
intmain(){CVolumevol(5,6);cout<<"Volume="<<vol.volume()<<endl;return0;}58构造函数的名字和它所在的类的名字相同构造函数没有返回值,也没有void类型声明。这时一种规则。关于构造函数的几个问题(1)构造函数和普通函数一样也可以有参数,但不能有返回值。这是因为构造函数通常是在定义一个新的对象时调用,它无法检查构造函数的返回值。59(2)在实际应用中,如果没有给类定义构造函数,则编译系统将为该类生成一个缺省的构造函数,该缺省的构造函数没有参数,只是简单地把对象中的每个实例变量初始化为0。(3)构造函数可以有缺省参数。
例如,下面定义的类CVolume中的构造函数#include<iostream>#definePI3.1415926usingnamespacestd;classCVolume{intr,h;public:CVolume(int,int);doublevolume(void){return(PI*r*r*h);}};CVolume::CVolume(inta=5,intb=6){r=a; h=b;}intmain(){CVolumevol;cout<<"Volume="<<vol.volume()<<endl;return0;}构造函数CVolume的两个参数均为缺省参数,在定义对象时可以省略或部分省略实参。因此,可以把main函数改写成如下形式:intmain(){CVolumevol1(1,2),vol2(1),vol3;cout<<"Volume1="<<vol1.volume()<<endl;cout<<"Volume2="<<vol2.volume()<<endl;cout<<"Volume3="<<vol3.volume()<<endl;return0;}这时的输出结果如下:Volume1=6.28319Volume2=18.8496Volume3=471.23961对于vol1(1,2),那么原来缺省值失效;对于vol2(1),那么按参数顺序,a被重新初始化为1,而参数b没有被初始化,保留原来的缺省值6;对于vol3,由于没有给出新的参数,完全采用原来缺省的参数值。(4)构造函数也可以没有参数。例如,下面定义的类My_class中的构造函数就没有参数,该类的构造函数#include<iostream>usingnamespacestd;classCVolume{public:CVolume() { cout<<"NOPARAMETER"<<endl; }};intmain(){CVolume();return0;}1.6.2析构函数析构函数也是类中的特殊成员函数,与定义它的类具有相同的名字,但要在前面加上一个波浪号(“~”)。析构函数没有参数,也没有返回值,而且也不能重载,因此一个类中只能有一个析构函数。63析构函数执行与构造函数相反的操作,通常用于释放分配给对象的存储空间。当程序超出类对象的作用域时,或者当对一个类指针使用运算符delete时,系统将自动调用析构函数。和构造函数一样,如果在类的定义中不定义析构函数,编译系统将为之产生一个缺省的析构函数,对于大多数类来说,缺省的析构函数就能满足要求。如果在一个对象完成其操作之前还需要做一些内部处理,则应定义析构函数。例如,下面定义的类My_class中定义了该类的析构函数。classMy_class{ char*str; intMaxLen;public: My_class(char*) //定义类My_class的构造函数{ str=newchar[MaxLen]; } ~My_class() //构造类My_class的析构函数{ cout<<"Heredeletethestr"<<endl; deletestr;}voidGetString(char*);};
上面的例子是析构函数和构造函数最常见的用法,即在构造函数中用运算符new为字符串分配存储空间,最后在析构函数中用delete释放已分配的存储空间。
析构函数也可以在类定义的内部声明而在类定义的外部定义。例如,上面的析构函数My_class在类定义的外部定义如下:My_class::~My_class(){ deletestr;}析构函数的应用:#include<iostream>#definePI3.1415926usingnamespacestd;classCVolume{int*r,*h;public:CVolume(int,int);~CVolume(){cout<<"Heredeletether&h"<<endl;deleter;deleteh;};doublevolume(void){return(PI*(*r)*(*r)*(*h));}};
6667CVolume::CVolume(inta,intb){r=newint;h=newint;*r=a;*h=b;}intmain(){CVolumevola(1,2),volb(3,4);cout<<"vola="<<vola.volume()<<endl;cout<<"volb="<<volb.volume()<<endl;return0;}上述代码的运行结果如下:vola=6.28319volb=113.097Heredeletether&hHeredeletether&h
上面的例子是析构函数和构造函数最常见的用法,即在构造函数中用运算符new为字符串分配存储空间,最后在析构函数中用delete释放已分配的存储空间。析构函数也可以在类定义的内部声明而在类定义的外部定义。例如,上面的析构函数CVolume在类定义的外部定义如下。CVolume::~CVolume(){cout<<"Heredeletethew&h"<<endl;deleter;deleteh;}681.7函数重载69
重载是C++的一个重要特征,重点介绍函数重载所谓函数重载是指同一个函数名可以对应着多个函数的实现。函数重载允许一个程序内声明多个名称相同的函数,这些函数可以完成不同的功能,并可以带有不同的类型、不同数目的参数及返回值。使用函数重载可以减轻用户的记忆负担,并使程序的结构简单、易懂。下例实现的是两个不同参数类型的重载。在类My_class中对方法plus进行了重载,通过重载,使得在求两个数的和时不用再区分整数和浮点数之间的不同之处,而只需直接调用类My_class的方法plus即可。#include<iostream>usingnamespacestd;classMy_class{public:intplus(int,int);doubleplus(double,double);};intMy_class::plus(intx,inty){returnx+y;}doubleMy_class::plus(doublex,doubley){returnx-y;}intmain(){My_classData;cout<<"Theresultforplus(int,int)is:"<<Data.plus(5,10)<<endl;cout<<"Theresultforplus(double,double)is:"<<Data.plus(5.0,10.0)<<endl;return0;}两个重载的成员函数分别调用求整型数之加和和求浮点数之加和的函数plus来对不同的参数进行求和运算。
函数重载要求编译器能够唯一地确定调用一个函数时应执行哪个函数代码。确定函数实现时,要求从函数参数的个数和类型上来区分。也就是说,函数重载时,要求函数的参数个数或参数类型不同。
不仅在类的成员函数上可以实现重载,在构造函数上,也可以实现函数的重载。例如:#include<iostream>usingnamespacestd;classCSample{inti;public:CSample(); //定义重载的构造函数CSample(int);intarea(void){return(i);}};
在处理不同类型的数据时,成员函数的重载使得类具有更大的灵活性和通用性,这也有力地支持了通过类来对我们周围的客观世界进行抽象的程序设计的思想。CSample::CSample() //定义构造函数sample{i=0;cout<<"sampleA,i="<<i<<endl;}CSample::CSample(intx) //定义构造函数sample(int){i=x;cout<<"sampleB(5),i="<<i<<endl;}intmain(){CSamplej1,j2(5);cout<<"j1.area()="<<j1.area()<<endl;//调用构造函数Csamplecout<<"j2.area()="<<j2.area()<<endl;//调用构造函数Csample(int)return0;}定义重载的构造函数后,声明对象时就可以根据不同的参数来分别调用不同的构造函数。应用重载的构造函数求圆柱体的体积:#include<iostream>#definePI3.1415926usingnamespacestd;classCVolume{intr,h;public:CVolume();CVolume(int,int);doublevolume(void){return(PI*r*r*h);}};
CVolume::CVolume(){r=10;h=20;}7374CVolume::CVolume(intm,intn){r=m;h=n;}
intmain(){CVolumeVole1(3,4);CVolumeVole2;cout<<"Vole1="<<Vole1.volume()<<endl;cout<<"Vole2="<<Vole2.volume()<<endl;return0;}在上面的例子中,Vole2被声明的时候没有参数,所以它被使用没有参数的构造函数进行初始化,也就是r和h分别被赋值为10和20。值得注意的是,如果在我们声明一个新的对象的时候不需要传入参数,则不要写括号“()”,如“CVolumeVole2;”这个语句就没有使用括号。1.8友元
类的主要特点是数据隐藏,即类的私有部分在该类的作用域之外是不可见的。
但有时候可能需要在类的外部访问类的私有部分。为此,C++提供了一种方法,允许类外部的函数或者类具有该类的私有部分的特权,它通过关键字friend把其它类或非成员函数声明为一个类的“友元”。
在类的内部,友元被作为该类的成员看待,并且对对象公用部分的访问没有任何限制。75友元函数的声明方式为:class类名称{typevars;…public:friend函数类型函数名称();//友元函数…}例如,下面定义了类friend_Class及友元函数friend_Function。class friend_Class //类friend_Class的定义{private: intnMemberData;public: //声明函数Friend_Function为类Friend_Class的友元
friendvoidfriend_function(friend_Classclass_member,intx);};
77//下面是友元函数friend_function的函数体,它要在类的外部定义voidfriend_function(friend_Classclass_member,intx){class_member.nMemberData=x;//通过友元函数访问类的私有成员}上面代码中函数friend_function并不是类friend_Class的成员函数,而是一个普通的函数,不过由于它在类friend_Class的定义中被声明为友元函数,因此,函数friend_function可以访问类friend_Class的私有部分友元函数的应用:#include<iostream>usingnamespacestd;classCRectangle{intwidth,height;public:voidset_values(int,int);intarea(void){return(width*height);}
friendCRectangleduplicate(CRectangle);};voidCRectangle::set_values(inta,intb){width=a;height=b;}CRectangleduplicate(CRectanglerectparam){CRectanglerectres;rectres.width=rectparam.width*2;rectres.height=rectparam.height*2;return(rectres);}intmain(){CRectanglerect,rectb;rect.set_values(2,3);rectb=duplicate(rect);cout<<rectb.area();return0;}8.2.8友元类就像我们可以定义一个friend函数,我们也可以定义一个class是另一个的friend,以便允许第二个class访问第一个class的protected和private成员。80//#include"stdafx.h"#include<iostream>usingnamespacestd;classCSquare;classCRectangle{intwidth,height;public:intarea(void){return(width*height);}voidconvert(CSquarea);};classCSquare{private:intside;public:voidset_side(inta){side=a;}friendclassCRectangle;};81这里声明了CRectangle是CSquare的friend,因此CRectangle可以访问CSquare的protected和private成员,更具体地说,可以访问CSquare::side,它定义了正方形的边长。voidCRectangle::convert(CSquarea){width=a.side;height=a.side;}intmain(){CSquaresqr;CRectanglerect;sqr.set_side(4);rect.convert(sqr);cout<<rect.area();return0;}类的友元可以是一个函数,也可以是一个类。例如,下面的程序中将整个教师类看成是学生类的友元:classStudent;classTeacher{public:voidassignGrades(Student&s);voidadjustHours(Student&s);protected:intNoOfStudent;Student*pList[100];};classStudent{public:
friendclassTeacher;//友类protected:intsemesterHours;floatgpa;};
Teacher是友元类,它必须在它被定义以前声明,因此,类Teacher的定义在类Student之前。一旦一个类被声明为另一个类的友元之后,该类的每一个成员函数都是另一个类的友元函数。一个类的友元的声明既可以在该类定义的公用部分声明,也可以在类的私有部分声明。两个类还可以相互定义为对方的友元,当两个类的联系较紧密时,把它们定义为相互的友元就更为有意义了。1.9类的指针
指针大家都很熟悉,在C++中,和其他数据类型一样,程序中也可以定义指向类对象的指针,类一旦被定义就成为一种有效的数据类型,只需要用类的名字作为指针的类型就可以了。在定义了类的指针后,还必须为其分配内存才能使用,类对象的指针定义及分配内存空间的一般格式为:类名*指针名=new类名;例如:CMy_class*p=newCMy_class;
这里定义的指针p就是一个指向classCMy_class类型的对象的指针。如果要想直接引用一个由指针指向的对象中的成员,同样可以使用操作符“->”。84这里是一个例子,显示了几种可能出现的情况:#include<iostream>#definePI3.1415926usingnamespacestd;classCVolume{intr,h;public:voidparam(int,int);doublevolume(void){return(PI*r*r*h);}};
voidCVolume::param(intm,intn){r=m;h=n;}
85intmain(){CVolumea,*b,*c;CVolume*d=newCVolume[2];b=newCVolume;c=&a;a.param(1,2);b->param(3,4);d->param(5,6);d[1].param(7,8);cout<<"avolume:"<<a.volume()<<endl;cout<<"*bvolume:"<<b->volume()<<endl;cout<<"*cvolume:"<<c->volume()<<endl;cout<<"d[0]volume:"<<d[0].volume()<<endl;cout<<"d[1]volume:"<<d[1].volume()<<endl;return0;}
上述代码中定义了CVolume类的指针b、c和d,然后d的指针指向了CVolume对象的首地址,b指针指向了CVolume类的对象,然后分别调用CVolume类的成员函数volume求体积。avolume:6.28319*bvolume:113.097*cvolume:6.28319d[0]volume:471.239d[1]volume:1231.5大家再来看看下面的例子,这里定义了类class1以及指向class1对象的指针p。#include<iostream>usingnamespacestd;classClass1 //定义类Class1{intValue; //定义类的私有成员public:Class1(intVal) //类Class1的构造函数{ Value=Val; //对成员变量初始化}intGetValue(void) //类Class1的成员函数{returnValue; //获取类对象的成员变量的值}};intmain() //主函数{Class1Object1(888),*p; //定义类Class1的对象和一个对象指针p=&Object1; //使对象指针指向对象Object1cout<<"ThevalueofObject1is:"<<p->GetValue()<<endl; //用对象指针调用成员函数return0;}87代码中,由于“p=&Object1;”的执行,使得指针p指向了Object1(),然后就把888的值传给了构造函数class1(),对构造函数中的Value进行了初始化,其值就是888;然后通过“p->GetValue()”的调用,返回了Value的值,this指针this指针是指向一个类的对象的地址。this是一种隐含指针,它隐含于每个类的成员函数之中,也就是说,每个成员函数都有一个this指针变量,this指针指向该成员函数所属的类的对象。当定义一个类的对象时,该对象的成员均含有由系统自动产生的指向当前对象的this指针。成员函数访问类中成员变量的格式可以写成: this->成员变量88当一个对象调用成员函数时,该成员函数的this指针便指向这个对象。若不同的对象调用同一个成员函数,C++编译器将根据成员函数的this指针所指向的不同对象类确定应引用哪一个对象的数据成员。也就是说,每个对象都有一个地址,而this指针所指的就是这个地址。89和其他数据类型一样,程序中也可以定义指向类对象的指针,在定义了类的指针后,还必须为其分配内存才能使用,类对象的指针定义及分配内存空间的一般格式为:
类名*指针名=new类名;例如,下面的语句定义类Student的对象指针并为其分配内存。
Student*Student1=newStudent;
当通过类对象的指针访问类的成员时,通常可以使用运算符“->”,例如,下面的程序中,主函数通过指向类对象的指针调用类的成员函数。#include<iostream>usingnamespacestd;classCMy_class{public:inttest(CMy_class¶m);};90intCMy_class::test(CMy_class¶m){if(¶m==this) return1;else return0;}
intmain(){CMy_classa;CMy_class*b=&a;if(b->test(a))
温馨提示
- 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
- 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
- 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
- 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
- 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
- 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
- 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。
最新文档
- 嘉善县招聘协管员面试题及答案
- 劳保协管员面试题及答案解析(2025版)
- 2025国家公务员国家税务总局富顺县税务局面试试题及答案
- 2025年东光辅警招聘真题及答案
- 定西市陇西县招聘城镇公益性岗位工作人员考试真题2024
- 2025广西柳州市林业科学研究所招聘编外聘用人员1人备考核心题库及答案解析
- 北京市公安局辅警招聘245人考试核心试题及答案解析
- 北京市水利规划设计研究院2026年校园招聘备考题库及一套参考答案详解
- 2025年智能机器人手术系统发展报告
- 2025年中国科学院水土保持科学与工程学院招聘备考题库及参考答案详解
- 沟槽施工安全培训课件
- 铸牢中华民族共同体课件
- 送餐车采购方案范本
- 110kv高压配电网设计
- 2025贵州盘江煤电集团医院招聘68人笔试参考题库附答案解析
- 建筑工程土建施工技术方案
- 白天和夜间的消防应急预案(3篇)
- 液流电池原理讲解
- 盐酸羟考酮注射液用法
- 乳蛾中医护理
- 银保监会健康险政策解读
评论
0/150
提交评论