c++编程之OOP示例

通过一个 Time 类示例演示 C++ 面向对象设计、链式调用与接口演进。

1. 示例:使用引用的"Time"类

回到之前版本的"Time"类,假设我们想增加链式操作,例如t.nextSecond().nextSecond().print(),我们就需要让nextSecond()返回this的一个引用。

Time.h

1
2
3
4
5
6
7
class Time {
private:
	......
public:
	Time &nextSecond(); // Return a reference to "this" instance
	......
}

在函数原型中,我们申明了一个nextSecond()的成员函数,返回Time对象的引用。返回的引用可以用来继续调用成员函数。

Time.cpp

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
// Increase this instance by one second and return this instance by reference.
Time &Time::nextSecond() {
	if (++second == 60) {
		second = 0;
		if (++minute == 60) {
			minute = 0;
			if (++hour = 24) {
				hour = 0;
			}
		}
	}

	return *this; // Return this instance by reference
				  // "this" is a pointer to this instance. *this refers to the instance.
}

C++有一个关键字"this",它保存了一个指向当前实例的指针。也就是说,*this指的是当前实例。

TestTime.cpp

1
2
3
4
5
6
Time t1(23, 59, 58);
t1.print();
t2.nextSecond();
t1.print();
t1.nextSecond().nextSecond().print();
t1.print();

2. 示例:“Time"类的第三个版本,异常处理

输入检验是必要的,例如,在setHour(int h)函数中,我们需要检验输入的参数是 0~23 之间的数字。校验参数很简单,但是如果校验失败的话我们该如何处理错误呢,我们是输出一句错误 提示然后终止程序呢,还是输出一个警告,然后继续执行程序直到错误不能被容忍为止呢?这两种方式都不好。

1
2
3
4
5
6
7
8
void Time::setHour(int h) {
	if (h >= 0 && h <= 23) {
		hour = h;
	} else {
		cout << "Error: Invalid hour! Hour shall be 0~23." << endl;
		exit(1);
	}
}
1
2
3
4
5
6
7
8
void Time::setHour(int h) {
	if (h >= 0 && h <= 23) {
		hour = h;
	} else {
		cout << "Warning: Invalid hour! Hour shall be 0-23." << endl;
		hour = 0;
	}
}

取而代之的是,C++提供了一种异常处理机制(在头文件),这种机制能够优雅地处理异常。

Time.h

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
#ifndef TIME_H
#define TIME_H

class Time {
private:
	int hour;
	int minute;
	int second;

public:
	Time(int h = 0, int m = 0, int s = 0);
	int getHour() const;
	void setHour(int h);
	int getMinute() const;
	void setMinute(int m);
	int getSecond() const;
	void setSecond(int s);
	void setTime(int h, int m, int s);
	void print() const;
};

#endif

Time.cpp

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
#include <iostream>
#include <iomanip>
#include <stdexcept>
#include "Time.h"

using namespace std;

Time::Time(int h, int m, int s) {
	setHour(h);
	setMinute(m);
	setSecond(s);
}

int Time::getHour() const {
	return hour;
}

void Time::setHour(int h) {
	if (h >= 0 && h <= 23) {
		hour = h;
	} else {
		throw invalid_argument("Invalid hour! Hour shall be 0~23.");
	}
}

int Time::getMinute() const {
	return minute;
}

void Time::setMinute(int m) {
	if (m >= 0 && m <= 59) {
		minute = m;
	} else {
		throw invalid_argument("Invalid minute! Minute shall be 0~59.");
	}
}

int Time::getSecond() const {
	return second;
}

void Time::setSecond(int s) {
	if (s >= 0 && s <= 59) {
		second = s;
	} else {
		throw invalid_argument("Invalid second! Second shall be 0~59.");
	}
}

void Time::setTime(int h, int m, int s) {
	setHour(h);
	setMinute(m);
	setSecond(s);
}

void Time::print() const {
	cout << setfill('0');
	cout << setw(2) << hour << ":" << setw(2) << minute << ":"
		<< setw(2) << second << endl;
}

TestTime.cpp

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
#include <iostream>
#include <stdexcept>

#include "Time.h"

using namespace std;

int main() {
	try {
		Time t1(25, 0, 0);
		t1.print();
	} catch (invalid_argument &ex) {
		cout << "Exception:" << ex.what() << endl;
	}

	cout << "Next statement after try-catch" << endl;
}

3. 对象引用,指针和数组中的动态内存分配(高级)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
#include <iostream>
#include "Time.h"
using namespace tsd;

