- 1 Time类实例研究
- 1.1 包含防护
- 1.2 异常抛出
- 2 类的作用域和类成员的访问
- 圆点成员选择运算符(.)和箭头成员选择运算符(->)
- 3 访问函数和工具函数
- 访问函数
- 工具函数
- 4 Time类实例研究:具有默认实参的构造函数
- C++11:使用列表初始化器调用构造函数
- C++11:重载的构造函数和委托构造函数
- 5 析构函数
- 6 何时调用构造函数和析构函数
- 7 Time类实例研究:微妙的陷阱——返回private数据成员的引用或指针
- 8 默认的逐个成员赋值
- 9 const对象和const成员函数
- 10 组成:对象作为类的成员
- 11 friend函数和friend类
- friend的声明
- 重载友元函数
- 12 使用this指针
- 使用this指针来避免名字冲突
- this指针的类型
- 使用this指针使串连的函数调用成为可能
- 13 static类成员
- 静态数据成员的作用域和初始化
- 访问静态数据成员
#ifndef TIME_H
#define TIME_H
// class definition
class Time
{
public:
Time(int =0,int = 0,int =0);//default constructor
void SetTime(int, int, int);//set hour,minute and second
void PrintUniversal() const;// print time in universal-time format
void PrintStandard() const;// print time in standard-time format
private:
unsigned int hour;//0-23
unsigned int minute;//0-59
unsigned int second;//0-59
};
#endif
当构建大型程序时,头文件中还会放入其他的定义和声明。在名字TIME_H已被定义时,前述的包含防护可以阻止将ifndef(意思是“如果没有定义”)和endif之间的代码包含到文件中。如果以前没有在文件中包含此头文件,那么TIME_H这个名字将被define指令定义,并且包含该头文件的语句。
应利用预处理器指令ifndef、define和endif等构成包含防护,从而避免头文件在一个程序中被多次包含。
按照惯例,在头文件的预处理器指令ifndef和define中,应使用大写的头文件名,并用下画线代替圆点。
// Time.cpp // Time class member-function definitions #include#include #include // for invalid_argument exception class #include "time.h" using namespace std; // Time constructor initialze the data member to zero. Time::Time(int h,int m,int s) { SetTime(h,m,s); } //set new time value using universal time void Time::SetTime(int h, int m, int s) { if((h>=0&&h<24)&&(m>=0&&m<60)&&(s>=0&&h<60)) { hour = h; minute = m; second = s; } else throw invalid_argument("values for hour, minute, second are invalid!"); } void Time::PrintUniversal() const { cout << setfill('0')< cout < 任何超出指定范围的值都会造成setTime 抛出一个类型为invalid_argument (来自头文件
的异常)。throw语句创建了一个类型为invalid_argument 的新对象。跟在名称后边的圆括号表示对该对象构造函数的一个调用,其中允许我们指定一个用户自定义的错误信息字符串。在异常对象被创建后此throw语句立即终止函数setTime,然后异常返回到尝试设置时间的代码处。 参数化的流操纵运算符setfill用于指定输出域宽大于输出整数值中数字个数时所显示的填充字符。因为默认情况下数的输出是右对齐的,填充字符出现在树中数字的左边。如果将输出的数填满了指定的域宽,那么不显示填充字符。
setfill时黏性设置,setw是非黏性设置。
尽管类定义中声明的成员函数可以定义在类定义的外部,并通过二元作用域分辨运算符“绑定”到该类,然而这样的成员函数仍在该类的作用域之内。
#include#include using namespace std; #include "Time.h" // main program int main() { Time t;//instantiate object t of class Time //output Time object t's initial value cout<<"The inital universal time is "; t.PrintUniversal(); cout<<"nThe inital stardard time is "; t.PrintStandard(); t.SetTime(14, 12, 2); cout << "nnUniversal time after setTime is "; t.PrintUniversal(); cout << "nStandard time after setTime is "; t.PrintStandard(); try { t.SetTime(2,92,5); } catch (invalid_argument &e) { cout<< "Exception: " << e.what()< t.SetTime(2,92,5) 为无效值调用,异常被捕获后通过调用what成员函数打印异常信息。
2 类的作用域和类成员的访问类的数据成员和成员函数属于该类的作用域。在类的作用域内,类的成员可以被所有成员函数直接访问,也可以通过名字引用。在类的作用域之外,public类成员可通过对象的句柄之一而引用。句柄可以是对象名称、对象的引用或者对象的指针。
圆点成员选择运算符(.)和箭头成员选择运算符(->)给定以下定义:
Account account; Account &accountRef = account; Account *accountPtr = &account;圆点成员选择运算符前面加对象名称或者对象的引用,则可以访问该对象的成员。
account.setBalance(123.45); accountRef.setBalance(123.45);箭头成员选择运算符前面加对象的指针,则可以访问该对象的成员。
accountPtr->setBalance(123.45);3 访问函数和工具函数 访问函数访问函数可以读取或者显示数据。访问函数另一个常见用法是测试条件是真还是假,常常称这样的函数为判定函数(predicate function)。例如,任何容器类都有的isEmpty函数就是判定函数。
工具函数工具函数(也称为助手函数)是一个用来支持类的其他成员函数操作的private成员函数。工具函数需要被声明为private的,因为它们不希望被类的客户所使用。工具函数的一个非常普遍的使用情况是希望将一些公共代码放在一个函数中,否则这些代码将重复出现在多个成员函数中。
4 Time类实例研究:具有默认实参的构造函数每个类最多只有一个默认构造函数
如果类的成员函数已经提供了类的构造函数(或其他成员函数)所需要的全部或者部分功能,那么就可以在构造函数(或其他成员函数)中调用这样的成员函数。
C++11:使用列表初始化器调用构造函数C++11现在提供了一个统一的初始化语法,称之为列表初始化器,可以用来初始化任何变量。
Time t2(2); Time t3(21,34); Time t4(12,25,42);上述语句可以用列表初始化器写成如下形式:
Time t2{2}; Time t3{21,34}; Time t4{12,25,42};或者
Time t2 = {2}; Time t3 = {21,34}; Time t4 = {12,25,42};C++11:重载的构造函数和委托构造函数重载的构造函数允许用不同类型和数量的实参初始化对象。例如;
Time(); Time(int); Time(int,int); Time(int,int,int);C++11现在也允许构造函数调用同一个类中的其他构造函数。这样的构造函数称为委托构造函数(delegating constructor)。例如:
Time::Time() :Time(0, 0, 0) //delegate to Time(int,int,int) { } Time::Time(int hour) : Time(hour, 0, 0) //delegate to Time(int,int,int) { } Time::Time(int hour, int minute) : Time(hour, minute, 0) //delegate to Time(int,int,int) { }5 析构函数析构函数(destructor)是另一种特殊的成员函数。当对象撤销时,类的析构函数会隐式地调用。实际上,析构函数本身并不释放对象占用的内存空间,它只是在系统回收对象的内存空间前执行扫尾工作,这样内存可以用来保存新的对象。
每个类都有一个析构函数。如果程序员没有显式地提供析构函数,那么编译器将生成一个“空的”析构函数。
6 何时调用构造函数和析构函数构造函数和析构函数调用发生的顺序由执行过程进入和离开对象实例化的作用域的顺序决定。一般而言,析构函数的调用顺序与相应的构造函数的调用顺序相反。但是,对象的存储类别可以改变调用析构函数的顺序。
//CreateAndDestroy.h #includeusing namespace std; #ifndef CDORDER_H #define CDORDER_H class CreateAndDestroy { public: CreateAndDestroy(int, string); ~CreateAndDestroy(); private: int objectID; string message; }; #endif //CreateAndDestroy.cpp #include#include "CreateAndDestroy.h" using namespace std; CreateAndDestroy::CreateAndDestroy(int ID, string messageString) :objectID(ID),message(messageString) { cout<< "Object " << objectID << " constructor runs " << message << endl; } CreateAndDestroy::~CreateAndDestroy() { cout << (objectID==1||objectID==6?"n":""); cout<< "Object " << objectID << " destructor runs " << message << endl; } #include#include "CreateAndDestroy.h" using namespace std; void create(void); CreateAndDestroy first(1, "(global before main)"); int main() { cout << "nMAIN FUNCTION EXCUTION BEGINS" << endl; CreateAndDestroy second(2, "(local automatic in main)"); static CreateAndDestroy third(3, "(local static in main)"); create(); cout<< "nMAIN FUNCTION EXECUTION RESUMES" < cout << "nCREATE FUNCTION: EXECUTION BEGINS" < 以下为程序运行结果:
Object 1 constructor runs (global before main) MAIN FUNCTION EXCUTION BEGINS Object 2 constructor runs (local automatic in main) Object 3 constructor runs (local static in main) CREATE FUNCTION: EXECUTION BEGINS Object 5 constructor runs (local automatic in create) Object 6 constructor runs (local static in create) Object 7 constructor runs (local automatic in create) CREATE FUNCTION: EXECUTION ENDS Object 7 destructor runs (local automatic in create) Object 5 destructor runs (local automatic in create) MAIN FUNCTION EXECUTION RESUMES Object 4 constructor runs (local automatic in main) MAIN FUNCTION EXECUTION ENDS Object 4 destructor runs (local automatic in main) Object 2 destructor runs (local automatic in main) Object 6 destructor runs (local static in create) Object 3 destructor runs (local static in main) Object 1 destructor runs (global before main)7 Time类实例研究:微妙的陷阱——返回private数据成员的引用或指针如在Time.h中定义了public的成员函数返回了对private数据成员的引用:
class Time { public: unsigned int& badSetHour(int hh) { if (hh >= 0 && hh < 24) { hour = hh; } else throw invalid_argument("hour must be 0-23!"); return hour; } private: unsigned int hour; };在main函数中出现了以下语句:
int &hourRef = t.badSetHour(20); //20 is a valid hour hourRef = 30; //invalid t.badSetHour(12) = 74; //invalid这恰恰展示了hourRef如何破坏类的封装性——main函数中的语句不应该有访问该类的private数据的权利。
8 默认的逐个成员赋值赋值运算符(=)可以将一个对象赋给另一个类型相同的对象。默认情况下,这样的赋值通过逐个成员赋值的方式进行。如:
Date date1(7,4,2004); Date date2; date2 = date1;对象可以作为函数的实参进行传递,也可以由函数返回。这种传递和返回默认情况下是以按值传递的方式执行的。所谓按值传递是指传递和返回对象的一份副本。
9 const对象和const成员函数const Time noon(12,0,0);该语句声明了一个Time类的const对象noon,并初始化为中午12点。
对于const对象,C++编译器不允许进行成员函数的调用,除非成员函数本身也声明为const 。
对于构造函数和析构函数来说,两者都会修改对象,所以试图将构造函数和析构函数声明为const是一个编译错误。
10 组成:对象作为类的成员软件复用性的一个普遍的形式是组成,即一个类将其他类的对象作为成员。在创建对象时,构造函数自动调用。
成员对象以在类定义中声明的顺序(不是以在构造函数的成员初始化器列表中列出的顺序)且在包含它们的对象(有时称为宿主对象)构造前建立。
为了清晰起见,成员初始化器列表的顺序与类数据成员声明的顺序一致。
类类型成员的对象必须是private的,否则会破坏包含类实现的封装性和隐藏性。
11 friend函数和friend类类的friend函数(友元函数)在类的定义域之外定义,却具有访问类的非public(以及public)成员的权限。单独的函数、整个类和其他类的成员函数都可以被声明为另一个类的友元。
friend的声明在类定义中函数原型前加保留字friend,就将函数声明为该类的友元。如:
friend class ClassTwo;友元关系不是对称的,也不是传递的。
重载友元函数可以指定重载函数为类的友元。每个打算成为友元的重载函数必须在类的定义里显示地声明为类的一个友元。
即使friend函数的原型在类定义内出现,友元仍不是成员函数。
友元定义可以放在类定义内任何地方,但一般放在最前面的位置,并且不要在其前面添加任何成员访问说明符。
12 使用this指针每个对象可以使用一个称为this的指针来访问自己的地址。对象的this指针不是对象本身的一部分,也就是this指针占用的内存大小不会反映在对对象进行sizeof运算得到的结果中。
使用this指针来避免名字冲突void Time::setHour(int hour) { if (hour >= 0 && hour < 24) this->hour = hour; else throw invalid_argument("hour must be 0-23"); }在该函数定义中,setHour的参数与数据成员hour有相同的名字。在setHour的作用域内,参数hour将隐藏数据成员。然而,你能使用this->来获取访问该数据成员hour的资格。
this指针的类型this指针的类型取决于对象的类型及使用this的成员函数是否被声明为const。例如在Employee类的非const成员函数中,this指针具有的类型是Employee* const(一个指向非const Employee对象的const指针)。可是在Employee类的const成员函数中,this指针具有的类型却为const Employee* const (指向一个const Employee对象的const指针)。
class Test { private: int x; }; void Test::print() const { cout << x << endl; cout << this->x << endl; cout << (*this).x << endl; }使用this指针使串连的函数调用成为可能先在头文件中定义:
class Time { public: Time& setHour(int); Time& setMinute(int); Time& setSecond(int); private: unsigned int hour; unsigned int minute; unsigned int second; };在类的实现中为:
Time& Time::setHour(int h) { if (h >= 0 && h < 24) hour = h; else throw invalid_argument("hour must be 0-23"); return *this; } Time& Time::setMinute(int m) { if (m >= 0 && m < 24) minute = m; else throw invalid_argument("minute must be 0-59"); return *this; } Time& Time::setSecond(int s) { if (s >= 0 && s < 24) second = s; else throw invalid_argument("second must be 0-59"); return *this; }在main函数中:
Time t; t.setHour(18).setMinute(30).setSecond(22);13 static类成员对于类的每个对象来说,一般都满足一条规则,即它们各自拥有类所有数据成员的一份副本。但对static数据成员而言,仅有变量的一份副本供类的所有对象共享。
静态数据成员的作用域和初始化尽管类的static数据成员看上去就像是全局变量,但它们只在类的作用域起作用。静态数据成员必须被精确地初始化一次。基本类型的static数据成员默认情况下将初始化为0。注意类类型的static数据成员(即static成员对象),如果这个类类型具有默认构造函数,那么这样的数据成员无须初始化,因为它们的默认构造函数将会被调用。
访问静态数据成员类的private和protected的static成员通常通过类的public成员函数或者类的友元访问。即使在没有任何类对象存在时,类的static成员仍然存在。当没有类的对象存在时,要访问类的public static成员,只需在数据成员名前加类名和二元作用域分辨运算符(::)即可。



