程序设计基础 第四章 数组 4.5 动态储存 C++的动态存储分配机制可以根据需要在程序运行时建立和撤销对象
4.5.1 new和delete操作符 new 运算符动态分配堆内存,从堆分配一块“类型”大小的存储空间,返回首地址 delete 运算符释放已分配的内存空间
☆new的使用形式 指针变量 = new 类型(常量->可以不写,因为是初始化值) 指针变量 = new 类型[表达式]; ☆创建数组对象时,不能为对象指定初始值
☆delete的使用形式 delete 指针变量 delete [] 指针变量 ; 指针变量必须是new出来的。建议delete完之后再给指针赋一个NULL值。
4.5.2 动态储存的应用 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 #include <iostream> using namespace std;int main () { int * p = NULL ; p = new int ; if (p == NULL ) { cout << "allocation faiure\n" ; } *p = 20 ; cout << *p << endl; delete p; p = NULL ; }
☆动态数组 一般的动态数组 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 #include <iostream> using namespace std ;int main () { int * p = NULL , * t ; int i ; p = new int [10 ] ; if ( p == NULL ) { cout << "allocation faiure\n" ; return 1 ; } for ( i = 0 ; i < 10 ; i ++ ) p[i] = 100 + i ; cout << endl ; for ( t = p ; t < p+10 ; t ++ ) cout << *t << " " ; cout << endl ; delete [] p ; }
函数申请动态数组 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 #include <iostream> using namespace std;void App (int *& pa, int n) ; 引用int main () { int * ary = NULL , * t, i, n; cout << "n= " ; cin >> n; App (ary, n); 调用函数时实参ary的值(NULL )赋给形参变量pa for (i = 0 ; i < n; i++) { ary[i] = 10 + i; cout << ary[i]<<endl; } delete []ary; ary=NULL ; }void App (int *& pa, int len) { pa = new int [len]; if (pa == NULL ) { cout << "allocation faiure\n" ; return ; } for (int i = 0 ; i < len; i++) pa[i] = 0 ; }
第五章 集合与结构 5.5 ☆链表 单向链表的存取方式:顺序存取 头指针:head,存在的目的是为了找到node1
静态链表 所谓静态链表,指的是每个节点并不是按照程序运行时的需求而动态创建的,而是在一开始就创建好,并用指针连接起来。和数组比较类似,但是时顺序访问,不能通过下标访问。
动态链表 第六章 类与对象 第三节 类的其他成员 常成员(常量) 定义常成员常用const约束,初始化之后被约束,不可修改。
常数据成员 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 const int M; 类名() : M(5 ) { } 此后M的值就不可修改了。##### 常对象 让对象的全部成员在作用域中约束为只读 #include<iostream> using namespace std; class T_class { public : int a, b; T_class(int i, int j) { a = i; b = j; } }; int main() { const T_class t1 (1 , 2 ); T_class t2 (3 , 4 ); t2.a = 7 ; t2.b = 8 ; cout << "t1.a=" << t1.a << '\t' << "t1.b=" << t1.b << endl; cout << "t2.a=" << t2.a << '\t' << "t2.b=" << t2.b << endl; }
常成员函数 常函数的this指针被约束为指向常量的常指针。
1 2 3 4 5 class X { int a; int g ()const {return a++}; }
静态成员 类成员冠以static声明时,称为静态成员。
静态数据成员为同类对象共享。
静态成员函数与静态数据成员协同操作。
静态数据成员 #include<iostream>
using namespace std;
class counter
{
static int num; //设定静态成员
public:
void setnum(int i) { num = i; }
void shownum() { cout << num << '\t'; }
};
int counter::num = 0; //类外定义num
int main()
{
counter a, b;
a.shownum(); b.shownum();
a.setnum(10);
a.shownum(); b.shownum(); // 这里a和b的输出都是10,因为静态数据共享,所以a中的num变了,b中的也要变
cout << endl;
}
静态成员函数 静态成员函数数冠以关键字static
静态成员函数没有this指针,只能对静态数据操作
在类外调用静态成员函数用 “类名 :: ”作限定词,或通过对象调用
性质上和静态数据成员差不多
友元 友元是对类操作的辅助手段。友元能够引用类中本来被隐蔽的信息
使用友元目的是基于程序的运行效率
运算符重载的某些场合需要使用友元
友元可以是函数,也可以是类
友元关系是非传递的。即 Y 是 X 的友元,Z 是 Y 的友元,但 Z 不一定是X的友元
友元函数 class Point
{
public:
Point(double xi, double yi) { X = xi; Y = yi; }
friend double Distance(Point& a, Point& b); //传入两个类类型的参数
private: double X, Y; //X和Y不可直接访问
};
double Distance(Point& a, Point& b)
{
double dx = a.X - b.X; //通过友元函数访问X
double dy = a.Y - b.Y; //通过友元函数访问Y
return sqrt(dx * dx + dy * dy);
}
友元类 若F类是A类的友元类,则F类的所有成员函数都是A类的友元函数
友元类通常设计为一种对数据操作或类之间传递消息的辅助类
来看一个例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 #include<iostream> using namespace std ; class A { friend class B ; public : void Display () { cout << x << endl ; } ; private : int x ; } ; plan1:在B中为A创建一个类对象 class B { public : void Set ( int i ) { Aobject . x = i ; } void Display () { Aobject . Display () ; } private : A Aobject ; } ; int main () { B Bobject ; Bobject . Set ( 100 ) ; Bobject . Display () ; } plan2:直接对A操作 class B { public : void Set (A&Aobject,int i ) { Aobject.x = i; } void Display (A& Aobject ) { Aobject.Display(); } }; int main () { B Bobject; A Aobject; Bobject.Set(Aobject, 100 ); Bobject.Display(Aobject); }
第四节 类的包含 类的包含(称为has A)是程序设计中一种软件重用技术。即定义一个新的类时,通过编译器把另一个类 “抄”进来。 当一个类中含有已经定义的类类型成员,带参数的构造函数对数据成员初始化,须使用初始化语法形式。 构造函数 ( 变元表 ) : 对象成员1( 变元表 ) , … , 对象成员n ( 变元表 ) ;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 #include<iostream> using namespace std ;class A {public : A(int x) :a(x) {} int a; };class B {public : B(int x, int y) : aa(x) { b = y; } void out () { cout << "aa = " << aa.a << endl << "b = " << b << endl; } private :int b; A aa; };int main () {B objB (3 , 5 ) ; objB.out (); }
第八章 继承 第一节 类之间的关系 1 2 3 has-A :包含关系,用以描述一个类由多个“部件类”构成。实现has-A 关系用类成员表示,即一个类中的数据成员是另一种已经定义的类。 uses-A :一个类部分地使用另一个类。通过类之间成员函数的相互联系,定义友元或对象参数传递实现。 is-A :机制称为“继承”。关系具有传递性,不具有对称性。
具有传递性,但是不具有对称性。 继承 是类之间定义的一种重要关系 一个 B 类继承A类,或称从类 A 派生类 B 类 A 称为基类(父类),类 B 称为派生类(子类)
第二节 基类和派生类 类继承的语法形式:
1 2 3 4 5 6 7 8 9 10 11 class 派生类名 : 基类名表 { 数据成员和成员函数声明 }; 基类名表的构成: 访问控制 基类名1 , 访问控制 基类名2 ,… , 访问控制 基类名n 而访问控制又表示派生类对基类的继承方式,使用关键字: public公有继承 基类的公有成员private 私有继承 基类的公有成员和保护成员protected 保护继承 基类的公有成员和保护成员 ☆不论种方式继承基类,派生类都不能直接使用基类的私有成员
访问控制 公有继承 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 #include<iostream> using namespace std ;class A {public : void get_XY () { cout << "Enter two numbers of x, y : " ; cin >> x >> y; } void put_XY () { cout << "x = " << x << ", y = " << y << '\n' ; }protected : int x, y; };class B : public A {public : int get_S () { return s; }; void make_S () { s = x * y; }; protected : int s; };class C : public B {public : void get_H () { cout << "Enter a number of h : " ; cin >> h; } int get_V () { return v; } void make_V () { make_S(); v = get_S() * h; } protected : int h, v; };int main () { ··· }
还有一种方式就是将变量设置为private,利用函数调用它。语法为:对象.函数名();
私有继承 访问私有的方法和上面一样
保护继承 略
访问声明 1 2 3 4 访问方式:基类类名::函数名1. 访问声明仅调整名字的访问权限。被声明时,成员不能说明类型,函数不能带参数和返回值类型2. 访问声明不可降低或提升基类成员的可访问性。比如:protected 只能给protected 声明。3. 相同访问域的重载函数声明一次就都可以用了,不同访问域的不能操作,基类派生类同名的也不能操作
重名成员 派生类定义了与基类同名的成员,在派生类中访问同名成员时屏蔽了基类的同名成员 在派生类中使用基类的同名成员,显式地使用类名限定符:类名 :: 成员
重名数据成员 不重名的可以直接用”对象名.数据成员”引用 重名的基类成员要用”对象名.基类名::数据成员”引用
重名成员函数 与重名数据成员的使用方法一样。 派生类也是基类,基类指针可以指向派生类对象 派生类中定义与基类同名的成员函数,称为重载成员函数
派生类中访问静态成员 基类定义的静态成员,将被所有派生类共享 根据静态成员自身的访问特性和派生类的继承方式,在类层次体系中具有不同的访问性质 派生类中访问静态成员,用以下形式显式说明: 类名 :: 成员/ 对象名 . 成员 ☆私有继承到派生类之后可以在派生类中使用,可以在public中用。派生类中无需声明也可以直接用函数名或成员名。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 class B { public : static void Add () { i++ ; } static int i; void out () { cout<<"static i=" <<i<<endl; } };int B::i=0 ;class D : private B { public : void f () { i=5 ; Add (); B::i++; B::Add (); } };
以上几行的++都是对同一个i进行操作。
第三节 基类的初始化 建立一个类层次后,通常创建某个派生类的对象,包括使用基类的数据和函数 C++提供一种机制,在创建派生类对象时用指定参数调用基类的构造函数来初始化派生类继承基类的数据 派生类构造函数声明为: 派生类构造函数 ( 变元表 ) : 基类 ( 变元表 ) , 对象成员1( 变元表 )… 对象成员n ( 变元表 ) ; 构造函数执行顺序:基类–>对象成员–>派生类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 #include <iostream> using namespace std;class parent_class { int data1, data2;public : parent_class (int p1, int p2) { data1 = p1; data2 = p2; } int inc1 () { return ++data1; } int inc2 () { return ++data2; } void display () { cout << "data1=" << data1 << " , data2=" << data2 << endl; } };class derived_class : private parent_class { int data3; parent_class data4;public : derived_class (int p1, int p2, int p3, int p4, int p5) : parent_class (p1, p2), data4 (p3, p4) { data3 = p5; } int inc1 () { return parent_class::inc1 (); } int inc3 () { return ++data3; } void display () { parent_class::display (); data4.display (); cout << "data3=" << data3 << endl; } };int main () { derived_class d1 (17 , 18 , 1 , 2 , -5 ) ; d1.inc1 (); d1.display (); }
第四节 继承的应用实例 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 #include <iostream> using namespace std;#include <iomanip> class Point { friend ostream& operator << (ostream&, const Point&);public : Point (int = 0 , int = 0 ); void setPoint (int , int ) ; int getX () const { return x; } int getY () const { return y; }protected : int x, y; };class Circle : public Point { friend ostream& operator << (ostream&, const Circle&); public : Circle (double r = 0.0 , int x = 0 , int y = 0 ); void setRadius (double ) ; double getRadius () const ; double area () const ; protected : double radius; };class Cylinder :public Circle { friend ostream& operator <<(ostream&, const Cylinder&); public : Cylinder (double h = 0.0 , double r = 0.0 , int x = 0 , int y = 0 ); void setHeight (double ) ; double getHeight () const ; double area () const ; double volume () const ; protected : double height; }; Point::Point (int a, int b) { setPoint (a, b); }void Point::setPoint (int a, int b) { x = a; y = b; } ostream& operator << (ostream& output, const Point& p) { output << '[' << p.x << "," << p.y << "]" ; return output; } Circle::Circle (double r, int a, int b) : Point (a, b) { setRadius (r); }void Circle::setRadius (double r) { radius = (r >= 0 ? r : 0 ); }double Circle::getRadius () const { return radius; }double Circle::area () const { return 3.14159 * radius * radius; } ostream& operator << (ostream& output, const Circle& c) { output << "Center = " << '[' << c.x << "," << c.y << "]" << "; Radius = " << setiosflags (ios::fixed | ios::showpoint) << setprecision (2 ) << c.radius; return output; } Cylinder::Cylinder (double h, double r, int x, int y) :Circle (r, x, y) { setHeight (h); } void Cylinder::setHeight (double h) { height = (h >= 0 ? h : 0 ); } double Cylinder::getHeight () const { return height; } double Cylinder::area () const { return 2 * Circle::area () + 2 * 3.14159 * radius * height; } double Cylinder::volume () const { return Circle::area () * height; } ostream& operator << (ostream & output, const Cylinder & cy) { output << "Center = " << '[' << cy.x << "," << cy.y << "]" << "; Radius = " << setiosflags (ios::fixed | ios::showpoint) << setprecision (2 ) << cy.radius << "; Height = " << cy.height << endl; return output; } int main () { Point p (72 , 115 ) ; cout << "The initial location of p is " << p << endl; p.setPoint (10 , 10 ); cout << "\nThe new location of p is " << p << endl; Circle c (2.5 , 37 , 43 ) ; cout << "\nThe initial location and radius of c are\n" << c << "\nArea = " << c.area () << "\n" ; c.setRadius (4.25 ); c.setPoint (2 , 2 ); cout << "\nThe new location and radius of c are\n" << c << "\nArea = " << c.area () << "\n" ; Cylinder cyl (5.7 , 2.5 , 12 , 23 ) ; cout << "\nThe initial location, radius ang height of cyl are\n" << cyl << "Area = " << cyl.area () << "\nVolume = " << cyl.volume () << '\n' ; cyl.setHeight (10 ); cyl.setRadius (4.25 ); cyl.setPoint (2 , 2 ); cout << "\nThe new location, radius ang height of cyl are\n" << cyl << "Area = " << cyl.area () << "\nVolume = " << cyl.volume () << "\n" ; }
解析:我们想便捷的计算点,圆和圆柱相关的东西,但它们之中有一部分参量是重复的,我们不想再重复输入,所以选择继承的方式。 1.点类中要有点的构造函数初始化点坐标,为点赋值的函数,返回X、Y的函数,重载输出流(定义输出类对象时输出点的坐标) 2.圆类中公有继承,还要有构造函数初始化点坐标和半径,为半径赋值的函数,返回半径和面积的函数,重载输出流。(定义输出类对象时输出点的坐标,半径) 3.圆柱类中公有继承,构造函数初始化点坐标、半径、高度,为高度赋值的函数。返回高度值的函数,返回面积的函数,返回体积的函数,重载输出流(定义输出类对象时输出点的坐标,半径,高度)。 在main函数中: 先用Point p(72,115);创建并初始化对象 再利用setPoint(置点新的数据值) 后面的都一样,先set新的值,再通过形如area()等函数返回要的结果。
类的继承和类的包含的比较 对比两种方式我们发现,二者在Circle类中声明outpoint的方式不一样,在输出时的方式也不一样。
1 2 3 4 5 6 7 继承的方式: 构造函数初始化对象时,利用基类名初始化 在派生类的函数体内要通过(类名::函数名())来声明;在派生类其他地方通过(类名::函数名)来声明。 通过(派生类对象.函数名())的方式调用: 构造函数初始化对象时,利用被包含类的对象名初始化。 包含的方式要通过(被包含对象名.函数名())的方式在函数体内声明 通过(包含对象名.被包含对象名.函数名())的方式调用
第五节 多继承 一个类有多个直接基类的继承关系称为多继承 多继承声明语法:
1 2 3 4 class 派生类名 : 访问控制 基类名1 , 访问控制 基类名2 , … , 访问控制 基类名n { 数据成员和成员函数声明 };
多继承的派生类构造和访问 1 2 3 1.多个基类的派生类构造函数可以用初始式调用基类构造函数初始化数据成员。 2.执行顺序与单继承构造函数情况类似。多个直接基类构造函数执行顺序取决于定义派生类时指定的各个继承基类的顺序。 3.一个派生类对象拥有多个直接或间接基类的成员。不同名成员访问不会出现二义性。如果不同的基类有同名成员,派生类对象访问时应该加以识别。 (同名可能会有二义性)
☆若派生类在继承基类的公有函数时,公有函数有同名的函数时,不可直接调用,会有二义性。例如: 假设C类继承了A和B类,C有一个对象叫做d,A类和B类中各有一个叫做Get的函数,则不可通过d.Get的方法去调用Get函数(其他的可以)。如果要调用,只能用d.A::Get()/d.B::Get()的方式调用函数;
虚继承 如果一个派生类从多个基类派生,而这些基类又有一个共同的基类,则在对该基类中声明的名字进行访问时,可能产生二义性。我们现在假设有一个基类B,它有两个派生类B1B2,而这两个派生类又为D的基类。
非虚继承 初始化时通过B->B1,B->B2的路径初始化基类,所以两次调用了B的构造函数。若B中有变量b和对象d,则D在继承时同时继承了来自B1的b和来自B2的b,会产生二义性,所以会在调用时要显示的说明是从哪个直接基类派生的成员。d.B1.::b/d.B2::b。
虚继承 如非虚继承所述,如果一个派生类从多个基类派生,而这些基类又有一个共同的基类,则在对该基类中声明的名字进行访问时,可能产生二义性。要使这个公共基类在派生类中只产生一个子对象,必须对这个基类声明为虚继承,使这个基类成为虚基类。 虚继承声明使用关键字virtual。 如假设有如上的情况,只需使B1和B2在继承时在访问控制前面加一个virtual即可。 则在初始化时仅调用了一次B类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 using namespace std ;class B {public : B (int x = 10) { b = x ; cout << "Constructor called:B\n " ; } ~ B () { cout << "Destructor called:B\n " ; } int b ; };class B1 :virtual public B {public : B1 (int x1 = 11 , int y1 = 21 ) :B (x1 ) { b1 = y1 ; cout << "Constructor called:B1\n " ; } ~ B1 () { cout << "Destructor called:B1\n " ; } int b1 ; };class B2 :virtual public B {public : B2 (int x2 = 12 , int y2 = 22 ) :B (x2 ) { b2 = y2 ; cout << "Constructor called:B2\n " ; } ~ B2 () { cout << "Destructor called:B2\n " ; } int b2 ; };class D :public B1 , public B2 {public : D (int i = 1 , int j1 = 2 , int j2 = 3 , int k = 4 ) :B (i ), B1 (j1 ), B2 (j2 ) { d = k ; cout << "Constructor called:D\n " ; } ~ D () { cout << "Destructor called:D\n " ; } int d ; };void test () { D objD ; cout << "objD.b=" << objD .b << endl ; cout << "objD.b1=" << objD .b1 << "\n objD.b2=" << objD .b2 << "\n objD.d" << objD .d << endl ; B1 objB1 ; cout << "objB1.b=" << objB1 .b << "\n objB1.b1=" << objB1 .b1 << endl ; } int main () { test (); } 输出的值依次为:1 ,21 ,22 ,4 11 ,21
☆需要注意的是:D的对象在调用b1,b2时,直接调用基类的构造函数,而忽略D的调用。比如:在创建类对象时初始值为(20,05,06,06)时输出为 20,21,22,06。 一个类在类体系中可以作为虚基类和非虚基类,这取决于继承方式,与本身的定义无关。
第九章 虚函数与多态性 联编:指一个程序模块、代码之间互相关联的过程。 多态性:是指一个名字,多种语义;或界面相同,多种实现。重载函数是多态性的一种简单形式。 动态联编:指程序联编推迟到运行时进行,所以又称为晚期联编。如:switch 语句和 if 语句是动态联编的例子。虚函数允许函数调用与函数体的联系在运行时才进行。
9.1 静态联编 普通成员函数重载可表达为两种形式:
在一个类说明中重载。例如: void Show ( int , char ) ;void Show ( char * ,float ) ;
基类的成员函数在派生类重载。有 3 种编译区分方法:1 2 3 4 5 6 7 (1 )根据参数的特征加以区分 例如:void Show ( int , char );与void Show ( char * , float );不是同一函数,编译能够区分 (2 )使用“ :: ”加以区分(类名::函数名()) 例如:A :: Show ( );有别于 B :: Show ( ); (3 )根据类对象加以区分(根据this指针类型区分 例如:Aobj . Show ( )调用A :: Show ( );Bobj . Show ( ) 调用 B :: Show ( ) ☆以上方法实际上都是根据类或对象来确定成员函数隐式参数this指针的不同关联类型的,因此重载函数在编译阶段即完成匹配。
9.2 类指针的关系 概述: 基类指针和派生类指针与基类对象和派生类对象4种可能匹配:
1 2 3 4 直接用基类指针引用基类对象;(安全) 直接用派生类指针引用派生类对象;(安全) 用基类指针引用一个派生类对象; 用派生类指针引用一个基类对象。
我们这一讲主要讲述后两种情况。
基类指针引用派生类对象 正如计算机书籍也是书籍一样,基类指针引用派生类对象是安全的。 一般情况下,基类指针指向派生类对象时,只能引用基类成员。 ☆☆☆如果试图引用派生类中特有的成员,则必须通过强制类型转换把基类指针转换成派生类指针,否则会出错。
1 2 3 4 5 6 7 8 大概的使用方式: A * p A A_obj B B_obj p = & A_obj p = & B_obj 利用 p,可以通过 B_obj 访问所有从 A 类继承的元素 ☆但不能用 p访问 B 类自定义的元素,除非用了显式类型转换(在用指针时前面加一个(派生类类名*)指针)
实例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 #include <iostream> #include <cstring> using namespace std;class A_class { char name[20 ];public : void put_name (const char * s) { strcpy_s (name, s); } void show_name () { cout << name << "\n" ; } };class B_class : public A_class { char phone_num[20 ];public : void put_phone (const char * num) { strcpy_s (phone_num, num); } void show_phone () { cout << phone_num << "\n" ; } };int main () { A_class* A_p; A_class A_obj; B_class B_obj; A_p = &A_obj; A_p->put_name ("Xiao xiang jun" ); A_p->show_name (); A_p = &B_obj; A_p->put_name ("Xie hong jin" );A_p->show_name (); B_obj.put_phone ("13532877395" ); ((B_class*)A_p)->show_phone (); }
派生类引用基类对象 派生类指针只有经过强制类型转换之后,才能引用基类对象。 两种方法:
法一 正常写完其他部分,然后再main函数中创建一个派生类对象和指向它的指针,利用强制类型转换对象/指针后,就可以直接调用基类成员函数了。
1 2 3 4 5 6 7 8 9 10 例如:int main() { DateTime dt(2004 ,12 ,30 ,15 ,25 ,00 ); DateTime* pdt = &dt; ((Date)dt).Print (); dt.Print (); ((Date*)pdt)->Print (); pdt->Print (); }
法二 在需要用到基类函数的函数体中((基类类名*)指针)this–>基类函数名()。 例如:(以下是一个派生类函数)
1 2 3 4 5 void Print() { ((Date *)this) -> Print(); cout << hours << ':' << minutes << ':' << seconds << '\n' ; }
9.3 ☆虚函数和动态联编 概念: 冠以关键字 virtual 的成员函数称为虚函数 实现运行时多态的关键首先是要说明虚函数,另外,必须用基类指针调用派生类的不同实现版本 (先说明,基类指派生)
9.3.1 虚函数和基类指针 ☆基类指针虽然获取派生类对象地址,却只能访问派生类从基类继承的成员,没继承的不能用!!!只能显示的调用或者强制类型转换。 例如:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 #include <iostream> using namespace std;class Base {public : Base (char xx) { x = xx; } void who () { cout << "Base class: " << x << "\n" ; }protected : char x; };class First_d : public Base {public : First_d (char xx, char yy) :Base (xx) { y = yy; } void who () { cout << "First derived class: " << x << ", " << y << "\n" ; }protected : char y; };class Second_d : public First_d {public : Second_d (char xx, char yy, char zz) : First_d (xx, yy) { z = zz; } void who () { cout << "Second derived class: " << x << ", " << y << ", " << z << "\n" ; }protected : char z; };int main () { Base B_obj ('A' ) ; First_d F_obj ('T' , 'O' ) ; Second_d S_obj ('E' , 'N' , 'D' ) ; Base* p; p = &B_obj; p->who (); p = &F_obj; p->who (); p = &S_obj; p->who (); F_obj.who (); ((Second_d*)p)->who (); } 所以输出为:A,T,E.TO,END
☆☆☆但是其实我们也感觉得到,好麻烦,那怎么办?能不能让指针指向派生类对象时,直接可以调用类对象中的who?因为强制或显式调用真的好麻烦。欸,说来就来,C++提供了一个虚函数解释机制,,可以让基类指针依赖运行时的地址,调用不同类版本的成员函数(装逼一点可以叫它动态性质) ☆☆☆此时,我们只需要再基类的who函数前面加一个virtual,这样who函数就会变成一个虚函数,后面的who函数就会默认为虚函数。然后我们就可以达到我们的目的get√。
1 2 3 4 5 ☆☆☆虽然这么做很爽,但是还有4 点要注意一下的:1. 一旦一个成员被说明成虚函数,则无论派生了多少次,所有界面相同的重载函数都保持虚特性2. 虚函数必须是类的成员函数。不能说明为全局函数或者静态成员函数。因为虚函数的动态联编必须再类层次中依靠this 指针实现。3. 不能将友元说明成虚函数,但虚函数可以是另一个类的友元4. 析构函数可以是虚函数,但是构造函数不能是虚函数。
9.3.2 虚函数的重载特性 ☆重定义基类的虚函数是函数重载的一种特殊的形式,它与一般的函数重载不同,有一个要求(人家都这么方便人了,提点要求怎么了>﹏<)。在派生类中重载基类的虚函数要求函数名、返回类型、参数个数、参数类型和顺序完全相同(完全喔,这个要求不高把?)。如果返回值类型不同,一开始就不小心错了,那我算C++错误重载;但是,要是其他的错误,就直接没有虚特性了(手动狗头)。 可是,你为什么要提这样的要求呢?因为表面上它们是相同的,但是他们隐含的this指针不同,其关联类型分别是重载它们的派生类。C++的虚特性只负责把基类this指针的关联类型转换成当前指向对象的类类型,而不能改变函数其他参数的性质。
9.3.3 虚析构函数 1 2 构造函数不能是虚函数。建立一个派生类对象时,必须从类层次的根开始,沿着继承路径逐个调用基类的构造函数 析构函数可以是虚的。虚析构函数用于指引 delete 运算符正确析构动态对象
例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 #include <iostream> using namespace std;class A {public : ~A () { cout << "A::~A() is called.\n" ; } };class B : public A {public : ~B () { cout << "B::~B() is called.\n" ; } };int main () { A* Ap = new B; B* Bp2 = new B; cout << "delete first object:\n" ; delete Ap; Ap = NULL ; cout << "delete second object:\n" ; delete Bp2; Bp2 = NULL ; } 在对A的基类指针进行delete 时,只析构了基类,没析构派生类。 解决方法也很简单,在基类的析构函数前面加一个virtual 。 啊?那有什么用啊,感觉也不影响程序运行?哈哈,其实没什么用,但是用了没坏处,那就用呗
纯虚函数与抽象类 基类往往表示一个抽象概念,在一个工作系统中员工employee就是一个基类,可以派生出管理人员啊之类的,不同的人员有不同的工资计算方式。但是专门在基类里面定义一个工资计算函数是没有意义的,所以我们可以把基类的工资计算函数说明为虚函数,仅说明一个公共的界面,而由各自派生类提供各自的实现版本。一个具有纯虚函数的基类叫做抽象类。
9.4.1 纯虚函数 纯虚函数是一个在基类中说明的虚函数,在基类中没有定义,要求任何派生类都定义自己的版本 纯虚函数为各派生类提供一个公共界面(真的虚,啥都没有) ☆纯虚函数说明形式:virtual 类型 函数名(参数表)= 0 ; 一个具有纯虚函数的基类称为抽象类。 ☆☆☆若一个类公有继承了一个抽象类,那么它也为抽象类。啊?但是我不想它也抽象怎么办?那就要在派生类中声明它(不带virtual)并且提供它们的定义
9.4.2 抽象类 怎么说,抽象类好用吧?但是这哥们要求也很多:
1 2 3 1.抽象类只能用作其他类的基类 2.抽象类不能建立类对象 3.抽象类不能用作参数类型、函数返回类型或显式类型转换
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 通过指针的方式使纯虚函数在派生类中实现不同版本。#include <iostream> using namespace std;class figure {protected : double x, y;public : void set_dim (double i, double j = 0 ) { x = i; y = j; } virtual void show_area () = 0 ; };class triangle : public figure {public : void show_area () { cout << "Triangle with high " << x << " and base " << y << " has an area of " << x * 0.5 * y << "\n" ; } };class square : public figure {public : void show_area () { cout << "Square with dimension " << x << "*" << y << " has an area of " << x * y << "\n" ; } };class circle : public figure {public : void show_area () { cout << "Circle with radius " << x; cout << " has an area of " << 3.14 * x * x << "\n" ; } };int main () { figure* p; triangle t; square s; circle c; p = &t; p->set_dim (10.0 , 5.0 ); p->show_area (); p = &s; p->set_dim (10.0 , 5.0 ); p->show_area (); p = &c; p->set_dim (9.0 ); p->show_area (); }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 或者采用引用的方式实现 #include<iostream> using namespace std;class Number {public : Number(int i) { val = i; } virtual void Show() = 0 ;protected : int val ; };class Hex_type : public Number {public : Hex_type(int i) : Number(i) { } void Show() { cout << "Hexadecimal:" << hex << val << endl; } };class Dec_type : public Number {public : Dec_type(int i) : Number(i) { } void Show() { cout << "Decimal: " << dec << val << endl; } };class Oct_type : public Number {public : Oct_type(int i) : Number(i) { } void Show() { cout << "Octal: " << oct << val << endl; } }; void fun (Number& n) { n.Show(); } int main() { Dec_type n1(50 ); fun (n1) ; Hex_type n2(50 ); fun (n2) ; Oct_type n3(50 ); fun (n3) ; } 哎呀这个地方就是往fun 函数的参数表里面放基类,然后再调用各自类的show。
第十章 模板 模板把函数或类要处理的数据类型参数化,表现为参数的多态性,称为类属。 模板用于表达逻辑结构相同,但具体数据元素类型不同的数据对象的通用行为。
10.1 什么是模板 类属 —— 类型参数化,又称参数模板,使得程序(算法)可以从逻辑功能上抽象,把被处理的对象(数据)类型作为参数传递,C++只有函数模板和类模板
10.2 函数模板 重载函数通常基于不同的数据类型实现类似的操作;对不同数据类型的操作完全相同,用函数模板实现更为简洁方便
10.2.1 模板说明 声明模板中使用的类属参数。形式为 template < 类型形式参数表 >
10.2.2 函数模板与模板函数 函数模板声明:
1 2 3 4 template < 类型形式参数表 --->typename T1/class T1 > 类型 函数名 ( 形式参数表 ) { 语句序列 }
函数模板定义由模板说明和函数定义组成 模板说明的类属参数必须在函数定义中至少出现一次 函数参数表中可以使用类属类型参数,也可以使用一般类型参数
10.2.3 重载函数模板 可以定义同名的函数模板,提供不同的参数和实现;也可以使用其他非模板函数重载。
函数模板的重载 当函数参数的类型、个数不相同时所进行的类似操作
用普通函数重载版本 对于Max函数来说,当传入的两个参数的类型不一样(int和char可以比较但是版本类型不能提供类型的隐式转换)的时候,需要对函数进行重载。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 #include <iostream> using namespace std ; #include <string.h > template <typename T>T Max ( const T a, const T b ) { return a>b ? a : b ; }template <typename T>T Max ( const T a, const T b , const T c) { T t ; t = Max (a, b) ; return Max ( t, c ) ; }int Max ( const int a , const char b ) { return a>b ? a : b ; }int main ( ) { cout<< " Max( 3, 'a' ) is " << Max ( 3 , 'a' ) << endl ; cout << " Max(9.3, 0.5) is " << Max (9.3 , 0.5 ) << endl ; cout << " Max(9, 5, 23) is " << Max (9 , 5 , 23 ) << endl ; }
执行顺序和匹配约定
1 2 3 4 5 6 寻找和使用最符合函数名和参数类型的函数,若找到则调用它; 否则,寻找一个函数模板,将其实例化产生一个匹配的模板函数,若找到 则调用它; 否则,寻找可以通过类型转换进行参数匹配的重载函数,若找到则调用它 如果按以上步骤均未能找到匹配函数,则调用错误。 如果调用有多于一个的匹配选择,则调用匹配出现二义性。
10.3 类模板 类模板用于实现类所需数据的类型参数化 类模板在表示如数组、表、图等数据结构显得特别重要,这些数据结构的表示和算法不受所包含的元素类型的影响
10.3.1 类模板与模板类 定义: template <类型形式参数表—>typename T> 类声明 例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 #include <iostream> using namespace std ;template < typename T >class Array { public : Array ( int s ) ; virtual ~ Array () ; virtual const T& Entry ( int index ) const ; virtual void Enter ( int index, const T & value ) ; protected : int size ; T * element ; } ;template <typename T> Array<T>::Array (int s) { if ( s > 1 ) size = s ; else size = 1 ; element = new T [ size ] ; }template < typename T > Array < T > :: ~Array () { delete [] element ; }template < typename T > const T& Array < T > :: Entry ( int index ) const { return element [ index ] ; }template < typename T > void Array < T > :: Enter (int index, const T& value) { element [ index ] = value ; }int main () { Array <int > IntAry ( 5 ) ; int i ; for ( i = 0 ; i < 5 ; i ++ ) IntAry.Enter ( i, i ) ; cout << "Integer Array : \n" ; for ( i = 0 ; i < 5 ; i ++ ) cout << IntAry.Entry (i) << '\t' ; cout<<endl ; Array <double > DouAry ( 5 ) ; for ( i = 0 ; i < 5 ; i ++ ) DouAry.Enter ( i, (i+1 )*0.35 ) ; cout << "Double Array : \n" ; for ( i = 0 ; i < 5 ; i ++ ) cout << DouAry.Entry (i) << '\t' ; cout<<endl; }
10.3.2 类模板作函数参数 函数的形式参数类型可以是类模板或类模板的引用对应的实际参数是该类模板实例化的模板类对象–>利用函数调用类模板 当一个函数拥有类模板参数时,这个函数必定是函数模板
1 2 3 4 5 6 7 8 定义了一个用Array 作为参数的函数模板template < typename T >void Tfun( const Array <T> & x , int index ) { cout << x.Entry( index ) << endl ; } 调用函数模板Array <double > DouAry( 5 ) ;//Array <double >这一动作是初始化;DouAry( 5 )调用构造函数,实例化模板类,建立对象 … Tfun(DouAry,3 ); //Tfun实例化为模板函数,(DouAry,3 )调用模板函数。
10.3.3 在类层次中的类模板 1 2 3 4 5 6 一个类模板在类层次结构中既可以是基类也可以是派生类: ·类模板可以从类模板,或从普通类(非模板类)派生; ·模板类可以从类模板或从普通类派生。 ·当一个类模板从普通类派生时,意味着派生类增加了类属参数; ·当一个模板类从类模板派生时,意味着派生类继承基类时提供了实例化的类型参 数。
10.3.4 类模板与友元 1 2 3 4 在类模板中可以声明各种友元关系 一个函数或函数模板可以类是或类模板的友元 一个类或类模板可以是类或类模板的友元类 声明这种模板之间的友元关系符号比较烦琐
10.3.5 类模板与static成员 从类模板实例化的每个模板类有自己的类模板数据成员,该模板类的所有对象共享一个static数据成员 和非模板类的static数据成员一样,模板类的static数据成员也应该在文件范围定义和初始化 每个模板类有自己的类模板的static数据成员副本
10.4 标准模板 标准模板库中有三个组件:容器,迭代器和算法,利用它进行泛编程,可以节省大量的时间和精力。
10.4.1 容器 “容器”是数据结构,按某种特定的逻辑关系把数据元素组装起来的数据集。
杂七杂八 Date(int y, int m, int d) { SetDate(y, m, d); } void SetDate(int y, int m, int d) { year = y; month = m; day = d; } DateTime(int y, int m, int d, int h, int mi, int s) : Date(y, m, d) { SetTime(h, mi, s); } void SetTime(int h, int mi, int s) { hours = h; minutes = mi; seconds = s; }
关于final,final类不可继承,final虚函数不可被重写 一般重写的函数后面要加一个override
全书总结: 第六章 类与对象 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 class A {public : A(int x=0 ,int y=0 ):X(x),Y(y){} A(int x=0 ):X(x){} A(const A&,int =1 ); A(int x,int y):b(x),y(y){}; ~A(){cout<<"释放内存空间。" << endl;} const int M; const A a; void print ()const ; static void print (); friend void print (A*或者A&a,int n); friend class B ;protected : private : static b; B b; }class B {public : B(xx):b(xx){}; }
我们第六章差不多就学了这一段内容。我现在来展开说说: public 类内类外可见 protected:类内派生类内可见 private:只有类内可见 this指针:类的成员自带的,告诉编译器,我们用的是哪一个类的内容 构造函数和析构函数只能写在公有段,因为对象必须在类说明之外被创建和撤销 构造函数: 1.一般会带参数初始化数据成员。 2.构造函数可以重载,对象会根据参数调用匹配的一个,防止二义性的话可以用拷贝构造函数 3.浅复制和深复制(可以对参数进行简单的赋值)深复制就是把原本的函数copy下来,把形参改成对象的形式 常成员: 常数据成员:初始化后不可修改,带参数的构造函数对常赋值可以让每个不同的常数据成员有不同的初值 常对象:使得对象的全部数据成员被约束为只读,不可修改 常成员函数:不能修改数据成员。 静态成员: 1.静态数据成员:由类的各个对象共用 2.静态成员函数:没有this指针,只能访问静态数据成员。(他的存在只为她,哭了.jpg) 友元:友元可以访问类的所有成员,包括私有成员,非对称非传递。 1.友元函数:在那个段声明无所谓,不影响使用。友元函数必须显示的指明要访问的对象,因为它没有this指针。 2.友元类:若B类为A类的友元类,则B中的所有函数都是A类中的成员函数。B类可以直接调用A类的数据成员。一般B类没有自己的数据成员,它的存在只是为了对A类进行操作。 类的包含:把另一个类抄写进来
第八章 继承 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 class D : {public : D (int i):x (i){} int X; void Y(); int Y(int ); static i;private : }; D::i=0 ; class A :public B,protected C,private D ,virtual public E {public : A (int j):h (j),D (2 ); A (int j):h (j),D (j); 定要这么用的话,那么在初始化对象时,应该把D的值给初始化了。 D::X D::Y void Y () ; void Z () { A::Y; }private : int h; };int main () { A a; D d; a.X; a.D::X; a.A::Y; }
公有继承:把public和protected里的东西平移下去 保护继承:把基类的protected和private变成protected继承下去 私有继承:把public和protected里的东西变成private平移下去 访问声明:格式 基类名::成员; 1.不允许降低或提升基类成员的可访问性 2.此做法将会重载基类中同一段的所有的同名函数 3.派生类也有同名函数时不可用(类内用类名调用,类外用对象调用) 类内访问静态成员:略。 基类的初始化:利用构造函数对基类数据成员初始化 多继承和虚继承: 只需要在基类的构造函数前加一个virtual,那么构造函数只会执行开头那一次
第九章 虚函数与多态性 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 class A {public : X(int ); X(char ); visual void print () {cout<<"Up to Fate" ;} visual void J () {cout<<"XXJ" ;} vistual ~A(); visual int HYH ( char ) =0 ; }class B : public A {public : X(int ); void H () {cout<<"XHJ" <<endl;} void print () {cout<<"Up to Yourself" ;} visual void J () {cout<<"XHJ" ;} int HYH (char ) {cout<<"Hallo Everyone,I'am HYH." <<endl;} ~B(); }int main () { A *h; B *j; A a; B b; h=&a; h->X(1 /2 /···); h=&b; h->X(1 /2 /···); ((B*)h)->H(); j=&b; j->H(); j=&a; ((A*)j)->X(1 /2 /···); ((A*)a).X(1 /2 /···); }
静态联编;就是差不多是重载函数的意思; 类指针的关系: 1.基类指针引用基类对象 2.派生类指针指向派生类对象 3.基类指针引用派生类对象(强制类型转换) 4.派生类指针引用基类对象(强制类型转换) 虚函数:让基类指针指向派生类时直接调用派生类的同名函数 虚函数的重载(重写):在虚函数的基础上执行不同的语句; 虚析构函数:使得继承了基类的派生类对象在delete时把基类和派生类的析构函数都执行了 纯虚函数:给定一个函数的框架,具体的定义在派生类里面完成; 抽象类:有虚构函数的类就叫做抽象类,它只能做基类。不能有对象。可以说明指针和引用;
第十章 模板 模板函数:通常基于不同的数据类型实现类似的操作