第十二章 C与C++ 12.1 C转入C++时不需改变的内容 12.2 C转入C++的一些与类无关的 新特性 12.4 面向对象程序设计
12.1 C转入C++时不需改变的内容 (1) 各种数据类型变量的定义与使用, 函数、 数组、 指针、文件等基本知识。 (2) 许多有效的算法。 (3) 程序的基本调试思想方法。 (4) 程序设计中的自顶向下的总体思想: (5) 程序段中具体的最小模块仍然由顺序、 分支、 循环三种基本模块组成。
12.2 C转入C++的一些与类无关的新特性 12.2.1 C转入C++时需改变的内容 C++中不能再作为标识符的关键字 2. 函数声明 2. 函数声明 3. 函数声明的形式
12.2.2 C++中独有的与类无关的部分新特性 1. 局部变量说明语句的位置 如: void main() { …… /* 程序的其它部分(包括执行语句) */ int count=0; // 变量count说明 while(++count<=100) // 变量count使用 {……} …… /* 后续程序 */ }
2. 注释符 C++中可以使用两种注释符(如上例),比C多一种注释符“//”, 其功能是其后至行末为注释部分。程序中可灵活选用。
3. 范围分解符:: C中,当全局变量和某函数内的局部变量同名时, 该函数对全局变量起屏蔽作用,即在该函数内无法访问此全局变量,而在C++中,只要在其前使用范围分解符即可访问同名的全局变量。 例如: float n; void main() {int n; n=5; // 给局部变量n赋值 :: n=5.53; // 给全局变量n赋值 …… }
4. 内联函数 内联函数的特性类似于宏。 C中, 在定义了宏 #define ABS(n)((n)<0?-(n): (n)) 之后, 程序每调用一次宏ABS,替换宏名的字符串就展开并插入在调用处一次。 C中的函数调用就不是这种展开方式,而是程序转入子函数执行,执行完后再返回主调函数,每个函数只有一段代码。在C++中,可以定义类似于宏的内联函数,关键字是 inline,如定义求绝对值的内联函数如下: inline int Abs(int n) { return n<0?-n: n; }
C++中可以在同一程序中多次定义同名函数, 只要各函数间的参数个数或类型不同, 例如: 5. 重载函数 C++中可以在同一程序中多次定义同名函数, 只要各函数间的参数个数或类型不同, 例如: int Abs(int n) { return n<0?-n: n; } float Abs(float n) { return n<0?-n: n; }
在同一范围内定义同名函数称为重载函数。 编译器根据函数调用时具体的实参的个数和类型自动调用匹配的重载函数版本。 例如: int Abs(int n); float Abs(float n); void main() {int i; float f; i=Abs(5); // 调整型的求绝对值函数 f=Abs(-2.5); // 调实型的求绝对值函数 …… }
6.new和delete操作符 以下为动态开辟内存块的例子: struct node { char *name; int salary; node *next; }; char *pchar; // 定义三种类型的指针变量 int *pint; struct node *pnode;
pchar=new char; // 开辟了三块内存区 pint= new int; pnode=new node; *pchar=′a′; // 为内存单元赋值 *pint=6; pnode->name="hello "; pnode->salary=1000;
12.3 C++的核心新特性——类 12.3.1 类 以下是一个有关“人”的类的定义 12.3.1 类 以下是一个有关“人”的类的定义 class CHuman // class ——类定义关键字 {char name[20]; // 类的数据成员 int age; void GetInfo() // 类的成员函数 {…… } void OutInfo() } };
12.3.2 类实例 类的定义格式与结构体类型定义格式类似,其含义也类似,均是定义了一种新的数据类型,要作为程序处理的对象。对于结构体类型, 接着要定义结构体型变量以便在程序中引用(如第九章中的struct staff worker1, worker2;); 对于类,则要生成类实例,又称为类对象。其地位如同变量。如在上述的CHuman类定义之后,语句: CHuman human; (类名前不需加关键字class) 就生成了类CHuman的实例human,即为类对象human分配了一块可以存放数据和对数据进行处理的程序代码的内存块, 又称为类的实例化。
和内部变量一样,类对象在定义范围内有效(如在函数内定义的类对象, 在函数返回时销毁)。一个类可以生成多个实例。 也可以用C++的new操作符生成类的实例如下: CHuman *PHuman=new CHuman; 这个语句分配足以放置类对象的内存块并返回其首地址给这个对象的指针。对象所占内存保持到用delete操作符释放: delete PHuman; C++中多用这种分配、 销毁模式。
12.3.3 类成员的访问 例 12.1 使用C语言全局变量实现有关一个人的信息的输出操作。 12.3.3 类成员的访问 例 12.1 使用C语言全局变量实现有关一个人的信息的输出操作。 #include <string.h> #include <stdio.h> char name[20]; /* 两个全局变量 */ int age; void GetInfo() {strcpy(name , "Li-Li"); age=22; }
void OutInfo() {count<<"name: "<<name<<end; /* << 为输出显示符 */ count<<"age: "<<age<<end; } /* 下面是一些其它的函数 */ function1() {…… // 此函数内可调用GetInfo()和OutInfo()及全局变量name[]和age Function2() main() { char ch; GetInfo(); OutInfo(); …… }
图 12.1 C程序中全局变量不安全性
例 12.2 用C++语言定义一个有关人的“类” class CHuman // class ——类的定义符 { private: // 以下为私有变量,可以也只能由该类的成员函数调用 char name[20]; int age; public: // 以下为公有函数,此类内外的函数均可以调用它们 int GetInfo() { …… } int OutInfo() {…… } }; // 类定义结束
图 12.2 类对私有数据成员的封装
怎样正确地调用类成员: main() { CHuman human; // 定义了一个CHuman类的对象 strcpy(human.name,"Li-Li"); // 错误: 不能访问私有数据成员 human.age=22; // 错误: 同上 human.GetInfo(); // 正确: 可以访问公有成员函数 human.OutInfo(); // 正确: 同上 }
12.3.4 构造函数 在定义一个类时,直接初始化数据成员是不允许的。下列类的定义的初始化会产生错误: class C 12.3.4 构造函数 在定义一个类时,直接初始化数据成员是不允许的。下列类的定义的初始化会产生错误: class C {private: int n=0; // 出错 int *pi=&n; // 出错 …… };
其实,在类的定义中初始化是没有意义的,它只是创建了一个模板,类似于C语言中的结构体类型的定义,而程序是不可以直接对类型操作的, 只能对定义在其上的变量操作。 所以想要初始化成员,就必须生成一个类的实例。 例如: …… // 其它程序 void CHuman:: initial(char name[20], int age) // 定义一初始化函数 {strcpy(name," "); age=0; } …… // 其它程序 CHuman human; // 生成了一个CHuman类的实例human human.initial(name, age); // 初始化类实例hu man
初始化成员变量的更好办法是定义一个特殊的成员函数: “构造函数”。 class CHuman {private: char name[20]; int age; public: CHuman() // 定义构造函数, 它的名称与类的名称一样
{strcpy(name, " "); age=0; } void GetInfo() {…… void OutInfo() }; }
12.3.5 析构函数 析构函数也是自动执行的,但它与构造函数相反,是在程序结束时执行,它负责在程序撤消类的对象时,释放所占资源。 例如: 12.3.5 析构函数 析构函数也是自动执行的,但它与构造函数相反,是在程序结束时执行,它负责在程序撤消类的对象时,释放所占资源。 例如: ~CHuman() // 定义析构函数 { delete name; // 释放name所占内存 } 析构函数的名称为构造函数名称前加“ ~ ”
例 12.3 CHuman类的完整定义 class CHuman { private: char name[20]; int age; public: CHuman() // 定义构造函数 {strcpy(name, " "); age=0; }
void GetInfo() {……} void OutInfo() ~CHuman() // 定义析构函数 { delete name; } };
12.3.6 类的继承 类的最重要的特性是继承性,它很贴近现实世界,如: 儿子继承父亲的一些特征,并且拥有自己的特征。孙子又继承了儿子的一些特征, 并且拥有自己的特征……。类也一样,它的一些特性是可以继承的。下面我们将前面的有关人的类增加一些数据成员,并派生出一个“学生”的类: class CHuman // 重新定义一表示人的类 CHuman { private: char *m-name; int m-age; char *m-sex; float m-height;
public: CHuman() // 定义构造函数 {m-name= " "; m-age=0; m-sex= " "; m-height=0.0; } void GetInfo(char *name, int age, char *sex, float height) {m-name=new char[strlen(name)+1]; m-name=name; m-age=age; m-sex=sex; m-height=height; }
void print() {cout<< "Name: "<<m-name; cout<< "Age: "<<m-age; cout<< "Sex: "<<m-sex; cout<< "High: "<<m-height; } ~CHuman() // 定义析构函数 {delete m-name; delete m-sex; } };
以上是一个“人”的类,现在,我们需要描述一个学生的一些特征。当然,学生是属于“人”的,所以,有关人的特性我们不应该重复,可以从“人”的类中“派生”出我们所要定义的“学生类”,继承“人”类中所有的属性, 再增加些学生特有的属性,定义“学生类”如下: class CStudents: public CHuman // 表示类CStudents是从类Chuman 中公有派生的 {private: // 学生类所具有的新的属性 char *m-schoolname; // 校名 int m-grade; // 年级
public: void printstu() {cout<< "Schoolname: "<<m-schoolname; cout<< "Grade: "<<m-grade; } void GetStuInfo(char *schoolname, int grade ) {m-schoolname=new char[strlen(schoolname)+1]; m-schoolname=schoolname; m-grade=grade; }
CStudents() // 定义构造函数 {m-schoolname= " "; m-grade=0; } ~CStudents() // 定义析构函数 { delete m-schoolname; };
图 12.3 基类及其派生类的成员可访问图
12.4 面向对象程序设计 12.4.1 从面向过程到面向对象 12.4.2 什么是面向对象 12.4 面向对象程序设计 12.4.1 从面向过程到面向对象 12.4.2 什么是面向对象 面向对象程序设计是面向对象理论在程序设计中的应用,面向对象理论的应用范围则更广。面向对象系统有三大特性: 封装性、 继承性和多态性。 面向对象的概念可以由如下公式表示: 面向对象=对象+类+继承+通信
1. 对象 在面向对象的系统中,对象是基本的、运行时的实体, 它既包括数据(属性),也包括作用于数据的操作(行为)。所以一个对象把属性和行为密封成一个整体。从程序设计者来看,对象是一个程序模块;从用户来看,对象为他们提供了所希望的行为。
2. 类 一个类定义了一组大体上相似的对象。一个类所包含的方法和数据描述了一组对象的共同行为和属性。把一组对象的共同特性加以抽象并存贮在一个类中的能力,是面向对象技术最重要的一点。 类是在对象之上的抽象。有了类以后,对象则是类的具体化,是类的实例。类可以有子类,同样也可以有父类, 形成层次结构。
3. 继承性 继承性是父类和子类之间共享数据和操作的机制。 这是类之间的一种关系,在定义和实现一个类的时候,可以在一个已经存在的类的基础上进行,把这个已经存在的类所定义的内容做为自己的内容, 并加入若干新的内容。 继承性是面向对象程序设计语言的标志。它使程序的可重用性大大提高。因为程序员只要对已有的类加以整理和分类就有可能重用这些代码,并且某些软件开发平台如VC++ 还提供了大量的微软基础类(MFC),使程序员可以根据需要进行派生、剪裁和修改,在此基础上集中精力编写子类新增加部分,大大简化了大型应用系统的开发复杂性和难度,提高了软件的开发效率。
4. 通信 各个类的对象间通过消息进行通信。所谓消息,实际上是一个类的对象要求另一个类的对象执行某个服务的指令, 指明要求哪一个对象执行这个服务,必要时还要传递调用参数。系统功能的实现,就是通过一系列对象消息的传递, 执行一系列服务达到的,即系统是用消息将对象动态链接在一起的。 面向过程的程序是通过函数调用来运行的, 而面向对象程序的运行则是通过“消息”来驱动的。消息中只包含发送者的要求,但并不指定接收者具体的处理方法。实现方法由接收消息者来确定。
消息有很多种,常见的为· 单击鼠标左键/右键。 · 双击鼠标左键/右键。 · 输入字符 例如, 单击鼠标左键显示某人信息。 首先, 创建鼠标左键单击的消息响应函数LButtonDown(), 在鼠标左键按下后,系统会自动调用此函数。 其次,加入处理消息的代码,即:显示信息。 LButtonDown() {CHuman man; // 生成的"人类"的对象 man.GetInfo("王军", 23, "男", 1.75); man.print(); }
12.4.3 两种程序设计思想为指导的软件开发周期 图 12.4 面向过程的软件开发过程
具体各步解释如下: 分析阶段。 (2) 设计阶段。 (3) 编码阶段。 (4) 测试阶段。 (5) 维护阶段。
图 12.5 面向对象的软件开发过程
12.4.4 Windows应用程序开发 Windows的运行机制有两个主要特点: 消息驱动。 ① 做一些初始化工作, 登录“窗口类”, 建立窗口等。 ② 执行消息循环。 ③ 等待WM-QUIT消息, 结束应用程序 (2) 多任务。