COS 333: Chapter 11, Part 1

Willem van Heerden
1 Nov 202141:47

Summary

TLDRThis lecture delves into abstract data types (ADTs) and encapsulation, fundamental concepts in programming languages. It explains abstraction as a simplified representation focusing on significant attributes, integral to high-level languages that hide lower-level details. ADTs are user-defined, grouped in syntactic units with hidden representations, ensuring data integrity and simplifying program organization. The lecture also discusses design issues for ADT support in languages, using C++ as an example to illustrate class-based encapsulation, constructors, destructors, and the importance of separating interface from implementation.

Takeaways

  • 📚 The lecture introduces the concepts of Abstract Data Types (ADTs) and encapsulation within the context of programming languages.
  • 🎯 Abstraction in programming is about providing a summarized view of an entity that includes only the most significant attributes, hiding the lower-level details.
  • 🏛️ High-level programming languages have evolved to offer varying degrees of abstraction, simplifying the implementation of complex systems without exposing the intricacies of lower-level operations.
  • 🔐 ADTs are user-defined constructs that encapsulate data and operations within a single syntactic unit, ensuring data integrity and preventing external code from directly accessing the data.
  • 🔑 Two key conditions for ADTs are: 1) the representation and operations must be defined within a single unit, and 2) the representation must be hidden from the program units that use these objects.
  • 🛠️ The advantages of grouping related ADT elements within a single syntactic unit include better organization, increased modifiability, and the ability to compile each ADT separately, improving efficiency.
  • 🔍 Hiding the ADT representation from user code enhances reliability, as changes to the internal workings do not affect the code that uses the ADT, and reduces the likelihood of naming conflicts.
  • 📝 A programming language must provide a syntactic unit for encapsulating ADTs, a method for making essential parts of an ADT visible to clients while hiding its representation, and support for access controls to regulate visibility.
  • 🛑 Design issues for ADTs in programming languages include the form of the interface container, the parameterization of ADTs, access controls, and whether the ADT specification is separate from its implementation.
  • 💻 The lecture provides examples of how C++ supports ADTs through classes, access control specifiers, constructors, destructors, and friend functions, demonstrating how these features address the design issues discussed.
  • 📁 The implementation of ADTs in C++ can be separated into a header file for declarations and an implementation file for definitions, allowing for the hiding of object representation from the client and better organization of code.

Q & A

  • What are the main topics discussed in the lectures on Chapter 11?

    -The main topics discussed are abstract data types (ADTs), encapsulation, and the implementation of ADTs in programming languages, along with design issues associated with ADTs.

  • What is the general concept of abstraction in programming languages?

    -Abstraction in programming is a view or representation of an entity that includes only the most significant attributes, essentially providing a summarized view that contains only the most important details.

  • How do high-level programming languages provide abstraction?

    -High-level programming languages provide abstraction by hiding details of lower-level implementations, allowing programmers to implement complex systems without needing to understand the underlying operations.

  • What are the two conditions that an abstract data type (ADT) must satisfy?

    -An ADT must have its representation and operations defined in a single syntactic unit, and the representation of the objects must be hidden from the program units that use these objects.

  • Why is it advantageous to group everything related to an ADT within the same syntactic unit?

    -Grouping everything related to an ADT within the same syntactic unit allows for better organization, increased modifiability, and the ability to compile each ADT separately, which can be more efficient.

  • What is the purpose of hiding the representation of an ADT from the user code?

    -Hiding the representation of an ADT from the user code increases reliability, as it prevents the user code from depending on the specific representation and allows the internal workings of the ADT to be changed without affecting the user code.

  • What are the advantages of hiding the representation of an ADT in terms of programming?

    -The advantages include increased reliability, as the user code cannot directly access the objects of the ADT; increased simplicity for the programmer, as they only need to know about the interface; and reduced likelihood of name conflicts.

  • What features must a programming language provide to support ADTs?

    -A programming language must provide a syntactic unit to contain the ADT's representation and operations, and a method to make the essential parts of an ADT visible to clients while hiding the actual data representation.

  • What are some design issues to consider when a high-level programming language provides support for ADTs?

    -Design issues include the form of the container for the interface, whether ADTs can be parameterized, the access controls provided, and whether the specification of the ADT is physically separate from its implementation.

  • How does C++ support ADTs?

    -C++ supports ADTs through the use of classes, which encapsulate the data and operations within a single syntactic unit, and provides features like constructors, destructors, access control specifiers, and friend functions to support various aspects of ADT implementation.

  • What is the significance of constructors and destructors in C++ for ADTs?

    -Constructors in C++ are special functions used to initialize the data members of an instance, while destructors are used to clean up after an instance is destroyed, typically by deallocating resources and performing other cleanup operations.

Outlines

00:00

📘 Introduction to Abstract Data Types and Encapsulation

The lecture begins with an introduction to Chapter 11, focusing on abstract data types (ADTs) and encapsulation. The speaker explains that abstraction in programming hides unnecessary details, allowing programmers to work at a higher level without needing to understand lower-level implementations. High-level languages provide varying degrees of abstraction, simplifying complex systems. The lecture will cover the definition of ADTs, their design issues, and examples of how programming languages implement them. The concept of process abstraction through sub-programs is also introduced, setting the stage for a deeper dive into data abstraction in the subsequent lecture.

05:02

🔐 Understanding Abstract Data Types and Their Benefits

This paragraph delves into the specifics of abstract data types (ADTs), outlining the two key conditions they must meet: encapsulation of representation and operations within a single syntactic unit, and the concealment of the representation from users. The benefits of these conditions include organized code, increased modifiability, and the ability to compile ADTs separately, which optimizes the development process by minimizing the need for recompilation. The speaker also discusses the advantages of hiding the ADT's representation, such as enhanced reliability and the prevention of name conflicts, and emphasizes the importance of a clear interface for interaction with the ADT.

