C++ Templates L03 - Iterator 10 – Iterator
Attendance Quiz #11 Iterators
Tip #12: new vs. malloc Differences between malloc and new: Iterators Differences between malloc and new: Calling Constructors: new calls constructors, malloc() does not. In fact primitive data types (char, int, float.. etc) can also be initialized with new. Operator vs function: new is an operator, malloc() is a function. Return type: new is type safe and returns exact data type, malloc() returns void *. Failure Condition: On failure, malloc() returns NULL, new Throws. Memory: In case of new, memory is allocated from free store where as in malloc() memory allocation is done from heap. Overriding: We are allowed to override new operator where as we can not override the malloc() function legally. Size: Required size of memory is calculated by compiler for new, where as we have to manually calculate size for malloc(). The short answer is: Fundamentally, malloc is a C feature and new is a C++ feature. Don't use malloc with C++ without a really good reason for doing so.
Strings Are Really Templates C++ Templates Strings Are Really Templates
Template Classes and Functions Iterators C++ templates use "Instantiation-style polymorphism". A template is literally a template; a class template is not a class, it's a recipe for creating a class using template parameters. Class and function functionality can be adapted to more than one type or class without repeating the entire code for each type. template <typename T> class MyPair { private: T values [2]; public: MyPair (T first, T second) values[0]=first; values[1]=second; } }; MyPair<int> myInts(115, 36); MyPair<double> myFloats(3.0, 2.18); template <typename int> int GetMax(int a, int b) { return (a > b ? a : b); } int x,y; GetMax<int>(x, y); template <typename myType> myType GetMax(myType a, myType b) { return (a > b ? a : b); } T is the template parameter name instead of myType because it is shorter and in fact is a very common template parameter name. But you can use any identifier you like.
Iterators Nested Classes
Nested Class Iterators A nested (inner) class is a class which is declared inside an enclosing (outer) class. While an inner class is a member of the outer class and has the same access rights as any other outer class member, An inner class does not have an implicit reference to an instance of the outer class. If the inner class needs access to outer class members, you must pass an outer class reference (pointer) to the inner class, then the inner class can reference anything in the outer class instance. class Outer { class Inner Outer* ptr; Inner(Outer* p) : ptr(p) {} };
Nested Class Iterators You make the parent-child relationship manually thru a reference/pointer or constructor arguments. class Outer { private: int var; public: Outer() : var(0) {} class Inner Outer* optr; Inner(Outer* op) : optr(op) {} void add(int x) { optr->var += x; } }; Inner newInner() { return Outer::Inner(this); } int getVar() { return var; } int main() { Outer outer; Outer::Inner inner = outer.newInner(); inner.add(20); cout << outer.getVar(); return 0; } 20
Nested Class Nested classes increase encapsulation. Iterators Nested classes increase encapsulation. Nested classes can lead to more readable and maintainable code – useful for developing object models in your component. Iterators are generally implemented as nested classes. class MyClass { class Iterator }; Iterator begin() { return MyClass::Iterator(this); } Iterator end() { return MyClass::Iterator(this); }
Iterators Iterators
Array Container Access Iterators By Index #include <iostream> using namespace std; int main() { int dog[] = { 1, 2, 3, 4 }; int* dptr = dog; for (size_t i = 0; i < 4; ++i) cout << *dptr++ << endl; } return 0; By Pointer #include <iostream> using namespace std; int main() { int dog[] = { 1, 2, 3, 4 }; for (size_t i = 0; i < 4; ++i) cout << dog[i] << endl; } return 0;
Other Containers #include <iostream> #include <vector> Iterators #include <iostream> #include <vector> using namespace std; int main() { vector<int> numbers; numbers.push_back(1); numbers.push_back(2); numbers.push_back(3); numbers.push_back(4); for (size_t i = 0; i < numbers.size(); ++i) cout << numbers[i] << endl; } return 0; #include <iostream> #include <list> using namespace std; int main() { list<int> numbers; numbers.push_back(1); numbers.push_back(2); numbers.push_back(3); numbers.push_back(4); for (size_t i = 0; i < numbers.size(); ++i) cout << numbers[i] << endl; } return 0;
Other Containers w/Iterator Iterators #include <iostream> #include <vector> using namespace std; int main() { vector<int> numbers; numbers.push_back(1); numbers.push_back(2); numbers.push_back(3); numbers.push_back(4); vector<int>::iterator iter = numbers.begin(); while (iter != numbers.end()) cout << *iter++ << endl; } return 0; #include <iostream> #include <list> using namespace std; int main() { list<int> numbers; numbers.push_back(1); numbers.push_back(2); numbers.push_back(3); numbers.push_back(4); list<int>::iterator iter = numbers.begin(); while (iter != numbers.end()) cout << *iter++ << endl; } return 0;
Why Use an Iterator? Those who avoid using iterators: Assume your container has index ([]), at, and increment (++,--) operators. Assume your container elements can be randomly accessed, are contiguous, and same size (only true for vectors...) Have to write their own versions of common algorithms (ie., sort or reverse) Iterators bring you closer to container independence. You're not making assumptions about random-access ability, storage format, efficiency of operations such as size(), or most algorithms. You only need to know that the container has iterator capabilities. Iterators enhance your code further with standard algorithms. Depending on what it is you're trying to achieve, you may elect to use for_each(), find(), replace(), partition(), search(), transform(), sort(), … By using a standard algorithm rather than an explicit loop you're avoiding re-inventing the wheel. Your code is likely to be more efficient (given the right algorithm is chosen), correct, and reusable.
The STL Iterator Approach Iterators #include <iostream> #include "myArray" using namespace std; int main() { MyArray<int> myArray; myArray.push_back(1); myArray.push_back(2); myArray.push_back(3); myArray.push_back(4); MyArray<int>::iterator iter = myArray.begin(); while (iter != myArray.end()) cout << *iter << " "; ++iter; } return 0; #include <iostream> #include <vector> using namespace std; int main() { vector<int> myArray; myArray.push_back(1); myArray.push_back(2); myArray.push_back(3); myArray.push_back(4); vector<int>::iterator iter = myArray.begin(); while (iter != myArray.end()) cout << *iter << " "; ++iter; } return 0; iter "points" to first element in myArray. myArray.end() "points" to something NOT in myArray. Dereference iter to access myArray elements.
Lab 03 - Iterator
You may freely use the code in any way you deem useful. ****Disclaimer**** The following code examples are flawed and incomplete, but demonstrate how an iterator class might be implemented. You may freely use the code in any way you deem useful.
Step 1 – int Array Class Iterators #include <iostream> #include <string> #include <sstream> #define MAX_ARRAY_SIZE 1000 using namespace std; class MyArray { private: size_t size_; int* array_; public: MyArray(size_t maxSize) : size_(0) { array_ = (int*)malloc(maxSize * sizeof(int)); } void push_back(int item) { array_[size_++] = item; } }; int main(int argc, char * argv[]) { MyArray numbers(MAX_ARRAY_SIZE); numbers.push_back(1); numbers.push_back(2); numbers.push_back(3); numbers.push_back(4); cout << numbers << endl; return 0; } Be sure to use a destructor to free array! string toString() const { stringstream out; out << "myArray"; for (size_t i = 0; i < size_; ++i) out << ((i % 10) ? " " : "\n") << array_[i]; return out.str(); } friend std::ostream& operator<< (ostream& os, const MyArray<T>& myArray) os << myArray.toString(); return os; Remember, every class needs a toString and a Friend!
Step 2 – Template Class Iterators #include <iostream> #include <string> #include <sstream> #define MAX_ARRAY_SIZE 1000 using namespace std; template<typename T> class MyArray { private: size_t size_; T* array_; public: MyArray(size_t maxSize) : size_(0) { array_ = (T*)malloc(maxSize * sizeof(T)); } void push_back(T item) { array_[size_++] = item; } }; int main(int argc, char * argv[]) { MyArray<int> numbers(MAX_ARRAY_SIZE); numbers.push_back(1); numbers.push_back(2); numbers.push_back(3); numbers.push_back(4); cout << numbers << endl; return 0; } Templates are a feature of the C++ programming language that allows functions and classes to operate with generic types, allowing a function or class to work on many different data types without being rewritten for each one. There are three kinds of templates: function templates, class templates and, since C++14, variable templates.
Step 3 – Add a Nested Iterator Iterators #include <iostream> #include <string> #include <sstream> #define MAX_ARRAY_SIZE 1000 using namespace std; template<typename T> class MyArray { private: size_t size_; T* array_; public: MyArray(size_t maxSize) : size_(0) { array_ = (T*)malloc(maxSize * sizeof(T)); } void push_back(T item) { array_[size_++] = item; } class Iterator size_t index_; MyArray<T>* array_ptr_; Iterator(MyArray<T>* a, size_t s) : array_ptr_(a), index_(s) { } T operator[](size_t i) const { return array_ptr_->array_[i]; } }; Iterator begin() { return MyArray<T>::Iterator(this, 0); } Iterator end() { return MyArray<T>::Iterator(this, size_); } int main(int argc, char * argv[]) { MyArray<int> numbers(MAX_ARRAY_SIZE); numbers.push_back(1); numbers.push_back(2); numbers.push_back(3); numbers.push_back(4); MyArray<int>::Iterator iter = numbers.begin(); for (size_t i = 0; i < 4; ++i) cout << iter[i] << ' '; cout << endl << endl; return 0; } MyArray Iterator Nested classes can access all members of the parent via a reference/pointer.
Step 4 – Add Functionality Iterators #include <iostream> #include <string> #include <sstream> #define MAX_ARRAY_SIZE 1000 using namespace std; template<typename T> class MyArray { private: size_t size_; T* array_; public: MyArray(size_t maxSize) : size_(0) { array_ = (T*)malloc(maxSize * sizeof(T)); } void push_back(T item) { array_[size_++] = item; } class Iterator size_t index_; MyArray<T>* array_ptr_; Iterator(MyArray<T>* a, size_t s) : array_ptr_(a), index_(s) { } bool operator!=(Iterator rhs) const { ... } T& operator*() const { ... } Iterator& operator++() { ... } }; Iterator begin() { return MyArray<T>::Iterator(this, 0); } Iterator end() { return MyArray<T>::Iterator(this, size_); } int main(int argc, char * argv[]) { MyArray<int> numbers(MAX_ARRAY_SIZE); numbers.push_back(1); numbers.push_back(2); numbers.push_back(3); numbers.push_back(4); MyArray<int>::Iterator iter = numbers.begin(); while (iter != numbers.end()) cout << *iter << ‘ ‘; ++iter; } cout << endl << endl; return 0; Add not equal (!=), dereference (*), and pre-increment (++) operators.
Output of Iterator Lab myArray: 1 2 3 4 5 6 7 8 9 10 Iterators myArray: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 ITERATORS: begin(): size=20 index=0 end(): size=20 index=20 SEQUENTIAL: PRIME: 2 3 5 7 11 13 17 19 COMPOSITE: 4 6 8 9 10 12 14 15 16 18 20 FIBONACCI: 3 = 1 + 2