《C++大学教程》学习笔记(九)
1.Time类实例研究
1.1包含防护
在开始之前,先说明一个重要的C++软件工程概念:在头文件中使用“包含防护”,从而避免头文件中的代码被多次包含到同一源代码文件中的情况。
通过在头文件中加入预处理指令#ifndef,#define,#endif即可构成“包含防护”。同时,在预处理指令 #ifndef 和 #define 中,应使用大写的头文件名,并用下划线代替圆点。
具体使用方法如下所示:
//prevent multiple inclusions of header
#ifndef TIME_H
#define TIME_H
...
#endif
1.2Time类的实例
创建一个Time类,实现对时间(HH:MM:SS)的基本操作。Time实例中具体实践了“包含防护”,异常处理这些内容,下面给出文件结构及代码:
Time.h文件:
//包含防护
#ifndef Time_h
#define Time_h
class Time{
public:
Time();
void setTime(int,int,int);
void printUniversal() const; //输出格林威治时间
void printStandard() const; //输出标准时间
private:
unsigned int hour;
unsigned int minute;
unsigned int second;
};
#endif /* Time_h */
Time.cpp文件:
#include "Time.h"
#include <iostream>
#include <iomanip>
#include <stdexcept>
using namespace std;
Time::Time()
: hour(0),minute(0),second(0)
{
//Nothing
}
void Time::setTime(int h, int m, int s){
if ((h >= 0 && h <=24) && (m >= 0 && m <= 60) && (s >= 0 && s <= 60)) {
hour = h;
minute = m;
second = s;
}
else
throw invalid_argument("Wrong Input!\n");
}
void Time::printUniversal() const {
cout << setfill('0') << setw(2) << hour << ":"
<< setw(2) << minute << ":"
<< setw(2) << second << endl;
}
void Time::printStandard() const {
cout << setfill('0') << setw(2) << ((hour == 0 || hour == 12) ? 12 : hour % 12) << ":"
<< setw(2) << minute << ":"
<< setw(2) << second << (hour < 12 ? " AM" : " PM") << endl;
}
main.cpp文件:
#include <iostream>
#include <stdexcept>
#include "Time.h"
using namespace std;
int main(){
Time t;
cout << "The initial universal time is: ";
t.printUniversal();
cout << "The initial standard time is: ";
t.printStandard();
t.setTime(13, 27, 6);
cout << "\nThe universal time after setTime is: ";
t.printUniversal();
cout << "The tandard time after setTime is: ";
t.printStandard();
try {
t.setTime(99, 99, -100);
} catch (invalid_argument e) {
cout << "\nException: " << e.what(); //注意e.what()的what函数是属于logic_error命名空间的
}
return 0;
}
运行结果如下图所示:
1.3通过对象、引用、指针访问类的public成员
以下三种方式调用成员函数的方式不同,但归根到底都只是对同一个Time对象(即 t )进行操作。
Time t;
Time &tRef = t;
Time *tPtr = &t;
t.setTime(0, 0, 0);
tRef.setTime(1, 1, 1);
tPtr->setTime(2, 2, 2);
2.改进型Time类:具有默认实参的构造函数
在原有的Time类上加以改进,引入了具有默认实参的构造函数,优化了setTime函数,为每个private数据成员搭配了相应的get、set函数。
具体代码如下所示:
Time.h文件:
//包含防护
#ifndef Time_h
#define Time_h
class Time{
public:
explicit Time(int = 0,int = 0,int = 0);
void setTime(int,int,int);
void setHour(int);
void setMinute(int);
void setSecond(int);
unsigned int getHour() const;
unsigned int getMinute() const;
unsigned int getSecond() const;
void printUniversal() const; //输出格林威治时间
void printStandard() const; //输出标准时间
private:
unsigned int hour; // 0 - 23
unsigned int minute; // 0 - 59
unsigned int second; // 0 - 59
};
#endif /* Time_h */
Time.cpp文件:
#include "Time.h"
#include <iostream>
#include <iomanip>
#include <stdexcept>
using namespace std;
Time::Time(int hour,int minute,int second){
setTime(hour, minute, second);
}
void Time::setTime(int h, int m, int s){
setHour(h);
setMinute(m);
setSecond(s);
}
void Time::setHour(int h){
if (h >= 0 && h < 24)
hour = h;
else
throw invalid_argument("hour must be 0-23");
}
void Time::setMinute(int m){
if (m >= 0 && m < 60)
minute = m;
else
throw invalid_argument("minute must be 0-59");
}
void Time::setSecond(int s){
if (s >= 0 && s < 60)
second = s;
else
throw invalid_argument("second must be 0-59");
}
unsigned int Time::getHour() const{
return hour;
}
unsigned int Time::getMinute() const{
return minute;
}
unsigned int Time::getSecond() const{
return second;
}
void Time::printUniversal() const {
cout << setfill('0') << setw(2) << hour << ":"
<< setw(2) << minute << ":"
<< setw(2) << second << endl;
}
void Time::printStandard() const {
cout << setfill('0') << setw(2) << ((hour == 0 || hour == 12) ? 12 : hour % 12) << ":"
<< setw(2) << minute << ":"
<< setw(2) << second << (hour < 12 ? " AM" : " PM") << endl;
}
main.cpp文件:
#include <iostream>
#include <stdexcept>
#include "Time.h"
using namespace std;
int main(){
Time t1;
Time t2(2);
Time t3(21,34);
Time t4(12,25,42);
cout << "t1:\n";
t1.printUniversal();
t1.printStandard();
cout << "\nt2:\n";
t2.printUniversal();
t2.printStandard();
cout << "\nt3:\n";
t3.printUniversal();
t3.printStandard();
cout << "\nt4:\n";
t4.printUniversal();
t4.printStandard();
try {
Time t5(23,61,59);
} catch (invalid_argument e) {
cout << "\nException: " << e.what() << endl; //注意e.what()的what函数是属于logic_error命名空间的
}
return 0;
}
运行结果如下图所示:
3.零零散散
析构函数(destructor)是一种特殊的成员函数,它不接收任何参数,也不返回任何值。当对象撤销时,类的析构函数会隐式地调用。析构函数的声明方式如下所示:
class Employee{
public:
Employee(const string&,const string&,const Date&,const Date&);
~Employee();
private:
...
...
};
返回一个private类型的数据成员的引用或指针会破坏类的封装性,可以参考书P299。
一般情况下,类的对象是可以相互赋值的,比如:
Date date1(7,4,2004);
Date date2;
date2 = date1;
对于每个类,编译器都提供一个默认的复制构造函数,可以将原始对象(date1)的每个成员复制到新对象(date2)的相应成员中。
但这种复制并不是总是有效的,当所用类的数据成员包含指向动态分配内存的指针时,默认复制构造函数会引发严重问题,这种时候就需要自己去定义一个定制的复制构造函数。
一个“const”对象的“常量性”是从构造函数完成对象的初始化到析构函数被调用之间,因此将构造函数和析构函数声明为const是一个编译错误。
const对象的使用是非常严格的:即const对象只能访问被显式声明为const的成员函数,无论这个函数是否修改了这个对象。
类的friend函数(友元函数)在类的作用域之外定义,却具有访问类的public、private、protected成员的权限。友元定义可以放在类定义的任何地方,即使friend函数的原型在类定义内出现,友元仍不是成员函数。
友元关系是授予的而不是索取的,既不是对称的也不是传递的,声明一个友元函数如下所示:
class Court{
friend void setX(Count & , int); //友元函数定义
public:
...
...
private:
int x;
};
4.组成:对象作为类的成员
软件复用性的一个普遍的形式是组成,即一个类将其他类的对象作为成员。
在下面这个实例中,Employee类中将Date类的两个对象作为成员,具体的文件结构与代码如下所示:
Date.h文件:
#ifndef Date_h
#define Date_h
class Date{
public:
static const unsigned int monthsPerYear = 12;
explicit Date (int = 1,int = 1,int = 1);
void print() const;
~Date();
private:
unsigned int month; //1 - 12
unsigned int day; //1 - 31 based on month
unsigned int year;
unsigned int checkDay(int) const;
};
#endif /* Date_h */
Date.cpp文件:
#include "Date.h"
#include <iostream>
#include <array>
#include <stdexcept>
using namespace std;
Date::Date(int m,int d,int y){
if (m > 0 && m <= monthsPerYear)
month = m;
else
throw invalid_argument("month must be 1 - 12\n");
year = y;
day = checkDay(d);
cout << "Date object constructor for date: ";
print();
cout << endl;
}
Date::~Date(){
cout << "Date object destructor for date: ";
print();
cout << endl;
}
void Date::print() const {
cout << month << "/" << day << "/" << year;
}
unsigned int Date::checkDay(int testDay) const{
static const array< int , monthsPerYear + 1 > daysPerMonth =
{0,31,28,31,30,31,30,31,31,30,31,30,31};
if (testDay > 0 && testDay <= daysPerMonth[month])
return testDay;
if (testDay == 29 && month == 2 && (year % 400 == 0 || (year % 4 == 0 && year%100 != 0)))
return testDay;
throw invalid_argument("Invalid day for current month and year\n");
}
Employee.h文件:
#ifndef Employee_h
#define Employee_h
#include "Date.h"
#include <string>
using namespace std;
class Employee{
public:
Employee(const string&,const string&,const Date&,const Date&);
~Employee();
void print() const;
private:
string firstName;
string lastName;
const Date birthDate;
const Date hireDate;
//当Employee对象撤销时,这两个Date也要撤销,即执行析构函数
};
#endif /* Employee_h */
Employee.cpp文件:
#include "Employee.h"
#include "Date.h"
#include <iostream>
using namespace std;
Employee::Employee(const string &first,const string &last,const Date &birth,const Date &hire)
:firstName(first),lastName(last),birthDate(birth),hireDate(hire)
{
cout << "Employee object constructor: " << firstName << " " << lastName << endl;
}
void Employee::print()const {
cout << lastName << ", " << firstName << " Birthday: ";
birthDate.print();
cout << " Hired: ";
hireDate.print();
cout << endl;
}
Employee::~Employee(){
cout << "Employee object destructor: " << firstName << " " << lastName << endl;
}
main.cpp文件:
#include "Date.h"
#include "Employee.h"
#include <iostream>
using namespace std;
int main(){
Date birth(7,24,1949);
Date hire(3,12,1988);
Employee manager("Bob","blue",birth,hire);
cout << endl;
manager.print();
cout << endl;
return 0;
}
上述程序的运行结果是:
5.this指针
每个对象都可以用一个称为this的指针来访问自己的地址,通过使用this指针可以避免命名冲突。比如:
//Time类中的setHour成员函数
void Time::setHour(int hour){
if (hour >= 0 && hour < 24)
this->hour = hour;
//等号左边是数据成员hour,等号右边是局部变量hour
else
throw invalid_argument("hour must be 0-23");
}
除了
this->hour
这种访问方式之外,
(* this).hour
也是可以的。
除此之外,使用this指针还能使串联的函数调用成为可能,具体的使用方法可以见书P310。
6.static成员与static成员函数
我们考虑这么一种情况:在Employee类引入一个普通的count变量来记录实际雇员的个数,而Employee类又会产生多个实例对象。当一名雇员加入或离职时,必须对所有对象的count值都进行修改(以保持统一),这显然是不必要的。
事实上,我们只需要一个static的count变量,就能很好的表示这一被所有实例所共享的性质。
由于static成员在对象撤销之后仍然存在,所以要想使用static成员,就必须搭配相应的static成员函数。
我们以Employee类来简单说明:
#ifndef Employee_h
#define Employee_h
class Employee{
public:
...
...
static unsigned int getCount(); //作为static数据成员搭配使用的static成员函数
private:
...
...
static unsigned int count; //无法在此初始化,需要到.cpp文件中初始化
};
#endif /* Employee_h */
getCount函数的具体实现则是:
unsigned int Employee::getCount(){
return count;
}
count的初始化(.cpp文件中)则是:
unsigned int Employee::count = 0;
在main函数中,当没有一个Employee对象时,通过
Employee::getCount()
可以访问到count的值。
而当存在Employ对象时,则通过
Employee e1("a","b");
cout << "Employee number after: " << e1.getCount() << endl;
cout << "Employee number after: " << Employee::getCount() << endl << endl;
两种方式均可访问。