10:03

🛠 Language Features for Supporting ADTs

The speaker outlines the essential language features required to support abstract data types, including a syntactic unit for encapsulation and mechanisms for visibility control. The need for a syntactic unit to contain the ADT's type definition and operations is highlighted. Additionally, the paragraph discusses the necessity of making certain parts of an ADT visible to clients while keeping the actual data representation hidden. The importance of sub-program headers for defining supported operations is also emphasized, providing a clear interface for users without exposing the underlying implementation.

15:05

🏗️ Design Considerations for ADT Support in Programming Languages

The paragraph explores various design issues that programming languages must address to effectively support ADTs. These include deciding the form of the container for the ADT's interface, the possibility of parameterizing ADTs, access control mechanisms, and whether the specification of an ADT should be physically separate from its implementation. The discussion provides insight into how different languages might approach these design challenges, affecting how ADTs are structured and utilized within those languages.

20:07

🤖 C++ Implementation of ADTs Through Classes and Constructors

This section examines how C++ supports ADTs using classes, which serve as the encapsulation device. Classes in C++ allow for the sharing of member functions across all instances while maintaining separate data for each object. The use of access control specifiers to achieve information hiding is explained, along with the role of constructors in initializing ADT instances. The paragraph also touches on the concept of destructors for cleaning up resources and the use of friend functions and classes to allow external access to private class members when necessary.

25:07

📚 Implementing a Stack ADT in C++

The speaker provides an example of implementing a stack ADT in C++, illustrating the use of a class to define the stack's representation and operations. The example includes a default constructor, a destructor, and a member function for the push operation. The implementation details are kept within the class, preventing client code from depending on them. The importance of separating the interface from the implementation to enhance encapsulation is emphasized, showcasing a simple yet effective way to utilize ADTs in C++.

30:10

📁 Separating Interface and Implementation in C++

The paragraph discusses a more advanced implementation of the stack ADT in C++, which enforces a separation between the header file and the implementation file. This approach hides the object representation from the client, ensuring that the client relies only on the public interface defined in the header file. The implementation file contains the actual code for the member functions, constructors, and destructors, which are compiled separately. This method promotes better organization and encapsulation, preventing the client from depending on the internal details of the ADT.

35:11

🔚 Conclusion and Preview of the Next Lecture

The final paragraph wraps up the current lecture on abstract data types and encapsulation, summarizing the key points discussed and the examples provided. It also gives a preview of the next lecture, which will continue the exploration of Chapter 11, presumably delving deeper into the implementation and application of ADTs in programming languages.

Mindmap

Keywords

💡Abstract Data Types (ADTs)

Abstract Data Types, or ADTs, are user-defined constructs in programming that encapsulate data and the operations that can be performed on that data into a single syntactic unit. They are central to the video's theme of data abstraction. The script discusses how ADTs must satisfy two conditions: the representation and operations are defined together, and the representation is hidden from the program units that use the objects. An example from the script is the stack ADT, which abstracts the concept of a list with operations like push, pop, and top.

💡Encapsulation

Encapsulation is the mechanism of bundling the data and the methods that operate on the data into a single unit or class. In the context of the video, encapsulation is a key concept for implementing ADTs, as it allows the data representation to be hidden from the outside while exposing only the necessary operations. The script mentions encapsulation in C++ using classes, where member functions are shared, and data members are unique to each instance.

💡Abstraction

Abstraction in general terms is the concept of representing an entity by focusing only on the most significant attributes, omitting the less important details. The video uses abstraction to discuss how high-level programming languages provide a summarized view of complex systems, allowing programmers to work without understanding the underlying low-level details. For example, the script contrasts machine language with high-level languages, showing the latter's abstraction of register and memory operations.

💡Data Abstraction

Data abstraction specifically refers to the process of defining objects in terms of what operations can be performed on them, rather than how the data is stored or manipulated. The video emphasizes data abstraction as a fundamental concept in programming, with ADTs being a primary method for achieving this. The script explains that since the 1980s, most programming languages have supported data abstraction through ADTs.

💡Process Abstraction

Process abstraction is the concept of breaking down code into smaller, manageable units known as sub-programs. The video script uses process abstraction as an example of how programming languages abstract away the details of lower-level implementations. It explains that by using sub-programs, programmers can forget about the internal operations and simply use the functionality provided by these units.

💡Modifiability

Modifiability refers to the ease with which changes can be made to a system. In the context of the video, increased modifiability is an advantage of grouping everything related to an ADT within a single syntactic unit. The script explains that changes to an ADT would be localized to one file, simplifying the maintenance and evolution of the code.

💡Reliability

Reliability in programming is about creating systems that behave predictably and consistently. The video discusses how hiding the representation of ADTs from user code increases reliability, as it prevents the user code from depending on the specific implementation details of the ADT. For instance, changing the internal representation of a list ADT from an array to a linked list would not affect the user code.

💡Name Conflicts

Name conflicts occur when the same name is used for different variables or entities within the same scope, leading to ambiguity or errors. The video script explains that by hiding the representation of ADTs, the likelihood of name conflicts is reduced. This is because the names used for internal data representation are confined within the ADT and do not interfere with names used elsewhere in the program.

💡Constructor

A constructor in object-oriented programming is a special type of method that is called when an object is created. The video script describes constructors in C++ as functions with the same name as the class they belong to, used for initializing the data members of an instance. The script provides an example of a stack class with a default constructor that allocates memory for the stack and sets initial values for its member variables.

💡Destructor

A destructor is a special function in object-oriented programming that is called when an object's lifetime ends. The video script explains that destructors in C++ have the same name as the class with a tilde symbol prefix, and they are used to clean up resources, such as deallocating heap memory that was allocated in a constructor. The script illustrates this with a stack class destructor that deallocates the stack pointer array.

