计算机学院信息教研室
lxj
数据结构 ( C++语言版)
朱战立西安电子科技大学出版社计算机学院信息教研室
lxj
第一章 C++知识概要
过程化基础
项目开发过程

构造函数和析构函数
继承与派生
多态性计算机学院信息教研室
lxj
过程化基础
字符集和保留字字符集大小写字母( 52个)
数字( 10个)
其他符号保留字( ANSI C有 32个,ANSI C++又补充了 29个)
计算机学院信息教研室
lxj
过程化基础
数据类型基本:
整形 int
字符型 char和 w_char
实型 单精度 float 双精度 double
布尔型 bool
计算机学院信息教研室
lxj
过程化基础非基本数据类型:
数组 type []
指针 type *
空类型 void
结构 struct
联合 union
枚举 enum
类 class
计算机学院信息教研室
lxj
过程化基础
Typedef
为一个已有的类型名提供一个同义词。
例,typedef double profit;
typedef int INT,integer;
INT a; //相当于 int a;
profit d; //相当于 double d;
注,建立一个 typedef时,没有分配内存空间。因为没有实际的定义一个新的数据类型计算机学院信息教研室
lxj
过程化基础
指针指针的类型 用已说明所指地址中存放的数据类型。
指针的初始化
int add=3500;
int *ipadd=&add;
计算机学院信息教研室
lxj
过程化基础
条件语句
if语句
if(expression)
{
statement;
...
}
计算机学院信息教研室
lxj过程化基础
if…else 语句
if(expression)
{
statement1;
...
}
else
{
statement2;
...
}
计算机学院信息教研室
lxj
过程化基础
if…elseif…else 语句
if(expression1)
{
statement1;
...
}
elseif (expression2)
{
statement2;
...
计算机学院信息教研室
lxj
过程化基础
}

