This presentation was stolen from Chuck Anderson.
#include <iostream> using namespace std; int *copyInts(int *src, int num) { int *dest = new int[num]; for (int i=0; i<num; i++) dest[i] = src[i]; return dest; } double *copyDoubles(double *src, int num) { double *dest = new double[num]; for (int i=0; i<num; i++) dest[i] = src[i]; return dest; } char *copyLetters(char *src, int num) { char *dest = new char[num]; for (int i=0; i<num; i++) dest[i] = src[i]; return dest; } void printInts(int *src, int num) { for (int i=0; i<num; i++) cout << src[i] << ' '; cout << endl; } void printDoubles(double *src, int num) { for (int i=0; i<num; i++) cout << src[i] << ' '; cout << endl; } void printLetters(char *src, int num) { for (int i=0; i<num; i++) cout << src[i] << ' '; cout << endl; } int main() { int ints[5] = {1, 2, 3, 4, 5}; double doubles[4] = {0.1, 0.2, 0.3, 0.4}; char letters[6] = {'a', 'b', 'c', 'd', 'e', 'f'}; int *ints2 = copyInts(ints,5); double *doubles2 = copyDoubles(doubles,4); char *letters2 = copyLetters(letters,6); printInts(ints,5); printInts(ints2,5); printDoubles(doubles,4); printDoubles(doubles2,4); printLetters(letters,6); printLetters(letters2,6); }
When run, we see:
1 2 3 4 5 1 2 3 4 5 0.1 0.2 0.3 0.4 0.1 0.2 0.3 0.4 a b c d e f a b c d e f
Can this code be simplified? Yep. We don’t need printInts
,
printDoubles
and printLetters
. All can be named print
,
because the types of the compiler can disambiguate the calls to
print
by the types of the arguments.
#include <iostream> using namespace std; int *copyInts(int *src, int num) { int *dest = new int[num]; for (int i=0; i<num; i++) dest[i] = src[i]; return dest; } double *copyDoubles(double *src, int num) { double *dest = new double[num]; for (int i=0; i<num; i++) dest[i] = src[i]; return dest; } char *copyLetters(char *src, int num) { char *dest = new char[num]; for (int i=0; i<num; i++) dest[i] = src[i]; return dest; } void print(int *src, int num) { for (int i=0; i<num; i++) cout << src[i] << ' '; cout << endl; } void print(double *src, int num) { for (int i=0; i<num; i++) cout << src[i] << ' '; cout << endl; } void print(char *src, int num) { for (int i=0; i<num; i++) cout << src[i] << ' '; cout << endl; } int main() { int ints[5] = {1, 2, 3, 4, 5}; double doubles[4] = {0.1, 0.2, 0.3, 0.4}; char letters[6] = {'a', 'b', 'c', 'd', 'e', 'f'}; int *ints2 = copyInts(ints,5); double *doubles2 = copyDoubles(doubles,4); char *letters2 = copyLetters(letters,6); print(ints,5); print(ints2,5); print(doubles,4); print(doubles2,4); print(letters,6); print(letters2,6); }
And we still get:
1 2 3 4 5 1 2 3 4 5 0.1 0.2 0.3 0.4 0.1 0.2 0.3 0.4 a b c d e f a b c d e f
Can we do the same for the copy functions?
#include <iostream> using namespace std; int *copy(int *src, int num) { int *dest = new int[num]; for (int i=0; i<num; i++) dest[i] = src[i]; return dest; } double *copy(double *src, int num) { double *dest = new double[num]; for (int i=0; i<num; i++) dest[i] = src[i]; return dest; } char *copy(char *src, int num) { char *dest = new char[num]; for (int i=0; i<num; i++) dest[i] = src[i]; return dest; } void print(int *src, int num) { for (int i=0; i<num; i++) cout << src[i] << ' '; cout << endl; } void print(double *src, int num) { for (int i=0; i<num; i++) cout << src[i] << ' '; cout << endl; } void print(char *src, int num) { for (int i=0; i<num; i++) cout << src[i] << ' '; cout << endl; } int main() { int ints[5] = {1, 2, 3, 4, 5}; double doubles[4] = {0.1, 0.2, 0.3, 0.4}; char letters[6] = {'a', 'b', 'c', 'd', 'e', 'f'}; int *ints2 = copy(ints,5); double *doubles2 = copy(doubles,4); char *letters2 = copy(letters,6); print(ints,5); print(ints2,5); print(doubles,4); print(doubles2,4); print(letters,6); print(letters2,6); }
Yep. It works:
1 2 3 4 5 1 2 3 4 5 0.1 0.2 0.3 0.4 0.1 0.2 0.3 0.4 a b c d e f a b c d e f
Do you see further simplifications? No, unless there is some way to parameterize the type of the arguments when you define the functions.
#include <iostream> using namespace std; template <typename T> T *copy(T *src, int num) { T *dest = new T[num]; for (int i=0; i<num; i++) dest[i] = src[i]; return dest; } template <typename T> void print(T *src, int num) { for (int i=0; i<num; i++) cout << src[i] << ' '; cout << endl; } int main() { int ints[5] = {1, 2, 3, 4, 5}; double doubles[4] = {0.1, 0.2, 0.3, 0.4}; char letters[6] = {'a', 'b', 'c', 'd', 'e', 'f'}; int *ints2 = copy(ints,5); double *doubles2 = copy(doubles,4); char *letters2 = copy(letters,6); print(ints,5); print(ints2,5); print(doubles,4); print(doubles2,4); print(letters,6); print(letters2,6); }
Look at all the places where T
was used and was not used in copy()
.
Templates can also be used to avoid redefining classes for different types. Here’s an example. Let’s say we want to keep track of either integer or floating-point 2-D points:
#include <iostream> using namespace std; class PointInt { private: int x, y; public: PointInt(const int xarg, const int yarg) : x(xarg), y(yarg) { } void setx(const int xarg) { x = xarg; } void sety(const int yarg) { y = yarg; } int getx() const { return x; } int gety() const { return y; } }; class PointDouble { private: double x, y; public: PointDouble(const double xarg, const double yarg) : x(xarg), y(yarg) { } void setx(const double xarg) { x = xarg; } void sety(const double yarg) { y = yarg; } double getx() const { return x; } double gety() const { return y; } }; int main() { PointInt pi(1,2); PointDouble pd(3.4,2.5); cout << '(' << pi.getx() << ',' << pi.gety() << ')' << endl; cout << '(' << pd.getx() << ',' << pd.gety() << ')' << endl; }
When run, we see this output.
(1,2) (3.4,2.5)
Can we get rid of the duplications between PointInt
and
PointDouble
? Only if there is a way to parameterize the type of the
class.
#include <iostream> using namespace std; template <typename T> class Point { private: T x, y; public: Point(const T xarg, const T yarg) : x(xarg), y(yarg) { } void setx(const T xarg) { x = xarg; } void sety(const T yarg) { y = yarg; } T getx() const { return x; } T gety() const { return y; } }; int main() { // This next statement doesn’t work. // For templates classes, must specify the type. // Point pi(1,2); Point<int> pi(1,2); Point<double> pd(3.4,2.5); cout << '(' << pi.getx() << ',' << pi.gety() << ')' << endl; cout << '(' << pd.getx() << ',' << pd.gety() << ')' << endl; }
You can have more than one parameter—just be sure you use the right type in the right place.
#include <iostream> using namespace std; template <typename Tx, typename Ty> class Point { private: Tx x; Ty y; public: Point(const Tx xarg, const Ty yarg) : x(xarg), y(yarg) { } void setx(const Tx xarg) { x = xarg; } void sety(const Ty yarg) { y = yarg; } Tx getx() const { return x; } Ty gety() const { return y; } }; int main() { // This next statement doesn’t work. For templates classes, must specify the type. // Point pi(1,2); Point<int,int> pi(1,2); Point<double,double> pd(3.4,2.5); Point<int,double> pid(5, 6.42); cout << '(' << pi.getx() << ',' << pi.gety() << ')' << endl; cout << '(' << pd.getx() << ',' << pd.gety() << ')' << endl; cout << '(' << pid.getx() << ',' << pid.gety() << ')' << endl; }
Output is
(1,2) (3.4,2.5) (5,6.42)
You can also include a constant integer to specify things like the size of an array, and can have a default value.
#include <iostream> using namespace std; template <typename T, int N = 10> class Bunch { private: T data[N]; public: Bunch() {} void setData(int index, const T &item) { data[index] = item; } friend ostream &operator<< (ostream &out, Bunch<T,N> &b) { for (int i=0; i<N; i++) out << b.data[i] << ' '; return out; } }; int main() { Bunch<char,5> b; b.setData(0,'a'); b.setData(1,'b'); b.setData(2,'c'); b.setData(3,'d'); b.setData(4,'e'); cout << b << endl; }
Produces the output:
a b c d e
Adding these lines at the end of main()
Bunch<int> nums; nums.setData(0,11); nums.setData(1,22); cout << nums << endl;
produces:
a b c d e 11 22 4196139 0 9376000 0 4197223 0 9339136 0
Why the funky numbers?
Now, back to the points. We have parameterized the types of x and y in
Point
. Now, how do we make a collection, like an array, of
Point
objects, when Point
itself is parameterized?
#include <iostream> using namespace std; template <typename T> class Point { private: T x, y; public: Point() : x(0), y(0) { } Point(const T xarg, const T yarg) : x(xarg), y(yarg) { } void setx(const T xarg) { x = xarg; } void sety(const T yarg) { y = yarg; } T getx() const { return x; } T gety() const { return y; } }; template <typename T, template <typename> class P> // Whoa ... class Points { private: P<T> *data; int number; public: Points(const int num) : number(num), data(new P<T>[num]) {} void set(int index, const T &x, const T &y) { data[index].setx(x); data[index].sety(y); } friend ostream &operator<<(ostream &out, Points<T,P> &p) { for (int i=0; i<p.number; i++) out << " (" << p.data[i].getx() << ',' << p.data[i].gety() << ')'; return out; } }; int main() { Points<int,Point> points(3); points.set(0,1,2); points.set(1,10,20); points.set(2,30,40); cout << points << endl; Points<double,Point> pointsd(3); pointsd.set(0,1.1,2.2); pointsd.set(1,10.1,20.2); pointsd.set(2,30.1,40.2); cout << pointsd << endl; } (:sourceend:)
Output is:
(1,2) (10,20) (30,40) (1.1,2.2) (10.1,20.2) (30.1,40.2)
Tough slogging ... but cool!
Modified: 2012-04-06T18:29 User: Guest Check: HTML CSSEdit History Source |
Apply to CSU |
Contact CSU |
Disclaimer |
Equal Opportunity Colorado State University, Fort Collins, CO 80523 USA © 2015 Colorado State University |