int main() {
	Time t1(1, 2, 3);
	t1.print();

	Time *ptrT1 = &t1;
	(*ptrT1).print();
	ptrT1->print();

	Time &refT1 = t1;
	refT1.print();

	Time *ptrT2 = new Time(4, 5, 6);
	ptrT2->print();
	delete ptrT2;

	Time tArray1[2];
	tArray1[0].print();
	tArray1[1].print();

	Time tArray2[2] = {Time(7, 8, 9), Time(10)};
	tArray2[0].print();
	tArray2[1].print();

	Time *ptrTarray3 = new Time[2];
	ptrTarray3[0].print();
	ptrTarray3[1].print();
	delete[] ptrTarray3;

	// C++11 syntax, compile with -std=c++0x
	Time *ptrTarray4 = new Time[2] {Time(11, 12, 13), Time(14)};
	ptrTarray4->print();
	(ptrTarray4 + 1)->print();
	delete[] ptrTarray4;
}

4. 示例:复数类

下面是复数类的类图

Complex.h

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#ifndef COMPLEX_H
#define COMPLEX_H

class Complex {
private:
	double real;
	double imag;

public:
	Complex(double real = 0.0, double imag = 0.0);
	double getReal() const;
	void setReal(double real);
	double getImag() const;
	void setImag(double imag);
	void setValue(double real, double imag);
	void print() const;
	bool isReal() const;
	bool isImaginary() const;

	Complex &addInto(const Complex &another);
	Complex &addInto(double real, double imag);

	Complex addReturnNew(const Complex *another) const;
	Complex addReturnNew(double real, double imag) const;
};
#endif

Complex.cpp

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
#include <iostream>
#include "Complex.h"

using namespace std;

Complex::Complex(double real, double imag) : real(real), imag(imag) {}

double Complex::getReal() const {
	return real;
}

void Complex::setReal(double real) {
	this->real = real;
}

double Complex::getImag() const {
	return imag;
}

void Complex::setImag(double imag) {
	this->imag = imag;
}

void Complex::setValue(double real, double imag) {
	this->real = real;
	this->imag = imag;
}

void Complex::print() const {
	cout << '(' << real << ',' << imag << ')' << endl;
}

bool Complex::isReal() const {
	return (imag == 0);
}

bool Complex::isImaginary() const {
	return (imag != 0);
}

Complex &Complex::addInto(const Complex &another) {
	real += another.real;
	imag += another.imag;
	return *this;
}

Complex &Complex::addInto(double real, double imag) {
	this->real += real;
	this->imag += imag;
	return *this;
}

Complex Complex::addReturnNew(const Complex &another) const {
	return Complex(real + another.real, imag + another.imag);
}

Complex Complex::addReturnNew(double real, double imag) const {
	return Complex(this->real + real, this->imag + imag);
}

TestComplex.cpp

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
#include <iostream>
#include <iomanip>
#include "Complex.h"

using namespace std;

int main() {
	Complex c1, c2(4, 5);
	c1.print();
	c2.print();

	c1.setValue(6, 7);
	c1.print();

	c1.setReal(0);
	c1.setImag(8);
	c1.print();

	cout << boolalpha; // print true/false instead of 0/1
	cout << "Is real?" << c1.isReal() << endl;
	cout << "Is Imaginary?" << c1.isImaginary() << endl;

	c1.addInto(c2).addInto(1, 1).print();
	c1.print();

	c1.addReturnNew(c2).print();
	c1.print();
	c1.addReturnNew(1, 1).print();
	c1.print();

	return 0;
}

**注意:**不要返回一个局部变量的引用!

假设我们将addReturnNew函数修改为下面的样子:

1
2
3
Complex &Complex::addReturnNew(const Complex &another) const {
	return Complex(real + another.real, imag + another.imag);
}

那么在编译的时候会报以下错误:“invalid initialization of non-const reference of type ‘Complex&’ from an rvalue of type ‘Complex’"。 这是因为临时变量是在函数体内构造的,不能作用于函数体外,从而外部调用的引用就是非法的。

5. 示例:“Date"类

Date.h

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
#ifndef DATE_H
#define DATE_H

#include <string>

using namespace std;

class Date {
private:
	int year;
	int month;
	int day;
	const static string STR_MONTHS[];
	const static string STR_DAYS[];
	const static int DAYS_IN_MONTHS[];
	const static int YRER_MIN = 1753;
	const static int YRER_MAX = 9999;

public:
	static bool isLeapYear(int y);
	static bool isValidDate(int y, int m, int d);
	static int getDayOfWeek(int y, int m, int d);

	Date(int y, int m, int d);
	void setDate(int y, int m, int d);
	int getYear() const;
	int getMonth() const;
	int getDay() const;
	void setYear(int y);
	void setMonth(int m);
	void setDay(int d);
	void print() const;

