版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领
文档简介
1、.,1,目录,第一节 c+概述 第二节 函数 第三节 类和对象 第四节 友元和重载 第五节 模板 第六节 继承,.,2,第一节 c+概述,main()函数 标准输入输出流 exit语句 数据类型 标准库string类型 引用类型 指针和const限定符 动态内存分配,.,3,1.1 main()函数,int main() return 0; 每个c+程序必须含有main()函数,且main函数是唯一被操作系统显式调用的函数 定义函数必须制定4个元素:返回类型、函数名、形参表、函数体。 操作系统通过main的返回值来确定程序是否成功执行完毕,返回0表示程序成功执行完毕,通常非0表示有错误出现,.
2、,4,1.2 标准输入输出流,C+没有直接定义进行输入/输出的任何语句,这个功能由标准库iostream.h提供。包含两个类:输入流istream和输出流ostream. #include using namespace std; 标准库中的四个IO对象 cin标准输入(如键盘),为istream对象 cout 标准输出(如显示屏),为ostream对象 cerr 标准错误,用于输出警告和错误信息,为 ostream对象 clog用于产生程序执行的一般信息,为ostream对象,.,5,cin读入流(由键盘输入),作用从键盘取得数据送至内存,与一起使用结合方向为自左向右 例如:int v1,v
3、2; cinv1v2; 从流中读取信息时,输入流缓冲指针跟踪最后一个读入到流的字符,每次尝试从流获取信息时,都从指针的当前位置开始 cin自动跳过空白字符(whitespace) 返回值为左操作数,.,6,用cout写入到流(输出到屏幕),cout必须与输出操作符一起使用,结合方向为自左向右 例如: coutEnter two numbersendl; cout decx;(hex/oct) 作用将右操作数插入到cout中,可同时接受不同类型的数据输出,所以可有多个s; /从第一个非空字符读至下一个空白字符 读入一行getline(cin,line);两个参数:输入流对象和string对象 功
4、能:从输入流的中读取内容到line中,换行符是该行的结束标志 注:getline()不忽略开头的换行符,但line并不保存换行符,即若开头遇换行符,line为空string 输出:coutsstr; 输入了一次回车,这个语句遇到回车就结束,这样,回车符也跟着到了内存,到用getline()再输入时,内存里第一个字符就是回车,所以getline()一读到这个回车就结束了,所以getline(cin,str);不起作用。 解决: 处理掉cinstr;遗留的回车符,可以在该语句下加一句: getchar();/吸收内存里的回车符,.,12,string对象的操作 s.empty(); 若字符串为空,
5、返回true s.size(); 返回s中字符的个数 sn;返回s中位置为n的字符 s1=s2;比较,所有的比较运算符都可以使用 s1+=s2; 连接 s1=s2;赋值,string类型可以和字符串字面值连接,赋给string类型,但是,+操作符的左右操作数必须至少有一个string类型 string s=hello+,+s1;,.,13,1.6 引用类型,引用就是对象的另一个名字,主要用于函数的形参 引入 void swap(int a , int b) int temp=a; a=b; b=temp; int main() int x=10, y=20; swap(x,y); 形参与实参有
6、各自不同的内存空间,若实参是一个复杂对象,重新分配内存会引起程序执行效率大大下降 形参对实参为值传递,对形参的任何改变不会引起实参值的改变,.,14,1.6.1 非const引用,引用就是某一变量(目标)的一个别名,对引用的操作与对变量直接操作完全一样。 声明:类型标识符 /定义引用ra,它是变量a的引用(别名) (1) 1.引用必须在定义时初始化, int ,.,16,1.6.2 const引用,格式: const 类型标识符 /正确 引用型参数应该在能被定义为const的情况下,尽量定义为const 。,.,17,对const对象的引用,const int ival=1024; int i
7、=34; const int refval是对const型的引用,所以任何对refval的赋值都是非法 const对象必须用const引用 const引用可以绑定到相关的类型的对象,或绑定到右值,.,18,1.6.3 指针和引用的比较,指针和引用都可间接访问另一个值,但是 1、引用总是指向某个对象,定义时必须初始化。 指针则可以在任何时候被初始化 2、引用一旦被初始化,就不能改变引用的关系 而指针则可以随时改变所指向的对象 3、赋值:引用即为别名,给引用赋值修改的是该引用所关联的对象的值(非const引用)而指针可以更改其指向的对象,也可以更改所指向的对象的值 4、不能有NULL引用,引用必须
8、与合法的存储单元关联,而指针则可以是NULL,.,19,1.7 指针和const限定符,使用const修饰指针时,由于const的位置不同,而含意不同。 1.7.1 指向const对象的指针 指向const的指针,不可以通过该指针修改对象,但可以其他方式修改 double dval=3.14,pi=3.1415; const double *ptr = / /合法,.,20,若一个指针是指向const对象,则该指针必须具有const特性。例如 const double pi=3.14159; const double *ptr= /错误,const对象要用指向const的指针来指向,这样可以保
9、证既不能通过*ptr,也不能通过pi修改其值 指向const的指针常用作函数的形参,这样可以确保函数的实参在函数调用过程中不被修改 void use_ptr(const int *p),.,21,1.7.2 const指针,固定指向一个对象的指针,即指针本身是常量 char * const ptr1 = stringptr1; /const放在类型说明和变量之间 ptr1 = stringptr2; /非法,指针本身的值不可改变 *ptr1 = m; /合法 指针所指的变量的值可以改变 若指针及指针所指向的变量的值都不可以更改 const char * const ptr=stringptr;
10、,.,22,1.8 动态内存分配,一 内存分配有三种方式 从静态存储区域分配。内存在程序编译的时候就已经分配好,这块内存在程序的整个运行期间都存在。例如全局变量,static变量。 栈,就是那些由编译器在需要的时候分配,在不需要的时候自动清除的变量的存储区。里面的变量通常是局部变量、函数参数等。栈内存分配运算内置于处理器的指令集中,效率很高,但是分配的内存容量有限。 堆,亦称动态内存分配。程序在运行的时候用malloc或new申请任意多少的内存,程序员自己负责在何时用free或delete释放内存。动态内存的生存期由我们决定如果程序员没有释放掉,那么在程序结束后,操作系统会自动回收。,.,23
11、,1.8.2 单个对象的动态分配与释放,动态分配:由关键字new及其后面的类型指示符构成。该类型指示符可以是内置类型或class类型。 例:new int ;从堆中分配了一个int型的对象。 new Student ; 分配了一个Student类对象。 需要注意的是堆中分配的对象没有名字。new表达式返回了一个指向该对象的指针,对该对象的全部操作都要通过这个指针间接完成。 例如: int *pi = new int ; 该new表达式创建了一个int型的对象,由pi指向它。 初始化 int *pi = new int (0) ; 该语句表示pi指向一个int型的对象,该对象的初始值为0。括号中
12、的表达式被称作初始化式,.,24,动态内存的释放,与静态分配内存的对象不同,编译器不会自动释放它们所占的内存-除非整个程序结束。所以当动态分配内存的对象完成它的使命,需要被销毁的时候不能依赖编译器,而要靠程序员用delete释放。 格式: delete pi;/释放pi所指向的内存空间 指针pi本身是个在全局域中声明的全局对象,它的生命期由编译器控制。pi的存储区在程序开始之前就被分配,一直保持到程序结束。 而pi指向的对象的生命期是由程序员控制的,它是在程序执行过程中遇到new表达式时才被创建,遇到delete表达式时被销毁并收回存储区,.,25,动态内存的释放,delete只能用在指向内存
13、是用new动态分配的指针上,如果将其在指向堆以外内存的指针上,会使程序运行期间出现未定义的行为。唯一的例外是,当指针指向NULL时,不管指针指向的对象是如何分配内存的,都不会引发麻烦。 void f() int i; char *str = asdd; int *pi = /安全!pd指向一个动态分配的对象 ,.,26,常见错误,1 忘记了释放内存,造成内存泄露。 含有这种错误的函数每被调用一次就丢失一块内存。刚开始时系统的内存充足,你看不到错误。终有一次程序突然死掉,系统出现提示:内存泄漏(memory leak)。 函数体内的局部变量在函数结束时自动消亡。例如p是局部的指针变量,它消亡的时
14、候会让它所指的动态内存一起消亡。这是错觉! void Func(void) char *p = (char *) malloc(100); / 动态内存会自动释放吗? 指针消亡了,并不表示它所指的内存会被自动释放 2 对同一内存区应用了多次delete表达式。这通常发生在多个指针指向同一个动态分配对象的时候。若多个指针指向同一对象,当通过某一个指针释放该对象时就会发生这种情况。 3 内存被释放了,并不表示指针会消亡或者成了NULL指针,形成野指针,.,27,野指针,因此,当程序执行delete pi;语句时,pi指向对象的内存被释放,但指针pi本身的内存并没有受到delete表达式的影响。在d
15、elete pi;之后,pi被称作空悬指针(俗称野指针),即指向无效内存的指针。空悬指针是错误的根源,它很难被检测到,如果对它进行操作将会产生无法预测的结果。一个比较好的办法是在指针所指的对象被释放后,马上将该指针设置为NULL,这样可以清楚地表明该指针不再指向任何对象 char *p = (char *) malloc(100); strcpy(p, “hello”); free(p); / p 所指的内存被释放,但是p所指的地址仍然不变 if(p != NULL) / 没有起到防错作用 strcpy(p, “world”); / 出错 ,.,28,野指针,指针操作超越了变量的作用范围。这种
16、情况让人防不胜防,示例程序如下: class A public: void Func(void) cout Func(); / p是“野指针” ,.,29,1.8.3 数组的动态分配与释放,new表达式也可以在堆中分配数组。在这种情况下new表达式中的类型指示符后面必须有一对方括号,里面的值代表数组的长度,而且该数值可以是一个复杂的表达式。new表达式返回指向数组第一个元素的指针。 动态分配数组只需指定类型和长度 格式: new typesize; /size为数组元素的长度,可以是任意表达式。new返回指向新分配数组的第一个元素的指针。 例如: int *p2; p2=new intn; d
17、elete 释放new分配的存储空间 delete p;/表明p指向的是动态存储区的数组,如果遗漏,编译器无法发现错误,.,30,int *pi = new int (1024) ; /分配单个int型的对象,用1024初始化 int *pia = new int 1024 ; /分配一个含有1024个元素的int型数组,未被初始化,.,31,第二节 函数,参数传递 函数返回值 内联函数 函数的重载,.,32,2.1 参数传递,C+语言中,函数的参数和返回值的传递方式有三种:值传递、指针传递和引用传递。在调用函数时,对于每一个实参,其类型必须与对应的形参类型相同,或可被转换为该形参类型 2.1
18、.1 普通的非引用形参 为传值调用,调用时将实参的值赋给形参,而形参的任何改变不会改变实参 void swap(int p1, int p2) int temp=p1; p1=p2; p2=temp; 调用时:int a=5,b=9; swap(a,b); / a、b并没有互换,.,33,指针形参,指针传递传递的是地址,函数可以通过指针赋值,修改指针所指向的对象的值 void swap(int *p1, int *p2) int temp=*p1; *p1=*p2; *p2=temp; 调用时怎么写? 1、若要保护指针指向的对象的值,则形参需定义为指向const对象的指针 void use_p
19、tr(const int *p) 在函数体中,不能通过*p修改p指向的对象的值 实参既可以为int *,也可以为const int *类型,.,34,2、如果输入参数采用“值传递”,由于函数将自动产生临时变量用于复制该参数,调用结束释放所以该参数本来就无需保护,没有必要加const修饰。例如不要将函数void Func1(int x) 写成void Func1(const int x)。同理不要将函数void Func2(A a) 写成void Func2(const A a)。其中A为用户自定义的数据类型。 对于非内部数据类型的参数而言,象void Func(A a) 这样声明的函数注定效率
20、比较低。因为函数体内将产生A类型的临时对象用于复制参数a,而临时对象的构造、复制、析构过程都将消耗时间。为了提高效率,可以将函数声明改为void Func(A cinab; /输入a,b两变量的值 swap(a,b); /直接以变量a和b作为实参调用swap函数 couta b; /输出结果 (1)被调函数的形参就成为原来主调函数中的实参变量或对象的一个别名来使用,所以在被调函数中对形参变量的操作就是对其相应的目标对象(在主调函数中)的操作。 (2)使用引用传递函数的参数,在内存中并没有产生实参的副本,它是直接对实参操作;在传值调用中若传递的是对象,将调用拷贝构造函数。因此,当参数传递的数据较
21、大时,用引用比用一般变量传递参数的效率和所占空间都好。,.,37,三种传递方式的比较,值传递 void Func1(int x) x = x + 10; /形参的使用 int n = 0; Func1(n); /调用 cout “n = ” n suit=suit; this-faceUp=true; ,.,63,const成员函数,使用const关键字进行说明的成员函数为常成员函数。只有常成员函数才有资格操作常量或常对象,没有使用const关键字说明的成员函数不能用来操作常对象。 说明格式如下: 类型说明符 函数名 (参数表) const; 其中,const是加在函数说明后面的类型修饰符,它
22、是函数类型的一个组成部分,因此,在函数实现部分也要带const关键字。 任何不会修改数据成员的函数都应该声明为const类型。如果在编写const成员函数时,不慎修改了数据成员,或者调用了其它非const成员函数,编译器将指出错误,这无疑会提高程序的健壮性。,.,64,class Stack public: void Push(int elem); int Pop(void); int GetCount(void) const; / const成员函数 private: int m_num; int m_data100; ; int Stack:GetCount(void) const + m
23、_num; / 编译错误,企图修改数据成员m_num Pop(); / 编译错误,企图调用非const函数 return m_num; ,.,65,3.5 构造函数与析构函数,概述 构造函数的初始化表 缺省的构造函数 缺省的拷贝构造函数 运算符重载 缺省的赋值函数 析构函数,.,66,3.5.1 概述,每个类只有一个析构函数和一个赋值函数,但可以有多个构造函数(包含一个拷贝构造函数,其它的称为普通构造函数)。对于任意一个类A,如果不想编写上述函数,C+编译器将自动为A产生四个缺省的函数,如 A(); / 缺省的无参数构造函数 A(const A / 缺省的赋值函数,.,67,作用:把对象的初始
24、化工作放在构造函数中,把清除工作放在析构函数中。当对象被创建时,构造函数被自动执行。当对象消亡时,析构函数被自动执行。这样可以自动执行对象的初始化和清除工作。 命名:构造函数、析构函数与类同名,由于析构函数的目的与构造函数的相反,就加前缀以示区别。 构造、析构函数没有返回值,即使是void也不可以。 构造、析构函数置于类定义的public部分 析构函数不得带有任何参数 构造函数可以有一个或多个,一旦定义一个有参的构造函数,一定要显式定义一个无参构造函数,.,68,3.5.2 构造函数的初始化表,和其他函数不同,构造函数有个特殊的初始化方式叫“初始化表达式表”(初始化表)。初始化表位于函数参数表
25、之后,以冒号开始,后面接数据成员列表,列表之间用逗号分隔,每一个变量后面用一对圆括号包含它的初始值 。 初始化表放在构造函数的定义中 PlayingCard :PlayingCard (Suits is, int ir) : suit(is), rank(ir),faceUp(true) /函数体 ,.,69,(2) 执行顺序,首先执行初始化列表,然后执行该构造函数函数体中的语句 成员对象初始化的次序完全不受它们在初始化表中次序的影响,只由成员对象在类中声明的次序决定。这是因为类的声明是唯一的,而类的构造函数可以有多个,因此会有多个不同次序的初始化表。如果成员对象按照初始化表的次序进行构造,这
26、将导致析构函数无法得到唯一的逆序。 例 class x int i; int j; public: x(int val):j(val),i(j) /相当于i=j;j=val;j还没有初值就被使用 ; 所以尽量避免使用成员来初始化其他成员 x(int val):j(val),i(val),.,70,const数据成员,const数据成员只在某个对象生存期内是常量,而对于整个类而言却是可变的,因为类可以创建多个对象,不同的对象其const数据成员的值可以不同。 不能在类声明中初始化const数据成员。以下用法是错误的,因为类的对象未被创建时,编译器不知道SIZE的值是什么。 class A con
27、st int SIZE = 100; / 错误,企图在类声明中初始化const数据成员 int arraySIZE; / 错误,未知的SIZE ; const数据成员的初始化只能在类构造函数的初始化表中进行, 类的定义 class A A(int size); / 构造函数 const int SIZE ; ; 类的实现 A:A(int size) : SIZE(size) / 构造函数的初始化表 生成类的对象 A a(100); / 对象 a 的SIZE值为100 A b(200); / 对象 b 的SIZE值为200,.,71,(3)使用说明,大多数成员可以在构造函数的初始化表中初始化,也
28、可以在函数体中赋值。但是 类的const常量和引用成员只能在初始化表里被初始化,因为不能对他们赋值,所以要在执行构造函数的函数体之前完成初始化。 class ConstRef public: ConstRef(int ii); private: int i; const int ci; int ,.,72,ConstRef:ConstRef(int ii) i=ii; ci=ii; /不能对常量赋值 ri=i; /此时是对ri所绑定的对象赋值,但是还不知道ri是谁的别名 ConstRef:ConstRef(int ii):i(ii),ci(ii),ri(i),.,73,3.5.3 缺省的构造函
29、数,格式:A:A() 当类没有定义构造函数时,编译器才会自动生成一个不带参数的缺省构造函数 在缺省构造函数中,对于类中的成员,若为类类型,调用默认构造函数来初始化;若为内置或复合类型(指针、数组),若对象定义在局部作用域中,必须对它赋初值 使用缺省构造函数创建对象 Complex c; 注: Complex c();是错误的。编译器把他解释为一个函数的声明。c是函数名,返回类型为Complex 一旦定义一个有参的构造函数,一定要显式的定义一个无参构造函数,.,74,Complex:Complex(double r , double i) real=r; image=i; Complex:Com
30、plex(double r) real=r; image=0; Complex:Complex() real=0; image=0; cout “Initializing 0 0”endl; 调用方式: 创建对象时自动调用缺省的构造函数 Complex c1; Complex *pc1 = new Complex; /注意,调用缺省构造函数时,没有括号 调用有参构造函数 Complex c2(4.5); 或 Complex c2=new Complex(4.5); /一个参数 Complex c3(4.5 , 6.2); 或 Complex c3=new Complex(4.5,6.2);,.
31、,75,3.5.4 缺省的拷贝(复制)构造函数,格式: A(const A ,.,76,在类中,初始化对象有两种方式 直接初始化:用括弧直接调用与实参匹配的构造函数。如:string dots(10,.); string empty; 复制初始化:用等号首先使用指定构造函数创建一个临时对象,然后使用复制构造函数将那个临时对象复制到正在创建的对象。 string empty_copy=string(); 复制构造函数的应用场合 定义一个对象并用一个同类型对象初始化时 类的对象作为实参传递给形参时 从函数返回时复制一个对象 即使是定义了其他的构造函数,这个缺省的复制构造函数也是存在的,.,77,3
32、.5.5 运算符重载,对于基本数据类型,有许多相应的基本运算,如算术运算的+、-、*、/,但是对用户自定义类型,却不可以用这些运算符进行运算,为了使类类型也能使用这些运算符,必须对这些运算符进行重载 重载操作符是具有特殊名称的函数,在C+语言中,可以用关键字operator加上运算符来表示函数名,叫做运算符重载。 遵循的规则: *不能改变原运算符操作数的个数 + + *不能改变原运算符的自然含义 *重载操作符必须具有至少一个类类型或枚举类型的操作数,既不能对内置类型对象的操作符重载。 不能重载的运算符:成员运算符. 作用域运算符: 条件运算符?:一元运算符*,.,78,如果运算符被重载为类的成
33、员函数,那么一元运算符没有参数,二元运算符只有一个参数,因为对象自己成了左侧参数。从语法上讲,运算符既可以定义为友元函数,也可以定义为成员函数。 作为类的成员函数重载的格式: 一元运算符 返回值类型 类名:operator运算符() /operator运算符作为一个整体作为函数名 二元运算符 返回值类型 类名:operator运算符(参数说明) /参数说明只允许有一个参数,即右操作数,.,79,例如:对复数进行相加,通过+号能直接实现 类内声明: Complex operator+(const Complex ,.,80,3.5.6 缺省的赋值函数,格式:A ,.,81,拷贝构造函数与赋值函数
34、的比较,拷贝构造函数和赋值函数非常容易混淆,常导致错写、错用。拷贝构造函数是在对象被创建时调用的,而赋值函数只能被已经存在了的对象调用。 String a(“hello”); String b(“world”); String c = a; / 调用了拷贝构造函数,最好写成 c(a); c = b; / 调用了赋值函数,.,82,赋值和复制,内存分配方法影响赋值的含义: 1. 复制语义:c+中使用,实际是调用的赋值函数,右侧变量值赋给左侧变量,两个变量占用不同存储空间,值是独立的 2. 指针语义:同一(Java中使用)左侧变量的地址赋给了右侧变量的地址,两个变量是共用同一地址,其中一个变量值的
35、改变会改变另一个变量,.,83,3.5.8 析构函数,在对象脱离其作用域时,系统自动执行析构函数,作善后清理工作,主要用于资源回收 析构函数与类同名,无参数,无返回值。只是在函数名前加 例:Complex:Complex() 编译器总会自动生成一个析构函数,他按成员在类中的声明次序逆序撤销成员。默认析构函数释放对象生命期内或构造函数中获取的资源。 注:动态分配的对象只有在指向该对象的指针被删除时才会撤销,若没有删除,不会运行该对象的析构函数,对象一直存在,会导致内存泄漏 当对象的引用或指针超出作用域时,不会运行析构函数。当删除指向动态分配对象的指针或实际对象超出作用域时,才会运行析构函数,.,
36、84,第四节 友元和重载,友元 友元函数 友元类 友元关系与继承 重载与友元 重载 重载输出运算符 重载输入运算符 转换与类类型,.,85,4.1 友元,我们已知道类具有封装和信息隐藏的特性。只有类的成员函数才能访问类的私有成员。非成员函数可以访问类中的公有成员,但是如果将数据成员都定义为公有的,这又破坏了隐藏的特性。另外,应该看到在某些情况下,特别是在对某些成员函数多次调用时,由于参数传递,类型检查和安全性检查等都需要时间开销,而影响程序的运行效率。 为了解决上述问题,提出一种使用友元的方案。友元是一种定义在类外部的普通函数,但它需要在类体内进行说明,为了与该类的成员函数加以区别,在说明时前
37、面加以关键字friend。友元不是成员函数,但是它可以访问类中的私有成员。友元的作用在于提高程序的运行效率,但是,它破坏了类的封装性和隐藏性,使得非成员函数可以访问类的私有成员。,.,86,4.1.1 友元函数,友元可以是一个函数,该函数被称为友元函数;友元也可以是一个类,该类被称为友元类。 1、友元函数 友元函数的特点是他是能够访问类中的私有成员的非成员函数。友元函数从语法上看,它与普通函数一样,即在定义上和调用上与普通函数一样。下面举一例子说明友元函数的应用。,.,87,#include using namespace std; class Point public: Point(doub
38、le xx, double yy) x=xx; y=yy; void Getxy(); friend double Distance(Point ,.,88,void main() Point p1(3.0, 4.0), p2(6.0, 8.0); p1.Getxy(); p2.Getxy(); double d = Distance(p1, p2); /友元函数被调用时与普通函数一样 coutDistance isd只能重载为成员函数 二元操作符+= -= /= *= type可以是内置类型名、类名等,但是,一般不允许转换为数组或函数类型,转换为指针和引用类型是可以的 转换操作符是一种特殊的
39、类成员函数。它的功能是将类类型的值转换为其他类型的值。 转换操作符在类定义体内声明,在operator后跟转换的目标类型 不能指定返回类型,形参表必须为空,.,98,class SmallInt friend ostream 转换函数一般不应该改变被转换的对象,所以转换操作符通常应被定义为const成员,.,99,该转换是用户为类类型对象自定义的转换,但是调用时是由系统隐式调用 SmallInt si1,si2; int main() 输入si1、si2的值; cout127)?”greater than”: (si1127)?”less than”:” equal to”); (b)?(a)
40、:(b) 但是在复杂调用下,它的行为是不可预期的。 例如 const int size=10; int iasize; int main() int elem_cnt=0; int *p= 每次扩展p=p+1都应用了两次。与函数不同,函数调用之前确保实参只被计算一次,.,103,函数模板,函数模板提供一种用来自动生成各种类型函数实例的算法,程序员对于函数接口参数化,而函数体保持不变。 函数模板的声明在关键字template后跟随的尖括弧内可以有一个到多个参数。参数不能为空,模板参数可以是模板类型参数,他代表一种类型;也可以模板非类型参数,他代表一个常量表达式。 可以在某个头文件中定义模板。例如
41、:/ file max.h #ifndef MAX_INCLUDED #define MAX_INCLUDED template class T T max(T t1, T t2) return (t1 t2) ? t1 : t2; / template class T为模板前缀,定义和声明都由此开始 #endif 函数模板不是一个实在的函数,编译系统无法产生执行代码。class T 定义 T 作为模板类型参数。max 是函数名,t1和t2是其参数,返回值的类型为 T,.,104,模板非类型参数是由一个普通的参数声明构成,模板非类型参数的值代表了模板定义中的一个常量 例如,size代表arr指
42、向的数组的长度 template Type min(Type (/模板类型参数使用 模板定义的余下部分中size可以出现在要求常量的地方 模板类型参数作为一个类型指示符,它的使用方式与内置或用户自定义的类型完全一样,.,105,模板实例化,当实例化 max()时(调用),由具体的数据类型替换。你可以像使用普通的函数那样使用max()。编译器按照所使用的数据类型自动产生相应的模板实例: int n=10,m=16; float f1=60.4, f2=23.2; int highest = max(n , m); / 编译器知道n,m为int型,用模板生成一个具体的函数定义,并将T换成int f
43、loat fh=max(f1 , f2): /发现该调用时,又生成一个定义,将T换成float,成为模板函数 complexdouble c1, c2; /. 给 c1,c2 赋值 complexdouble higher=max(c1,c2); / complex 版本 类模板,.,106,显式模板实参,template T min(T,T) unsigned int ui; int main() min(ui,1024); min()的实参必须类型相同,此时实参推演失败,模板实例化出现错误,可以采用显式模板实参。显式模板实参被指定在逗号分隔的列表中,用尖括号括起来,紧跟在模板实例名的后面
44、上例调用时可写成 min(ui,1024);,.,107,模板编译模式,有两种:包含模式和分离模式 包含模式 /model1.h中 template class T T min(T t1, T t2) return (t1 t2) ? t1 : t2; 在每个使用min()实例的文件中都包含该文件 /model1.cpp中 #include “model1.h” int i=7,j=5; double d=min( i , j ); 缺点:模板体对于用户来说应该是透明的 在多个文件之间编译相同的函数模板定义增加了不必要的编译时间,.,108,分离模式:函数模板的声明放在头文件中,而定义放在cp
45、p文件中 /model2.h中 template class T T min(T t1, T t2) ; /model2.cpp中 export template class T /在生成其他文件使用的模板实例化时可能要用到这个定义 T min(T t1, T t2) return (t1 t2) ? t1 : t2; /user.cpp中 #include “model2.h” int i=7,j=5; double d=min( i , j ); 使用该模式可以很好的将定义和接口分开,但并非所有的编译器都支持该模式,.,109,类模板,(1)定义和声明 template class Arr
46、ay public: Array(int n); T 类模板的定义和声明都是以关键字template开头,关键字后面是一个逗号分隔的模板参数表,参数之间用括起来。 当有多个类型参数的时候,每个参数必须以关键字class开头 template,.,110,2)类模板中成员函数的实现 template Array:Array(int n) 一般形式: template 返回类型 类模板名:成员函数名(形参)函数体 注:每一个成员函数前都要加模板前缀template ,每一个成员函数名前都要加类模板名: 3)创建类模板的实例 格式:类名 对象 Array b(10); Array c(8.5);,.
47、,111,4)类模板和编译模式 只有当编译器看到了实际的类模板的定义,而不仅仅是声明时,它才能实例化类模板,所以当程序使用一个类模板并要求其实例时,程序首先必须提供类模板的定义 而类模板的成员函数与模板本身很相像,编译器只有看到成员函数并真正使用时,才实例化这个函数。所以一个成员函数被定义在类模板定义之外,那么这些定义应该被放在含有该类模板定义的头文件中。(包含编译模式) 分离编译模式,.,112,/array.h中 export template /声明Array是一个可导出的类模板 class Array ; array.cpp中 #include “array.h” template A
48、rray:Array(int n) /user.cpp中 #include “array.h” Array a(5);,.,113,第六节 继承,关于继承的直观描述 c+继承格式 改写、重定义与遮蔽 接口和抽象类 继承和构造函数 虚拟析构函数,.,114,6.1 关于继承的直观描述,继承可以使一个子类的实例存取和它的父类相关的数据和行为。也就是说用继承的方法可以自动为一个类提供来自另一个类的属性和操作,进而使程序设计人员在一个一般的类的基础上很快建立一个新的类,而不必从零开始设计每个类。 当一个类被其他的类继承,被继承的类称为基类,又称为父类。继承其他类的类称为派生类,又称为子类。 继承的含义
49、 子类具有父类的属性和方法(扩展) 子类可以声明新的属性和方法,也可以剔出不适用其用途的父类操作(收缩) 继承总是向下传递的,因此一个类可以从它上面的多个超类中继承各种属性 。,.,115,继承的含义,例:如果Dog是Mammal的派生类,而Mammal又是Animal的派生类,则Dog不仅继承了Mammal的属性,同时也继承了 Animal的属性。 派生类可以覆盖从基类继承来的行为。 “是一个”检验: 检验两个类是否为继承关系时,存在一项规则,这项规则称为“是一个”(is-a)检验。例如,A是B的子类,那么A is-a B,.,116,6.2 c+继承的格式,从一个基类派生的继承称为单继承。
50、单继承声明语句的常用格式为: class 派生类名: 访问控制关键字 基类名 数据成员和成员函数声明 ; 从多个基类派生的继承称为多继承或多重继承,也就是说,一个派生类有多个直接基类。 class 派生类名: 访问控制关键字 基类名1, 访问控制关键字 基类名2,. 数据成员和成员函数声明 ;,.,117,.,118,例,class Parent private: int three; protected: int two; public: int one; Parent () one = two = three = 42; void inParent () cout one two thre
51、e; / all legal ; class Child : public Parent public: void inChild () cout one; / legal cout two; / legal cout three; / error - not legal ; void main () Child c; cout c.one; / legal cout c.two; / error - not legal cout c.three; / error - not legal ,.,119,6.3 改写、遮蔽与重定义,静态类和动态类: 变量的静态类是指用于声明变量的类。 静态类在编
52、译时就确定下来,并且再 也不会改变 变量的动态类指与变量所表示的当前 数值相关的类。动态类在程序的执行 过程中,当对变量赋新值时可以改变 对于静态类型面向对象编程语言,在编译时消息传递表达式的合法性不是基于接收器的当前动态数值,而是基于接收器的静态类来决定的,.,120,例,class Animal public:virtual void speak() cout“Animal Speak!”; ; class Dog:public Animal public: void speak()bark(); void bark()cout“Woof!”; ; class Bird:public An
53、imal public:void speak()cout“Tweet!”; ;,.,121,Animal *pet; Dog fido; fido.speak(); /woof! fido.bark(); pet= /error! 编译错误,pet为指向Animal的指针,.,122,6.3.1 多态,多态:如果方法所执行的消息绑定是由最近赋值给变量的数值的类型来决定的,那么就称这个变量是多态的。 Java变量都是多态的。 C+声明为简单类型的变量,非多态。 在c+中,编译器不会自动将派生类类型对象转换为基类类型对象 Animal a; Dog b; b.speak(); /woof! a=b
54、; a.speak(); / Animal speak! /当接收器的类型为Animal时,speak所执行的方法处于静态类Animal中,即使变量所包含的数值来自于不同类型的赋值,.,123,实现多态要满足的条件,a. 使用指针或引用; b. 父类相关方法声明为virtual; Bird b; Animal *pet , /error,.,124,Animal没有声明为virtual,class Animal public: void reply()coutreply(); / Animal Reply! d.reply(); /Woof again!,.,125,6.3.2 改写和虚拟方法
55、,改写: 语法上:子类定义一个与父类有着相同名称且类型签名相同的方法。 运行时:变量声明为一个类,它所包含的值来自于子类,与给定消息相对应的方法同时出现于父类和子类。 改写与替换结合时,想要执行的一般都是子类的方法。 改写机制 Java语言,只要子类通过同一类型签名改写父类的方法,自然便会发生所期望的行为。 C+中需要父类中使用关键字Virtual来表明这一含义。,.,126,虚函数的定义与派生类中的重定义 class 类名 public: virtual 成员函数说明; class 类名:基类名 public: virtual 成员函数说明; /virtual optional ,.,127
56、,例,class A public: virtual void f() cout A:f endl; ; class B :public A public: virtual void f() cout B:f endl; ;,.,128,虚函数是成员函数,如果某类的一个成员函数被说明为虚函数,就意味这该成员函数在派生类中可能有不同的实现。 在c+中通过指向基类对象的指针来访问这些虚函数。 class C :public A public: virtual void f() cout f(); pa=,.,129,6.3.2 重定义,当子类定义了一个与父类具有相同名 称但类型签名不同的方法时,发生重定义。 类型签名的变化是重定义区别
温馨提示
- 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
- 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
- 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
- 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
- 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
- 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
- 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。
最新文档
- 2025版新编国际商务日语谈判竞赛活动组织合同3篇
- 二零二四年度珠宝首饰定制加工合同范本共3篇
- 二零二五年度二手房买卖合同(含物业交接流程规范)3篇
- 二零二四年度学校食堂厨房设备维护与保养合同
- 中石油加油站零售购销合同加油卡样本
- 2025版押一付三别墅租赁合同模板参考6篇
- 二零二五年度跨境电子商务搬运物流合同4篇
- 二零二四年度新型路基材料研发与施工应用合同3篇
- 2025年个人车位抵押贷款合同
- 二零二五年度智能语音识别软件购销合同范本3篇
- 《风电场项目经济评价规范》(NB-T 31085-2016)
- 拔罐技术操作考核评分标准
- 戒赌法律协议书范本
- 竞选市级三好学生PPT
- 2024届甘肃省兰州市五十一中生物高一上期末检测模拟试题含解析
- 高标准农田建设上图入库(技术培训)
- 火灾隐患整改登记表
- 普通地质学教材
- 天津华宁KTC101说明书
- 【智慧校园】-智慧校园系统方案
- 外研版高中新教材英语单词表(必修一)
评论
0/150
提交评论