320 likes | 467 Views
Abstract Containers and Logic. Structures, Unions, Bit Manipulation and Enumerations. Outline. Structures Unions Bit Manipulation Enumerations. Abstract Containers and Logic. Previously we introduced the concept and techniques related to the array data structure.
E N D
Abstract Containers and Logic Structures, Unions, Bit Manipulation and Enumerations
Outline • Structures • Unions • Bit Manipulation • Enumerations
Abstract Containers and Logic • Previously we introduced the concept and techniques related to the array data structure. • A collection of identical data types • Permits use of operations defined on the data types • Allocated (logically) contiguous locations in RAM • Permits use of relative offset to provide direct access to elements, based on the properties of the data types • This raises the question: Can one create other kinds of (abstract) data structures, and types, in the C language? • The answer is Yes !
Abstract Containers - struct • First we consider an abstract data structure that • Contains different data types • Permits use of operations appropriate to each type • Allocates memory in a way that supports direct access to elements • Supports user defined abstract data types and containers • The C language struct will provide what we need • Quick example of what one struct looks like: • struct {int ID ; // ID for this circle float XC ; // Centre X coordinate float YC ; // Center Y coordinatefloat Radius ; // Radius of circle char Colour ; // Colour of circle} aCircle ; // Name of this circle data structure
Abstract Containers - struct • It is up to programmers to design and define the internal fields within a struct. • Keep in mind that this is a logical framework that supports being able to refer to a field within a struct • struct{ int ID ; // ID for this circle float XC ; // Centre X coordinate float YC ; // Center Y coordinate float Radius ; // Radius of circle char Colour ; // Colour of circle} aCircle ; // Name of this circle data container • Referencing struct fields is based on hierarchical access, using the dot ‘.’ operator as a binary operator connecting the name of the struct container and one of its subfields. • This is demonstrated by the initialization assignments • aCircle.ID = 1 ;aCircle.Radius = 1.0 ;aCircle.Colour = ‘R’ ; Structure “child” Structure “parent”
Abstract Containers - struct • The actual organization and allocation of memory (RAM) to each field, and to the struct as a whole, may not follow the conceptual definition. • struct{int ID ; float XC ; float YC ; float Radius ; char Colour ; } aCircle; A hole (leak) in memory
Abstract Containers - struct • There is a severe limitation with respect to the example • struct { int ID ; // ID for this circle float XC ; // Centre X coordinate float YC ; // Center Y coordinate float Radius ; // Radius of circle char Colour ; // Colour of circle} aCircle ; // Name of this circle data structure • We can only define one Circle variable (or a list of variables) • To overcome this, we need to define a structure type tag, hence: • structCircleStruct{ // Add the type tag CircleStructint ID ; // ID for this circle float XC ; // Centre X coordinate float YC ; // Center Y coordinate float Radius ; // Radius of circle char Colour ; // Colour of circle} ; • Now the type of this structure is called structCircleStruct
Abstract Containers - struct • Using this user defined data type, one can now define multiple variables of identical (ie. compatible) data type. • structCircleStruct{ int ID ; // ID for this circle float XC ; // Centre X coordinate float YC ; // Center Y coordinate float Radius ; // Radius of circle char Colour ; // Colour of circle } ; • Example:structCircleStructaCircle1, aCircle2, AnotherCircle ; • Also very useful, create an alias name for this type (and avoid unnecessary typing) using the typedef compiler directive • typedefstructCircleStructcircleTypeName ; • And then (after defining the struct itself) use:circleTypeName aCircle3, CircleArray[ 100 ], *ptrCircle ;
Abstract Containers - struct • In the following example declarations, note the implications of each data container described, based on • typedefstructCircleStructcircleTypeName ; • Examples:circleTypeName aCircle3 ; // a single structcircleTypeNameCircleArray[ 100 ] ; // an array of struct’scircleTypeName *ptrCircle ; // a pointer to a struct
Abstract Containers - struct • It is also possible to use struct’s hierarchically, as shown in the example: • structobjectType {circleTypeNameaCircle ;rectangleTypeNameaRectangle ;ellipseTypeNameaEllipse ;squareTypeNameaSquare ;structobjectType *ptrCircle ; } aObject ; • Above, we assume that typedef’s have been defined for rectangleTypeName, ellipseTypeName, and squareTypeName. • One particular, important use of struct’s arises when we include one or more pointer fields, including cases where the pointer points to the same struct type • This is called a self-referential data structure, to be discussed later.
NOTE: This example illustrates that structs, like arrays, are passed (implicitly) by reference as addresses of the original data structures. Compare: struct { intaV ; } aStructContainer ; intaArrayContainer [ ] ; Each of these defines a container object, but when passing either as an argument to a function, as in Copy( A, B ) ; It is only relevant that both A and B have common data structure types. With either arrays or structs, one is implicitly passing the address of a base element in the data structure (eg. &aArrayContainer[0], or &aStructContainer when expressed explicitly). Abstract Containers - struct // Prototype void copyCircle( circleTypeName C1, circleTypeName C2 ) ; . . . for( k=0; k<20; k++ )copyCircle( arrCircle[k], aCircle1.ID ); . . . // Definition void copyCircle( circleTypeName C1, circleTypeName C2 ) { C1.ID = C2.ID ; C1.XC = C2.XC ;C1.YC = C2.YC ;C1.Radius = C2.Radius ;C1.Colour = C2.Colour ; return ; } // This approach ensures that correct copying // (ie. assignment) is performed, without any // dependence upon implementation in RAM. • Consider the declaration of two struct’s, as in:structCircleStructaCircle1, aCircle2, arrCircle[20] ; • It is vital to understand the rules and limitations concerning assignment operations and the size of the struct allocation • Recall that the manner of implementation in RAM may not follow the definition • As a consequence, holes may exist. By definition, a hole cannot be referenced, hence cannot be initialized. • Any uninitialized variable may not be assigned to another variable • Examples: • aCircle2 = aCircle1 ; // NOT ALLOWED ! aCircle1.ID = aCircle2.ID // Copy field by field !
Abstract Containers - struct • To emphasize the issue of RAM allocation implementation dependency, consider again • structCircleStruct{ int ID ; // ID for this circle float XC ; // Centre X coordinate float YC ; // Center Y coordinate float Radius ; // Radius of circle char Colour ; // Colour of circle } aCircle; • Note the relationship based on the example abovesizeof( aCircle ) >=sizeof( int ) + 3*sizeof( float ) + sizeof( char ); • Any difference between the left hand side and the right hand side is due to the size of all holes within the struct as it is implemented in RAM. • Can also use the alternative syntax sizeof( structCircleStruct )
Abstract Containers - struct • Note the use of the pointer-to-structure dereferencing operator, -> (a ‘-’ character followed by a ‘>’ character). • This operator avoids confusion using the * dereferencing operator, as shown below: • ptrCard->Value = 10 ; becomes (*ptrCard).Value = 10 ; ptrCard->Suit = ‘H’ ; becomes (*ptrCard).Suit = ‘H’ ; • Note how the parentheses must surround the *ptrCard reference! • Now we return to deal with pointers to struct’s • Consider the statements • #typedefstruct Card Card_t ;struct { int Value ; char Suit ; } Card ;Card_t Hand[52] ;Card_t*ptrCard ; . . .ptrCard = &Hand[0] ; // refer to the first card in the hand Hand[0].Value = 1 ; // deal an Ace Hand[0].Suit = ‘S’ ; // make it a Spade (‘S’, ‘H’, ‘D’, ‘C’) Hand[1] = { 3, ‘D’ } ; // Deal next card as a 3 of Diamonds . . .ptrCard->Value = 10 ; // change to a 10 insteadptrCard->Suit = ‘H’ ; // and make it a Heart
Abstract Containers - struct • When a struct contains a field that is a pointer to the same struct type, it is called a self-referential data structure • Example: • #typedefstruct Employee Empl_t ; #typedefstructEmplNodeEmplNode_t ;struct Employee {int ID ; char Fname[20] ; char Lname[30] ; float Wage ; }structEmplNode {Empl_tEmpl ;EmplNode_t * ptrNext ; }EmplNode_t E1, E2 ; • Based on these declarations, we show how an initialization might be achieved E1.Empl = { 2548, “Roger”, “Dodger”, 32.54 } ;E1.ptrNext = &E2 ; E2.Empl = { 2574, “Marjorie”, “Fleischer”, 32.54 } ;E2.ptrNext = NULL ; E1 E2 Empl data Empl data
Abstract Containers and Logic • The C language also provides for another abstract container called union • Contains different data types • Permitting use of operations appropriate to each type • Allocates memory in a way that still supports direct access to elements? • The big thing about unions is that they permit data of differing types to be located within the same memory space allocation (or overlapping, shared memory) • Example: • union TagName {int N ; // First declared field float F ; // Second declared field double D ; // Third declared field } aU ;aU.N = 5 ; // Must initialize using its first declared field type The implementation of a union is not fully standardized. Some of the allocations may overlap in RAM, but it is only guaranteed that Sizeof( union TagName ) >= max( sizeof(int), sizeof(float), sizeof(double) ) in the example shown. Holes may be present within unions.
Abstract Containers and Logic • The unioncontainer is used primarily as a way of saving storage since many variables with different data types can re-use the same (roughly) allocated RAM storage. • This is less important today than years ago due to the dramatic increases in RAM size (typically 4+GB) and lowering of costs ($/MB) • Given the expanding nature and complexity of modern software (a trend that is increasing), however, it is still a good idea to be conservation minded in use of memory resources • There are many abstract problems for which the use of unions provides important opportunities for solution strategies • Most of these are based on the intricacies of data representations • Questions: • How can one acquire a set of data values of known size (# of bits or bytes) without knowing in advance what the types of the data are? • How might one then determine the meaning of the data?
Logic and Sequences • As well as introducing abstract data types and containers we also consider two additional issues • Bit Manipulation • Enumerated data types • Boolean logic • Created by George Boole, further developed by others • Fundamental operations defined on bits, or sets of bits, • AND (&), OR (|), COMPLEMENT (~), and other operators • These allow for manipulation of bits • Enumeration of sequences • Provides for user defined data sequences (eg. days, months) • Elements are assigned integer values in a (closed) range • These allow for conceptual simplification of related programming tasks
Logic and Sequences • George Boole, an English theologian, philosopher and mathematician, introduced the notions (mid 1800’s) that established the foundations for Boolean Set Theory and Boolean Algebra • Boole is considered one of the founders of Computer Science • Boolean Logic is essential for designing and constructing digital computers (von Neumanarchitectures) • Based on notions from Set Theory • Fundamental ideas include • The bit (binary digit) concept as a 2-valued state • Basic operations on bits, including: • AND (set intersection), OR (set union), COMPLEMENT (set negation, sometimes called “NOT”)
Logic and Sequences • Boolean concepts and operations in detail • Bits are represented by the values 0 or 1 only • 0 represents FALSE • 1 represents TRUE • Basic operations on bits: • & - AND (set intersection) • | - inclusive OR (set union) • ^ - exclusive OR (set distinction) • ~ - Complement (set negation)
Logic and Sequences The term bitwise operation refers to the fact that the logic operation is carried on the bits in each corresponding position, independentof other bits. • Performing the same operations on sets of bits are referred to as bitwise operations. • Assume two ordered sets of 4 bits, A and B • Specifically: A = 1100 and B = 1010 • Examples: • A & B: A 1100 B 1010 A&B 1000 • A | B:A 1100 A^B: A 1100B 1010 B 1010A|B 1110 A^B 0110 • ~A: A 1100 ~A 0011 • Do not confuse with the relational NOT ‘!’ operator
Logic and Sequences • Some interesting interpretive patterns arise for the & and | operators with certain argument values. • Examples: (A0 = 00, A1 = 11, B = 10) • Using & to probe or reset bit values • A1 & B :A1&B = 11 & 10 = 10 = B • A0 & B :A0&B = 00 & 10 = 00 All bits reset to 0 • Using | to probe or setbit values • A1 | B :A1|B = 11 | 10 = 11 All bits set to 1 • A0 | B :A0|B = 00 | 10 = 10 = B • Using ~ as a bit toggle • ~ A1 : ~B = ~ 10 = 01 Toggle each bit • Using ^ to toggle bit values • A1 ^ B :A1^B = 11 ^ 10 = 01 Toggle each bit • A0 ^ B :A0^B = 00 ^ 10 = 10 = B No effect
Logic and Sequences Another interesting property of ^ (xor) is illustrated in the example B 0001 1101 0001 0111 A 0111 1010 1010 0001 A^B 0110 0111 1011 0110 A 0111 1010 1010 0001 A^(A^B) 0001 1101 0001 0111 By inspection, note that the final answer has the same value as B, hence: B == A^(A^B). This has application in computer graphics (especially in games) where a mask, A, can be applied once to change a region, B, of pixel values (a portion of the game character), then the mask is reapplied (at a later time) to obtain the original bit values (ie. the background scene B). Such sets of bit masks for character rendering (supporting character movement within a scene as well) are often referred to as sprites. • An interesting fact about XOR. • Assume two ordered sets of 4 bits, A and B • Recall that A^B: A 1100 B 1010 A^B 0110 • Now consider the sequence of statements A = A ^ B ; B = A ^ B ; A = A ^ B ; • What happens to A and B? Do a trace. • A = A ^ B ; // A = 0110 B = 1010 • B = A ^ B ; // A = 0110 B = 1100 • A = A ^ B ; // A = 1010 B = 1100 • Note that A and B have been swapped !! With no temporary !! • This result is fully general for bit strings of arbitrary length.
Logic and Sequences • Boolean Logic also admits several additional operators • NAND (NOT AND) • A nand B == not ( A and B ) In C: ~(A & B) • NOR (NOT OR) • A nor B == not ( A or B ) In C: ~(A | B) • XNOR (selective OR) • A xnorB == (A and B) or ((not A) and (not B)) In C: ~(A ^ B) • These are used in hardware circuit design • NAND and NOR gates are considered the fundamental units of manufacture in many circuits • Transistor logic is often based on these operations • All other operators can be expressed in terms of just one of NAND or NOR (try this out on your own!) • They can also be very useful in programming • C does not provide standard library support, but these are easy to program functions for.
One simple test of this logic is to verify that for every N (at least within a range of values), there correspond two signed binary representations X = | N | and Y = - | N | where the minus sign refers to arithmetic negation (different from complementation!) and we don’t need to consider N=0. For mathematical consistency, it must follow that X + Y == 0 To see that this is the case, note that : X + Y | N | + ( ~ | N | ) + 1 {111….1111} + 1 {000….0000} + throwaway bit Note that the last step results in the required, computed value of zero. However, one must deal with the “overflow” bit 1 and this is handled easily by the hardware logic, literally as a throwaway bit. Logic and Sequences • Before proceeding, a quick aside on representations of integer data, using bits. • The unsigned intdata type has a numeric representation starting at 0 and increasing in steps of 1. In base-2, using an 8-bit container size • 0 00000000 1 00000001 • 10 00001010 65 00100001 • 100 00110100 255 11111111 • The signed binary type int provides for both positive and negative integer numbers. Assume an arbitrary integer, N, and we’ll call the machine representation X. • X is formally defined (using logic) by: • X = | N | if N>=0 , X = ~| N | + 1 if N < 0 • For N = 1, X = {000….0001} while for N = -1, X = {111….1111}
Logic and Sequences • Another type of bit manipulation involves shifting of bit positions within a container. • We will focus on shifting with integer types, both unsigned (logical shift) and signed binary (arithmetic shift) representations • Shifting is done differently on each representation • BASICS: • Shift operators have two forms: • Left shift: << Right shift: >> • Examples: (Assume A = 00011010) A = A << 3 ; // A = 11010000 A = A >> 3 ; // A = 00000011 A = A << 8 ; // A = 00000000 • Unsigned integers are easy to deal with • When left shifting, high order bits are shifted out of the container and henceforth ignored – they become lost bits! Zero bits are inserted into the low order positions. • Right shifting brings in zero bits into the high order positions and low order bits are lost. Note that shifting to the left by 1 is equivalent to multiplying by 2. Thus, shifting left by K bits is equivalent to multiplying by pow(2,K). Shifting to the right is equivalent to division by 2 (to some power).
Logic and Sequences • We must be a bit more careful dealing with signed binary (arithmetic shift) representations • The semantic context of negative numbers must be preserved • Must be careful to interpret some results • Examples (independent statements): • Assume A = 00011010 (decimal 28)A = A << 3 ; // A = 11010000 negative ? A = A >> 3 ; // A = 00000011 3 A = A << 8 ; // A = 00000000 0 • Assume A = 11100010 (decimal -30)A = A << 3 ; // A = 00010000 positive ? A = A >> 3 ; // A = 11111100 4 A = A << 8 ; // A = 00000000 0 A = A >> 8 ; // A = 11111111 -1 • In each example above, one must be careful in how the result is interpreted – does it make sense in the context of the operation, and the application program?
Logic and Sequences • An enumerated constant is a user-defined specification of a range of values, represented symbolically, and adapted to specific programming problems. • Defined by the C language statementenumeName { idlist } ; • Examples: • enum Workday { MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY }; • enum Colour { Red, Green, Blue, Violet }; • Each of the symbols above are assigned default values by the compiler, beginning with the first symbol (usually 0) and then incrementing by 1 the value of each successive symbol in the list • This can be modified by programmers • enum Colour { Red=1, Green, Blue, Violet }; // Start at 1 • enum Colour { Red=1, Green=3, Blue=4, Violet=2 }; // Redefine ordering • Can check the values assigned to each symbol using a printf() statement.
Logic and Sequences • Enumerated lists provide a self-documenting approach to useful symbols. • Consider the following • enum Colour { Red=1, Green, Blue, Violet }; // Start at 1 • char ColText [ 5 ] [10 ] = { “ERROR”, “Red”, “Green”, “Blue”, “Violet” } ; • char * ptrColour ;ptrColour = &ColText [ Green ] ; // Using enumerated constantprintf( “The colour is %s\n”, ptrColour ) ; // outputs the string “Green” • It is the ability to write code using common, intuitive terminology that makes the job easier for programmers • Working with numbers and pointers can be confusing in simple applications
Example: Bit Fields and Masks • Problem: Consider a 16-bit string that contains sub-strings, each of which carries a different interpretation as data. • Such encodings are used in many applications, such as network protocol header strings • We consider the case of graphics device control over pixel colour and intensity. For a 3-colour scheme (eg. RGB, CYM) with variable pixel intensity at 2 levels (On/Off), and each colour can be varied on a 32 level intensity scale giving a colour mix of pow(2,15) different colours (32,768 in decimal). • Bit string and interpretation of fields using a unsigned short int container (16 bits). • Toggle ON/OFF field <1 bit> :: Values 1/0 • Red colour intensity field <5 bits>:: Value range 0..31 • Greencolour intensity field <5 bits>:: Value range 0..31 • Bluecolour intensity field <5 bits>:: Value range 0..31 • How do we access each bit field to perform data processing? 1 0 0 1 0 1 0 0 0 1 0 1 1 1 0 1
Example: Bit Fields and Masks • How do we access each bit field to perform data processing? • unsigned short int Pixel ; // 16 bits with value above • Define bit masksunsigned intBlueMask = 31 ; // 0000000000011111 unsigned intRedMask, GreenMask, OnOffMask = 1 ; short int R, B, G ;OnOffMask <<= 15 ; // Shift bit to On/Off field positionGreenMask= BlueMask <<= 5 ; // Copy and shift bits to Green positionsRedMask = BlueMask <<= 10 ; // Copy and shift bits to Red positions • Toggle Pixel OFF, then back ON Pixel ^= OnOffMask ; // Turn OFF Pixel ^= OnOffMask ; // and back ON again • Increase intensity of Red by 3 levels R = Pixel & RedMask ; // Copy and reset all bits except Red field bits R >>= 10 ; // Shift into lower order positions (now a 5-bit unsigned integer) R += 3 ; // Modify Red intensity value as specified R <<= 10 ; // Shift back into Red positions Pixel = Pixel & (~ RedMask) ; // Reset Red field bits to 0 Pixel |= R ; // Insert updated Red field bits 1 0 0 1 0 1 0 0 0 1 0 1 1 1 0 1 These examples barely serve to illustrate the many possibilities that may arise, but they demonstrate how one approaches (a) access and extraction of bits, (b) modification and insertion of bits. Each application problem requires its own logic.
Summary Concepts of Abstract Data Types and Logic: Structures, Unions, Bit Manipulation and Enumerations
Topic Summary • Structures • Unions • Bit Manipulation • Enumerated constants • Reading – Chapter 10 • Review all sections and end of chapter problems. • Reading – Chapter 11: File Processing • Moving beyond RAM to include data on persistent storage in the file system. Practice and experiment !