自己的学习笔记,C++中关于对象和类的说明
I
***类,多么富有艺术性的词!想一想能把真实世界中的一切在它中模拟实现,就让人兴奋不已。掌握了它也就掌握了程序艺术的真谛、也就掌握了打通了现实与虚幻的时空门!---Skyala***
Skyala请您记住下面惊人的相似规律: 人(类)->父母(对象)->子女(继承、多重继承)->子孙(多态性)---哇,My God!多么完美的曲线!
面向对象程序设计总结:向对象发送消息。 可以减少函数传递过程中的参数 建立的类要有用,实用为准!
一、类杂谈 1。定义: 类:对实体的属性和行为的一般描述。 Skyala注:太完美的概念是定义不来的 是程序设计中的基本单位。可以说是一种新的数据类型,所以有这么多的面向对象程序设计语言。太灵活了
类的数据部分:数据成员(data member) ,其初始化不能在定义体中声明它们的地方初始化,而应该用类 的构造函数初始化或给它们设置值的函数赋值。 最好保持类的所有数据成员都是私有的,提供函数来操作这些数据,可隐藏类的实现、减少错误, 提高可修改性。 外部不能访问类的私有成员,只能利用成员函数。记住main也是一个类的外部
类的函数部分:成员函数(member function),在外部访问类公有成员函数,也要用来成员访问运算符
对象:由类产生的大量的项称为对象也即“存储的一个区域” 对象是类的最终实例化。 对象初始化 Time T(23)在对象名右,分号前的圆括号中
类是C中结构的自然延伸。
2。类的成员访问运算符: 圆点:通过对象名或对对象的引用访问 p.t 箭头:通过指向对象的指针访问 p->t
4。构造函数与析构函数 a.与类同名的成员函数叫“构造函数(constructor)”,用来初始化类的对象的数据成员。 构造函数:没有返回类型--所以没有返回值,可重载 给构造函数提供默认参数值即使调用构造函数时没有提供参数值,也会确保按默认参数初始化。所有的参数都是默认参数的也是默认构造函数。 不要在构造函数中调用其它函数(虽然这是允许的)但在初始化正确地完成之前使用数据成员可能会导致错误
b. 与类同名但加了“~”(按位取反运算符)的成员函数叫“析构函数(destructor)”,系统回收内存前 做清理工作 析构函数:没有返回类型--所以没有返回值,不能重载--所以在类中只有一个
c.在对象的建立和撤销时分别自动调用相应的构造和析构函数,两者的调用顺序正好相反 根据对象的不同:全局作用域(程序终止)、局部对象(声明对象的程序块)、静态static局部对象(程序终止,但在全局前)
5。 接口和实现 类的定义:包括数据成员和成员函数的声明,引处的成员函数声明也即函数的原型。 要用分号结束类的定义 接口:类的公有函数(访问说明符public中的函数)提供的操作该类的数据成员的方法,此种函数也叫接 口 把类的声明放入某个头文件中构成类的公有接口 实现:类的成员函数的定义部分,在外部定义的要用双目运算符“::”使用成员函数, 把成员函数的定义放入某个源文件中,从而构成类的实现--信息隐藏 用户可以访问类的接口,便不能访问类的实现 好处:只要接口没变,实现改变了只要使用类的代码重新编译而不需改动。
**注软件工程的基本原则: 最低访问权原则:(当然相对于用户了--类的使用者) 除了很小的函数外,所有的成员函数都应在类定义体外定义,有利于接口和实现分离。能鼓励独立软件销售商(ISV)类库作为商品 ---Skyala:全部行业有公用类,统一规化软件类库,多么有趣的一件事
6。一个成员函数中类的内部定义的会自动成为内联函数(inline),外部的要加inline。 7。防止多次包含相同的头文件time.h为头文件名,TIME为假设的类,"_"代替".",此处运用了预处理指令 #ifndef TIME_H #define TIME_H . . #endif 8。公有(Public)、私有(private)、受保护(protected) 私有数据成员只能被本类的成员函数或类的友元访问 类的默认情况下是私有访问类型 9。访问函数及工具函数 set 设置函数,可进行合法性检查。get 获取函数的地位。 不仅有助于保护数据的完整性,而且也使得数据成员的实现方式对客户隐藏起来
10。不要让公有成员函数返回对私有数据成员的非常量引用(或指针) 如下: public: int &badset(); int Time::badset(inthh) { return hour; } T.badset(12) = 74; //返回的引用函数调用可作左值。
11。“=”可以将一个类赋值给同类型的另一个类,通过逐个成员拷贝的默认赋值方式实现的
应用举例:LeapYear.h、LeapYear.cpp 注:这是个闰年的计算,不过本人不满意,有点儿乱。 编译环境:Window2000 Vc6.0 #ifndef LEAPYEAR_H //预处理指令,防止包含多个同名头文件 #define LEAPYEAR_H //"-"代替"." class LeapYear { public: //可以给构造函数提供默认参数,无返回值无返回类型 LeapYear (int, int, int); ~LeapYear (); int getYear (); void setMonth (int); int getDay (); void print () const; //const成员函数
private: int iYear; int iMonth; //int iday; 要注意大小不要输入错误 int iDay; int checkDay (int); //工具函数 }; //类定义结束 #endif
#include <iostream> #include <string> #include "LeapYear.h" using namespace std;
//成员初始化 LeapYear::LeapYear (int iY, int iM ,int iD) { iMonth = iM; iYear = iY; //iDay = checkDay (int iD); //函数调用格式错误 iDay = checkDay (iD); cout << "constructor sucessed!" << endl; }
LeapYear::~LeapYear() { cout << "对象内存释放成功!" << endl; }
int LeapYear::getYear () { return iYear; }
void LeapYear::setMonth (int iM) { iMonth = (iM < 12 && iM > 1)?iM: 1; }
int LeapYear::getDay () { return iDay; }
int LeapYear::checkDay (int testDay) { static int dayPerMonth [13] ={0, 31, 28, 31,30, 31, 30, 31, 31, 30, 31, 30, 31};
if (iMonth != 2) { if (testDay > 0 && testDay <= dayPerMonth [iMonth]) return dayPerMonth [iMonth]; } else { //闰年计算公式 int days = (iYear % 400 == 0 || (iYear % 4 == 0 && iYear % 100 != 0)?29: 28); if (testDay > 0 && testDay <= days) return days; }
cout << "今天Day" << testDay << "invalid set to day \n"; return 1; }
void LeapYear::print () const { cout << iYear << '/' << iMonth << '/' << iDay; }
void main() { LeapYear lyDay (1994, 2, 4); // lyDay.setDay (4); lyDay.print (); cout << "年份" << lyDay.getYear () << endl; cout << "月日" << lyDay.getDay () << endl; }
II
***再续前情,此生不待 ---Skyala***
Skyala请您记住下面惊人的相似规律: 人(类)->父母(对象)->子女(继承、多重继承)->子孙(多态性)---哇,My God!多么完美的曲线!
面向对象程序设计总结:向对象发送消息。 可以减少函数传递过程中的参数 建立的类要有用,实用为准!
从此部分开始笔记中加入声明功能,并在每一定义部分后加入小程序(伪代码)用来讲解,方便多了。
*************************************声明***************************** 1. const对象和const函数 2. 友元(friend)类和友元函数 3. 静态(static)数据成员、静态数据函数及静态对象 4. 模块(template)类 5. 其他杂述(复合、包容器类、递取类......) * 信息隐藏:对客户隐藏实现细节 类定义了抽象数据类型(ADT):表示了数据及对数据的操作 C++强调了数据的重要性 * char * temp = new char [stlen (fristname) + 1]; assert (temp != 0); //确认内存分配是否成功
6. 应用举例 a. 堆栈模块类 (TStack.cpp) b. 遍历包容器类的递取类(Queue类) c. List类 7. 问题: this指针不理解 为什么不能用const声明静态成员函数
************************************定义****************************** 1. const对象和const函数 * const对象不能调用非const函数、不能修改const对象 const对象和const变量是不能赋值的,必须要初始化。在构造函数中可以用成员初始值进行初始化 * const成员函数不能调用非const成员函数、不能修改对象的数据成员 非const成员函数可重载为const成员函数 * 特例:当声明一个const对象时与构造函数和析构函数肯定会修改对象矛盾,此处这两个函数都不要声 明关键字const,为了能正确初始化对象,允许这两个函数修改对象 * 切记:当一个类包括const对象时,必须给构造函数提供成员初始化值 class Time { Time(int = 0, int = 0); void setTime(int = 0,int = 0,int = 0); //默认构造函数 int getHour() const {return hour;} //const成员函数
private: int hour; int time const; //const数据成员,要进行成员赋值初始化 }
Time::Time(int s, int m) :time(m) //成员初始化值 { hour = i; time = m; //企图赋值,是错误的 //数据初始化 }
void main() { const Time T (12, 0, 0); // const对象不能赋值必须初始化 T.getHour(); // 正确,只有const成员函数才能访问const对象 T.setTime(); // 错误,非const函数 }
2.友元类和友元函数 友元函数是在类作用域外定义的,但它有权访问该类的私有成员和受保护成员 在类定义中,在函数原型或类前加入friend,它可放入类中任何地方,一般在类定义开始处 是一种“给予”关系,即B是A的友元,B可访问A中的数据成员及成员函数 class count { friend void setX(count & ,int); //声明友元类 public: private: int x; //私有数据成员 }
//看到此处了没有,没有用类的类作用域符号,它是类外的一个函数,区别处 void setX(count &c, int val) { c.x = val; //友元函数所以可以修改私有数据 }
void main() { count c; setX(c, 8); //用友元函数设置x的值,此处也没用类作用域符,它是类作用外的一个函数 }
3.静态数据成员、函数及对象 * 类的对象通常都有该类的所有数据成员的单独拷贝,为了让对象共享一份拷贝引入静态(static)数据成员.可节省内存。好像全局变量但只有类作用域 * 公有的静态数据成员,可以用双目作用域符通过类名访问Time::count(最好在不存在对象时用),也可用类对象访问 T.count,静态成员函数只能用第二种方式 * 私有的和受保护的只能用公有静态成员函数访问。一个成员函数不要访问类的非静态成员时,声明为静态函数有利于节省内存 * 即使不存在类对象,静态数据成员、函数也存在并可使用。 public: static int getCount(); //静态函数 private: static int count; //静态成员 int Time::getCount(return count;)
4.模板类(template ) 模块类需要一种或多种类型参数,所以模板类也叫带参数的数据类型 声明:template <typename elemType>,模板类外的成员函数也都要以这个形式开头(多长了脑袋--Skyala) template <typename elemType> int Stack<elemType>::pop() {} 类名:Stack <elemType>, 实例化:Stack <float> floatStack ,stack为假设的一个模板类对象 多个参数:template <typename felemType,typename selemtype>
5.复合、包容器类、递取类及其他 * 复合: 一个类把另一个作为自己的成员,成员对象是在包括它们的对象之前建立的 成员对象不一定要提供成员初始化值的,其构造函数会自动调用,但如也没默认的构造函数,会出错 * 成员初始化值可避免对成员对象初始化两次(调用默认构造函数及用"set"函数) Time::Time(int hour, int minute, int second) :Minute (minute), Second (second) {}
6.应用举例: 编译环境:window2000 Vc6.0 a. Stack.cpp 堆栈类
#ifndef TSTACK_H #define TSTACK_H template<typename elemType> class Stack { public: Stack (int = 10); //构造函数,栈默认大小为10 ~Stack (); //析构函数 int pop (elemType&); //入栈 int push (const elemType &); //出栈 int isEmpty () const {return _top == -1;} //栈空,返回-1 int isFull () const {return _top == _size - 1;} //栈滿,返回栈顶大小
private: int _size; //堆栈能容纳元素的数目 int _top; //栈顶元素的数目 elemType * _stackPtr; //指向栈的指针 };
template<typename elemType> Stack<elemType>::Stack (int s) { _size = s; _top = -1; //空栈 _stackPtr = new elemType [_size]; }
template<typename elemType> Stack<elemType>::~Stack () { delete [] _stackPtr;}
//压入一元素,如成功返回1,失败返回0 template<typename elemType> int Stack<elemType>::push (const elemType &item) { if (! isFull ()) { _stackPtr [++_top] = item; //如栈没滿则栈顶赋值后加1 return 1; //成功 } return 0; //入栈失败 }
//出栈 template<typename elemType> int Stack<elemType>::pop (elemType &popValue) { if (! isEmpty ()) { popValue = _stackPtr [_top--]; //如栈不为空则赋值后减1 return 1; } return 0; } #endif
#include <iostream.h> #include <stdlib.h>
void main () { Stack<int> floatStack (5); //模板实例化 int fS = 1; cout << "Pushing \n"; while (floatStack.push (fS)) { cout << fS << ' '; fS += 1; } cout << "Full is:" << fS << '\n' << "Poping \n";
while (floatStack.pop (fS)) { cout << fS << ' '; } cout << endl;
Stack<char> charStack (10); char cS = 'a'; cout << "Pushing \n"; while (charStack.push (cS)) { cout << cS << ' '; cS += 1; //字符串可进行运算 } cout << "Full is:" << cS << '\n' << "Poping \n";
while (charStack.pop (cS)) { cout << cS << ' '; } cout << endl; }
程序艺术--没了思想的程序永远不会打动人--天翼(Skyala) 
|