💡Friend Functions

Friend functions in C++ are functions that are granted access to the private and protected members of a class. The video script discusses friend functions as a mechanism to allow certain functions to work with the private data of a class, which is typically inaccessible from outside the class. An example given is a multiplication operation between a matrix and a vector, where the multiplication operator might be defined inside the matrix class, and the vector class would declare this operation as a friend.

Highlights

Introduction to abstract data types (ADTs) and encapsulation, emphasizing their importance in programming languages.

Definition of abstraction in programming, highlighting its role in summarizing entities with significant attributes.

The evolution of high-level programming languages, from FORTRAN to modern languages, focusing on their abstraction capabilities.

Explanation of process abstraction through sub-programs, simplifying complex operations for programmers.

The concept of data abstraction since 1980, illustrating how it is achieved via abstract data types.

Criteria for defining ADTs, including the grouping of related data and operations within a single syntactic unit.

The necessity of hiding the representation of ADT objects from program units to ensure data integrity.

Advantages of organizing related ADT elements within a single syntactic unit, such as better program organization and increased modifiability.

Benefits of hiding ADT representations, including increased reliability and reduced likelihood of name conflicts.

Features required by programming languages to support ADTs, such as syntactic units and methods for visibility control.

Design issues in ADT support, including the form of the container for the interface and parameterization of ADTs.

C++'s approach to ADT support using classes, encapsulating data and member functions, and the role of constructors and destructors.

Implementation of a stack ADT in C, demonstrating the grouping of representation and operations within a class.

Separation of header and implementation files in C++ for ADTs to enhance encapsulation and hide object representation.

Discussion on friend functions and classes in C++, their role in accessing private members across different classes.

Conclusion of the lecture with a preview of the next session, which will continue the discussion on chapter 11 of the textbook.

Transcripts

play00:01

welcome to the first of two lectures on

play00:04

chapter 11 where we will be discussing

play00:07

abstract data types and encapsulation

play00:10

concepts

play00:13

these are the topics that we'll be

play00:14

discussing in today's lecture we'll

play00:17

begin by looking at what abstraction in

play00:20

a general sense is in the context of

play00:23

programming languages

play00:25

we'll then move on to data abstraction

play00:28

specifically we will talk about what

play00:31

abstract data types or adts are

play00:35

then we'll take a look at the various

play00:37

design issues that are associated with

play00:40

abstract data types and then finally

play00:43

we'll begin with our discussion on a

play00:46

couple of programming language examples

play00:49

in terms of how these languages

play00:51

implement abstract data types and how

play00:54

these differ in terms of the design

play00:57

issues that we will discuss

play01:00

in the next lecture we'll also be

play01:02

continuing with these language examples

play01:05

and therefore this will

play01:07

form the main

play01:09

body of the work that we'll be

play01:11

discussing in this lecture and the next

play01:17

now before we can talk about what

play01:18

abstract data types are we first need to

play01:22

define what we are talking about when we

play01:24

discuss abstraction in a general sense

play01:28

so an abstraction is a view or a

play01:31

representation of some sort of entity

play01:33

that includes only the most significant

play01:36

attributes so you can think of an

play01:38

abstraction as a summarized view of

play01:41

something where the summarized view

play01:43

contains only the most important details

play01:47

of the thing that we are trying to

play01:48

represent

play01:50

now abstraction is a very fundamental

play01:52

concept right at the core of programming

play01:55

and computer science in general

play01:57

and if we look at high-level programming

play02:00

languages we can in fact see that they

play02:03

have aimed to provide at least some

play02:05

level of abstraction all the way from

play02:08

the very earliest high-level programming

play02:10

languages like fortran

play02:13

so we can see this in high-level

play02:15

programming languages because they

play02:17

attempt to hide details

play02:20

of lower level

play02:22

implementations which are not necessary

play02:25

for a programmer to understand in order

play02:28

for them to implement a fairly

play02:31

complicated system so for example we can

play02:34

see if we look at machine language

play02:37

compared to a high level programming

play02:40

languages code then the operations that

play02:43

need to be performed on a register and

play02:46

memory level do not need to be performed

play02:49

in the high-level programming language

play02:51

because those operations have been

play02:53

abstracted away and the programmer

play02:55

doesn't need to worry about them

play02:58

now nearly all programming languages

play03:01

support what is referred to as process

play03:03

abstraction and this is provided by

play03:06

means of sub programs so here we are

play03:09

cutting our code up into smaller units

play03:12

and then once we have a unit that

play03:15

contains a series of related operations

play03:18

then we can essentially forget about

play03:21

those operations and simply use the

play03:23

sub-program so for example if we have a

play03:26

sub-program that prints something out to

play03:28

the screen

play03:29

then that essentially will be abstracted

play03:32

away and we don't need to then consider

play03:35

how the mechanics work in terms of

play03:39

actually

play03:40

displaying something to the screen

play03:43

now nearly all programming languages

play03:45

since 1980 support what is referred to

play03:48

as data abstraction

play03:50

and this is achieved by means of

play03:52

abstract data types so process

play03:54

abstraction was discussed in the

play03:57

previous set of lectures for the last

play03:59

chapter

play04:00

and in this lecture and the next lecture

play04:03

we will be focusing on data abstraction

play04:09

so now that we know what an abstraction

play04:11

is we can focus our attention on

play04:14

abstract data types or adts

play04:18

so adts are user-defined constructs and

play04:22

they must satisfy two conditions

play04:26

firstly for every abstract data type the

play04:29

representation of and the operations on

play04:32

objects of this type are defined in a

play04:35

single syntactic unit

play04:38

so what does this mean well it means

play04:40

everything that is related to the adt

play04:43

will be grouped together within a single

play04:46

unit so in other words the data that the

play04:48

