预备知识——C++、类与对象 C++要素 类和对象 模版 类的继承.

Slides:



Advertisements
Similar presentations
第九讲 类与对象 (I)面向对象基础.
Advertisements

第九章 排序 插入排序 交换排序 选择排序 归并排序 基数排序.
7.2 访问控制 —— 公有继承 公有继承练习 //Point.h #ifndef _POINT_H #define _POINT_H class Point { //基类Point类的定义 public: //公有函数成员 void initPoint(float x = 0, float.
内容提要 代码重用 类的继承 多态 抽象类 多重继承 虚拟继承. 常宝宝 北京大学计算机科学与技术系
第14章 c++中的代码重用.
C++语言程序设计 第七章 继承与派生 清华大学 郑 莉.
C++ 面对对象程序设计 授课老师:.
C++中的声音处理 在传统Turbo C环境中,如果想用C语言控制电脑发声,可以用Sound函数。在VC6.6环境中如果想控制电脑发声则采用Beep函数。原型为: Beep(频率,持续时间) , 单位毫秒 暂停程序执行使用Sleep函数 Sleep(持续时间), 单位毫秒 引用这两个函数时,必须包含头文件
第6章 多态性与虚函数.
Using C++ The Weird Way Something about c++11 & OOP tricks
4.3函数 4.3.1函数的概念及定义 1、函数的概念: 可以被其它程序调用具有 特定功能的一段相对独立的 程序(模块),称函数。
C++语言程序设计 第四章 类与对象 成都信息工程学院计算机系.
C++程序设计 第二讲 清华大学软件学院.
EBNF 请用扩展的 BNF 描述 C语言里语句的结构; 请用扩展的 BNF 描述 C++语言里类声明的结构;
刘胥影 东南大学计算机学院 面向对象程序设计1 2011~2012第3学期 刘胥影 东南大学计算机学院.
第11章 运算符重载 什么是运算符重载 运算符重载的方法 几个特殊的运算符的重载 自定义类型转换运算符 运算符重载实例.
第七章 搜索结构 静态搜索结构 二叉搜索树 AVL树.
·线性表的定义及ADT ·线性表的顺序存储结构 ·线性表的链接存储结构 · 单向循环链表 · 双链表、双向循环链表 · 一元多项式的加法
授课老师:龚涛 信息科学与技术学院 2018年3月 教材: 《Visual C++程序员成长攻略》 《C++ Builder程序员成长攻略》
Object-Oriented Programming in C++ 第一章 C++的初步知识
程序设计期末复习 黎金宁
第三章 C++中的C 面向对象程序设计(C++).
第11讲 类的继承 1. 类的继承的概念 2. 类的单继承机制 3. 单继承中的构造函数和析构函数.
第12讲 多继承与虚基类 多继承 虚基类.
辅导课程六.
第9章 类和对象(一) 9.1 面向对象的基本概念 9.2 类与对象的声明和定义 9.3 成员函数 9.4 对象的访问 9.5 对象的存储.
第一单元 初识C程序与C程序开发平台搭建 ---观其大略
第二章 C++对C 在非面向对象方面的改进 更简洁,更安全.
类类型 C++支持的内置类型和操作,如 int i=10; i=i%6; i=i+4;
用event class 从input的root文件中,由DmpDataBuffer::ReadObject读取数据的问题
第七章 操作符重载 胡昊 南京大学计算机系软件所.
C++语言程序设计 C++语言程序设计 第七章 类与对象 第十一组 C++语言程序设计.
简单介绍 用C++实现简单的模板数据结构 ArrayList(数组, 类似std::vector)
第11讲 类的继承 1. 类的继承的概念 2. 类的单继承机制 3. 单继承中的构造函数和析构函数.
$9 泛型基础.
C++复习3 ----类的继承与派生.
第14讲 运算符重载 运算符重载的概念、方法和规则 运算符重载作为类的成员函数 运算符重载作为类的友元函数 特殊运算符的重载 类型转换函数.
C#面向对象程序设计 $6 深入理解类.
第13讲 多态 友员函数 多态性与虚函数 纯虚函数和抽象类.
C++语言程序设计 C++语言程序设计 第九章 类的特殊成员 第十一组 C++语言程序设计.
补课.
第10讲 构造函数和析构函数 构造函数 析构函数 This 指针.
第三章 数据抽象.
C++语言程序设计 C++语言程序设计 第九章 类的特殊成员 第十一组 C++语言程序设计.
本节内容 类成员的访问控制 视频提供:昆山爱达人信息技术有限公司 官网地址: 联系QQ: QQ交流群 : 联系电话:
C++语言程序设计 C++语言程序设计 第十章 多态 第十一组 C++语言程序设计.
第九节 赋值运算符和赋值表达式.
C++程序设计基础 主讲人:谢昕 华东交通大学信息工程学院 第十~十二讲 多态性和虚函数 2005年春季学期.
多层循环 Private Sub Command1_Click() Dim i As Integer, j As Integer
C++语言程序设计 C++语言程序设计 第六章 指针和引用 第十一组 C++语言程序设计.
C++语言程序设计 C++语言程序设计 第八章 继承 C++语言程序设计.
第1章 C++面向对象程序设计要点 1.1 函数和函数参数 1.2 输入输出   1.3 类 1.4 抽象类型和模板.
第 9 章 建構函式與解構函式.
辅导课程十五.
授课老师:龚涛 信息科学与技术学院 2016年3月 教材:《Visual C++程序员成长攻略》 《C++ Builder程序员成长攻略》
#include <iostream.h>
第7章 模板 陈哲 副教授 南京航空航天大学 计算机科学与技术学院.
C++语言程序设计 C++语言程序设计 第十一章 异常处理 C++语言程序设计.
C++语言程序设计 C++语言程序设计 第八章 继承 C++语言程序设计.
C++语言程序设计 C++语言程序设计 第一章 C++语言概述 第十一组 C++语言程序设计.
本节内容 动态链接库 视频提供:昆山爱达人信息技术有限公司 官网地址: 联系QQ: QQ交流群 : 联系电话:
C++语言程序设计 C++语言程序设计 第九章 类的特殊成员 第十一组 C++语言程序设计.
C++语言程序设计 C++语言程序设计 第九章 类的特殊成员 第十一组 C++语言程序设计.
第三章 高级函数特性.
C++语言程序设计 C++语言程序设计 第十章 多态 第十一组 C++语言程序设计.
C++语言程序设计(第4版) 第七章 继承与派生 数学与统计科学学院 胡凤珠.
第9章 C++程序设计初步 9.1 C++的特点 9.2 最简单的C++程序 9.3 C++的输入输出 9.4 函数的重载
第六章 复合数据类型 指针的声明与使用 数组的声明与使用 指针与数组的相互引用 字符串及相关库函数 new与delete
C++语言程序设计 C++语言程序设计 第九章 类的特殊成员 第十一组 C++语言程序设计.
第十二章 C与C C转入C++时不需改变的内容 12.2 C转入C++的一些与类无关的 新特性
Presentation transcript:

