Description
1 Objectives
To experience creating an abstract data type (ADT)
To implement an ADT in C++, using the operator overloading facility of the C++ language To learn how to turn objects of a class into “function objects”
To learn how to convert objects of a class to objects of unrelated types (e.g., Foo to bool)
2 Background
A data type represents a set of data values sharing common properties. An abstract data type (ADT) specifies a set of operations on a data type, independent of how the data values are actually represented or how the operations are implemented.
Classic ADTs representing mathematical entities such as rational number and complex number ADTs support many arithmetic, relational and other operations, making them ideal data types for operator overloading.
Since there is no shortage of code for C++ classes representing rational and complex numbers, assignments designed to provide practice with operator overloading tend to get a bit creative with their choice of data types; ideally, a data type that is not as ubiquitous as rational and complex number ADTs, but one that lends itself to operator overloading just as good.
3 Mat2x2 ADT
Mat2x2 is an ADT that encapsulates a “2×2 matrix”, a simple mathematical entity of the form
, where the numbers a, b, c, and d represent the “value” of a 2 × 2 matrix data type.
For example, the numbers 4, 8, 7, and 3 together can represent the value of the 2 × 2 matrix
.
3.1 Notation
a real number, a multiplicative scalar a real number, an additive scalar
3.2 Operations
Scalar Multiplication
Scalar Addition Scalar Subtraction
addition
subtraction
multiplication
determinant
inverse A−1 = inv(A) = inv , det(A) 6= 0
division A/B = AB−1, det B 6= 0
Norm p
||A|| = norm(A) = a2 + b2 + c2 + d2
Relational Equality .
The symbol denotes a tolerance, a small positive amount the value ||A − B|| can change and still be acceptable that A is equal to B.
Relational Inequality A < B if ¬(A = B) and ||A|| < ||B||
where ¬ denotes the negation operator.
Recall that the definitions of the operators < and = on objects X and Y are sufficient for deriving the definitions of the other four relational
operators >, ≥, 6=, and ≤:
X > Y ≡ Y < X
X ≥ Y ≡ ¬(X < Y )
X 6= Y ≡ ¬(X = Y )
X ≤ Y ≡ X < Y or X = Y
4 Your Task
Implement the Mat2x2 ADT above using a C++ class, called Mat2x2, that uses one of the representations listed in section 5 to store the underlying data as private member(s).
The public interface of your Mat2x2 class must include the following member functions:
1. Constructors:
explicit Mat2x2(double = 0, double = 0, double = 0, double = 0);
Mat2x2(const array<double, 4> &); // using std::array;
Mat2x2(const array<array<double, 2>, 2>&); // using std::array;
Mat2x2(const initializer_list<double>); // using std::initializer_list;
2. Defaulted copy/move constructors, copy/move assignment operators, and destructor.
3. norm(), returns the norm of the calling object
4. inverse(), returns the inverse of the calling object
5. det(), returns the determinant of the calling object
4.1 Member Operator Overload Functions
6. Compound assignment operator overloads.
Mat2x2 op Mat2x2 x+=y, x-=y, x*=y, x/=y
Mat2x2 op double x+=k, x-=k, x*=k, x/=k
7. Unary operators ++x, x++, +x, –x, x–, -x, where x is a Mat2x2 object.
8. An overloaded XOR operator^ such that x^k returns the Mat2x2 object resulting from raising x to the power k (an integer). It does not modify x.
9. Subscript operators [ ], both const and non-const overloads. If subscript is invalid, must throw: invalid argument(“index out of bounds”)
These operator overloads provide direct access to the underlying data members, effectively eliminating the need for friend functions.
10. operator bool() const Returns whether or not the invoking object has inverse. For example, if x is a Mat2x2 object, it returns true if , and returns false otherwise.
4.2 Function objects
Overloading the function call operator, these overloads effectively turn Mat2x2 objects into functions; hence the name “function object.”
11. double operator()()const Returns the norm of the invoking Mat2x2 object. For example, if x is a Mat2x2 object, then x() returns x.norm().
12. double& operator()(size t r, size t c)
Returns an lvalue reference to the entry at row r and column c.
Since it would be more intuitive for humans to start row and column indexing at 1, this function must ensure that the values of r and c are 1-based.
For example, if x stores the matrix , then x(i, j) should return a reference to xij, i = 1,2, j = 1,2.
If r or c is invalid, then this function must throw an exception as shown below:
if (r < 1 || r > 2) throw invalid_argument(“row index out of bounds”); if (c < 1 || c > 2) throw invalid_argument(“column index out of bounds”);
1
2
4.3 static Members
static double tolerance; // initial value = 1.0E-6 static void setTolerance(double tol); static double getTolerance();
1
2
3
See Relational Equality in section 3.2 where “tolerance” is defined.
4.4 Non-Member Operator Overload Functions
1. Overloaded extraction operator >> for reading Mat2x2 values
2. Overloaded insertion operator << for writing Mat2x2 values
3. Basic arithmetic operators. None modifies it operands. Not all can be implemented as members (which ones?). For consistency, all are typically implemented as free functions.
Mat2x2 op Mat2x2 x+y, x-y, x*y, x/y
Mat2x2 op double x+k, x-k, x*k, x/k
double op Mat2x2 k+y, k-y, k*y, k/y
4. Relational operators. None modifies it operands. For consistency, all are typically implemented as free functions.
Mat2x2 op Mat2x2 x<y, x<=y, x>y, x>=y, x==y, x!=y
5 Alternative Representations for a 2×2 Matrix
Applying the OOP’s principle of information hiding, an implementation of an ADT can optimize both representation of the data type and implementation of the operations without affecting the client code.
However, since an ADT does not depend on the representation of the data type it specifies, the first order of business for any implementation of an ADT is to define concrete representation of the data type so that the operations on the type can be implemented.
The examples below show three common representations of a 2 × 2 matrix :
Eaxmple 1: Viewing the 2 × 2 matrix as four individual elements a, b, c, and d:
double a; // top-left double b; // top-right double c; // bottom-left double d; // bottom-right
1
2
3
4
For the benefit of human readers of the code, such representation must explicitly document the correspondence between the matrix elements and the variable a, b, c, and d.
Eaxmple 2: Viewing the 2 × 2 matrix as a sequence (y0,y1,y2,y3)
For storing and managing any sequence of const size, modern C++ provides a smart array class template in the <array> header file:
std::array <double, 4> y; // an array of 4 doubles
1
std::array provides a rich set of useful methods as well as supporting the familiar array notation y[0], y[1], y[2], and y[3].
This representation too must document the correspondence between the matrix elements and the variable y[0], y[1], y[2], and y[3].
Eaxmple 3: Viewing the 2 × 2 matrix as , or as , as an
array of two rows, each in turn an array of two elements.
std::array< std::array <double, 2>, 2> x;
// x is an array of size 2;
// each element of x is an array of 2 doubles;
1
2
3
Note that x is a std::array of std::arrays, representing a two-dimensional table or matrix of doubles.
Overloading the subscript operator [], std::array provides the familiar array notation. Specifically, the two elements (the rows) x[0] and x[1] of the 2D-array x are each of type std::array<double, 2>, and as such, x[0]’s two elements on the first row are x[0][0], and x[0][1], and x[1]’s two elements on the second row are x[1][0], and x[1][1].
5.1 What About Multi-Dimensional Raw Arrays?
C++ does not provide a special multi-dimensional raw array type. Instead, C++ provides only one-dimensional array type, but it allows the array elements themselves to be “one-dimensional arrays”.
For example, denoting a two-dimensional 5 × 3 array of 5 rows and 3 columns, the declaration double A[5][3] means that A is a one-dimensional array of 5 elements, each of which, in turn, is an array of 3 doubles. Specifically,
A[0] points to an array of 3 elements A[0][0], A[0][1], A[0][2] on row 1,
A[1] points to an array of 3 elements A[1][0], A[1][1], A[1][2] on row 2, and in general, A[r] points to an array of 3 elements A[r][0], A[r][1], A[r][2] on row r+1.
Similarly, the declaration double B[3][4][5] declares B as an array of size 3 whose elements are each arrays of size 4 whose elements are each arrays of 5 doubles.
Question 1
Write a declaration for a function named process2D that is to receive and process a raw array of 7 raw arrays, each of 5 doubles.
Question 2
Write a declaration for a function named process3D that is to receive and process a three-dimensional 5 × 3 × 7 array of integers.
5.2 Prefer std::arrays to Raw Arrays
Given std::array<T,n> container, a smart class template modeling an array of fixed size n and elements of type T, there is really no good reason to use raw arrays in C++.
The justification for this is that a std::array<T,n> object stores and can tell the size of the array, provides bounds checking facilities, can be passed as an argument to a function without decaying to a pointer, can be used by any algorithm designed to work with C++ container classes, provides the familiar raw array notation (by overloading the [] operator), and more.
6 Deliverables
1. Header files: Mat2x2.h
2. Implementation files: Mat2x2.cpp, Mat2x2 test driver.cpp
3. A README.txt text file (as described in the course outline).
7 Grading scheme
8 Sample Test Driver
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
#include <iostream>
#include <iomanip>
#include <string>
#include <cassert> #include “Mat2x2.h” using std::cout; using std::cin; using std::endl;
/*
Tests class Mat2x2 . Specifically, tests constructors, compound assignment operator overloads, basic arithmetic operator overloads, unary +, unary -, pre/post-increment/decrement, subscripts, function objects, input/output operators, and relational operators.
@return 0 to indicate success.
*/
int main()
{ const Mat2x2 ZERO;
// must not compile, because zero is const
//ZERO[1] = 0;
//ZERO[2] = 0;
//ZERO[3] = 0; //ZERO[4] = 0; const Mat2x2 IDENTITY(1, 0, 0, 1);
// ctor that takes an std::initializer_list<double>
Mat2x2 a = { 11, 22, 33, 44, 55, 66.5 }; // notice intentional too many cout << “a = ” << a << endl; assert(a == Mat2x2(11, 22, 33, 44));
Mat2x2 b{ 111,222.7,333 }; cout << “b = ” << b << endl;
assert(b == Mat2x2(111, 222.7, 333, 0));
Mat2x2 c{ 1234 }; cout << “c = ” << c << endl; assert(c == Mat2x2(1234, 0, 0, 0));
// a conversion constructor
Mat2x2 d(1234); // int -> Mat2x2 // [1234, 0, 0, 0] cout << “d = ” << d << endl; assert(d == Mat2x2(1234, 0, 0, 0));
29 initializers
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
Mat2x2 e; // default ctor
cout << “e = ” << e << endl; // cout << Mat2x2 assert(e == ZERO); // Mat2x2 == Mat2x2
Mat2x2 f(2); // normal ctor with 1 arg
cout << “f = ” << f << endl; assert(f == Mat2x2(2, 0, 0, 0));
Mat2x2 g(2, 3); // normal ctor with 2 args
cout << “g = ” << g << endl; assert(g == Mat2x2(2, 3, 0, 0));
Mat2x2 h(2, 3, 8); // normal ctor with 3 args cout << “h = ” << h << endl; assert(h == Mat2x2(2, 3, 8, 0));
Mat2x2 m1(2.5, 3.6, 8.7, 5.8); // normal ctor with 4 args Mat2x2 m1_inverse = m1.inverse(); // inverse, copy ctor
Mat2x2 m1_inverse_times_m1 = m1_inverse * m1; // Mat2x2 * Mat2x2 assert(m1_inverse_times_m1 == IDENTITY); // invariant, must hold
Mat2x2 m1_times_m1_inverse = m1 * m1_inverse; assert(m1_times_m1_inverse == IDENTITY); // invariant, must hold
assert(+m1 == -(-m1)); // +Mat2x2 , -Mat2x2
Mat2x2 t1 = m1;
++m1; // ++Mat2x2 assert(m1 == t1 + 1);
–m1; // –Mat2x2
assert(m1 == t1);
Mat2x2 m1_post_inc = m1++; // Mat2x2 ++ assert(m1_post_inc == t1); assert(m1 == t1 + 1);
Mat2x2 m1_post_dec = m1–; // Mat2x2 —
assert(m1_post_dec == t1 + 1); assert(m1 == t1);
cout << ” “;
h += Mat2x2(0, 0, 0, 5); // Mat2x2 += Mat2x2 Mat2x2 m2 = h + 1.0; // Mat2x2 = Mat2x2 + int assert(m2 == Mat2x2(3, 4, 9, 6)); cout << “m2 = ” << m2 << endl;
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
m2 = 1 + h; // Mat2x2 = double + Mat2x2;
assert(m2 == Mat2x2(3, 4, 9, 6));
Mat2x2 m3 = m2 – 1.0; // Mat2x2 = Mat2x2 – double assert(m3 == h); cout << “m3 = ” << m3 << endl;
Mat2x2 m4 = 1.0 – m3; // Mat2x2 = double – Mat2x2 cout << “m4 = ” << m4 << endl; assert(m4 == Mat2x2(-1, -2, -7, -4));
Mat2x2 m5 = m4 * 2.0; // Mat2x2 = Mat2x2 * double cout << “m5 = ” << m5 << endl; assert(m5 == Mat2x2(-2, -4, -14, -8));
Mat2x2 m6 = -1 * m5; // Mat2x2 = double * Mat2x2 cout << “m6 = ” << m6 << endl; assert(m6 == Mat2x2(2, 4, 14, 8));
assert(m6 / -1.0 == m5); // Mat2x2 = Mat2x2 / double assert(1 / m6 == 1 * m6.inverse()); // double / Mat2x2, inverse assert(-1.0 * m4 * 2.0 == m6); // double * Mat2x2 * double
Mat2x2 m7 = m1++; //Mat2x2 ++
cout << “m1 = ” << m1 << endl; cout << “m7 = ” << m7 << endl;
assert(m7 == m1 – Mat2x2(1, 1, 1, 1)); // Mat2x2 – Mat2x2
Mat2x2 m8 = –m1; // –Mat2x2
cout << “m1 = ” << m1 << endl; cout << “m8 = ” << m8 << endl; assert(m8 == m1);
m8–; // Mat2x2–
cout << “m8 = ” << m8 << endl;
assert(m1 == 1 + m8); // double + Mat2x2 assert(m1 – 1 == m8); assert(-m1 + 1 == -m8); assert(2 * m1 == m8 + m1 + 1); assert(m1 * m1 == m1 * (1 + m8));
Mat2x2 m9(123, 6, 6, 4567.89); cout << “m9 = ” << m9 << endl;
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
// subscripts (non-const) m9[0] = 3; m9[1] = 1; m9[2] = 7; m9[3] = 4; cout << “m9 = ” << m9 << endl; assert(m9 == Mat2x2(3, 1, 7, 4));
// relational operators
double smallTol = Mat2x2::getTolerance() / 10.0;
Mat2x2 m9Neighbor(3 – smallTol, 1 + smallTol, 7 – smallTol, 4 + smallTol); assert(m9 == m9Neighbor);
double tol = Mat2x2::getTolerance(); assert(m9 == m9); assert(m9 == (m9 + 0.1 * tol)); assert(m9 == (m9 + 0.2 * tol)); assert(m9 == (m9 + 0.3 * tol)); assert(m9 == (m9 + 0.4 * tol)); assert(m9 == (m9 + 0.5 * tol)); assert(m9 != (m9 + 0.6 * tol)); assert(m9 != (m9 + tol));
assert(m9 < (m9 + 0.001)); assert(m9 <= (m9 + 0.001)); assert((m9 + 0.001) <= (m9 + 0.001));
assert((m9 + 0.001) > m9); assert((m9 + 0.001) >= m9); assert((m9 + 0.001) >= (m9 + 0.001));
// compound operators
m9 += m9; cout << “m9 = ” << m9 << endl; assert(m9 == 2 * Mat2x2(3, 1, 7, 4));
Mat2x2 m10; m10 += (m9 / 2); cout << “m10 = ” << m10 << endl; assert(m10 == Mat2x2(3, 1, 7, 4));
m10 *= 2; cout << “m10 = ” << m10 << endl;
assert(m10 == m9);
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
m10 /= 2; cout << “m10 = ” << m10 << endl; assert(m10 == m9 / 2);
m10 += 10; cout << “m10 = ” << m10 << endl; assert(m10 == (m9 + 20) / 2);
m10 -= 10; cout << “m10 = ” << m10 << endl; assert(m10 == 0.5 * m9);
// ctor that takes a std::array<double, 4> std::array<double, 4> arr = { 2, 0, 0, 2 }; Mat2x2 m11{ arr }; cout << “m11 = ” << m11 << endl;
// ctor that takes a std::array< std::array <double, 2>, 2> std::array <double, 2> row1{ 2, 0 }; std::array <double, 2> row2{ 0, 2 };
std::array< std::array <double, 2>, 2> mat{ row1, row2 }; Mat2x2 m12{ mat }; cout << “m12 = ” << m12 << endl; assert(m12 == arr);
// multiplications
Mat2x2 i{ 1,2,3,4 }; Mat2x2 j{ 2,0,1,2 }; assert((i * j) == Mat2x2(4, 4, 10, 8)); assert((j * i) == Mat2x2(2, 4, 7, 10));
// inverse operation Mat2x2 k{ 4,7,2,6 }; if (k) // this is how if(cin) works!
{ cout << “k is invertible “; Mat2x2 m4aInv1 = k.inverse();
Mat2x2 m4aInv2 = k ^ (-1); // operator ^ overload assert(m4aInv1 == m4aInv2);
} else { cout << “k is NOT invertible “;
}
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
220
221
222
223
224
225
Mat2x2 p{ k * k * k * k * k }; Mat2x2 q{ k ^ (5) }; assert(p == q);
Mat2x2 x = Mat2x2{ 4,7,2,6 }.inverse();
Mat2x2 y{ x * x * x * x * x };
Mat2x2 z{ q ^ (-1) }; assert(y == z);
// test function objects (that is, function call operators) assert(k() == k.norm());
cout << “k = ” << k << endl; k(1, 1) = 10; k(1, 2) = 20; k(2, 1) = 30; k(2, 2) = 40; cout << “k = ” << k << endl;
try {
k(3, 1) = 40;
} catch (std::invalid_argument& ia)
{ cout << “Problem: ” << ia.what() << endl;
}
try {
k(1, 3) = 40;
}
catch (std::invalid_argument& ia)
{ cout << “Problem: ” << ia.what() << endl;
}
//testing operator>>
cout << “Please enter the numbers 1, 2, 3, 4.5, in that order “; Mat2x2 input; cin >> input; cout << “input = ” << input << endl;
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
} Mat2x2 diff = input – Mat2x2(1, 2, 3, 4.5); assert(diff.norm() <= tol); // absolute value assert(diff() <= tol); // function object
cout << “Test completed successfully!” << endl; return 0;
269
270
271
272
273
274
275
276
277
8.1 Output
a = [11.00, 22.00, 33.00, 44.00] b = [111.00, 222.70, 333.00, 0.00] c = [1234.00, 0.00, 0.00, 0.00] d = [1234.00, 0.00, 0.00, 0.00] e = [0.00, 0.00, 0.00, 0.00] f = [2.00, 0.00, 0.00, 0.00] g = [2.00, 3.00, 0.00, 0.00] h = [2.00, 3.00, 8.00, 0.00]
m2 = [3.00, 4.00, 9.00, 6.00] m3 = [2.00, 3.00, 8.00, 5.00] m4 = [-1.00, -2.00, -7.00, -4.00] m5 = [-2.00, -4.00, -14.00, -8.00] m6 = [2.00, 4.00, 14.00, 8.00] m1 = [3.50, 4.60, 9.70, 6.80] m7 = [2.50, 3.60, 8.70, 5.80] m1 = [2.50, 3.60, 8.70, 5.80] m8 = [2.50, 3.60, 8.70, 5.80] m8 = [1.50, 2.60, 7.70, 4.80] m9 = [123.00, 6.00, 6.00, 4567.89] m9 = [3.00, 1.00, 7.00, 4.00] m9 = [6.00, 2.00, 14.00, 8.00] m10 = [3.00, 1.00, 7.00, 4.00] m10 = [6.00, 2.00, 14.00, 8.00] m10 = [3.00, 1.00, 7.00, 4.00] m10 = [13.00, 11.00, 17.00, 14.00] m10 = [3.00, 1.00, 7.00, 4.00] m11 = [2.00, 0.00, 0.00, 2.00]
m12 = [2.00, 0.00, 0.00, 2.00] k is NOT invertible k = [4.00, 7.00, 2.00, 6.00] k = [10.00, 20.00, 30.00, 40.00]
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
Problem: row index out of bounds
Problem: column index out of bounds
Please enter the numbers 1, 2, 3, 4.5, in that order
1 2 3 4.5
input = [1.00, 2.00, 3.00, 4.50] Test completed successfully!
34
35
36
37
38
39
40
Reviews
There are no reviews yet.