adt stores as well as the operations

play04:52

that the adt can perform such as for

play04:55

example search operations or reordering

play04:58

operations are all grouped together

play05:01

within a single unit

play05:04

and what this then implies is that other

play05:07

program units such as functions or

play05:11

methods are allowed to create variables

play05:14

of this defined type so essentially they

play05:18

are then creating a specific instance of

play05:21

an adt

play05:22

and then the data stored within the adt

play05:26

will be as specified in the syntactic

play05:28

unit and also the operations that can be

play05:31

performed on the adt will also be

play05:34

specified within the same syntactic unit

play05:38

now the second condition that an adt

play05:40

must satisfy

play05:42

is that the

play05:44

representation of objects of the type

play05:47

will be hidden from the program units

play05:50

that use these objects

play05:52

so what this means is the only

play05:54

operations that are possible for the adt

play05:57

are those that are provided in the types

play06:00

definition

play06:02

in other words what we are saying is

play06:04

that the data stored within the adt

play06:07

cannot be directly accessed by external

play06:10

entities such as functions or methods

play06:14

it is necessary to manipulate this data

play06:18

by means of the operations defined by

play06:20

the adt and these operations form an

play06:23

interface and they effectively then

play06:26

filter whatever interactions are

play06:29

performed

play06:30

on the abstract data type and therefore

play06:33

ensure that the data is then always in a

play06:36

correct congruent state

play06:39

and that data corruption does not occur

play06:44

so let's look in more detail at what the

play06:47

advantages are of the first condition

play06:51

associated with abstract data types in

play06:54

other words that everything related to

play06:56

an abstract data type will be grouped

play06:59

together within the same syntactic unit

play07:02

so the first advantage is fairly

play07:04

straightforward it allows for our

play07:07

programs to be better organized and this

play07:10

is of course because everything related

play07:13

to an abstract data type will be grouped

play07:16

together within the same syntactic unit

play07:19

which will typically be contained within

play07:22

its own file so this means there's only

play07:24

one place that we need to look

play07:28

for any code related to that abstract

play07:31

data type

play07:32

related to this we also have increased

play07:35

modifiability and this is again because

play07:38

everything associated with the abstract

play07:40

data type will be grouped together so if

play07:43

we need to make any changes then we know

play07:45

that these changes will be localized to

play07:48

one particular file that contains this

play07:51

single syntactic unit

play07:54

and then finally each adt can generally

play07:58

be compiled separately in its own file

play08:02

and what this means is that there is no

play08:05

need to recompile the entire system and

play08:08

you should be familiar with this from

play08:10

the concept of make files in c plus and

play08:14

java

play08:15

so essentially if we look at a make file

play08:18

this allows us to localize what needs to

play08:21

be recompiled whenever a change is made

play08:24

so if we modify for example a class that

play08:27

is defined in one file then we only need

play08:30

to recompile that file as well as other

play08:33

files that then

play08:35

depend on that file and the abstract

play08:38

data type defined within it

play08:41

so

play08:41

we then only need to compile a subset of

play08:44

the files related to our larger project

play08:48

and not everything as a whole if we

play08:51

didn't have these separate syntactic

play08:54

units we wouldn't be able to separate

play08:56

our adt's out into secret files and

play08:59

therefore every time there's a minor

play09:01

change we'd have to recompile the entire

play09:03

system from scratch

play09:08

so what is the advantage of the second

play09:10

condition associated with all abstract

play09:13

data types in other words that the

play09:16

representation of an abstract data type

play09:19

is hidden from the user code

play09:22

so the first big advantage is an

play09:24

increase in reliability and this is

play09:26

because the user code cannot directly

play09:29

access objects of the abstract data type

play09:32

or depend on the representation of these

play09:36

objects

play09:37

and this allows the inner workings of

play09:39

the abstract data types to be changed

play09:42

without affecting user code

play09:45

so let's imagine that we are defining an

play09:48

abstract data type that represents a

play09:50

list and we decide initially that our

play09:53

abstract data type is going to be

play09:55

represented using an array

play09:58

however later on we decide that we will

play10:00

replace the array representation and

play10:03

instead use a linked list representation

play10:07

now if the representation of this list

play10:11

adt

play10:12

were exposed to the user code then the

play10:15

user code could depend on the fact that

play10:18

an array is being used for the

play10:20

representation so they may depend on it

play10:23

in terms of making certain operations

play10:26

more efficient for example

play10:29

if the inner representation is hidden

play10:32

from the user code it means that the

play10:34

user code

play10:35

should not be aware of whether an array

play10:39

or a linked list or some other kind of

play10:41

representation is being used so all that

play10:44

they do is they use the abstract data

play10:46

type through its interface in terms of

play10:49

the operations that are supported for it

play10:51

and they cannot grow to depend on the

play10:54

inner representation of the actual data

play10:57

within the adt

play11:00

the second big advantage is that a

play11:03

programmer will know about list code and

play11:06

variables so this just simply means that

play11:09

a programmer who uses an abstract data

play11:11

type

play11:12

needs to know about fewer things and

play11:15

this relates back to our discussion on

play11:18

why abstraction in general is a good

play11:21

thing

play11:22

the third big advantage is that name

play11:25

conflicts are less likely to occur

play11:29

so this relates to the fact that we of

play11:32

course need to name the data variables

play11:35

that are used to represent the abstract

play11:38

data type somehow

play11:40

and if these were exposed then we would

play11:42

have to be sure that we don't use

play11:44

variables with the same name because

play11:47

then we would get naming conflicts that

play11:49

would occur now if these representations

play11:52

these variables that represent the data

play11:55

within an adt are hidden in the adt it

play11:58

means that that name is then limited to

play12:01

the adt and therefore we could use the

play12:03

same name somewhere else in our program

play12:06

without worrying about it conflicting

