640 likes | 865 Views
面向对象程序设计. 1. www.themegallery.com. 引入:. 面向对象程序设计( OOP )按 对象 分割问题。 面向对象其实就是现实世界模型的一个自然延伸,现实世界中的任何实体,都可以看作一个对象,而对象之间通过消息相互作用。如:每一个账户可以看作一个个对象。 对同一类型对象抽象出其 共同特性 ,形成 类 。 类是 OOP 中的主体. 一组数据和操作. 问题: 1 、在 C++ 中类是怎样定义? 2 、对象是怎样调用自己类的操作(成员函数)? 3 、类的操作怎样使用才方便?. 第3章 类( Classes). 教学内容 :
E N D
面向对象程序设计 1 www.themegallery.com www.themegallery.com
引入: • 面向对象程序设计(OOP)按对象分割问题。 • 面向对象其实就是现实世界模型的一个自然延伸,现实世界中的任何实体,都可以看作一个对象,而对象之间通过消息相互作用。如:每一个账户可以看作一个个对象。 • 对同一类型对象抽象出其共同特性,形成类。 • 类是OOP中的主体 一组数据和操作
问题: 1、在C++中类是怎样定义? 2、对象是怎样调用自己类的操作(成员函数)? 3、类的操作怎样使用才方便?
第3章 类(Classes) 教学内容: §定义类(Defining Class) §成员函 数(Member Functions) §程序结构( Program Structure) §静态成 员(Static Members) §友元(Friends) §运算符重载(Operators Overloading )
§ 定义类 • class类名 • { • private: • 私用数据成员和成员函数 • protected : • 保护数据成员和成员函数 • public: • 公用数据成员和成员函数 • };
私有成员: • 在关键字private后面声明,只允许本类中的成员函数访问,而类外部的任何函数都不能访问。 • 如果紧跟在类名后面声明私有成员,则关键字private可以省略。 • 保护成员: • 与private类似,其差别表现在继承与派生时对派生类的影响不同,类外不能直接使用,但它的派生类的成员函数可以使用。
公有成员: • 在关键字public后面声明,它们是类与外部的接口,任何外部函数都可以访问公有数据成员和成员函数。
//定义一个日期类 class Date{private:int year, month, day; public:void set(int y, int m, int d); //置日期值bool isLeapYear();//判断闰年void print();//输出日期 };
§ 成 员 函 数 • 一、成员函数定义 • 1、在类中定义成员函数在类中定义的成员函数一般规模都比较小,语句只有1~5句,而且特别的switch语句、循环语句不允许用。它们一般为内联函数,即使没有明确用inline标示。由于在类中定义的成员函数被默认为内联函数,所以在C++中,类定义通常可以在头文件中, 成员函数定义也伴随着进入头文件。 例子
class Date{ • int year, month, day; • public: • void set(int y,int m,int d){// 赋值操作 • year=y; month=m; day=d; • }//-------------------------------- • bool isLeapYear(){ // 判断闰年 • return (year%4==0 && year%100!=0)||(year%400==0); • }//-------------------------------- • void print(); { // 输出日期 • cout<<setfill('0'); • cout<<setw(4)<<year<<'-'<<setw(2)<<month<<'-'<<setw(2)<<day<<'\n'; • cout<<setfill(' '); • }//-------------------------------- • };
2、在类外部定义成员函数 对于大的成员函数来说,直接把代码放在类定义中使用起来十分不便。为了避免这种情况,C++允许在其它地方定义成员函数。这样把类定义(头文件)看成是类的外部接口,类的成员函数定义看成是类的内部实现 一般定义形式为: 返回类型类名::成员函数名(形参表) { 函数体; } 与普通函数的重要区别
//定义日期类 class Date{ int year,month,day; public: void set(int y, int m, int d);//置日期值 bool isLeapYear();//判断闰年 void print();//输出日期 };
void Date::set(int y,int m,int d){ year=y; month=m; day=d; }//-------------------------------- bool Date::isLeapYear(){ return (year%4==0 && year%100!=0)||(year%400==0); }//-------------------------------- void Date::print(){ cout<<setfill('0'); cout<<setw(4)<<year<<'-'<<setw(2)<<month<<'-'<<setw(2)<<day<<'\n'; cout<<setfill(' '); }//--------------------------------
3、采用显式内联技术 inline void Date::set(int y,int m,int d){ year=y; month=m; day=d; }//-------------------------------- inline bool Date::isLeapYear(){ return (year%4==0 && year%100!=0)||(year%400==0); }//-------------------------------- inline void Date::print(){ cout<<setfill('0'); cout<<setw(4)<<year<<'-'<<setw(2)<<month<<'-'<<setw(2)<<day<<'\n'; cout<<setfill(' '); }//--------------------------------
二、调用成员函数 一个类可以创建无数个对象,其任何对象都可以使用该类的成员函数。 使用“对象名.成员”方式访问成员 使用“对象指针->成员”方式访问成员 使用“(*对象指针).成员”方式访问成员
int main(){ • Date d,*dp; • dp=&d; • dp->set(2000,12,6); • if( dp->isLeapYear()) • (*dp) .print(); • } • int main(){ • Date d; • d.set(2000,12,6); • if(d.isLeapYear()) • d.print(); • } 说明: 类中成员当被声明为private,protected 时,在普通函数或其它类的成员函数中就不能对它们进行访问,如果想从类的外部修改private,protected类型的成员时,可以在类中提供一个成员函数来达到这个目的。
三、常成员函数与函数的常量参数 常成员函数不能修改所捆绑对象的数据成员。 常成员函数说明格式:类型说明符 函数名(参数表)const;注:这里,const是函数类型的一个组成部分,因此在实现部分也要带const关键字。 class Date{int year, month, day; public:void set(int y, int m, int d); //置日期值bool isLeapYear() const;//判断闰年void print() const;//输出日期 };
void Date::set(int y,int m,int d){ year=y; month=m; day=d; }//-------------------------------- bool Date::isLeapYear() const{ return (year%4==0 && year%100!=0)||(year%400==0); }//-------------------------------- void Date::print() const{ cout<<setfill('0'); cout<<setw(4)<<year<<'-'<<setw(2)<<month<<'-'<<setw(2)<<day<<'\n'; cout<<setfill(' '); }//--------------------------------
函数的常量参数 对传递的参数, 不允许写操作 例如: bool Date::comp(const Date& a){ a.year = 2003; // error: 常量对象 …… } 注:const对象所捆绑的数据成员不能被更改, 且一般情况下:const对象只能调用 const成员函数
四、成员函数的重载 成员函数与普通函数一样,可以重载,而且编译器对重载的识别和使用规则也是相同 //日期类 class Date{int year, month, day; public:void set(int y, int m, int d); //设置日期值bool isLeapYear(int y) const;//判断闰年void print() const;//输出日期 };
例如 int main(){ Date d, e; d.set(2000,12,6); e.set("2005-05-05"); } class Date{ int year, month, day; public: void set(int y,int m,int d); void set(const string& s); …… };//------------------------------- void Date:: set(int y,int m,int d){ year=y; month=m; day=d; }//----------------------------------- void Date::set( const string& s){ year=atoi(s.substr(0,4).c_str()); month=atoi(s.substr(5,2).c_str()); day=atoi(s.substr(8,2).c_str()); }//-------------------------------- • atoi函数把字符串转为整型 • 原型:int atoi( const char * ) • substr函数取出子串 • c_str函数指向字符串数据缓冲区的const指针
注意: • 由于类名是成员函数名的一部分,所以 不同类的成员函数同名不是重载。 • 成员函数与非成员函数同名不是重载
§程序结构(Program Structure) • 一、类的程序结构 • 类定义文件(.h文件) • 类实现文件(.cpp文件) • 类的使用文件(.cpp文件) 例子
二、屏蔽类实现的意义 使得使用类的程序员可以不管类的源代码,只需连接类的目标码,这样软件开发商就可以只需提供头文件和目标模块,不提供源代码。而且提供者对类实现进行改写或升级,并不影响到类的使用者(即代码不需要重写,但可能需要重新编译)。
二、类作用域 • 1、类定义作用域: • 从类定义结束开始,到从外面包围类定义的块结束(若类定义外无包围块,则结束于文件) • 使用类的程序员在类定义作用域下编程 2、类作用域: • 类定义内部及成员函数定义内部 • 对于两个嵌套的作用域,如果在内层作用域内声明了与外层作用域中同名的标识符,则外层作用域的标识符在内层不可见。 例子
A类定义作用域范围 B类定义作用域范围 //文件x.cpp class A{ //… }; void f( ) { class B{ //… }; … } … //文件到此为止
对象1 对象2 对象n 数据区 数据区 数据区 …………… 公共代码区-成员函数执行代码 §静态成员(Static Members) 一、静态数据成员(Static Data Members) 1、静态数据成员的需要性 当创建类的对象时,系统就为该对象分配一块私有的内存单元来存放它的的所有数据成员,而该类的所有对象共享成员函数的执行代码。 例子
但在某些应用中,需要程序中属于某个类的所有对象共享某个数据。为此,一个解决的办法就是将所要共享的数据说明为全局变量,但这将破坏数据的封装性;较好的解决办法是将所要共享的数据说明为类的静态成员。但在某些应用中,需要程序中属于某个类的所有对象共享某个数据。为此,一个解决的办法就是将所要共享的数据说明为全局变量,但这将破坏数据的封装性;较好的解决办法是将所要共享的数据说明为类的静态成员。
2、静态数据成员 • 静态数据成员是类的所有对象中共享的成员,而不是某个对象的成员,因此可以实现多个对象间的数据共享。 • 在类的作用域内用关键字static声明 • 在类定义作用域中进行初始化,一般是在main函数启动之前,在多文件程序结构中,一般放在类的实现编译单元中.初始化格式为: <类型> <类名>::<静态数据成员>=<值>; 例子
Q:访问私有的静态数据成员需通过调用成员函数,而调用成员函数需绑定对象,而静态数据成员不属于任何对象,这显然不合理。而静态数据成员设计为公有的,又有失安全性和可维护性。Q:访问私有的静态数据成员需通过调用成员函数,而调用成员函数需绑定对象,而静态数据成员不属于任何对象,这显然不合理。而静态数据成员设计为公有的,又有失安全性和可维护性。
二、静态成员函数(Static Members Functions) class Student{ static int number; string name; public: void set(string str){ name = str; ++number; } static void printNumber(){ cout<<number<<" total numbers\n"; } void print(){ cout<<name<<" -> students are "<< number<<" numbers\n"; } };
或: • class Student{ • static int number; • string name; • public: • static void printNumber(); • void print(){ • cout<<name<<" -> students are "<<number<<" numbers\n"; } • };//----------------------------------- • void Student::printNumber() { • cout<<number<<" total numbers\n"; • }
//----------------------------------- int Student::number = 0; //静态数据成员在类外分配空间和初始化 //------------------------------------- int main(){ Student s1,s2; s1.set("Smith"); s2.set("Jenny"); Student::printNumber(); //访问静态成员函数 }//==================================== 静态成员函数与静态数据成员一样,与类相联系,不与对象相联系,只要类存在,静态成员函数就可以使用,所以访问静态成员函数时不需要对象。
注: 静态成员函数只能直接访问类中的静态成员,若要访问类中的非静态成员时,必须借助对象名、引用或指向对象的指针。
§友元(Friends) • 一、需要友元的原因 • 类的封装性,使类只能通过成员函数来访问私有成员。这是好事但有时会造成频繁调用成员函数,导致调用开销明显增多,影响了性能,从而对发挥C++编程优势不利。 例子
C++语言提供了友元方法来解决上述问题。所谓友元是说一个类或一个函数是另一个类的朋友(friend)元素。C++语言提供了友元方法来解决上述问题。所谓友元是说一个类或一个函数是另一个类的朋友(friend)元素。 • 友元可以是下列之一: • 友元函数―――不属于任何类的一般函数 • 友元成员―――另一个类的某个成员函数 • 友元类―――另一个类(整个类作友元)
友元函数 在类定义时,在类内声明一个普通函数,该函数前加上 friend 表示这个函数不是成员函数,而是本类的友元函数。友元函数可以访问本类的私有成员和保护成员。 一个友元函数还可以是多个类的友元,能够访问相应的所有类的数据
class Point { //Point类声明 public: //外部接口 void Set(int xx,int yy) {X=xx;Y=yy;} int GetX() {return X;} int GetY() {return Y;} friend double Distance( Point& a, Point& b); private: //私有数据成员 int X,Y; };
double Distance( Point& a, Point& b) { double dx=a.X-b.X; double dy=a.Y-b.Y; return sqrt(dx*dx+dy*dy); } int main() { Point p1, p2; p1.Set(3,4); p2.Set(4,5); double d=Distance(p1, p2); cout<<"The distance is "<<d<<endl; return 0; }
友元成员函数:一个类的成员函数是另一个类的友元友元成员函数:一个类的成员函数是另一个类的友元 class Teacher { //... public: void assignGrades(Student& s,float d);//修改学生的成绩 }; class Student { public: void display(); private: float score; }; class Student;//前向声明,类名声明 friend void Teacher::assignGrades(Student& s,float d);
void Teacher::assignGrades(Student& s,float d) { s.score=d; //修改学生的成绩 } void Student::display() { cout<<"score="<<score<<endl; } void main() { Teacher t; Student s; t.assignGrades(s, 90); s.display(); }
友元类 • 友元类:是一个类,而且是另一个类的友元 • 友元类的声明:friendclass 类名; • 说明:类A和B,A被声明为B的友元类,则A类的所有成员函数都可以访问B类的私有成员和保护成员。
class Student;//前向声明,类名声明 class Teacher {//... public: void assignGrades(Student& s,float d);//修改学生的成绩 void changeNum(Student& s,string &n);//修改学生的姓名 }; class Student { public: friend class Teacher;//友类 void display(); private: float score; string num; };
void Teacher::assignGrades(Student& s,float d) { s.score=d; //修改学生的成绩 } void Teacher::changeNum(Student& s, string &n) { s.num=n; } void Student::display() { cout<<"num="<<num<<endl; cout<<"score="<<score<<endl; }
小结: 友元提供了在不同类的成员函数之间、类的成员函数与一般函数之间进行数据共享的机制。通过友元,一个普通函数或另一个类中的成员函数可以访问类中的私有成员和保护成员。 友元的正确使用能提高程序的运行效率, 破坏了类的封装性和数据的隐蔽性
运算符 运算符名称 禁止重载的理由 ? : 条件运算符 C++中没有定义三目运算符的语法 . *. -> 成员访问操作符 为保证成员操作符对成员访问的安全性 :: 作用域操作符 该操作符右操作数不是表达式 sizeof 类型字长操作符 该操作符的操作数为类型名,不是表达式 §运算符重载(Operator Overloading) 1、什么是运算符重载: 运算符重载是对已有的运算符赋予多重含义,使其能按用户的要求完成一些特定的操作。 2、运算符重载的规则 • 可以重载C++中除下列运算符外的所有运算符:
不改变原运算符的优先级和结合性。 • 不能改变操作数个数。 • 经重载的运算符,其操作数中至少应该有一个是自定义类型。 • 对于相同类的两个对象使用赋值运算符而不用重载,默认的方式是复制数据成员。地址运算符(&)也无需重载就可以用于任何类的对象,它返回对象内存的地址 3、运算符重载为类的成员函数形式: 函数类型 operator 运算符(形参表);
如在日期类中对赋值运算符进行重载,使它能完成如下的操作如在日期类中对赋值运算符进行重载,使它能完成如下的操作 原理:C++编译器把表达式 d="2005-05-05" 解释为函数调用: d. operator= ("2005-05-05") Date d; d="2005-05-05"; class Date{ public: Date& operator=(const string& s); …… } Date& Date::operator=(const string& s){ year = atoi(s.substr(0,4).c_str()); month = atoi(s.substr(5,2).c_str()); day = atoi(s.substr(8,2).c_str()); return *this; } 每个成员函数(静态成员函数除外)都有一个this指针变量。this是一个隐含的指针。当某个对象调用成员函数时,成员函数的this指针便指向该对象。this 指针只能在成员函数内使用,并且不能被更新。
(1)运算符重载为类成员函数时,参数个数是原操作数个数-1(后置++、--除外)(1)运算符重载为类成员函数时,参数个数是原操作数个数-1(后置++、--除外) Date& Date::operator=(const string& s){ year = atoi(s.substr(0,4).c_str()); month = atoi(s.substr(5,2).c_str()); day = atoi(s.substr(8,2).c_str()); return *this; } Q:如果返回的是类对象,那么采用值返回或引用返回?
(2)值返回与引用返回 • 如果返回的是作用域范围内的对象,那么用值返回与引用返回都行,只是性能上不同而已。 Date Date::operator=(const string& s){ year = atoi(s.substr(0,4).c_str()); month = atoi(s.substr(5,2).c_str()); day = atoi(s.substr(8,2).c_str()); return *this; } Date d; d="2005-05-05";