Download presentation
Presentation is loading. Please wait.
1
第 3 章 类的基础部分 陈哲 副教授 南京航空航天大学 计算机科学与技术学院
2
3.1.1 过程化程序设计的缺陷 出现大量的全局变量; 程序复杂: 程序员难以理解成百上千的函数; 程序难以进行修改和扩充。
3
3.1.2 面向对象程序设计的基本思想 OOP 以对象为中心,把数据和对数据的操作封装在一起 过程化设计是以过程为中心 (函数)
面向对象设计是以对象为中心 Example:
4
3.2 类的基本概念 类是一种用户自定义类型,声明形式: class 类名 { 变量和函数的声明; …… } ; 例如:
5
属性 方法 成员变量 float width; float length; float area; 成员函数
setData( ) { ……} calcArea( ) { ……} getWidth( ) { ……} getLength( ){ ……} getArea( ) { ……} 属性 方法
6
void setData(float, float); void calcArea( ); float getWidth( );
class Rectangle { float width; float length: float area; void setData(float, float); void calcArea( ); float getWidth( ); float getLength( ); float getArea( ); }; 默认情况下类的成员是私有的,而结构体(struct)中的成员是公有的。
7
3.2 类的基本概念(续) 为了使类的成员能够在类外面被访问,其成员必须定义为public. Example:
8
class Rectangle { private: float width; float length: float area; public: void setData(float, float); void calcArea( ); float getWidth( ); float getLength( ); float getArea( ); };
9
class Rectangle { public: void setData(float, float); void calcArea( ); float getWidth( ); float getLength( ); float getArea( ); private: float width; float length: float area; };
10
class Rectangle { private: float width; public: void setData(float, float); void calcArea( ); float getWidth( ); float getLength( ); float getArea( ); float length: float area; };
11
惯例: class class-name { private: declaration; // ... more declarations may follow... public: };
12
3.3 定义成员函数 类的成员函数的定义与普通函数的定义类似. 成员函数在类之外定义的常规方式: { … }
3.3 定义成员函数 类的成员函数的定义与普通函数的定义类似. 成员函数在类之外定义的常规方式: <返回值类型> <类名> :: <函数名> ( 参数列表 ) { … }
13
class Rectangle { private: float width; float length: float area; public: void setData(float, float); void calcArea( ); float getWidth( ); float getLength( ); float getArea( ); };
14
void Rectangle::setData(float w, float l )
{ width = w; length = l; } float Rectangle::getWidth ( ) return width;
15
3.4 定义对象 定义对象称为类的实例化 (模具-铸件) Example: Rectangle box;
3.4 定义对象 定义对象称为类的实例化 (模具-铸件) Example: Rectangle box; box.setData(10.0, 12.5); cout << Box.getWidth( ); Rectangle *boxPtr; boxPtr = &box; boxPtr->setData(15, 12);
16
// Program 3-1 class Rectangle { private: float width; float length; float area; public: void setData(float, float); void calcArea( ); float getWidth( ); float getLength( ); float getArea( ); };
17
// 参数值传递给私有成员变量 void Rectangle::setData( float w, float l ) { width = w; length = l; } // 计算面积 void Rectangle::calcArea( ) { area = width * length;
18
// 返回私有成员变量width float Rectangle::getWidth( ) { return width; } // 返回私有成员变量 length float Rectangle::getLength( ) return length; // 返回私有成员变量 area float Rectangle::getArea( ) return area;
19
cin >> wide>> boxLong; box.setData(wide, boxLong);
void main( ) { Rectangle box; float wide, boxLong; cout << " 请输入长和宽 ? "; cin >> wide>> boxLong; box.setData(wide, boxLong); box.calcArea( ); cout << "矩形的数据:\n"; cout << "宽: "<< box.getWidth( ) << endl; cout << "长: "<< box.getLength( )<<endl; cout << "面积: "<< box.getArea( )<<endl; } 3-1.cpp
20
3.4.3 引入私有成员的原因 在OOP程序设计中,对象保护重要的数据不被破坏是一件很重要的事情,它是通过将关键数据声明为私有成员;
引入私有成员的原因 在OOP程序设计中,对象保护重要的数据不被破坏是一件很重要的事情,它是通过将关键数据声明为私有成员; 当一个成员变量被定义为私有时,唯一对它的访问途径就是通过公有的成员函数。
21
3.5 类的多文件组织 程序的组织方式: 类的声明存储在头文件里 (类的声明文件). 成员函数定义存储在 .cpp文件 (类的实现文件) .
3.5 类的多文件组织 程序的组织方式: 类的声明存储在头文件里 (类的声明文件). 成员函数定义存储在 .cpp文件 (类的实现文件) . 用户程序(使用该类)通过#include包含头文件。 链接时将类的实现文件和用户程序进行链接,从而生成一个完整的程序。
22
// Contents of Rectang.h
#ifndef RECTANGLE_H #define RECTANGLE_H class Rectangle { float width, length , area; public: void setData(float, float); void calcArea( ); float getWidth( ); float getLength( ); float getArea( ); }; #endif 第一个文件
23
// Contents of rectang.cpp #include "Rectang.h" // 把参数传递给私有成员
void Rectangle::setData(float w, float l) { width = w; length = l; } // 计算面积 void Rectangle::calcArea( ) area = width * length; 为什么需要这行?
24
// 返回私有成员:width float Rectangle::getWidth( ) { return width; } // 返回私有成员: length float Rectangle::getLength( ) return length; // 返回私有成员:area float Rectangle::getArea( ) return area;
25
Rectang.h Rectang.cpp 3-2.cpp
//主程序 #include "Rectang.h" //不能省略这一行 void main( ) { Rectangle box; float wide, boxLong; cout << " 输入长和宽 ? "; cin >> wide>> boxLong; box.setData(wide, boxLong); box.calcArea( ); cout << "长: "<< box.getLength( )<<endl; cout << "宽: "<< box.getWidth( ) << endl; cout << "面积: "<< box.getArea( )<<endl; } Rectang.h Rectang.cpp cpp
26
3.6 私有函数成员的作用 专门用于内部处理的函数,它们在类的外部不能使用,这些函数为私有的。
3.6 私有函数成员的作用 专门用于内部处理的函数,它们在类的外部不能使用,这些函数为私有的。 私有函数可以被同一个类中的其它函数调用。Example: class Rectangle { private: float width, length , area; void calcArea( ); public: … };
27
void Rectangle::setData(float w, float l)
{ width = w; length = l; calcArea( ); } …
28
3.7 内联函数 函数体出现在类的定义中,就是内联函数 Example:
29
#ifndef RECTANGLE_H #define RECTANGLE_H class Rectangle{ private: float width , length, area ; public: void setData( float , float ) ; void calculateArea( ) { area = width * length ; } // 内联函数 float getWidth( ) ; float getLength( ) ; float getArea( ) ; } ; inline void Rectangle::setData( float w, float l ) // 内联函数 { width = w ; length = l ; } #endif
30
3.7 内联函数 内联函数的特点: 函数体要简单. ( no loop, no switch )
3.7 内联函数 内联函数的特点: 函数体要简单. ( no loop, no switch ) 对于内联函数,不是通过函数调用方式,而是把函数体直接嵌入调用函数中。 内联发生时间:编译时。
31
3.8 构造函数和析构函数 1. 构造函数是一个函数成员,当定义类对象时,自 动调用该函数对数据成员进行初始化。
3.8 构造函数和析构函数 1. 构造函数是一个函数成员,当定义类对象时,自 动调用该函数对数据成员进行初始化。 2. 析构函数也是一个函数成员,当对象终止时将自动调用该函数进行“善后”处理。
32
3.8.1 构造函数 构造函数是与类同名的函数成员; 没有返回值类型,也不允许有void; 如果构造函数没有参数,则称为缺省构造函数;
构造函数 构造函数是与类同名的函数成员; 没有返回值类型,也不允许有void; 如果构造函数没有参数,则称为缺省构造函数; 构造函数的作用:在对象被创建时,采用给定的值将对象初始化为一个特定的状态。 在对象创建时,由系统自动调用; 如果程序中未声明,则系统自动产生出一个缺省形式的构造函数; 允许为内联函数、重载函数、带缺省形参值的函数。 给各成员数据赋初值。
33
class InvoiceItem // 例3-6 { char *desc; int units; public:
InvoiceItem( ) { desc = new char [51]; } void setInfo(char *dscr, int un) strcpy(desc, dscr); units = un; } char *getDesc( ) { return desc; } int getUnits( ) { return units; } };
34
<< stock.getDesc( ) << endl; cout << "库存量: "
void main( ) { InvoiceItem stock; stock.setInfo( "鼠标", 20); cout << "库存物品: " << stock.getDesc( ) << endl; cout << "库存量: " << stock.getUnits( ) << endl; } 3-6.cpp 指向对象的指针: InvoiceItem *ptr ; ptr = new InvoiceItem ; // 此时调用构造函数
35
3.8.2 析构函数 析构函数也与类同名,前面多个波浪号(~) 当一个对象终止时会自动调用析构函数
析构函数 析构函数也与类同名,前面多个波浪号(~) 当一个对象终止时会自动调用析构函数 Example: Program 3-7.
36
class InvoiceItem { char *desc; int units; public: InvoiceItem( ) { desc = new char[51]; cout<< "构造函数 \n"; } ~InvoiceItem( ) { delete [ ]desc; cout<< "析构函数 \n";
37
void setInfo(char *dscr, int un) { strcpy(desc, dscr); units = un; }
char *getDesc( ) { return desc; } int getUnits( ) { return units; } }; void main( ) { InvoiceItem stock; stock.setInfo( "鼠标", 20); cout << stock.getDesc( ) << endl; cout << stock.getUnits( ) << endl; 3-7.cpp
38
3.8.2 析构函数 注 意 Delete对象时,将调用析构函数,例如: InvoiceItem *ptr ;
析构函数 Delete对象时,将调用析构函数,例如: InvoiceItem *ptr ; ptr = new InvoiceItem ; delete ptr ; 1. 同构造函数一样,析构函数也没有返回值类型; 2. 析构函数无参数。 3. 一个类只能有一个析构函数。 注 意
39
带参构造函数 常常需要把某些数据传递给构造函数用于初始化对象成员。 构造函数可以有缺省参数。 Example:
40
// Contents of sale2.h class Sale { float taxRate , total; public: Sale(float rate ) { taxRate = rate; } void calcSale( float cost) { total = cost +(cost * taxRate); } float getTotal( ) { return total; } };
41
//Contents of main program
void main( ) { Sale cashier( 0.06f ) ; // 6% 税率 float amount ; cout << "请输入销售额: " ; cin >> amount ; cashier.calculateSale( amount ) ; cout << "销售总额是 RMB" ; cout << cashier.getTotal( ) << endl ; }
42
构造函数可以有缺省参数: class Sale { float taxRate , total; public: Sale( float rate = 0.05f ) taxRate = rate ; } void calcSale( float cost) { total = cost +(cost * taxRate); } float getTotal( ) { return total; } }; 例 3-9
43
//Contents of main program
void main( ) { Sale cashier1 ; // 缺省形参值 Sale cashier2(0.06f ) ; // 指定形参值 float amount ; cout << "请输入销售额: " ; cin >> amount ; cashier1.calculateSale( amount ) ; cashier2.calculateSale( amount ) ; cout << cashier1.getTotal( ) << endl ; cout << cashier2.getTotal( ) << endl ; }
44
注意 当构造函数没有参数时,称为缺省构造函数;
如果构造函数所有的参数都有缺省值,那么在函 数调用时不需要显式地传递参数,也属于缺省构 造函数。
45
构造函数应用举例—输入有效的对象 例3-10. 设计一个CharRange类,这种类型的对象允许用户输入一个字符,然后检验该字符是否位于指定范围(例如,‘A’~‘D’)之内。当用户输入的字符超出指定范围时,该对象将显示一个出错信息,并等待用户重新输入一个新字符 。
46
#include <iostream>
#include <cstring> using namespace std; class CharRange { char *errMsg ; // 出错信息 char input ; // 用户输入值 char lower ; // 有效字符的低界 char upper ; // 有效字符的高界 public: CharRange( char , char , const char * ) ; char getChar( ) ; } ;
47
CharRange::CharRange( char low , char high ,
const char *str ) { lower = toupper( low ) ; upper = toupper( high ) ; errMsg = new char [ strlen( str) + 1] ; strcpy( errMsg , str ) ; }
48
char CharRange::getChar( )
{ cin.get(input) ; cin.ignore( ) ; input = toupper( input ) ; while( input < lower || input > upper ) cout << errMsg ; } return input ;
49
const char *Msg="仅接受 J~N字符"; CharRange input( 'J' , 'N' , Msg ) ;
int main( ) { const char *Msg="仅接受 J~N字符"; CharRange input( 'J' , 'N' , Msg ) ; while( input.getChar( ) != 'N') ; return 0; } 3-10.cpp
50
重载构造函数 一个类中可以定义多个构造函数。 Example: 3-11
51
InvoiceItem( int size = 51) { desc = new char[size]; }
class InvoiceItem { char *desc; int units; public: InvoiceItem( int size = 51) { desc = new char[size]; } InvoiceItem(char *d) { desc = new char[strlen(d)+1]; strcpy(desc, d); } ~InvoiceItem( ) { delete [ ]desc; } // 其它方法略 }; void main( ) { InvoiceItem iteml( "ABC"); InvoiceItem item2; } 3-11.cpp
52
缺省构造函数的表现形式 1. 如果类中没有定义构造函数,系统将提供一个无参构造函数(属缺省构造函数),该函数不实现任何功能。如果用户自定义了一个构造函数 ,那么系统缺省的构造函数将失效。 2. 如果类中定义有无参的构造函数,那么该构造函数也属于缺省的构造函数。 3. 如果类中定义有带参的构造函数,并且所有形参均具有缺省值,那么该构造函数也属于缺省的构造函数。 4. 一个类只能有一个缺省构造函数,否则将产生二义性。 Example: 下面这个例子就错误的定义了构造函数:
53
illegal class InvoiceItem { char *desc; int units; public:
InvoiceItem( ) { desc = new char[80]; } InvoiceItem(int size = 51) { desc = new char[size]; } ~InvoiceItem( ) { delete[ ] desc; } // 其他函数略 }; illegal
54
3.9 对象数组 创建对象数组时,数组中每个元素(对象)都将调用构造函数。
3.9 对象数组 创建对象数组时,数组中每个元素(对象)都将调用构造函数。 如果没有为数组元素指定显式初始值,数组元素便使用缺省值初始化(调用缺省构造函数)。 当数组中每一个对象被删除时,都要调用一次析构函数。 例3-12
55
class InvoiceItem { char *desc ; int storage ; public: InvoiceItem( int size = 51) { desc = new char [ size] ; } InvoiceItem( char *d ) { /* 函数代码略*/ } InvoiceItem( char *d , int u) ~InvoiceItem( ) // 析构函数 { delete[ ] desc ; } // 其他函数略 } ;
56
InvoiceItem Inventory[3] = { InvoiceItem( "ABC", 10),
思考: 三个对象的初始化 void main( ) { InvoiceItem Inventory[3] = { InvoiceItem( "ABC", 10), InvoiceItem( "DEF") }; for( int i = 0 ; i < 3 ; i++ ) cout << Inventory[i].getDesc ( ) <<endl; } 3-12.cpp
57
3.11 抽象数组类型 C++对数组不进行下标越界检查,程序员很容易在下标上出错,我们可以创建一个具有数组功能的类实现下标越界检查。
Example: 例3-14.
58
// 构造函数。对list 中的每个元素初始化 IntArray::IntArray( ) {
class IntArray { int list[20]; bool isValid(int); public: IntArray( ); bool set(int, int); bool get(int, int& ); }; // 构造函数。对list 中的每个元素初始化 IntArray::IntArray( ) { for(int i = 0; i < 20; i++) list[i] = 0; } 3-14.cpp
59
// 检验参数 element 是否为有效的下标
bool IntArray::isValid(int element) { bool status = true; if(element < 0 || element > 19) cout << "ERROR: "<< element; cout << "is an invalid subscript.\n"; status = false; } return status;
60
// set向指定的数组位置存储一个值。 bool IntArray::set(int element, int value) { bool status = false; if(isValid(element)) list[element] = value; status = true; } return status;
61
// get获得数组中指定位置的值 . bool IntArray::get(int element, int &value) { bool status = false; if(isValid(element)) value = list [element]; status = true; } return status;
62
void main( ) { IntArray numbers; int val , x ; // 将 1 存储在数组中,同时显示20个'*' for(x = 0 ; x < 20 ; x++ ) if( numbers.set(x, 1 ) ) cout << "* " ; cout << endl;
63
for(x = 0; x < 20; x++) if(numbers.get(x, val)) cout << val << " " ; cout << endl; // Attempt to store a value outside the bounds. if(numbers.set(50, 3)) cout << "Element 50 successfully set. \n"; }
64
扩充抽象数组类型 例3-15. 上节讨论的IntArray类包含一个具有20个元素的整形数组成员,可以完成数组下标越界检查。本节扩展如下几个函数成员 linearSearch BinarySearch bubbleSort selectionSort
65
class IntArray { int list [20]; bool isValid(int); public: IntArray( ); bool set(int, int); bool get(int, int&); int linearSearch(int); int binarySearch(int); void bubbleSort( ); void selectionSort( ); };
66
// IntArray 类的构造函数 IntArray:: IntArray( ) { for(int i = 0; i < 20; i++) list [i] = 0; } // isValid 函数检验参数是否为有效的下标 bool IntArray::isValid(int element) { if(element < 0 || element > 19) { cout << "ERROR: "<< element; cout << "is an invalid subscript. \n"; return false; } else return true; }
67
// set 函数,向指定的数组位置存储一个值
bool IntArray::set(int element, int value) { if(isValid(element)) { list[element] = value; return true; } else return false; } // get member function. bool IntArray::get(int element, int &value) if (isValid(element)) { value = list[element];
68
// linearSearch线形查找函数.
int IntArray::linearSearch(int value) { int status = -1; for (int count = 0; count < 20; count++) if (list [count] == value) status = count; break; } return status;
69
// binarySearch member function.
int IntArray::binarySearch(int value) { int first = 0, last = 19, middle ; selectionSort( ) ; // 首先对数组排序 while( first <= last ) { middle = (first + last) / 2 ; if( list[middle] == value ) return middle ; else if( list[middle] > value ) last = middle - 1 ; else first = middle + 1 ; } return -1 ; // 代表未找到指定的元素
70
// bubbleSort member function.
void IntArray::bubbleSort( ) { int temp ; for( int line = 0 ; line < 19 ; line++ ) for( int col = 0 ; col < 19 - line ; col++ ) if( list [ col ] > list [ col + 1 ] ) { temp = list [ col ] ; list [ col ] = list [ col + 1] ; list [ col + 1 ] = temp ; }
71
// selectionSort member function.
void IntArray::selectionSort( ) { int startScan, minIndex, temp ; for( startScan = 0 ; startScan < 19 ; startScan++ ) { minIndex = startScan ; for( int i = startScan + 1 ; i < 20 ; i++ ) if( list [ i ] < list [ minIndex ] ) minIndex = i ; temp=list[minIndex] ; list[minIndex] = list[startScan] ; list[startScan] = temp ; }
72
int main ( ) { IntArray numbers; int val, x , searchResult; for ( x = 0; x < 20; x++) if (! numbers.set(x, rand( ))) cout << "存储数据出错! \n" ;
73
cout << "\n下面是随机产生的 20 个数:\n" ;
for( x = 0 ; x < 20 ; x++ ) { if(numbers.get(x, val ) ) cout <<setw( 10 )<<val ; if((x+1) % 5 == 0 ) cout << endl ; } cout << "按 Enter 键继续..."<<endl ; cin.get( ) ;
74
numbers.selectionSort( ) ;
cout << "下面是排序后的 20 个数:\n" ; // 显示排序后的 20 个数 for( x = 0 ; x < 20 ; x++ ) { if(numbers.get(x, val ) ) cout <<setw( 10 )<<val ; if((x+1) % 5 == 0 ) cout << endl ; } cout << endl << endl ;
75
cout << "输入一个数,然后进行查找: " ; cin >> val ;
cout << "正在查找,请稍侯 ...\n" ; searchResult = numbers.binarySearch( val ) ; if( searchResult == -1 ) cout << "没找到!\n" ; else { cout << "在排序后,它的下标位置是:" ; cout << searchResult << endl ; } return 0; 3-15.cpp
Similar presentations