play12:09

with the variables defined within the

play12:11

abstract data type

play12:15

so what features must a programming

play12:18

language provide in order to be able to

play12:20

support adts

play12:22

well first and foremost and what should

play12:24

be obvious from our previous discussion

play12:28

is that we need a syntactic unit and

play12:31

this is of course because all adt's must

play12:34

use a single syntactic unit to contain

play12:37

their representation and their

play12:39

operations

play12:40

so the syntactic unit is then

play12:42

responsible for encapsulating the adt's

play12:45

type definition

play12:48

we also require a method to make the

play12:50

essential parts of an adt visible to a

play12:53

client or a user

play12:55

while still hiding the actual definition

play12:58

of how the adt is represented in terms

play13:02

of actual data

play13:04

so the essential parts then of the adt

play13:06

that should be visible will be of course

play13:08

the type name for the adt because we

play13:12

need to know what the type name is in

play13:14

order to be able to create objects of

play13:16

that particular adt

play13:18

and then secondly we need sub-program

play13:21

headers and these sub-program headers

play13:24

will be representations of what

play13:26

operations are supported by the abstract

play13:30

data type so this will be then by means

play13:33

of member functions or methods depending

play13:36

on the programming language that we are

play13:37

using which will be then defined for a

play13:41

specific abstract data type the sub

play13:44

program headers as we saw in our

play13:47

discussion on the previous chapter only

play13:49

need to include the details related to

play13:52

the name return types and parameters

play13:55

for these sub-programs and they don't

play13:58

need to include the actual

play14:00

implementation so generally the

play14:02

implementation of these operations will

play14:04

also be hidden within our adt

play14:11

now there are of course several design

play14:13

issues that need to be considered if a

play14:16

high-level programming language is to

play14:18

provide support for adts

play14:20

the first design issue is what is the

play14:23

form of the container for the interface

play14:26

of the type

play14:28

so when we're talking about the

play14:30

interface to an adt we're talking about

play14:34

the public interface that is exposed to

play14:37

client or user code

play14:40

so the public interface thing is the set

play14:43

of operations that are publicly visible

play14:46

to client or user code where that client

play14:49

or user code can actually then invoke

play14:52

those operations on the adt in question

play14:57

so the

play14:59

interface then needs to be contained

play15:02

within some sort of unit

play15:04

and this would be then our container

play15:07

which also typically specifies the name

play15:10

of the adt's type

play15:12

and we need to consider what form this

play15:15

container takes on so do we use for

play15:18

instance a class to represent this

play15:20

container or do we use some other kind

play15:22

of construct

play15:25

the second design issue is can abstract

play15:28

data types be parameterized

play15:31

so we spoke about parameterized

play15:34

sub-programs in the previous chapter

play15:37

where we used the examples of template

play15:41

functions in c plus and generic methods

play15:44

in java in a similar fashion we can also

play15:47

create parameterized abstract data types

play15:50

for instance in c plus we can create

play15:53

template classes and in java we can

play15:56

create generic classes

play15:59

then the third design issue is what

play16:02

access controls are provided access

play16:04

controls

play16:06

allow us to specify

play16:08

the visibility of certain parts of our

play16:11

abstract data type to client or user

play16:15

code so here we're talking about things

play16:18

like public private and protected access

play16:22

controls

play16:23

and then finally the last design issue

play16:26

is is the specification of the adt

play16:30

physically separate from its

play16:32

implementation

play16:34

so some high-level programming languages

play16:36

such as c plus plus doing for say

play16:38

separation where in c plus we have a

play16:42

header file which contains the public

play16:44

interface

play16:46

as well as part of the representation

play16:48

and then we have a secret implementation

play16:51

file that contains the implementation

play16:54

of our various operations within our

play16:57

abstract data type some other

play17:00

programming languages don't enforce this

play17:02

kind of separation so for instance in

play17:04

java there's no separation

play17:07

between the specification of the type

play17:10

and the implementation all of this falls

play17:12

within the same class file

play17:18

at this point we'll move on to some

play17:21

examples of programming languages that

play17:23

implement adts

play17:25

and we will through this discussion then

play17:28

look at different ways that the design

play17:31

issues that we discussed on the previous

play17:32

slide can be addressed in a high level

play17:35

programming language

play17:37

so the first language example we'll look

play17:39

at is c plus and in c plus plus adt

play17:43

support is based firstly on struct types

play17:46

within the earlier c programming

play17:49

language and we discussed structs

play17:51

earlier on in this course

play17:54

secondly adt support is also inspired by

play17:57

classes within simulated 67

play18:02

so the encapsulation device in c plus

play18:05

for an adt is the class

play18:08

and the class is then used to define an

play18:12

abstract data type

play18:14

now all class instances of a class then

play18:18

share a single copy of the member

play18:21

functions so the member functions are

play18:23

the functions or operations that belong

play18:26

to a particular adt

play18:29

secondly we also then have data that is

play18:33

represented within an adt

play18:35

and this is represented by data members

play18:38

which are essentially in the variables

play18:40

within a class

play18:42

and so each instance of a class then has

play18:45

its own copy of the classes data members

play18:50

so what this means is then each instance

play18:52

of a class that is created has its own

play18:56

separate data which doesn't interfere

play18:58

with any other objects of the same class

play19:04

now we saw earlier on in this course

play19:07

that c plus plus allows for variables to

play19:10

be static stack dynamic or heap dynamic

play19:14

so instances of a class in other words

play19:17

objects of a class behave in exactly the

play19:20

same way and therefore instances of a

play19:22

class can also be static or stack

play19:25

dynamic or alternatively heap dynamic if

play19:28

we use new to specifically allocate

play19:32

space for the object on the heap

play19:37

now in c plus information hiding is

play19:40

achieved by means of access control

play19:44

specifiers

play19:46