	Date &nextDay();
	Date &previousDay();
	Date &nextMonth();
	Date &previousMonth();
	Date &nextYear();
	Date &previousYear();
};
#endif

Date.cpp

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
/* Implementation for Date Class (Date.cpp) */
#include <iostream>
#include <stdexcept>
#include "Date.h"
using namespace std;

// Initialize static non-integer variable (must be done outside the class declaration)
const string Date::STR_MONTHS[] = {"Jan", "Feb", "Mar", "Apr", "May", "Jun",
                                   "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"};

const int Date::DAYS_IN_MONTHS[] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};

const string Date::STR_DAYS[] = {"Sunday", "Monday", "Tuesday", "Wednesday",
                                 "Thursday", "Friday", "Saturday"};

// A static function that returns true if the given year is a leap year
bool Date::isLeapYear(int year) {
   return ((year % 4 == 0 && year % 100 != 0) || (year % 400 == 0));
}

// A static function that returns true if the given y, m, d constitutes a valid date
bool Date::isValidDate(int y, int m, int d) {
   if (y >= YEAR_MIN && y <= YEAR_MAX && m >= 1 && m <= 12) {
      int lastDayOfMonth = DAYS_IN_MONTHS[m-1];
      if (m == 2 && isLeapYear(y)) {
         lastDayOfMonth = 29;
      }
      return (d >= 1 && d <= lastDayOfMonth);
   } else {
      return false;
   }
}

// A static function that returns the day of the week (0:Sun, 6:Sat) for the given date
// Wiki "Determination of the day of the week" for the algorithm
int Date::getDayOfWeek(int y, int m, int d) {
   int centuryTable[] = {4, 2, 0, 6, 4, 2, 0, 6}; // 17xx, 18xx, ...
   int MonthTable[] = {0, 3, 3, 6, 1, 4, 6, 2, 5, 0, 3, 5};
   int MonthLeapYearTable[] = {6, 2, 3, 6, 1, 4, 6, 2, 5, 0, 3, 5};

   int century = y / 100;
   int twoDigitYear = y % 100;
   int centuryTableIndex = (century - 17) % 8;
   // Date before 17xx are not valid, but needed to prevent negative index
   if (centuryTableIndex < 0) {
      centuryTableIndex += 8;
   }
   int sum = centuryTable[centuryTableIndex] + twoDigitYear + twoDigitYear / 4;
   if (isLeapYear(y)) {
      sum += MonthLeapYearTable[m-1];
   } else {
      sum += MonthTable[m-1];
   }
   sum += d;
   return sum % 7;
}

// Constructor
Date::Date(int y, int m, int d) {
   setDate(y, m, d);
}

// With Input validation
void Date::setDate(int y, int m, int d) {
   setYear(y);
   setMonth(m);
   setDay(d); // need to set the day after year and month
}

int Date::getYear() const {
   return year;
}

void Date::setYear(int y) {
   if (y >= YEAR_MIN && y <= YEAR_MAX) {
      year = y;
   } else {
      throw invalid_argument("Error: Invalid year (1753-9999)!");
   }
}

int Date::getMonth() const {
   return month;
}

void Date::setMonth(int m) {
   if (m >= 1 && m <= 12) {
      month = m;
   } else {
      throw invalid_argument("Error: Invalid month (1-12)!");
   }
}

int Date::getDay() const {
   return day;
}

// Assuming that the year and month are already set
void Date::setDay(int d) {
   int lastDayOfMonth = DAYS_IN_MONTHS[month-1];
   if (month == 2 && isLeapYear(year)) {
      lastDayOfMonth = 29;
   }
   if (d >= 1 && d <= lastDayOfMonth) {
      day = d;
   } else {
      throw invalid_argument("Error: Invalid day (1-28|29|30|31)!");
   }
}

// Print this instance in the format "xxxday, d mmm yyyy".
void Date::print() const {
   cout << STR_DAYS[getDayOfWeek(year, month, day)] << ", "
        << day << " " << STR_MONTHS[month-1] << " " << year << endl;
}

// Increment this instance to the next day and return this instance by reference
Date& Date::nextDay() {
   int lastDayOfMonth = DAYS_IN_MONTHS[month-1];
   if (month == 2 && isLeapYear(year)) {
      lastDayOfMonth = 29;
   }

   // check day against the end of month
   if (++day > lastDayOfMonth) {
      day = 1;
      if (++month > 12) {
         month = 1;
         if (++year > YEAR_MAX) {
            throw out_of_range("Error: Next day is out of range!");
         }
      }
   }
   return *this;
}