else
{
statementN;
,..
}
计算机学院信息教研室
lxj
过程化基础
条件操作符
expression1?expression2:expression3;
例:
x=(a>b)?a:b;
switch语句用于测试一个变量具有多个值时所执行的动作计算机学院信息教研室
lxj
过程化基础
Switch(expression)
{
case constant1:statement1;break;
case constant2:statement2;break;

default:statement
}
计算机学院信息教研室
lxj
过程化基础循环语句
while语句
while(expression)
{
stament;
}
计算机学院信息教研室
lxj
过程化基础
Do…while 语句
do
{
statement;
...
}while(expression);
总能保证循环体被执行一次计算机学院信息教研室
lxj
过程化基础
for语句用于处理没有固定长度的数据结构,如数组等
for(expression1; expression2;expression3)
{
statement;
}
计算机学院信息教研室
lxj
过程化基础
for(I=0;I<100;I++)
{
if (I>45)
cout<<“i”<<I<<endl;
}
初始化或赋值控制循环初始化循环变量计算机学院信息教研室
lxj
过程化基础
转移语句
break语句用于强制退出循环语句以及 case语句的执行
continue语句终止当前运行的这一次最内层循环计算机学院信息教研室
lxj
过程化基础
结构化程序设计主要思想是 功能分解并逐步求精缺点:当数据量增大时,数据与方法之间的分离使程序更难理解每一种相对于老问题的方法都需要带来额外的开销也就是 重复性投入 。
迫切需要 采用新的程序设计方法计算机学院信息教研室
lxj
过程化基础
结构化程序设计把程序看成是处理数据的一系列过程。
过程或函数定义为一个接一个顺序执行的一组指令。
数据与程序分开存储,编程的主要技巧在于追踪哪些函数调用哪些函数,哪些数据发生了变化计算机学院信息教研室
lxj
项目开发过程
第一步:是利用 编辑 器建立程序代码文件,包括头文件、代码文件、资源文件等。
第二步,启动编译程序,编译 程序首先调用预处理程序处理程序中的预处理命令(如 #include,#define等),经过预处理程序处理的代码将作为编译程序的输入。
计算机学院信息教研室
lxj
项目开发过程编译对用户程序进行词法和语法分析,
建立目标文件,文件中包括机器代码、
连接指令、外部引用以及从该源文件中产生的函数和数据名。
第三步,连接 程序将所有的目标代码和用到的静态连接库的代码连接起来,为所有的外部变量和函数找到其提供地点,
最后产生一个可执行文件。
计算机学院信息教研室
lxj
类和对象计算机学院信息教研室
lxj
计算机学院信息教研室
lxj
计算机学院信息教研室
lxj
计算机学院信息教研室
lxj
计算机学院信息教研室
lxj
计算机学院信息教研室
lxj
类和对象
对象可以被创建和销毁,但类是无所不在的。
属于不同类的对象在不同时刻,不同地方被分别创建。全局对象在主函数开始执行前首先被建立、局部对象在程序执行遇到他们的对象定义时才被建立。
与定义变量类似,定义对象时,C++为其分配内存空间。
计算机学院信息教研室
lxj
计算机学院信息教研室
lxj类成员的访问控制
公有类型( public),声明了一个类的外部接口,任何来自外部的访问都必须通过这个接口来进行。
私有类型( private) 只允许本类的成员函数来访问,而类外部的任何访问都是非法的。
私有类型成员紧接着类名称,private可以省略。
保护类型( protected),继承过程中对产生的新类影响不同计算机学院信息教研室
lxj
计算机学院信息教研室
lxjclass Tdate
{
private:
int month;
int day;
int year;
public:
void Set(int m,int d,int y);
int isLeapYear();
void Print();
}
计算机学院信息教研室
lxjvoid Tdate::Set(int m,int d,int y){
month=m;day=d;year=y;
}
int Tdate::isLeapYear(){
return(year%4==0&&year%100!=0)
||(year%400==0);
}
void Tdate::Print(){
cout<<month<<"/"<<day<<"/"<<year
<<endl;
}
计算机学院信息教研室
lxj
Void main()
{
Tdate a;
a.Set(2,4,1998);
a.Print();
}
主函数运行时,首先创建一个 Tdate类的对象,然后调用 Tdatede的公共成员函数 Set()来给对象的数据成员赋值在调用成员函数 Print()打印输出结果。
计算机学院信息教研室
lxj
类定义的常识:类定义是提供给更多不同用途的程序共享的,并不受单个程序应用的影响而“优化”。
Set(int,int,int )的全名是
Tdate::Set(int,int,int )。 类名 Tdate的作用是指出 Set(int,int,int )是 Tdate的一个成员函数,而不是其他类的成员函数,也不是普通函数。没有类名的是非成员函数。
成员函数也叫方法( method),它多出现与面向对象方法论的叙述中计算机学院信息教研室
lxj
作用域区分符,:
:,指明一个函数属于哪个类或一个数据属于哪个类。
::可以不跟类名,表示全局数据或全局函数(即非成员函数)
下面的代码,在成员函数中,调用了非成员函数。
计算机学院信息教研室
lxj
计算机学院信息教研室
lxj
计算机学院信息教研室
lxj
调用成员函数
调用成员函数的形式类似于访问一个结构对象的分量,先指明对象,再指明分量。必须指定对象和成员名。
#include<iostream.h>
#include,tdate.h”
Tdate s;
void func()
{
month=10;
计算机学院信息教研室
lxj调用成员函数
#include<iostream.h>
#include,tdate.h”
Tdate s;
void func()
{
month=10; //成员还是对象?
Tdate::month=10; //是 Tdate的哪个对象
Tdate::Set(2,15,1998);
//Set对哪个对象进行操作呢?
}
计算机学院信息教研室
lxj
计算机学院信息教研室
lxj
计算机学院信息教研室
lxj
计算机学院信息教研室
lxj
类的封装
概念:
首先是,数据和算法(操作)结合,构成一个不可分割的整体;
其次是,在这个整体中的一些成员是保护的,他们被有效的屏蔽,以防外界的干扰和误操作。另一些成员是公共的,他们作为接口提供给外界使用。
计算机学院信息教研室
lxj
计算机学院信息教研室
lxj
计算机学院信息教研室
lxj
计算机学院信息教研室
lxj
计算机学院信息教研室
lxj
计算机学院信息教研室
lxj构造函数
C++规定与类同名的函数是构造函数
class Desk width=5;
{ length=5;}
public,protected,
Desk() int weight;
{ int high;
weight=10; int width;
high=5; int length;
};
计算机学院信息教研室
lxj
构造函数
构造函数也可以放在类的外部定义
Desk::Desk() //构造函数定义
{
.,,,;
.,,,;
.,,,;
}
计算机学院信息教研室
lxj
构造函数
放在类外部定义的构造函数,其函数名前要加上“类名”。
特殊之处:没有返回类型,函数体中也不允许返回值。
专门用于创建对象和为其初始化,所以不能随意被调用。
计算机学院信息教研室
lxj
析构函数
特殊的类成员函数,没有返回类型,没有参数,不能随意调用,也没有重载。
只是在类对象生命周期结束的时候,由系统自动调用。
跟构造函数形成了鲜明的对立,所以析构函数名就在构造函数名前加上一个逻辑非运算符,~”,表示“逆构造函数”
计算机学院信息教研室
lxj
计算机学院信息教研室
lxj
计算机学院信息教研室
lxj
计算机学院信息教研室
lxj
计算机学院信息教研室
lxj
计算机学院信息教研室
lxj
计算机学院信息教研室
lxj
计算机学院信息教研室
lxj
计算机学院信息教研室
lxj
计算机学院信息教研室
lxj
计算机学院信息教研室
lxj
计算机学院信息教研室
lxj
计算机学院信息教研室
lxj
计算机学院信息教研室
lxj
计算机学院信息教研室
lxj
计算机学院信息教研室
lxj
计算机学院信息教研室
lxj
计算机学院信息教研室
lxj
默认构造函数
C++规定,每个类必须有一个构造函数,
没有构造函数,就不能创建任何对象。
如果没有提供一个构造函数(一个都未提供),则 C++提供一个默认的构造函数,该默认构造函数是个无参构造函数,
它仅负责创建对象,而不做任何初始化工作。
计算机学院信息教研室
lxj
默认构造函数
只要一个类定义了一个默认构造函数
(不一定是无参构造函数),C++就不在提供默认 默认构造函数。也就是说,
如果为类定义了一个带参数的构造函数,
还想要无参构造函数,则必须自己定义。
与变量定义类似,如果创建的是全局对象或静态对象,则对象的位模式全为零,
否则,对象值是随机的。
计算机学院信息教研室
lxj
类和继承计算机学院信息教研室
lxj
引子
类的抽象性、封装性以及数据的共享和组织 ------有效实现对于实际问题的抽象描述和处理了。
代码的重用性和可扩充性 ------还没充分体现出来?
继承和派生 ------重用和扩充计算机学院信息教研室
lxj
计算机学院信息教研室
lxj
继承和派生的层次结构
类的继承,是新的类从已有类那里得到已有的特性。从另一个角度看,从已有类产生新类的过程就是 类的派生 。
类的派生实际上是一种演化、发展过程,
即通过扩展、更改、和特殊化,从一个已知类出发建立一个新类。通过类的派生可以建立具有共同关键特征的对象家族,从而实现代码的重用。
计算机学院信息教研室
lxj
继承和派生的层次结构
继承和派生的层次结构,是人们对自然界中的事物进行分类、分析和认识的过程在程序设计中的体现。
这个分类树反映了生物的派生关系,最高层是抽象层次最高的,是最具有普遍和一般意义的概念,下层具有上层的特性,同时加入了自己的新特征,而最下层是最为具体的。
计算机学院信息教研室
lxj
继承和派生的层次结构
由上到下(父类到子类),是一个 具体化、
特殊化 的过程;由下到上(子类到父类),
是一个 抽象化 的过程。
上下层的关系可以看做是基类与派生类的关系。
具体化特殊化 抽象化父类子类计算机学院信息教研室
lxj
派生类的声明
class 派生类名,继承方式 基类名
{
派生类成员声明;
};
class creature:public biology
{
public:
run();
};
计算机学院信息教研室
lxj
派生类的声明单继承:一个派生类只有一个直接基类;
多继承:一个派生类可以同时有多个基类。
class 派生类名,继承方式 1,基类名 1,继承方式 2,基类名 2
{
派生类成员声明;
};
单继承可以看作是多继承的一个简单的特例,多继承可以看作是多个单个继承的组合,他们之间的很多特性是相同的。
计算机学院信息教研室
lxj
计算机学院信息教研室
lxj
派生类成员
派生类成员:除了从基类继承来的所有成员外,新增加的数据和函数成员。
派生类成员 -------不同于基类的关键所在,
是派生类对基类的发展。
当扩充已有代码时,就是通过派生类中新增成员来添加新的属性和功能。这就是在类的继承基础上进化和扩展。
计算机学院信息教研室
lxj
派生类的生成过程
给出派生类的声明后,给出该类的成员函数的实现,派生类就算完成,可以由它来生成对象进行实际的处理。
新类生成的三个步骤:
吸收基类成员改造基类成员添加新的成员计算机学院信息教研室
lxj
派生类的生成过程
吸收基类成员:派生类中包含了它的所有基类中除构造函数和析构函数之外的所有成员。
计算机学院信息教研室
lxj
派生类的生成过程
改造基类成员
1.基类成员的访问控制问题(主要依靠派生类声明时的继承方式来控制)
2.对基类数据或函数成员的覆盖(在派生类中声明一个和基类数据或函数同名的新成员)
同名覆盖,如果派生类声明了一个和某个基类成员(如果是成员函数,则参数表也要相同,参数不同属于重载)同名的新成员,派生的新成员就覆盖了外层同名成员。
计算机学院信息教研室
lxj
派生类的生成过程
添加新的成员 -----继承和派生机制的核心在派生过程中,基类的构造函数和析构函数是不能被继承下来的,因此一些特别的初始化和扫尾清理工作需要我们在派生类中重新加入新的构造函数和析构函数。
功能扩展函数计算机学院信息教研室
lxj继承方式规定了如何访问从基类继承的成员。
每一个“继承方式”只限定紧随其后的基类。(多继承)
类的继承方式 指定 了派生类成员以及类外对象对于从基类继承来的成员的 访问权限。
public,公有继承
protected 保护继承
private 私有继承(默认)
计算机学院信息教研室
lxj
多态性计算机学院信息教研室
lxj
多态性
继承 -----类与类的层次关系
多态 -----考虑在不同层次的类中,以及在一个类的内部,同名成员之间的关系问题,是解决功能和行为的再抽象问题。
直观来讲:多态是指类族中具有相似功能的不同函数使用同一个名称来实现,
从而可以使用相同的调用方式来调用这些具有不同功能的同名函数。
计算机学院信息教研室
lxj
多态性
多态 ----指同样的 消息 被不同的对象接收时导致完全 不同的行为 。
消息 ----对类的函数的调用
不同的行为 -----不同的实现,也就是调用了不同的函数。
计算机学院信息教研室
lxj
函数重载
两个以上的函数,取相同的函数名,但是形参的个数或者类型不同,编译器根据实参和形参的类型及个数的最佳匹配,
自动调用哪一个函数,这就是函数的重载。
int iadd(int x,int y);
float fadd(float x,float y);
计算机学院信息教研室
lxj
函数重载
C++允许功能相近的函数在相同的作用域内以相同函数名定义 -----形成重载
int add(int x,int y);
float add(float x,float y); //形参类型不同
int add(int x,int y);
int add(int x,int y,int z);//形参个数不同计算机学院信息教研室
lxj
函数重载
注意:重载函数的形参必须不同,即类型或个数不同。
int add(int x,int y);
int add(int a,int b);//不以形参名来区分
int add(int x,int y);
void add(int x,int y);//不以返回值类型区分计算机学院信息教研室
lxj
函数重载
注意:
不要将不同功能的函数定义为重载函数,
以免出现对调用结果的误解、混淆。
int add(int x,int y)
{return x+y;}
float add(float x,float y)
{return x-y;}
计算机学院信息教研室
lxj
运算符重载
运算符重载,对已有的运算符赋予多重含义,使用同一个运算符作用于不同类型的数据导致不同类型的行为。
计算机学院信息教研室
lxj运算符重载
#include<iostrem.h>
class complex
{
public:
complex(double r=0.0,double i=0.0)
{real=r;imag=i;}
void display();
private:
double real;
double imag;
}
计算机学院信息教研室
lxj
运算符重载声明如下对象:
complex a(1,4),b(2,5);
如果要对 a,b进行加法运算,该如何实现呢?
希望用 a+b,
但编译器会不知道如何完成这个加法。
需要我们编程序来说明,+”在作用于
complex类对象时,该实现什么样的功能,这就是运算符重载。
计算机学院信息教研室
lxj
运算符重载
实现过程:
– 首先把指定的运算表达式转换为对运算符函数的调用,运算对象转换为运算符函数的实参
– 然后根据实参的类型来确定需要调用的函数
(在编译过程中完成)
优点,允许改变使用于系统内部的运算符的操作方式,以适应用户自定义类型的类似运算。
计算机学院信息教研室
lxj
运算符重载
规则:
1.C++中 除了五个运算符以外,都可以重载。
2.重载之后运算符的优先级和结合性都不变
3.针对新类型数据的实际需要,对原有运算符进行适当改造。一般来讲,重载的功能应当与原有功能相类似,不能改变原运算符的操作对象个数,同时至少要有一个操作对象是自己定义的计算机学院信息教研室
lxj
运算符重载
不能重载的 5个运算符关系运算符,
成员指针 运算符 *
作用域分辨符,:
三目运算符?:
sizeof运算符计算机学院信息教研室
lxj
运算符重载的两种形式
重载为类的成员函数函数类型 operator 运算符(形参表)
{
函数体;
}
计算机学院信息教研室
lxj
运算符重载的两种形式
函数类型,指定了重载运算符的返回值类型,也就是运 算结果类型;
operator 定义运算符重载的关键字
运算符:要重载的运算符名称,必须是
C++中可重载的运算符
形参表:给出重载运算符所需要的参数和类型。
计算机学院信息教研室
lxj
运算符重载的两种形式
重载为类的友员函数
Friend 函数类型 operator 运算符(形参表)
{
函数体;
}
计算机学院信息教研室
lxj
运算符重载
当运算符重载为类的成员函数时,函数的参数个数比原来的操作数个数要少一个(后置,++,--”除外)
原因:如果某个对象使用了重载了的成员函数,自身的数据可以直接访问,就不需要再放在参数表中进行传递,少了的操作数就是该对象本身。
计算机学院信息教研室
lxj
运算符重载
当重载为类的友员函数时,参数个数与原操作数个数相同。
原因:重载为类的友员函数时,友员函数对某个对象的数据进行操作,就必须通过该对象的名称来进行,因此使用到的参数都要进行传递,操作数的个数就不会有变化。
计算机学院信息教研室
lxj
运算符重载
运算符重载的实质:函数重载。重载为成员函数,就可以自由访问本类的数据成员。
实际使用时,总是通过该类的某个对象来访问重载的运算符。
如果时双目运算符,一个操作数是对象本身的数据,由 this指针指出,另一个操作数则需要通过运算符函数的参数表来传递计算机学院信息教研室
lxj
运算符重载
对于双目运算符 B,如果要重载 B为类的成员函数,使之能实现 oprd1 B oprd2
其中 oprd1为 A类的对象,则应当把 B重载为 A类的成员函数,该函数只有一个形参,
形参的类型是 oprd2所属的类型。
经过重载后,表达式 oprd1 B oprd2就相当于调用 oprd1.operator B(oprd2)
计算机学院信息教研室
lxj运算符重载
#include<iostream.h>
class complex //复数类声明
{
public,//外部接口
complex(double r=0.0,double i=0.0);{real=r;imag=i;}
complex operator +(complex c2);//运算符,+”重载成员函数
complex operator -(complex c2);//运算符,-”重载成员函数
void display(); /输出复数
private:
double real; //实部
double imag; //虚部
}
计算机学院信息教研室
lxj
运算符重载
#include"complex.h"
complex complex::operator +(complex c2)
//重载运算符函数实现
{
return complex(real+c2.real,imag+c2.imag);
//创建一个临时无名对象作为返回值
}
计算机学院信息教研室
lxj
运算符重载
complex complex::operator -(complex c2)
//重载运算符函数实现
{
return complex(real-c2.real,imag-c2.imag);
//创建一个临时无名对象作为返回值
}
void complex::display()
{
cout<<"("<<real<<","<<imag<<")"<<endl;
}
计算机学院信息教研室
lxj运算符重载
void main()
{
complex c1(4,5),C2(2,10),C3;
//定义复数类的对象
cout<<"c1=";c1.display();
cout<<"c2=";c2.display();
c3=c1-c2;
cout<<"c3=c1-c2=";
//使用重载运算符完成复数减法
c3.display();
c3=c1+c2;
cout<<"c3=c1+c2=";
//使用重载运算符完成复数加法
c3.display();
}
计算机学院信息教研室
lxj
运算符重载
可以看出:除了在函数声明及实现的时候使用了关键字 operator之外,运算符重载函数与类的普通成员函数没有什么区别。
对整型数、浮点数等基本类型数据的运算符仍然遵循 C++预定义的规则,同时添加了新的针对复数运算的功能。
具有更广泛的多态特征。
计算机学院信息教研室
lxj
运算符重载
如果是单目运算符,操作数由对象的 this
指针给出,就不在需要任何参数。
对于前置单目运算符 U,如果要重载 U为类的成员函数,如,-”(负号等)使之能够实现表达式 U oprd,其中 oprd为 A类的对象,
则 U应当重载为 A类的成员函数,函数没有形参。经过重载后,表达式 U oprd相当于函数调用 oprd.operator U( ).
计算机学院信息教研室
lxj
运算符重载
对于后置单目运算符,++”和,--”,
如果要将它们重载为类的成员函数,用来实现表达式 oprd++或 oprd--,其中 oprd
为 A类的对象,那么运算符就应该重载为
A类的成员函数,这时函数要带有一个整型( int) 形参。
重载之后,表达式 oprd++和 oprd- -就相当于函数调用 oprd.operator ++(0)和
oprd.operator - -(0)
计算机学院信息教研室
lxj
虚函数
根据赋值兼容规则,可以使用派生类的对象代替基类对象。如果用基类类型的指针指向派生类对象,就可以通过这个指针来访问该对象,问题是访问到的只是从基类继承来的同名成员。而无法访问到派生类的与基类同名的成员?
解决办法:首先在基类中将这个函数声明为虚函数,就可访问,从而实现多态。
计算机学院信息教研室
lxj虚函数
虚函数成员的定义语法:
virtual 函数类型 函数名(形参表)
{
函数体;
}
注意,虚函数声明只能出现在类声明中的函数原型声明中,而不能在成员的函数体实现的时候。
计算机学院信息教研室
lxj
虚函数
运行过程中的多态应满足三个条件:
首先:类之间应满足赋值兼容规则其二:要声明虚函数最后:要由成员函数来调用或者是通过指针、引用来访问虚函数。
注,如果是使用对象名来访问虚函数、则联编在编译过程中就可进行(静态联编),而无需在运行过程中进行。
计算机学院信息教研室
lxj
计算机学院信息教研室
lxj
计算机学院信息教研室
lxj
计算机学院信息教研室
lxj
计算机学院信息教研室
lxj
虚函数
在程序中,如果派生类没有显式给出虚函数声明,系统会根据以下规则判断一个函数成员是不是虚函数
–是否与基类的虚函数有相同的名称;
–是否与基类的虚函数有相同的参数个数及相同的对应参数类型;
–是否与基类的虚函数有相同的返回值或者满足赋值兼容规则的指针、引用型的返回值 。
计算机学院信息教研室
lxj
计算机学院信息教研室
lxj
纯虚函数
声明格式:
virtual 函数类型 函数名(参数表) =0
声明为纯虚函数之后,基类中就不在给出函数的实现部分。纯虚函数的函数体由派生类给出。所在的类是抽象类,不能进行实例化。
函数体为空的虚函数。
计算机学院信息教研室
lxj
计算机学院信息教研室
lxj
抽象类
是一种特殊的类,它为一族类提供统一的操作界面。
为了抽象和设计的目的而建立的,建立抽象类,就是为了通过它多态的使用其中的成员函数。
位于类层次的上层,一个抽象类自身无法实例化,只能通过继承机制,生成抽象类的非抽象派生类,然后实例化。
计算机学院信息教研室
lxj
抽象类
抽象类派生出新的类后,如果派生类给出所有纯虚函数的函数实现,这个派生类就可以声明自己的对象,因而不在是抽象类;
反之,如果派生类没有给出全部纯虚函数的实现,这时的派生类仍然是一个抽象类。