and these are private public or

play19:48

protected clauses

play19:51

so private clauses specify entities

play19:54

within a class that are hidden in other

play19:57

words they are not accessible to client

play20:00

or user code they are only accessible

play20:04

within the class itself

play20:06

public clauses on the other hand specify

play20:09

the interface entities within our adt so

play20:13

in other words the entities that are

play20:16

directly accessible to client or user

play20:19

code

play20:20

and then finally we have protected

play20:23

clauses and these come into play

play20:26

when inheritance is implemented

play20:29

for a particular class now we won't

play20:33

discuss protected clauses in more detail

play20:36

at this point

play20:38

we will discuss them further in the next

play20:40

chapter which deals with object oriented

play20:43

programming

play20:47

c plus plus has what are referred to as

play20:50

constructors which are special functions

play20:53

where their name is the same as the

play20:56

class name for which they are defined

play20:59

and constructors are responsible for

play21:01

initializing the data members of an

play21:04

instance in other words the

play21:07

representation of the adt in question

play21:11

now constructors do not create objects

play21:14

the runtime system of c plus is

play21:17

responsible for actually creating the

play21:19

object however constructors are

play21:22

implicitly called when an instance is

play21:25

created now it is also possible to

play21:28

explicitly call a constructor and one

play21:30

would do this when creating a heap

play21:33

allocated instance of the adt

play21:36

using new for example

play21:39

now if part of the object is heap

play21:42

dynamic

play21:43

so this would then occur in a situation

play21:47

where we have a pointer defined within

play21:51

our class

play21:52

and this pointer would then be allocated

play21:54

using new then the allocation of this

play21:57

pointer would take place within the

play22:00

constructor

play22:01

so this is how heap dynamic storage is

play22:05

allocated inside objects of a particular

play22:08

adt

play22:09

now constructors can also include

play22:11

parameters and this allows us then to

play22:15

parameterize objects

play22:18

so

play22:18

with

play22:19

constructors that have parameters we

play22:22

would typically then send through

play22:25

initial values for the data that would

play22:28

be stored within the adt object

play22:34

now c plus allows the definition of

play22:37

multiple constructors as long as each

play22:39

constructor has a different set of

play22:42

parameters

play22:43

in addition to this however c plus also

play22:46

provides another special function known

play22:49

as a destructor

play22:51

a destructor has the same name as the

play22:53

class within which it resides however

play22:56

this name is preceded by a tilde symbol

play23:00

now the function of a d structure is

play23:03

essentially the opposite of a

play23:05

constructor where a construct is used to

play23:08

allocate resources for a new object

play23:11

of a particular adt a destructor is used

play23:15

to clean up after an instance of the adt

play23:19

is destroyed

play23:20

so usually this involves reclaiming heap

play23:23

storage in other words where we would

play23:25

allocate heap storage using new in a

play23:28

constructor we would de-allocate it

play23:30

using a delete in the destructor

play23:34

additionally however these structures

play23:36

may also perform other cleanup

play23:37

operations for example if an object of a

play23:41

particular adt

play23:43

maintains open files then the destructor

play23:46

may be responsible for closing the

play23:48

handles of these files

play23:51

now a destructor is implicitly called

play23:54

when the object's lifetime ends so for

play23:58

example if we have a local object of a

play24:00

particular adt

play24:02

and it goes out of scope then the

play24:05

destructor would implicitly be called

play24:08

however it is also possible to

play24:10

explicitly call a destructor and we can

play24:13

only do this with heap allocated

play24:16

objects of a particular adt in which

play24:19

case we would use the delete special

play24:22

word and this would then de-allocate

play24:25

dynamically allocated storage

play24:28

and we would of course have to do this

play24:30

through a pointer to an object

play24:35

c plus also provides support for friend

play24:38

functions and friend classes

play24:41

now friend functions and friend classes

play24:44

allow these functions or classes to

play24:47

access private members of a different

play24:50

class

play24:51

now friendship is granted in c plus so

play24:54

what this means is if i have a class

play24:58

called student for example then it isn't

play25:01

possible for a function or another class

play25:04

to claim friendship of the student class

play25:07

the student class must within itself

play25:09

define which functions or classes are

play25:13

friends of the student class

play25:16

any friend then whether it is a function

play25:18

or a class can then directly access the

play25:21

student classes private

play25:24

members

play25:25

now

play25:26

generally speaking we will avoid

play25:29

defining friend classes and this is

play25:32

because it drastically increases the

play25:35

exposure of private data

play25:37

to classes that are not defined for the

play25:41

specific adt

play25:43

however sometimes friend functions are

play25:46

necessary and they are typically used

play25:49

where an operation is defined in which

play25:51

the operator must work with multiple

play25:53

classes so for example we may be

play25:56

multiplying a matrix with a vector we

play25:59

may then choose for the multiplication

play26:01

operator to be defined inside the matrix

play26:04

class

play26:06

and then the vector class would define

play26:08

this multiplication operation as a

play26:11

friend operation this would then allow

play26:14

the operator to both access the private

play26:18

members contained within the matrix

play26:21

clause as well as the vector class and

play26:24

this is typically necessary because

play26:26

multiplication works with the data

play26:28

contained within each of those classes

play26:34

next we'll look at a few examples of how

play26:37

a stack adt can be implemented in c

play26:42

the example on the next slide is a

play26:44

relatively simple example of a stack

play26:47

class

play26:48

and here the representation and the

play26:51

implementation of the class are

play26:53

represented within a single unit so what

play26:56

this means is we have the representation

play27:00

of our class in other words the member

play27:03

variables

play27:04

as well as the prototypes for all of our

play27:07

member functions but then also the

play27:09

implementations of these member

play27:11

functions and these will all appear

play27:14

within the same unit

play27:17

now this is not an ideal approach to use