预备知识——C++、类与对象 C++要素 类和对象 模版 类的继承

C复习 数据类型:在C语言中:基本类型和构造类型 基本类型:整型、浮点型、字符型、双精度型 构造类型:数组、结构、联合、指针、枚举型 数据对象: int n=3; int a[3]; a[0]=-1; //整型数据对象 char ch=‘A’;//字符类型数据对象 对象指针: int *p=&n, *q=a;

C复习 标识符 表达式 语句:赋值句、条件句、循环句、函数调用(输入输出) 函数:返回值类型,函数名,参数列表 递归:直接递归、间接递归

C++介绍 一. C++要素 (Borland C ,VC)

1. 文件扩展名 *.cpp 注意:C++ 源文件扩展名用 .cpp C 源文件扩展名用 .c

2.注释 /* ……… */ 段注释 以 /* 开始 到 */ 结束 // 行注释 到行末

4. 常量说明 const 例1:const int m=10; //定义m为常量 值 是 10 例2: const int a[ ]={1,3,5,7,9}; // 定义a是常量数组 例3. int * const p; //常指针p指向一个固定的地址 例4. const int * q; //指针q 指向常量

例 for(int i=0; i<5;i++) 其作用域从申明地起到文末或函数末 5. 变量申明 可以在任何地方申明一个变量 例 for(int i=0; i<5;i++) 其作用域从申明地起到文末或函数末 但不能 while(int i) i++;

6.函数缺省值参数 int f(int a, int b=0); 调用 f(5), 即调用 f(5,0); 缺省参数 要写在参数表的右边 int f(int a, int b=0,int c=1);

7.内联函数 inline int square(int x) {return x*x;} 内联函数先编译,效率高,速度快 但只能有四五个语句, 不能有循环语句,条件语句.

8. 函数重载 overload abs; int abs(int); float abs(float); 同名不同参数的函数可以重载 系统会自动选择调用

9. 引用操作符 & 定义函数的变量参数 例 int f(int & x) { int n=2*x++; return n;} 9. 引用操作符 & 定义函数的变量参数 例 int f(int & x) { int n=2*x++; return n;} x 为函数 f 的变量参数 调用实参的地址 调用后实参的值可以改变 函数需要两个以上返回值时,用变量参数

10. 动态函数 动态变量 new delete 例 int *p,*q; p = new int (3); q = new int [4]; 为p分配一个整形地址(2字节) *p==3 为 q 分配4个整形地址(8个连续字节) delete p; //撤销p的地址 delete [ ] q; //撤销q的多个连续地址

11.输入输出 #include “iostream.h” int a,b; char x,y; cout<<“ Enter x,y,a,b”<<endl; cin>>x>>y; \\从键盘为变量x,y输入数据 cin>>a>>b; \\从键盘为变量a,b输入数据 cout<<“ x=” <<x<<endl; cout<< y<<a<<b<<endl;

文件输入输出 include “fstream.h” include “stdlib.h” void main( ) { ifstream infile(“datafile1.doc”); if(!infile) { cerr<<“Cannot open datafile1.doc”<<endl; exit(1);} ofstream outfile(“datafile2.doc”); if(!outfile) { cerr<<“Cannot open datafile2.doc”<<endl; exit(1);} int n; string name; while(infile>>n) { infile>>name; outfile<<n<<“ ”<<name<<endl; } }

一. C++要素 (Borland C 3.1) 1. 文件扩展名 *.cpp 2.注释 /* ……… */ 段注释 // 行注释 到行末 2.注释 /* ……… */ 段注释 // 行注释 到行末 3. 续行 \ 常量说明 const 5. 变量申明 可以在任何地方申明一个变量 6.函数缺省值参数 int f(int a, int b=0);7.内联函数 8. 重载 overload 引用操作符 & 10. 动态函数 动态变量 new Delete 11输入输出

二.        类和对象

1.类的定义 class 类名称 { private: 数据成员; 成员函数; protected: 数据成员; public: 数据成员; }; //类定义结束 必须有分号“ ;”

class 是保留字, 作用与struct 相同 定义一个结构也叫类。 private(私有), 缺省 protected(保护) , public(公有) 都是访问限制 class 是保留字, 作用与struct 相同 定义一个结构也叫类。 private(私有), protected(保护) , public(公有) 都是访问限制

