EXERCISE ■ 779 Exercise In data communications between two remote computers messages are transmitted via multiple subnets. En route to the target computers, so-called routers store these messages in queues before transmitting them towards the target via the next available line. The routers assume responsibility for route discovery, generally referring to complex address tables. There are various routing techniques, including a simple algorithm that can do with- out address tables, the so-called Hot Potato Algorithm. The router simply tries to dispose of incoming messages as quickly as possible by sending each incoming message to the outgoing line with the shortest queue. ■ Define a container class VecQueue<T>, which is parameterized with a message type T to represent this scenario. The class comprises an array of queues of type vector< queue<T> > and a data member used to store the current number of queues in the array. The constructor creates the number of empty queues passed to it as an argument for the array. Additionally, you will need to declare the methods size(), empty(), push() and pop(). Overload the size() method in two versions: If no argument has been passed to the method, it returns the current number of messages in all queues. If an argu- ment i of type int has been passed, the method returns the current number of messages in the i-th queue. Additionally, overload the empty() and empty(int i) methods, which return true, if all queues or the i-th queue are empty. The push() method uses the hot potato algorithm to append a message passed to it at the end of the shortest queue. The pop() and pop(int i) methods are used to simulate the assignment of messages to lines, that is retrieval and removal of messages from queues, in this exercise. The method pop() retrieves the message at the top of a randomly selected queue and deletes it, returning the message. The method pop(int i) retrieves the message at the top of the i-th queue and deletes it, returning the message. ■ To test your class, declare a container of the type VecQueue<int> in your main function. A message is represented by a number. Use a loop to insert ran- dom numbers between 0 and 99 into the container and relay some of them to the outside lines. Then display the remaining messages on screen, as shown opposite, by calling the pop(int i) method. solution 780 ■ CHAPTER 33 CONTAINERS Solution // // vecQueue.h // Defining the Class Template VecQueue<T> // to represent a vector of queues. // #ifndef _VECQUEUE_H #define _VECQUEUE_H #include <vector> #include <queue> #include <cstdlib> // For srand(), rand() #include <ctime> // For time() using namespace std; template <class T> class VecQueue { private: vector< queue<T> > v; size_t sz; // Number of queues public: VecQueue(size_t n); size_t size() const; // Current number of all // elements. size_t size(int i) const // Number of elements in { return v[i].size(); } // the i-th queue. bool empty() const { return size() == 0; } bool empty(int i) const { return size(i) == 0; } void push(const T& a); // Hot potato algorithm const T& pop(); // Removes the element at the // beginning of a randomly // choosen queue. const T& pop(int i); // Removes the element at the }; // beginning of the i-th queue template <class T> VecQueue<T>::VecQueue( size_t n) // Constructor { if(n > 0) v.resize(n); sz = n; srand(time(NULL)); } SOLUTION ■ 781 template <class T> // Current number of all elements size_t VecQueue<T>::size() const { size_t count = 0; for( int i=0; i < sz; ++i) count += v[i].size(); return count; } template <class T> // To insert the argument into the void VecQueue<T>::push(const T& a) // shortest queue { int small = 0; // To determine the for(int i = 0; i < sz; i++) // shortest queue. if( v[i].size() < v[small].size()) small = i; v[small].push(a); // and insert there. } template <class T> // To retrieve and delete const T& VecQueue<T>::pop() // an element in a randomly { // choosen queue. static T temp; int i, i0; i = i0 = rand() % sz; do { if(!v[i].empty()) // If i-th queue is not empty: { // To retrieve and delete the temp = v[i].front(); // element at the beginning. v[i].pop(); break; } i = (i+1) % sz; // Or else: Move to the next queue. } while( i != i0); return temp; } template <class T> // To retrieve and delete const T& VecQueue<T>::pop(int i) // an element in the { // i-th queue. static T temp; if( i >= 0 && i < sz) // If the index is okay: { // To retrieve the element temp = v[i].front(); // at the beginning and v[i].pop(); // to delete. } return temp; } #endif // _VECQUEUE_H 782 ■ CHAPTER 33 CONTAINERS Solutions (continued) // // hotpot_t.cpp : Simulates the hot potato algorithm // using a vector of queues. // #include <cstdlib> // For srand(), rand() #include <ctime> // For time() #include <iostream> #include <iomanip> using namespace std; #include “vecQueue.h” int main() { const int nQueues = 9; VecQueue<int> vq(9); // Vector of 9 queues cout << nQueues << “ queues have been created.” << endl; srand(time(NULL)); cout << “\nThe queues will now be filled “ << “using the hot potato algorithm.” << endl; int i; for(i = 0; i < 100; i++) // To insert 100 elements vq.push(rand()%100); cout << “\nSome elements of randomly selected “ “queues are removed.” << endl; for(i=0; i < 50; i++) // To remove 50 elements vq.pop(); cout << “\nTo output the queues:” << endl; // To retrieve, remove for( i = 0; i < nQueues; ++i) // and display all { // remaining elements. cout << “\n” << i+1 << “.Queue: “; while( vq.size(i) > 0 ) { cout << setw(4) << vq.pop(i); } cout << endl; } return 0; } 783 This appendix contains ■ binary number representation ■ preprocessor directives ■ pre-defined standard macros ■ binding C functions ■ operator precedence table ■ ASCII Code table ■ screen control characters appendix 784 ■ APPENDIX ■ BINARY NUMBERS The numbers used by a program can be divided into two groups depending on their type: ■ integers of the char, signed char, unsigned char, short, unsigned short , int, unsigned int, long, unsigned long types and ■ floating-point numbers of the float, double, and long double types. Both integral and floating-point numbers are represented internally as binary numbers, that is, as sequences of 0 and 1 values. However, the formats for representing integral and floating-point numbers differ. Thus, the bit-pattern of an integer will be interpreted dif- ferently from that of a floating-point number by the computer. Representing Signed and Unsigned Integers The binary format of integers is basically the same for the char, short, int and long types and differs only in ■ the number of bytes available for each type and ■ whether the number is interpreted as signed or unsigned. The bit-pattern of a positive integer can be represented as a base 2 power series. The sign bit 0 additionally indicates that the number is positive in the case of signed types. The number 4 can be represented by the following power series: 0*2 0 + 0*2 1 + 1*2 2 + 0*2 3 + 0*2 4 The binary representation of the number 4 as signed char type value (8 bits) is thus as follows: Two’s complement is used to represent a negative number, for example -4: 11111011 First, one's complement of 4 is computed, that is, all the bits are inverted: Then the number 1 is added: Producing the bit pattern of –4: 00000001 11111100 2 6 2 5 2 2 2 1 2 0 00000 001 Sign bit You can also use two’s complement to compute the absolute value of a negative num- ber. Two’s complement for -4 yields a value of 4. Sign bits are not required for unsigned types. The bit can then be used to represent further positive numbers, doubling the range of positive numbers that can be repre- sented. The following table contains the binary formats of signed and unsigned integral 8 bit values: If the bit-pattern of a negative number is interpreted as an unsigned number, the value of the number changes. The bit-pattern 1111 1100 of the number Ϫ4 will thus yield the following unsigned value: 0*2 0 + 0*2 1 + 1*2 2 + 1*2 3 + 1*2 4 + 1*2 5 + 1*2 6 + 1*2 7 that is, the decimal number 252. Representing Floating-point Numbers To represent a given floating-point number, x, the number is first broken down into a sign, v, a mantissa, m, and a power, exp, with a base of 2: x = v * m * 2 exp 0000 0000 0000 0001 0000 0010 0000 0011 0111 1101 0111 1110 0111 1111 1111 1100 1111 1101 1111 1110 1111 1111 1000 0000 1000 0001 Binary Signed decimal Unsigned decimal . . . . 0 1 2 3 125 126 127 –128 –127 –4 –3 –2 –1 . . . . 128 129 252 253 254 255 . . 0 1 2 3 125 126 127 . . BINARY NUMBERS ■ 785 Memory for the values v, m, and exp is normally assigned in IEEE (Institute of Elec- tronics and Electronical Engineers) format. The type float (32 bit) will thus be organ- ized as follows: In this “normalized” form, floating-point numbers are unambiguous. The mantissa, m, has a value that is greater than or equal to 1 and less than 2, the only exception being x == 0 , where the mantissa is 0. Example: -4.5 = -1 * 1.125 * 2 2 The first digit of the mantissa is always 1 and need not be stored. The power is stored along with its bias. A bias of 127 applies for float types; thus a power e of a floating- point number is represented internally as e + 127. The memory reserved for the mantissa defines the accuracy, and the memory reserved for the power defines the range of values for the floating-point number. If platform-dependent ranges, such as the length of the mantissa or the smallest or largest value that can be represented, are significant in your programs, you can discover these ranges in the cfloat or climits header files. You can use an instantiation of the numeric_limits class template for the type in question to query platform-dependent ranges by method calls. Bit position (v = signbit) 31 30 23 22 0 v exp m 786 ■ APPENDIX ■ PREPROCESSOR DIRECTIVES The #define Directive The #define directive is used to define symbolic constants and macros. Syntax: #define name[(parameterlist)] [SubstituteText] The preprocessor replaces name or name(parameterlist)with SubstituteText throughout the whole program. If SubstituteText is not stated, the preprocessor will delete the symbolic constant or macro throughout the program code (see also Chapter 7, “Symbolic Constants and Macros”.) Example: #define BUFSIZ 512 // Symbolic constant #define CLS cout << "\033[2J" // Macro #define MAX(a,b) ((a)>(b) ? (a):(b))// Macro The # Operator A macro parameter in a substitute text can be preceded by the # operator (or stringizing token). When the macro is called, the argument is set in quotes, that is, a string constant is formed using the characters of the current argument. Example: #define TITLE(s) "**** " #s " *****" The call cout << TITLE(Catalog); causes the preprocessor to expand the following string "**** " "Catalog" " ****" which is then concatenated to "**** Catalog ****". The characters " and \ are represented by \" and \\ within an argument. Example: #define path(logid,subdir) "\\user\\" #logid "\\bin\\" #cmd With path(Smith, games) the string "\user\Smith\bin\games " is produced. The ## Operator When a macro is defined, character sequences can be concatenated in the substitute text. The past token operator, ##, is used to this effect. When the macro is called, the parameter preceding or following the ## token is replaced by the appropriate argument. Then the token and any leading or trailing white- space character is removed. PREPROCESSOR DIRECTIVES ■ 787 Example: #define debug(n) cout << "x" #n "=" << x ## n Calling debug(1); will generate the statement cout << "x1=" << x1; The arguments of a macro are not parsed for symbolic constants or macros. However, if the result of a concatenation is a symbolic constant or a macro, text replacement is again performed. The #undef Directive To change the definition of a symbolic constant or a macro at program runtime, you must first remove the original definition. To do so, the #undef directive is used. Syntax: #undef name Do not supply the parameter list for parameterized macros. You can then use the #define directive to redefine the macro. Example: #define BUFSIZE 512 . . #undef BUFSIZE #define BUFSIZE 1024 The #include Directive The #include directive copies a file to a program. The #include directive is replaced by the content of the file. Syntax: #include <filename> #include "filename" If the file name is surrounded by < and >, the file will only be looked up in the directo- ries defined by the environment variable (usually INCLUDE). If the file name is stated in quotes the file will also be looked up in the current direc- tory first. The name filename can include a path. In this case the file is only looked up in the directory stated. You can also supply the file name as a symbolic constant. The substitute text must be in quotes or square brackets in this case. 788 ■ APPENDIX . ? (a) :(b))// Macro The # Operator A macro parameter in a substitute text can be preceded by the # operator (or stringizing token). When the macro is called, the argument is set in quotes, that. mantissa is always 1 and need not be stored. The power is stored along with its bias. A bias of 127 applies for float types; thus a power e of a floating- point number is represented internally. ## Operator When a macro is defined, character sequences can be concatenated in the substitute text. The past token operator, ##, is used to this effect. When the macro is called, the parameter