play27:20

the reason is that the client code will

play27:23

be able to see the full implementation

play27:26

of the stack class

play27:28

so this means that the client may then

play27:32

begin depending on the implementation

play27:35

details and even if they don't depend on

play27:37

the implementation details they may get

play27:40

confused by these details

play27:43

now the private data members are

play27:45

provided at the top of the stack class

play27:48

and these are not directly accessible to

play27:51

client codes in other words client code

play27:53

must go through the public interface of

play27:57

the class

play27:58

there's also one default constructor and

play28:01

one destructor and then we also show the

play28:04

implementation of one member function

play28:07

which is for the push operation

play28:13

on this slide we have the implementation

play28:15

of our first basic stack class adt

play28:19

example

play28:21

over here you can see we are defining a

play28:22

class and the name of the class is stack

play28:26

generally the convention that we follow

play28:28

is that class names start with an upper

play28:30

case later

play28:31

we can also see that the class is

play28:33

delimited by an opening brace as well as

play28:36

a closing brace at the end

play28:39

and this indicates the beginning and the

play28:41

end of our syntactic unit within which

play28:45

our stack adt

play28:47

is located

play28:49

next we have a private label as you can

play28:51

see over here and this means that

play28:53

everything under the private label is

play28:55

then private to the stack class so in

play28:58

other words it can only be accessed by

play29:01

the stack class and not by anything

play29:04

external to the stack class such as

play29:07

client code

play29:09

so under the private section we have

play29:12

listed a number of member variables and

play29:15

these represent the data or

play29:17

alternatively the representation

play29:20

of our stack adt so we can see that we

play29:22

have three variables here firstly we

play29:24

have stack ptr

play29:26

which is an integer pointer and this is

play29:29

a pointer to an array that will be

play29:32

dynamically allocated on the heap

play29:35

next we have maxlin which is an integer

play29:38

member variable and this represents the

play29:41

largest possible subscript value within

play29:44

our stack ptr array and then finally we

play29:48

have top sub which is also an integer

play29:51

value

play29:52

and top sub indicates the subscript at

play29:56

the top of our stack

play29:59

next we have our public label over here

play30:01

and this indicates that everything under

play30:03

the public label is public

play30:06

and therefore can be accessed externally

play30:10

from outside of the stack class

play30:13

so in other words client code can then

play30:16

rely on these public member functions

play30:20

so the first member function that we

play30:21

have is our special function this is a

play30:24

stack constructor we can see that it has

play30:27

no return and it is named after the

play30:29

class that it is contained in namely the

play30:32

stack class

play30:33

you can also see that there are no

play30:35

parameters so this indicates that this

play30:37

is the default constructor

play30:40

so inside the body of our default

play30:42

constructor we then set initial values

play30:46

for our member variables

play30:48

so stack ptr is initialized using new

play30:51

which allocates memory for it on the

play30:54

heap and we are allocating a new integer

play30:56

array containing 100 integer values

play31:00

maxlin is initialized to a value of 99

play31:04

because that is the largest valid

play31:06

subscript within the stack ptr array and

play31:09

top sub is set to negative 1 which

play31:12

indicates that there are no values

play31:15

contained within the stack ptr array

play31:18

here we have our d structure we know

play31:20

this is a destructor because of its name

play31:22

which contains the name of the class

play31:25

that it is defined for but also

play31:28

additionally has this tilde symbol

play31:31

we don't have any parameters for our

play31:34

stack destructor

play31:36

inside our destructor all that we are

play31:38

doing is deleting the stack ptr array so

play31:42

we can see that it is allocated using

play31:44

new in the constructor and then

play31:46

de-allocated using delete in the

play31:48

destructor

play31:50

we also then have our final

play31:52

implementation that we're showing over

play31:54

here which is for a member function so

play31:58

this is publicly available to the client

play32:01

code and this is a member function

play32:04

called push it returns nothing because

play32:06

its return type is void and it receives

play32:09

a single parameter called number which

play32:12

is an integer value

play32:15

so this then indicates the value that we

play32:18

want to insert into our stack we then

play32:21

perform a check using an if else

play32:24

statement and we check to see whether

play32:27

our top subscript is equal to max length

play32:31

if it is then it means we have no more

play32:33

space in our array so in that case we

play32:36

simply print out a

play32:38

error message to the standard error

play32:41

stream

play32:43

otherwise we enter the else portion

play32:44

which means we can still insert a value

play32:46

into our stack and so we access stack

play32:49

ptr

play32:50

at top sub which has been incremented by

play32:53

a value of one so on the first insertion

play32:56

top top sub will have been incremented

play32:59

to a value of zero and that will then be

play33:01

the first position that we insert a

play33:04

value into the value of course that we

play33:06

are inserting is number which is the

play33:08

parameter value

play33:10

we additionally have three more member

play33:13

functions over here the implementations

play33:16

are not shown but we have a pop

play33:19

operation

play33:20

top operation and an empty operation

play33:26

the next example that we'll look at on

play33:29

the next couple of slides is a better

play33:32

implementation of the stack class and in

play33:35

this implementation we enforce a

play33:37

separation between a header file and an

play33:41

implementation file and this allows us

play33:44

to hide the object representation from

play33:46

the client

play33:48

so we'll begin by looking at the first

play33:50

part of our implementation which is the

play33:52

header file and typically this would be

play33:55

stored in a dot h file so for our

play33:58

example this would usually be in a stack

play34:01

dot h file

play34:03

now this header file is not compiled and

play34:06

it only provides function prototypes

play34:10

so the client can't depend on the

play34:13

implementation of any of these functions

play34:15

because the implementations will be in a

play34:18

secret implementation file which we'll

play34:20

get to in a moment

play34:23

now private data members are also

play34:25

provided in the header file and this is

play34:28

unfortunately not ideal because the

