1、第一讲 C+基础知识,成都理工大学地球科学学院测绘工程系测绘工程专业,测量程序设计,主讲教师: 杨容浩,联系电话: 13683470591,电子邮箱: ,实例:小型公司的人员信息管理系统,问题的提出,某小型公司,主要有四类人员:经理、兼职技术人员、销售经理和兼职推销员。现在,需要存储这些人员的姓名、编号、级别、当月薪水,计算月薪总额并显示全部信息。人员编号基数为1000,每输入一个人员信息编号顺序加1;程序要有对所有人员提升级别的功能;(所有人员的初始级别均为1级,然后进行升级,经理升为4级,兼职技术人员和销售经理升为3级,推销员仍为1级)月薪计算办法是:经理拿固定月薪8000元;兼职技术人员
2、按每小时100元领取月薪;兼职推销员的月薪按该推销员当月销售额的4%提成;销售经理既拿固定月薪也领取销售提成,固定月薪为5000元,销售提成为所管辖部门当月销售总额的5。,解决方案一,特点: 数据和处理数据的过程分离为相互独立的实体,当数据结构改变时,所有相关的处理过程都要进行相应的修改,每一种相对于老问题的新方法都要带来额外的开销,程序的可重用性差。,解决方案二,相关概念,对象:,系统中用来描述客观事物的一个实体,它是用来构成系统的一个基本单位,由一组属性和一组行为构成。,属性是用来描述对象静态特征的数据项,行为是用来描述对象动态特征的操作序列。,类:,具有相同属性和服务的一组对象的集合。内
3、部包含属性和行为两个主要部分。一个属于某类的对象称为该类的一个实例。,类的声明:,class 类名称 public:外部接口 protected:保护型成员 private:私有成员 对象1,对象2;,访问控制,公有类型:(public),在程序的任何地方都可以被访问。类与外部进行数据交换的接口。,私有类型:(private),只能被类的成员函数和其友元访问。,保护类型:(protected),派生类和成员函数可以访问,外部不能访问。,设置访问控制的作用:,1.错误局部化易于纠错; 2.修改局部化易于修改; 3.易于学习; 4.信息隐藏。,class employee protected:ch
4、ar name20; /姓名int individualEmpNo; /个人编号int grade; /级别float accumPay; /月薪总额static int employeeNo; /本公司职员编号目前最大值 public:employee(); /构造函数employee(); /析构函数void pay(); /计算月薪函数void promote(int); /升级函数void SetName(char *); /设置姓名函数char * GetName(); /提取姓名函数int GetindividualEmpNo(); /提取编号函数int Getgrade(); /
5、提取级别函数float GetaccumPay(); /提取月薪函数 ;,构造函数:,具有与类名相同的名字,其作用是在对象被创建时利用特定的值构造对象,将对象初始化为一个特定的状态,使此对象具有区别于彼对象的特征。完成一个从一般到具体的过程,在对象被创建的时候由系统自动调用。,构造函数不能指定返回类型,甚至void也不行;构造函数的数目没有限制,只要每个构造函数的参数表是惟一的即可;只有当没有构造函数或声明了缺省构造函数时,我们才能不指定实参集来定义类对象。一旦一个类声明了一个或者多个构造函数,类对象就不能被定义为不调用任何构造函数的实例。,缺省构造函数:,指不需要用户指定实参就能够被调用的构
6、造函数,这并不意味着它不能接受实参,只意味着构造函数的每个参数都有一个缺省值与之关联。,拷贝构造函数:,其形参是本类的对象的引用。其作用是使用一个已经存在的对象去初始化一个新的同类的对象。,如果程序员没有定义类的拷贝构造函数,系统就会自动生成一个默认函数,这个默认拷贝构造函数的功能是把初始值对象的每个数据成员的值都复制到新建立的对象中。,拷贝构造函数被调用的情况,当用类的一个对象去初始化该类的另一个对象时;,int main(void) point A(1,2);point B(A); /用对象A初始化对象B,拷贝构造函数被调用return 0; ,如果函数的形参是类的对象,调用函数时,进行形
7、参和实参结合时;,void f(point p) coutp.GetX()endl; ,int main() point A(1,2);f(A); /函数的形参为类的对象,当调用return 0; /函数时,拷贝构造函数被调用 ,如果函数的返回值是类的对象,函数执行完成返回调用者时。,point g() point A(1,2);return A;/函数的返回值是类对象,返 /回函数值时,调用拷贝构造函数 ,int main() point B;B=g(); return 0; ,继承与派生,继承,新的类从已有类那里得到已有的特性。,派生,从已有类产生新类的过程。,派生类的声明,class 派
8、生类名:继承方式 基类名1,继承方式 基类名2,继承方式 基类名n 派生类成员声明; ,通过类的派生可以建立具有共同关键特征的对象家族,从而实现代码的重用。,访问控制,当类的继承方式为公有继承时,基类的公有和保护成 员的访问属性在派生类中不变,而基类的私有成员不可访问;当类的继承方式为私有继承时,基类中的公有成员和保护成员都以私有成员身份出现在派生类中,而基类 的私有成员在派生类中不可访问;保护继承中,基类的公有和保护成员都以保护成员的身份出现在派生类中,而基类的私有成员不可访问。,派生类的构造函数,一般语法,派生类名:派生类名(参数总表):基类名1(参数表1),基类名n (参数表n),内嵌对
9、象名1(内嵌对象参数表1),内嵌对象名m(内嵌对象参数表m)派生类新增成员的初始化语句;,当一个类同时有多个基类时,对于所有需要给予参数进行初始化的基类,都要显式给出基类名和参数表;对于使用默认构造函数的基类,可以不给出类名;如果基类定义了带有形参表的构造函数时,派生类就应当定义构造函数;基类的构造函数的调用顺序是按照声明派生类时基类的排列顺序来进行,而内嵌成员对象构造函数的调用顺序则是按照对象在派生类中声明语句出现的先后顺序来进行。,实 例,# include class B1 /基类B1,构造函数有参数 public:B1(int i)cout“constructing B1”iendl;
10、 ; class B2 public: /基类B2,构造函数有参数B2(int j)cout “constructing B2”jendl; ; class B3 /基类B3,构造函数没有参数 public:B3()cout “constructing B3 *”endl; ; Class C:public B2,public B1,public B3 /派生新类C /注意基类名的顺序 public: /派生类的公有成员 C(int a,int b,int c,int d):B1(a),memberB2(d),memberB1(c),B2(b) /注意基类名的个数与顺序,注意成员对象名的个数与
11、顺序 Private: /派生类的私有对象成员 B1 memberB1; B2 memberB2; B3 memberB3; ; void main() C obj(1,2,3,4); ,派生类的析构函数,派生类的析构函数的功能是在该类对象消亡之前进行一些必要的清理工作。,作用域分辨,作用域分辨符(:):用来限定要访问的成员所在的类的名称,一般的使用形式为,基类名:成员名;/数据成员 基类名:成员名(参数表);/函数成员,对于在不同的作用域声明的标识符,可见性原则是:,如果存在两个或多个具有包含关系的作用域,外层声明的标识符如果在内层没有声明同名标识符,那么它在内层仍可见;如果内层声明了同名标
12、识符,则外层标识符在内层不可见,这时称内层变量覆盖了外层同名变量(同名覆盖)。,实 例,# include class B1 /声明基类B1 public: /外部接口int nV;void fun()cout“Member of B1“endl; ; class B2 /声明基类B2 public: /外部接口int nV;void fun()cout“Member of B2“endl; ; class D1:public B1,public B2 /声明派生新类D1 public:int nV; /同名数据成员void fun()cout“Member of D1“endl; /同名函数
13、成员 ; void main() D1 d1;d1.nV=1; /对象名.成员名标识d1.fun(); /访问D1类成员d1.B1:nV=2; /作用域分辨符标识d1.B1:fun(); /访问B1基类成员d1.B2:nV=3; /作用域分辨符标识d1.B2:fun(); /访问B2基类成员 ,Member of D1 Member of B1 Member of B2,实 例,# include class B0 /声明基类B0 public: /外部接口int nV;void fun()cout“Member of B0“endl; ; class B1:public B0 /派生类B1声
14、明 public: /新增外部接口int nV1; ; class B2:public B0 /派生类B2声明 public: /新增外部接口int nV2; ; class D1:public B1,public B2 /派生新类D1声明 public:int nVd; void fun()cout“Member of D1“endl; ; void main() D1 d1; /声明D1类对象d1d1.B1:nV=2; /使用直接基类d1.B1:fun();d1.B2:nV=3; /使用直接基类d1.B2:fun();d1.fun();d1.D1:fun(); ,Member of B0
15、Member of B0 Member of D1 Member of D1,虚基类,特点,从不同路径继承过来的同名数据成员在内存中就只有一个拷贝,同一个函数名也只有一个映射。,虚基类的声明,class 派生类名:virtual 继承方式 基类名,在多继承情况下,虚基类关键字的作用范围和继承方式关键字相同,只对紧跟其后的基类起作用。声明了虚基类之后,虚基类的成员在进一步派生过程中和派生类一起维护同一个内存数据拷贝。,如果虚基类定义有非默认形式(即带形参的)构造函数,并且没有定义默认形式的构造函数,则在整个继承结构中,直接或间接继承虚基类的所有派生类,都必须在构造函数的成员初始化列表中列出对虚基
16、类的初始化。,实 例,# include class B0 /声明基类B0 public: /外部接口B0(int n)nV=n;cout“c0“endl;int nV;void fun()cout“Member of B0“endl; ; class B1:virtual public B0 /B0为虚基类,派生B1类 public: /新增外部接口B1(int a):B0(a)cout“c1“endl;int nV1; ; class B2:virtual public B0 /B0为虚基类,派生B2类 public: /新增外部接口B2(int a):B0(a)cout“c2“endl;
17、int nV2; ; class D1:public B1,public B2 /派生类D1声明 public:D1(int a):B0(a),B1(a),B2(a)nV=0;int nV1; void fun()cout“Member of D1“endl; ; void main() D1 d1(2); /声明D1类对象d1 coutd1.nVendl;coutd1.B0:nVendl;d1.fun();d1.B2:fun(); ,c0 c1 c2 0 0 Member of D1 Member of B0,class technician:public employee /兼职技术人员类
18、 private:float hourlyRate; /每小时酬金int workHours; /当月工作时数 public:technician(); /构造函数void SetworkHours(int wh); /设置工作时数void pay(); /计算月薪函数 ; class salesman:virtual public employee /兼职推销员类 protected:float CommRate; /按销售额提取酬金的百分比float sales; /当月销售额 public:salesman(); /构造函数void Setsales(float sl); /设置销售额v
19、oid pay(); /计算月薪函数 ; class manager:virtual public employee /经理类 protected:float monthlyPay; /固定月薪数 public:manager(); /构造函数void pay(); /计算月薪函数 ; class salesmanager:public manager,public salesman /销售经理类 public:salesmanager(); /构造函数void pay(); /计算月薪函数 ;,人员信息管理系统(续),类的实现,基本形式,返回值类型 类名:函数成员名(参数表) 函数体 ,带默
20、认值的成员函数,void Clock:SetTime(int NewH=0,int NewM=0,int NewS=0) Hour=NewH;Minute=NewM;Second=NewS; ,默认形参值必须按从右向左的顺序定义,在有默认值的形参右边,不能出现无默认值的形参;在相同的作用域内,默认形参值的说明应保持惟一,但如果在不同的作用域内,允许说明不同的默认形参。,内 联 成员函数,class Clock public:void SetTime(int NewH,int NewM,int NewS);void ShowTime()coutHour“:“Minute“:“Secondendl
21、; private:int Hour,Minute,Second; ;,inline void Clock:ShowTime()coutHour“:“Minute“:“Secondendl;,1. 隐式声明,2. 显式声明,内联函数不是在调用时发生控制转移,而是在编译时将函数体嵌入 在每一个调用语句处;内联函数体内一般不能有循环语句和switch语句;内联函数的定义必须出现在第一次被调用之前;对内联函数不能进行异常接口声明。,存在不足:,方案二实现,基类的成员函数pay()和displayStatus()的函数体均为空,在实现部分仍要写出函数体,显得冗余;在main()函数中,建立了四个不同类
22、的对象,对它们进行了类似的操作,但是却重复写了四遍类似的语句,程序不够简洁;无法利用一个循环结构依次处理同一类族中的不同类的对象。,解决方案三,manager(); void pay(); void displayStatus(); void promote(int);,赋值兼容规则,在需要基类对象的任何地方都可以使用公有派生类的对象来代替。,替代内容,派生类的对象可以赋值给基类对象;派生类的对象可以初始化基类的引用;派生类对象的地址可以赋给指向基类的指针。,在替代之后,派生类对象就可以作为基类的对象使用,但只能使用从基类继承的成员。,根据赋值兼容规则,我们可以在基类出现的场合使用派生类进行替
23、代,但是替代之后派生类仅仅发挥出基类的作用。,#include class B0 /基类B0声明 public:void display()coutdisplay(); /“对象指针-成员名” void main() /主函数 B0 b0; /声明B0类对象B1 b1; /声明B1类对象D1 d1; /声明D1类对象B0 *p; /声明B0类指针p=,B0:display() B0:display() B0:display(),多态性,指发出同样的消息被不同类型的对象接收时有可能导致完全不同的行为。,消息:指对类的成员函数的调用; 不同的行为:指不同的实现,也就是调用不同的函数。,专用多态,通
24、用多态,重载多态,强制多态,包含多态,参数多态,将一个变元的类型加以变化,以符合一个函数或者操作的要求。,类族中定义于不同类中的同名成员函数的多态行为,主要是通过虚函数来实现。,运算符重载,class complex /复数类声明 public: /外部接口complex(double r=0.0,double i=0.0)real=r;imag=i; /构造函数void display(); /输出复数 private: /私有数据成员double real; /复数实部double imag;/复数虚部 ;,complex a(10,20),b(5,8),c;,c=a+b;,?,运算符重载
25、是对已有的运算符赋予多重含义,使同一个运算符作用于不同类型的数据导致不同类型的行为。实质为函数重载,运算符重载的规则,C+中的运算符除了少数几个(类属关系运算符“.”、成员指针运算符“*”、作用域运算符“:”、sizeof运算符和三目运算符“?:”)之外,全部可以重载,而且只能重载C+中已有的运算符;重载之后运算符的优先级和结合性都不会改变;运算符重载是针对新类型数据的实际需要,对原有运算符进行适当的改造。一般来讲,重载的功能应当与原有功能相类似,不能改变原运算符的操作对象个数,同时至少要有一个操作对象是自定义类型。,运算符的重载形式有两种:重载为类的成员函数和重载为类的友元函数。,一 般 语
26、 法,重载为类的成员函数:,函数类型 operator 运算符(形参表) 函数体; ,重载为类的友元函数:,friend 函数类型 operator 运算符(形参表) 函数体; ,#include using namespace std; class complex /复数类声明 public: /外部接口complex(double r=0.0,double i=0.0)real=r;imag=i;/构造函数complex operator + (complex c2); /+重载为成员函数complex operator - (complex c2); /-重载为成员函数void disp
27、lay(); /输出复数 private: /私有数据成员double real; /复数实部double imag; /复数虚部 ;,complex complex:operator +(complex c2) /重载函数实现complex c;c.real=c2.real+real;c.imag=c2.imag+imag;return complex(c.real,c.imag); complex complex:operator -(complex c2) /重载函数实现complex c;c.real=real-c2.real;c.imag=imag-c2.imag;return co
28、mplex(c.real,c.imag); ,void complex:display() cout“(“real“,“imag“)“endl; void main() /主函数 complex c1(5,4),c2(2,10),c3; /声明复数类的对象 cout“c1=“; c1.display(); cout“c2=“; c2.display(); c3=c1-c2; /使用重载运算符完成复数减法 cout“c3=c1-c2=“; c3.display(); c3=c1+c2; /使用重载运算符完成复数加法 cout“c3=c1+c2=“; c3.display(); ,c1=(5,4)
29、 c2=(2,10) c3=c1-c2=(3,-6) c3=c1+c2=(7,14),当运算符重载为成员函数时,函数的参数个数比原来的操作个数要少一个(后置“+”、“-”除外);如果是双目运算符,一个操作数是对象本身的数据,由this指针指出,另一个操作数则需要通过运算符重载函数的参数表来传递;如果是单目运算符,操作数由对象的this指针给出,就不再需要任何参数;对于后置运算符“+”和“-”,如果重载为类的成员函数,这时要带有一个整型(int)形参。,#include using namespace std; class complex /复数类声明 public: /外部接口complex(
30、double r=0.0,double i=0.0) real=r; imag=i; /构造函数friend complex operator + (complex c1,complex c2);/运算符+重载为友元函数friend complex operator - (complex c1,complex c2); /运算符-重载为友元函数void display(); /显示复数的值 private: /私有数据成员double real;double imag; ;,complex operator +(complex c1,complex c2) /运算符重载友元函数实现 retur
31、n complex(c2.real+c1.real, c2.imag+c1.imag); complex operator -(complex c1,complex c2) /运算符重载友元函数实现 return complex(c1.real-c2.real, c1.imag-c2.imag); ,虚 函 数,#include using namespace std; class B0 /基类B0声明 public: /外部接口void display() /成员函数coutdisplay(); void main() /主函数 B0 b0, *p; /声明基类对象和指针B1 b1; /声明
32、派生类对象D1 d1; /声明派生类对象p= /调用派生类D1函数成员 ,virtual void display() /虚成员函数cout“B0:display()“endl;,B0:display() B0:display() B0:display(),B0:display() B1:display() D1:display(),虚 函 数,一般虚函数成员的定义语法,virtual 函数类型 函数名(形参表) 函数体 ,虚函数声明只能出现在类声明中的函数原型声明中,而不能在成员函数体实现的时候。,运行过程中的多态需要满足的三个条件:,类之间应满足赋值兼容规则;要声明虚函数;要由成员函数来调
33、用或者是通过指针、引用来访问虚函数。,派生类中虚函数的判别:,该函数是否与基类的虚函数有相同的名称;该函数是否与基类的的虚函数有相同的参数个数及相同的对应参数类型;该函数是否与基类的的虚函数有相同的返回值或者满足赋值兼容规则的指针、引用型的返回值。,如果满足上述条件,就被自动确定为虚函数。(本质:不是重载声明而是覆盖),在C+中,不能声明虚构造函数,但是可以声明虚析构函数。,virtual 类名();,抽 象 类,抽象类是一种特殊的类,它为一族类提供统一的操作界面;抽象类处于类层次的上层,一个抽象类自身无法实例化,而只能通过继承机制,生成抽象类的非抽象派生类,然后再实例化;带有纯虚函数的类是抽
34、象类。,纯虚函数:在基类中说明的虚函数,它在该基类中没有定义具体的操作内容,要求各派生类根据实际需要定义自己的版本。,virtual 函数类型 函数名(参数表)=0;,声明为纯虚函数之后,基类中就不再给出函数的实现部分。,#include using namespace std; class B0 /抽象基类B0声明 public: /外部接口 virtual void display( )=0; /纯虚函数成员 ; class B1: public B0 /公有派生 public: void display()coutdisplay(); void main() /主函数 B0 *p; /声
35、明抽象基类指针 B1 b1; /声明派生类对象 D1 d1; /声明派生类对象 p= /调用派生类D1函数成员 ,运行结果: B1:display() D1:display(),class employee protected:char name20; /姓名int individualEmpNo; /个人编号int grade; /级别float accumPay; /月薪总额static int employeeNo; /本公司职员编号目前最大值 public:employee(); /构造函数employee(); /析构函数virtual void pay()=0; /计算月薪函数(纯虚函数)virtual void promote(int increment=0); /升级函数(虚函数)void SetName(char *); /设置姓名函数char * GetName(); /提取姓名函数int GetindividualEmpNo(); /提取编号函数int Getgrade(); /提取级别函数float GetaccumPay(); /提取月薪函数 ;,