程序设计基础

程序设计基础

第四章 数组

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 << " " ;//再利用*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);//☆☆☆注意一下如果在函数体内new一个对象,则一定要用指针参数的
引用
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;//☆☆☆如果修改ary的值那么将会内存泄漏
}
delete[]ary;
ary=NULL
}
void App(int*& pa, int len)
{
pa = new int[len];//函数App执行new操作把地址写入pa,与ary无关
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);
//t1.a=5; 错误
//t1.b=6; 错误
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 ; //这里说明了B是A的一个友元类
public :
void Display() { cout << x << endl ; } ;
private :
int x ;
} ;
plan1:在B中为A创建一个类对象
class B
{ public :
void Set ( int i ) { Aobject . x = i ; } //利用友元类访问A类中的private中的x
void Display () { Aobject . Display () ; } //利用友元类调用A类中的函数
private :
A Aobject ; //这里B类为A类创建了一个类对象,并且在public中使用类对象
} ;
int main()
{ B Bobject ;
Bobject . Set ( 100 ) ;
Bobject . Display () ;
}
plan2:直接对A操作
class B
{
public:
void Set(A&Aobject,int i) { Aobject.x = i; } //调用函数时应当放入一个A类的类对象和一个整形数据作为实参
void Display(A& Aobject) { Aobject.Display(); } //调用函数时应当放入A的一个类对象
};
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) {} //"a(x)"等价于把a=x放入大括号内
int a;
};
class B
{
public:
B(int x, int y) : aa(x) { b = y; } //利用类对象aa给B类中的x赋值
void out() { cout << "aa = " << aa.a << endl << "b = " << b << endl; } //利用创建的类对象aa调用A类中的成员
private:
int b;
A aa; //为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; //如果要使子类能用他们,就设置成protected
};
class B : public A
{
public:
int get_S() { return s; };
void make_S() { s = x * y; }; // 使用基类数据成员x,y
protected: int s; //如果要使子类能用他,就设置成protected
};
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::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;//为基类创建了一个叫做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; // Point类的数据成员
};
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 类的成员函数
// 构造函数,调用成员函数对 x,y作初始化
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::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::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()等函数返回要的结果。

类的继承和类的包含的比较

img
对比两种方式我们发现,二者在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
//8.13 虚继承的测试
#include<iostream>
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 << "\nobjD.b2=" << objD.b2 << "\nobjD.d" << objD.d << endl;
B1 objB1;
cout << "objB1.b=" << objB1.b << "\nobjB1.b1=" << objB1.b1 << endl;
}
int main()
{
test();
}
输出的值依次为:121224
1121

☆需要注意的是:D的对象在调用b1,b2时,直接调用基类的构造函数,而忽略D的调用。比如:在创建类对象时初始值为(20,05,06,06)时输出为 20,21,22,06。
一个类在类体系中可以作为虚基类和非虚基类,这取决于继承方式,与本身的定义无关。

第九章 虚函数与多态性

联编:指一个程序模块、代码之间互相关联的过程。
多态性:是指一个名字,多种语义;或界面相同,多种实现。重载函数是多态性的一种简单形式。
动态联编:指程序联编推迟到运行时进行,所以又称为晚期联编。如:switch 语句和 if 语句是动态联编的例子。虚函数允许函数调用与函数体的联系在运行时才进行。

9.1 静态联编

普通成员函数重载可表达为两种形式:

  1. 在一个类说明中重载。例如:
    void Show ( int , char ) ;void Show ( char * ,float ) ;
  2. 基类的成员函数在派生类重载。有 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 A_obj ; // 类型 A 的对象
B B_obj ; // 类型 B 的对象
p = & A_obj ; // p 指向类型 A 的对象
p = & B_obj ; // p 指向类型 B 的对象,它是 A 的派生类
利用 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.抽象类不能用作参数类型、函数返回类型或显式类型转换

img

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); // triangle::set_dim()
p->show_area();
p = &s;
p->set_dim(10.0, 5.0); // square::set_dim()
p->show_area();
p = &c;
p->set_dim(9.0); // circle::set_dim()
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); // Dec_type::Show()
Hex_type n2(50);
fun(n2); // Hex_type::Show()
Oct_type n3(50);
fun(n3); // Oct_type::Show()
}
哎呀这个地方就是往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 : //注意是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 容器

“容器”是数据结构,按某种特定的逻辑关系把数据元素组装起来的数据集。

1
2
3
分类:
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);//友元函数 (访问时(&a或者a,1))
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;//静态数据成员,在A类和D类中都可以进行修改。
private:
};
D::i=0; //类外初始化静态成员
class Apublic B,protected C,private D ,virtual public E//A类公有继承B,保护继承C,私有继承D。省略默认私有,struct默认公有。这里也是多继承.E为虚继承。
{
public
A(int j):h(j),D(2);//基类的初始化,这里为基类的成员也初始化了。
A(int j):h(j),D(j);//基类的初始化,这里的初始化就是有问题的,因为C++先执行基类的构造函数,此时还没有j这个东西,只是为x开辟了一个数据空间,没有初始化。如果一
定要这么用的话,那么在初始化对象时,应该把D的值给初始化了。
D::X //访问声明,虽然在public这个地方声明,但是此这个已经不是原本的数据成员了,原本的已经到了派生类的private里面,这个属于重名的成员
D::Y //访问声明,这里相当于把两个同名函数都声明了,然后它们继承下来之后变成了public的;
void Y();
void Z()
{
A::Y;//在类中调用基类的重名函数;
}
private:
int h;
};
int main()
{
A a;
D d;
a.X;//访问重名数据成员,这里默认调用派生类
a.D::X;//访问重名数据成员,这里显示的调用了基类的成员;
a.A::Y;//在类外main函数中调用基类的重名函数;
}

公有继承:把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";}//定义虚函数,使得基类指针在调用不同的派生类对象时,调用该派生类对象中的print函数,而非只调用基类中的
visual void J(){cout<<"XXJ";}//定义虚函数
vistual ~A();//虚析构函数
visual int HYH ( char )=0;//定义HYH为纯虚函数;
}
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时把基类和派生类的析构函数都执行了
纯虚函数:给定一个函数的框架,具体的定义在派生类里面完成;
抽象类:有虚构函数的类就叫做抽象类,它只能做基类。不能有对象。可以说明指针和引用;

第十章 模板

模板函数:通常基于不同的数据类型实现类似的操作


程序设计基础
http://example.com/2024/03/23/程序设计基础/
作者
Faido
发布于
2024年3月23日
许可协议