1.基本的数据类型: bool 1 char 1 short 2 int 4 unsigned int 4 long 4 float 4 long long 8 double 8 2.const 符号常量 在声明时一定要赋初值 3.“,”运算符,先计算表达式一,再计算表达式二,最终结果为表达式2. a=3*5,a*4 最终结果为60 4.优先级 ! && || 优先级从高到低 5.按位操作 按位与 & 按位或 | 按位异或 ^ 按位取反 ~ 移位 左移<< 右移>> int a=3; <span style="white-space:pre"> </span>int b=5; <span style="white-space:pre"> </span>int c=a|b; <span style="white-space:pre"> </span>cout<<c<<endl;//结果为7 <span style="white-space:pre"> </span>a&b 1 6.IO控制 dec 十进制 hex 十六进制 oct 八进制 ws 提取空白符 setw() 设置域宽 7.瑞年 ( (year%4==0&&year%100!=0)||(year%400==0) ) 8. typedef声明 将一个标识符声明成某个数据类型的别名 9.enum 枚举类型 10.带有默认值形参值的函数 有默认值的形参,必须在形参列表的最后。 11.函数重载 形参的个数 或 类型不同 12.封装:将得到的数据和操作数据函数代码结合。 13.多态 强制多态 重载多态 类型多态 包含多态四种形式 强制多态:一种类型的数据转换成另一种类型的数据来实现。 C++中采用虚函数实现包含多态。虚函数是多态的精华。 14. 类成员的访问控制 公有类型 私有类型:外部不可访问 保护类型:性质和私有类似,区别在于继承过程中对产生的新类的影响不同。 15 break的作用范围 for (int i=0;i<5;i++) <span style="white-space:pre"> </span>for (int j=0;j<5;j++) <span style="white-space:pre"> </span>{ <span style="white-space:pre"> </span>if(3==j) <span style="white-space:pre"> </span>break; <span style="white-space:pre"> </span>cout<<"i="<<i<<"j="<<j<<endl; <span style="white-space:pre"> </span>} 只能跳出第二个循,对第一个循环无效。 16 枚举类型 enum weekday{mon,tues}; <span style="white-space:pre"> </span> weekday ws=mon; <span style="white-space:pre"> </span> cout<<ws<<endl; 17 <span style="white-space:pre"> </span> static 的使用 #include <iostream> using namespace std; void fun() { <span style="white-space:pre"> </span>static int i=0; <span style="white-space:pre"> </span>i++; <span style="white-space:pre"> </span>cout<<"i="<<i<<endl; } int main() { <span style="white-space:pre"> </span>fun(); <span style="white-space:pre"> </span>fun(); <span style="white-space:pre"> </span>return 0; } /* <span style="white-space:pre"> </span>输出 <span style="white-space:pre"> </span>i=1成员 <span style="white-space:pre"> </span>i=2 */ 18 UML图 19 静态数据类型 static 声明的静态 成员 在每个类中只有一个副本 使用类名::标识符 访问 具有静态的生存周期 记住:通常静态数据成员在类声明中声明,在包含类方法的文件中初始化.初始化时使用作用域操作符来指出静态成员所属的类.但如果静态成员是整型或是枚举型const,则可以在类声明中初始化!!! 类的静态成员要在外面定义: 因为需要这种方式专门为他们分配空间。非静态就不需要这种方式。 #include <iostream> using namespace std; class test { public: static int num; }; int test::num = 0; void main() { cout<<test::num <<endl; test::num = 20; cout<<test::num <<endl; } 静态成员函数可以通过类名和对象名来调用,非静态成员函数只能通过对象名来 访问 静态成员函数可以直接访问该类的静态数据和函数成员,访问静态成员必须通过对象名 20 类的友元 友元函数是在类声明中由关键字friend修饰说明的非成员函数,在它的函数体中能够通过对象名访问private 和protected成员 友元类: 友元关系是单向的 如果声明B类是A类的友元,B类的成员函数就可以访问A类的私有和保护数据,但A类的成员函数却不能访问B类的私有、保护数据。 21 常量 常类型的对象必须进行初始化,而且不能被更新 一个对象为常对象,则只能调用它的常成员函数(唯一的对外接口) const 可以对重载加以区分 22常引用:被引用的对象不能被更新。 const 类型说明符&引用名 对于在函数中无须改变其值的参数,不宜使用普通引用的方式传递,因为那会使常对象无法被传入,采用传值的方式或传递常引用可以解决这个问题 传值会耗时过多 22 –#include<文件名> 按标准方式搜索,文件位于C++系统目录的include子目录下 –#include"文件名" 首先在当前目录中搜索,若没有,再按标准方式搜索 #define 宏定义指令 –定义符号常量,很多情况下已被const定义语句取代。 –定义带参数宏,已被内联函数取代。 #undef –删除由#define定义的宏,使之不再起作用 #if 常量表达式 //当“常量表达式”非零时编译 程序正文1 #else //当“常量表达式”为零时编译 程序正文2 #endif #ifdef 标识符 程序段1 #else 程序段2 #endif 如果“标识符”经#defined定义过,且未经undef删除,则编译程序段1,否则编译程序段2。 #ifndef 标识符 程序段1 #else 程序段2 #endif 如果“标识符”未被定义过,则编译程序段1,否则编译程序段2。 //head.h #ifndef HEAD_H #define HEAD_H … class Point { … }… #endif 23 外部变量 外部函数 外部变量: 一个变量除了在定义它的源文件可以使用外,还能被其他文件使用,这就是外部变量 其他文件使用需要使用 extern 声明 外部变量为多个文件共享的全局变量 外部函数: 24 被 static 修饰的变量和函数 无法被其他编译单元引用 25 不希望被其他编译单元引用是函数和变量放在匿名的空间中 namespace { int n; void fun() <span style="white-space:pre"> </span>{ <span style="white-space:pre"> </span>} } 26 二维数组 是按照行优先存储的 数组作为函数的参数,特别注意:在被调用的函数中对形参的数组元素值的改变,主调用函数的相应元素值也会改变,这要特别注意 使用数组名作为参数,传递的是地址 void fun( int arr[][5]) //注意二维数组的使用 { <span style="white-space:pre"> </span>//函数体 } 27 对象数组的使用 对象数组定义的大小n 调用n次构造函数 指针型对象数组 不调用对象的构造函数 #include <iostream> #include <iomanip> using namespace std; class A { public: <span style="white-space:pre"> </span>int num; <span style="white-space:pre"> </span>A() <span style="white-space:pre"> </span>{ <span style="white-space:pre"> </span>cout<<"这是默认的构造函数"<<endl; <span style="white-space:pre"> </span>} <span style="white-space:pre"> </span>A(int n):num(n) <span style="white-space:pre"> </span>{ <span style="white-space:pre"> </span>cout<<"我是构造函数"<<endl; <span style="white-space:pre"> </span>} }; int main( ) { <span style="white-space:pre"> </span>A arr[5]={1,2,3,4,5}; <span style="white-space:pre"> </span>A* poinArr[3]; <span style="white-space:pre"> </span>return 0; } 28 引用 &var 表示变量var在内存中的起始地址 指针 可以赋值为整数0,表示空指针 指针的类型是它所指向变量的类型,而不是指针本身数据值的类型,任何一个指针本身的数据值都是 unsigned long int 型 void vobject;//错,不能声明void类型的变量 void *pv;//对,可以声明void类型的指针 //void指针赋值给int指针需要类型强制转换: pint = (int *)pv; 29 const char *name1="John"; //指向常量的指针 char s[]="abc"; name1=s; //正确,name1本身的值可以改变 *name1='1'; //编译时指出错误 char *const name2="John"; name2="abc";//错误,指针常量值不能改变 const int a; int const a; 这两个写法是等同的,表示a是一个int常量。 const int *a; int const *a; 前两个意思是指针指向的对象是const(指针本身的值可变,不是const) int *const a; 是指针本身不可变const(但他指向的对象可变,不是const) const int *a; 表示a是一个指针,可以任意指向int常量或者int变量,它总是把它所指向的目标当作一个int常量。也可以写成int const* a;含义相同。 int * const a; 表示a是一个指针常量,初始化的时候必须固定指向一个int变量,之后就不能再指向别的地方了。 int const * a const;这个写法没有,倒是可以写成int const * const a;表示a是一个指针常量,初始化的时候必须固定指向一个int常量或者int变量,之后就不能再指向别的地方了,它总是把它所指向的目标当作一个int常量。也可以写成const int* const a;含义相同。 例如:y=*px++ 相当于y=*(px++) (*和++优先级相同,自右向左运算) –指针可以和零之间进行等于或不等于的关系运算。例如:p==0或p!=0 30 访问数组 for(i=0; i<10; i++) cout<<a[i]; for(i=0; i<10; i++) cout<<*(a+i); for(p=a; p<(a+10); p++) cout<<*p; 数组的元素是指针型 Point *pa[2]; //由pa[0],pa[1]两个指针组成 cout<<*(*(array2+i)+j)<<" "; //或者cout<<array2[i][j]<<" "; 31 指针型的函数 函数的返回值是指针 目的:在函数结束时把大量的数据从被调函数返回到主函数中 指向函数的指针 函数名就是函数代码在内存中的起始位置 存储类型数据类型(*函数指针名) (形参表); void print_stuff(float data_to_print); void (*function_pointer)(float); print_stuff(pi); function_pointer= print_stuff; 对象指针 Point A(5,10); Piont *ptr; ptr=&A; 通过指针访问对象成员 对象指针名->成员名 ptr->getx() 相当于(*ptr).getx(); 32 this指针 隐含于每一个类的成员函数中的特殊指针。 明确地指出了成员函数当前所操作的数据所属的对象。 当通过一个对象调用成员函数时,系统先将该对象的地址赋给this指针,然后调用成员函数,成员函数对对象的数据成员进行操作时,就隐含使用了this指针。 33 对类的静态成员的访问不依赖于对象 可以用普通的指针来指向和访问静态成员 34 动态申请内存 new 类型名T(初值列表) 功能:在程序执行期间,申请用于存放T类型对象的内存空间,并依初值列表赋以初值。 结果值:成功:T类型的指针,指向新分配的内存。失败:0(NULL) delete 指针P 功能:释放指针P所指向的内存。P必须是new操作的返回值。 使用new分配的内存必须使用 delete 进行释放 new int new int() //加上括号,则表示使用0 进行初始化操作 创建数组 int *p=new int [5]();//加上()说明使用0进行初始化操作 delete 在删除数组时在指针前要加上 [] int *p=new int [5](); delete[] p; delete [] char (*fp)[3]; fp = new char[2][3]; new 类型名 T[数组的第一维长度][第二维长度].. 数组的第一维长度是任何结果为正整数的表达式而其他各维数组长度必须是结果为正整数的常量表达式, T是一个指向T类型数组的指针,数组的元素个数为除左边一维外的各维下标表达式的乘积. float (*fp)[25][10]; fp=new float [10][25][10]; vector 创建数组 vector<元素类型>数组对象名(数组长度) 注意: //vector 定义的数组对象的所有元素都会被初始化,基本类型初始化为0,为类类型将使用默认的构造函数进行初始化.vector 动态数组需要保证数组元素的类具有默认的构造函数 vector<元素类型>数组对象名{数组长度,元素初值} vector<int> arr(10); vector<int> arr(10); <span style="white-space:pre"> </span>for (int i=0;i<10;i++) <span style="white-space:pre"> </span>{ <span style="white-space:pre"> </span>cout<<arr[i]<<endl; <span style="white-space:pre"> </span>} 动态存储分配函数 malloc void *malloc( size ); 参数size:欲分配的字节数 返回值:成功,则返回void型指针。失败,则返回空指针 free void free( void *memblock ); 参数memblock:指针,指向需释放的内存。 返回值:无 头文件:<cstdlib> 和<cmalloc> 35 深复制与浅复制 两个类 C c1,c2; c2=c1; 调用默认的复制构造函数,实现的是浅复制,两者使用相同的内存单元. 36 字符串处理函数 strcat(连接),strcpy(复制),strcmp(比较),strlen(求长度),strlwr(转换为小写),strupr(转换为大写) 头文件<cstring> cin>> 输入时空格会被作为输入的分隔符 getline(cin,s2) 从键盘读入数据,直到行末,不以中间的空格输入作为分隔符. getline(cin,s2,',') 以','作为分隔符 37 继承与派生 三种继承方式 –公有继承 –私有继承 –保护继承 基类的public和protected成员的访问属性在派生类中保持不变,但基类的private成员不可直接访问。 派生类中的成员函数可以直接访问基类中的public和protected成员,但不能直接访问基类的private成员。 //通过派生类的对象只能访问基类的public成员 protected 成员的特点与作用 对建立其所在类对象的模块来说,它与private 成员的性质相同。 对于其派生类来说,它与public 成员的性质相同。 既实现了数据隐藏,又方便继承,实现代码重用。 class A { <span style="white-space:pre"> </span>protected: <span style="white-space:pre"> </span>int x; } int main() { A a; a.x=5; //错误 } class A { <span style="white-space:pre"> </span>protected: <span style="white-space:pre"> </span>int x; } class B: public A { <span style="white-space:pre"> </span>public: <span style="white-space:pre"> </span>void Function(); }; void B::Function() { <span style="white-space:pre"> </span>x=5; //正确 } 基类的继承 派生类继承了基类中除构造函数和析构函数之外的所有成员 对基类数据和函数成员的覆盖和隐藏 :如果派生类声明了一个和某基类成员同名的新成员(参数也相同),派生类隐藏了外层的同名成员. 类型兼容规则: 基类可以替代子类 38 派生类的析构和构造函数 声明构造函数时,只需要对本类中新增成员进行初始化,对继承来的基类成员的初始化,自动调用基类构造函数完成 派生类名::派生类名(基类1形参,基类2形参,...基类n形参,本类形参):基类名1(参数), 基类名2(参数), ...基类名n(参数) { 本类成员初始化赋值语句; }; //多继承且有内嵌对象时的构造函数 派生类名::派生类名(基类1形参,基类2形参,...基类n形参,本类形参):基类名1(参数), 基类名2(参数), ...基类名n(参数),对象数据成员的初始化 { 本类成员初始化赋值语句; }; //例子 class Derived: public Base2, public Base1, public Base3 { <span style="white-space:pre"> </span>//派生新类Derived,注意基类名的顺序 public:<span style="white-space:pre"> </span>//派生类的公有成员 <span style="white-space:pre"> </span>Derived(int a, int b, int c, int d): Base1(a), member2(d), member1(c), Base2(b) { } <span style="white-space:pre"> </span>//注意基类名的个数与顺序,注意成员对象名的个数与顺序 private:<span style="white-space:pre"> </span>//派生类的私有成员对象 <span style="white-space:pre"> </span>Base1 member1; <span style="white-space:pre"> </span>Base2 member2; <span style="white-space:pre"> </span>Base3 member3; }; //构造函数的调用顺序 1.调用基类构造函数,调用顺序按照它们被继承时声明的顺序(从左向右)。 2.调用成员对象的构造函数,调用顺序按照它们在类中声明的顺序。 3.派生类的构造函数体中的内容。 39 复制构造函数 若建立派生类对象时调用默认拷贝构造函数,则编译器将自动调用基类的默认拷贝构造函数。 若编写派生类的拷贝构造函数,则需要为基类相应的拷贝构造函数传递参数。例如: C::C(C &c1):B(c1) {…} 继承时的析构函数 析构函数也不被继承,派生类自行声明 声明方法与一般(无继承关系时)类的析构函数相同。 不需要显式地调用基类的析构函数,系统会自动隐式调用。 析构函数的调用次序与构造函数相反。 40同名隐藏规则 当派生类与基类中有同名成员时: 若未显式指定类名,则通过派生类对象使用的是派生类中的同名成员。 如果派生类中声明了与基类成员函数同名的新函数,即使函数的参数表不同,从基类继承的同名函数的所有重载形式也都会被隐藏。 如要通过派生类对象访问基类中被隐藏的同名成员,应使用基类名限定。 41 二义性问题 //@@@ 当派生类从多个基类派生,而这些基类又从同一个基类派生,则在访问此共同基类中的成员时,将产生二义性——采用虚基类来解决。 共同维护一个副本 解决方法一:用类名来限定c1.A::f() 或c1.B::f() 解决方法二:同名隐藏在C 中声明一个同名成员函数f(),在f()中根据需要调用A::f() 或B::f() 虚基类的使用 虚基类的引入 –用于有共同基类的场合 声明 –以virtual修饰说明基类例:class B1:virtual public B 作用 –主要用来解决多继承时可能发生的对同一基类继承多次而产生的二义性问题. –为最远的派生类提供惟一的基类成员,而不重复产生多次拷贝 注意: –在第一级继承时就要将共同基类设计为虚基类 class B{ private: int b;}; class B1 : virtualpublic B { private: int b1;}; class B2 :virtualpublic B { private: int b2;}; class C : public B1, public B2{ private: float d;} 下面的访问是正确的: C cobj; cobj.b; 虚基类及其派生类构造函数 如果虚基类有非默认的(带有形参的)构造函数, 直接和间接继承虚基类的所有派生类,都必须在构造函数的成员初始化表中列出对虚基类的初始化 //7_8.cpp #include <iostream> using namespace std; //例子: class Base0 {<span style="white-space:pre"> </span>//定义基类Base0 public: Base0(int var):var0(var){} <span style="white-space:pre"> </span>int var0; <span style="white-space:pre"> </span>void fun0() { cout << "Member of Base0" << endl; } }; class Base1: virtual public Base0 {<span style="white-space:pre"> </span>//定义派生类Base1 public:<span style="white-space:pre"> </span>//新增外部接口 <span style="white-space:pre"> </span>Base1(int var):Base0(var){} <span style="white-space:pre"> </span>int var1; }; class Base2: virtual public Base0 {<span style="white-space:pre"> </span>//定义派生类Base2 public:<span style="white-space:pre"> </span>//新增外部接口 <span style="white-space:pre"> </span>Base2(int var):Base0(var){} <span style="white-space:pre"> </span>int var2; }; class Derived: public Base1, public Base2 {<span style="white-space:pre"> </span>//定义派生类Derived public:<span style="white-space:pre"> </span>//新增外部接口 <span style="white-space:pre"> </span>Derived(int var):Base0(var),Base1(var),Base2(var){} <span style="white-space:pre"> </span>int var; <span style="white-space:pre"> </span>void fun() { cout << "Member of Derived" << endl; } }; int main() {<span style="white-space:pre"> </span>//程序主函数 <span style="white-space:pre"> </span>Derived d;<span style="white-space:pre"> </span>//定义Derived类对象d <span style="white-space:pre"> </span>d.var0 = 2;<span style="white-space:pre"> </span>//直接访问虚基类的数据成员 <span style="white-space:pre"> </span>d.fun0();<span style="white-space:pre"> </span>//直接访问虚基类的函数成员 <span style="white-space:pre"> </span>return 0; } 42 多态性 多态性是面向对象程序设计的重要特征之一。 多态性是指发出同样的消息被不同类型的对象接收时有可能导致完全不同的行 多态分为: 重载多态: 强制多态:强制转换 包含多态:类族中定义于不同类中的同名成员函数的多态行为,主要通过虚函数进行实现 参数多态:与类模板相关联 分为编译时多态(静态绑定):重载 强制 参数 运行时多态(动态绑定):包含(通过虚函数) 多态的实现: –函数重载 –运算符重载 –虚函数 运算符重载的实质:(实质是函数重载) 实现机制 –将指定的运算表达式转化为对运算符函数的调用,运算对象转化为运算符函数的实参。 –编译系统对重载运算符的选择,遵循函数重载的选择原则。 可以重载C++中除下列运算符外的所有运算符:. .* :: ?: 只能重载C++语言中已有的运算符,不可臆造新的。 不改变原运算符的优先级和结合性。 不能改变操作数个数。 经重载的运算符,其操作数中至少应该有一个是自定义类型。 //运算符重载的两种形式 重载为类成员函数。 重载为非成员函数(通常为友元函数)。 声明形式 函数类型operator 运算符(形参) { ...... } 重载为类成员函数时参数个数=原操作数个数-1(后置++、--除外) 重载为友元函数时参数个数=原操作数个数,且至少应该有一个自定义类型的形参。 双目运算符B –如果要重载B 为类成员函数,使之能够实现表达式oprd1 B oprd2,其中oprd1 为A 类对象,则B 应被重载为A 类的成员函数,形参类型应该是oprd2所属的类型。 –经重载后,表达式oprd1 B oprd2相当于oprd1.operator B(oprd2) //前置单目运算符U –如果要重载U 为类成员函数,使之能够实现表达式U oprd,其中oprd 为A类对象,则U 应被重载为A 类的成员函数,无形参。 –经重载后,表达式U oprd相当于oprd.operator U() ock& operator ++ ();<span style="white-space:pre"> </span>//前置单目运算符重载 <span style="white-space:pre"> </span> //后置单目运算符++和-- –如果要重载++或--为类成员函数,使之能够实现表达式oprd++或oprd--,其中oprd 为A类对象,则++或--应被重载为A 类的成员函数,且具有一个int 类型形参。 –经重载后,表达式oprd++相当于oprd.operator ++(0) Clock operator ++ (int);<span style="white-space:pre"> </span>//后置单目运算符重载 非成员运算符函数的设计 双目运算符B重载后,表达式oprd1 B oprd2 等同于operator B(oprd1,oprd2 ) 前置单目运算符B重载后,表达式B oprd等同于operator B(oprd ) 后置单目运算符++和--重载后,表达式oprd B等同于operator B(oprd,0 ) //8_3.cpp #include <iostream> using namespace std; class Complex {<span style="white-space:pre"> </span>//复数类定义 public:<span style="white-space:pre"> </span>//外部接口 <span style="white-space:pre"> </span>Complex(double r = 0.0, double i = 0.0) : real(r), imag(i) { }<span style="white-space:pre"> </span>//构造函数 <span style="white-space:pre"> </span>friend Complex operator + (const Complex &c1, const Complex &c2);<span style="white-space:pre"> </span>//运算符+重载 <span style="white-space:pre"> </span>friend Complex operator - (const Complex &c1, const Complex &c2);<span style="white-space:pre"> </span>//运算符-重载 <span style="white-space:pre"> </span>friend ostream & operator << (ostream &out, const Complex &c);<span style="white-space:pre"> </span>//运算符<<重载 private:<span style="white-space:pre"> </span>//私有数据成员 <span style="white-space:pre"> </span>double real;<span style="white-space:pre"> </span>//复数实部 <span style="white-space:pre"> </span>double imag;<span style="white-space:pre"> </span>//复数虚部 };<span style="white-space:pre"> </span> Complex operator + (const Complex &c1, const Complex &c2) {<span style="white-space:pre"> </span>//重载运算符函数实现 <span style="white-space:pre"> </span>return Complex(c1.real + c2.real, c1.imag + c2.imag); } Complex operator - (const Complex &c1, const Complex &c2) {<span style="white-space:pre"> </span>//重载运算符函数实现 <span style="white-space:pre"> </span>return Complex(c1.real - c2.real, c1.imag - c2.imag); } ostream & operator << (ostream &out, const Complex &c) {<span style="white-space:pre"> </span>//重载运算符函数实现 <span style="white-space:pre"> </span>out << "(" << c.real << ", " << c.imag << ")"; <span style="white-space:pre"> </span>return out; } int main() {<span style="white-space:pre"> </span>//主函数 <span style="white-space:pre"> </span>Complex c1(5, 4), c2(2, 10), c3;<span style="white-space:pre"> </span>//定义复数类的对象 <span style="white-space:pre"> </span>cout << "c1 = " << c1 << endl; <span style="white-space:pre"> </span>cout << "c2 = " << c2 << endl; <span style="white-space:pre"> </span>c3 = c1 - c2;<span style="white-space:pre"> </span>//使用重载运算符完成复数减法 <span style="white-space:pre"> </span>cout << "c3 = c1 - c2 = " << c3 << endl; <span style="white-space:pre"> </span>c3 = c1 + c2;<span style="white-space:pre"> </span>//使用重载运算符完成复数加法 <span style="white-space:pre"> </span>cout << "c3 = c1 + c2 = " << c3 << endl; <span style="white-space:pre"> </span>return 0; } 43 虚函数 虚函数是动态绑定的基础。 是非静态的成员函数。 虚函数一般不声明为内联函数,因为虚函数需要动态绑定. 在类的声明中,在函数原型之前写virtual。 virtual只用来说明类声明中的原型,不能用在函数实现时。 具有继承性,基类中声明了虚函数,派生类中无论是否说明,同原型函数都自动为虚函数。 本质:不是重载声明而是覆盖。 //调用方式:通过基类指针或引用,执行时会根据指针指向的对象的类,决定调用哪个函数 判断: 1.该函数是否与积累的虚函数有相同的名称 2.有相同的参数个数和参数类型 3.相同的返回值或者满足赋值兼容规则的指针,引用型的返回值 //只有通过基类的指针或引用调用虚函数才会发生动态绑定. //8_4.cpp #include <iostream> using namespace std; class Base1 { //基类Base1定义 public: <span style="white-space:pre"> </span>virtual void display() const;<span style="white-space:pre"> </span>//虚函数 }; void Base1::display() const { <span style="white-space:pre"> </span>cout << "Base1::display()" << endl; } class Base2: public Base1 { //公有派生类Base2定义 public: <span style="white-space:pre"> </span>void display() const;<span style="white-space:pre"> </span>//覆盖基类的虚函数 }; void Base2::display() const { <span style="white-space:pre"> </span>cout << "Base2::display()" << endl; } class Derived: public Base2 { //公有派生类Derived定义 public: <span style="white-space:pre"> </span>void display() const;<span style="white-space:pre"> </span>//覆盖基类的虚函数 }; void Derived::display() const { <span style="white-space:pre"> </span>cout << "Derived::display()" << endl; } void fun(Base1 *ptr) { //参数为指向基类对象的指针 <span style="white-space:pre"> </span>ptr->display();<span style="white-space:pre"> </span>//"对象指针->成员名" } int main() {<span style="white-space:pre"> </span>//主函数 <span style="white-space:pre"> </span>Base1 base1;<span style="white-space:pre"> </span>//定义Base1类对象 <span style="white-space:pre"> </span>Base2 base2;<span style="white-space:pre"> </span>//定义Base2类对象 <span style="white-space:pre"> </span>Derived derived;<span style="white-space:pre"> </span>//定义Derived类对象 <span style="white-space:pre"> </span>fun(&base1);<span style="white-space:pre"> </span>//用Base1对象的指针调用fun函数,调用Base1 的函数 <span style="white-space:pre"> </span>fun(&base2);<span style="white-space:pre"> </span>//用Base2对象的指针调用fun函数,调用Base2 的函数 <span style="white-space:pre"> </span>fun(&derived);<span style="white-space:pre"> </span>//用Derived对象的指针调用fun函数,调用derived 的函数 <span style="white-space:pre"> </span>return 0; } 在C++中不能声明虚构造函数但可以声明虚析构函数 一个类的析构函数是虚函数,由他派生而来的所有子类的析构函数也是虚函数. 可以调用子类的析构函数 //8_5.cpp #include <iostream> using namespace std; class Base { public: <span style="white-space:pre"> </span>virtual ~Base(); }; Base::~Base() { <span style="white-space:pre"> </span>cout<< "Base destructor" << endl; } class Derived: public Base { public: <span style="white-space:pre"> </span>Derived(); <span style="white-space:pre"> </span>~Derived(); private: <span style="white-space:pre"> </span>int *p; }; Derived::Derived() { <span style="white-space:pre"> </span>p = new int(0); } Derived::~Derived() { <span style="white-space:pre"> </span>cout << "Derived destructor" << endl; <span style="white-space:pre"> </span>delete p; } void fun(Base* b) { <span style="white-space:pre"> </span>delete b; } int main() { <span style="white-space:pre"> </span>Base *b = new Derived(); <span style="white-space:pre"> </span>fun(b); <span style="white-space:pre"> </span>return 0; } 44 纯虚函数和抽象类 抽象类位于类的层次上层,一个抽象类无法实例化,无法定义一个抽象类的对象只能通过继承机制,生成抽象类的非抽象类然后实例化 抽象类是带有纯虚函数的类 纯虚函数: virtual类型函数名(参数表)=0;//纯虚函数 作用 –抽象类为抽象和设计的目的而声明,将有关的数据和行为组织在一个继承层次结构中,保证派生类具有要求的行为。 –对于暂时无法实现的函数,可以声明为纯虚函数,留给派生类去实现。 注意 –抽象类只能作为基类来使用。 –不能声明抽象类的对象。 –构造函数不能是虚函数,析构函数可以是虚函数。 45 函数模板类模板 函数模板可以用来创建一个通用功能的函数,以支持多种不同形参,进一步简化重载函数的函数体设计。 声明方法: template <typename 标识符> 函数声明 //例子 #include<iostream> using namespace std; template<typename T> T abs(T x) { <span style="white-space:pre"> </span>return x<0?-x:x; } int main() { <span style="white-space:pre"> </span>int n=-5; <span style="white-space:pre"> </span>double d=-5.5; <span style="white-space:pre"> </span>cout<<abs(n)<<endl; <span style="white-space:pre"> </span>cout<<abs(d)<<endl; } 类模板的作用 使用类模板使用户可以为类声明一种模式,使得类中的某些数据成员、某些成员函数的参数、某些成员函数的返回值,能取任意类型(包括基本类型的和用户自定义类型)。 类模板: template <模板参数表> class 类名 {类成员声明} 如果需要在类模板以外定义其成员函数,则要采用以下的形式 template <模板参数表> 类型名类名<T>::函数名(参数表) //例子 //9_2.cpp #include <iostream> #include <cstdlib> using namespace std; struct Student {<span style="white-space:pre"> </span>// 结构体Student <span style="white-space:pre"> </span>int id;<span style="white-space:pre"> </span>//学号 <span style="white-space:pre"> </span>float gpa;<span style="white-space:pre"> </span>//平均分 }; template <class T> //类模板:实现对任意类型数据进行存取 class Store { private: <span style="white-space:pre"> </span>T item;<span style="white-space:pre"> </span>// item用于存放任意类型的数据 <span style="white-space:pre"> </span>bool haveValue;<span style="white-space:pre"> </span>// haveValue标记item是否已被存入内容 public: <span style="white-space:pre"> </span>Store();<span style="white-space:pre"> </span>// 缺省形式(无形参)的构造函数 <span style="white-space:pre"> </span>T &getElem();<span style="white-space:pre"> </span>//提取数据函数 <span style="white-space:pre"> </span>void putElem(const T &x); //存入数据函数 }; //以下实现各成员函数。 template <class T><span style="white-space:pre"> </span>//缺省构造函数的实现 Store<T>::Store(): haveValue(false) { } template <class T> //提取数据函数的实现 T &Store<T>::getElem() { <span style="white-space:pre"> </span>if (!haveValue) {<span style="white-space:pre"> </span>//如果试图提取未初始化的数据,则终止程序 <span style="white-space:pre"> </span>cout << "No item present!" << endl; <span style="white-space:pre"> </span>exit(1);<span style="white-space:pre"> </span>//使程序完全退出,返回到操作系统。 <span style="white-space:pre"> </span>//参数可用来表示程序终止的原因,可以被操作系统接收 <span style="white-space:pre"> </span>} <span style="white-space:pre"> </span>return item; // 返回item中存放的数据 } template <class T><span style="white-space:pre"> </span>//存入数据函数的实现 void Store<T>::putElem(const T &x) { <span style="white-space:pre"> </span>haveValue = true;<span style="white-space:pre"> </span>// 将haveValue 置为true,表示item中已存入数值 <span style="white-space:pre"> </span>item = x;<span style="white-space:pre"> </span>// 将x值存入item } int main() { <span style="white-space:pre"> </span>Store<int> s1, s2;<span style="white-space:pre"> </span>//定义两个Store<int>类对象,其中数据成员item为int类型 <span style="white-space:pre"> </span>s1.putElem(3);<span style="white-space:pre"> </span>//向对象S1中存入数据(初始化对象S1) <span style="white-space:pre"> </span>s2.putElem(-7);<span style="white-space:pre"> </span>//向对象S2中存入数据(初始化对象S2) <span style="white-space:pre"> </span>cout << s1.getElem() << " " << s2.getElem() << endl;<span style="white-space:pre"> </span>//输出对象S1和S2的数据成员 <span style="white-space:pre"> </span>Student g = { 1000, 23 };<span style="white-space:pre"> </span>//定义Student类型结构体变量的同时赋以初值 <span style="white-space:pre"> </span>Store<Student> s3;<span style="white-space:pre"> </span>//定义Store<Student>类对象s3,其中数据成员item为Student类型 <span style="white-space:pre"> </span>s3.putElem(g); //向对象D中存入数据(初始化对象D) <span style="white-space:pre"> </span>cout << "The student id is " << s3.getElem().id << endl;<span style="white-space:pre"> </span>//输出对象s3的数据成员 <span style="white-space:pre"> </span>Store<double> d;<span style="white-space:pre"> </span>//定义Store<double>类对象s4,其中数据成员item为double类型 <span style="white-space:pre"> </span>cout << "Retrieving object d... "; <span style="white-space:pre"> </span>cout << d.getElem() << endl; //输出对象D的数据成员 <span style="white-space:pre"> </span>//由于d未经初始化,在执行函数D.getElement()过程中导致程序终止 <span style="white-space:pre"> </span>return 0; } 46 STL是泛型程序设计的一个范例 –容器(container) –迭代器(iterator) –算法(algorithms) –函数对象(function object) 命名空间(namespace) 一个命名空间将不同的标识符集合在一个命名作用域(named scope)内 –为了解决命名冲突 –例如,声明一个命名空间NS: namspace NS { class File; void Fun (); } 则引用标识符的方式如下, NS:: File obj; NS:: Fun (); 没有声明命名空间的标识符都处于无名的命名空间中 可以用using来指定命名空间 –例如,经过以下声明:using NS::File;在当前作用域中就可以直接引用File –using namespace std;命名空间std中所有标识符都可直接引用 47 异常处理 #include<iostream.h> int Div(int x,int y); int main() { <span style="white-space:pre"> </span>try <span style="white-space:pre"> </span>{ cout<<"5/2="<<Div(5,2)<<endl; <span style="white-space:pre"> </span>cout<<"8/0="<<Div(8,0)<<endl; <span style="white-space:pre"> </span>cout<<"7/1="<<Div(7,1)<<endl; <span style="white-space:pre"> </span>} <span style="white-space:pre"> </span>catch(int) <span style="white-space:pre"> </span>{ cout<<"except of deviding zero.\n"; } <span style="white-space:pre"> </span>cout<<"that is ok.\n"; <span style="white-space:pre"> </span>} int Div(int x,int y) { if(y==0) throw y; return x/y; }