例 计数器类 存储于文件“count.h”中 class counter { private: //私有成员 unsigned int value; //数据成员 public: //公有成员 counter( ) { value=0;} //无参构造函数 counter(int x){ if(x>0)value=x; else value=0;}//有参构造函数 void increment( ){if(value<65535)value++;} void decrement( ){if(value>0)value--;} unsigned access_value( ){return value;} };

2. 对象的定义 object counter c1, c2; //语句1 counter c3(5); //语句2 (实例)。 对象定义时必须为数据成员赋初值即初始化。初始化由类中的构造函数自动完成。语句1 自动调用 counter 类中无参构造函数,使 c1.value=c2.value=0。 语句2定义对象c3, 自动调用有参构造函数使c3.value=5.

注意:不能使用c1.value为它赋值; 因为 value 在counter类中是私有成员不可见,只能用成员函数来调用。 对象不能直接调用私有成员,只能通过公有成员函数来调用私有成员 对象调用成员函数,叫发一个消息 为c1发消息: c1.increment( ); //计数器自动加1 c1.decrement( ); // 计数器自动减1

for( int i=1; i<=8;i++) {c1.increment( ); 例 计数器测试程序 #include “iostream.h” #include “count.h” void main( ) { counter c1,c2; for( int i=1; i<=8;i++) {c1.increment( ); cout<<“ \nc1=” <<c1.access_value( ); c2.increment( ); } cout<<“ c2=” <<c2.access_value( ); for( i=1; i<=5;i++) {c2.decrement( ); cout<<“ \nc2=” <<c2.access_value( ); c1.decrement( ); } cout<<“ c1=” <<c1.access_value( ); }

测试结果 c1=1 c1=2 c1=3 c1=4 c1=5 c1=6 c1=7 c1=8 c2=8 c2=7 c2=6 c2=5 c2=4 c2=3 c1=3

圆的类 class Circle { float radius; public: Circle(float r=0):radius(r){}//构造函数 float GetRadius( ); float CircleCircum( ); float CircleArea( ); };

成员函数类外定义 Circle:: Circle(float r) //构造函数 { radius = r; } float Circle:: GetRadius( ) { return radius;} float Circle:: CircleCircum( ) { return 2*3.14.6*radius; } float Circle:: CircleArea( ) { return 3.1416*radius*radius; }

圆类的测试 #include “iostream.h” #include “circle.h” void main( ) { Circle a(3), b(2); cout<<“Circum of Circle a = ” <<a.CircleCircum( )<<endl; cout<<“Area of Circle b = ” <<b.CircleArea( )<<endl; }

长方形类 class Rectangle { float x, y; public: Rectangle(float a=0, float b=0): x(a),y(b) { } float RecCircum( ){return 2*(x+y);} float RecArea( ){return x*y;} };

3. 对象指针和对象数组 a. 对象指针 如同定义一个对象一样,用类名可以申明一个对象指针。 例 counter *p,*q; 调用指针必须先分配内存,或指向一个变量的地址,否则出严重错误,甚至死机。

(1) p=new counter(3); 分配一个整形数据内存,这时系统自动调用有参 构造函数初始化*p的value=3; (2)q=new counter[3]; 分配三个连续整形数据内存,这时系统自动调用无参构造函数初始化 q,q+1, q+2的value都是0。 如果类中没有无参构造函数,语句(2)出错。

确定地址后的对象指针可以调用类中公有数 据及公有函数。 p→increment( ); (q+1) →decrement( ); (*p).decrement( ); (*p).decrement( )中的括号不能省略,因为 运算符 . 的优先级高于*; 对象指针用毕,要予以撤销,释放内存, delete p; delete[ ] q; 撤销后的指针可以再用,只要重新分配内存或指向一个内存。

b. 对象数组 如果类中有无参构造函数,可以定义对象数组 (3) counter c[3]; 这时系统自动调用无参构造函数初始化c[0],c[1],c[2]的value都是0。 如果类中没有无参构造函数,语句(3)出错。

4. 类的数据成员 a. 一个类中的数据成员不可以初始化, 例 class A { int value=0;\\出错 .......}; b. 另一个类的对象可以做本类的数据成员,但要先定义后作成员。 只先申明不行。本类对象不可以做自己的数据成员。 c. 指向本类或另一类对象的指针,或引用可以作本类的数据成员。 只要先申明就可以。

d. 公有成员和私有成员的先后次序可以交换。可以把私有成员写在类的前部,也可以写在后部。 e. 本类对象可以调用本类公有数据或公有函数,不能调用私有数据和私有成员。

例 class A; class B { A *p; //合法 A& q; //合法 B * r; //合法 A a; //出错 B b; //出错 };

长方形和圆的类 class RecCircle { Rectangle Rec; Circle Cir; public: RecCircle(float a, float b, float c): Rec(a,b), Cir(c){ } float Circum( ){return Rec.RecCircum( )+Cir.CircleCircum( );} float Area( ){ return Rec.RecArea( )+Cir.CircleArea( ); };

5. 类的 成员函数 成员函数也叫类所具有的方法或操作。 函数头叫方法的界面,也叫消息模式。 函数可以重载(要求参数不同)不必用overload。 可以类内申明类外定义,类内定义时不用inline即内联。 成员函数可以调用本类数据成员,无论公有或私有。 对象调用公有成员函数叫做发一个消息。

构造函数可以重载,带缺省参数时,要特别注意避免二义性。 构造函数一般都是公有函数。只有私有构造函数的类不能定义对象。 只有本类对象或本类成员函数才能调用成员函数。

6. This 指针 成员函数必须由对象调用,调用成员函数的对象可以看作一个隐含的参数,这是一个指针参数,用this表示。this 指针指向调用成员函数的对象。 例 Circle::CircleArea( ) {return 3.1416*radius*radius;} //其中 radius 实际意义是this→radius . Circle a(3.4); cout <<a.CircleArea( );//this指针指向a

this指针 counter::increament( ) {if(value<65535)value++;} //隐含参数this指针, this->value counter c; c.increament( ); //实际参数是c, this 指向c

7. 构造函数 与类名同名的成员函数叫构造函数,构造函数没有返回类型。构造函数的作用是自动为对象初始化。对象不能调用构造函数。 如果一个类中没有构造函数,系统会自动生成一个无参构造函数。如果一个类中有有参构造函数,系统就不再自动生成无参构造函数,这时,用类定义对象数组或对象指针都可能出错。

一个类中如果没有构造函数,系统会自动生成 一个无参构造函数,使所有的数据成员都置0。 一个类中如果有有参构造函数,系统就不再生 成无参构造函数。 例如 RecCirle类中,就没有无参构造函数。 这时要小心,下面一个语句会出错 RecCircle d; //出错 无法初始化 RecCircle s[3]; //出错 无法初始化 正确的定义形式是 RecCircle d(2.0, 3.5, 4);

8. 拷贝构造函数 构造函数的参数不能是本类对象,但可以是本类对象的引用, 这样的构造函数叫拷贝构造函数。

class Circle { float radius; public: Circle(float r=0):radius(r){}//构造函数 Circle(Circle & c){radius=c.radius;} // 拷贝构造函数 float GetRadius( ); float CircleCircum( ); float CircleArea( ); };

拷贝构造函数的作用: 1) 用已有对象定义新的对象。 Circle a(2); Circle b(a); 第二个语句就是利用拷贝构造函数定义赋数对象b, b的半径等于a的半径。

拷贝构造函数的作用: 2) 本类对象作函数的参数时,要用拷贝构造函数传值。 3) 一个函数的返回值是本类对象时,要用拷贝构造函数赋值。 Circle Fun(Circle t) { return Circl(2*t.radius);}

如果类中没有拷贝构造函数系统会自动生成一个。 如果类中有指针成员,就必须专门定义拷贝构造函数,否则可能出错。

例 class A { int *p; pubic: A( ){p= new int(0);} A(A&s){p=new int(*s.p); };

9. 析构函数 以~开头与类名同名的成员函数称为析构函数。析构函数无参无返回值。 例 class A {.... ~A( ); ....};

析构函数的作用: 当本类的一个对象用完后,系统自动调用析构函数,撤销内存。 这种情况常在一个函数中发生,一个对象在函数中作局部变元,函数用毕后,局部变元失效,析构函数就会自动起作用。 类中没有析构函数时系统会自动生成一个析构函数。 当类中含有指针对象时,应当定义一个析构函数,以免指针指向的内存挂起失。

class A { int *p; public: A( ){p=new int;} .... ~A( ){delete p;} ....}; 注意 类外对象指针用 new 分配的内存,不会调用析构函数,而要用delete释放。

例 坐标点point类 class Point { public: Point(int,int); //构造函数 Point(Point &);//拷贝构造函数 //~Point( ); 析构函数 int get_x( ); //取横坐标 int get_y( ); //取纵坐标 Point operator+(Point&); //重载+ Point operator *(int); //重载*

Point operator-( ); //一元函数-重载 Point& operator=(const Point&); //赋值函数 private: //私有数据 int x, y; friend istream& operator>>(istream&, Point&); //友元 输入函数重载>> friend ostream& operator<<(ostream&,Point&); //友元 输出函数重载<< };

Point::Point(int a, int b) {x=a; y=b;} Point::Point(Point& p) {x=p.get_x( ); y=p.get_y( );} int Point::get_x( ) {return x;} int Point::get_y( ) {return y;}

Point Point::operator+(Point &p) {x=x+p.x; y=y+p.y; return*this;} Point Point::operator *(int n) {x=n*x; y=n*y; return*this;} Point Point::operator-( ) {x=-x; y=-y; return *this;} Point& Point::operator=(const Point& p) { x=p.x; y=p.y; return *this;}

istream& operator>>(istream& istr, Point& p) { istr>>p.x>>p.y; return istr; } ostream& operator<<(ostream& ostr, Point& p) { ostr<<“(”<<p.x<<“,”<<p.y<<“)”<<endl; return ostr; }

除了少数几个操作符,如 “,” ,“::” ,“?:” ,“{ }”之外,其余都可以重载。 11. 运算符(操作符)重载 除了少数几个操作符,如 “,” ,“::” ,“?:” ,“{ }”之外,其余都可以重载。 二元运算在成员函数看来,只有一个参数,另一个是this指针。 例 Point Point::operator +(Point & c); 二元运算+在复数类中重载,变成只有一个参数。

Point a1(1,2) , a2(2,-1); a1=a1+a2; 表达式a1=a1+a2中对象a1调用运算+,实 际参数为a2,返回值还是 Point类型, 还可以再调用运算符+,因此表达式 a1+a2+a1有意义。

12. 赋值函数重载 在class Point 中重载赋值函数 class Point { .... Point& operator=(const Point& c) { x=c.x; y=c.y; return *this;} ...... };

赋值函数的作用是把一个对象的所有内容,赋予另一个对象. c1=c2; c1=c1+c2; 类中没有赋值函数时系统会自动生成一个赋值函数。当类中含有指针对象时,应当定义一个赋值函数,以保证值把内容赋值,而不是把地址赋值造成析构函数调用时出错。

例 class A { int *p; public: ..... A& operator=(A& a) { if (this=&a) return *this; else *p=*a.p;return *this;} };

13. 一元运算重载 一元运算作成员函数重载时变成零元函数。 Point Point :: operator -( ) {x=-x;y=-y;return return *this;} 调用写成 -c1 或 c1=-c2;

10. 友元

友元有权访问类内所有成员,不论公有,保护,还是私有成员。 友元没有this指针,必要时可以用类对象的引用作参数。 友元可以是一个类,这时友元类中所有成员函数都是友元。 友元不传递,不对称,不继承。 友元没有this指针,比成员函数多一个参数。

istream& operator>>(istream& istr, Point& p) { istr>>p.x>>p.y; return istr; } ostream& operator<<(ostream& ostr, Point& p) { ostr<<“(”<<p.x<<“,”<<p.y<<“)”<<endl; return ostr; } 友元不是类的成员,不用类名域 Point::operator

函数在类内申明类外定义 例 日期类保存于“date.h”中 class date { int month, day, year; //私有数据 public: date(int, int, int); //有参构造函数 void next( ); void print( ); }today;  

inline date::date(int a, int b, int c) { month=a; day=b; year=c;} void date:: print( ) { cout<<month<<“ /”<<day; cout<<“ /”<<year<<endl;}

void date::next( ) { switch month case 1: case3: case 5: case 7: case 8: case 10: { if(day<31)day++; else {month++;day=1;}}break; case 4: case 6: case 9: case 11: { if(day<30)day++; case 12: else {year++;month=1;day=1;}}break; case 2:

case 2: if((year%4==0)&&year%400!=0) | |(year%400= =0)) { if(day<29)day++; else {month++;day=1;}} else{ if(day<28)day++; }

例 date 类测试程序 #include “ iostream.h” #include “ date.h” void main( ) { date today(2,13,2001);//定义对象 date myBirthday(6,24,1983);//定义对象 today.next( );//对象调用操作 today.print( ); myBirthday.print( ); }

测试结果 2/14/2001 6/24/1983

三、模版( template) 通用数据类 模版函数 通用函数 2. 类模版 通用数据类型

模版函数 通用函数 几个形式上完全相同的函数,只有 参数类型和返回值类型不同,可以 写成通用函数,也叫模版函数。

例. int max(int a, int b) {return a>b?a:b;} float max(float a, float b) {return a>b?a:b;} char max(char a, char b) 可以统一写成一个模版函数

template <class T> T max(T a, T b) {return a>b?a:b;} 这里 T 是类型参数 可以用任意一个标识符

模版函数的调用 对不同的参数都可以调用函数max int a, b; char x,y; a=3; b=5; x= 'A'; y='b'; a=max(a, b); \\有意义 x=max(x, y); \\有意义 参数可以用字符串,甚至复杂的结构变 量,对象,只要事先定义大小关系。

2.类模版 通用数据类型 //array.h 例. 通用数组 抽象数组类型 template <class T> 2.类模版 通用数据类型 //array.h 例. 通用数组 抽象数组类型 template <class T> class Array { T *alist; //指针数据 表示一个数组 int size; //表示数组长度 public: Array(int s=50) //构造函数 Array(const Array<T>&X); //拷贝构造函数 ~Array( ){delete[ ] element;} //析构函数 Array<T>&operator=(const Array<T>&X);// 赋值函数重载 T& operator[ ](int i); //一元运算[ ]重载 下标函数 operator T*( )const; //强制类型转换,将当前 //对象变成指向它的首地址的指针, int ArraySize( )const; //取数组长 void Resize(int sz); //数组长重定义 friend ostream& operator<<(ostream&, const Array<T>&); //输出操作重载 };

template <class T> class Array { T *alist; //指针数据 表示一个数组 int size; //表示数组长度 public: Array(int s=50) //构造函数 Array(const Array<T>&X); //拷贝构造函数 ~Array( ){delete[ ] element;} //析构函数

Array<T>&operator=(const Array<T>&X); // 赋值函数重载 T& operator[ ](int i); //一元运算[ ]重载 //下标函数 operator T*( )const; //强制类型转换,将当前 //对象变成指向它的首地址的指针, int ArraySize( )const; //取数组长 void Resize(int sz); //数组长重定义 friend ostream& operator<< (ostream&, const Array<T>&); //输出操作重载 };

//构造函数 template <class T> Array<T>::Array(int sz) { size = sz; alist = new T[size]; if (alist == 0) {cout<< “内存不够”; return; } }

// 析构函数 destructor template <class T> Array<T>::~Array(void) { delete [ ] alist; }

// copy constructor 拷贝构造函数 template <class T> Array<T>::Array(const Array<T>& X) { int n = X.size; //取对象X的长度作为当前对象的长 size = n; alist = new T[n]; // allocate dynamic array if (alist == 0) //作正确性检查 {cout<<“内存不够”;return;} //copy array items from X to current object T* srcptr = X.alist; // address at start of X.alist T* destptr = alist; // address at start of alist while (n--) // copy list *destptr++ = *srcptr++; }

template <class T> //赋值函数重载 Array<T>& Array<T>::operator= (const Array<T>& X) { int n = X.size; // 取X的长度 if (size != n) // 若长度不同,重新分配内存 {delete [ ] alist; // destroy original memory alist = new T[n]; // allocate a new array if (alist == 0) {cout<<“内存不够”;return;} size = n; }

// 将对象X的元素逐个拷贝到当前对象 // copy array items from X to current object T* destptr = alist; T* srcptr =X.alist; while (n--) *destptr++ = *srcptr++; // 返回当前对象的值 //return reference to the current object return *this; }

//一元运算[ ]重载 下标函数 // overloaded index operator template <class T> T& Array<T>::operator[] (int n) { // do array bounds checking if (n < 0 || n > size-1) {cout<< "下标超界”;return;} // return the element from the private array list return alist[n]; }

//强制类型转换,将当前对象 //变成指向它的首地址的指针, // pointer conversion operator template <class T> Array<T>::operator T* ( ) const { // return address of private array in the //current object return alist; }

//取当前对象的长 template <class T> int Array<T>::ListSize( ) const { return size; }

//改变当前对象的长度 // resize operator template <class T> void Array<T>::Resize(int sz) { // test new size parameter; terminate if size <= 0 if (sz <= 0) {cout<< “长度不能小于等于0”;return;} // nothing to do if size hasn't changed if (sz == size) return; // request new memory and verify system response T* newlist = new T[sz]; if (newlist == 0) {cout<<“内存不够”;return;}

// copy n array items from old to new memory int n = (sz <= size) ? sz : size;//n取较小的一个 // copy n array items from old to new memory T* srcptr = alist; // address at start of alist T* destptr = newlist; // address at start of newlist while (n--) // copy list *destptr++ = *srcptr++; delete[] alist; // delete old list // reset alist to point at newlist and update the size alist = newlist; size = sz; }

ostream& operator<<(ostream& out, const Array<T>& a) { for(int i=0; i<a.size; i++) out<<a.alist[i]<< ‘ ’ ; out<<endl; }

//通用数组类的测试 #include<iostream.h> #include "array.h" void main(void ) { Array<int> a(20);//定义长为0的整数数组 for(int i=0;i<20;i++) a[i]=i+1; //下标函数返回值是类型T的引用 //可以写在表达式的左边 Array<int>b(a), c=a; //用拷贝构造函数, //赋值函数建立新对象 cout<<a<<b<<c; }

还可以定义字符数组,记录类型数组,复数类的数组,二维数组,三维数组等等,方法与C语言相同。

#include<iostream.h> #include "array.h" #include “complex.h" void main(void ) { Array<complex> a(20); complex s(1,2); for(int i=0;i<20;i++) a[i]=a[i]+s; Array<complex>b(a), c=a; cout<<a<<b<<c; }

3. 多于一个参数的模版 模版可以有不止一个参数,可以是某 个简单类型参数,也可以是通用参数。

例 template<class T, class S> class Node { Node<T,S> *previous, *next; T *T_data; S *S_data; Public: Node(Node<T,S>*, Node<T,S>*,T*,S*); ~Node( ); }

template <class T, class S> Node<T,S>::Node(Node<T,S>*p, Node<T,S>*q, T*t, S* s) { previous=p; next=q; T_data=t; S_data=s;} template<class T,class S> Node<T,S>::~Node( ) {delete T_data; delete S_data;}

4.类模版可以嵌套 template<class T,class S> class List { Node<T,S> *head, *current; public: List( ); void Insert( T*, S*); void Remove(T*, S*); ~List( ); }

四、类的继承 简单继承 多重继承 多态性和虚函数

简单继承 1.派生类的定义 2.基类成员的引用和访问的规则 3.导出类的构造函数 4. 覆盖 作用域分辨 5.派生类的类型转换 4. 覆盖 作用域分辨 5.派生类的类型转换 6. 导出类的指针

简单继承 从一个类可以派生出新的类——派生类或导出类,也叫子类. 原来的类叫基类也叫父类. 1.派生类的定义形式 class 新类名:public 基类名 //公有继承 {·········}; class 新类名:private 基类名 //私有继承 //缺省为私有继承, //基类是struct类时缺省是公有继承

基类限制词:public和private指明基类成员在导出类中的访问限制 基类中的成员自动成为导出类的成员。 基类限制词:public和private指明基类成员在导出类中的访问限制 class A class B : public A class C : public B

例:class roadVehicle { int wheels, passengers; public: roadVehicle(int, int); void SetWheels(int num); void SetPassengers(int num); int GetWheels(void); int GetPassengers(void); void print( ) {cout<<“\nwheels=”<<wheels <<“\nPassengers=”<<passengers; } };

class truck : public roadVehicle { int cargo; public: truck(int,int,int); void SetCargo(int size); int GetCargo(void); void Show(void); void print( ); }; Truck类有三个数据成员: wheels,passengers cargo. 有9个成员函数

不继承的部分 1。构造函数和析构函数 2。重载的操作符和赋值符 3。友元 基类中同名的成员被派生类中相应的名字覆盖

2.基类成员的引用和访问的规则 class A class B : public A class C : public B private 不可见 protected public class B : private A class C : private B

保护成员protected数据在基类中同private 公有基类的保护成员和公有成员是导出类的保护和公有成员 私有基类的保护成员和公有成员是导出类私有成员 基类的私有成员在导出类中不可见只能通过基类的公有成员函数调用

例 void truck::print(void) { cout<<“\nwheels=”<<wheels //出错 <<“\npassengers=”<<passengers //出错 <<“\ncargo=”<<cargo;} 应改为 void truck::print(void) { roadVehicle::print( ); cout <<“\ncargo=”<<cargo; }

3.导出类的构造函数 派生类不继承构造函数和析构函数 派生类的构造函数必须包含基类构造函数的信息 要为基类构造函数提供初始化参数 class truck 的构造函数: truck::truck(int a,int b,int c) : roadVahicle(a,b)

如果 roadVehicle类中 roadVehicle::roadVehicle(int x,int y) {wheels=x; passengers=y;} 则truck类中 truck::truck(int a, int b, int c) : roadVehicle(a,b) { cargo=c; }

注意: 导出类的构造函数中未写明基类构造函数 基类构造函数的参数无法传递, 这时导出类自动调用基类的无参构造函数, 如果基类没有无参构造函数,就会出错。

如果派生类中还有基类的对象成员 class truck : public roadVehicle { int cargo; roadVehicle a; public: truck(int,int,int,int,int); truck(int x):roadVehicle(x,x+1),a(x,x){ cargo=x; } void SetCargo(int size); int GetCargo(void); void Show(void); void print( ); };

例 truck类的构造函数 truck::truck(int a1, int a2, int b1, int b2, int c) : roadVehicle(a1,a2), a(b1,b2) { cargo=c; } 基类的构造函数由基类名标出 对象成员的构造函数由对象名标出

构造函数的调用顺序 先基类再导出类 析构函数相反

4. 覆盖 作用域分辨 基类中同名的成员被派生类中相应的名字覆盖 例 class A { ······ public: ······ int fun( ) {return 1;} } class B : public A { ······ public: ······ int fun( )//覆盖A中同名元 {return 2;} } A a; B b; int i=a.fun( ); // i=1 int j=b.fun( ); // j=2 i=b.A::fun( ); // i=1 作用域分辨

class B : public A { ······ public: ······ int fun( ){return 2;} int f( ){return A::fun( );} } B b; int i=b.fun( ); //i=2 int j=b.f( ); //i=1

例 truck类中的print函数 truck t(4,3,4); t.print( ); 输出: wheels=4 void truck::print(void) { roadVehicle::print( ); cout <<“\ncargo=”<<cargo; } truck t(4,3,4); t.print( ); 输出: wheels=4 passengers=3 cargo=4

5.派生类的类型转换 class A {······}; class B : public A {······}; A a; B b; A *ap=new A; B *bp=new B; a=b; //正确 ap=bp; //正确 b=a; //错误 bp=ap; //错误 B是A的派生类, B是A的超集 可以将类B的对象自动转换 为A类对象,反之不成立.

用户定义类型转换 类型转换函数重载 X::operator T( ); X是原定义类型,T是转换后类型。 例 String::operator char *(void ) {return str;} operator char *(void )是String类的成员函数 String类对象可以作char*字符串类型使用。

X::operator T( ); X类对象可以作T类型使用 X s; T a=s; //隐式转换 用于初始化 T fun( T t); a=fun(s); //隐式转换 用于函数参数 X f( X t); a=f(s); //隐式转换 用于函数返回值

隐式类型转换的规则: 用于初始化,函数参数,函数返回值 1.标准转换优先(转换成标准类型)。 2.不允许转换再转换 3.必须唯一确定,不能有二义性。 不能用隐式类型转换时用强制类型转换 a= T(s); //显式转换 强制类型转换

6. 导出类的指针 {······}; class B : public A {······}; 1 公有基类 指向导出类对象的指针可以隐式转换为指向基类对象的指针 反之不成立 class A {······}; class B : public A {······}; A *ap=new A; B *bp=new B; ap=bp; //正确 bp=ap; //错误 bp=(B*)ap;//正确, //显式转换 2.私有基类——不能隐式转换

class C :public A, public B {······}; 多重继承 1.多重继承的定义 由多个基类派生出一个类 class A class B class A; class B; class C :public A, public B {······}; class C 每个基类都应有自己的存定符 A,B的成员都自动成为C的成员,存取权限同单继承

2.构造函数 导出类的构造函数要为基类提供初始化参数 方法与单继承相同。 调用时先基类再导出类。基类的次序从左到右。 C::C(int a, int b, int c):A(a),B(b) {······}

3. 二义性和覆盖 两个基类中有同名成员都被继承。导出类对象调用时出现二义性,要用类名域加以区别。 class A class B class C 两个基类中有同名成员都被继承。导出类对象调用时出现二义性,要用类名域加以区别。 也可以在导出类中定义一个同名成员加以覆盖,消除二义性。

class B { public: void f( ); }; class A { public: void f( ); }; class C : public A, public B { public: ······ }; C c; c.f( ); //二义性 A的f, B的f ? c.A::f( ); //合法 c.B::f( ); //合法

class B { public: void f( ); }; class A { public: void f( ); }; class C : public A, public B { public: ······ }; void f( ){A::f( ); B::f( ); } }; C c; c.f( ); //二义性 A的f, B的f ? c.A::f( ); //合法 c.B::f( ); //合法 //合法 同时执行两个f

4.虚基类 一个基类被派生出两个以上导出类,这两个导出类再生成一个多重导出类. 在导出类D中, A的一个元素通过两个途径被继承了两次。这种情况叫双重继承。 class A class B class C class D

虚基类 虚基类定义可以避免双重继承 class A; class B : virtual public A; class C class A; class B : virtual public A; class C : virtual public A; Class D : public B, public C; class D 这样定义的D类中A的成员只继承一次,可以直接访问A的成员,也可以用任意路径访问。

注意 基类的所有直接导出类中都要用virtual声明为虚基类,否则仍然是多重继承。 class A class B:virtual A class C:virtual A class D

构造函数的调用次序 先虚基类(依声明次序从左到右) 后非虚基类(依声明次序)

编制学校全体教职员工人员卡 在校人员 学生 教师 职工 本科生 研究生 在职研究生 兼职教师 要求:编号,姓名,性别,出生日期, 学生 教师 职工 本科生 研究生 在职研究生 兼职教师 要求:编号,姓名,性别,出生日期, 学生:系,级,三门主课成绩, 研究生:专业,导师 教师: 系,专业,职称, 职工: 部门, 职务 输入:10个代表人物,格式化打印

多态性和虚函数 1.多态性 不同的对象调用同一个函数 导致不同的操作称为多态性 运算符重载和函数重载 是最简单的多态性

1.动态联编 程序在编译时决定由某对象对实例发出的消息,翻译并完成所要求的操作,叫前期装配,或早联编(early binding),也叫静态联编。 程序在编译时不确定由某对象对实例发出的消息,而是在运行过程中确定由哪个对象实例发出的消息,翻译并完成所要求的操作,叫后期装配,或后联编(later binding),也叫动态联编。

例 class B:public A { char *grade; public: B(char*p,int i):A(p) {grade=i;} void print( ) {A::print( ); cout<<“\n”<<grade;} } ; class A { char *name; public: A(char*p) {strcpy(name,p);} void print( ) {cout<<“\n”<<name;} }; class C:public A { char *major; public: C(char*p,char*q):A(p){strcpy(major,q);} void print( ) {A::print( );cout<<“\n”<<major;} };

void show(A*t) {t->print( );} Early binding输出: Zhang Wang 99 Li computer Wang Li void main( ) { A a(“Zhang”); B b(“Wang”,99); C c(“Li”, “computer”); a.print( ); b.print( ); c.print( ); show(&a); show(&b); show(&c); } &a,&b,&c在show中都看成是基类指针调用print( );

例:用虚函数 class B:public A { char *grade; public: B(char*p,int i):A(p) {grade=i;} void print( ) {A::print( ); cout<<“\n”<<grade;} } ; class A { char *name; public: A(char*p) {strcpy(name,p);} void print( ) {cout<<“\n”<<name;} }; virtual void print( ) class C:public A { char *major; public: C(char*p,char*q):A(p){strcpy(major,q);} void print( ) {A::print( );cout<<“\n”<<major;} };

void show(A*t) {t->print( );} later binding 输出: Zhang Wang 99 Li computer void main( ) { A a(“Zhang”); B b(“Wang”,99); C c(“Li”, “computer”); a.print( ); b.print( ); c.print( ); show(&a); show(&b); show(&c); } print( )是虚函数,后联编, 程序根据调用print的对象的实际类型决定如何操作

虚函数的用法 1. 动态联编只能在基类和派生类中有同名同参的函数才能使用。同名不同参不能覆盖。 2.只要在基类成员函数(公共或保护部分)使用 virtual ,中间类同名同参的函数自然也是虚函数 3.如果调用函数为show(B*t)时,中间类也要声明 virtual函数

4.如果派生类要通过虚函数机制存取虚函数,必须建立一条基类到派生类的虚函数路径。即每个中间类都必须有同名同参的函数。 5.如果某个中间类不需要这样的函数,而其后的派生类需要这个虚函数。可以在中间类中声明一个同名同参的空虚函数。 virtual print( ){ } 任何对象都不能调用空虚函数

私有函数也可以声明为虚函数 不能将全局函数或静态函数声明为虚函数 全局函数,静态函数与对象无关。

构造函数不能声明为虚函数 构造函数可以调用虚函数 小心:构造函数中基类构造函数优先 如果基类构造函数调用虚函数, 则必定调用了基类中的虚函数 而派生类中的构造函数则调用派生类中的虚函数,如果派生类中没有这个虚函数则调用基类中的虚函数。

2.纯虚函数 类等级的顶层可以将所有公共函数都声明为纯虚函数。这就可以使每个公用函数都成为后联编实现动态多样性。这样的类不需要任何操作,也不必实例化,即不用定义对象。 纯虚函数的声明 virtual int f( )=0; //表示这个函数没有定义

抽象类 至少有一个纯虚函数的类是一个抽象类。 抽象类不能定义对象,但可以定义指针 指向它。 抽象类的派生类必须覆盖基类中所有纯虚函数,否则纯虚函数被继承,使派生类仍然是抽象类,而不能定义对象。 抽象基类相当于派生类的模板,它的数据和方法可以被所有派生类共享

一个类的虚函数可以定义为另一个 类的友元。 友元函数不能是虚函数。 运算符可以定义为虚函数。 析构函数也可以定义为虚函数。

何时要用虚函数 1.类顶或其附近的类中。 2.函数的性质依赖于类的结构或类型。 3.实现类的输入或输出操作的函数。 4.函数的操作是为专门的类而定义的。

如何使多态性失效 用作用域分辨符分辨操作符,就可以使多态性失效。 派生类中的虚函数可以调用基类中的同名虚函数,只要使用作用域分辩符,指明调用基类函数,就不会引起无穷循环。

template <class T> class List //抽象类 { protected: int size; public: List(void); virtual ~List(void); virtual int ListSize(void) const; virtual int ListEmpty(void) const; virtual int Find (T& item) = 0; virtual void Insert(const T& item) = 0; virtual void Delete(const T& item) = 0; virtual void ClearList(void) = 0; };

template <class T> List<T>::List(void): size(0) {} List<T>::~List(void)

template <class T> int List<T>::ListSize(void) const { return size; } int List<T>::ListEmpty(void) const return size == 0;

异质链表 #include "iostream.h" #include "string.h" #include "stdio.h" #include "stdlib.h" class list;

class univ_community { friend class list; protected: char name[15]; int age; univ_community *ptr, *next; public: univ_community( ){name[0]='\0'; age=0;next=0;} univ_community(char *nam,int a) {strcpy(name,nam); age=a; next=0;} virtual void print( ) {cout<<"\n"<<"\n"<<name <<"\nage"<<age;} virtual void insert( ){} };

class student : public univ_community { friend class list; float score; public: student( ):univ_community( ) { score=0;} student(char*nam,int a,float sc): univ_community(nam,a) {score=sc;} void print( ) {univ_community::print(); cout<<"\nscore="<<score;} void insert( ) { ptr=new student(name,age,score);} };

class teacher : public univ_community { friend class list; char position[15]; public: teacher( ):univ_community( ) { position[0]='\0';} teacher(char*nam,int a,char*pos): univ_community(nam,a) { strcpy(position,pos);} void print( ) { univ_community::print( ); cout<<"\nposition "<<position;} void insert( ) {ptr=new teacher(name,age,position);} };

class staff : public univ_community { friend class list; char class staff : public univ_community { friend class list; char *dept; public: staff( ):univ_community( ) {dept[0]='\0';} staff(char*nam,int a,char*dep): univ_community(nam,a) {strcpy(dept,dep);} void print( ) {univ_community::print( ); cout<<"\ndepartment "<<dept;} void insert( ) {ptr=new staff(name,age,dept);} };

class list { univ_community class list { univ_community *root; public: list( ){root=0; } void insert_person(univ_community*s); void remove(char*nam); void print_list( ); };

void list::insert_person(univ_community void list::insert_person(univ_community *s) { char key[15]; strcpy(key,s->name); univ_community *curr=root; univ_community *prev=0; while(curr!=0&&strcmp(curr->name,key)<0) {prev=curr;curr=curr->next; } s->insert( ); s->ptr->next=curr; if(prev==0)root=s->ptr; else prev->next=s->ptr; }

void list::remove(char. nam) { univ_community void list::remove(char *nam) { univ_community *curr=root; univ_community *prev=0; while(curr!=0&&strcmp(curr->name,nam)!=0) { prev=curr; curr=curr->next; } if(curr!=0&&prev==0) { root=curr->next; delete curr ; } else if(curr!=0&&prev!=0) { prev->next=curr->next; delete curr; } }

void list::print_list( ) { univ_community. current=root; while(current void list::print_list( ) { univ_community *current=root; while(current!=0) { current->print( ); current=current->next; } }

void main( ) { list people; student stu("Zhang", 18,90); staff stf("Li", 30, "Education"); teacher teach("Wang", 45,"proffessor"); people.insert_person(&stu); people.insert_person(&stf); people.insert_person(&teach); people.print_list( ); people.remove("Li"); people.print_list( ); }