270 likes | 285 Views
Learn about ADTs, data structures, and how to implement them through a book example using C++ code. Understand concepts like coercion, casting, binary representation, and logic gates.
E N D
CS 240: Data Structures Tuesday, June 10th ADT Requirements (Card), I/O
Terminology • Here is some stuff to memorize: • “Container Class” • mycontainer is a type of “Container Class” • “Sequential Container” • An array is a sequential container so is mycontainer. • “Coercion” and “Casting”
Some Material • This is a list of some material which I can cover if you want but already expect you all to know as part of the background courses. • Binary representation • Also, real values (you do not need to translate, just understand that the binary is an abstraction) • Logic • And, or, xor, nor, etc • I may draw gates, but I’ll give you a key
ADTs • An ADT is a collection of data that represents some sort of abstraction. • A person is represented by: • Name, age, height, etc • A book “can” be represented by: • Title, ISBN, author, year
ADTs • However, an ADT isn’t too useful if it just stores data and nothing else. • All this would do is allow you to organize data better:
#ifndef BOOK_H #define BOOK_H class Book { public: string title; string ISBN; string author; int year; }; #endif This just stores data. A “storage only” ADT can have public variables/members. A “storage only” ADT has no associated functions; therefore, it has no cpp file. In C, this would instead be a struct Book ADT
#ifndef BOOK_H #define BOOK_H class Book { public: string title; string ISBN; string author; int year; }; #endif #include<iostream> #include”Book.h” using namespace std; int main() { Book mylibrary[10]; string intitle,inISBN,inauthor; int inyear; for(int i=0;i<10;i++) { cin >> intitle >> inISBN; cin >> inauthor >> inyear; mylibrary[i].title=intitle; mylibrary[i].ISBN=inISBN; mylibrary[i].author=inauthor; mylibrary[i].year=inyear; } cout << mylibrary[0].title <<endl; cout << mylibrary[0].ISBN <<endl; } Book ADT
Book ADT • Manipulating this book can become annoying. • Therefore, we turn it into a Data Structure by associating functions with it.
How can we simplify the input loop? It would be annoying if every time we wanted to use the Book class we would have to copy that entire set of assignment instructions. There are two approaches to this: 1) Create an input function (low-level) 2) Overload input to allow direct access (high-level) Book ADT
#ifndef BOOK_H #define BOOK_H #include<iostream> using namespace std; class Book { public: void input(istream & in); void display(ostream & out); private: string title; string ISBN; string author; int year; }; istream & operator >>(istream & in, Book & target); ostream & operator <<(ostream & out, Book & target); #endif void Book::input(istream & in) { in >> title; in >> ISBN; in >> author; in >> year; } void Book::display(ostream & out) { out << title << “ “ << ISBN << “ “; out << author << “ “ << year <<endl; } istream? istream stands for “input stream” We can tell the function what input stream to use: cin, or some other stream. The syntax of operator >> requires memorization.
#ifndef BOOK_H #define BOOK_H #include<iostream> using namespace std; class Book { public: void input(istream & in); void display(ostream & out); private: string title; string ISBN; string author; int year; }; istream & operator >>(istream & in, Book & target); ostream & operator <<(ostream & out, Book & target); #endif istream & operator >>(istream &in, Book & target) { target.input(in); return in; } ostream & operator <<(ostream & out, Book & target) { target.display(out); return out; } Really, we should also be able to return a string but we won’t worry about that for this example.
Instead of: #include<iostream> #include”Book.h” using namespace std; int main() { Book mylibrary[10]; string intitle,inISBN,inauthor; int inyear; for(int i=0;i<10;i++) { cin >> intitle >> inISBN; cin >> inauthor >> inyear; mylibrary[i].title=intitle; mylibrary[i].ISBN=inISBN; mylibrary[i].author=inauthor; mylibrary[i].year=inyear; } cout << mylibrary[0].title <<endl; cout << mylibrary[0].ISBN <<endl; } Now: #include<iostream> #include”Book.h” using namespace std; int main() { Book mylibrary[10]; string intitle,inISBN,inauthor; int inyear; for(int i=0;i<10;i++) { cin >> mylibrary[i]; } for(int i=0;i<10;i++) { cout << mylibrary[i]; } return 0; } Now we can update our code:
Or: #include<iostream> #include”Book.h” using namespace std; int main() { Book mylibrary[10]; string intitle,inISBN,inauthor; int inyear; for(int i=0;i<10;i++) { mylibrary[i].input(cin); } for(int i=0;i<10;i++) { mylibrary[i].display(cout); } return 0; }
Rationale • Why do we do the second method? • Well, it allows us to see more readily what is going on. • We call the input method with cin. • We call the display method with cout. • Alternatively, we can give these functions other streams.
I/O Streams • The streams we have been exposed to so far: • cin and cout • However, there are other streams we can use: • ifstream and ofstream • These are for file input and file output
File I/O Example ifstream myfile; string toopen = “filename.txt”; myfile.open(toopen.c_str(),ios::in); string input; myfile >> input; myfile.close();
File I/O Example ofstream myfile; string toopen = “filename.txt”; myfile.open(toopen.c_str(),ios::out); string output = “I’m writing data”; myfile << output; myfile.close();
Card ADT • Let us come up with a specification for a playing card. Then we can code it: • We need to come up with the important abstractions that relate to a playing card. • We also need to take into consideration how the Card is managed in memory. • We will also examine the three required methods of most ADTs • Copy constructor, assignment operator and destructor • Also, what would a container class of Card require?
What is a primitive? • A primitive is a built-in data type. • Generally, it has complete functionality. • They have a value and are located in memory. • During compilation, they have a name. • Only a memory address is used while running. Primitive Type: int Value (32 bits): ? Address: ? Primitive Type: char Value (8 bits): ? Address: ?
Data Manipulation • Therefore, we use the address of the data to locate the value we want. • How about an array of ints? • Ok, so if we know where the array starts we can find successive data.
Data Manipulation • An array is a form of a data abstraction. class array { T value; memory_address next_data; memory_address own_location; }; Where next_data is always equal to the size of T. This is done automatically when we use array implementation (this abstraction only works in theory since the next_location would be offset by the other class members). If you created an array of this time (and set next_data to size of array instead of T), it would mirror the real implementation.
Data Manipulation • These abstractions allow us to group data together so that we access data we can get more than 1 piece of information. Address: ? ADT: array (int) 96 bits (32 bits) -> int (32 bits) -> 12 (32 bits) -> *this Primitive Type: int Value (32 bits): ? Address: ?
array testdata[3]; Address: X Address: X + 12 Address: X + 24 ADT: array (int) 96 bits (32 bits) -> int (5) (32 bits) -> 12 (32 bits) -> *this (X) ADT: array (int) 96 bits (32 bits) -> int (10) (32 bits) -> 12 (32 bits) -> *this (X+12) ADT: array (int) 96 bits (32 bits) -> int (15) (32 bits) -> 12 (32 bits) -> *this (X+24) This actually requires array to have a constructor to set: next_data = sizeof(array); own_location = *this; Therefore: &testdata[0] = &testdata[0].own_location; &testdata[1] = &testdata[1].own_location; and &testdata[1] = &testdata[0].own_location + testdata[0].next_data; This is a representation of how it is actually done in memory.
Data Manipulation • You wouldn’t implement an array like we did in the last slide. However, we using it slightly differently we can achieve a differently goal.
array testdata[3]; Address: X Address: X + 12 Address: X + 24 ADT: array (int) 96 bits (32 bits) -> int (5) (32 bits) -> 24 (32 bits) -> *this (X) ADT: array (int) 96 bits (32 bits) -> int (10) (32 bits) -> 12 (32 bits) -> *this (X+12) ADT: array (int) 96 bits (32 bits) -> int (15) (32 bits) -> -12 (32 bits) -> *this (X+24) If we use [ ], we will get all the data in the same order as before: 5 10 15 However, if we use next_data: 5 15 10 We now have a new representation – an array-based linked list
We don’t need an array… Address: X Address: Y Address: Z ADT: array (int) 96 bits (32 bits) -> int (5) (32 bits) -> Z-X (32 bits) -> *this (X) ADT: array (int) 96 bits (32 bits) -> int (10) (32 bits) -> -Y (32 bits) -> *this (Y) ADT: array (int) 96 bits (32 bits) -> int (15) (32 bits) -> Y-Z (32 bits) -> *this (Z) In our assignments we won’t use “our_location” We always know where the first item is and can find the remaining items by using next_item.
Without our_location Address: X Address: Y Address: Z ADT: array (int) 96 bits (32 bits) -> int (5) (32 bits) -> Z ADT: array (int) 96 bits (32 bits) -> int (10) (32 bits) -> 0 ADT: array (int) 96 bits (32 bits) -> int (15) (32 bits) -> Y If our first item is at address X then, (5) we find the second item by using next_item (15) and the third item by using next_item of the second (10) and since third_item.next_item == 0, we are done.