// Decrement this instance to the previous day and return this instance by reference
Date& Date::previousDay() {
   int lastDayOfMonth = DAYS_IN_MONTHS[month-1];
   if (month == 2 && isLeapYear(year)) {
      lastDayOfMonth = 29;
   }

   // check day against the end of month
   if (--day < 1) {
      day = lastDayOfMonth;
      if (--month < 1) {
         month = 12;
         if (--year < YEAR_MIN) {
            throw out_of_range("Error: Previous day is out of range!");
         }
      }
   }
   return *this;
}

// Increment this instance to the next month and return this instance by reference
Date& Date::nextMonth() {
   if (++month > 12) {
      month = 1;
      if (++year > YEAR_MAX) {
         throw out_of_range("Error: Next month is out of range!");
      }
   }
   // may need to adjust the last day of the month
   int lastDayOfMonth = DAYS_IN_MONTHS[month-1];
   if (month == 2 && isLeapYear(year)) {
      lastDayOfMonth = 29;
   }
   if (day > lastDayOfMonth) {
      day = lastDayOfMonth;
   }
   return *this;
}

// Decrement this instance to the previous month and return this instance by reference
Date& Date::previousMonth() {
   if (--month < 1) {
      month = 12;
      if (--year < YEAR_MIN) {
         throw out_of_range("Error: Previous month is out of range!");
      }
   }
   // may need to adjust the last day of the month
   int lastDayOfMonth = DAYS_IN_MONTHS[month-1];
   if (month == 2 && isLeapYear(year)) {
      lastDayOfMonth = 29;
   }
   if (day > lastDayOfMonth) {
      day = lastDayOfMonth;
   }
   return *this;
}

// Increment this instance to the next year and return this instance by reference
Date& Date::nextYear() {
   if (++year > YEAR_MAX) {
      throw out_of_range("Error: Next year is out of range!");
   }
   // may need to adjust the last day of the month for leap year (29 Feb)
   //  to non-leap year (28 Feb)
   if (month == 2 && day == 29 && !isLeapYear(year)) {
      day = 28;
   }
   return *this;
}

// Decrement this instance to the previous year and return this instance by reference
Date& Date::previousYear() {
   if (--year < YEAR_MIN) {
      throw out_of_range("Error: Previous year is out of range!");
   }
   // may need to adjust the last day of the month for leap year (29 Feb)
   //  to non-leap year (28 Feb)
   if (month == 2 && day == 29 && !isLeapYear(year)) {
      day = 28;
   }
   return *this;
}

TestDate.cpp

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
/* Test Driver Program (TestDate.cpp) */
#include <iostream>
#include <stdexcept>
#include "Date.h"

int main() {
   Date d1(2012, 1, 1);
   d1.print();  // Sunday, 1 Jan 2012
   d1.nextDay().print();  // Monday, 2 Jan 2012
   d1.print();  // Monday, 2 Jan 2012

   d1.setDate(2012, 1, 31);
   d1.print();  // Tuesday, 31 Jan 2012
   d1.nextDay().print();  // Wednesday, 1 Feb 2012

   d1.setDate(2012, 2, 28);
   d1.print();  // Tuesday, 28 Feb 2012
   d1.nextDay().print();  // Wednesday, 29 Feb 2012

   d1.setDate(2012, 12, 31);
   d1.print();  // Monday, 31 Dec 2012
   d1.nextDay().print();  // Tuesday, 1 Jan 2013

//   Date d2(2011, 2, 29);  // abrupt termination!
//   d2.print();

   try {  // graceful handling of exception
      Date d3(2011, 2, 29);
      d3.print();
   } catch (invalid_argument &ex) {
      cout << ex.what() << endl;  // Error: Invalid day (1-28|29|30|31)!
   }
   cout << "Next Statement after try-catch" << endl;

   try {  // graceful handling of exception
      Date d4(9999, 12, 30);
      d4.nextDay().print(); // Friday, 31 Dec 9999
      d4.nextDay();
      d4.print();
   } catch (out_of_range &ex) {
      cout << ex.what() << endl;  // Error: Next day is outside the valid range!
   }

   Date d5(2012, 1, 1);
   d5.previousDay().print();  // Saturday, 31 Dec 2011

   Date d6(2012, 3, 31);
   d6.nextMonth().print();  // Monday, 30 Apr 2012

   Date d7(2012, 3, 31);
   d7.previousMonth().print();  // Wednesday, 29 Feb 2012

   Date d8(2012, 2, 29);
   d8.nextYear().print(); // Thursday, 28 Feb 2013

   Date d9(2012, 2, 29);
   d9.previousYear().print();  // Monday, 28 Feb 2011
}