-
Notifications
You must be signed in to change notification settings - Fork 0
/
ourvector.h
527 lines (453 loc) · 11.6 KB
/
ourvector.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
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
220
221
222
223
224
225
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
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
/*ourvector.h*/
//
// ourvector class --- a replacement for std::vector using the same
// dynamic array approach. Starts with initial capacity of 4, and
// doubles in size as needed. Usage statistics are also collected
// and output to stderr when the last vector is destroyed.
//
// Prof. Joe Hummel
// U. of Illinois, Chicago
// CS 251: Spring 2020
// Project #01
//
#pragma once
#include <iostream>
#include <exception>
#include <stdexcept>
#include <typeinfo>
using namespace std;
//
// ourvector<T>
//
template<typename T>
class ourvector
{
private:
//
// A vector is a dynamically-allocated array of elements, of type T.
// Since it's a low-level C-style array, we need to keep track of how
// many elements we have stored in the array (Size), and the total # of
// available locations (Capacity). When it's full, we double in size.
//
T* A; // pointer to dynamically-allocated C-style array
int Size; // # of elements in the array
int Capacity; // total # of locations available
//
// Global stats on vector usage:
//
static int InUse; // # of vector objects currently in use
static int Vectors; // # of vector objects created
static int Inserts; // # of elements inserted into the vector
static int Accesses; // # of times elements were accessed (read or written)
//
// Iterators are objects that allow iteration in a structure-neutral way.
// For this vector class, we keep track of the vector object and the current
// index into the vector. As we iterate, we advance the index.
//
class iterator
{
private:
ourvector* V; // which vector are we iterating through?
int CurIndex; // which element are we currently on?
public:
// default constructor: denotes first element
iterator(ourvector* underlyingVector)
{
V = underlyingVector;
CurIndex = 0;
}
// constructor: denotes ith element
iterator(ourvector* underlyingVector, int i)
{
V = underlyingVector;
CurIndex = i;
}
// advance:
iterator& operator++()
{
CurIndex++;
return *this;
}
// access current element:
T& operator*()
{
V->Accesses++;
return V->A[CurIndex];
}
// compare iterators, e.g. to stop iterating:
bool operator!=(const iterator& rhs)
{
// is this iterator != to rhs iterator?
if (V != rhs.V) // different vectors?
return true;
if (CurIndex != rhs.CurIndex) // different index?
return true;
// same underlying vector and index, so iterators denote same element:
return false;
}
};
public:
//
// default constructor:
//
// Called automatically by C++ to create an empty vector with an
// initial capacity of 4.
//
ourvector()
{
A = new T[4]; // we start with a capacity of 4:
Size = 0;
Capacity = 4;
InUse++;
Vectors++;
}
//
// copy constructor:
//
// Called automatically by C++ to create a vector that contains a copy
// of an existing vector. Example: this occurs when passing ourvector as
// a parameter by value.
//
ourvector(const ourvector& other)
{
//
// allocate this vector with same capacity as the other:
//
this->A = new T[other.Capacity];
this->Size = other.Size;
this->Capacity = other.Capacity;
InUse++;
Vectors++;
//
// now make a copy the elements from the other vector into this one:
//
for (int i = 0; i < other.Size; ++i)
{
this->A[i] = other.A[i];
Accesses++;
Inserts++;
}
//
// done, this vector is now a copy of the other vector:
//
}
//
// move constructor:
//
// Called automatically by C++ to create a vector by *moving* the contents
// of the other vector into this vector; when we're done, the other vector
// no longer exists. Example: this is called when a function returns a
// vector as a result ---so the result is moved instead of copied.
//
ourvector(ourvector&& other)
{
//
// NOTE: we can "move" the elements over by simply pointing to the
// array in the other vector:
//
this->A = other.A;
//
// we have the same size and capacity as the other vector:
//
this->Size = other.Size;
this->Capacity = other.Capacity;
//
// clear the data members of the other vector since we now own the data:
//
other.A = nullptr;
other.Size = 0;
other.Capacity = 0;
//
// done, this vector now contains the data from the other vector, and
// the other vector is now completely empty:
//
}
//
// destructor:
//
// Called automatically by C++ to free the memory associated by the vector.
//
virtual ~ourvector()
{
if (A != nullptr) // then we have something to free:
{
delete[] A; // since we new'd an array, we delete[] the same array
InUse--; // one fewer vector in use:
}
if (InUse == 0) // last vector was just deleted, so output stats:
{
_stats();
}
}
//
// clear:
//
// Empties the vector of all elements, reseting size to 0 and capacity
// to 4.
//
void clear()
{
//
// to make sure elements are properly freed, let's free the array and
// reallocate:
//
delete[] A; // since we new'd an array, we delete[] an array
A = new T[4]; // reset to a new, empty vector with capacity of 4:
Size = 0;
Capacity = 4;
}
//
// copy operator=
//
// Called when you assign one vector into another, i.e. this = other;
//
ourvector& operator=(const ourvector& other)
{
//
// special case: are we assigning to ourself?
//
if (this == &other)
return *this;
//
// unlike a copy constructor, *this* vector exists and so we first
// have to free the memory associated with this vector:
//
delete[] A; // since we new'd an array, we delete[] an array
//
// allocate this array with same capacity as the other:
//
this->A = new T[other.Capacity];
this->Size = other.Size;
this->Capacity = other.Capacity;
//
// now make a copy the elements from the other vector into this one:
//
for (int i = 0; i < other.Size; ++i)
{
this->A[i] = other.A[i];
Accesses++;
Inserts++;
}
//
// done, this vector is now a copy of the other vector:
//
return *this; // we have to return a vector based on how = works:
}
//
// move operator=
//
// Called when you move one vector into another, i.e.
// this = std::move(other);
//
ourvector& operator=(ourvector&& other)
{
//
// special case: are we moving to ourself?
//
if (this == &other)
return *this;
//
// unlike a move constructor, *this* vector exists and so we first
// have to free the memory associated with this vector:
//
delete[] A; // since we new'd an array, we delete[] an array
//
// now move the *other* data to this vector:
//
this->A = other.A;
this->Size = other.Size;
this->Capacity = other.Capacity;
//
// clear the data members of the other vector since we now own the data:
//
other.A = nullptr;
other.Size = 0;
other.Capacity = 0;
//
// done, this vector now owns the data from the *other* vector:
//
return *this; // we have to return a vector based on how = works:
}
//
// size:
//
// Returns the # of elements currently in the array.
//
int size() const
{
return Size;
}
//
// capacity:
//
// Returns the total # of available locations in the vector before it
// will need to be resized; resizing happens automatically when full.
//
int capacity() const
{
return Capacity;
}
//
// push_back:
//
// Adds e to the end of the vector; if the vector is full, it's capacity
// is doubled and then e is added.
//
void push_back(T e)
{
//
// is the vector full? if so, we have to double in size before we can add:
//
if (Size == Capacity)
{
int newCapacity = Capacity * 2;
T* newA = new T[newCapacity];
// copy the elements from A to the new array:
for (int i = 0; i < Size; ++i)
{
newA[i] = A[i];
}
// now delete A, and update private data members to point to new array
// and reflect new capacity:
delete[] A;
A = newA;
Capacity = newCapacity;
}
//
// there's room, add to the end of the array:
//
A[Size] = e;
Size++;
Inserts++;
}
//
// at(i):
//
// Returns a reference to the ith elements in the vector, which allows
// you to read (access) or write (modify) this element. If i is out of
// bounds, an exception is thrown.
//
T& at(int i)
{
if (i < 0 || i >= Size)
{
throw out_of_range("ourvector: i out of bounds");
}
Accesses++;
return A[i];
}
//
// [i]:
//
// Returns a reference to the ith elements in the vector, which allows
// you to read (access) or write (modify) this element. If i is out of
// bounds, an exception is thrown. This is very convenient, since it
// allows you to treat a vector like a built-in array:
//
// cout << V[i] << endl;
//
T& operator[](int i)
{
return at(i);
}
//
// front:
//
// Returns a reference to the first elements in the vector, which allows
// you to read (access) or write (modify) this element. If the vector is
// empty, an exception is thrown.
//
T& front()
{
if (Size == 0)
{
throw runtime_error("ourvector: empty");
}
Accesses++;
return A[0];
}
//
// back:
//
// Returns a reference to the last elements in the vector, which allows
// you to read (access) or write (modify) this element. If the vector is
// empty, an exception is thrown.
//
T& back()
{
if (Size == 0)
{
throw runtime_error("ourvector: empty");
}
Accesses++;
return A[Size-1];
}
//
// begin:
//
// Returns an iterator (think pointer) denoting the first element
// in the vector. Iterators are used with the built-in C++ algorithms
// to iterate through various data structures in an abstract way.
//
iterator begin()
{
return iterator(this);
}
//
// end:
//
// Returns an iterator (think pointer) denoting the end of the iteration
// space. Iterators are used with the built-in C++ algorithms to
// iterate through various data structures in an abstract way.
//
iterator end()
{
return iterator(this, Size);
}
//
// _output:
//
// Outputs the contents of the vector to stdout; for debugging purposes.
//
void _output()
{
cout << "** ourvector: ";
for (int i = 0; i < Size; ++i)
cout << A[i] << " ";
cout << endl;
}
//
// _stats:
//
// Outputs vector usage stats to stderr; for debugging purposes.
//
void _stats()
{
string name = typeid(T).name();
//
// if the name is complicated, strip off just the prefix:
//
size_t pos = name.find('<');
if (pos != string::npos)
{
name = name.substr(0, pos);
}
cerr << "*********************************************************" << endl;
cerr << "ourvector<" << name << "> stats:" << endl;
cerr << " # of vectors created: " << Vectors << endl;
cerr << " # of elements inserted: " << Inserts << endl;
cerr << " # of elements accessed: " << Accesses << endl;
cerr << "*********************************************************" << endl;
}
};
//
// For collecting stats on vector usage:
//
template<typename T>
int ourvector<T>::InUse = 0;
template<typename T>
int ourvector<T>::Vectors = 0;
template<typename T>
int ourvector<T>::Inserts = 0;
template<typename T>
int ourvector<T>::Accesses = 0;