play34:30

client may start depending on the

play34:33

representations in our stack class we

play34:36

saw that we had three

play34:39

data elements contained within it as

play34:41

member variables

play34:43

and these will then still be present

play34:46

within our header files so the client

play34:49

does then know for example

play34:51

that an array is used in order to

play34:54

represent our stack now unfortunately we

play34:57

can't get away from this because the

play34:59

compiler needs to know this information

play35:02

and generally this is related to the

play35:04

fact that the compiler needs to know the

play35:07

size that a stack object will take up in

play35:10

memory

play35:11

so for example the compiler must know

play35:14

the size of a stack class object for

play35:17

memory allocation within client code and

play35:21

this would typically happen when an

play35:23

object of the stack class is created

play35:26

either on the stack or the heap

play35:31

so on this slide we have our stack.h

play35:35

file which is the header file for our

play35:37

stack class

play35:39

we can see that once again we specify

play35:42

that the class is named stack and we see

play35:44

that we have braces that delimit our

play35:47

class

play35:48

we also have exactly the same private

play35:51

label

play35:52

and the three member variables stack ptr

play35:57

maxlin and top sub

play36:00

so these are of course visible only to

play36:04

members of the stack class itself as

play36:06

well as friends and there aren't any

play36:08

friends of the stack class defined so

play36:11

therefore these members are only visible

play36:14

within the stack class

play36:17

next we have our public label over here

play36:19

and then we have all of the public

play36:22

member functions that we defined in our

play36:24

previous example including our

play36:26

constructor our destructor and then our

play36:30

push pop top and mt member functions

play36:34

what's important to notice here is that

play36:36

there are no bodies provided for any of

play36:39

these functions

play36:40

and the reason for this is that the

play36:43

implementation will be provided in the

play36:45

implementation file which we'll discuss

play36:48

next

play36:49

so the client then can only see this

play36:52

header file and therefore they can only

play36:54

see

play36:55

which member functions are provided for

play36:58

their use

play37:00

this means that they can't rely on the

play37:02

implementation of the constructor

play37:04

destructor or any of the member

play37:07

functions

play37:11

now the second part of our beta class

play37:14

implementation for the stack adt is

play37:17

called the implementation file and this

play37:20

is usually contained within either a dot

play37:23

cpp or a dot c file so for our example

play37:28

the implementation file would either be

play37:30

stack dot cpp or stack dot c

play37:35

now the implementation file is compiled

play37:38

into an object file which is usually a

play37:41

dot o or a dot obj file

play37:45

now within a larger project all of the

play37:48

implementation files are compiled into

play37:50

separate object files so for example if

play37:54

we had a binary search tree

play37:56

class then its implementation file would

play37:59

be binarysearchtree.co.cpp

play38:03

and this would be compiled into a binary

play38:06

search tree object file

play38:09

once we've compiled all of our

play38:11

implementation files into object files

play38:14

then these object files are combined

play38:17

together into an executable file by

play38:19

means of the linking process which is

play38:22

part of the compilation procedure

play38:25

now the implementation file contains

play38:27

definitions for our functions so in

play38:29

other words the actual implementation of

play38:32

all of the functions for which

play38:34

prototypes were included in our stack.h

play38:38

file

play38:40

now it's also important to note that at

play38:42

the top of the implementation file we

play38:45

include the header file for that

play38:48

implementation so in our example we

play38:51

would include our stack dot h

play38:54

file

play38:55

at the top of our stack dot cpp or stack

play38:58

stack.c file

play39:00

and the reason for this is that the

play39:05

header file is included wherever the

play39:08

stack class is actually used

play39:11

so because the stack.cpp file uses the

play39:15

stack class we must include the stack.h

play39:19

file right at the top of the stack.cpp

play39:23

or stack.c file

play39:25

additionally because our client code

play39:28

also uses the stack class we must also

play39:31

include them at the top of the client

play39:34

code file and inclusion for our stack.h

play39:39

file

play39:42

finally on this slide we have the

play39:44

implementation file for our beta

play39:47

implementation of the stack class

play39:50

we can see that this file is named

play39:52

stack.cpp

play39:54

and this includes the actual

play39:55

implementation of all of the member

play39:58

functions constructors and destructors

play40:02

notice that we have a hash include over

play40:05

here which includes the stack.h file the

play40:08

stack.h file is included in quotation

play40:12

marks and the reason for this is that it

play40:15

is a user-provided header file as

play40:17

opposed to a system provided header file

play40:20

such as the iostream header file that

play40:22

we've also included over here which is

play40:25

contained within angled brackets

play40:29

then we can see that we have the

play40:31

implementations for our stack

play40:33

constructor over here our stack

play40:36

destructor and then our push member

play40:40

function we would of course have had

play40:42

implementations for all of the other

play40:44

member functions as well but those are

play40:46

emitted here just to make the example a

play40:49

bit simpler these implementations are

play40:52

exactly the same implementations as we

play40:55

saw in our original simple example

play40:59

now notice that none of these members

play41:02

are included within a class construct

play41:05

instead we specify for each one which

play41:08

class it is a member of so here we have

play41:11

specified that the stack constructor is

play41:14

a member of the stack class

play41:16

similarly we've also indicated that the

play41:19

stack destructor is a member of the

play41:22

stack class and finally that our push

play41:26

member function is a member of the stack

play41:30

class

play41:32

right so that concludes our discussion

play41:34

for this lecture

play41:36

in the next lecture we will be

play41:38

continuing with and finishing off our

play41:41

discussion on chapter 11 of the textbook

Rate This

5.0 / 5 (0 votes)

Related Tags
Abstract Data TypesEncapsulationProgramming ConceptsData AbstractionHigh-Level LanguagesCode OrganizationModifiabilityInformation HidingC++ ClassesStack ADTConstructors Destructors