Data Structures And Object Oriented Design
Lecture Notes Usc Csci104 Itebooks download
https://ebookbell.com/product/data-structures-and-object-
oriented-design-lecture-notes-usc-csci104-itebooks-23836322
Explore and download more ebooks at ebookbell.com
Here are some recommended products that we believe you will be
interested in. You can click the link to download.
Mastering Java An Effective Project Based Approach Including Web
Development Data Structures Gui Programming And Object Oriented
Programming Beginner To Advanced White
https://ebookbell.com/product/mastering-java-an-effective-project-
based-approach-including-web-development-data-structures-gui-
programming-and-object-oriented-programming-beginner-to-advanced-
white-11063222
Object Oriented Programming And Data Structures E Balagurusamy
https://ebookbell.com/product/object-oriented-programming-and-data-
structures-e-balagurusamy-10959566
Java Methods Objectoriented Programming And Data Structures Third Ap
3rd Ap Maria Litvin
https://ebookbell.com/product/java-methods-objectoriented-programming-
and-data-structures-third-ap-3rd-ap-maria-litvin-59073076
Java Methods Objectoriented Programming And Data Structures 4th
Edition 4th Maria Litvin
https://ebookbell.com/product/java-methods-objectoriented-programming-
and-data-structures-4th-edition-4th-maria-litvin-60399372
Java Methods A Ab Objectoriented Programming And Data Structures
Student Maria Litvin
https://ebookbell.com/product/java-methods-a-ab-objectoriented-
programming-and-data-structures-student-maria-litvin-2393214
Objectorientation Abstraction And Data Structures Using Scala Second
Edition 2nd Ed Lacher
https://ebookbell.com/product/objectorientation-abstraction-and-data-
structures-using-scala-second-edition-2nd-ed-lacher-5742584
Data Structures And Other Objects Using C 4th Edition Michael G Main
Walter J Savitch
https://ebookbell.com/product/data-structures-and-other-objects-
using-c-4th-edition-michael-g-main-walter-j-savitch-33556400
Data Structures And Other Objects Using Java 4th Edition Main
https://ebookbell.com/product/data-structures-and-other-objects-using-
java-4th-edition-main-55557912
Objects Abstraction Data Structures And Design Using C 1st Edition
Elliot B Koffman
https://ebookbell.com/product/objects-abstraction-data-structures-and-
design-using-c-1st-edition-elliot-b-koffman-50318832
Data Structures And Object Oriented Design Lecture Notes Usc Csci104 Itebooks
Class Notes for CSCI 104: Data Structures and Object-Oriented
Design
David Kempe and the awesome Fall 2013 sherpas
May 15, 2014
2
Preface
These lecture notes grew out of class notes provided for the students in CSCI 104 (“Data Structures and
Object-Oriented Design”) at the University of Southern California in Fall of 2013. The class is typically taken
in the second semester of freshman year or the first semester of sophomore year. Students are expected to
be familiar with structural programming in the C++ programming language, the basics of dynamic memory
management, and recursion. Brief refreshers on these two specific topics are included at the beginning of
the notes, and typically covered for about one lecture each, but a student not already familiar with these
concepts is likely to struggle in the class.
These notes represent the specific way in which we like to present the material, by motivating object-
oriented design primarily from the point of view of implementing data structures. There is perhaps somewhat
more focus on analysis and allusions to advanced topics than in a typical programming-heavy data structures
course for undergraduates. The notes are, at least at present, not intended to replace an actual detailed
textbook on data structures. We currently use the book “Data Abstraction and Problem Solving” by Carrano
and Henry.
The notes are based on lecture notes taken by the CSCI 104 sherpas in Fall 2013: Chandler Baker,
Douglass Chen, Nakul Joshi, April Luo, Peter Zhang, Jennie Zhou. Douglass was the main notetaker,
in charge of about half of the notes himself, and coordinating the others’ notes as well. The notes were
subsequently expanded significantly by David Kempe, and then expannded further and reorganized again
for the Spring 2014 semester, when they took essentially their current form.
3
4
Contents
1 Overview: Why Data Structures and Object-Oriented Thinking 9
2 Strings and Streams: A Brief Review 13
2.1 Strings . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13
2.2 Streams . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13
3 Memory and Its Allocation 17
3.1 Scoping of Variables . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 18
3.2 Dynamic allocation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 19
3.3 Pointers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 19
3.4 Dynamic Memory Allocation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 20
3.4.1 C Style . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 21
3.4.2 C++ Style . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 21
3.5 Memory Leaks . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 22
4 Recursion 25
4.1 Computing Factorials . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 25
4.2 Binary Search . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 26
4.3 The n-Queens problem . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 28
4.4 Some General Comments on Recursive Functions . . . . . . . . . . . . . . . . . . . . . . . . . 30
4.5 Recursive Definitions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 31
5 Linked Lists 33
5.1 Implementing Linked Lists . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 34
5.1.1 Linked list operations . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 35
5.2 Recursive Definition of Linked Lists . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 37
6 Abstract Data Types 39
7 Classes and Objects 43
7.1 Header Files and Declarations . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 43
7.2 Implementation of Member Functions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 45
7.3 Constructors and Destructors . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 46
7.4 The this pointer . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 47
8 Templates 49
8.1 The const keyword . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 50
5
9 Error Handling and Exceptions 53
9.1 Handling errors . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 53
9.2 Setting Flags . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 54
9.3 Exceptions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 54
10 Analysis of Running Time 57
10.1 Which running time? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 57
10.2 The Value of Big-O Notation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 58
10.3 Obtaining Upper and Lower Bounds . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 59
10.4 Computing Upper Bounds in Practice . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 60
10.5 Some Examples . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 61
11 Operator Overloading and Copy Constructors 65
11.1 Overloading and Operators . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 65
11.2 Operator Overloading and Classes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 66
11.3 Friend Access . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 68
11.3.1 Friend Classes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 69
11.4 An Illustration: Complex Numbers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 70
11.5 Copy Constructors . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 72
12 Inheritance and Polymorphism 75
12.1 Member Visibility, Multiple Inheritance, and calling Base Class Methods . . . . . . . . . . . . 76
12.1.1 Inheritance and Visibility . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 77
12.1.2 Multiple inheritance . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 77
12.1.3 Calling Base Class Methods . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 77
12.2 Class Relationships . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 78
12.3 Static vs. Dynamic Binding . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 79
12.4 Pure virtual functions and abstract classes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 80
12.4.1 Why and how to use virtual functions . . . . . . . . . . . . . . . . . . . . . . . . . . . 80
12.5 Constructors and Destructors with Inheritance . . . . . . . . . . . . . . . . . . . . . . . . . . 82
13 Array Lists 85
13.1 Comparison of Running Times . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 87
14 Stacks and Queues 89
14.1 A quick overview . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 89
14.2 Stacks . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 90
14.2.1 Example . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 91
14.2.2 Implementation of a general-purpose stack . . . . . . . . . . . . . . . . . . . . . . . . . 92
14.3 Queues . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 92
14.3.1 Implementation of a queue . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 93
14.4 Why use restrictive data structures? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 93
15 C++ Standard Template Library 95
15.1 More Details on Container Classes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 96
15.1.1 Sequence Containers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 96
15.1.2 Adapter Containers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 96
15.1.3 Associative Containers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 97
6
16 Iterators 99
16.1 Some first attempts . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 99
16.2 Iterators . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 100
16.3 Implementing an Iterator . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 101
16.4 The Bigger Picture . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 104
16.4.1 Design Patterns . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 104
16.4.2 Foreach, Map, Functional Programming, and Parallelization . . . . . . . . . . . . . . . 104
17 Searching Lists and Keeping them Sorted 107
17.1 Searching in a list . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 107
17.2 Interpolation Search . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 109
17.3 Keeping a List sorted . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 109
18 Qt and Event-Based Programming 111
18.1 Layout of Graphical Objects . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 111
18.1.1 Layouts . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 111
18.1.2 Widgets . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 112
18.2 Event-Based Programming . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 114
18.2.1 Signals . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 115
18.2.2 Slots . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 115
18.2.3 Connections . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 116
18.3 Putting it Together . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 116
18.3.1 Getting at data from widgets . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 116
18.3.2 Control flow and main function . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 117
18.3.3 Compiling Qt . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 117
19 Sorting Algorithms 119
19.1 Applications of Sorting . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 119
19.2 Stability of sorting algorithm . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 120
19.3 Bubble Sort . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 120
19.4 Selection Sort . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 122
19.5 Insertion Sort . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 123
19.6 Merge Sort . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 124
19.6.1 Running Time of Merge Sort . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 126
19.7 Quick Sort . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 128
19.7.1 Running Time . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 130
20 Graphs: An Introduction 133
20.1 The Basic Definitions and Examples . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 133
20.2 Paths in Graphs . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 135
20.3 The Abstract Data Type Graph . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 135
20.3.1 How to Implement the ADT internally? . . . . . . . . . . . . . . . . . . . . . . . . . . 136
20.4 Graph Search: BFS and DFS . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 137
20.4.1 Breadth-First Search (BFS) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 138
20.4.2 Depth-First Search (DFS) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 140
20.5 PageRank . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 141
21 Trees and Tree Search 145
21.1 The Basics . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 145
21.2 Implementing trees . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 146
21.3 Traversing a tree . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 147
21.4 Search Trees . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 148
7
22 Priority Queues and Heaps 151
22.1 Abstract Data Type: Priority Queue . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 151
22.2 Simple Implementations of a Priority Queue . . . . . . . . . . . . . . . . . . . . . . . . . . . . 153
22.3 Implementation using a Heap . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 153
22.4 Running Time of Priority Queue Operations . . . . . . . . . . . . . . . . . . . . . . . . . . . . 156
22.5 Heap Sort . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 156
23 2–3 Trees 159
23.1 The Basics . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 159
23.2 Search and Traversal . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 160
23.3 Inserting and Removing Keys: Overview . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 161
23.4 Inserting Keys . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 161
23.5 Removing Keys . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 165
23.5.1 A detailed illustration . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 167
23.5.2 Running Time . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 169
24 General B Trees and Red-Black Trees 173
24.1 More General B Trees . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 173
24.2 Red-Black Trees . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 174
24.2.1 Inserting into a Red-Black Tree . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 175
25 Hashtables 179
25.1 Introduction and Motivation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 179
25.2 Hash Tables Defined . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 179
25.3 Avoiding Collisions, and Choices of Hash Functions . . . . . . . . . . . . . . . . . . . . . . . . 180
25.3.1 Theory behind Choices of Hash Functions . . . . . . . . . . . . . . . . . . . . . . . . . 181
25.3.2 Practice of Hash Functions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 183
25.3.3 Another Application of Hash Functions . . . . . . . . . . . . . . . . . . . . . . . . . . 183
25.4 Dealing with Collisions: Chaining and Probing . . . . . . . . . . . . . . . . . . . . . . . . . . 184
25.4.1 Chaining . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 184
25.4.2 Probing . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 185
25.4.3 Implementing Hash Tables with Probing . . . . . . . . . . . . . . . . . . . . . . . . . . 186
25.5 Bloom Filters . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 187
26 Tries and Suffix Trees 191
26.1 Tries . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 192
26.1.1 An Application: Routing and IP Addresses . . . . . . . . . . . . . . . . . . . . . . . . 193
26.1.2 Path Compression . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 193
26.2 Suffix Trees . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 193
27 Dijkstra’s Algorithm and A∗
Search 197
27.1 Dijkstra’s Algorithm . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 197
27.1.1 Fibonacci Heap Implementation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 199
27.2 A∗
Search . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 199
27.2.1 The 15-Puzzle . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 201
28 Design Patterns 203
28.1 Observer . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 203
28.2 Visitor . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 204
28.3 Factory . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 205
28.4 Adapter/Wrapper . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 206
8
Chapter 1
Overview: Why Data Structures and
Object-Oriented Thinking
[Note: This chapter covers material of about 0.75 lectures.]
As a result of our introductory CS classes (CSCI 103, or CSCI 101 for earlier generations), most students
are probably somewhat proficient in basic programming, including the following basic features:
Data types int, String, float/double, struct, arrays, . . ..
Arithmetic
Loops for, while and do-while
Tests if, else and switch
Pointers
Functions and some recursion
Other I/O and other useful functions
OOP Some students have probably already learned a bit about object-oriented programming, but for now,
we will not rely on this.
In principle, these features are enough to solve any programming problem. In fact, in principle, it is
enough to have nothing except while loops, if tests, integers, and arithmetic. Everything that we learn
beyond those basics is “just” there to help us write better, faster, or more easily maintained or understood
code. Writing “better” code comes in two flavors:
1. Learning problem-solving and conceptual ideas that let us solve problems which we didn’t know how
to solve (even though we had the tools in principle), or didn’t know how to solve fast enough.
2. Learning new language features and programming concepts helps us write code that “feels good”, in
the sense that it is easy to read, debug, and extend.
The way most students are probably thinking about programming at this point is that there is some
input data (maybe as a few data items, or an array), and the program executes commands to get an output
from this. Thus, the sequence of instructions — the algorithm — is at the center of our thinking.
As one becomes a better programmer, it’s often helpful to put the data themselves at the center of one’s
thinking, and think about how data get transformed or changed throughout the execution of the program.
Of course, in the end, the program will still mostly do the same, but this view often leads to much better
(more maintainable, easier to understand, and sometimes also more efficient) code. Along the way, we’ll see
how thinking about the organization of our data can be really important for determining how fast our code
executes.
9
Example 1.1 In class, we did the following example. Two students each got a list of the names and e-mails
of all students in the class. One student got the list sorted alphabetically, while the other’s version contained
the names in random order. Both students were asked to look up the e-mail of one particular student by
name. Not too surprisingly, the student with the sorted list was much faster.
What this example showed us is that the way we organize our data can have a profound impact on how
fast (or slowly) we can solve a computational task. The basic question of how to organize data to support
the operations we need is the centerpiece of what you will learn in this class.
What do we mean by “support the operations we need”? Let’s go back to the example of looking up
e-mails of students by name. Here, the key operation is clearly the “lookup” operation, at least from the
standpoint of the volunteer doing the looking up. But when we look at the bigger picture, those data items
(pairs of a student and an e-mail) had to be added into that form somehow. And sometimes, we may want
to delete them. So really, the three operations we are interested in are:
• Insert a pair of a name and e-mail into the list.
• Remove a name from the list.
• Look up the e-mail for a given name.
Purely from the perspective of speeding up the lookup operation, the sorted list was much better than
the unsorted one. But in terms of inserting items, it’s clearly better to keep things unsorted, as we can just
append them at the end of the list, and not have to worried about putting the item in the right place. For
removal, well, we’ll learn about that a little more later.
It’s easy to imagine scenarios where keeping the list sorted is not worth it. For instance, if we need to add
data items all the time, but very rarely look them up (say, some kind of backup mechanism for disasters),
then it’s not worth it. Or maybe, the list is so short that even scanning through all the items is fast enough.
The main take-away message is that the answer to “What is the best way to organize my data?” is almost
always “It depends”. You will need to consider how you will be interacting with your data, and design an
appropriate structure in the context of its purpose. Will you be searching through it often? Will data be
added in frequent, short chunks, or in occasional huge blocks? Will you ever have to consolidate multiple
versions of your structures? What kinds of queries will you need to ask of your data?
There is another interesting observation we can see from the previous example. We said that we wanted
our data structure to support operations add, remove, and lookup. Let’s be a little more general than just
a list of students.
• The add (key, value) operation gets a key and a value. (In our case, they were both strings, but in
general, they don’t have to be.) It creates an association between the key and value.
• The remove (key) operation gets a key, and removes the corresponding pair.
• The lookup (key) operation gets a key, and returns the corresponding value, assuming the key is in
the structure.
This type of data structure is typically called a map, or (in our textbook) a dictionary.
So is a sorted list a map? Not exactly. After all, an unsorted list would also be a map. There seems to
be a difference. The main difference is that the word “map” describes the functionality that we want: what
the data structure is supposed to do. On the other hand, a sorted list (or an unsorted list) is a specific way
of getting this functionality: it says how we do it.
This distinction is very important. When you start to program, usually, you just go straight ahead and
plan out all of your code. As you take on bigger projects, work in larger teams, and generally learn to think
more like a computer scientist, you get used to the notion of abstraction: separating the “what” from the
“how”. You can work on a project with a friend, and tell your friend which functions you need from him/her,
without having to think through how they will be implemented. That’s your friend’s job. So long as your
10
friend’s implementation works (fast enough) and meets the specification that you prescribed (say, in terms
of which order the parameters are in), you can just use it as is.
So the way we would think about a map or dictionary is mostly in terms of the functions it provides: add,
remove, and lookup. Of course, it must store data internally in order to be able to provide these functions
correctly, but how it does that is secondary — that’s part of the implementation. This combination of data
and code, with a well-specified interface of functions to call, is called an abstract data type.
Once we start realizing the important role that data organization (such as using abstract data types)
plays in good code design, we may change the way we think about code interacting with data. When starting
to program, as we mentioned above, most students think about code that gets passed some data (arrays,
structs, etc.) and processes them. Instead, we now think of the data structure itself as having functions that
help with processing the data in it. In this way of thinking, an array in which you can only add things at
the end is different from one in which you are allowed to overwrite everywhere, even though the actual way
of storing data is the same.
This leads us naturally to object-oriented design of programs. An object consists of data and code; it
is basically a struct with functions inside in addition to the data fields. This opens up a lot of ideas
for developing more legible, maintainable, and “intuitive” code. Once we think about objects, they often
become almost like little people in our mind. They serve different functions, and interact with each other in
particular ways.
One of the main things that thinking in terms of object-oriented design (and abstract data types) does
for us is to help achieve encapsulation: shielding as much of the inside of an object as possible from the
outside, so that it can be changed without negatively affecting the code around it. The textbook refers to
this as “walls” around data. Encapsulation helps us achieve modularity, i.e., ensuring that different pieces
of code can be analyzed and tested in isolation more easily.
To summarize, the learning goals of this class are:
1. Learn the basic and advanced techniques for actually implementing data structures to provide efficient
functionality. Some of these techniques will require strong fundamentals, and their analysis will enter
into more mathematical territory, which is why we will be drawing on material you will be learning
simultaneously in CSCI 170.
2. Learn how to think about code more abstractly, separating the “what” from the “how” in your code
design and utilizing abstract data types to specify functionality.
3. Learn about good programming practice with object-oriented design, in particular as it relates to
implementing datatypes.
11
12
Chapter 2
Strings and Streams: A Brief Review
[Note: This chapter covers material of about 0.25 lectures.]
In this class, you will frequently need to read and process strings, and input and output in C++ are
typically handled using streams. While you should have learned these in your introductory programing class,
we will give a brief review here.
2.1 Strings
In basic C, string are implemented just as arrays of characters. There is no separate string type. This means
that you need to allocate enough space for your character array to hold any input string that you might
receive. It also means that you need a special way of marking how much of that array you are actually using:
just because you allocated, say, 80 characters in your string doesn’t mean you’ll always use all 80. The way
C does this is by having a special role for the character 0. (That’s the character number 0, not the actual
character ‘0’.) It marks the end of the used part of the string.
The string class in C++ is a little easier to use: in particular, it hides some of the memory management
for you, so that you don’t have to worry about allocating enough space. It also implements some operators
that you will frequently use, such as = (assignment), == (comparison) and + (appending strings). The string
class also contains a number of other very useful functions. Since you’ll be doing a lot of processing of data
you are reading in from files in this class, you can probably save yourself a lot of work by reviewing the
string class and its features.
One string function which is really useful in C, but to our knowledge does not have an equivalent in C++
in standard implementations, is strtok. It allows you to tokenize a string, i.e., break it into smaller chunks
using any set of characters as separators. Check it out!
2.2 Streams
Streams are the C++ ways of interacting with files, keyboard, screen, and also strings. They typically divide
into streams you read (keyboard, files, strings) and streams you write (screen, files, strings). In all cases,
they are basically a sequence of characters that you are extracting from (read) or inserting into (write).
They are a fairly clean abstraction of reading and writing “items” one by one.
The standard streams you’ll be interacting with are the following. They require you to #include different
header files, also listed here.
The difference between cout and cerr is twofold: First, cout buffers its output instead of printing it
immediately, whereas cerr prints immediately. This makes cerr very useful to output debug information,
whereas you’ll probably write your “real” output to cout. They are also two different logical streams. This
means that if you redirect your output to a file (which we will do when grading you), the default is that only
cout gets redirected. So if you print debug information to cerr, it still appears on the screen, and doesn’t
13
Stream Target Header File
cin Keyboard iostream
cout, cerr Screen iostream
ifstream File to read fstream
ofstream File to write fstream
stringstream String sstream
“pollute” your output. Finally, cerr often gets displayed in a different color. All this is to say: it may be
good to get into the habit to print all your “real” output to cout, and all the debug output to cerr.
To extract items from an input stream, you use the >> operator, and to insert items into an output
stream, you use <<.
A few things that are worth remembering about reading from an input stream. (These frequently trip
students up.)
• When reading from a stream (say, cin, or a ifstream myinput), you can use cin.fail() (or myinput.fail())
to check whether the read succeeded. When one read fails (and the fail flag is set), it will remain set
until you explicitly reset it with cin.clear() or myinput.clear(). Until then, all further reads will
fail, since C++ assumes that until you’ve fixed the source of the problem, all incoming data will be
unreliable now.
• Remember that >> reads until the next white space, which includes spaces, tabs, and newlines.
Be careful here when entering things manually. If at the first prompt, you enter multiple items separated
by space, they will “supply” the next few extractions. That is, if you write code like
cout << "Please enter n:"
cin >> n;
cout << "Please enter m:"
cin >> m;
and enter “5 3” at the first prompt, it will read 5 into n and 3 into m.
• If you want to also read spaces, then instead of using >>, you should use getline, such as cin.getline()
or myinput.getline(). It lets you specify a string to read into, a maximum number of characters to
read, and character to stop at (default is newline), and will then read into the string. That string can
later be further processed.
Notice that you need to be a bit careful about getline at the end of a file, and reading the correct
flags to avoid reading the last line multiple times.
• If you want to “clear” off the remaining characters in a stream so they don’t cause issues later, you can
use the ignore() function, which will ignore the next n characters (which you can specify — default
is 1).
File Streams and String Streams
File streams are the typical C++ way of reading data from files. They two types are ifstream (for files you
read) and ofstream (for files you write). A file stream needs to be opened before you can read/write from
it. Once you have opened it, it can basically be used like cin and cout.
Notice that in file streams, when you read over the end of the file, you will get a fail condition. That
happens after you reach the end of the file, which is often cause for confusion when using getline.
The other thing to keep in mind about file streams is that after you’re done, you should call close() on
them.
14
Since strings are sequences of characters, just like files, it’s natural to treat them as streams as well. This
is what the stringstream type is for. This is particularly useful when you’ve read an entire string and want
to break it into smaller pieces, e.g., separated by spaces. You can treat the string as a stringstream, and
then use extraction operations on it to get the different items. It’s also a convenient way to convert strings
to integers.
As a piece of advice, avoid using the same stringstream object multiple times — or if you do, at least
make sure to reset it.
15
16
Chapter 3
Memory and Its Allocation
[Note: this chapter covers material of about 1 lecture.]
At the physical level, computer memory consists of a large number of flip flops. Each flip flop consists of a
few transistors, and is capable of storing one bit. Individual flip flops are addressable by a unique identifier,
so we can read and overwrite them. Thus, conceptually, we can think of all of our computer’s memory as
just one giant array of bits that we can read and write.
Since as humans, we are not that good at doing all of our thinking and arithmetic in bits, we group them
into larger groups, which together can be used to represent numbers. 8 bits are called 1 byte; beyond bytes,
there are words (which are sometimes 16, sometimes 32 bits). Thus, more often, we conceptually regard our
computer’s memory as one large (size 232
or so) array of bytes. A lot of things are stored in this memory.
1. All variables and other data used by all programs.
2. But also the programs’ code, including the operating system’s.
The compiler and the operating system work together to take care of most memory management for you,
but it is instructive to see what is going on under the hood.
When you compile your code, the compiler can examine primitive data types and calculate how much
memory they will need ahead of time. The required amount is then allocated to the program in the stack1
space. For example, consider the following declarations:
int n;
int x[10];
double m;
The compiler can immediately see that the code requires 4 + 4 × 10 + 8 = 52 bytes2
.
It will insert code that will interact with the operating system to request the necessary number of bytes
on the stack for your variables to be stored. In the example above, the compiler knows that the memory
will look as follows:
It knows the exact memory address of each variable; in fact, whenever we write n, this gets translated
into something like “memory address 4127963” internally.
Notice that if we attempted to access x[10] here, we would access the data associated with m, and may end
up reading (or overwriting) some of its bits. This would almost certainly have very undesired consequences
for the rest of the program.
When functions call other functions, each function gets its own chunk of stack at the moment it is called;
it keeps all its local variables there, but also a program counter that remembers where in its execution it
1This space is called the stack space because as functions get called, their memory gets added on top of existing memory.
As they terminate, they are removed in a LIFO (last in, first out) order.
2That’s with the current sizes for integers and doubles. About 20 years ago, int were typically 2 bytes, and double 4 bytes.
Your code should never have to depend on what is at this moment the size of the basic data types.
17
n x[0] x[5] m
Figure 3.1: The layout of memory on the stack for the declaration above.
was. When the function finishes, its memory block is made available for other purposes again. Again, for
statically allocated variables, the compiler can take care of all of this.
3.1 Scoping of Variables
While we are talking about local variables and the stack, we should briefly talk about the scope of a variable.
Global variables are, of course, accessible to any function or any block of code in your program. Local
variables in function or code blocks only exist while the execution is in the function or the code block. It is
typically stored on the stack. As soon as the function or block terminates, the variable is deallocated.
When the execution of a function is temporarily suspended, for instance because another function got
called inside this one, the variables are stored on the stack, but not active, so they cannot be accessed.
Consider the following example:
void foo (int x)
{
int y;
// do some stuff
}
void bar (int n)
{
int m;
foo (n+m);
// do more stuff
}
Here, when bar calls foo, the function foo cannot access the variables n or m. As soon as foo finishes,
the variables x and y are deallocated, and permanently lost. bar now resumes and has access to n and m,
but not to x or y.
You will sometimes have multiple variables (in different parts of your code) sharing the same name. For
instance, you may have both a global variable n and a local variable in a function called n. Or you could
have something like the following:
void foo (int n)
{
int m = 10;
// do something
for (int i = 0; i < m; i ++)
{
int n = 3, m = 5;
// do something
cout << n << m;
}
}
18
Here, the cout << n << m statement would output the innermost versions, so the values 3 and 5. As
a general rule, when there are multiple variables with the same name, any reference is to the one in the
smallest code block enclosing the statement that contains that variable.
3.2 Dynamic allocation
Unfortunately, things aren’t quite as easy as statically allocated arrays when we don’t know at compile time
how much memory a variable will need. Suppose we want to do something like the following:
int n;
cin>>n;
// create an array a of n integers
Here, at compile time, the compiler does not know how much memory the array will need. It can therefore
not allocate room for a variable on the stack3
. Instead, our program needs to explicitly ask the operating
system for the right amount of space at run-time. This memory is assigned from the heap4
space.
The difference between static and dynamic memory allocation is summarized in the following table. To
fully understand how dynamic memory allocation works, we need to spend some time on pointers.
Static allocation Dynamic allocation
Size must be known at compile time Size may be unknown at compile time
Performed at compile time Performed at run time
Assigned to the stack Assigned to the heap
First in last out No particular order of assignment
Table 3.1: Differences between statically and dynamically allocated memory.
3.3 Pointers
A pointer is an “integer” that points to a location in memory — specifically, it is an address of a byte5
.
In C/C++, pointer types are declared by placing a star ‘*’ behind a regular type name. Thus, int
*p; char *q; int **b; void *v; all declare pointers. In principle, all these are just addresses of some
memory location, and C/C++ does not care what we store there. Declaring them with a type (such as int)
is mostly for the programmer’s benefit: it may prevent us from messing up the use of the data stored in the
location. It also affects the way some arithmetic on memory locations is done, which we explain below.
Two of the ways in which “regular” variables and pointers often interact are the following:
1. We want to find out where in memory a variable resides, i.e., get the pointer to that variable’s location.
2. We want to treat the location a pointer points to as a variable, i.e., access the data at that location,
by reading it or overwriting it.
The following piece of code illustrates some of these, as well as pitfalls we might run into.
3Technically, this is not quite true. Some modern compilers let you define arrays even of dynamic sizes, but we advise against
using this functionality, and instead do things as we write in these notes.
4This is called the heap space since it can be selected from any portion of the space that has not been allocated already.
While the stack remains nicely organized, memory in the heap tends to be more messy and all over the place. Hence the name.
5This means that all pointers are of the same size, and that the size of pointer used by a computer places a limit on the size
of memory it can address. For example, a computer using typical 32-bit pointers can only use up to 232 bytes or 4 gigabytes
of memory. The modern shift to 64-bit architectures turns this to 264 bytes, which will be enough for a while.
19
int* p, *q; //Pointers to two integers
int i, j;
i = 5; j = 10;
p = &i; //Obtain the address of i and save it to p
cout << p; //Prints the value of p (address of i)
cout << *p; //Prints the value of the integer that p points to (which is the value of i)
*p = j; // Overwrites the value of the location that p points to (so, i) with the value of j
*q = *p; // Overwrites the value of the location that q points to with the one that p points to
q = p; // Overwrites the pointer p with q, so they now point to the same location
A few things are worth noting here.
1. The last two commands both result in *p being equal to *q, but in different ways. In the first case, we
copy the value from one location to the other, while in the second, we make both point to the same
place.
2. The second-to-last command is very risky, and will likely cause a run-time error. We have not initialized
q, so it points to an arbitrary memory location, quite possibly location 0. We are trying to overwrite it,
which most likely the program is not allowed to do. The location may belong to the operating system
or some other program6
.
3. NULL is a special pointer we use for an uninitialized pointer variable, or to express that a pointer is not
pointing anywhere. If we try to write *p when p == NULL, we get a runtime error. In a sense, NULL is
really just another way of saying 0; we said above that pointers are just numbers, and the NULL pointer
points to memory location 0. In C++11, there is a new keyword for this, namely nullptr. If you set
the compiler flags to compile C++11, you can/should use nullptr instead of NULL.
4. In fact, the earlier observation about the perils of uninitialized pointers suggests that whenever you
have a pointer variable, you always assign it NULL right away as you declare it. So in our example,
we should have written int *p = NULL, *q = NULL; That way, if we later forget to assign it before
dereferencing, at least, it will cause a crash of our program, rather than possibly processing some
garbage that it reads from the location. This is good coding practice to reduce your debug time.
5. & and * are inverses of each other. Thus, &*p and *&p are the same as p. (The exception is that &*p
throws a runtime error when applied to p==NULL, instead of being equal to p.)
In general, it is quite easy to mess up with pointers. A few rules of thumb (which we discussed in class)
will weed out common mistakes, but generally speaking, it is easy to forget to assign a pointer correctly,
resulting in run-time errors.
We discussed before that pointers are basically just integers. They differ in that arithmetic is somewhat
different. When you have a pointer p to an int, and you write p+1, the compiler assumes that what you want
is the address where the next integer will start, which is 4 bytes later. Thus, the actual address referenced
by writing p+1 is actually 4 bytes after the address of p.
This is where the type of pointer matters, as we hinted at above. When you have a void*, then addition
really does refer to adding individual bytes. For all others, when you write p+k for a pointer p and integer
k, this references memory location p+k*size(<type>), where <type> is the type of the pointer p.
3.4 Dynamic Memory Allocation
In order to dynamically allocate and deallocate memory, there are two pairs of functions, one in C style
and one in C++ style. In C, the function for allocating memory is malloc, and for deallocation free. In
C++, the functions are new and delete. We will first discuss the C style, as it is a little closer to the actual
low-level implementation; we’ll then see the C++ style, which shields us from some of the low-level details.
6Fortunately, nowadays, all that happens is that your program throws a run-time error. In the past, the OS would often
allow you to do such an overwrite, in which case often some complete system crashes would happen.
20
3.4.1 C Style
The function void* malloc (unsigned int size) requests size bytes of memory from the operating sys-
tem, and returns the pointer to that location as a result. If for some reason, the OS failed to allocate the
memory (e.g., there was not enough memory available), NULL is returned instead. The function void free
(void* pointer) releases the memory located at pointer for reusing. A solution to our earlier problem of
a dynamically sized array could look as follows:
int n;
int* b;
cin >> n;
b = (int*) malloc(n*sizeof(int));
for (int i=0; i<n; i++)
cin >> b[i];
In order to request space for n integers, we need to figure out how many bytes that is. That’s why we
multiply with sizeof(int). Using sizeof(int) is much better than hard-coding the constant 4, which
may not be right on some hardware now or in the future.
Because malloc returns a void* (it does not know what we want to use the memory for), and we want
to use it as an array of integers, we need to cast it to an int*.
For good coding practice, we should probably also check whether b==NULL before dereferencing it, but
this example is supposed to remain short.
Another thing to observe here is that we can reference b just like an array, and we write b[i]. The
compiler treats this exactly as *(b+i), and, as you probably remember from the part about pointer arith-
metic, this points to the ith
entry of the array. In fact, that’s exactly how C/C++ internally treats all arrays
anyway; basically, they are just pointers.
If we wanted to write b[i] in a complicated way by doing all the pointer arithmetic by hand, we could
write instead *((int*) ((void*) b + i*sizeof(int))). Obviously, this is not what we like to type (or
have to understand), but if you understand everything that happens here, you are probably set with your
knowledge of pointer arithmetic and casting.
To return the memory to the OS after we’re done using it, we use the function free, as follows:
free(b);
b = NULL;
Note that free does nothing to the pointer b itself; it only deallocates the memory that b pointed to,
telling the operating system that it is available for reuse. Thus, it is recommended that you immediately set
the pointer to NULL so that your code does not attempt to tamper with invalid memory. If you reference b
somewhere, you’ll just get a runtime error. If you don’t set b=NULL, the OS may give the memory to another
variable, and you accidentally reference/overwrite that one. That kind of mistake can be much harder to
detect, and is easily avoided by setting the pointer to NULL immediately after deallocating it.
3.4.2 C++ Style
C++ provides the new() and delete() functions that provide some syntactic sugar to C’s malloc() and
free(). Basically, they relieve you from the calculations of the number of bytes needed, the casting of
pointers, and provide a more “array-like” syntax. Our example now looks as follows:
int n;
int *b;
cin >> n;
b = new int[n];
21
Notice that there are no parentheses, but instead, we have brackets for the number of items. new figures out
by itself how much memory is needed, and returns the correct type of pointer.
If we wanted space for just one integer, we could write int *p = new int; While this is not really very
useful for a single integer, it will become very central to allocating objects later, where we often allocate one
at a time dynamically.
To release memory, the equivalent of free is the delete operator, used as follows:
delete [] b;
delete p;
The first example deallocates an array, while the second deallocates a single instance of a variable (a single
int in our example). This deallocates the memory pointed to by the pointers. As with free, it still leaves
the pointers themselves pointing to the same memory location, so it is good style to write b = NULL or p =
NULL after the delete commands.
3.5 Memory Leaks
Let us look a little more at the things that can go wrong with dynamic memory allocation.
double *x;
...
x = (double*) malloc(100*sizeof(double));
...
x = (double*) malloc(200*sizeof(double)); // We need a bigger array now!
...
free(x);
This code will compile just fine, and most likely will not crash, at least not right away. We correctly
allocate an array of 100 double, use it for some computation, and then allocate an array of 200 double when
we realize that we need more memory.
But notice what happens here. The moment we do the second allocation, x gets overwritten with a
pointer to the newly allocated memory block. At that point, we have no more recollection of the pointer to
the previous memory block. That means we cannot read it, write it, or free it. When at the end of the code
snippet, the program calls free(x), it successfully frees up the second allocated block, but we are unable
to tell the operating system that we don’t need the first block any more. Thus, those 800 bytes will never
become available again (until our program terminates — but for all we know, it may run for several years
as a backend server somewhere).
This kind of situation is called a memory leak: available memory is slowly leaking out of the system. If
it goes on long enough, our program may run out of memory and crash for that reason. It could be quite
hard to diagnose why the crash happened if it does after a long time of running.
It is good practice to keep close track of the memory blocks you reserve, and make sure to free (or
delete) memory pointed to by a pointer before reassigning it7
. A better version of the code above would
be the following:
double *x;
...
x = (double*) malloc(100*sizeof(double));
...
free(x);
x = NULL;
7There are tools for checking your code for memory leaks, and we recommend familiarizing yourself with them. The most
well-known one, for which the course web page contains some links, is called valgrind.
22
x = (double*) malloc(200*sizeof(double));
...
free(x);
x = NULL;
That way, the memory gets released while we still have a pointer to it. You will recall that it is always
good practice to immediately set pointers to NULL after deallocating their memory. In the middle of the
code, that may seem very redundant: after all, we immediately overwrite x with another value. In fact, it is
completely redundant. However, we still recommend that you add the line; for example, you may later insert
some code between free(x) and the new assignment to x, and that could cause problems otherwise. And if
you are worried about your program wasting time with unnecessary assignments, don’t — the compiler will
almost certainly optimize that assignment away anyway, and the final code will look exactly the same.
One question you may be wondering about is if we could just free the memory by setting x = NULL;
without calling free(x) first. That would only overwrite the pointer, but it would not tell the operating
system that it can have the memory back. In other words, it would exactly create a memory leak. Whenever
you want to return memory to the system, you must do so explicitly8
.
As a side not, if you look up some of this information online or in other sources, keep in mind that the
pool of memory where you allocate variables with new or malloc is called the memory heap. This is different
from a data structure known as the heap which we will learn about later. Unfortunately, people (including
us . . .) are not quite consistent in naming these.
8There are other programming languages that do much more of the garbage collection and memory handling for you, but
C/C++ is not one of them.
23
24
Chapter 4
Recursion
[Note: this chapter covers material of about 1.5 lectures.]
The adjective recursive means “defined in terms of itself”. (If you Google “recursion”, you get the
answer: “Did you mean recursion?”) As computer scientists, we frequently run into recursion in the form of
recursive functions, which are functions that call themselves (directly, or indirectly through another function).
However, as we see below, another very important application is recursive definitions of objects. We will
explore recursion through several examples.
4.1 Computing Factorials
The first one is to compute the factorial of n, which is defined as n! = n · (n − 1) · (n − 2) · · · 2 · 1 =
Qn
i=1 i.
Of course, we could use iteration (a simple for loop) to compute n!.
int factorial(int n) {
int p=1;
for (int i=1; i<=n; i++)
p*= i;
return p;
}
This is a perfectly valid, and probably even the best, way to compute the factorial of a number. But
instead, we would like to use this very easy example to illustrate how recursion works.
Looking at the definition, we observe that 0! = 1 (by definition), and n! = n · (n − 1)!. This suggests a
recursive solution:
int factorial (int n) {
if (n==0) return 1;
else return n*factorial(n-1);
}
Notice that the function factorial calls itself; this is what makes this a recursive implementation.
Students often have trouble thinking about recursion initially. Our instinct is often to make a complete
plan for the computation: first multiply n with n − 1, then with n − 2, and so on, all the way to 1. In a
recursive solution, we instead treat most of the work as a “black box”: we don’t really worry how the call
with parameter n − 1 will obtain the correct results, and just trust that it does. (Of course, that only works
if the function is actually correct.)
In class, we illustrated this with acting out recursion with a group of students. Each student was in
charge of computing n! only for one particular number n. The student in charge of computing 5! relied on
another student to compute 4!, then used that other student’s result and multiplied it with 5. The important
25
insight was that the student who computed 5! did not need to know how 4! was computed; so long as the 4!
student got the right result, it could just be plugged in.
Again, to recap this once more, in a recursive solution, we don’t have to think of every step, nor do
we have to keep track of various intermediate results, as those are returned as values by the other function
calls. Students getting started on recursion often try as hard as possible to have recursion emulate loops,
by passing around “global variables” (or pointers to variables, which amounts to the same thing) that are
altered and store intermediate results.
This type of thinking can take a while to get used to, but once you firmly grasp it, a lot of things —
induction in CSCI 170 and dynamic programming in CSCI 270 — will come to you much more easily.
Two things that pretty much all correct recursive functions share are the following:
• A recursive function needs one or more base case: at some point, the function must hit a point where it
will no longer call itself (like the n==0 case for the factorial). Otherwise, the function will keep calling
itself forever, and eventually run out of stack memory.
• Recursive calls must have “smaller” inputs than the main input. In the case of the factorial function,
the recursive call within factorial(n) was for factorial(n-1). In this case, it is clear that n − 1 is
“smaller” than n. In other cases, “smaller” refers to the remaining size of an array, or even a number
that is closer to an upper bound. (For instance, the base case could be i==n, and the call with input
i could be to i + 1.)
Let us look quickly at two examples violating these conditions, and see what happens.
int UCLAfact (int n) // apologies to our neighboring school
{
if (n == 0) return 1;
else return UCLAfact (n); // error: input not getting smaller!
}
int NDfact (int n)
{
return n*NDfact (n-1); // ...this doesn’t stop!
}
Neither of these functions will terminate. In the first example, we do have a base case, but the recursive
call has the same size. It is of course correct that n! = n! (which is what the functions uses), but it doesn’t
help us compute it. In the second example, we do have a smaller input in the recursive call, but no base
case, so the function will continue calling itself with different values forever (or until it runs out of stack
space and crashes).
4.2 Binary Search
(Note: We did not cover this one in Spring of 2014. But it’s a useful example to know anyway, and we’ll
return to it.)
Admittedly, computing factorials is not a very strong example to show why recursion is useful, since the
iterative solution is short and elegant. However, we were able to illustrate some of the important properties
of recursion with an easy enough general setup. Next, let us look at a somewhat more challenging task:
Given a (pre-sorted) array of integers in increasing order, find the location of a target element, or return -1
if it is not in the array.
This is accomplished using the Binary Search algorithm: Check the middle of the remaining array. If the
element is there, we are done. If the desired element is smaller, continue searching to the left of the middle
element; otherwise, continue searching to the right. A corresponding iterative solution looks as follows:
26
int binarySearch (int n, int* b, int len)
{
int lo = 0, hi = len, mid;
while(lo <= hi) {
mid = (hi+lo)/2;
if (b[mid]==n) return mid;
else if(n < b[mid])
hi = mid-1;
else lo = mid+1;
}
return -1;
}
A recursive solution would look as follows instead:
int recSearch(int n, int* b, int lo, int hi) {
if (hi < lo) return -1; // not in the array
else
{
int mid = (hi+lo)/2; // the midpoint of the array
if (n == b[mid]) return mid; // we found it
else if (n < b[mid])
return recSearch(n, b, lo, mid-1); // element to the left of the midpoint
else return recSearch(n, b, mid+1, hi); // element to the right of the midpoint
}
}
We then call the function as recSearch(n, b, 0, len). Whether you like the iterative or the recursive
solution better here may be a matter of taste, but notice that there is a certain elegance to the recursive
solution. When we have decided where the element must be (to the left or the right), rather than updating
a variable and repeating, we simply ask the function to find it for us in that (correct) subarray, and return
its return value unchanged.
Notice that both implementations will work whether the array has an even or odd number of elements.
If we hadn’t written mid-1 and mid+1 for the recursive calls, we might have needed another base case when
lo == hi.
Let us check that we satisfy the two conditions above for a recursive solution to have a shot:
• If the array is empty (which is the case hi < lo), the function returns directly, reporting that the
element was not found.
• If n is less than the midpoint, the function recurses on the left half of the array; otherwise, on the right
half. In either case, because we eliminate at least the midpoint, the remaining array size is strictly
smaller than before.
One of the things that can be confusing about recursion is that there seem to be many “active” versions
of the function at once. What is “the” value of variables like n, lo, hi, or mid? After all, different invocations
of the function will have different values for them. To solve this “conundrum”, remember from our overview
of memory that local variables are stored on the stack, and translated into memory locations by the compiler
and OS. Thus, when the function recSearch(12,b,0,10) calls recSearch(12,b,0,4), their variables lo,
hi translate to completely different memory locations. When the call recSearch(12,b,0,4) executes, the
values are lo=0, hi=4, and mid=2 will be computed. In the call recSearch(12,b,0,4), we instead have
lo=0, hi=10, mid=5. The computer has no problem with the same variable name, as only one meaning of
the variable is in scope at a time.
27
4.3 The n-Queens problem
The first two examples we saw of recursion in class were pretty easily coded with a simple loop, as you saw.
In general, if you have a recursive function that uses just one recursive call to itself, it is often easily replaced
by a loop. Recursion becomes much more powerful (as in: it lets you write short and elegant code for
problems that would be much more messy to solve without recursion) when the function calls itself multiple
times.
We will see several examples of that later in class with some clever recursive sorting algorithms and
others. Another very common application is via a technique called Backtracking for solving complicated
problems via exhaustive search. In this class, we saw the classic n-queens problem as an example for this
technique.
In the n-queens problem, you want to place n queens on an n × n chessboard (square grid). Each queen
occupies one square on a grid and no two queens share the same square. Two queens are attacking each
other if one of them can travel horizontally, vertically, or diagonally and hit the square the other queen is
on. The problem is to place the queens such that no two queens are attacking each other. For instance,
what you see in Figure 4.1 is not a legal solution: the queens in rows 1 and 2 attack each other diagonally.
(All other pairs of queens are safe, though.)
Q
Q
Q
Q
Figure 4.1: Illustration of the 4-queens problem
Before we try to solve the problem, we make some observations. Because queens attack each other when
they are in the same row or column of the chessboard, we can phrase the problem equivalently as follows:
place exactly one queen per row of the board such that no two queens are in the same column or attack each
other diagonally.
We can solve this by having n variables q[i], one per queen. Each variable loops from 0 to n − 1, trying
all n places in which the queen could be theoretically placed. Of all those at most nn
ways of placing the
queen, we check if they are legal, and output them if so. Of course, it will be more efficient if we abort a
search as soon as there is an attack between queens, since placing more queens can never fix that.
So we will have an array q[0 . . . (n − 1)] that contains the positions within the rows for each of the n
queens. Those will be a global variable. Also, we’ll have a global variable for the size n of the grid:
int *q; // positions of the queens
int n; // size of the grid
Then, the main search function will be of the following form:
void search (int row)
{
if (row == n) printSolution (); // that function shows the layout
else
{
for (q[row] = 0; q[row] < n; q[row]++)
28
{
search (row+1);
}
}
}
That’s the general outline of most Backtracking solutions. At each point where a decision must be made,
have a loop run over all options. Then recursively call the function for the remaining choices.
Of course, so far, we don’t check anywhere to make sure that the solution is legal, i.e., no pair of queens
attack each other. To do that, we want to add an array that keeps track of which squares are safe vs. attacked
by a previously placed queen. We capture it in an array t (for “threatened”), which we can make a global
variable. (It’s a two-dimensional array, which translates to a pointer to a pointer.)
int **t;
Now, we need to check whether the place we’re trying to place a queen at is actually legal, and update
the available positions when we do. Instead of just keeping track whether a square is attacked by any queen,
we should actually keep track of how many queens attack it. Otherwise, if we just set a flag to true, and
two queens attack a square, when we move one of them, it will be hard to know whether the square is safe.
The more complete version of the function now looks as follows:
void search (int row)
{
if (row == n) printSolution (); // that function shows the layout
else
{
for (q[row] = 0; q[row] < n; q[row]++)
if (t[row][q[row]] == 0)
{
addToThreats (row, q[row], 1);
search (row+1);
addToThreats (row, q[row], -1);
}
}
}
We still have to write the function addToThreats, which increases the number of threats for the correct
squares. The function should mark all places on the same column, and on the two diagonals below the
current square. For the latter, we need to make sure not to leave the actual grid. Looking at it a little
carefully, you’ll see that the following function does that:
void addToThreats (int row, int column, int change)
{
for (int j = row+1; j < n; j++)
{
t[j][column] += change;
if (column+(j-row) < n) t[j][column+(j-row)] += change;
if (column-(j-row) >= 0) t[j][column-(j-row)] += change;
}
}
Finally, we need to write our main function that reads the size, creates the dynamic arrays, initializes
them, and starts the search.
29
int main (void)
{
cin >> n;
q = new int [n];
t = new int* [n];
for (int i = 0; i < n; i++)
{
t[i] = new int [n];
for (int j = 0; j < n; j ++)
t[i][j] = 0;
}
search (0);
delete [] q;
for (int i = 0; i < n; i ++) delete [] t[i];
delete [] t;
return 0;
}
If you do not yet fully understand how the above solution works, try tracing its execution by hand on
a 5 × 5 board, by simulating all the q[i] variables by hand. That will probably give you a good idea of
backtracking.
4.4 Some General Comments on Recursive Functions
At a high level, there are two types of recursion: direct and indirect. Direct recursion happens when a
function f calls itself. That’s what we have seen so far. Not quite as frequent, but still quite common, is
indirect recursion: you have two functions f, g, and f calls g, and g calls f. There is nothing particularly
deep about this distinction: we’re mentioning it here mostly so that you are familiar with the terms. If you
find yourself using indirect recursion and running into compiler errors, the problem could be that one of the
two function definitions has to be first, and when you define that function, the compiler does not know about
the other function yet. The way around that is as follows (in the examples, we assume that our functions
are from int to int, but there’s nothing special about that):
int f (int n); // just a declaration of the signature (this will often go in the .h file)
int g (int n)
{
// insert code for g here, including calls to f
}
int f (int n)
{
// insert code for f here, including calls to g
}
This way, when the compiler gets to the definition of g, it already knows that there is a function f; when
it gets to f, you have already defined g.
Among direct recursion, if the function calls itself just once, there are also two common terms: head
recursion, and tail recursion. These refer to when the recursive call happens. If it happens at the end
of a function, this is called tail recursion. Tail recursion is particularly easily replaced by a loop. When
the recursive call happens earlier than the end (e.g., at the beginning), this is called head recursion. Head
30
recursion turns out to be able to easily do some surprising things, such as print strings or linked lists in
reverse order. The distinction isn’t really a huge deal, but it’s probably good to have heard the terms.
Another thing to keep in mind is that there are some programming languages (called functional languages)
in which typically all problems are solved using recursion. Several of them do not even have loops. Some
examples of such languages are ML (or its variant OCAML), Lisp, Scheme, Haskell, Gofer. There are others.
Some of these (in particular, ML) are actually used in industry, and Lisp is used in the Emacs editor.
Functional languages make functions much more central than procedural ones. It is very typical to write
a function that takes another function as an argument. For instance, you may think of a function g that
operates on an array or list, and gets passed another function f as an argument, and what it does is apply f
to each element of the array/list. (For instance, you could have a function that turns each entry of an array
into a string.) This operation is called map. Another common thing is to have a function h that applies
some other function to compute a single output from an entire array/list. An example would be to sum up
all elements of an array/list, or to compute the maximum. This operation is called reduce.
Programs that are written by applying only these two types of operations can often be very easily
parallelized over large computation clusters, which is why the Map-Reduce framework has become quite
popular lately (e.g., in Google’s Hadoop). It has led to a resurgence in interest in some aspects of functional
programming.
From a practical perspective, when you write functional programs, it often takes longer to get the program
to compile, because many logical mistakes that lead to weird behavior in C++ can’t even be properly
implemented in a functional language. Once a functional program compiles correctly, it is much more often
bug-free than a procedural program (assuming both are written by fairly experienced programmers).
4.5 Recursive Definitions
So far, we have talked about recursion as a programming technique. An almost equally important application
of recursion is as a way of specifying objects concisely, by virtue of recursive definitions. These will come in
very handy later on when defining lists, stacks, heaps, trees, and others. To be ready for that when we need
it, we’ll practice here with a few easier recursive definitions. The first of these are examples that you can
define pretty easily without recursion (just like our earlier examples of using recursion as a programming
technique), while the later ones may be more involved (and would be very hard to define non-recursively).
1. A string of (lower-case) letters is either: (1) the empty string (often written as ǫ or λ), or (2) a letter
‘a’–‘z’, followed by a string of letters.
The recursion happens in case (2), and case (1) is the base case. Of course, for this one, we could just
have said that a string is a sequence of 0 or more lower-case letters, which would have been just fine.
But we’re practicing recursion on easy examples here.
2. A non-negative integer is either: (1) the number 0, or (2) n + 1, where n is a non-negative integer.
Here, defining what exactly integers are without referring to integers in the first place may be a little
puzzling. Recursion helps with that. It says that there is a first one (the number 0), and a way to get
from one to the next one. In this sense, 4 is really just shorthand for 0 + 1 + 1 + 1 + 1.
3. A palindrome is either: (1) the empty string ǫ, or (2) a single letter ‘a’–‘z’, or (3) a string xPx, where
x is a single letter ‘a‘–‘z’, and P is a palindrome itself.
Here, we needed two base cases; case (3) is the recursion. Notice that the other definition of a
palindrome, “a string that reads the same forward as backward”, is correct, but much more procedural:
it tells us how to test whether something is a palindrome (“Is it the same forward as backward?”), but
it doesn’t tell us how to describe all of them.
4. A simple algebraic expression consists of numbers, variables, parentheses, and + and *. (We leave out
- and / to keep this a little shorter.) We’ll use abundant parentheses and forget about the precedence
31
order here. We now want to express that something like “(5*(3+x))” is legal, while “x ( 5 * * + )” is
not. We can recursively say that the following are legal expressions:
• Any number. (This is a base case, and we could use our definitions of numbers above.)
• Any variable. (This is another base case; we could use our definition of strings.)
• (hAi + hBi), where both hAi and hBi are legal expressions themselves.
• (hAi ∗ hBi), where both hAi and hBi are legal expressions themselves.
For this example, you’d probably be very hard-pressed to come up with a non-recursive definition.
What we have written down here is called a “context-free grammar” (or CFG). There are tools (a
program called bison, which is the newer version of one called yacc) which, given such a recursive
definition, will automatically generate C code for parsing inputs that conform to the definition. They
are quite useful if you are trying to define your own input format or programming language.
5. In fact, following up on the previous discussion, you can write down a complete recursive definition of
the C or C++ programming language. In fact, that is how programming languages are specified. It
would be pretty hopeless to try this without recursion.
32
Chapter 5
Linked Lists
[Note: this chapter covers material of about 1 lecture.]
Arrays are nice, simple ways to store blocks of data, but we don’t always know the necessary array size
right off the bat. How many spaces should we reserve/allocate? Allocating up to an arbitrary size (say,
1000) is not a good idea because it may be too much or too little memory for particular cases.
Dynamically sized arrays, which we saw in the previous lectures, give us a partial solution to the problem:
at least, we don’t need to know the array size at compile time. But we do still need to know how large to
make the array at run time when we declare it. How large do you think that Facebook should have made
its user array when it started? If you have more and more customers arriving over time, it will be very hard
to guess the “right” size.
In this class, we will be looking at many data structures that don’t need to know the required number
of elements beforehand; rather, their size can dynamically change over time. Perhaps the easiest such data
structure is called the linked list; it nicely illustrates many of the techniques we’ll see reappear many times
later.
Consider how an array (dynamically or statically allocated) stores data. Whenever new users arrive, a
really convenient way of dealing with it would be to just ask the operating system to expand our block of
memory to the required size. However, there is no way to do this, and with good reason: the memory right
after our array may be in use for some other data, so expanding our array may overwrite existing memory
just after our array’s memory block. A way to “expand” our array may be instead to just get a second block
of memory somewhere else, and put a pointer at the end of our first block to indicate where the other half is.
This loses many of the advantages of arrays, such as being able to do simple arithmetic to look up elements.
But at least, it would solve the problem in principle. Once we think about it that way, we could go to the
extreme and make every element of our “array” point to the next element, removing all semblance of an
array. This is what a linked list is: a series of nodes where each one points to the next one in memory, and
each node contains a piece of data. We often depict linked lists as follows:
value
next •
value
next •
value
next •


1


1


1



*
P
P
P
P
P
P
i
head tail
Figure 5.1: Basic illustration of a linked list
Linked lists can be made as long as we want without declaring an initial size first. All we have to do is
33
attach a node after the last one, and ensure that the previously last element of the list now points to the
new one.
It may be instructive to compare vectors and linked lists. Of course, part of why we learn about these
things here is that we want to understand here how something like vectors works under the surface. Someone
programmed vectors as an abstract data type, by using the types of primitives we are learning about in this
class.
But also, there is a fundamental difference: What a vector really does is allocate a dynamic array and
keep track of its size. When the size is not sufficient any more, an array with larger size will be allocated.
(Typically, implementations will double the size, but this can often be changed by passing parameters into
the vector at creation.) Next, all data will be copied from the old array to the new one. This copying could
slow down the use of the data structures.
More importantly, vectors provide a functionality that we may or may not need: namely, accessing
elements by an index. Linked lists don’t really allow that. On the other hand, linked lists are tailored
towards appending elements cheaply, and traversing all elements. Like with practically any question about
“Which data structure should I use?”, the answer is “It depends”. Namely, it depends on what types of
operations you will frequently need to use.
Finally, the main “real” reason to study linked lists is that understanding them thoroughly practices
dynamic memory and recursion, and prepares you for some of the more complex data structures we will
see later in the class; they rely on many of the same principles as a linked list, but are more complex. It’s
usually a good idea to practice with easier examples first.
Some analogies to keep in mind for linked lists are the following:
• A treasure hunt for children. The parents provide a clue for the first treasure. When the children figure
out the clue, they go there, and find a treasure, along with a note that has the next clue. Following that
clue, they get to the second treasure, where they find another note with a clue to the next treasure,
and so on. The clues play the role of pointers.
• The game of “Assassin,” in which each participant gets the name of another participant to “kill” in
a an agreed-upon way (shooting with a nerf gun, touching with a plastic spoon, etc.). When one
participant is “killed,” his killer inherits his next assignment, and the winner is the last survivor.
Again, the assignments can be thought of to form a linked list (except the tail points back at the
head).
• There are several other analogies of sequential events, like dominoes, train cars, and others. What’s
nice about the two examples example is the explicit nature of the “pointers”.
5.1 Implementing Linked Lists
Each node/element in the linked lists contains data (such as an int, string, etc.) as well as a pointer to the
next node/element of the same type. Here, we will build a linked list of integers — it will be pretty obvious
how to alter this for other types. In order to keep track of these two elements, we create a struct which we
call Item.
struct Item {
int value;
Item *next;
Item (int val, Item *n)
{ value = val; next = n; }
}
Every Item has an int — a piece of data in the linked list — and a pointer next to another node. The
first node will have a pointer to the second node, and so on. For the last node, we need a way to make sure
34
to remember that there’s no node after it. The most common way is to have its next pointer go to NULL,
but some people also have it link back to itself instead.
The function Item we declare inside the struct is used to make initialization easy. This way, we can
just write something like Item* p = new Item (5, NULL); instead of Item *p = new Item; p-value
= 5; p-next = NULL; It is basically the same as a constructor of a class, which we will learn about in
roughly a week.
Note that, in order to access the first element of the list at all, we need a head pointer to the first Item.
(If we lose track of this, the rest of the list can no longer be accessed.)
5.1.1 Linked list operations
At a minimum, we want to be able to add elements to our list, remove them, and traverse the entire list.
Here is how to implement those.
Traversal: Unlike arrays, linked lists do not supply functionality to directly access the ith
element. Instead,
we start from the first node and then visit the next node repeatedly. To do so, we declare a variable
Item *p that will keep track of the current node we are looking at. p starts out as the pointer to the
head element. Then in each iteration of a for loop, we update it to its next pointer. So the code looks
like this:
void traverse (Item *head)
{
for (Item *p = head; p != NULL; p = p-next)
{ // Do something with p, such as print or read its value
}
}
We can also traverse a list recursively, as follows:
void traverse (Item *head)
{
// Do something with head, such as print or read its value
traverse (head-next);
}
The nice thing about the recursive implementation (besides its extreme simplicity) is that it is very
easy to make it traverse the list in reverse order, even if the list is singly linked. You simply change the
order of the two statements, calling traverse (head-next) before doing the processing (such as the
printing). Notice that the task of printing a linked list in reverse order is a very popular job/internship
interview question, because it tests knowledge of linked lists and recursion.
Addition: We take our input data item and create a new Item from it. This element will typically be
appended to the end of the list, so its next pointer will be set to NULL. (We could also add it at the
beginning of the list, which would change the implementation below.) In order to append it at the end
of the list, we first need to find the last element tail, then set its next pointer to our new element.
We need a special case for a previously empty list, as then, we also have to set the head pointer which
was previously NULL. In summary, the code for adding a new element looks as follows:
void append (Item *head, int n)
{
Item *newElement = new Item (n, NULL);
if (head == NULL) head = newElement;
else {
35
Item *p = head;
while (p-next != NULL) p = p-next;
p-next = newElement;
}
}
Notice the somewhat strange construction of the Item *head. We want to pass the head of the list,
which is an Item *. But we may also need to change it (when it was NULL), so we need to pass the
pointer by reference.
A somewhat shorter implementation can be obtained by using recursion:
void append (Item *head, int n)
{
if (head == NULL) head = new Item (n, NULL);
else append (head-next, n);
}
Notice that both the recursive and the iterative implementation have to traverse the entire list to find
the last element. This seems rather inefficient, and unnecessarily so. This is why typically, it is a good
idea to not only maintain a head pointer, but also a tail pointer to the last element of the list. That
way, we don’t have to traverse the entire list every time we want to add something.
Removal: If we are given a pointer Item *toRemove to an element of the list we’d like to remove, we’ll
eventually have the command delete toRemove; But before that, we also need to make sure that the
link structure of the list stays intact. To do so, we need a pointer prev to the element right before
toRemove in the list, so that we may set prev-next = toRemove-next;
One way to get this pointer (if it exists — otherwise, toRemove itself must be the head of the list)
is to start from the beginning of the list and scan through until we find the node p with p-next ==
toRemove. But that would take a long time and be cumbersome.
The better solution is to store in each Item not only a pointer to the next element in the list, but
also to the previous element. The result is called a doubly linked list, and unless you have a strong
reason to prefer a singly linked list (such as a job interview or homework assignment specifying it), you
should normally make your linked list doubly linked. In the definition of Item, we add the line Item
*prev; In the function for adding an element, we set newElement-prev = NULL in the first case, and
newElement-prev = p in the second case.
For removing an element, we can now write something like:
void remove (Item *head, Item *toRemove)
{
toRemove-prev-next = toRemove-next;
toRemove-next-prev = toRemove-prev;
delete toRemove;
}
This sets the next pointer of the preceding element to the next pointer of the element to be deleted,
effectively unlinking it. Similarly for the second line with the prev pointers. While this looks good
at first sight, we have to be more careful when the element we want to remove is the head or tail of
the list (or both, for a list of a single element). Then, toRemove-prev or toRemove-next could be
NULL, which means we can’t change their pointers. However, in those cases, we don’t need to update
the corresponding pointers, so the actual implementation looks as follows:
36
void remove (Item *head, Item *toRemove)
{
if (toRemove != head)
toRemove-prev-next = toRemove-next;
else head = toRemove-next;
if (toRemove-next != NULL)
toRemove-next-prev = toRemove-prev;
delete toRemove;
}
As we saw, the real reason for having doubly linked lists is that they make deletions much easier and
cleaner. (For your own amusement, you should perhaps also implement deletion in a singly-linked list, and
measure how much slower it is.) A side benefit is that a doubly linked list can be easily traversed back
to front. Sometimes, that’s listed as a reason for having a double linked list, but I think that that’s a
red herring: first, traversing a linked list back to front is not hard even for a singly-linked list, if you use
recursion. And second, it’s not a functionality that is often needed.
Once we really wrap our heads around the idea of having a pointer (or two) to other elements in our
Item, we may ask ourselves why not have more pointers to different elements. In fact, that is exactly what
we will be doing when we get to more complex data structures such as trees and heaps later on.
5.2 Recursive Definition of Linked Lists
While we know intuitively what a linked list is based on our discussion so far, we haven’t defined it formally.
In other words, we haven’t yet said exactly how to distinguish a “Linked List” from a “Not Linked List”.
In trying to come up with a definition, our intuition tells us that it is a bunch of items, where each item
has associated data and points to another item. But first of all, one of the items doesn’t point to anything.
And second, this would include a lot of nodes, all of which point to one other node. That’s not what we
want. We may try something like “a bunch of item each of which points to 1 other node, and is pointed to
by 1 other node, except for one node that points to no other node and 1 node which is not pointer to by any
other.” This would be correct, but getting a little cumbersome.
To keep the definition short yet clear, we can draw on recursive definitions which we saw a few lectures
ago, and define Linked Lists as follows:
• The empty list is a linked list.
• A node that points to a “shorter” linked list is a linked list.
We need the adjective “shorter” to exclude, for example, a one-node list where the node points to itself;
in that case, it would be pointing to a linked list of the same length. (Remember that for recursive functions
and definitions, we should only reference “smaller” objects, which we do so explicitly here.)
37
38
Chapter 6
Abstract Data Types
[Note: this chapter covers material of about 0.5 lectures.]
If we take a step back from what we have been doing with Linked Lists, we can think about the func-
tionality they enable. Let’s say we look at the version we just analyzed, which allows us to do three things:
1. Add an integer n to the list.
2. Remove an item from the list.
3. Print all the items in the list, in the order they were added.
If all we care about is being able to perform these three operations, it doesn’t matter so much whether
we implement them using linked lists, or perhaps arrays, or vectors, or some other method. What we really
want is some data structure that stores data internally, and allows us to use these three operations. In other
words, we focus on what we want to do, not how exactly it is done. Linked lists were one answer to how.
Being precise about the what, i.e., the operations we want to see implemented, or are implementing, is
what is specified as an Abstract Data Type (ADT). An ADT is defined entirely by the operations it supports,
such as the ones above. In this class, we will spend a lot of time focusing on the following three ADTs.
List: A list data type is defined by supporting the following key operations (where T denotes any type, such
as int, string, or something else):
1. insert (int position, T value): inserts the value right before the given position, moving all
the later elements one position to the right.
2. remove (int position): removes the value at the given position, moving all later elements one
position to the left.
3. set (int position, T value): overwrites the given position with the given value.
4. T get (int position): returns the value at the given position.
Thus, the List data type is a lot like an array in its functionality, but it allows inserting and removing
elements while shifting all others.
Notice that this definition is not quite complete. For instance, it does not tell us what values of
position are legal, or what happens when they are out of range. For instance, if we are trying to
insert something right before position 4 of a 2-element list, what should happen? Should an error be
signaled? Should the list be filled up with dummy elements to make it possible? Similarly, we haven’t
said what the legal ranges are of the position variable for the other operations. Here, we intend that
position needs to be between 0 and size-1 (where size is the number of elements in the list) for all
operations except insert, where it needs to be between 0 and size.
39
Of course, we could add other functions that should be supported, such as size() or isEmpty. The
textbook lists several others, but the four we have given are really the key functions that most define
a List.
You are probably already familiar with the C++ vector and deque data types, which are two natural
implementation of the List data type (and provide a few other functions). But they are not the only
ones, and we will look into this a bit more soon.
Bag (set): A set (called Bag in the textbook) supports the following operations:
1. add (T item): Adds the item into the set.
2. remove (T item): Removes the item from the set.
3. bool contains (T item): Returns whether the set contains the given item.
This is a rudimentary implementation of a mathematical set (see CSCI 170). Again, our specification
isn’t complete. For instance, we do not say what should happen if an item is added multiple times: are
multiple copies added, or only one, and is an error signaled? If multiple copies are added, then what
does remove do? Does it remove just one of them or multiple?
Typically, in a set, we only allow one copy. If we want to allow multiple copies of the same item, we
call it a multiset.
Dictionary (map): A map (or Dictionary, as it is called in the textbook) is a data structure for creating
and querying associations between keys and values. Both keys and values can be arbitrary data types
keyType and valueType themselves. The supported operations are:
1. add (keyType key, valueType value): Adds a mapping from key to value.
2. remove (keyType key): Removes the mapping for key.
3. valueType get (keyType key): Returns the value that key maps to.
Again, this is a bit underspecified. What happens if the given key is not in the map? What should
remove and get do now? Will they signal an error? Also, if another mapping is added for the same
key, what will happen? Can two mappings co-exist? Will an error be signaled? Does the new one
overwrite the old one?
Typically, one would not allow co-existences of mappings for the same keys. The data type implements
basically a (partial) function from keys to values, and you can’t have a key mapping to multiple values
in a function.
Let’s look a bit at commonalities and differences between these abstract data types. First, all of them
support storing data and accessing them; after all, they are abstract data types.
The big difference between List and the others is that a list really cares about the order. A list in which
the number 2 is in position 0 and the number 5 in position 1 is different from a list in which the two numbers
are in opposite order. On the other hand, neither a set nor a map care about order. An element is either in
a set or not, but there isn’t even any meaning to such a thing as an index in a set. Similarly, elements map
to others under a map, but there is no sense in which the elements are ordered or in a designated position.
Let’s illustrate this in terms of some applications for these data structures. A List may be a good thing
for such a thing as a music playlist, the lines in a computer program, or the pages of a book. For all of
them, the order of the items is important, as is the ability to access specific items by index (page, or place
in a playlist). On the other hand, a map is a natural fit for any kind of directory (student records or e-mail
address, phone book, dictionary of words, web page lookup by search query). Here, it doesn’t matter in
what order the items are stored, so long as it is easy, given the key, to find the corresponding record.
While a List is thus fundamentally different from a map, a set is actually quite similar to a map. The
main difference is that for a map, we store additional information for each item that we store, whereas for
the set, we only remember the presence (or absence) of items. In other words, set is a special case of map,
40
where the value associated with each key is some kind of dummy that is never used. For that reason, we
will not really look much at implementing set. Instead, we will put a lot of emphasis on implementing map,
and set then falls out for free.
Of course, just because a List by itself is a very different object from a map does not mean we cannot
use one to implement the other. In fact, a pretty natural (though not very efficient) way of implementing
maps is to use lists as the place to store the data. It is important to notice, though that this is now a “how”
question: just because we can use one technology to solve a different problem does not mean that the two
are the same.
Learning which ADT most naturally fits the requirements of what you are trying to do is an important
skill. That way, you separate out the implementation of whatever uses the ADT from the implementation
of the ADT itself, and structure your code much better.
41
42
Chapter 7
Classes and Objects
[Note: this chapter covers material of about 0.5 lectures.]
Last time, we learned about Abstract Data Types. The notion of an Abstract Data Type goes hand
in hand with Object-Oriented Design. In Object-Oriented Design of programs, we group together data
items with the operations that work on them into classes; classes are data types, and we can then generate
instances of these classes called objects. The objects have both the data inside them and the functions that
operate on the data.
When specifying a class, we usually take good care to keep two things separate: (1) the specification of
the functions that other classes can call. This is like the specification of an abstract data type, and usually
given in a header (.h) file. (2) the implementation of how the functions are actually doing their job, and
how data items are stored internally. This is in a .cpp file, and hidden from other classes. Hiding it allows
us to change the implementation without needing to change the implementation of other pieces of code that
use our classes. We will see way more about classes and object-oriented design in the next few lectures.
A class maps almost exactly to an Abstract Data Type; you can think of it as essentially a struct with
functions in it. An object is one data item whose type matches a class; think of a class as the abstract notion
of a car and the operations it supports (accelerate, slow down, honk the horn, switch on/off the lights, . . .),
while an object is a particular car that implements these properties. So while there is only one “car” class,
there can be many objects of type “car”.
Remember how we implemented Linked Lists recently? Inside our main function, we had a variable for
the head of the list, and we provided functions for appending and removing items from the list, as well as for
printing (or processing) items. In a sense, the data storage and the functions belong together. The append
or remove function is useless without the data to operate on, and the data do not help us without those
functions to operate on them. Classes are exactly the way to group them together.
Once you get more experienced with object-oriented design ideas, you will start thinking of your objects
not just as pieces of code, but real entities which sometimes interact with each other in complex ways,
notifying each other of things going on, helping each other out, and so on. One sign of a more experienced
programmer in an object-oriented language (such as C++ or Java) is to be able to think of code not just as
many lines in a row, but as almost an eco-system of different objects, each fulfilling its own little well-defined
role, and interacting with each other in interesting and productive ways.
7.1 Header Files and Declarations
As a first cut, our class declaration for a linked list of integers may look as follows:
class IntLinkedList {
void append (int n);
void remove (Item *toRemove);
void printlist ();
43
Item *head;
}
Notice that the class includes both functions and data (head). Also notice that because the class contains
the pointer head itself, we do not need to pass the head of the list to the function any more, as we did earlier.
Of course, we still need to implement the functions. But before going there, we notice that this is what
the rest of the world really needs to know about our class: they need to know the functions to invoke and
(maybe) about the variables we use. Thus, these parts should go into a header file (with extension .h), while
the actual implementation will go into a .cpp file.
The header file should make copious use of comments to describe what exactly the functions do. For
instance, what happens if the toRemove is not actually an element of the list? What format is used for
printing the list?
A moment ago, we said that the header file also contains the variables head inside the class. While this
is true, to keep our code as modular as possible, other pieces of code should not really be allowed to directly
access those variables1
. For instance, if a week later, we decide to use a vector implementation to achieve
the same functionality, we don’t want any code out there to rely on the existence of a variable named head.
We want to hide as much about the class as possible.
The way to do this is to declare the variables private: this means that only functions in the class are
allowed to access those variables, but not any other code. (We can also declare functions to be private,
something we mostly do for internal helper functions.) The opposite of private variables/functions are
public ones: these can be used by all other parts of the program. (There is also a protected modifier,
which we will learn about later, when we learn about inheritance.) private, public, and protected are
called access modifiers, since they modify who can access the corresponding elements of the class. Our new
version of the class declaration will look as follows:
class IntLinkedList {
public:
void append (int n);
void remove (Item *toRemove);
void printlist ();
private:
Item *head;
}
In this context, it is also important to know about get and set functions. Often, you will declare a class
for which you really kind of do want to be able to overwrite the elements. For instance, think about Item,
which we previously declared as a struct. Instead, we could declare it as a class, as follows:
class Item {
public:
int value;
Item *prev, *next;
}
In order to emulate the functionality of a struct when using a class, it is necessary to make all elements
public. But making all elements public is very bad style: it allows other code to see the internals of a
class, which could mean that changing things in the future is harder. But if we make the fields private,
how can we read and change the value field? The answer is to add two functions getValue and setValue,
which will read/write the value variable. Often, these functions can be as easy as return value; or value
1One downside of this example is that our remove function actually works with a pointer to an element. So in this sense,
the implementation is not really hidden. We should overlook this issue for now, for the sake of continuing with this example.
44
= n;. The reason to have them is that you may later change your mind about something. For instance, you
may decide that value is a bad name for the variable, and you want to call it storedNumber instead. But if
you do that, then all code that referenced value will break. If you have a getValue and setValue function,
then you just have to change their implementation.
More importantly, the get and set functions allow you to filter out illegal values, or perform other
transformations. For instance, if you decide that only positive numbers are supposed to be stored, then you
can create an error (or do something else) whenever the given value is negative. This kind of filtering can be
very useful in implementing something like a Date class for storing a day of the year, or others where there
is a natural desire to restrict values that can be stored.
7.2 Implementation of Member Functions
Now that we have sorted out the header file, we can think about the implementation, which we would do in
a file called IntLinkedList.cpp. At the top, we would include the header file:
//IntLinkedList.cpp
#include IntLinkedList.h
Then, we can start associating functions with our IntLinkedList. The syntax for implementing class
functions is of the following form:
void IntLinkedList::append (int n) {
// implementation...
}
That is, the function name is really the class name, followed by two colons, and then the name of the
function. This tells the compiler that this code belongs to the class. If we did not include IntLinkedList::
in the method signature, append would just be a simple global function. The compiler has no way to know
that we intend our function to be part of a class, unless we either put it inside class IntLinkedList { ...
} (which is bad style), or put the class name in the signature, as we just did.
For the actual implementation of the functions, we can just basically copy over the code from our previous
linked list implementation — the only difference is that now, the functions are member functions of a class,
rather than global functions, and the variable head is a (private) member variable of the class rather than
having to be passed as a function parameter. So we won’t repeat all the code here. Inside the class, you can
treat all variables (even the private ones) like global variables: all member functions know about all of the
class’s members.
However, before moving on, let us briefly revisit our nice recursive traverse() function, which we could
use, for instance, to print the entire list, or print it in reverse order. Let’s look at the reverse order print
version, and call it printreverse().
void printreverse (Item *head)
{
cout  head-value;
printreverse (head-next);
}
Here, the head pointer wasn’t always pointing to the actual head of the list, but rather, we had it point to
different members of the list during different function calls. On the other hand, the overall public signature
for the function should probably be
...
public:
void printreverse ();
...
45
So how can we implement the recursive function? The answer is that we declare a private helper function.
Something like
...
private:
void _printreversehelper (Item *p);
...
Then, the implementation of printreverse is just
void LinkedList::printreverse ()
{ _printreversehelper (head); }
7.3 Constructors and Destructors
We could now use our implementation to create an object of type IntLinkedList and append/remove
elements from it:
// main.cpp
int main (void)
{
IntLinkedList *myList = new IntLinkedList;
for (int i = 1; i  10; i ++) myList.append (i);
myList.printlist ();
return 0;
}
But one concern at this point is: where does the head variable get initialized? Earlier, we said that we
express an empty linked list by having head=NULL. How can we be assured that when we create a new object
of type IntLinkedList, the pointer is actually NULL? Because we made the variables private, we cannot add
the line head=NULL; before the loop. So perhaps, we should create a member function initialize in the
class IntLinkedList. Then, we just have to remember to call it right after generating the object.
Fortunately, C++ has a mechanism called constructor to do just that. We can define functions with a
special name which get automatically called when we create a new object of that class. That’s the right
place to put initialization code. The name of a constructor is always the name of the class, and a constructor
does not return anything. The constructor will be run as soon as an object is created. It looks as follows:
IntLinkedList::IntLinkedList() {
head = NULL;
}
Notice (again) that there is no return type. You can have multiple constructors; for instance, we may add a
constructor that initializes the list to contain one element with a given number already:
IntLinkedList::IntLinkedList(int n) {
head = NULL;
append (n);
}
If we define two constructors, when we create the object, we can write things like
IntLinkedList *p = new IntLinkedList (), *q = new IntLinkedList (3);
46
The first case calls the first constructor (and creates an empty list), while the second calls the second
constructor (and creates a list with one item, containing the number 3). This is often useful for copying the
data from one object to another, or reading an entire object from a file or a string. (We will learn more
about those so-called copy constructors soon.) So long as their signatures (number or types of arguments)
are different, we can create as many constructors as we want. Of course, they should all be declared in the
header file, and implemented in the .cpp file.
Similar to the initialization, we may also want to destroy data objects we created when deleting an object.
Per default, when we call delete myList; in the code above, it will free up the space used for storing the
pointer head, but it will not free up any of the memory for the elements of the linked list. That is a good
thing — after all, other code may still need them. So if we do want them deleted, we need to create a
function that does the “opposite” of the initialization.
Such a function is called a destructor, and the destructor is automatically called when the object is
deleted. Just as for constructors, there is a naming convention for destructors: they always have the same
name as the class, with a preceding tilde: IntLinkedList::~IntLinkedList(). Our implementation of the
destructor will look something like the following:
IntLinkedList::~IntLinkedList ()
{
Item *p = head, q;
while (p != NULL)
{
q = p-next;
delete p;
p = q;
}
}
7.4 The this pointer
Sometimes, in implementing a member function, you will run into scoping issues, such as: a function has a
local variable with the same name as a member variable. For instance, imagine that we write a setValue
function for the class Item, and it looks as follows:
void setValue (int value)
{ // code here
}
We now want to somehow set the value field of the class to the variable value, but writing value=value
clearly isn’t going to work, since — whichever variable value actually refers to (the answer is the function’s
parameter) — both mentions of value will refer to the same variable. To make explicit that an assignment
or other operation is talking about a member of the particular object to which the function belongs, there
is the keyword this.
this is always a pointer to the object to which the method belongs. So we can write this-value to
refer to the data of the object itself, and write the above code as:
void setValue (int value)
{ this-value = value; }
There are other, more important, uses of the this pointer that you will learn about, partly in this class,
and more importantly as you move on. One of the most important ones arises when you have one object
A generating another object B, and B is supposed to know A’s identity. For instance, A could be the main
part of an application, and B a part of the user interface that is being created on the fly. Now, A might want
B to call some function in A when the user presses a certain button. But for that, B needs to be told the
47
“identity” of A. The way one normally does this is that when B is created, it gets passed the this pointer
of A, and thus knows about A.
48
Chapter 8
Templates
[Note: this chapter covers material of about 0.5 lectures.]
Once we have carefully implemented a class IntLinkedList, we may discover that it is quite nice, and
we would like to also have a LinkedList of strings. And one for users, and for web pages, and so many other
types of objects. The obvious solution would be to copy the code we wrote for integers, and then replace the
word int with string everywhere. This will cause a lot of code duplication. In particular, imagine now that
after making 20 different copies, we discover that the original implementation of IntLinkedList actually
had a few bugs. Now we have to fix them in all copies.
What we’d really want is a mechanism for creating a “generic” LinkedList class, which allows us to
substitute in any type of data, rather than just int, or just string. This way, we avoid code duplication
and all the problems it creates (in particular in terms of debugging and keeping versions in synch).
The C++ mechanism for doing this is called templates. The syntax is a little clunky, and because of
the particular way in which C++ implements it, it unfortunately also forces us to give up some of our good
coding practices. (It was not originally part of the C++ design, but rather grafted on top later, which
explains why it does not fit very smoothly.)
The basic way of defining templates classes is explained first via an implementation of a generic struct
Item:
template class T
struct Item
{
T data;
ItemT *prev, *next;
}
This tells C++ that we are defining a template class, which will be generally based on another class. We
call that class T for now. By writing this, we can treat T as though it were an actual name of an existing
class.
What really happens is the following. Suppose that in some later code, we want to create a ListElement
for strings. We will now write:
Itemstring *it = new Itemstring;
When the compiler sees this code, it will copy over the template code, and wherever it sees T, it will
instead substitute string. In other words, it will exactly do the text replacement that we wanted to avoid
doing by hand. Of course, in this way, we only maintain one piece of code, and the rest is generated only at
compile time, and automatically. That’s how templates save us work.
We can now apply the same ideas to our LinkedList class:
49
template class T
class LinkedList {
public:
LinkedList ();
~LinkedList ();
void append (T value);
void remove (ItemT *toRemove);
void printlist ();
private:
ItemT *head;
}
When we implement class functions of template classes, the syntax is as follows:
template class T
LinkedListT::add (T item) {
//code
}
Note the templateclass T line and the T in the class name. You have to put these in front of each
member function definition; otherwise, how is the compiler to know that you are implementing a member
function of the template class. Usually, if you get a “template undefined” error, you might have missed a
T. (Note that the identifier T is arbitrary — you can use X or Type or whatever).
By the way, the keyword template cannot be used just with classes, but also with other things, such as
functions. For instance, we could write the following:
template class Y
Y minimum (Y a, Y b)
{ if (a  b) return a; else return b; }
Another thing worth noting about templates is that you can’t just use one type: you can use multiple
types. For instance, you will remember that a map has two types: one for the key, and one for the value. We
could specify that as follows:
template class keyType, class valueType
class map
{
// prototypes of functions and data go here
}
Templates were kind of grafted on top of C++, and not really part of the initial design. That is why the
syntax is rather unpleasant, and the “obvious” way of doing things causes all kinds of compiler and linker
problems. In order to get things to work, you will sometimes need to deviate from good coding practices,
for instance by having #include of a .cpp file. Check the course coding guide for detailed information on
how to deal with some of the most common problems. Seriously, read it very carefully before attempting to
code anything with templates. It will save you many hours of debugging - promised!
8.1 The const keyword
Because the type (referred to by T above) could be large, such as a custom type (think about a linked list
each of whose items is a vector or some complicated struct), it is usually preferable to pass the parameters
by reference, to avoid copying the whole item. So we would revise the syntax for the above definition as
follows:
50
Other documents randomly have
different content
Data Structures And Object Oriented Design Lecture Notes Usc Csci104 Itebooks
Data Structures And Object Oriented Design Lecture Notes Usc Csci104 Itebooks
Data Structures And Object Oriented Design Lecture Notes Usc Csci104 Itebooks
The Project Gutenberg eBook of Il nemico è in
noi
This ebook is for the use of anyone anywhere in the United States
and most other parts of the world at no cost and with almost no
restrictions whatsoever. You may copy it, give it away or re-use it
under the terms of the Project Gutenberg License included with this
ebook or online at www.gutenberg.org. If you are not located in the
United States, you will have to check the laws of the country where
you are located before using this eBook.
Title: Il nemico è in noi
Author: Luigi Capuana
Release date: May 4, 2013 [eBook #42643]
Most recently updated: October 23, 2024
Language: Italian
Credits: Produced by Carlo Traverso, Claudio Paganelli, Barbara
Magni and the Online Distributed Proofreading Team at
http://www.pgdp.net (This file was produced from images
generously made available by The Internet Archive)
*** START OF THE PROJECT GUTENBERG EBOOK IL NEMICO È IN
NOI ***
Data Structures And Object Oriented Design Lecture Notes Usc Csci104 Itebooks
“Semprevivi„
BIBLIOTECA POPOLARE CONTEMPORANEA
LUIGI CAPUANA
Il nemico è in noi
C ATA N I A
Cav. Niccolò Giannotta, Editore
Libraio della Real Casa
—
1914
PROPRIETÀ LETTERARIA
Catania — Offic. tipografica Giannotta nel R. Ospizio di Beneficenza
INDICE
Avvertenza pag. vii
Tormenta 1
Storia fosca 45
Convalescenza 69
Un bacio 87
Contrasto 99
L'ideale di Pìula 115
Un caso di sonnambulismo 131
Il dottor Cymbalus 171
Nota 205
AVVERTENZA
Il dottor Cymbalus che chiude questo volume è la mia prima novella.
Fu pubblicata in La Nazione, nel settembre del 1865, ed ebbe
l'immeritato onore di esser discussa, in un lungo articolo, dal
corrispondente della Gazzetta di Augusta.
Quel bravo signore avea scambiato la mia fantastica narrazione per
un tentativo di studio della vita tedesca contemporanea, e si era
affannato a dimostrare che gli italiani ne avevano una stranissima
idea.
E quando io, che lo vedevo quasi tutti i giorni nella redazione di Via
Faenza, tentai di fargli capire l'equivoco in cui era caduto, non riuscii
a convincerlo di non aver mai sognato di credere che qualche grande
scienziato tedesco somigliasse al mio dottor Cymbalus, nè che i
giovani sentimentali della Germania del 1855 avessero qualcosa di
comune col mio William Usinger.
Così questo volume finisce come avrebbe dovuto cominciare, se
avessi voluto accennare al lettore le varie fasi delle mie esperienze
narrative, fino a Tormenta! che è tra le ultime cose da me scritte.
Caso mai, quest'avvertenza potrà, forse, avere qualche valore pei
critici. E per essi, se si degnassero di notarlo, è stata conservata la
data della pubblicazione di ogni novella.
Luigi Capuana.
TORMENTA!
Pietro Borgagli osservava con crescente terrore la rapida
trasformazione che avveniva in sua moglie.
Ai primi sintomi della strana gelosia egli aveva sorriso.
Da qualche mese in qua però la sua Diana andava insolitamente a
sedersi su una poltrona a dondolo in quello studio che non sembrava
stanza di raccoglimento e di lavoro per uno scrittore, ma piccola
serra di piante da salotto e di vasi da fiori; e là ella faceva sembiante
di svagarsi a leggere.
A traverso le foglie del bambù che nascondeva un po' la poltrona,
alzando gli occhi dalle pagine già riempite di grossa nervosa
scrittura, egli sorprendeva spesso la moglie fissamente intenta a
guardarlo sotto le sopracciglia corrugate per lo sforzo, pareva, di
voler vederci meglio.
Gli sembrava impossibile, che ella si sentisse invadere da
inesplicabile diffidenza dell'opera letteraria di lui, ora che la felicità
del possesso dell'adorata creatura, contèsagli per due anni da
insidiose circostanze, davano alla sua immaginazione un rigoglio che
i critici notavano con unanime compiacenza all'apparizione di ogni
suo nuovo lavoro.
Infatti egli sentiva dentro di sè qualcosa di più fresco, di più agile, di
alato quasi; e il suo godimento artistico durante la produzione era
così acuto, così intenso da fargli augurare che i lettori risentissero
almeno un terzo dell'effetto di bellezza e di vita da lui provato
scrivendo.
Il giorno delle sue nozze era stato pubblicato in elegantissima
edizione il suo primo romanzo: Il Gran Sogno. L'editore ne aveva
fatto tirare una copia speciale, su carta della Cina con larghissimi
margini, dove parecchi artisti avevano profuso disegni a penna, e
figure acquerellate che davano a quella copia un valore straordinario.
Rilegata in pergamena, con ornamenti quattrocenteschi, racchiusa in
un cofanetto di pelle dello stesso stile, era stata il più prezioso dono
delle loro nozze e certamente il più gradito.
Il cofanetto portava impresso in oro il motto Sic semper! E Pietro
aveva presentato, cerimoniosamente, piegando un ginocchio, il
regalo del munifico editore alla giovane signora che baciò in fronte,
tremando dalla commozione, il paggio editoriale, come egli si disse,
già commosso quanto lei.
Diana, distratta dal viaggio di nozze, dal trambusto di visite, di
ricevimenti, di spettacoli al loro ritorno in città — quando la stagione
invernale travolse nel suo vortice la giovane coppia, che la bellezza e
l'ingegno rendevano ricercatissima — dopo sei mesi di vita coniugale
non aveva ancora trovato un po' di tempo per tagliare e leggere la
copia ordinaria del romanzo che suo marito le aveva regalata con la
semplice dedica: a Diana Cantelli, mio vero «Gran sogno!.
E sentì un po' di mortificazione, una sera in casa Marzani, quando la
giovane signora, sua amica di collegio, le disse:
— Ah, quel Gran Sogno di tuo marito! Un capolavoro di sentimento,
di passione, di finezza! L'ho riletto due volte! Gli scrittori hanno un
bel dire che si tratta di semplici invenzioni della loro immaginazione
con qualche leggera tinta di realtà. Io credo, invece, che sia il
contrario.
E vedendo che Diana, rimasta confusa, non sapeva che cosa
rispondere, riprese maliziosamente:
— Di' la verità: non te l'ha mai dato a leggere?
— Sì, e con questa dedica: a Diana Cantelli, mio vero Gran sogno.
Solamente....
— Solamente....
— Ti confesso che non ho ancora avuto la curiosità di aprirlo; mi
basta di leggere... — e sorrise — il suo autore, per ora.
Intanto la mattina dopo si affrettò a tagliare il volume e, chiusa nel
suo studiolo, cominciò a divorare avidamente quelle pagine che,
come più andava avanti, più le producevano la triste sensazione di
farla inoltrare negli oscuri penetrali dell'animo di suo marito, quasi di
nascosto, di sorpresa, e dov'ella non sarebbe forse mai arrivata
senza le suggestive parole della sua amica: Gli scrittori hanno un bel
dire..... — Sì, sì, era impossibile che quei personaggi non fossero
davvero esistiti, che quelle violenti passioni non si fossero davvero
scatenate nel cuore di essi fino al delirio, fino al delitto; che quelle
parole, quelle frasi caratteristiche non fossero state davvero
pronunziate con la desolata espressione che le pareva di sentire fin
nelle righe del libro.
E come poteva mai darsi che un uomo indovinasse o inventasse
quelle passioni, quei contrasti, quelle lotte senza che il suo cuore vi
avesse davvero partecipato in una o in altra maniera? Se non
precisamente a quel modo, se con particolari diversi, non voleva dir
nulla; forse anche con maggior violenza, con circostanze tali, senza
dubbio, da far esitare la penna più esperta.... da costringerla ad
attenuare, a travisare un po' la realtà, a deformarla probabilmente,
per non far riconoscere persone e fatti e suscitare scandali e
recriminazioni.
Si maravigliava ella stessa di quell'improvvisa compenetrazione che
le faceva intravedere l'intimo legame tra l'autore e l'opera sua. Non
le era mai passato per la mente che ognuna delle figure,
specialmente di donna — erano quelle che più la interessavano —
fossero ancora qualcosa di vivo, di segreto nel cuore dell'artista, se
egli sentiva la necessità di riprodurle con la magìa della sua parola,
quasi per fissarle meglio, e perpetuarle per sè e per gli altri. Lo
capiva ora tutt'a un tratto; e mentre fino a poche ore addietro ella si
credeva in pieno possesso del cuore e dello spirito di suo marito, ora
le sembrava di esserselo sentito sfuggire lentamente, di mano in
mano, senza nessuna lusinga di più tornare a riconquistarlo.
Reagì contro questa impressione, pensando che ben altro era il
sapersi legata a lui da attuali forti vincoli di sentimenti e di carne,
che non il sopravvivere, se pur poteva chiamarsi tale,
nell'immaginazione, nel ricordo. Bisognava anzi già esser pervenuto
a un punto di indifferenza completa per cacciar via fuori di sè, quasi
per sbarazzarsene, quei fantasmi di una realtà una volta cara, e che,
per felice disposizione d'ingegno, assumevano forma d'arte, e
dovevano probabilmente riuscire irriconoscibili a colui stesso che li
aveva a quel modo fatti vivere.
Rilesse alcune pagine, sfogliando il volume, fermandosi a un nome di
donna, seguendolo un po', abbandonandolo, riprendendolo verso la
fine nella scena più violenta, e sorrise, rassicurata.
Entrò col libro in mano nello studio del marito.
— Oh! finalmente...
— Sì, finalmente — ella lo interruppe, agitando il volume con
grazioso gesto di minaccia — e puoi esser contento di quel che mi
hai fatto soffrire.
Egli ebbe una mossa di stupore.
— Siete dei grandi sfacciati voi scrittori — riprese Diana con accento
indefinibile, di scherzo e di serietà. — Vi compiacete di raccontarci le
vostre prodezze, fingendo di raccontare la storia degli altri,
assegnandovi la più bella parte negli avvenimenti, cioè quella che a
voi sembra la più bella, e vi figurate così di aver gabbato i lettori. Ma
sai che questo tuo Gherardo del Gran sogno è uomo spregevole, con
tutte le sue arie di incorreggibile sentimentale?
— Spregevole poi... — fece Borgagli, lusingato di veder presa sul
serio la sua opera d'arte.
— Ah! Tu lo difendi; è naturale. Quanta parte di te c'entra,
confessalo, in quell'ambiguo carattere?
— Ambiguo, no; complicato forse volevi dire. Allora facevo anche io il
mio gran sogno e tu eri un po' la donna intravista, inseguita e non
mai raggiunta, per dimenticare la quale Gherardo...
— E perchè voleva dimenticarla?
— Ecco — disse il marito, alzandosi da sedere e raccogliendo i fogli
sparsi su la scrivania già coperti della sua grossa e nervosa scrittura.
— Tu non puoi immaginare il piacere che mi produci in questo
momento, ragionando dei personaggi e dei fatti del mio romanzo
come di persone e di avvenimenti reali. Certamente qualcosa di mio
c'è in essi e del me più schietto e più sincero. Sono passati per la
mia immaginazione, si sono fusi, si sono organizzati in essa pur
tentando di assumere una loro distinta personalità. Io stesso non
saprei precisamente dirti come questo avvenga. L'artista, scrivendo,
è in una specie di semi inconsapevolezza, sta a guardare,
maravigliato — più che non farebbero gli altri — quel che avviene
dentro di lui, il miracolo della creazione; e forse è il solo a goderne
pienamente, perchè, soltanto lui può osservarne il processo di mano
in mano che esso avviene. Quante viete cose ti dico, mentre dovrei
baciare la bella mano che tiene ancora il mio libro, e la bellissima
bocca che mi ha detto: Mi hai fatto soffrire!
Diana si lasciò baciare la mano e la bocca, spalancando i vividi occhi
caprini in viso al marito, un po' irrigidita di fronte alla calda effusione
di quel ringraziamento, di cui ella non riusciva a capire il preciso
significato, se egli non si burlasse, per caso, della sua femminile
ingenuità.
Parecchi volumi di novelle di suo marito ella aveva letti durante il
fidanzamento, ma senza interessarsi di scoprire quel che esse
potessero ricordare del passato di lui. Non aveva mai fatto nessuna
distinzione tra quelle narrazioni rapide, appassionate, contenenti un
fiero dramma interiore che talvolta scoppiava in tragedia; tra
parecchie di esse piene di finezze, argute, quasi maligne,
specialmente quando si trattava di rari caratteri di donne; e le molte
novelle drammatiche o ironiche di altri autori italiani e stranieri. Le
une e le altre erano servite a procurarle un po' di distrazione, con
lieve godimento intellettuale.
Ora invece si era messa a rileggere non solamente le novelle e i
racconti di lui già raccolti in quattro bei volumi, ma anche le altre
ancora disperse tra le colonne dei giornali e le pagine delle rassegne,
dove suo marito le spargeva con profusione da gran signore, e che
egli ritardava a raccogliere, attendendo che le impressioni della
recente lettura si fossero alquanto scancellate e fosse solleticato il
gusto di tornare a leggerle.
Dapprima Borgagli si era rallegrato della conquista di una lettrice che
non era, per cultura, per affettuoso interesse, lettrice ordinaria.
Diceva anzi: della riconquista perchè, di giorno in giorno, notava
certe sottili osservazioni intorno a parecchie novelle che prima le
erano passate quasi inosservate. Pareva che ella avesse bisogno di
schiarimenti, di dilucidazioni a proposito di una battuta di dialogo, di
un motto passionale fatto sfuggire dalla bocca di una creatura, in un
terribile momento, quando sembrava che soltanto quella parola
risolvesse la inevitabile crisi di un povero cuore.
— Perchè ha risposto così?... Come hai tu saputo che ha risposto
così? — domandava infine ansiosamente.
— Perchè la situazione, capisci, portava che doveva rispondere così
— egli spiegava, stupito di quelle insistenti domande. — Non ho
saputo, ho intuito che ha risposto... cioè che avrebbe risposto così.
— Tu hai detto una volta, parlando con Leoni, che certe frasi, certi
motti non s'inventano: si prendono dal vero, in circostanze inattese,
uditi direttamente o riferiti.
— Non ho rossore di confessare — rispose, sorridendo, Borgagli —
che quattro o cinque delle frasi che più destano ammirazione in
alcune mie novelle, io me le sono appropriate, come chi trova per via
un diamante, smarrito del suo sbadato proprietario. E il paragone è
soltanto giusto riguardo al diamante. Coloro che han pronunziate
quelle frasi, quei motti sublimi o caratteristi, ne ignoravano il valore.
Il pubblico dev'essermi grato di non averli lasciati disperdere.
E il giorno dopo, a proposito di altre novelle, di altri racconti
specialmente di quelli usciti recentemente dalla penna di lui, ella
riprendeva la sua indagine con maggiore insistenza; e Pietro già
notava con qualche sgomento, quell'accento di profonda tristezza
con cui ella interrogava, quella espressione del viso che significava la
dolorosa delusione di non aver raggiunto il suo scopo.
— Ma che hai? che vuoi sapere — le diceva. — Sembra impossibile
che una persona intelligentissima e colta come te, cerchi di scoprire
in un'opera d'arte quel che non c'è. Il mio passato? Ma tu, adorata
mia, da due anni hai scancellato tutto, tutto! Hai fatto cor novum
dentro di me, un cuore nuovo, dove non può più esservi posto
neppure pei ricordi, tanto tu lo riempi di te, rinnovando ogni giorno,
ogni ora, ogni istante il tuo sovrano possesso. Come non lo intendi?
Come non ti accorgi del male che ti fai? Giacchè tu soffri — non
negarlo — tu diventi sempre eccitabile, sempre più nervosa; ed io ho
paura quando ti sento tremare, fremere fra le mie braccia, come in
questo momento, scossa da brividi molto diversi da quelli, dolcissimi,
di una volta.
— No, no, t'inganni! — ella tentava di negare. — Forse attraverso un
periodo strano, di facili eccitazioni, nel quale mi sembra di sentir
sviluppato in me un senso inesplicabile di veggenza, che però è
ancora in uno stato torbido, fosco.
— E rimarrà sempre tale, perchè sei tu che tenti di formartelo
artificiosamente e non riesci; tenti l'impossibile. Che t'importa di
sapere, con precisione, quel che c'è di me, della mia vita, del mio
passato, del mio cuore, del mio spirito nell'opera mia narrativa?
Qualcosa, molto o poco, dev'esserci, per forza. Ma ormai questo
qualcosa, poco o molto, non ha nessuna importanza; è ridotto, per
dir così, proprio a materiale — nota: a materiale — da servire alla
costruzione dell'opera d'arte. L'importante è la forza creatrice che ora
aiuta ad adoprarlo; e questa forza creatrice ha un personale
slanciato, capelli di un biondo scuro, occhi grandi, caprini, scintillanti,
labbra rosee, mani — oh, mani! — minuscole e braccia
morbidissime; e dovrebbe stringermi più forte, più forte... così; e
baciarmi così, così, e giurarmi di non torturarsi più, se non vuol farmi
maledire quella che è stata il mio orgoglio, la mia consolazione, la
mia ragione di vivere prima che entrasse in questa casa l'attuale
gentile dominatrice, ed è e sarà, da ora in poi, il mio omaggio, la mia
raccolta di fiori immortali da spargerle ai piedi: intendo la mia opera
d'arte. Me lo giuri?.... Me lo giuri dunque?
Diana, vinta da intensa commozione, si abbandonò singhiozzante tra
le braccia del marito balbettando:
— Sì! Sì!
Sdraiata indolentemente su la poltrona a dondolo, con un libro in
mano, che di tratto in tratto quasi le cascava sui ginocchi aperto o
tenuto socchiuso dall'indice, Diana passava molte ore nello studio del
marito, intento a terminare il romanzo che doveva comparire nel
primo fascicolo del prossimo anno su la maggior rassegna italiana.
Come per riposarsi, in alcuni giorni della settimana egli scriveva una
novella che, secondo lui, doveva tenergli sciolta la mano con la
forzata rapidità della narrazione; e anche per sodisfare a certi
impegni con giornali e periodici che si contendevano i suoi lavori.
Mai Diana aveva mostrato curiosità di leggere le piccole cartelle del
manoscritto via via che suo marito le andava accumulando in un
angolo della scrivania.
Quella mattina egli la vide accostare con tale aria di sospetto che,
quando ella tese la mano per prendere le cartelle alle quali era già
sovrapposta l'ultima scritta, non ostante il sorriso, non ostante il tono
appositamente umile e gentile con cui furono pronunziate le parole:
— È permesso pregustare?... — non potè far a meno di fermarle il
braccio e domandarle:
— Ti senti male?
— No.... Lasciami leggere.
— Leggerai dopo. Rispondi: ti senti male?
— No.... Lasciami leggere.... Voglio leggere....
E si allontanò stringendo nel pugno il manoscritto, come una preda
vittoriosa.
Pietro Borgagli si sentì contrarre il cuore da uno spasimo atroce. E
ritto in piedi, col dorso delle mani fortemente appoggiato su l'orlo
della scrivania quasi avesse bisogno di premere su qualcosa di
resistente per convincersi di non esser vittima di un'allucinazione,
seguiva con gli occhi i movimenti di Diana, che leggeva le cartelle un
po' sgualcite dalla stretta del pugno con cui le aveva afferrate, e
indugiava, andava lestamente via con gli occhi, tornava addietro,
fino a che, arrivata all'ultima, non scattò, tendendo il manoscritto,
balbettando convulsamente:
— Ora non dirai che non è vero!
Per disgrazia, quella novella aveva forma di lettera. Un uomo
rinfacciava aspramente la donna che si era fatta giuoco di lui,
scoprendo tutte le vili manovre per mezzo delle quali lo aveva
irretito; e in quelle poche pagine già si sentiva lo schianto di un
cuore onesto, sincero, e l'amaro disprezzo con cui nobilmente si
vendicava dicendo: «Io non vi denunzierò a vostro marito nè al
vostro amante. La vostra miseria morale mi ispira in questo
momento tanta pietà da rendermi capace di perdonarvi, se il mio
perdono potesse giovarvi a qualcosa.
«Ma voi siete....
E doveva seguire il castigo, la parte più arditamente nobile, più
originale della novella.
Pietro Borgagli non poteva sorridere dell'inganno da cui si era
lasciata cogliere sua moglie. Questo morboso stato di animo di lei
durava da mesi; egli, sul principio, non se n'era preoccupato,
stimandolo la forma un po' esaltata di un amore che vuol avere
l'esclusivo possesso della persona amata. Avea contato su gli effetti
della serietà dell'assoluta dedizione della sua vita a colei che
rappresentava ancora, come anni addietro, il gran sogno di lui già
diventato realtà; sogno di bellezza, di sentimento, d'ideale, che ora
s'identificava con l'Arte, l'altro gran sogno di ideale e di bellezza,
raggiante, più che mai, nel suo fervido intelletto.
Avea contato su questo; ma già si accorgeva di essersi illuso.
— Ora non dirai che non è vero!
Che poteva risponderle? Che lo spunto di quella novella gli era stato
dato da Leoni, il quale, sere addietro, gli aveva riferito il caso di un
suo amico, accorso una mattina da lui per sfogarsi pregandolo di
ascoltarlo se non voleva che commettesse una pazzia? Infatti quello
sfogo lo aveva salvato....
Quand'anche avesse risposto così, non sarebbe stato creduto.
Prese le cartelle che Diana gli porgeva e fissandola con sguardo
implorante, accompagnato da un sorriso che nascondeva la grande
angoscia dell'artista, cominciò a fare, ad uno ad uno, in minutissimi
pezzi, i piccoli fogli del manoscritto; e quando li ebbe tutti accumulati
nel piatto indiano, di rame cesellato che a lui gran fumatore di
sigarette, serviva da portacenere, accese un cerino e vi appiccò
fuoco, rimescolandoli perchè bruciassero meglio e presto.
Pallidissima, trattenendo il respiro, Diana aveva assistito immobile,
strizzandosi le mani, a quell'olocausto tanto più grande, quanto non
chiesto, nè immaginato; e quando le ultime monachine dileguarono,
quasi inseguite, su gli ultimi pezzettini di carta consumati dal fuoco,
ella si rovesciò pesantemente indietro: e sarebbe cascata sul
pavimento se Pietro non l'avesse sorretta, portandola di peso sul
divano nel salottino là accanto.
Pareva che su quella casa piena di sorrisi di giovinezza e di sorrisi
d'arte si appesantisse tutt'a un tratto un'ombra di muta tristezza con
la grave cefalalgia che faceva gemere la povera Diana nell'oscurità
della sua camera dove non doveva penetrare un fil di luce.
Suo marito le stava seduto al capezzale e sembrava anche a lui che il
buio, mentre contribuiva a non rendere più acute le trafitture di cui si
lamentava sommessamente Diana, serviva a calmare le agitazioni del
suo spirito intorno alla malattia di lei, contro la quale lottarono
invano due famosi dottori.
In certe ore di maggior desolazione, un terribile sgomento lo
invadeva: di perdere la bella adorata creatura nel più spaventevole
modo, con la pazzia. All'idea che avrebbe veduto sopravvivere, chi sa
per quanti anni! il corpo della infelice, mentre la sua intelligenza
andrebbe di mano in mano immergendosi nelle tenebre
dell'ebetismo, Pietro Borgagli si sentiva straziare da una specie di
rimorso, quasi egli avesse contribuito con la sua arte a produrre
quell'eccitazione, quell'esaltamento nervoso nel delicato
impressionabile organismo della sua giovane moglie.
Per ciò non l'abbandonava un momento, per sviare a forza di
affettuosissime cure il tremendo pericolo; a forza di volontà anche,
tentando di comunicare a lei, come sapeva che fosse possibile, tutto
il suo vigore di salute, col tenerle strette le mani scosse sempre da
lievi tremiti, indizi delle intime agitazioni che la travagliavano.
Da un mese, egli era entrato due sole volte nel suo studio, e per
pochi minuti, provando dolorosissima ripugnanza di tutto quel che
richiamava alla sua memoria il lavoro: carta, penna, libri, giornali.
E così fu preso da gioia infantile la mattina che Diana, entrata in
piena convalescenza volle ricondurvelo per mano e intronizzarlo,
com'ella disse, davanti a la scrivania.
Egli aveva dovuto obbedire, ma sùbito si era alzato per stringerla tra
le braccia, per baciarla con tale impeto che fece venire a Diana le
lacrime agli occhi dalla straordinaria commozione.
Tenendosi per mano si erano affacciati al largo terrazzino che dava
su l'aperta campagna. Gli pareva che tutto sorridesse in quella stesa
di verde dorato dal sole, e che terminava laggiù laggiù, nella
scintillante striscia di mare interrotta qua e là dalle chiome degli
alberi, per cui prendeva apparenza di una sequela di azzurri laghetti.
— Guarda! — egli disse, indicando una bassa nuvola scura che si
avanzava rapidamente.
Come avanguardia, centinaia di allodole si precipitarono per l'aria,
volteggiando, inseguendosi — pareva — radunandosi compatta
disperdendosi, spaurita — ora si capiva bene — dalla presenza di
due falchi che intanto non riuscivano a ghermirne una. Ed ecco il
grande sciame, di migliaia e migliaia di allodole, la bassa nuvola
scura, che accorreva — se ne udiva distinto il forte strillìo — per
resistere all'assalto col numero almeno, fuggendo poi via, lontano,
per l'aperta campagna, poi mentre i due falchi proseguivano il loro
feroce inseguimento.
Diana sembrava intenta a guardare la strana battaglia; ma tutt'a un
tratto si afferrò al braccio del marito, balbettando:
— Pietro!... Pietro!
E portò vivamente le mani agli occhi.
— Oh, Dio! Oh, Dio!... Questi fili neri che mi tremolano davanti... che
mi scendono su le pupille.... Oh, Dio! Oh, Dio!... Pietro!...
Egli l'aveva presa tra le braccia, facendola rientrare, con la gola
improvvisamente così inaridita da non poter dirle una parola.
— Non ti vedo più!... Non ti vedo più!
E si aggrappava a lui, spalancandogli in viso gli occhi limpidissimi che
intanto non ci vedevano più!
— Non è niente!... Non spaventarti! Non è niente!
Egli si sforzava di rassicurarla, ma aveva nella voce uno sgomento
maggiore di quello di lei.
— Siedi qui, riposati! Hai voluto lasciare troppo presto la camera....
Sarà un abbagliamento — chiudi gli occhi — prodotto dalla luce
troppo viva.
Le accarezzava il viso bagnato di lacrime la baciava delicatamente su
gli occhi; e intanto un fremito di sdegno o di ribellione gli
infiammava il sangue contro la vigliacca crudeltà del Destino che si
accaniva su quel povero corpo, su quella povera anima, su tanta
fresca giovinezza, su tanto amore!
— Bisogna attendere: bisogna aver fede nella virtù medicatrice della
Natura che ne sa più di noi — aveva detto il valente oculista
consultato più volte.
A Pietro Borgagli sembrava che con l'oscurarsi delle pupille di Diana
qualcosa si fosse oscurato anche dentro di lui. La voleva ogni giorno
nel suo studio, seduta su la solita poltrona, come un misterioso
genio tutelare, che taceva, e che però pareva tendesse l'orecchio per
afferrar qualcosa di impercettibile per gli altri. Silenzio e
atteggiamento che paralizzavano ogni sforzo di riprendere il romanzo
interrotto o di scrivere qualcuna delle sue brevi novelle richieste con
insistenza dai giornali e dalle rassegne per impegni trascurati da un
pezzo.
Non sentendo il lieve stridere della penna su la carta, Diana
domandava ansiosamente:
— Non lavori?... Non puoi lavorare?
— Sì, sì, lavoro. Stento un po', dopo tanto intervallo.
— Povero amor mio!... Io sono la tua cattiva influenza.
— Non dovresti dirlo neppure per ischerzo!
— Oh! lo dico sul serio. Chi sa che qualche volta anche tu non lo
pensi?
— Diana! Diana!
Andava a inginocchiarsele davanti, prendendola per le mani,
baciandogliele con lieve carezza, accostando il viso al viso di lei per
osservare, desolatamente, quegli occhi limpidissimi da far credere
che il non vederci più fosse una finzione, se si fosse potuto supporre
tanta cattiveria in una dolce creatura di bontà come Diana.
— Vedi? Ti distraggo anche senza volerlo! Va', riprendi a lavorare....
Al romanzo? A una novella? Puoi dirmelo senza timore che io diventi
gelosa dei fantasmi del tuo passato... Come sono stata stupida!
Quanti dispiaceri ti ho dati! E il signore mi ha gastigato!... Sai? Però
ora mi sembra di esserti più vicina, più intima... Mi esprimo male...
Di volerti più bene, oh! assai più bene di prima. E mi pareva
impossibile che ciò potesse accadere... Ma tu non sai che fartene
dell'amore di questa povera cieca che impaccia la tua vita, che,
soprattutto, t'impedisce di lavorare, di farti vivo con tanti tuoi
ammiratori.... Lasciami.... dire....
Egli le turava la bocca! Non poteva sentirla parlare così, perchè
l'apparente tranquillità della voce non riusciva ad ingannarlo intorno
all'intimo significato di quelle parole di desolazione e di pianto
segreto.
E le settimane passavano, e i mesi passavano nel torpido silenzio di
quella solitudine dove Pietro Borgagli avea voluto rinchiudersi con
colei che egli sentiva di amare sempre più, specialmente dopo i
rapidi istanti — che poteva farci? — nei quali sentiva balenarsi in
fondo al cuore un improvviso sentimento di rivolta contro il suo
destino, un lampo di misero odio contro la innocente cagione di tutto
questo.....
Ed erano i momenti nei quali Diana, stretta forte al cuore di lui, si
sentiva pienamente compensata di ogni sua disgrazia; nei quali
Pietro non sapeva in che modo scontare quell'involontario lampo
d'odio che sembrava gli avesse lasciato qualcosa di attossicante nel
sangue.
— Non lavori? Non puoi lavorare?
— Il romanzo procede bene. Più tardi ti leggerò gli ultimi due capitoli
scritti.
— No, mi leggerai tutto a lavoro finito.
E il giorno dopo — e così ogni giorno — ella tornava cupamente a
interrogare:
— Non lavori?... Non ti riesce come prima?
— Anzi!... Mi pare che il tuo alito qui...
— Non dire bugie!... Non sento stridere la penna... Ho buon
orecchio, specialmente da che non ci vedo.
— Ho mutato penna. Perchè ti prendi il gusto di tormentarti senza
ragione?
— Te tormento, non me!
— Cattiva! Cattiva! Cattiva! Bisogna che io punisca cotesta bocca
calunniatrice!
Ed erano baci, ed erano abbracci deliranti, fino a che Diana vinta,
spossata dalla commozione, non pregava:
— Basta, Pietro!... Basta!
Che pietà, ogni mattina dover condurre per mano nello studio, fino a
una poltrona la bella creatura su le cui labbra appariva il
caratteristico sorriso dei ciechi, e farla sedere, aggiustandole alle
spalle e sotto i piedi i cuscini! Le si inginocchiava davanti, voleva che
gli posasse le mani su la testa in atto di benedizione, e le augurava:
— Sogna, mentre io inseguo il sogno della mia opera d'arte!
Diana diveniva, di giorno in giorno, più chiusa, più impenetrabile,
quantunque le rifiorisse sul viso una bellezza serena, gentile, una
maravigliosa maturità di bellezza, che si rivelava pure in certe
inflessioni della voce, in certe appassionate esitanze della parola,
quasi ella avesse una pienezza di cose da dire e volesse
assolutamente astenersene.
Questo formava la maggior tortura di Pietro Borgagli, gli produceva
un senso di stanchezza, di acredine, di sordo terrore insieme. E
pensando all'avvenire, egli levava gli occhi dai bianchi fogli che aveva
davanti e che stentava a coprire di quella caratteristica scrittura,
rivelatrice, una volta, dell'agile vivacità del suo pensiero di artista. E
fissava a lungo la cara silenziosa, che si dondolava lievemente su la
poltrona con le bianche mani aperte sui ginocchi, e gli occhi che non
vedevano, eppur fissi lontano, nello spazio, quasi guardassero,
intenti, una dolorosa visione.
Quella mattina, tutt'a un tratto, ella gli disse:
— Come dev'esser bella questa fine di aprile alla Roccetta!
Gli parve ch'ella intendesse di dirgli: — Andiamo ad isolarci di più! —
Per quanto vivessero segregati, ricevendo lui pochi amici, lei, e
raramente, una o due signore, intimissime, che non potevano farle
sentire l'offesa della compassione, pure qualcosa della vita esteriore
penetrava fino a loro, anche coi confusi rumori della via dove ferveva
fino a tardi la vita cittadina. Diana non potè accorgersi
dell'oscuramento del viso, del gesto d'impazienza provocati dalle sue
parole. Credè che suo marito, immerso nel lavoro, non avesse ben
udito, e ripetè:
— Come dev'esser bella questa fine di aprile alla Roccetta!
— Se vuoi, vi andremo domani... domani l'altro... — egli rispose.
— Domani... Grazie!... Non ti dispiace?
— Perchè dovrebbe dispiacermi, se fa piacere a te?
— Grazie!... Domani!
La villetta, con l'intonaco azzurro sbiadito dal tempo e dalle pioggie,
era stata fabbricata dall'avo di Pietro in cima a quella roccia che, da
ponente, scendeva quasi a picco su la vallata sassosa, coperta più in
là di erbe, di piante selvatiche, di alberi di ulivi.
Su la terrazza che permetteva di affacciarsi senza pericolo da quel
lato, al ritorno del loro viaggio di nozze, i giovani sposi avevano
passato serate deliziosissime, al lume di luna, in soavi colloqui, in più
soavi silenzi, durante i quali i loro cuori si erano detti quel che le
parole non avrebbero saputo mai dire!
E ogni volta che vi erano tornati, il maggior loro godimento era stato
il rivivere le indimenticabili serate di allora — Ricordi? — E tu,
ricordi? — quasi niente più potesse raggiungere le deliziose
impressioni di quel passato.
Due sere dopo, attorno ad essi, su la terrazza alitava una frescura
impregnata di selvaggi profumi campestri. Il sole stava per
tramontare dietro la collina dirimpetto, tra una maravigliosa gloria di
nuvole dagli orli d'oro, lanciando, diritti come frecce, nel cielo di
tenue smeraldo, i suoi ultimi raggi; e Pietro, assorto in questo
spettacolo che rapidamente vaniva sotto i suoi occhi, divinò, più che
non vide, il gesto di angoscia con cui Diana si passava le mani sul
viso, il crollo indietro della testa che rivelava la improvvisa
risoluzione scoppiatale nell'animo; e, senza un grido, si slanciò ad
afferrare l'esile corpo che, scavalcata la ringhiera, stava per
precipitare nell'abisso della vallata a trovarvi la morte.
La misera resistette un po', si agitò, tentò di svincolarsi, ma le forti
braccia di Pietro l'avevano già tirata dentro, su la terrazza, e
singhiozzante, mezza svenuta, la portavano di peso in camera,
deponendola sul letto.
Egli non osava di dirle una parola di rimprovero, quasi l'ingiusto
ribollimento di acredine contro di lei, che gli amareggiava la bocca
da due giorni, avesse contribuito a spingerla alla terribile risoluzione.
Ed ora tremava di rimorso davanti a quel corpo disteso sul letto e
che sussultava convulso; col terrore negli occhi di quel che sarebbe
avvenuto, se egli non fosse arrivato in tempo per impedirgli di
precipitare nel vuoto!
Diana alzò le braccia brancicando l'aria e chiamò con un fil di voce:
— Pietro! Pietro!
Sentendosi abbracciata e baciata impetuosamente, ella scoppiò in un
pianto dirotto, di sfogo, di sollievo. E appena si fu calmata, Pietro,
con dolce rimprovero, le disse su la bocca, come bacio:
— Perchè? Perchè?
— Per liberarti!
— Di che cosa?
— Di me, di me, che ti rendo infelice come uomo e come artista!
— T'inganni, Diana! La disgraziata sei tu che, forse, con un
altr'uomo... Ho detto forse... Nessuno avrebbe potuto amarti come ti
ho amato, come t'amo ancora, come sento di poter amarti sempre
più!... Lo sai che, in certi momenti, ho avuto fin la stoltezza di
rallegrarmi della tua cecità, geloso che i tuoi sguardi potessero, per
caso, vedere qualcosa.... qualcosa da menomare, da rubarmi il tuo
amore?
— Mi ripeti le belle cose che tu suoli scrivere.... Non mi illudi però....
Non è colpa mia, se ti ho fatto soffrire... Per questo... Oh, come sono
stanca! Stanca in tutti i sensi, col sangue tutto sossopra, con
improvvise nuove punture agli occhi...
— Riposa. Io ti veglierò a piè del letto...
— È già sera avanzata?
— Sono appena le sei e mezzo. Ma prima porgimi le tue mani, così, e
giurami per la santa memoria di tua madre, che non tenterai mai
più, mai più, in nessuna maniera... Ti figuri dunque che io potrei
sopravviverti? Tanta poca stima hai di me?... Giurami!
— Te lo giuro!... Ma sarebbe stato meglio altrimenti!
— Come sei crudele, Diana!
Si era buttato, vestito, sul lettino accanto, per poter accorrere sùbito
se Diana avesse avuto bisogno della sua assistenza. Non aveva
chiuso occhio, agitatissimo. Alle altre sue preoccupazioni, si era
aggiunta ora anche questa del possibile suicidio di Diana in un nuovo
improvviso sconvolgimento della sua coscienza!
— Ah — pensava — si ha un bel voler essere forti, scettici contro le
circostanze della vita! Arrivata a un punto, qualunque fibra più
resistente vien tutt'a un tratto spezzata.
Nella sua prima giovinezza, egli cavava, anzi, dalle contrarietà, gran
incitamento al lavoro. Aveva scritto molte delle cose più belle in
momenti in cui un altro si sarebbe lasciato vincere da
scoraggiamenti, da fiacchezze, da vili rinunzie.
Ora — e non poteva farne colpa ai suoi trentadue anni — non aveva
saputo resistere come allora, quando era solo a lottare pel suo ideale
d'arte. Si sentiva diminuito. Non amava più il lavoro; non trovava in
esso le consolazioni, le sodisfazioni di una volta. La strana gelosia di
Diana pel passato che le sembrava di veder rivivere in ogni pagina di
lui, lo facevano stare in guardia, in sorveglianza che un accenno, una
frase non dessero pretesti di turbamenti alla povera creatura
innamorata. Si sentiva impacciata la immaginazione, diminuita ogni
libertà di espressioni, di sentimento, di bollori, di passioni....
Era amato, è vero, come pochi potevano lusingarsi di essere stati
amati; egli avea visto realizzare il suo gran sogno di bellezza e di
amore non soltanto nell'Arte ma nella Vita; e colei che si agitava di
tratto in tratto su quel lettino, e che, appunto per accesso di amore,
poche ore addietro, avea tentato di morire, gli ispirava tale profonda
tenerezza per la quale in quel momento non gli sembrava gran
sacrifizio fin la rinunzia all'Arte.
— No, l'Arte non vale la vita! — ripeteva talvolta mentalmente.
Si sentì chiamare con voce così concitata da farlo balzare in piedi
atterrito:
— Quella striscia di luce... Pietro!...
E prima che potesse rendersi conto a che cosa Diana accennasse, un
grido acuto di gioia risuonava nella camera:
— Ti vedo!... Ti vedo! Pietro!... Pietro mio!
Quel che il valente oculista aveva dubbiosamente detto: — Bisogna
aver fede nella virtù medicatrice della Natura — riceveva improvvisa
conferma. La quasi identica violenta impressione che aveva prodotto
la cefalalgia e poi la cecità, oprando ora in senso contrario, rendeva
all'organo visivo la sua funzione non distrutta, ma impedita....
— Ti vedo! Ti vedo!...
Era un balbettamento; sillabe che pareva s'impigliassero tra i
singhiozzi; singhiozzi che non riuscivano, tormentosamente, a
sciogliersi in pianto; e mani che brancicavano quasi non fossero
sicure della realtà che tornava a sorridere agli occhi...
Pietro, dapprima, aveva creduto a un improvviso sconvolgimento
dell'intelligenza di sua moglie, all'estremo disastro tante volte temuto
in quei tristissimi giorni, nei quali era stato condannato a passare le
giornate nella buia camera dove ella soffriva gli strazi della
cefalalgia... Appena però dovè convincersi del miracolo oprato dalla
Natura, una gioia infantile lo sopraffece; poi un riso convulso, poi
una commozione così profonda che somigliava allo stupore....
E siccome la viva luce che inondava la camera dalla finestra
spalancata, abbagliava troppo la rediviva — non seppe meglio
chiamarla in quel punto — egli si affrettò a socchiudere gli scuri.
La teneva tra le braccia, seduta sui ginocchi, quasi l'avesse ritrovata
dopo lungo smarrimento, quasi avesse ancora paura di vedersela
nuovamente portar via.
E, nel silenzio, essi sentivano i battiti anelanti dei loro cuori felici.
Per parecchi mesi s'immersero, con vivissimo entusiasmo, nella vita
di società.
— Volete rifarvi del tempo perduto! — gli dicevano amiche, e amici
che non si stancavano di festeggiarli.
— Hanno ancora tanta giovinezza davanti a loro! — rispose una volta
la signora Marzani che non sospettava neppure dalla lontana il gran
male prodotto da certe sue parole.
Sì, era vero; volevano rifarsi del tempo perduto, quantunque
avessero tanta giovinezza davanti a loro. Troppi e troppi mesi erano
rimasti in un isolamento che ora cercavano di scancellare dalla loro
memoria, tanto era increscioso. Respiravano a pieni polmoni le
salsedine delle stazioni balneari, viaggiando in incognito, perchè
Pietro Borgagli odiava le interviste, i fotografi delle spiaggie, nè
voleva che la gente guardasse come bestia rara lo scrittore che il
caso della moglie, riferito dai giornali, aveva rimesso in vista, e del
quale si annunziano prossimi volumi di romanzi, di novelle, e un
dramma passionale.
Un giorno si era divertito a dir questo a un giovane ma importuno
giornalista che lo aveva riconosciuto e additato ai bagnanti di
Viareggio, costringendolo così a scappare di colà. Tutti i giornali
avevano riportato la notizia, rallegrandosi del suo ritorno all'Arte e
augurandogli nuovi gloriosi successi.
Gliene scrisse, lietissimo, il suo più caro amico, Leoni. Quella lettera,
che arrivava da Londra, e dove ogni parola, ogni periodo parevano
agitati da affettuosissima gioia li aveva raggiunti a Venezia, quando
già si preparavano a tornare a casa del troppo prolungato
pellegrinaggio, e produsse su tutte e due penosissima impressione.
Non osarono comunicarsi, lusingandosi l'una e l'altro di essersi
ingannati.
Diana aveva cominciato a notare che quella riposante tranquillità
venuta dietro alle esaltazioni, alle concitazioni, ai tormenti, alla paura
di risvegli del passato nel cuore del marito, alle momentanee
sodisfazioni di scoprirsi illusa, al riprendere e rinnovarsi delle stesse
esaltazioni, degli stessi tormenti, delle stesse paure, insieme con
l'angoscia della sopravvenuta cecità, e col cupo orrore della tenebra
dov'era sparito ogni sorriso di giovinezza; sì, aveva cominciato a
notare che in quella riposante tranquillità c'era qualcosa di torpido, di
insignificante, e che lei intanto vi si adagiava con vigliacca
rassegnazione, quasi non avesse più altro da desiderare, da
sperimentare, all'infuori delle giornaliere occupazioni casalinghe che
in certe circostanze la prendevano forte, come non avrebbe mai
creduto che potesse accaderle.
Da principio le era parso che ciò significasse stanchezza della vita di
alberghi, di riunioni, di teatri, di concerti; vivo bisogno di riposo, di
tregua almeno. Presto si accorse che era già, invece, un senso di
esaurimento, un disinteressarsi di ogni idealità, un abbandonarsi alle
minute cure esteriori della comoda vita che l'agiatezza le consentiva.
In quanto a suo marito, ormai, ella era assolutamente sicura di non
aver niente da temere dal passato, niente dal presente e molto
meno dall'avvenire.
Anche lui si sentiva evidentemente stanco, vinto da un torpore, che
egli non avrebbe saputo dire se fisico o intellettuale. Per poco, in
certi momenti, non credeva spenta o vicina a spegnersi ogni sua
facoltà artistica; e non ne provava nessun rimpianto, come se questo
fosse un fatto ordinario, nella natura delle cose. Gli pareva di averlo
osservato precedentemente in altri, che intanto o non se n'erano
accorti, o avevano voluto continuare per forza a produrre, dando
misero spettacolo di decadenza.
Non aveva lavorato a bastanza? Ora poteva coscenziosamente
riposarsi; ora poteva svagarsi leggendo i lavori degli altri, osservando
la tempesta dalla spiaggia: la tempesta della critica, la tempesta del
mutevole gusto del pubblico che si lasciava abbagliare dalle lustre
dei ciarlatani dell'arte, e aveva quel che si meritava.
Leoni, tornato da Londra, era rimasto profondamente afflitto di
ritrovarlo in questo stato d'animo. Non osava di credere ai suoi
orecchi, sentendolo parlare, non con profonda ironia, non con
desolante scetticismo, dell'Arte che ne aveva cinto di gloria il nome;
e sarebbe stato indizio di un'evoluzione che poteva riuscire feconda,
perchè l'ironia, lo scetticismo sono attività dello spirito capaci di
rivelarsi in splendide creazioni.
C'era però nelle parole di Pietro Borgagli un'incredibile supina
indifferenza.
— Ma è possibile? Tu mi fai strabiliare!
— Se è, vuol dire che è possibile — rispose Borgagli all'amico. — In
certi momenti — in certi lucidi intervalli, forse tu pensi — me ne
maraviglio anch'io. Sin dalla mia prima giovinezza ho lottato, ho fatto
a pugni contro tutto e contro tutti che volevano porre ostacoli alla
mia azione. Poi fu lotta diversa, inferiore, con le grandi difficoltà della
forma, con le non meno grandi difficoltà dei soggetti che mi piaceva
di affrontare. Vincevo perchè sentivo, fuori e dentro di me, qualcuno
o qualcosa che voleva opprimermi, atterrarmi; e per ciò tutti i miei
lavori, novelle, romanzi, polemiche, squillavano come trombe
guerresche, avevano l'aria di correre all'assalto, anche quando erano
soffuse di grande pietà, di sottile gentilezza, raggianti di poesia nella
loro umile espressione di tristissima realtà....
Welcome to our website – the perfect destination for book lovers and
knowledge seekers. We believe that every book holds a new world,
offering opportunities for learning, discovery, and personal growth.
That’s why we are dedicated to bringing you a diverse collection of
books, ranging from classic literature and specialized publications to
self-development guides and children's books.
More than just a book-buying platform, we strive to be a bridge
connecting you with timeless cultural and intellectual values. With an
elegant, user-friendly interface and a smart search system, you can
quickly find the books that best suit your interests. Additionally,
our special promotions and home delivery services help you save time
and fully enjoy the joy of reading.
Join us on a journey of knowledge exploration, passion nurturing, and
personal growth every day!
ebookbell.com

More Related Content

PDF
Programming: Principles and Practice Using C++ 2nd Edition, (Ebook PDF)
PDF
JavaScript The Definitive Guide 7th Edition David Flanagan
PDF
(eBook PDF) Starting Out with Java: From Control Structures through Objects, ...
PDF
C programming from problem analysis to program design 4th ed Edition D S Malik
PDF
Computer Systems A Programmer s Perspective 2nd Edition Randal E. Bryant
PDF
A comprehensive introduction to object oriented programming with Java 1st Edi...
PPTX
06 2 vector_matrici
PPT
Riepilogo Java C/C++
Programming: Principles and Practice Using C++ 2nd Edition, (Ebook PDF)
JavaScript The Definitive Guide 7th Edition David Flanagan
(eBook PDF) Starting Out with Java: From Control Structures through Objects, ...
C programming from problem analysis to program design 4th ed Edition D S Malik
Computer Systems A Programmer s Perspective 2nd Edition Randal E. Bryant
A comprehensive introduction to object oriented programming with Java 1st Edi...
06 2 vector_matrici
Riepilogo Java C/C++

Similar to Data Structures And Object Oriented Design Lecture Notes Usc Csci104 Itebooks (20)

PDF
Lezione 16 (2 aprile 2012)
PDF
Data Structures and Applications: A Simple and Systematic Approach Padma Reddy
PPT
06 1 array_stringhe_typedef
PDF
C Package 100 Knock 1 Hour Mastery Series 2024 Edition text version Tenko
PDF
PDF
A Philosophy of Software Design 2nd Edition John K. Ousterhout
PDF
C Net illuminated 4th Edition Arthur Gittleman
PDF
Get Solution Manual for C++ How to Program 10th by Deitel Free All Chapters A...
PPTX
06 3 struct
PDF
(E book ita) java introduzione alla programmazione orientata ad oggetti in ...
PDF
Guida C# By Megahao
PDF
Lezione 11 (26 marzo 2012)
PDF
Lezione 11 (26 marzo 2012)
PDF
Lezione 21 (2 maggio 2012)
PDF
Big Java Late Objects 1st Edition Horstmann Solutions Manual
PPT
05 2 integrali-conversioni-costanti-preproc-inclusione
PPTX
Effective Code Transformations in C++
PPT
3 Linguaggioc
PDF
Computer Science Illuminated Revised Nell B. Dale
PDF
Lezione01
Lezione 16 (2 aprile 2012)
Data Structures and Applications: A Simple and Systematic Approach Padma Reddy
06 1 array_stringhe_typedef
C Package 100 Knock 1 Hour Mastery Series 2024 Edition text version Tenko
A Philosophy of Software Design 2nd Edition John K. Ousterhout
C Net illuminated 4th Edition Arthur Gittleman
Get Solution Manual for C++ How to Program 10th by Deitel Free All Chapters A...
06 3 struct
(E book ita) java introduzione alla programmazione orientata ad oggetti in ...
Guida C# By Megahao
Lezione 11 (26 marzo 2012)
Lezione 11 (26 marzo 2012)
Lezione 21 (2 maggio 2012)
Big Java Late Objects 1st Edition Horstmann Solutions Manual
05 2 integrali-conversioni-costanti-preproc-inclusione
Effective Code Transformations in C++
3 Linguaggioc
Computer Science Illuminated Revised Nell B. Dale
Lezione01
Ad

Recently uploaded (8)

PDF
Presentazione educazione finanziaria e informazione.pdf
PDF
Presentazione di Chimica sui Coloranti Alimentari
PPTX
Santa Rosa da Lima, Vergine, Penitente, Terziaria Domenicana 1586-1617.pptx
PDF
Critico_o_creativo_Approcci_al_testo_let.pdf
PDF
NGÂN HÀNG CÂU HỎI TÁCH CHỌN LỌC THEO CHUYÊN ĐỀ TỪ ĐỀ THI THỬ TN THPT 2025 TIẾ...
PDF
NGÂN HÀNG CÂU HỎI TÁCH CHỌN LỌC THEO CHUYÊN ĐỀ TỪ ĐỀ THI THỬ TN THPT 2025 TIẾ...
PPTX
San Giovanni Eudes, 1601 – 1680, sacerdote e fondatore francese.pptx
PDF
NGÂN HÀNG CÂU HỎI TÁCH CHỌN LỌC THEO CHUYÊN ĐỀ TỪ ĐỀ THI THỬ TN THPT 2025 TIẾ...
Presentazione educazione finanziaria e informazione.pdf
Presentazione di Chimica sui Coloranti Alimentari
Santa Rosa da Lima, Vergine, Penitente, Terziaria Domenicana 1586-1617.pptx
Critico_o_creativo_Approcci_al_testo_let.pdf
NGÂN HÀNG CÂU HỎI TÁCH CHỌN LỌC THEO CHUYÊN ĐỀ TỪ ĐỀ THI THỬ TN THPT 2025 TIẾ...
NGÂN HÀNG CÂU HỎI TÁCH CHỌN LỌC THEO CHUYÊN ĐỀ TỪ ĐỀ THI THỬ TN THPT 2025 TIẾ...
San Giovanni Eudes, 1601 – 1680, sacerdote e fondatore francese.pptx
NGÂN HÀNG CÂU HỎI TÁCH CHỌN LỌC THEO CHUYÊN ĐỀ TỪ ĐỀ THI THỬ TN THPT 2025 TIẾ...
Ad

Data Structures And Object Oriented Design Lecture Notes Usc Csci104 Itebooks

  • 1. Data Structures And Object Oriented Design Lecture Notes Usc Csci104 Itebooks download https://ebookbell.com/product/data-structures-and-object- oriented-design-lecture-notes-usc-csci104-itebooks-23836322 Explore and download more ebooks at ebookbell.com
  • 2. Here are some recommended products that we believe you will be interested in. You can click the link to download. Mastering Java An Effective Project Based Approach Including Web Development Data Structures Gui Programming And Object Oriented Programming Beginner To Advanced White https://ebookbell.com/product/mastering-java-an-effective-project- based-approach-including-web-development-data-structures-gui- programming-and-object-oriented-programming-beginner-to-advanced- white-11063222 Object Oriented Programming And Data Structures E Balagurusamy https://ebookbell.com/product/object-oriented-programming-and-data- structures-e-balagurusamy-10959566 Java Methods Objectoriented Programming And Data Structures Third Ap 3rd Ap Maria Litvin https://ebookbell.com/product/java-methods-objectoriented-programming- and-data-structures-third-ap-3rd-ap-maria-litvin-59073076 Java Methods Objectoriented Programming And Data Structures 4th Edition 4th Maria Litvin https://ebookbell.com/product/java-methods-objectoriented-programming- and-data-structures-4th-edition-4th-maria-litvin-60399372
  • 3. Java Methods A Ab Objectoriented Programming And Data Structures Student Maria Litvin https://ebookbell.com/product/java-methods-a-ab-objectoriented- programming-and-data-structures-student-maria-litvin-2393214 Objectorientation Abstraction And Data Structures Using Scala Second Edition 2nd Ed Lacher https://ebookbell.com/product/objectorientation-abstraction-and-data- structures-using-scala-second-edition-2nd-ed-lacher-5742584 Data Structures And Other Objects Using C 4th Edition Michael G Main Walter J Savitch https://ebookbell.com/product/data-structures-and-other-objects- using-c-4th-edition-michael-g-main-walter-j-savitch-33556400 Data Structures And Other Objects Using Java 4th Edition Main https://ebookbell.com/product/data-structures-and-other-objects-using- java-4th-edition-main-55557912 Objects Abstraction Data Structures And Design Using C 1st Edition Elliot B Koffman https://ebookbell.com/product/objects-abstraction-data-structures-and- design-using-c-1st-edition-elliot-b-koffman-50318832
  • 5. Class Notes for CSCI 104: Data Structures and Object-Oriented Design David Kempe and the awesome Fall 2013 sherpas May 15, 2014
  • 6. 2
  • 7. Preface These lecture notes grew out of class notes provided for the students in CSCI 104 (“Data Structures and Object-Oriented Design”) at the University of Southern California in Fall of 2013. The class is typically taken in the second semester of freshman year or the first semester of sophomore year. Students are expected to be familiar with structural programming in the C++ programming language, the basics of dynamic memory management, and recursion. Brief refreshers on these two specific topics are included at the beginning of the notes, and typically covered for about one lecture each, but a student not already familiar with these concepts is likely to struggle in the class. These notes represent the specific way in which we like to present the material, by motivating object- oriented design primarily from the point of view of implementing data structures. There is perhaps somewhat more focus on analysis and allusions to advanced topics than in a typical programming-heavy data structures course for undergraduates. The notes are, at least at present, not intended to replace an actual detailed textbook on data structures. We currently use the book “Data Abstraction and Problem Solving” by Carrano and Henry. The notes are based on lecture notes taken by the CSCI 104 sherpas in Fall 2013: Chandler Baker, Douglass Chen, Nakul Joshi, April Luo, Peter Zhang, Jennie Zhou. Douglass was the main notetaker, in charge of about half of the notes himself, and coordinating the others’ notes as well. The notes were subsequently expanded significantly by David Kempe, and then expannded further and reorganized again for the Spring 2014 semester, when they took essentially their current form. 3
  • 8. 4
  • 9. Contents 1 Overview: Why Data Structures and Object-Oriented Thinking 9 2 Strings and Streams: A Brief Review 13 2.1 Strings . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13 2.2 Streams . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13 3 Memory and Its Allocation 17 3.1 Scoping of Variables . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 18 3.2 Dynamic allocation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 19 3.3 Pointers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 19 3.4 Dynamic Memory Allocation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 20 3.4.1 C Style . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 21 3.4.2 C++ Style . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 21 3.5 Memory Leaks . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 22 4 Recursion 25 4.1 Computing Factorials . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 25 4.2 Binary Search . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 26 4.3 The n-Queens problem . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 28 4.4 Some General Comments on Recursive Functions . . . . . . . . . . . . . . . . . . . . . . . . . 30 4.5 Recursive Definitions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 31 5 Linked Lists 33 5.1 Implementing Linked Lists . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 34 5.1.1 Linked list operations . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 35 5.2 Recursive Definition of Linked Lists . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 37 6 Abstract Data Types 39 7 Classes and Objects 43 7.1 Header Files and Declarations . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 43 7.2 Implementation of Member Functions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 45 7.3 Constructors and Destructors . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 46 7.4 The this pointer . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 47 8 Templates 49 8.1 The const keyword . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 50 5
  • 10. 9 Error Handling and Exceptions 53 9.1 Handling errors . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 53 9.2 Setting Flags . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 54 9.3 Exceptions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 54 10 Analysis of Running Time 57 10.1 Which running time? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 57 10.2 The Value of Big-O Notation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 58 10.3 Obtaining Upper and Lower Bounds . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 59 10.4 Computing Upper Bounds in Practice . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 60 10.5 Some Examples . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 61 11 Operator Overloading and Copy Constructors 65 11.1 Overloading and Operators . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 65 11.2 Operator Overloading and Classes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 66 11.3 Friend Access . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 68 11.3.1 Friend Classes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 69 11.4 An Illustration: Complex Numbers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 70 11.5 Copy Constructors . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 72 12 Inheritance and Polymorphism 75 12.1 Member Visibility, Multiple Inheritance, and calling Base Class Methods . . . . . . . . . . . . 76 12.1.1 Inheritance and Visibility . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 77 12.1.2 Multiple inheritance . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 77 12.1.3 Calling Base Class Methods . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 77 12.2 Class Relationships . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 78 12.3 Static vs. Dynamic Binding . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 79 12.4 Pure virtual functions and abstract classes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 80 12.4.1 Why and how to use virtual functions . . . . . . . . . . . . . . . . . . . . . . . . . . . 80 12.5 Constructors and Destructors with Inheritance . . . . . . . . . . . . . . . . . . . . . . . . . . 82 13 Array Lists 85 13.1 Comparison of Running Times . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 87 14 Stacks and Queues 89 14.1 A quick overview . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 89 14.2 Stacks . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 90 14.2.1 Example . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 91 14.2.2 Implementation of a general-purpose stack . . . . . . . . . . . . . . . . . . . . . . . . . 92 14.3 Queues . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 92 14.3.1 Implementation of a queue . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 93 14.4 Why use restrictive data structures? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 93 15 C++ Standard Template Library 95 15.1 More Details on Container Classes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 96 15.1.1 Sequence Containers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 96 15.1.2 Adapter Containers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 96 15.1.3 Associative Containers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 97 6
  • 11. 16 Iterators 99 16.1 Some first attempts . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 99 16.2 Iterators . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 100 16.3 Implementing an Iterator . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 101 16.4 The Bigger Picture . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 104 16.4.1 Design Patterns . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 104 16.4.2 Foreach, Map, Functional Programming, and Parallelization . . . . . . . . . . . . . . . 104 17 Searching Lists and Keeping them Sorted 107 17.1 Searching in a list . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 107 17.2 Interpolation Search . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 109 17.3 Keeping a List sorted . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 109 18 Qt and Event-Based Programming 111 18.1 Layout of Graphical Objects . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 111 18.1.1 Layouts . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 111 18.1.2 Widgets . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 112 18.2 Event-Based Programming . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 114 18.2.1 Signals . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 115 18.2.2 Slots . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 115 18.2.3 Connections . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 116 18.3 Putting it Together . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 116 18.3.1 Getting at data from widgets . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 116 18.3.2 Control flow and main function . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 117 18.3.3 Compiling Qt . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 117 19 Sorting Algorithms 119 19.1 Applications of Sorting . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 119 19.2 Stability of sorting algorithm . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 120 19.3 Bubble Sort . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 120 19.4 Selection Sort . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 122 19.5 Insertion Sort . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 123 19.6 Merge Sort . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 124 19.6.1 Running Time of Merge Sort . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 126 19.7 Quick Sort . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 128 19.7.1 Running Time . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 130 20 Graphs: An Introduction 133 20.1 The Basic Definitions and Examples . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 133 20.2 Paths in Graphs . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 135 20.3 The Abstract Data Type Graph . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 135 20.3.1 How to Implement the ADT internally? . . . . . . . . . . . . . . . . . . . . . . . . . . 136 20.4 Graph Search: BFS and DFS . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 137 20.4.1 Breadth-First Search (BFS) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 138 20.4.2 Depth-First Search (DFS) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 140 20.5 PageRank . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 141 21 Trees and Tree Search 145 21.1 The Basics . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 145 21.2 Implementing trees . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 146 21.3 Traversing a tree . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 147 21.4 Search Trees . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 148 7
  • 12. 22 Priority Queues and Heaps 151 22.1 Abstract Data Type: Priority Queue . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 151 22.2 Simple Implementations of a Priority Queue . . . . . . . . . . . . . . . . . . . . . . . . . . . . 153 22.3 Implementation using a Heap . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 153 22.4 Running Time of Priority Queue Operations . . . . . . . . . . . . . . . . . . . . . . . . . . . . 156 22.5 Heap Sort . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 156 23 2–3 Trees 159 23.1 The Basics . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 159 23.2 Search and Traversal . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 160 23.3 Inserting and Removing Keys: Overview . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 161 23.4 Inserting Keys . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 161 23.5 Removing Keys . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 165 23.5.1 A detailed illustration . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 167 23.5.2 Running Time . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 169 24 General B Trees and Red-Black Trees 173 24.1 More General B Trees . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 173 24.2 Red-Black Trees . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 174 24.2.1 Inserting into a Red-Black Tree . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 175 25 Hashtables 179 25.1 Introduction and Motivation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 179 25.2 Hash Tables Defined . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 179 25.3 Avoiding Collisions, and Choices of Hash Functions . . . . . . . . . . . . . . . . . . . . . . . . 180 25.3.1 Theory behind Choices of Hash Functions . . . . . . . . . . . . . . . . . . . . . . . . . 181 25.3.2 Practice of Hash Functions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 183 25.3.3 Another Application of Hash Functions . . . . . . . . . . . . . . . . . . . . . . . . . . 183 25.4 Dealing with Collisions: Chaining and Probing . . . . . . . . . . . . . . . . . . . . . . . . . . 184 25.4.1 Chaining . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 184 25.4.2 Probing . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 185 25.4.3 Implementing Hash Tables with Probing . . . . . . . . . . . . . . . . . . . . . . . . . . 186 25.5 Bloom Filters . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 187 26 Tries and Suffix Trees 191 26.1 Tries . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 192 26.1.1 An Application: Routing and IP Addresses . . . . . . . . . . . . . . . . . . . . . . . . 193 26.1.2 Path Compression . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 193 26.2 Suffix Trees . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 193 27 Dijkstra’s Algorithm and A∗ Search 197 27.1 Dijkstra’s Algorithm . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 197 27.1.1 Fibonacci Heap Implementation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 199 27.2 A∗ Search . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 199 27.2.1 The 15-Puzzle . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 201 28 Design Patterns 203 28.1 Observer . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 203 28.2 Visitor . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 204 28.3 Factory . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 205 28.4 Adapter/Wrapper . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 206 8
  • 13. Chapter 1 Overview: Why Data Structures and Object-Oriented Thinking [Note: This chapter covers material of about 0.75 lectures.] As a result of our introductory CS classes (CSCI 103, or CSCI 101 for earlier generations), most students are probably somewhat proficient in basic programming, including the following basic features: Data types int, String, float/double, struct, arrays, . . .. Arithmetic Loops for, while and do-while Tests if, else and switch Pointers Functions and some recursion Other I/O and other useful functions OOP Some students have probably already learned a bit about object-oriented programming, but for now, we will not rely on this. In principle, these features are enough to solve any programming problem. In fact, in principle, it is enough to have nothing except while loops, if tests, integers, and arithmetic. Everything that we learn beyond those basics is “just” there to help us write better, faster, or more easily maintained or understood code. Writing “better” code comes in two flavors: 1. Learning problem-solving and conceptual ideas that let us solve problems which we didn’t know how to solve (even though we had the tools in principle), or didn’t know how to solve fast enough. 2. Learning new language features and programming concepts helps us write code that “feels good”, in the sense that it is easy to read, debug, and extend. The way most students are probably thinking about programming at this point is that there is some input data (maybe as a few data items, or an array), and the program executes commands to get an output from this. Thus, the sequence of instructions — the algorithm — is at the center of our thinking. As one becomes a better programmer, it’s often helpful to put the data themselves at the center of one’s thinking, and think about how data get transformed or changed throughout the execution of the program. Of course, in the end, the program will still mostly do the same, but this view often leads to much better (more maintainable, easier to understand, and sometimes also more efficient) code. Along the way, we’ll see how thinking about the organization of our data can be really important for determining how fast our code executes. 9
  • 14. Example 1.1 In class, we did the following example. Two students each got a list of the names and e-mails of all students in the class. One student got the list sorted alphabetically, while the other’s version contained the names in random order. Both students were asked to look up the e-mail of one particular student by name. Not too surprisingly, the student with the sorted list was much faster. What this example showed us is that the way we organize our data can have a profound impact on how fast (or slowly) we can solve a computational task. The basic question of how to organize data to support the operations we need is the centerpiece of what you will learn in this class. What do we mean by “support the operations we need”? Let’s go back to the example of looking up e-mails of students by name. Here, the key operation is clearly the “lookup” operation, at least from the standpoint of the volunteer doing the looking up. But when we look at the bigger picture, those data items (pairs of a student and an e-mail) had to be added into that form somehow. And sometimes, we may want to delete them. So really, the three operations we are interested in are: • Insert a pair of a name and e-mail into the list. • Remove a name from the list. • Look up the e-mail for a given name. Purely from the perspective of speeding up the lookup operation, the sorted list was much better than the unsorted one. But in terms of inserting items, it’s clearly better to keep things unsorted, as we can just append them at the end of the list, and not have to worried about putting the item in the right place. For removal, well, we’ll learn about that a little more later. It’s easy to imagine scenarios where keeping the list sorted is not worth it. For instance, if we need to add data items all the time, but very rarely look them up (say, some kind of backup mechanism for disasters), then it’s not worth it. Or maybe, the list is so short that even scanning through all the items is fast enough. The main take-away message is that the answer to “What is the best way to organize my data?” is almost always “It depends”. You will need to consider how you will be interacting with your data, and design an appropriate structure in the context of its purpose. Will you be searching through it often? Will data be added in frequent, short chunks, or in occasional huge blocks? Will you ever have to consolidate multiple versions of your structures? What kinds of queries will you need to ask of your data? There is another interesting observation we can see from the previous example. We said that we wanted our data structure to support operations add, remove, and lookup. Let’s be a little more general than just a list of students. • The add (key, value) operation gets a key and a value. (In our case, they were both strings, but in general, they don’t have to be.) It creates an association between the key and value. • The remove (key) operation gets a key, and removes the corresponding pair. • The lookup (key) operation gets a key, and returns the corresponding value, assuming the key is in the structure. This type of data structure is typically called a map, or (in our textbook) a dictionary. So is a sorted list a map? Not exactly. After all, an unsorted list would also be a map. There seems to be a difference. The main difference is that the word “map” describes the functionality that we want: what the data structure is supposed to do. On the other hand, a sorted list (or an unsorted list) is a specific way of getting this functionality: it says how we do it. This distinction is very important. When you start to program, usually, you just go straight ahead and plan out all of your code. As you take on bigger projects, work in larger teams, and generally learn to think more like a computer scientist, you get used to the notion of abstraction: separating the “what” from the “how”. You can work on a project with a friend, and tell your friend which functions you need from him/her, without having to think through how they will be implemented. That’s your friend’s job. So long as your 10
  • 15. friend’s implementation works (fast enough) and meets the specification that you prescribed (say, in terms of which order the parameters are in), you can just use it as is. So the way we would think about a map or dictionary is mostly in terms of the functions it provides: add, remove, and lookup. Of course, it must store data internally in order to be able to provide these functions correctly, but how it does that is secondary — that’s part of the implementation. This combination of data and code, with a well-specified interface of functions to call, is called an abstract data type. Once we start realizing the important role that data organization (such as using abstract data types) plays in good code design, we may change the way we think about code interacting with data. When starting to program, as we mentioned above, most students think about code that gets passed some data (arrays, structs, etc.) and processes them. Instead, we now think of the data structure itself as having functions that help with processing the data in it. In this way of thinking, an array in which you can only add things at the end is different from one in which you are allowed to overwrite everywhere, even though the actual way of storing data is the same. This leads us naturally to object-oriented design of programs. An object consists of data and code; it is basically a struct with functions inside in addition to the data fields. This opens up a lot of ideas for developing more legible, maintainable, and “intuitive” code. Once we think about objects, they often become almost like little people in our mind. They serve different functions, and interact with each other in particular ways. One of the main things that thinking in terms of object-oriented design (and abstract data types) does for us is to help achieve encapsulation: shielding as much of the inside of an object as possible from the outside, so that it can be changed without negatively affecting the code around it. The textbook refers to this as “walls” around data. Encapsulation helps us achieve modularity, i.e., ensuring that different pieces of code can be analyzed and tested in isolation more easily. To summarize, the learning goals of this class are: 1. Learn the basic and advanced techniques for actually implementing data structures to provide efficient functionality. Some of these techniques will require strong fundamentals, and their analysis will enter into more mathematical territory, which is why we will be drawing on material you will be learning simultaneously in CSCI 170. 2. Learn how to think about code more abstractly, separating the “what” from the “how” in your code design and utilizing abstract data types to specify functionality. 3. Learn about good programming practice with object-oriented design, in particular as it relates to implementing datatypes. 11
  • 16. 12
  • 17. Chapter 2 Strings and Streams: A Brief Review [Note: This chapter covers material of about 0.25 lectures.] In this class, you will frequently need to read and process strings, and input and output in C++ are typically handled using streams. While you should have learned these in your introductory programing class, we will give a brief review here. 2.1 Strings In basic C, string are implemented just as arrays of characters. There is no separate string type. This means that you need to allocate enough space for your character array to hold any input string that you might receive. It also means that you need a special way of marking how much of that array you are actually using: just because you allocated, say, 80 characters in your string doesn’t mean you’ll always use all 80. The way C does this is by having a special role for the character 0. (That’s the character number 0, not the actual character ‘0’.) It marks the end of the used part of the string. The string class in C++ is a little easier to use: in particular, it hides some of the memory management for you, so that you don’t have to worry about allocating enough space. It also implements some operators that you will frequently use, such as = (assignment), == (comparison) and + (appending strings). The string class also contains a number of other very useful functions. Since you’ll be doing a lot of processing of data you are reading in from files in this class, you can probably save yourself a lot of work by reviewing the string class and its features. One string function which is really useful in C, but to our knowledge does not have an equivalent in C++ in standard implementations, is strtok. It allows you to tokenize a string, i.e., break it into smaller chunks using any set of characters as separators. Check it out! 2.2 Streams Streams are the C++ ways of interacting with files, keyboard, screen, and also strings. They typically divide into streams you read (keyboard, files, strings) and streams you write (screen, files, strings). In all cases, they are basically a sequence of characters that you are extracting from (read) or inserting into (write). They are a fairly clean abstraction of reading and writing “items” one by one. The standard streams you’ll be interacting with are the following. They require you to #include different header files, also listed here. The difference between cout and cerr is twofold: First, cout buffers its output instead of printing it immediately, whereas cerr prints immediately. This makes cerr very useful to output debug information, whereas you’ll probably write your “real” output to cout. They are also two different logical streams. This means that if you redirect your output to a file (which we will do when grading you), the default is that only cout gets redirected. So if you print debug information to cerr, it still appears on the screen, and doesn’t 13
  • 18. Stream Target Header File cin Keyboard iostream cout, cerr Screen iostream ifstream File to read fstream ofstream File to write fstream stringstream String sstream “pollute” your output. Finally, cerr often gets displayed in a different color. All this is to say: it may be good to get into the habit to print all your “real” output to cout, and all the debug output to cerr. To extract items from an input stream, you use the >> operator, and to insert items into an output stream, you use <<. A few things that are worth remembering about reading from an input stream. (These frequently trip students up.) • When reading from a stream (say, cin, or a ifstream myinput), you can use cin.fail() (or myinput.fail()) to check whether the read succeeded. When one read fails (and the fail flag is set), it will remain set until you explicitly reset it with cin.clear() or myinput.clear(). Until then, all further reads will fail, since C++ assumes that until you’ve fixed the source of the problem, all incoming data will be unreliable now. • Remember that >> reads until the next white space, which includes spaces, tabs, and newlines. Be careful here when entering things manually. If at the first prompt, you enter multiple items separated by space, they will “supply” the next few extractions. That is, if you write code like cout << "Please enter n:" cin >> n; cout << "Please enter m:" cin >> m; and enter “5 3” at the first prompt, it will read 5 into n and 3 into m. • If you want to also read spaces, then instead of using >>, you should use getline, such as cin.getline() or myinput.getline(). It lets you specify a string to read into, a maximum number of characters to read, and character to stop at (default is newline), and will then read into the string. That string can later be further processed. Notice that you need to be a bit careful about getline at the end of a file, and reading the correct flags to avoid reading the last line multiple times. • If you want to “clear” off the remaining characters in a stream so they don’t cause issues later, you can use the ignore() function, which will ignore the next n characters (which you can specify — default is 1). File Streams and String Streams File streams are the typical C++ way of reading data from files. They two types are ifstream (for files you read) and ofstream (for files you write). A file stream needs to be opened before you can read/write from it. Once you have opened it, it can basically be used like cin and cout. Notice that in file streams, when you read over the end of the file, you will get a fail condition. That happens after you reach the end of the file, which is often cause for confusion when using getline. The other thing to keep in mind about file streams is that after you’re done, you should call close() on them. 14
  • 19. Since strings are sequences of characters, just like files, it’s natural to treat them as streams as well. This is what the stringstream type is for. This is particularly useful when you’ve read an entire string and want to break it into smaller pieces, e.g., separated by spaces. You can treat the string as a stringstream, and then use extraction operations on it to get the different items. It’s also a convenient way to convert strings to integers. As a piece of advice, avoid using the same stringstream object multiple times — or if you do, at least make sure to reset it. 15
  • 20. 16
  • 21. Chapter 3 Memory and Its Allocation [Note: this chapter covers material of about 1 lecture.] At the physical level, computer memory consists of a large number of flip flops. Each flip flop consists of a few transistors, and is capable of storing one bit. Individual flip flops are addressable by a unique identifier, so we can read and overwrite them. Thus, conceptually, we can think of all of our computer’s memory as just one giant array of bits that we can read and write. Since as humans, we are not that good at doing all of our thinking and arithmetic in bits, we group them into larger groups, which together can be used to represent numbers. 8 bits are called 1 byte; beyond bytes, there are words (which are sometimes 16, sometimes 32 bits). Thus, more often, we conceptually regard our computer’s memory as one large (size 232 or so) array of bytes. A lot of things are stored in this memory. 1. All variables and other data used by all programs. 2. But also the programs’ code, including the operating system’s. The compiler and the operating system work together to take care of most memory management for you, but it is instructive to see what is going on under the hood. When you compile your code, the compiler can examine primitive data types and calculate how much memory they will need ahead of time. The required amount is then allocated to the program in the stack1 space. For example, consider the following declarations: int n; int x[10]; double m; The compiler can immediately see that the code requires 4 + 4 × 10 + 8 = 52 bytes2 . It will insert code that will interact with the operating system to request the necessary number of bytes on the stack for your variables to be stored. In the example above, the compiler knows that the memory will look as follows: It knows the exact memory address of each variable; in fact, whenever we write n, this gets translated into something like “memory address 4127963” internally. Notice that if we attempted to access x[10] here, we would access the data associated with m, and may end up reading (or overwriting) some of its bits. This would almost certainly have very undesired consequences for the rest of the program. When functions call other functions, each function gets its own chunk of stack at the moment it is called; it keeps all its local variables there, but also a program counter that remembers where in its execution it 1This space is called the stack space because as functions get called, their memory gets added on top of existing memory. As they terminate, they are removed in a LIFO (last in, first out) order. 2That’s with the current sizes for integers and doubles. About 20 years ago, int were typically 2 bytes, and double 4 bytes. Your code should never have to depend on what is at this moment the size of the basic data types. 17
  • 22. n x[0] x[5] m Figure 3.1: The layout of memory on the stack for the declaration above. was. When the function finishes, its memory block is made available for other purposes again. Again, for statically allocated variables, the compiler can take care of all of this. 3.1 Scoping of Variables While we are talking about local variables and the stack, we should briefly talk about the scope of a variable. Global variables are, of course, accessible to any function or any block of code in your program. Local variables in function or code blocks only exist while the execution is in the function or the code block. It is typically stored on the stack. As soon as the function or block terminates, the variable is deallocated. When the execution of a function is temporarily suspended, for instance because another function got called inside this one, the variables are stored on the stack, but not active, so they cannot be accessed. Consider the following example: void foo (int x) { int y; // do some stuff } void bar (int n) { int m; foo (n+m); // do more stuff } Here, when bar calls foo, the function foo cannot access the variables n or m. As soon as foo finishes, the variables x and y are deallocated, and permanently lost. bar now resumes and has access to n and m, but not to x or y. You will sometimes have multiple variables (in different parts of your code) sharing the same name. For instance, you may have both a global variable n and a local variable in a function called n. Or you could have something like the following: void foo (int n) { int m = 10; // do something for (int i = 0; i < m; i ++) { int n = 3, m = 5; // do something cout << n << m; } } 18
  • 23. Here, the cout << n << m statement would output the innermost versions, so the values 3 and 5. As a general rule, when there are multiple variables with the same name, any reference is to the one in the smallest code block enclosing the statement that contains that variable. 3.2 Dynamic allocation Unfortunately, things aren’t quite as easy as statically allocated arrays when we don’t know at compile time how much memory a variable will need. Suppose we want to do something like the following: int n; cin>>n; // create an array a of n integers Here, at compile time, the compiler does not know how much memory the array will need. It can therefore not allocate room for a variable on the stack3 . Instead, our program needs to explicitly ask the operating system for the right amount of space at run-time. This memory is assigned from the heap4 space. The difference between static and dynamic memory allocation is summarized in the following table. To fully understand how dynamic memory allocation works, we need to spend some time on pointers. Static allocation Dynamic allocation Size must be known at compile time Size may be unknown at compile time Performed at compile time Performed at run time Assigned to the stack Assigned to the heap First in last out No particular order of assignment Table 3.1: Differences between statically and dynamically allocated memory. 3.3 Pointers A pointer is an “integer” that points to a location in memory — specifically, it is an address of a byte5 . In C/C++, pointer types are declared by placing a star ‘*’ behind a regular type name. Thus, int *p; char *q; int **b; void *v; all declare pointers. In principle, all these are just addresses of some memory location, and C/C++ does not care what we store there. Declaring them with a type (such as int) is mostly for the programmer’s benefit: it may prevent us from messing up the use of the data stored in the location. It also affects the way some arithmetic on memory locations is done, which we explain below. Two of the ways in which “regular” variables and pointers often interact are the following: 1. We want to find out where in memory a variable resides, i.e., get the pointer to that variable’s location. 2. We want to treat the location a pointer points to as a variable, i.e., access the data at that location, by reading it or overwriting it. The following piece of code illustrates some of these, as well as pitfalls we might run into. 3Technically, this is not quite true. Some modern compilers let you define arrays even of dynamic sizes, but we advise against using this functionality, and instead do things as we write in these notes. 4This is called the heap space since it can be selected from any portion of the space that has not been allocated already. While the stack remains nicely organized, memory in the heap tends to be more messy and all over the place. Hence the name. 5This means that all pointers are of the same size, and that the size of pointer used by a computer places a limit on the size of memory it can address. For example, a computer using typical 32-bit pointers can only use up to 232 bytes or 4 gigabytes of memory. The modern shift to 64-bit architectures turns this to 264 bytes, which will be enough for a while. 19
  • 24. int* p, *q; //Pointers to two integers int i, j; i = 5; j = 10; p = &i; //Obtain the address of i and save it to p cout << p; //Prints the value of p (address of i) cout << *p; //Prints the value of the integer that p points to (which is the value of i) *p = j; // Overwrites the value of the location that p points to (so, i) with the value of j *q = *p; // Overwrites the value of the location that q points to with the one that p points to q = p; // Overwrites the pointer p with q, so they now point to the same location A few things are worth noting here. 1. The last two commands both result in *p being equal to *q, but in different ways. In the first case, we copy the value from one location to the other, while in the second, we make both point to the same place. 2. The second-to-last command is very risky, and will likely cause a run-time error. We have not initialized q, so it points to an arbitrary memory location, quite possibly location 0. We are trying to overwrite it, which most likely the program is not allowed to do. The location may belong to the operating system or some other program6 . 3. NULL is a special pointer we use for an uninitialized pointer variable, or to express that a pointer is not pointing anywhere. If we try to write *p when p == NULL, we get a runtime error. In a sense, NULL is really just another way of saying 0; we said above that pointers are just numbers, and the NULL pointer points to memory location 0. In C++11, there is a new keyword for this, namely nullptr. If you set the compiler flags to compile C++11, you can/should use nullptr instead of NULL. 4. In fact, the earlier observation about the perils of uninitialized pointers suggests that whenever you have a pointer variable, you always assign it NULL right away as you declare it. So in our example, we should have written int *p = NULL, *q = NULL; That way, if we later forget to assign it before dereferencing, at least, it will cause a crash of our program, rather than possibly processing some garbage that it reads from the location. This is good coding practice to reduce your debug time. 5. & and * are inverses of each other. Thus, &*p and *&p are the same as p. (The exception is that &*p throws a runtime error when applied to p==NULL, instead of being equal to p.) In general, it is quite easy to mess up with pointers. A few rules of thumb (which we discussed in class) will weed out common mistakes, but generally speaking, it is easy to forget to assign a pointer correctly, resulting in run-time errors. We discussed before that pointers are basically just integers. They differ in that arithmetic is somewhat different. When you have a pointer p to an int, and you write p+1, the compiler assumes that what you want is the address where the next integer will start, which is 4 bytes later. Thus, the actual address referenced by writing p+1 is actually 4 bytes after the address of p. This is where the type of pointer matters, as we hinted at above. When you have a void*, then addition really does refer to adding individual bytes. For all others, when you write p+k for a pointer p and integer k, this references memory location p+k*size(<type>), where <type> is the type of the pointer p. 3.4 Dynamic Memory Allocation In order to dynamically allocate and deallocate memory, there are two pairs of functions, one in C style and one in C++ style. In C, the function for allocating memory is malloc, and for deallocation free. In C++, the functions are new and delete. We will first discuss the C style, as it is a little closer to the actual low-level implementation; we’ll then see the C++ style, which shields us from some of the low-level details. 6Fortunately, nowadays, all that happens is that your program throws a run-time error. In the past, the OS would often allow you to do such an overwrite, in which case often some complete system crashes would happen. 20
  • 25. 3.4.1 C Style The function void* malloc (unsigned int size) requests size bytes of memory from the operating sys- tem, and returns the pointer to that location as a result. If for some reason, the OS failed to allocate the memory (e.g., there was not enough memory available), NULL is returned instead. The function void free (void* pointer) releases the memory located at pointer for reusing. A solution to our earlier problem of a dynamically sized array could look as follows: int n; int* b; cin >> n; b = (int*) malloc(n*sizeof(int)); for (int i=0; i<n; i++) cin >> b[i]; In order to request space for n integers, we need to figure out how many bytes that is. That’s why we multiply with sizeof(int). Using sizeof(int) is much better than hard-coding the constant 4, which may not be right on some hardware now or in the future. Because malloc returns a void* (it does not know what we want to use the memory for), and we want to use it as an array of integers, we need to cast it to an int*. For good coding practice, we should probably also check whether b==NULL before dereferencing it, but this example is supposed to remain short. Another thing to observe here is that we can reference b just like an array, and we write b[i]. The compiler treats this exactly as *(b+i), and, as you probably remember from the part about pointer arith- metic, this points to the ith entry of the array. In fact, that’s exactly how C/C++ internally treats all arrays anyway; basically, they are just pointers. If we wanted to write b[i] in a complicated way by doing all the pointer arithmetic by hand, we could write instead *((int*) ((void*) b + i*sizeof(int))). Obviously, this is not what we like to type (or have to understand), but if you understand everything that happens here, you are probably set with your knowledge of pointer arithmetic and casting. To return the memory to the OS after we’re done using it, we use the function free, as follows: free(b); b = NULL; Note that free does nothing to the pointer b itself; it only deallocates the memory that b pointed to, telling the operating system that it is available for reuse. Thus, it is recommended that you immediately set the pointer to NULL so that your code does not attempt to tamper with invalid memory. If you reference b somewhere, you’ll just get a runtime error. If you don’t set b=NULL, the OS may give the memory to another variable, and you accidentally reference/overwrite that one. That kind of mistake can be much harder to detect, and is easily avoided by setting the pointer to NULL immediately after deallocating it. 3.4.2 C++ Style C++ provides the new() and delete() functions that provide some syntactic sugar to C’s malloc() and free(). Basically, they relieve you from the calculations of the number of bytes needed, the casting of pointers, and provide a more “array-like” syntax. Our example now looks as follows: int n; int *b; cin >> n; b = new int[n]; 21
  • 26. Notice that there are no parentheses, but instead, we have brackets for the number of items. new figures out by itself how much memory is needed, and returns the correct type of pointer. If we wanted space for just one integer, we could write int *p = new int; While this is not really very useful for a single integer, it will become very central to allocating objects later, where we often allocate one at a time dynamically. To release memory, the equivalent of free is the delete operator, used as follows: delete [] b; delete p; The first example deallocates an array, while the second deallocates a single instance of a variable (a single int in our example). This deallocates the memory pointed to by the pointers. As with free, it still leaves the pointers themselves pointing to the same memory location, so it is good style to write b = NULL or p = NULL after the delete commands. 3.5 Memory Leaks Let us look a little more at the things that can go wrong with dynamic memory allocation. double *x; ... x = (double*) malloc(100*sizeof(double)); ... x = (double*) malloc(200*sizeof(double)); // We need a bigger array now! ... free(x); This code will compile just fine, and most likely will not crash, at least not right away. We correctly allocate an array of 100 double, use it for some computation, and then allocate an array of 200 double when we realize that we need more memory. But notice what happens here. The moment we do the second allocation, x gets overwritten with a pointer to the newly allocated memory block. At that point, we have no more recollection of the pointer to the previous memory block. That means we cannot read it, write it, or free it. When at the end of the code snippet, the program calls free(x), it successfully frees up the second allocated block, but we are unable to tell the operating system that we don’t need the first block any more. Thus, those 800 bytes will never become available again (until our program terminates — but for all we know, it may run for several years as a backend server somewhere). This kind of situation is called a memory leak: available memory is slowly leaking out of the system. If it goes on long enough, our program may run out of memory and crash for that reason. It could be quite hard to diagnose why the crash happened if it does after a long time of running. It is good practice to keep close track of the memory blocks you reserve, and make sure to free (or delete) memory pointed to by a pointer before reassigning it7 . A better version of the code above would be the following: double *x; ... x = (double*) malloc(100*sizeof(double)); ... free(x); x = NULL; 7There are tools for checking your code for memory leaks, and we recommend familiarizing yourself with them. The most well-known one, for which the course web page contains some links, is called valgrind. 22
  • 27. x = (double*) malloc(200*sizeof(double)); ... free(x); x = NULL; That way, the memory gets released while we still have a pointer to it. You will recall that it is always good practice to immediately set pointers to NULL after deallocating their memory. In the middle of the code, that may seem very redundant: after all, we immediately overwrite x with another value. In fact, it is completely redundant. However, we still recommend that you add the line; for example, you may later insert some code between free(x) and the new assignment to x, and that could cause problems otherwise. And if you are worried about your program wasting time with unnecessary assignments, don’t — the compiler will almost certainly optimize that assignment away anyway, and the final code will look exactly the same. One question you may be wondering about is if we could just free the memory by setting x = NULL; without calling free(x) first. That would only overwrite the pointer, but it would not tell the operating system that it can have the memory back. In other words, it would exactly create a memory leak. Whenever you want to return memory to the system, you must do so explicitly8 . As a side not, if you look up some of this information online or in other sources, keep in mind that the pool of memory where you allocate variables with new or malloc is called the memory heap. This is different from a data structure known as the heap which we will learn about later. Unfortunately, people (including us . . .) are not quite consistent in naming these. 8There are other programming languages that do much more of the garbage collection and memory handling for you, but C/C++ is not one of them. 23
  • 28. 24
  • 29. Chapter 4 Recursion [Note: this chapter covers material of about 1.5 lectures.] The adjective recursive means “defined in terms of itself”. (If you Google “recursion”, you get the answer: “Did you mean recursion?”) As computer scientists, we frequently run into recursion in the form of recursive functions, which are functions that call themselves (directly, or indirectly through another function). However, as we see below, another very important application is recursive definitions of objects. We will explore recursion through several examples. 4.1 Computing Factorials The first one is to compute the factorial of n, which is defined as n! = n · (n − 1) · (n − 2) · · · 2 · 1 = Qn i=1 i. Of course, we could use iteration (a simple for loop) to compute n!. int factorial(int n) { int p=1; for (int i=1; i<=n; i++) p*= i; return p; } This is a perfectly valid, and probably even the best, way to compute the factorial of a number. But instead, we would like to use this very easy example to illustrate how recursion works. Looking at the definition, we observe that 0! = 1 (by definition), and n! = n · (n − 1)!. This suggests a recursive solution: int factorial (int n) { if (n==0) return 1; else return n*factorial(n-1); } Notice that the function factorial calls itself; this is what makes this a recursive implementation. Students often have trouble thinking about recursion initially. Our instinct is often to make a complete plan for the computation: first multiply n with n − 1, then with n − 2, and so on, all the way to 1. In a recursive solution, we instead treat most of the work as a “black box”: we don’t really worry how the call with parameter n − 1 will obtain the correct results, and just trust that it does. (Of course, that only works if the function is actually correct.) In class, we illustrated this with acting out recursion with a group of students. Each student was in charge of computing n! only for one particular number n. The student in charge of computing 5! relied on another student to compute 4!, then used that other student’s result and multiplied it with 5. The important 25
  • 30. insight was that the student who computed 5! did not need to know how 4! was computed; so long as the 4! student got the right result, it could just be plugged in. Again, to recap this once more, in a recursive solution, we don’t have to think of every step, nor do we have to keep track of various intermediate results, as those are returned as values by the other function calls. Students getting started on recursion often try as hard as possible to have recursion emulate loops, by passing around “global variables” (or pointers to variables, which amounts to the same thing) that are altered and store intermediate results. This type of thinking can take a while to get used to, but once you firmly grasp it, a lot of things — induction in CSCI 170 and dynamic programming in CSCI 270 — will come to you much more easily. Two things that pretty much all correct recursive functions share are the following: • A recursive function needs one or more base case: at some point, the function must hit a point where it will no longer call itself (like the n==0 case for the factorial). Otherwise, the function will keep calling itself forever, and eventually run out of stack memory. • Recursive calls must have “smaller” inputs than the main input. In the case of the factorial function, the recursive call within factorial(n) was for factorial(n-1). In this case, it is clear that n − 1 is “smaller” than n. In other cases, “smaller” refers to the remaining size of an array, or even a number that is closer to an upper bound. (For instance, the base case could be i==n, and the call with input i could be to i + 1.) Let us look quickly at two examples violating these conditions, and see what happens. int UCLAfact (int n) // apologies to our neighboring school { if (n == 0) return 1; else return UCLAfact (n); // error: input not getting smaller! } int NDfact (int n) { return n*NDfact (n-1); // ...this doesn’t stop! } Neither of these functions will terminate. In the first example, we do have a base case, but the recursive call has the same size. It is of course correct that n! = n! (which is what the functions uses), but it doesn’t help us compute it. In the second example, we do have a smaller input in the recursive call, but no base case, so the function will continue calling itself with different values forever (or until it runs out of stack space and crashes). 4.2 Binary Search (Note: We did not cover this one in Spring of 2014. But it’s a useful example to know anyway, and we’ll return to it.) Admittedly, computing factorials is not a very strong example to show why recursion is useful, since the iterative solution is short and elegant. However, we were able to illustrate some of the important properties of recursion with an easy enough general setup. Next, let us look at a somewhat more challenging task: Given a (pre-sorted) array of integers in increasing order, find the location of a target element, or return -1 if it is not in the array. This is accomplished using the Binary Search algorithm: Check the middle of the remaining array. If the element is there, we are done. If the desired element is smaller, continue searching to the left of the middle element; otherwise, continue searching to the right. A corresponding iterative solution looks as follows: 26
  • 31. int binarySearch (int n, int* b, int len) { int lo = 0, hi = len, mid; while(lo <= hi) { mid = (hi+lo)/2; if (b[mid]==n) return mid; else if(n < b[mid]) hi = mid-1; else lo = mid+1; } return -1; } A recursive solution would look as follows instead: int recSearch(int n, int* b, int lo, int hi) { if (hi < lo) return -1; // not in the array else { int mid = (hi+lo)/2; // the midpoint of the array if (n == b[mid]) return mid; // we found it else if (n < b[mid]) return recSearch(n, b, lo, mid-1); // element to the left of the midpoint else return recSearch(n, b, mid+1, hi); // element to the right of the midpoint } } We then call the function as recSearch(n, b, 0, len). Whether you like the iterative or the recursive solution better here may be a matter of taste, but notice that there is a certain elegance to the recursive solution. When we have decided where the element must be (to the left or the right), rather than updating a variable and repeating, we simply ask the function to find it for us in that (correct) subarray, and return its return value unchanged. Notice that both implementations will work whether the array has an even or odd number of elements. If we hadn’t written mid-1 and mid+1 for the recursive calls, we might have needed another base case when lo == hi. Let us check that we satisfy the two conditions above for a recursive solution to have a shot: • If the array is empty (which is the case hi < lo), the function returns directly, reporting that the element was not found. • If n is less than the midpoint, the function recurses on the left half of the array; otherwise, on the right half. In either case, because we eliminate at least the midpoint, the remaining array size is strictly smaller than before. One of the things that can be confusing about recursion is that there seem to be many “active” versions of the function at once. What is “the” value of variables like n, lo, hi, or mid? After all, different invocations of the function will have different values for them. To solve this “conundrum”, remember from our overview of memory that local variables are stored on the stack, and translated into memory locations by the compiler and OS. Thus, when the function recSearch(12,b,0,10) calls recSearch(12,b,0,4), their variables lo, hi translate to completely different memory locations. When the call recSearch(12,b,0,4) executes, the values are lo=0, hi=4, and mid=2 will be computed. In the call recSearch(12,b,0,4), we instead have lo=0, hi=10, mid=5. The computer has no problem with the same variable name, as only one meaning of the variable is in scope at a time. 27
  • 32. 4.3 The n-Queens problem The first two examples we saw of recursion in class were pretty easily coded with a simple loop, as you saw. In general, if you have a recursive function that uses just one recursive call to itself, it is often easily replaced by a loop. Recursion becomes much more powerful (as in: it lets you write short and elegant code for problems that would be much more messy to solve without recursion) when the function calls itself multiple times. We will see several examples of that later in class with some clever recursive sorting algorithms and others. Another very common application is via a technique called Backtracking for solving complicated problems via exhaustive search. In this class, we saw the classic n-queens problem as an example for this technique. In the n-queens problem, you want to place n queens on an n × n chessboard (square grid). Each queen occupies one square on a grid and no two queens share the same square. Two queens are attacking each other if one of them can travel horizontally, vertically, or diagonally and hit the square the other queen is on. The problem is to place the queens such that no two queens are attacking each other. For instance, what you see in Figure 4.1 is not a legal solution: the queens in rows 1 and 2 attack each other diagonally. (All other pairs of queens are safe, though.) Q Q Q Q Figure 4.1: Illustration of the 4-queens problem Before we try to solve the problem, we make some observations. Because queens attack each other when they are in the same row or column of the chessboard, we can phrase the problem equivalently as follows: place exactly one queen per row of the board such that no two queens are in the same column or attack each other diagonally. We can solve this by having n variables q[i], one per queen. Each variable loops from 0 to n − 1, trying all n places in which the queen could be theoretically placed. Of all those at most nn ways of placing the queen, we check if they are legal, and output them if so. Of course, it will be more efficient if we abort a search as soon as there is an attack between queens, since placing more queens can never fix that. So we will have an array q[0 . . . (n − 1)] that contains the positions within the rows for each of the n queens. Those will be a global variable. Also, we’ll have a global variable for the size n of the grid: int *q; // positions of the queens int n; // size of the grid Then, the main search function will be of the following form: void search (int row) { if (row == n) printSolution (); // that function shows the layout else { for (q[row] = 0; q[row] < n; q[row]++) 28
  • 33. { search (row+1); } } } That’s the general outline of most Backtracking solutions. At each point where a decision must be made, have a loop run over all options. Then recursively call the function for the remaining choices. Of course, so far, we don’t check anywhere to make sure that the solution is legal, i.e., no pair of queens attack each other. To do that, we want to add an array that keeps track of which squares are safe vs. attacked by a previously placed queen. We capture it in an array t (for “threatened”), which we can make a global variable. (It’s a two-dimensional array, which translates to a pointer to a pointer.) int **t; Now, we need to check whether the place we’re trying to place a queen at is actually legal, and update the available positions when we do. Instead of just keeping track whether a square is attacked by any queen, we should actually keep track of how many queens attack it. Otherwise, if we just set a flag to true, and two queens attack a square, when we move one of them, it will be hard to know whether the square is safe. The more complete version of the function now looks as follows: void search (int row) { if (row == n) printSolution (); // that function shows the layout else { for (q[row] = 0; q[row] < n; q[row]++) if (t[row][q[row]] == 0) { addToThreats (row, q[row], 1); search (row+1); addToThreats (row, q[row], -1); } } } We still have to write the function addToThreats, which increases the number of threats for the correct squares. The function should mark all places on the same column, and on the two diagonals below the current square. For the latter, we need to make sure not to leave the actual grid. Looking at it a little carefully, you’ll see that the following function does that: void addToThreats (int row, int column, int change) { for (int j = row+1; j < n; j++) { t[j][column] += change; if (column+(j-row) < n) t[j][column+(j-row)] += change; if (column-(j-row) >= 0) t[j][column-(j-row)] += change; } } Finally, we need to write our main function that reads the size, creates the dynamic arrays, initializes them, and starts the search. 29
  • 34. int main (void) { cin >> n; q = new int [n]; t = new int* [n]; for (int i = 0; i < n; i++) { t[i] = new int [n]; for (int j = 0; j < n; j ++) t[i][j] = 0; } search (0); delete [] q; for (int i = 0; i < n; i ++) delete [] t[i]; delete [] t; return 0; } If you do not yet fully understand how the above solution works, try tracing its execution by hand on a 5 × 5 board, by simulating all the q[i] variables by hand. That will probably give you a good idea of backtracking. 4.4 Some General Comments on Recursive Functions At a high level, there are two types of recursion: direct and indirect. Direct recursion happens when a function f calls itself. That’s what we have seen so far. Not quite as frequent, but still quite common, is indirect recursion: you have two functions f, g, and f calls g, and g calls f. There is nothing particularly deep about this distinction: we’re mentioning it here mostly so that you are familiar with the terms. If you find yourself using indirect recursion and running into compiler errors, the problem could be that one of the two function definitions has to be first, and when you define that function, the compiler does not know about the other function yet. The way around that is as follows (in the examples, we assume that our functions are from int to int, but there’s nothing special about that): int f (int n); // just a declaration of the signature (this will often go in the .h file) int g (int n) { // insert code for g here, including calls to f } int f (int n) { // insert code for f here, including calls to g } This way, when the compiler gets to the definition of g, it already knows that there is a function f; when it gets to f, you have already defined g. Among direct recursion, if the function calls itself just once, there are also two common terms: head recursion, and tail recursion. These refer to when the recursive call happens. If it happens at the end of a function, this is called tail recursion. Tail recursion is particularly easily replaced by a loop. When the recursive call happens earlier than the end (e.g., at the beginning), this is called head recursion. Head 30
  • 35. recursion turns out to be able to easily do some surprising things, such as print strings or linked lists in reverse order. The distinction isn’t really a huge deal, but it’s probably good to have heard the terms. Another thing to keep in mind is that there are some programming languages (called functional languages) in which typically all problems are solved using recursion. Several of them do not even have loops. Some examples of such languages are ML (or its variant OCAML), Lisp, Scheme, Haskell, Gofer. There are others. Some of these (in particular, ML) are actually used in industry, and Lisp is used in the Emacs editor. Functional languages make functions much more central than procedural ones. It is very typical to write a function that takes another function as an argument. For instance, you may think of a function g that operates on an array or list, and gets passed another function f as an argument, and what it does is apply f to each element of the array/list. (For instance, you could have a function that turns each entry of an array into a string.) This operation is called map. Another common thing is to have a function h that applies some other function to compute a single output from an entire array/list. An example would be to sum up all elements of an array/list, or to compute the maximum. This operation is called reduce. Programs that are written by applying only these two types of operations can often be very easily parallelized over large computation clusters, which is why the Map-Reduce framework has become quite popular lately (e.g., in Google’s Hadoop). It has led to a resurgence in interest in some aspects of functional programming. From a practical perspective, when you write functional programs, it often takes longer to get the program to compile, because many logical mistakes that lead to weird behavior in C++ can’t even be properly implemented in a functional language. Once a functional program compiles correctly, it is much more often bug-free than a procedural program (assuming both are written by fairly experienced programmers). 4.5 Recursive Definitions So far, we have talked about recursion as a programming technique. An almost equally important application of recursion is as a way of specifying objects concisely, by virtue of recursive definitions. These will come in very handy later on when defining lists, stacks, heaps, trees, and others. To be ready for that when we need it, we’ll practice here with a few easier recursive definitions. The first of these are examples that you can define pretty easily without recursion (just like our earlier examples of using recursion as a programming technique), while the later ones may be more involved (and would be very hard to define non-recursively). 1. A string of (lower-case) letters is either: (1) the empty string (often written as ǫ or λ), or (2) a letter ‘a’–‘z’, followed by a string of letters. The recursion happens in case (2), and case (1) is the base case. Of course, for this one, we could just have said that a string is a sequence of 0 or more lower-case letters, which would have been just fine. But we’re practicing recursion on easy examples here. 2. A non-negative integer is either: (1) the number 0, or (2) n + 1, where n is a non-negative integer. Here, defining what exactly integers are without referring to integers in the first place may be a little puzzling. Recursion helps with that. It says that there is a first one (the number 0), and a way to get from one to the next one. In this sense, 4 is really just shorthand for 0 + 1 + 1 + 1 + 1. 3. A palindrome is either: (1) the empty string ǫ, or (2) a single letter ‘a’–‘z’, or (3) a string xPx, where x is a single letter ‘a‘–‘z’, and P is a palindrome itself. Here, we needed two base cases; case (3) is the recursion. Notice that the other definition of a palindrome, “a string that reads the same forward as backward”, is correct, but much more procedural: it tells us how to test whether something is a palindrome (“Is it the same forward as backward?”), but it doesn’t tell us how to describe all of them. 4. A simple algebraic expression consists of numbers, variables, parentheses, and + and *. (We leave out - and / to keep this a little shorter.) We’ll use abundant parentheses and forget about the precedence 31
  • 36. order here. We now want to express that something like “(5*(3+x))” is legal, while “x ( 5 * * + )” is not. We can recursively say that the following are legal expressions: • Any number. (This is a base case, and we could use our definitions of numbers above.) • Any variable. (This is another base case; we could use our definition of strings.) • (hAi + hBi), where both hAi and hBi are legal expressions themselves. • (hAi ∗ hBi), where both hAi and hBi are legal expressions themselves. For this example, you’d probably be very hard-pressed to come up with a non-recursive definition. What we have written down here is called a “context-free grammar” (or CFG). There are tools (a program called bison, which is the newer version of one called yacc) which, given such a recursive definition, will automatically generate C code for parsing inputs that conform to the definition. They are quite useful if you are trying to define your own input format or programming language. 5. In fact, following up on the previous discussion, you can write down a complete recursive definition of the C or C++ programming language. In fact, that is how programming languages are specified. It would be pretty hopeless to try this without recursion. 32
  • 37. Chapter 5 Linked Lists [Note: this chapter covers material of about 1 lecture.] Arrays are nice, simple ways to store blocks of data, but we don’t always know the necessary array size right off the bat. How many spaces should we reserve/allocate? Allocating up to an arbitrary size (say, 1000) is not a good idea because it may be too much or too little memory for particular cases. Dynamically sized arrays, which we saw in the previous lectures, give us a partial solution to the problem: at least, we don’t need to know the array size at compile time. But we do still need to know how large to make the array at run time when we declare it. How large do you think that Facebook should have made its user array when it started? If you have more and more customers arriving over time, it will be very hard to guess the “right” size. In this class, we will be looking at many data structures that don’t need to know the required number of elements beforehand; rather, their size can dynamically change over time. Perhaps the easiest such data structure is called the linked list; it nicely illustrates many of the techniques we’ll see reappear many times later. Consider how an array (dynamically or statically allocated) stores data. Whenever new users arrive, a really convenient way of dealing with it would be to just ask the operating system to expand our block of memory to the required size. However, there is no way to do this, and with good reason: the memory right after our array may be in use for some other data, so expanding our array may overwrite existing memory just after our array’s memory block. A way to “expand” our array may be instead to just get a second block of memory somewhere else, and put a pointer at the end of our first block to indicate where the other half is. This loses many of the advantages of arrays, such as being able to do simple arithmetic to look up elements. But at least, it would solve the problem in principle. Once we think about it that way, we could go to the extreme and make every element of our “array” point to the next element, removing all semblance of an array. This is what a linked list is: a series of nodes where each one points to the next one in memory, and each node contains a piece of data. We often depict linked lists as follows: value next • value next • value next • 1 1 1 * P P P P P P i head tail Figure 5.1: Basic illustration of a linked list Linked lists can be made as long as we want without declaring an initial size first. All we have to do is 33
  • 38. attach a node after the last one, and ensure that the previously last element of the list now points to the new one. It may be instructive to compare vectors and linked lists. Of course, part of why we learn about these things here is that we want to understand here how something like vectors works under the surface. Someone programmed vectors as an abstract data type, by using the types of primitives we are learning about in this class. But also, there is a fundamental difference: What a vector really does is allocate a dynamic array and keep track of its size. When the size is not sufficient any more, an array with larger size will be allocated. (Typically, implementations will double the size, but this can often be changed by passing parameters into the vector at creation.) Next, all data will be copied from the old array to the new one. This copying could slow down the use of the data structures. More importantly, vectors provide a functionality that we may or may not need: namely, accessing elements by an index. Linked lists don’t really allow that. On the other hand, linked lists are tailored towards appending elements cheaply, and traversing all elements. Like with practically any question about “Which data structure should I use?”, the answer is “It depends”. Namely, it depends on what types of operations you will frequently need to use. Finally, the main “real” reason to study linked lists is that understanding them thoroughly practices dynamic memory and recursion, and prepares you for some of the more complex data structures we will see later in the class; they rely on many of the same principles as a linked list, but are more complex. It’s usually a good idea to practice with easier examples first. Some analogies to keep in mind for linked lists are the following: • A treasure hunt for children. The parents provide a clue for the first treasure. When the children figure out the clue, they go there, and find a treasure, along with a note that has the next clue. Following that clue, they get to the second treasure, where they find another note with a clue to the next treasure, and so on. The clues play the role of pointers. • The game of “Assassin,” in which each participant gets the name of another participant to “kill” in a an agreed-upon way (shooting with a nerf gun, touching with a plastic spoon, etc.). When one participant is “killed,” his killer inherits his next assignment, and the winner is the last survivor. Again, the assignments can be thought of to form a linked list (except the tail points back at the head). • There are several other analogies of sequential events, like dominoes, train cars, and others. What’s nice about the two examples example is the explicit nature of the “pointers”. 5.1 Implementing Linked Lists Each node/element in the linked lists contains data (such as an int, string, etc.) as well as a pointer to the next node/element of the same type. Here, we will build a linked list of integers — it will be pretty obvious how to alter this for other types. In order to keep track of these two elements, we create a struct which we call Item. struct Item { int value; Item *next; Item (int val, Item *n) { value = val; next = n; } } Every Item has an int — a piece of data in the linked list — and a pointer next to another node. The first node will have a pointer to the second node, and so on. For the last node, we need a way to make sure 34
  • 39. to remember that there’s no node after it. The most common way is to have its next pointer go to NULL, but some people also have it link back to itself instead. The function Item we declare inside the struct is used to make initialization easy. This way, we can just write something like Item* p = new Item (5, NULL); instead of Item *p = new Item; p-value = 5; p-next = NULL; It is basically the same as a constructor of a class, which we will learn about in roughly a week. Note that, in order to access the first element of the list at all, we need a head pointer to the first Item. (If we lose track of this, the rest of the list can no longer be accessed.) 5.1.1 Linked list operations At a minimum, we want to be able to add elements to our list, remove them, and traverse the entire list. Here is how to implement those. Traversal: Unlike arrays, linked lists do not supply functionality to directly access the ith element. Instead, we start from the first node and then visit the next node repeatedly. To do so, we declare a variable Item *p that will keep track of the current node we are looking at. p starts out as the pointer to the head element. Then in each iteration of a for loop, we update it to its next pointer. So the code looks like this: void traverse (Item *head) { for (Item *p = head; p != NULL; p = p-next) { // Do something with p, such as print or read its value } } We can also traverse a list recursively, as follows: void traverse (Item *head) { // Do something with head, such as print or read its value traverse (head-next); } The nice thing about the recursive implementation (besides its extreme simplicity) is that it is very easy to make it traverse the list in reverse order, even if the list is singly linked. You simply change the order of the two statements, calling traverse (head-next) before doing the processing (such as the printing). Notice that the task of printing a linked list in reverse order is a very popular job/internship interview question, because it tests knowledge of linked lists and recursion. Addition: We take our input data item and create a new Item from it. This element will typically be appended to the end of the list, so its next pointer will be set to NULL. (We could also add it at the beginning of the list, which would change the implementation below.) In order to append it at the end of the list, we first need to find the last element tail, then set its next pointer to our new element. We need a special case for a previously empty list, as then, we also have to set the head pointer which was previously NULL. In summary, the code for adding a new element looks as follows: void append (Item *head, int n) { Item *newElement = new Item (n, NULL); if (head == NULL) head = newElement; else { 35
  • 40. Item *p = head; while (p-next != NULL) p = p-next; p-next = newElement; } } Notice the somewhat strange construction of the Item *head. We want to pass the head of the list, which is an Item *. But we may also need to change it (when it was NULL), so we need to pass the pointer by reference. A somewhat shorter implementation can be obtained by using recursion: void append (Item *head, int n) { if (head == NULL) head = new Item (n, NULL); else append (head-next, n); } Notice that both the recursive and the iterative implementation have to traverse the entire list to find the last element. This seems rather inefficient, and unnecessarily so. This is why typically, it is a good idea to not only maintain a head pointer, but also a tail pointer to the last element of the list. That way, we don’t have to traverse the entire list every time we want to add something. Removal: If we are given a pointer Item *toRemove to an element of the list we’d like to remove, we’ll eventually have the command delete toRemove; But before that, we also need to make sure that the link structure of the list stays intact. To do so, we need a pointer prev to the element right before toRemove in the list, so that we may set prev-next = toRemove-next; One way to get this pointer (if it exists — otherwise, toRemove itself must be the head of the list) is to start from the beginning of the list and scan through until we find the node p with p-next == toRemove. But that would take a long time and be cumbersome. The better solution is to store in each Item not only a pointer to the next element in the list, but also to the previous element. The result is called a doubly linked list, and unless you have a strong reason to prefer a singly linked list (such as a job interview or homework assignment specifying it), you should normally make your linked list doubly linked. In the definition of Item, we add the line Item *prev; In the function for adding an element, we set newElement-prev = NULL in the first case, and newElement-prev = p in the second case. For removing an element, we can now write something like: void remove (Item *head, Item *toRemove) { toRemove-prev-next = toRemove-next; toRemove-next-prev = toRemove-prev; delete toRemove; } This sets the next pointer of the preceding element to the next pointer of the element to be deleted, effectively unlinking it. Similarly for the second line with the prev pointers. While this looks good at first sight, we have to be more careful when the element we want to remove is the head or tail of the list (or both, for a list of a single element). Then, toRemove-prev or toRemove-next could be NULL, which means we can’t change their pointers. However, in those cases, we don’t need to update the corresponding pointers, so the actual implementation looks as follows: 36
  • 41. void remove (Item *head, Item *toRemove) { if (toRemove != head) toRemove-prev-next = toRemove-next; else head = toRemove-next; if (toRemove-next != NULL) toRemove-next-prev = toRemove-prev; delete toRemove; } As we saw, the real reason for having doubly linked lists is that they make deletions much easier and cleaner. (For your own amusement, you should perhaps also implement deletion in a singly-linked list, and measure how much slower it is.) A side benefit is that a doubly linked list can be easily traversed back to front. Sometimes, that’s listed as a reason for having a double linked list, but I think that that’s a red herring: first, traversing a linked list back to front is not hard even for a singly-linked list, if you use recursion. And second, it’s not a functionality that is often needed. Once we really wrap our heads around the idea of having a pointer (or two) to other elements in our Item, we may ask ourselves why not have more pointers to different elements. In fact, that is exactly what we will be doing when we get to more complex data structures such as trees and heaps later on. 5.2 Recursive Definition of Linked Lists While we know intuitively what a linked list is based on our discussion so far, we haven’t defined it formally. In other words, we haven’t yet said exactly how to distinguish a “Linked List” from a “Not Linked List”. In trying to come up with a definition, our intuition tells us that it is a bunch of items, where each item has associated data and points to another item. But first of all, one of the items doesn’t point to anything. And second, this would include a lot of nodes, all of which point to one other node. That’s not what we want. We may try something like “a bunch of item each of which points to 1 other node, and is pointed to by 1 other node, except for one node that points to no other node and 1 node which is not pointer to by any other.” This would be correct, but getting a little cumbersome. To keep the definition short yet clear, we can draw on recursive definitions which we saw a few lectures ago, and define Linked Lists as follows: • The empty list is a linked list. • A node that points to a “shorter” linked list is a linked list. We need the adjective “shorter” to exclude, for example, a one-node list where the node points to itself; in that case, it would be pointing to a linked list of the same length. (Remember that for recursive functions and definitions, we should only reference “smaller” objects, which we do so explicitly here.) 37
  • 42. 38
  • 43. Chapter 6 Abstract Data Types [Note: this chapter covers material of about 0.5 lectures.] If we take a step back from what we have been doing with Linked Lists, we can think about the func- tionality they enable. Let’s say we look at the version we just analyzed, which allows us to do three things: 1. Add an integer n to the list. 2. Remove an item from the list. 3. Print all the items in the list, in the order they were added. If all we care about is being able to perform these three operations, it doesn’t matter so much whether we implement them using linked lists, or perhaps arrays, or vectors, or some other method. What we really want is some data structure that stores data internally, and allows us to use these three operations. In other words, we focus on what we want to do, not how exactly it is done. Linked lists were one answer to how. Being precise about the what, i.e., the operations we want to see implemented, or are implementing, is what is specified as an Abstract Data Type (ADT). An ADT is defined entirely by the operations it supports, such as the ones above. In this class, we will spend a lot of time focusing on the following three ADTs. List: A list data type is defined by supporting the following key operations (where T denotes any type, such as int, string, or something else): 1. insert (int position, T value): inserts the value right before the given position, moving all the later elements one position to the right. 2. remove (int position): removes the value at the given position, moving all later elements one position to the left. 3. set (int position, T value): overwrites the given position with the given value. 4. T get (int position): returns the value at the given position. Thus, the List data type is a lot like an array in its functionality, but it allows inserting and removing elements while shifting all others. Notice that this definition is not quite complete. For instance, it does not tell us what values of position are legal, or what happens when they are out of range. For instance, if we are trying to insert something right before position 4 of a 2-element list, what should happen? Should an error be signaled? Should the list be filled up with dummy elements to make it possible? Similarly, we haven’t said what the legal ranges are of the position variable for the other operations. Here, we intend that position needs to be between 0 and size-1 (where size is the number of elements in the list) for all operations except insert, where it needs to be between 0 and size. 39
  • 44. Of course, we could add other functions that should be supported, such as size() or isEmpty. The textbook lists several others, but the four we have given are really the key functions that most define a List. You are probably already familiar with the C++ vector and deque data types, which are two natural implementation of the List data type (and provide a few other functions). But they are not the only ones, and we will look into this a bit more soon. Bag (set): A set (called Bag in the textbook) supports the following operations: 1. add (T item): Adds the item into the set. 2. remove (T item): Removes the item from the set. 3. bool contains (T item): Returns whether the set contains the given item. This is a rudimentary implementation of a mathematical set (see CSCI 170). Again, our specification isn’t complete. For instance, we do not say what should happen if an item is added multiple times: are multiple copies added, or only one, and is an error signaled? If multiple copies are added, then what does remove do? Does it remove just one of them or multiple? Typically, in a set, we only allow one copy. If we want to allow multiple copies of the same item, we call it a multiset. Dictionary (map): A map (or Dictionary, as it is called in the textbook) is a data structure for creating and querying associations between keys and values. Both keys and values can be arbitrary data types keyType and valueType themselves. The supported operations are: 1. add (keyType key, valueType value): Adds a mapping from key to value. 2. remove (keyType key): Removes the mapping for key. 3. valueType get (keyType key): Returns the value that key maps to. Again, this is a bit underspecified. What happens if the given key is not in the map? What should remove and get do now? Will they signal an error? Also, if another mapping is added for the same key, what will happen? Can two mappings co-exist? Will an error be signaled? Does the new one overwrite the old one? Typically, one would not allow co-existences of mappings for the same keys. The data type implements basically a (partial) function from keys to values, and you can’t have a key mapping to multiple values in a function. Let’s look a bit at commonalities and differences between these abstract data types. First, all of them support storing data and accessing them; after all, they are abstract data types. The big difference between List and the others is that a list really cares about the order. A list in which the number 2 is in position 0 and the number 5 in position 1 is different from a list in which the two numbers are in opposite order. On the other hand, neither a set nor a map care about order. An element is either in a set or not, but there isn’t even any meaning to such a thing as an index in a set. Similarly, elements map to others under a map, but there is no sense in which the elements are ordered or in a designated position. Let’s illustrate this in terms of some applications for these data structures. A List may be a good thing for such a thing as a music playlist, the lines in a computer program, or the pages of a book. For all of them, the order of the items is important, as is the ability to access specific items by index (page, or place in a playlist). On the other hand, a map is a natural fit for any kind of directory (student records or e-mail address, phone book, dictionary of words, web page lookup by search query). Here, it doesn’t matter in what order the items are stored, so long as it is easy, given the key, to find the corresponding record. While a List is thus fundamentally different from a map, a set is actually quite similar to a map. The main difference is that for a map, we store additional information for each item that we store, whereas for the set, we only remember the presence (or absence) of items. In other words, set is a special case of map, 40
  • 45. where the value associated with each key is some kind of dummy that is never used. For that reason, we will not really look much at implementing set. Instead, we will put a lot of emphasis on implementing map, and set then falls out for free. Of course, just because a List by itself is a very different object from a map does not mean we cannot use one to implement the other. In fact, a pretty natural (though not very efficient) way of implementing maps is to use lists as the place to store the data. It is important to notice, though that this is now a “how” question: just because we can use one technology to solve a different problem does not mean that the two are the same. Learning which ADT most naturally fits the requirements of what you are trying to do is an important skill. That way, you separate out the implementation of whatever uses the ADT from the implementation of the ADT itself, and structure your code much better. 41
  • 46. 42
  • 47. Chapter 7 Classes and Objects [Note: this chapter covers material of about 0.5 lectures.] Last time, we learned about Abstract Data Types. The notion of an Abstract Data Type goes hand in hand with Object-Oriented Design. In Object-Oriented Design of programs, we group together data items with the operations that work on them into classes; classes are data types, and we can then generate instances of these classes called objects. The objects have both the data inside them and the functions that operate on the data. When specifying a class, we usually take good care to keep two things separate: (1) the specification of the functions that other classes can call. This is like the specification of an abstract data type, and usually given in a header (.h) file. (2) the implementation of how the functions are actually doing their job, and how data items are stored internally. This is in a .cpp file, and hidden from other classes. Hiding it allows us to change the implementation without needing to change the implementation of other pieces of code that use our classes. We will see way more about classes and object-oriented design in the next few lectures. A class maps almost exactly to an Abstract Data Type; you can think of it as essentially a struct with functions in it. An object is one data item whose type matches a class; think of a class as the abstract notion of a car and the operations it supports (accelerate, slow down, honk the horn, switch on/off the lights, . . .), while an object is a particular car that implements these properties. So while there is only one “car” class, there can be many objects of type “car”. Remember how we implemented Linked Lists recently? Inside our main function, we had a variable for the head of the list, and we provided functions for appending and removing items from the list, as well as for printing (or processing) items. In a sense, the data storage and the functions belong together. The append or remove function is useless without the data to operate on, and the data do not help us without those functions to operate on them. Classes are exactly the way to group them together. Once you get more experienced with object-oriented design ideas, you will start thinking of your objects not just as pieces of code, but real entities which sometimes interact with each other in complex ways, notifying each other of things going on, helping each other out, and so on. One sign of a more experienced programmer in an object-oriented language (such as C++ or Java) is to be able to think of code not just as many lines in a row, but as almost an eco-system of different objects, each fulfilling its own little well-defined role, and interacting with each other in interesting and productive ways. 7.1 Header Files and Declarations As a first cut, our class declaration for a linked list of integers may look as follows: class IntLinkedList { void append (int n); void remove (Item *toRemove); void printlist (); 43
  • 48. Item *head; } Notice that the class includes both functions and data (head). Also notice that because the class contains the pointer head itself, we do not need to pass the head of the list to the function any more, as we did earlier. Of course, we still need to implement the functions. But before going there, we notice that this is what the rest of the world really needs to know about our class: they need to know the functions to invoke and (maybe) about the variables we use. Thus, these parts should go into a header file (with extension .h), while the actual implementation will go into a .cpp file. The header file should make copious use of comments to describe what exactly the functions do. For instance, what happens if the toRemove is not actually an element of the list? What format is used for printing the list? A moment ago, we said that the header file also contains the variables head inside the class. While this is true, to keep our code as modular as possible, other pieces of code should not really be allowed to directly access those variables1 . For instance, if a week later, we decide to use a vector implementation to achieve the same functionality, we don’t want any code out there to rely on the existence of a variable named head. We want to hide as much about the class as possible. The way to do this is to declare the variables private: this means that only functions in the class are allowed to access those variables, but not any other code. (We can also declare functions to be private, something we mostly do for internal helper functions.) The opposite of private variables/functions are public ones: these can be used by all other parts of the program. (There is also a protected modifier, which we will learn about later, when we learn about inheritance.) private, public, and protected are called access modifiers, since they modify who can access the corresponding elements of the class. Our new version of the class declaration will look as follows: class IntLinkedList { public: void append (int n); void remove (Item *toRemove); void printlist (); private: Item *head; } In this context, it is also important to know about get and set functions. Often, you will declare a class for which you really kind of do want to be able to overwrite the elements. For instance, think about Item, which we previously declared as a struct. Instead, we could declare it as a class, as follows: class Item { public: int value; Item *prev, *next; } In order to emulate the functionality of a struct when using a class, it is necessary to make all elements public. But making all elements public is very bad style: it allows other code to see the internals of a class, which could mean that changing things in the future is harder. But if we make the fields private, how can we read and change the value field? The answer is to add two functions getValue and setValue, which will read/write the value variable. Often, these functions can be as easy as return value; or value 1One downside of this example is that our remove function actually works with a pointer to an element. So in this sense, the implementation is not really hidden. We should overlook this issue for now, for the sake of continuing with this example. 44
  • 49. = n;. The reason to have them is that you may later change your mind about something. For instance, you may decide that value is a bad name for the variable, and you want to call it storedNumber instead. But if you do that, then all code that referenced value will break. If you have a getValue and setValue function, then you just have to change their implementation. More importantly, the get and set functions allow you to filter out illegal values, or perform other transformations. For instance, if you decide that only positive numbers are supposed to be stored, then you can create an error (or do something else) whenever the given value is negative. This kind of filtering can be very useful in implementing something like a Date class for storing a day of the year, or others where there is a natural desire to restrict values that can be stored. 7.2 Implementation of Member Functions Now that we have sorted out the header file, we can think about the implementation, which we would do in a file called IntLinkedList.cpp. At the top, we would include the header file: //IntLinkedList.cpp #include IntLinkedList.h Then, we can start associating functions with our IntLinkedList. The syntax for implementing class functions is of the following form: void IntLinkedList::append (int n) { // implementation... } That is, the function name is really the class name, followed by two colons, and then the name of the function. This tells the compiler that this code belongs to the class. If we did not include IntLinkedList:: in the method signature, append would just be a simple global function. The compiler has no way to know that we intend our function to be part of a class, unless we either put it inside class IntLinkedList { ... } (which is bad style), or put the class name in the signature, as we just did. For the actual implementation of the functions, we can just basically copy over the code from our previous linked list implementation — the only difference is that now, the functions are member functions of a class, rather than global functions, and the variable head is a (private) member variable of the class rather than having to be passed as a function parameter. So we won’t repeat all the code here. Inside the class, you can treat all variables (even the private ones) like global variables: all member functions know about all of the class’s members. However, before moving on, let us briefly revisit our nice recursive traverse() function, which we could use, for instance, to print the entire list, or print it in reverse order. Let’s look at the reverse order print version, and call it printreverse(). void printreverse (Item *head) { cout head-value; printreverse (head-next); } Here, the head pointer wasn’t always pointing to the actual head of the list, but rather, we had it point to different members of the list during different function calls. On the other hand, the overall public signature for the function should probably be ... public: void printreverse (); ... 45
  • 50. So how can we implement the recursive function? The answer is that we declare a private helper function. Something like ... private: void _printreversehelper (Item *p); ... Then, the implementation of printreverse is just void LinkedList::printreverse () { _printreversehelper (head); } 7.3 Constructors and Destructors We could now use our implementation to create an object of type IntLinkedList and append/remove elements from it: // main.cpp int main (void) { IntLinkedList *myList = new IntLinkedList; for (int i = 1; i 10; i ++) myList.append (i); myList.printlist (); return 0; } But one concern at this point is: where does the head variable get initialized? Earlier, we said that we express an empty linked list by having head=NULL. How can we be assured that when we create a new object of type IntLinkedList, the pointer is actually NULL? Because we made the variables private, we cannot add the line head=NULL; before the loop. So perhaps, we should create a member function initialize in the class IntLinkedList. Then, we just have to remember to call it right after generating the object. Fortunately, C++ has a mechanism called constructor to do just that. We can define functions with a special name which get automatically called when we create a new object of that class. That’s the right place to put initialization code. The name of a constructor is always the name of the class, and a constructor does not return anything. The constructor will be run as soon as an object is created. It looks as follows: IntLinkedList::IntLinkedList() { head = NULL; } Notice (again) that there is no return type. You can have multiple constructors; for instance, we may add a constructor that initializes the list to contain one element with a given number already: IntLinkedList::IntLinkedList(int n) { head = NULL; append (n); } If we define two constructors, when we create the object, we can write things like IntLinkedList *p = new IntLinkedList (), *q = new IntLinkedList (3); 46
  • 51. The first case calls the first constructor (and creates an empty list), while the second calls the second constructor (and creates a list with one item, containing the number 3). This is often useful for copying the data from one object to another, or reading an entire object from a file or a string. (We will learn more about those so-called copy constructors soon.) So long as their signatures (number or types of arguments) are different, we can create as many constructors as we want. Of course, they should all be declared in the header file, and implemented in the .cpp file. Similar to the initialization, we may also want to destroy data objects we created when deleting an object. Per default, when we call delete myList; in the code above, it will free up the space used for storing the pointer head, but it will not free up any of the memory for the elements of the linked list. That is a good thing — after all, other code may still need them. So if we do want them deleted, we need to create a function that does the “opposite” of the initialization. Such a function is called a destructor, and the destructor is automatically called when the object is deleted. Just as for constructors, there is a naming convention for destructors: they always have the same name as the class, with a preceding tilde: IntLinkedList::~IntLinkedList(). Our implementation of the destructor will look something like the following: IntLinkedList::~IntLinkedList () { Item *p = head, q; while (p != NULL) { q = p-next; delete p; p = q; } } 7.4 The this pointer Sometimes, in implementing a member function, you will run into scoping issues, such as: a function has a local variable with the same name as a member variable. For instance, imagine that we write a setValue function for the class Item, and it looks as follows: void setValue (int value) { // code here } We now want to somehow set the value field of the class to the variable value, but writing value=value clearly isn’t going to work, since — whichever variable value actually refers to (the answer is the function’s parameter) — both mentions of value will refer to the same variable. To make explicit that an assignment or other operation is talking about a member of the particular object to which the function belongs, there is the keyword this. this is always a pointer to the object to which the method belongs. So we can write this-value to refer to the data of the object itself, and write the above code as: void setValue (int value) { this-value = value; } There are other, more important, uses of the this pointer that you will learn about, partly in this class, and more importantly as you move on. One of the most important ones arises when you have one object A generating another object B, and B is supposed to know A’s identity. For instance, A could be the main part of an application, and B a part of the user interface that is being created on the fly. Now, A might want B to call some function in A when the user presses a certain button. But for that, B needs to be told the 47
  • 52. “identity” of A. The way one normally does this is that when B is created, it gets passed the this pointer of A, and thus knows about A. 48
  • 53. Chapter 8 Templates [Note: this chapter covers material of about 0.5 lectures.] Once we have carefully implemented a class IntLinkedList, we may discover that it is quite nice, and we would like to also have a LinkedList of strings. And one for users, and for web pages, and so many other types of objects. The obvious solution would be to copy the code we wrote for integers, and then replace the word int with string everywhere. This will cause a lot of code duplication. In particular, imagine now that after making 20 different copies, we discover that the original implementation of IntLinkedList actually had a few bugs. Now we have to fix them in all copies. What we’d really want is a mechanism for creating a “generic” LinkedList class, which allows us to substitute in any type of data, rather than just int, or just string. This way, we avoid code duplication and all the problems it creates (in particular in terms of debugging and keeping versions in synch). The C++ mechanism for doing this is called templates. The syntax is a little clunky, and because of the particular way in which C++ implements it, it unfortunately also forces us to give up some of our good coding practices. (It was not originally part of the C++ design, but rather grafted on top later, which explains why it does not fit very smoothly.) The basic way of defining templates classes is explained first via an implementation of a generic struct Item: template class T struct Item { T data; ItemT *prev, *next; } This tells C++ that we are defining a template class, which will be generally based on another class. We call that class T for now. By writing this, we can treat T as though it were an actual name of an existing class. What really happens is the following. Suppose that in some later code, we want to create a ListElement for strings. We will now write: Itemstring *it = new Itemstring; When the compiler sees this code, it will copy over the template code, and wherever it sees T, it will instead substitute string. In other words, it will exactly do the text replacement that we wanted to avoid doing by hand. Of course, in this way, we only maintain one piece of code, and the rest is generated only at compile time, and automatically. That’s how templates save us work. We can now apply the same ideas to our LinkedList class: 49
  • 54. template class T class LinkedList { public: LinkedList (); ~LinkedList (); void append (T value); void remove (ItemT *toRemove); void printlist (); private: ItemT *head; } When we implement class functions of template classes, the syntax is as follows: template class T LinkedListT::add (T item) { //code } Note the templateclass T line and the T in the class name. You have to put these in front of each member function definition; otherwise, how is the compiler to know that you are implementing a member function of the template class. Usually, if you get a “template undefined” error, you might have missed a T. (Note that the identifier T is arbitrary — you can use X or Type or whatever). By the way, the keyword template cannot be used just with classes, but also with other things, such as functions. For instance, we could write the following: template class Y Y minimum (Y a, Y b) { if (a b) return a; else return b; } Another thing worth noting about templates is that you can’t just use one type: you can use multiple types. For instance, you will remember that a map has two types: one for the key, and one for the value. We could specify that as follows: template class keyType, class valueType class map { // prototypes of functions and data go here } Templates were kind of grafted on top of C++, and not really part of the initial design. That is why the syntax is rather unpleasant, and the “obvious” way of doing things causes all kinds of compiler and linker problems. In order to get things to work, you will sometimes need to deviate from good coding practices, for instance by having #include of a .cpp file. Check the course coding guide for detailed information on how to deal with some of the most common problems. Seriously, read it very carefully before attempting to code anything with templates. It will save you many hours of debugging - promised! 8.1 The const keyword Because the type (referred to by T above) could be large, such as a custom type (think about a linked list each of whose items is a vector or some complicated struct), it is usually preferable to pass the parameters by reference, to avoid copying the whole item. So we would revise the syntax for the above definition as follows: 50
  • 55. Other documents randomly have different content
  • 59. The Project Gutenberg eBook of Il nemico è in noi
  • 60. This ebook is for the use of anyone anywhere in the United States and most other parts of the world at no cost and with almost no restrictions whatsoever. You may copy it, give it away or re-use it under the terms of the Project Gutenberg License included with this ebook or online at www.gutenberg.org. If you are not located in the United States, you will have to check the laws of the country where you are located before using this eBook. Title: Il nemico è in noi Author: Luigi Capuana Release date: May 4, 2013 [eBook #42643] Most recently updated: October 23, 2024 Language: Italian Credits: Produced by Carlo Traverso, Claudio Paganelli, Barbara Magni and the Online Distributed Proofreading Team at http://www.pgdp.net (This file was produced from images generously made available by The Internet Archive) *** START OF THE PROJECT GUTENBERG EBOOK IL NEMICO È IN NOI ***
  • 62. “Semprevivi„ BIBLIOTECA POPOLARE CONTEMPORANEA LUIGI CAPUANA Il nemico è in noi C ATA N I A Cav. Niccolò Giannotta, Editore Libraio della Real Casa — 1914
  • 63. PROPRIETÀ LETTERARIA Catania — Offic. tipografica Giannotta nel R. Ospizio di Beneficenza
  • 64. INDICE Avvertenza pag. vii Tormenta 1 Storia fosca 45 Convalescenza 69 Un bacio 87 Contrasto 99 L'ideale di Pìula 115 Un caso di sonnambulismo 131 Il dottor Cymbalus 171 Nota 205
  • 65. AVVERTENZA Il dottor Cymbalus che chiude questo volume è la mia prima novella. Fu pubblicata in La Nazione, nel settembre del 1865, ed ebbe l'immeritato onore di esser discussa, in un lungo articolo, dal corrispondente della Gazzetta di Augusta. Quel bravo signore avea scambiato la mia fantastica narrazione per un tentativo di studio della vita tedesca contemporanea, e si era affannato a dimostrare che gli italiani ne avevano una stranissima idea. E quando io, che lo vedevo quasi tutti i giorni nella redazione di Via Faenza, tentai di fargli capire l'equivoco in cui era caduto, non riuscii a convincerlo di non aver mai sognato di credere che qualche grande scienziato tedesco somigliasse al mio dottor Cymbalus, nè che i giovani sentimentali della Germania del 1855 avessero qualcosa di comune col mio William Usinger. Così questo volume finisce come avrebbe dovuto cominciare, se avessi voluto accennare al lettore le varie fasi delle mie esperienze narrative, fino a Tormenta! che è tra le ultime cose da me scritte. Caso mai, quest'avvertenza potrà, forse, avere qualche valore pei critici. E per essi, se si degnassero di notarlo, è stata conservata la data della pubblicazione di ogni novella. Luigi Capuana.
  • 66. TORMENTA! Pietro Borgagli osservava con crescente terrore la rapida trasformazione che avveniva in sua moglie. Ai primi sintomi della strana gelosia egli aveva sorriso. Da qualche mese in qua però la sua Diana andava insolitamente a sedersi su una poltrona a dondolo in quello studio che non sembrava stanza di raccoglimento e di lavoro per uno scrittore, ma piccola serra di piante da salotto e di vasi da fiori; e là ella faceva sembiante di svagarsi a leggere. A traverso le foglie del bambù che nascondeva un po' la poltrona, alzando gli occhi dalle pagine già riempite di grossa nervosa scrittura, egli sorprendeva spesso la moglie fissamente intenta a guardarlo sotto le sopracciglia corrugate per lo sforzo, pareva, di voler vederci meglio. Gli sembrava impossibile, che ella si sentisse invadere da inesplicabile diffidenza dell'opera letteraria di lui, ora che la felicità del possesso dell'adorata creatura, contèsagli per due anni da insidiose circostanze, davano alla sua immaginazione un rigoglio che i critici notavano con unanime compiacenza all'apparizione di ogni suo nuovo lavoro. Infatti egli sentiva dentro di sè qualcosa di più fresco, di più agile, di alato quasi; e il suo godimento artistico durante la produzione era così acuto, così intenso da fargli augurare che i lettori risentissero almeno un terzo dell'effetto di bellezza e di vita da lui provato scrivendo.
  • 67. Il giorno delle sue nozze era stato pubblicato in elegantissima edizione il suo primo romanzo: Il Gran Sogno. L'editore ne aveva fatto tirare una copia speciale, su carta della Cina con larghissimi margini, dove parecchi artisti avevano profuso disegni a penna, e figure acquerellate che davano a quella copia un valore straordinario. Rilegata in pergamena, con ornamenti quattrocenteschi, racchiusa in un cofanetto di pelle dello stesso stile, era stata il più prezioso dono delle loro nozze e certamente il più gradito. Il cofanetto portava impresso in oro il motto Sic semper! E Pietro aveva presentato, cerimoniosamente, piegando un ginocchio, il regalo del munifico editore alla giovane signora che baciò in fronte, tremando dalla commozione, il paggio editoriale, come egli si disse, già commosso quanto lei. Diana, distratta dal viaggio di nozze, dal trambusto di visite, di ricevimenti, di spettacoli al loro ritorno in città — quando la stagione invernale travolse nel suo vortice la giovane coppia, che la bellezza e l'ingegno rendevano ricercatissima — dopo sei mesi di vita coniugale non aveva ancora trovato un po' di tempo per tagliare e leggere la copia ordinaria del romanzo che suo marito le aveva regalata con la semplice dedica: a Diana Cantelli, mio vero «Gran sogno!. E sentì un po' di mortificazione, una sera in casa Marzani, quando la giovane signora, sua amica di collegio, le disse: — Ah, quel Gran Sogno di tuo marito! Un capolavoro di sentimento, di passione, di finezza! L'ho riletto due volte! Gli scrittori hanno un bel dire che si tratta di semplici invenzioni della loro immaginazione con qualche leggera tinta di realtà. Io credo, invece, che sia il contrario. E vedendo che Diana, rimasta confusa, non sapeva che cosa rispondere, riprese maliziosamente: — Di' la verità: non te l'ha mai dato a leggere? — Sì, e con questa dedica: a Diana Cantelli, mio vero Gran sogno. Solamente....
  • 68. — Solamente.... — Ti confesso che non ho ancora avuto la curiosità di aprirlo; mi basta di leggere... — e sorrise — il suo autore, per ora. Intanto la mattina dopo si affrettò a tagliare il volume e, chiusa nel suo studiolo, cominciò a divorare avidamente quelle pagine che, come più andava avanti, più le producevano la triste sensazione di farla inoltrare negli oscuri penetrali dell'animo di suo marito, quasi di nascosto, di sorpresa, e dov'ella non sarebbe forse mai arrivata senza le suggestive parole della sua amica: Gli scrittori hanno un bel dire..... — Sì, sì, era impossibile che quei personaggi non fossero davvero esistiti, che quelle violenti passioni non si fossero davvero scatenate nel cuore di essi fino al delirio, fino al delitto; che quelle parole, quelle frasi caratteristiche non fossero state davvero pronunziate con la desolata espressione che le pareva di sentire fin nelle righe del libro. E come poteva mai darsi che un uomo indovinasse o inventasse quelle passioni, quei contrasti, quelle lotte senza che il suo cuore vi avesse davvero partecipato in una o in altra maniera? Se non precisamente a quel modo, se con particolari diversi, non voleva dir nulla; forse anche con maggior violenza, con circostanze tali, senza dubbio, da far esitare la penna più esperta.... da costringerla ad attenuare, a travisare un po' la realtà, a deformarla probabilmente, per non far riconoscere persone e fatti e suscitare scandali e recriminazioni. Si maravigliava ella stessa di quell'improvvisa compenetrazione che le faceva intravedere l'intimo legame tra l'autore e l'opera sua. Non le era mai passato per la mente che ognuna delle figure, specialmente di donna — erano quelle che più la interessavano — fossero ancora qualcosa di vivo, di segreto nel cuore dell'artista, se egli sentiva la necessità di riprodurle con la magìa della sua parola, quasi per fissarle meglio, e perpetuarle per sè e per gli altri. Lo capiva ora tutt'a un tratto; e mentre fino a poche ore addietro ella si credeva in pieno possesso del cuore e dello spirito di suo marito, ora
  • 69. le sembrava di esserselo sentito sfuggire lentamente, di mano in mano, senza nessuna lusinga di più tornare a riconquistarlo. Reagì contro questa impressione, pensando che ben altro era il sapersi legata a lui da attuali forti vincoli di sentimenti e di carne, che non il sopravvivere, se pur poteva chiamarsi tale, nell'immaginazione, nel ricordo. Bisognava anzi già esser pervenuto a un punto di indifferenza completa per cacciar via fuori di sè, quasi per sbarazzarsene, quei fantasmi di una realtà una volta cara, e che, per felice disposizione d'ingegno, assumevano forma d'arte, e dovevano probabilmente riuscire irriconoscibili a colui stesso che li aveva a quel modo fatti vivere. Rilesse alcune pagine, sfogliando il volume, fermandosi a un nome di donna, seguendolo un po', abbandonandolo, riprendendolo verso la fine nella scena più violenta, e sorrise, rassicurata. Entrò col libro in mano nello studio del marito. — Oh! finalmente... — Sì, finalmente — ella lo interruppe, agitando il volume con grazioso gesto di minaccia — e puoi esser contento di quel che mi hai fatto soffrire. Egli ebbe una mossa di stupore. — Siete dei grandi sfacciati voi scrittori — riprese Diana con accento indefinibile, di scherzo e di serietà. — Vi compiacete di raccontarci le vostre prodezze, fingendo di raccontare la storia degli altri, assegnandovi la più bella parte negli avvenimenti, cioè quella che a voi sembra la più bella, e vi figurate così di aver gabbato i lettori. Ma sai che questo tuo Gherardo del Gran sogno è uomo spregevole, con tutte le sue arie di incorreggibile sentimentale? — Spregevole poi... — fece Borgagli, lusingato di veder presa sul serio la sua opera d'arte. — Ah! Tu lo difendi; è naturale. Quanta parte di te c'entra, confessalo, in quell'ambiguo carattere?
  • 70. — Ambiguo, no; complicato forse volevi dire. Allora facevo anche io il mio gran sogno e tu eri un po' la donna intravista, inseguita e non mai raggiunta, per dimenticare la quale Gherardo... — E perchè voleva dimenticarla? — Ecco — disse il marito, alzandosi da sedere e raccogliendo i fogli sparsi su la scrivania già coperti della sua grossa e nervosa scrittura. — Tu non puoi immaginare il piacere che mi produci in questo momento, ragionando dei personaggi e dei fatti del mio romanzo come di persone e di avvenimenti reali. Certamente qualcosa di mio c'è in essi e del me più schietto e più sincero. Sono passati per la mia immaginazione, si sono fusi, si sono organizzati in essa pur tentando di assumere una loro distinta personalità. Io stesso non saprei precisamente dirti come questo avvenga. L'artista, scrivendo, è in una specie di semi inconsapevolezza, sta a guardare, maravigliato — più che non farebbero gli altri — quel che avviene dentro di lui, il miracolo della creazione; e forse è il solo a goderne pienamente, perchè, soltanto lui può osservarne il processo di mano in mano che esso avviene. Quante viete cose ti dico, mentre dovrei baciare la bella mano che tiene ancora il mio libro, e la bellissima bocca che mi ha detto: Mi hai fatto soffrire! Diana si lasciò baciare la mano e la bocca, spalancando i vividi occhi caprini in viso al marito, un po' irrigidita di fronte alla calda effusione di quel ringraziamento, di cui ella non riusciva a capire il preciso significato, se egli non si burlasse, per caso, della sua femminile ingenuità. Parecchi volumi di novelle di suo marito ella aveva letti durante il fidanzamento, ma senza interessarsi di scoprire quel che esse potessero ricordare del passato di lui. Non aveva mai fatto nessuna distinzione tra quelle narrazioni rapide, appassionate, contenenti un fiero dramma interiore che talvolta scoppiava in tragedia; tra parecchie di esse piene di finezze, argute, quasi maligne, specialmente quando si trattava di rari caratteri di donne; e le molte novelle drammatiche o ironiche di altri autori italiani e stranieri. Le
  • 71. une e le altre erano servite a procurarle un po' di distrazione, con lieve godimento intellettuale. Ora invece si era messa a rileggere non solamente le novelle e i racconti di lui già raccolti in quattro bei volumi, ma anche le altre ancora disperse tra le colonne dei giornali e le pagine delle rassegne, dove suo marito le spargeva con profusione da gran signore, e che egli ritardava a raccogliere, attendendo che le impressioni della recente lettura si fossero alquanto scancellate e fosse solleticato il gusto di tornare a leggerle. Dapprima Borgagli si era rallegrato della conquista di una lettrice che non era, per cultura, per affettuoso interesse, lettrice ordinaria. Diceva anzi: della riconquista perchè, di giorno in giorno, notava certe sottili osservazioni intorno a parecchie novelle che prima le erano passate quasi inosservate. Pareva che ella avesse bisogno di schiarimenti, di dilucidazioni a proposito di una battuta di dialogo, di un motto passionale fatto sfuggire dalla bocca di una creatura, in un terribile momento, quando sembrava che soltanto quella parola risolvesse la inevitabile crisi di un povero cuore. — Perchè ha risposto così?... Come hai tu saputo che ha risposto così? — domandava infine ansiosamente. — Perchè la situazione, capisci, portava che doveva rispondere così — egli spiegava, stupito di quelle insistenti domande. — Non ho saputo, ho intuito che ha risposto... cioè che avrebbe risposto così. — Tu hai detto una volta, parlando con Leoni, che certe frasi, certi motti non s'inventano: si prendono dal vero, in circostanze inattese, uditi direttamente o riferiti. — Non ho rossore di confessare — rispose, sorridendo, Borgagli — che quattro o cinque delle frasi che più destano ammirazione in alcune mie novelle, io me le sono appropriate, come chi trova per via un diamante, smarrito del suo sbadato proprietario. E il paragone è soltanto giusto riguardo al diamante. Coloro che han pronunziate quelle frasi, quei motti sublimi o caratteristi, ne ignoravano il valore. Il pubblico dev'essermi grato di non averli lasciati disperdere.
  • 72. E il giorno dopo, a proposito di altre novelle, di altri racconti specialmente di quelli usciti recentemente dalla penna di lui, ella riprendeva la sua indagine con maggiore insistenza; e Pietro già notava con qualche sgomento, quell'accento di profonda tristezza con cui ella interrogava, quella espressione del viso che significava la dolorosa delusione di non aver raggiunto il suo scopo. — Ma che hai? che vuoi sapere — le diceva. — Sembra impossibile che una persona intelligentissima e colta come te, cerchi di scoprire in un'opera d'arte quel che non c'è. Il mio passato? Ma tu, adorata mia, da due anni hai scancellato tutto, tutto! Hai fatto cor novum dentro di me, un cuore nuovo, dove non può più esservi posto neppure pei ricordi, tanto tu lo riempi di te, rinnovando ogni giorno, ogni ora, ogni istante il tuo sovrano possesso. Come non lo intendi? Come non ti accorgi del male che ti fai? Giacchè tu soffri — non negarlo — tu diventi sempre eccitabile, sempre più nervosa; ed io ho paura quando ti sento tremare, fremere fra le mie braccia, come in questo momento, scossa da brividi molto diversi da quelli, dolcissimi, di una volta. — No, no, t'inganni! — ella tentava di negare. — Forse attraverso un periodo strano, di facili eccitazioni, nel quale mi sembra di sentir sviluppato in me un senso inesplicabile di veggenza, che però è ancora in uno stato torbido, fosco. — E rimarrà sempre tale, perchè sei tu che tenti di formartelo artificiosamente e non riesci; tenti l'impossibile. Che t'importa di sapere, con precisione, quel che c'è di me, della mia vita, del mio passato, del mio cuore, del mio spirito nell'opera mia narrativa? Qualcosa, molto o poco, dev'esserci, per forza. Ma ormai questo qualcosa, poco o molto, non ha nessuna importanza; è ridotto, per dir così, proprio a materiale — nota: a materiale — da servire alla costruzione dell'opera d'arte. L'importante è la forza creatrice che ora aiuta ad adoprarlo; e questa forza creatrice ha un personale slanciato, capelli di un biondo scuro, occhi grandi, caprini, scintillanti, labbra rosee, mani — oh, mani! — minuscole e braccia morbidissime; e dovrebbe stringermi più forte, più forte... così; e
  • 73. baciarmi così, così, e giurarmi di non torturarsi più, se non vuol farmi maledire quella che è stata il mio orgoglio, la mia consolazione, la mia ragione di vivere prima che entrasse in questa casa l'attuale gentile dominatrice, ed è e sarà, da ora in poi, il mio omaggio, la mia raccolta di fiori immortali da spargerle ai piedi: intendo la mia opera d'arte. Me lo giuri?.... Me lo giuri dunque? Diana, vinta da intensa commozione, si abbandonò singhiozzante tra le braccia del marito balbettando: — Sì! Sì! Sdraiata indolentemente su la poltrona a dondolo, con un libro in mano, che di tratto in tratto quasi le cascava sui ginocchi aperto o tenuto socchiuso dall'indice, Diana passava molte ore nello studio del marito, intento a terminare il romanzo che doveva comparire nel primo fascicolo del prossimo anno su la maggior rassegna italiana. Come per riposarsi, in alcuni giorni della settimana egli scriveva una novella che, secondo lui, doveva tenergli sciolta la mano con la forzata rapidità della narrazione; e anche per sodisfare a certi impegni con giornali e periodici che si contendevano i suoi lavori. Mai Diana aveva mostrato curiosità di leggere le piccole cartelle del manoscritto via via che suo marito le andava accumulando in un angolo della scrivania. Quella mattina egli la vide accostare con tale aria di sospetto che, quando ella tese la mano per prendere le cartelle alle quali era già sovrapposta l'ultima scritta, non ostante il sorriso, non ostante il tono appositamente umile e gentile con cui furono pronunziate le parole: — È permesso pregustare?... — non potè far a meno di fermarle il braccio e domandarle: — Ti senti male? — No.... Lasciami leggere. — Leggerai dopo. Rispondi: ti senti male? — No.... Lasciami leggere.... Voglio leggere....
  • 74. E si allontanò stringendo nel pugno il manoscritto, come una preda vittoriosa. Pietro Borgagli si sentì contrarre il cuore da uno spasimo atroce. E ritto in piedi, col dorso delle mani fortemente appoggiato su l'orlo della scrivania quasi avesse bisogno di premere su qualcosa di resistente per convincersi di non esser vittima di un'allucinazione, seguiva con gli occhi i movimenti di Diana, che leggeva le cartelle un po' sgualcite dalla stretta del pugno con cui le aveva afferrate, e indugiava, andava lestamente via con gli occhi, tornava addietro, fino a che, arrivata all'ultima, non scattò, tendendo il manoscritto, balbettando convulsamente: — Ora non dirai che non è vero! Per disgrazia, quella novella aveva forma di lettera. Un uomo rinfacciava aspramente la donna che si era fatta giuoco di lui, scoprendo tutte le vili manovre per mezzo delle quali lo aveva irretito; e in quelle poche pagine già si sentiva lo schianto di un cuore onesto, sincero, e l'amaro disprezzo con cui nobilmente si vendicava dicendo: «Io non vi denunzierò a vostro marito nè al vostro amante. La vostra miseria morale mi ispira in questo momento tanta pietà da rendermi capace di perdonarvi, se il mio perdono potesse giovarvi a qualcosa. «Ma voi siete.... E doveva seguire il castigo, la parte più arditamente nobile, più originale della novella. Pietro Borgagli non poteva sorridere dell'inganno da cui si era lasciata cogliere sua moglie. Questo morboso stato di animo di lei durava da mesi; egli, sul principio, non se n'era preoccupato, stimandolo la forma un po' esaltata di un amore che vuol avere l'esclusivo possesso della persona amata. Avea contato su gli effetti della serietà dell'assoluta dedizione della sua vita a colei che rappresentava ancora, come anni addietro, il gran sogno di lui già diventato realtà; sogno di bellezza, di sentimento, d'ideale, che ora
  • 75. s'identificava con l'Arte, l'altro gran sogno di ideale e di bellezza, raggiante, più che mai, nel suo fervido intelletto. Avea contato su questo; ma già si accorgeva di essersi illuso. — Ora non dirai che non è vero! Che poteva risponderle? Che lo spunto di quella novella gli era stato dato da Leoni, il quale, sere addietro, gli aveva riferito il caso di un suo amico, accorso una mattina da lui per sfogarsi pregandolo di ascoltarlo se non voleva che commettesse una pazzia? Infatti quello sfogo lo aveva salvato.... Quand'anche avesse risposto così, non sarebbe stato creduto. Prese le cartelle che Diana gli porgeva e fissandola con sguardo implorante, accompagnato da un sorriso che nascondeva la grande angoscia dell'artista, cominciò a fare, ad uno ad uno, in minutissimi pezzi, i piccoli fogli del manoscritto; e quando li ebbe tutti accumulati nel piatto indiano, di rame cesellato che a lui gran fumatore di sigarette, serviva da portacenere, accese un cerino e vi appiccò fuoco, rimescolandoli perchè bruciassero meglio e presto. Pallidissima, trattenendo il respiro, Diana aveva assistito immobile, strizzandosi le mani, a quell'olocausto tanto più grande, quanto non chiesto, nè immaginato; e quando le ultime monachine dileguarono, quasi inseguite, su gli ultimi pezzettini di carta consumati dal fuoco, ella si rovesciò pesantemente indietro: e sarebbe cascata sul pavimento se Pietro non l'avesse sorretta, portandola di peso sul divano nel salottino là accanto. Pareva che su quella casa piena di sorrisi di giovinezza e di sorrisi d'arte si appesantisse tutt'a un tratto un'ombra di muta tristezza con la grave cefalalgia che faceva gemere la povera Diana nell'oscurità della sua camera dove non doveva penetrare un fil di luce. Suo marito le stava seduto al capezzale e sembrava anche a lui che il buio, mentre contribuiva a non rendere più acute le trafitture di cui si lamentava sommessamente Diana, serviva a calmare le agitazioni del
  • 76. suo spirito intorno alla malattia di lei, contro la quale lottarono invano due famosi dottori. In certe ore di maggior desolazione, un terribile sgomento lo invadeva: di perdere la bella adorata creatura nel più spaventevole modo, con la pazzia. All'idea che avrebbe veduto sopravvivere, chi sa per quanti anni! il corpo della infelice, mentre la sua intelligenza andrebbe di mano in mano immergendosi nelle tenebre dell'ebetismo, Pietro Borgagli si sentiva straziare da una specie di rimorso, quasi egli avesse contribuito con la sua arte a produrre quell'eccitazione, quell'esaltamento nervoso nel delicato impressionabile organismo della sua giovane moglie. Per ciò non l'abbandonava un momento, per sviare a forza di affettuosissime cure il tremendo pericolo; a forza di volontà anche, tentando di comunicare a lei, come sapeva che fosse possibile, tutto il suo vigore di salute, col tenerle strette le mani scosse sempre da lievi tremiti, indizi delle intime agitazioni che la travagliavano. Da un mese, egli era entrato due sole volte nel suo studio, e per pochi minuti, provando dolorosissima ripugnanza di tutto quel che richiamava alla sua memoria il lavoro: carta, penna, libri, giornali. E così fu preso da gioia infantile la mattina che Diana, entrata in piena convalescenza volle ricondurvelo per mano e intronizzarlo, com'ella disse, davanti a la scrivania. Egli aveva dovuto obbedire, ma sùbito si era alzato per stringerla tra le braccia, per baciarla con tale impeto che fece venire a Diana le lacrime agli occhi dalla straordinaria commozione. Tenendosi per mano si erano affacciati al largo terrazzino che dava su l'aperta campagna. Gli pareva che tutto sorridesse in quella stesa di verde dorato dal sole, e che terminava laggiù laggiù, nella scintillante striscia di mare interrotta qua e là dalle chiome degli alberi, per cui prendeva apparenza di una sequela di azzurri laghetti. — Guarda! — egli disse, indicando una bassa nuvola scura che si avanzava rapidamente.
  • 77. Come avanguardia, centinaia di allodole si precipitarono per l'aria, volteggiando, inseguendosi — pareva — radunandosi compatta disperdendosi, spaurita — ora si capiva bene — dalla presenza di due falchi che intanto non riuscivano a ghermirne una. Ed ecco il grande sciame, di migliaia e migliaia di allodole, la bassa nuvola scura, che accorreva — se ne udiva distinto il forte strillìo — per resistere all'assalto col numero almeno, fuggendo poi via, lontano, per l'aperta campagna, poi mentre i due falchi proseguivano il loro feroce inseguimento. Diana sembrava intenta a guardare la strana battaglia; ma tutt'a un tratto si afferrò al braccio del marito, balbettando: — Pietro!... Pietro! E portò vivamente le mani agli occhi. — Oh, Dio! Oh, Dio!... Questi fili neri che mi tremolano davanti... che mi scendono su le pupille.... Oh, Dio! Oh, Dio!... Pietro!... Egli l'aveva presa tra le braccia, facendola rientrare, con la gola improvvisamente così inaridita da non poter dirle una parola. — Non ti vedo più!... Non ti vedo più! E si aggrappava a lui, spalancandogli in viso gli occhi limpidissimi che intanto non ci vedevano più! — Non è niente!... Non spaventarti! Non è niente! Egli si sforzava di rassicurarla, ma aveva nella voce uno sgomento maggiore di quello di lei. — Siedi qui, riposati! Hai voluto lasciare troppo presto la camera.... Sarà un abbagliamento — chiudi gli occhi — prodotto dalla luce troppo viva. Le accarezzava il viso bagnato di lacrime la baciava delicatamente su gli occhi; e intanto un fremito di sdegno o di ribellione gli infiammava il sangue contro la vigliacca crudeltà del Destino che si accaniva su quel povero corpo, su quella povera anima, su tanta fresca giovinezza, su tanto amore!
  • 78. — Bisogna attendere: bisogna aver fede nella virtù medicatrice della Natura che ne sa più di noi — aveva detto il valente oculista consultato più volte. A Pietro Borgagli sembrava che con l'oscurarsi delle pupille di Diana qualcosa si fosse oscurato anche dentro di lui. La voleva ogni giorno nel suo studio, seduta su la solita poltrona, come un misterioso genio tutelare, che taceva, e che però pareva tendesse l'orecchio per afferrar qualcosa di impercettibile per gli altri. Silenzio e atteggiamento che paralizzavano ogni sforzo di riprendere il romanzo interrotto o di scrivere qualcuna delle sue brevi novelle richieste con insistenza dai giornali e dalle rassegne per impegni trascurati da un pezzo. Non sentendo il lieve stridere della penna su la carta, Diana domandava ansiosamente: — Non lavori?... Non puoi lavorare? — Sì, sì, lavoro. Stento un po', dopo tanto intervallo. — Povero amor mio!... Io sono la tua cattiva influenza. — Non dovresti dirlo neppure per ischerzo! — Oh! lo dico sul serio. Chi sa che qualche volta anche tu non lo pensi? — Diana! Diana! Andava a inginocchiarsele davanti, prendendola per le mani, baciandogliele con lieve carezza, accostando il viso al viso di lei per osservare, desolatamente, quegli occhi limpidissimi da far credere che il non vederci più fosse una finzione, se si fosse potuto supporre tanta cattiveria in una dolce creatura di bontà come Diana. — Vedi? Ti distraggo anche senza volerlo! Va', riprendi a lavorare.... Al romanzo? A una novella? Puoi dirmelo senza timore che io diventi gelosa dei fantasmi del tuo passato... Come sono stata stupida! Quanti dispiaceri ti ho dati! E il signore mi ha gastigato!... Sai? Però ora mi sembra di esserti più vicina, più intima... Mi esprimo male...
  • 79. Di volerti più bene, oh! assai più bene di prima. E mi pareva impossibile che ciò potesse accadere... Ma tu non sai che fartene dell'amore di questa povera cieca che impaccia la tua vita, che, soprattutto, t'impedisce di lavorare, di farti vivo con tanti tuoi ammiratori.... Lasciami.... dire.... Egli le turava la bocca! Non poteva sentirla parlare così, perchè l'apparente tranquillità della voce non riusciva ad ingannarlo intorno all'intimo significato di quelle parole di desolazione e di pianto segreto. E le settimane passavano, e i mesi passavano nel torpido silenzio di quella solitudine dove Pietro Borgagli avea voluto rinchiudersi con colei che egli sentiva di amare sempre più, specialmente dopo i rapidi istanti — che poteva farci? — nei quali sentiva balenarsi in fondo al cuore un improvviso sentimento di rivolta contro il suo destino, un lampo di misero odio contro la innocente cagione di tutto questo..... Ed erano i momenti nei quali Diana, stretta forte al cuore di lui, si sentiva pienamente compensata di ogni sua disgrazia; nei quali Pietro non sapeva in che modo scontare quell'involontario lampo d'odio che sembrava gli avesse lasciato qualcosa di attossicante nel sangue. — Non lavori? Non puoi lavorare? — Il romanzo procede bene. Più tardi ti leggerò gli ultimi due capitoli scritti. — No, mi leggerai tutto a lavoro finito. E il giorno dopo — e così ogni giorno — ella tornava cupamente a interrogare: — Non lavori?... Non ti riesce come prima? — Anzi!... Mi pare che il tuo alito qui... — Non dire bugie!... Non sento stridere la penna... Ho buon orecchio, specialmente da che non ci vedo.
  • 80. — Ho mutato penna. Perchè ti prendi il gusto di tormentarti senza ragione? — Te tormento, non me! — Cattiva! Cattiva! Cattiva! Bisogna che io punisca cotesta bocca calunniatrice! Ed erano baci, ed erano abbracci deliranti, fino a che Diana vinta, spossata dalla commozione, non pregava: — Basta, Pietro!... Basta! Che pietà, ogni mattina dover condurre per mano nello studio, fino a una poltrona la bella creatura su le cui labbra appariva il caratteristico sorriso dei ciechi, e farla sedere, aggiustandole alle spalle e sotto i piedi i cuscini! Le si inginocchiava davanti, voleva che gli posasse le mani su la testa in atto di benedizione, e le augurava: — Sogna, mentre io inseguo il sogno della mia opera d'arte! Diana diveniva, di giorno in giorno, più chiusa, più impenetrabile, quantunque le rifiorisse sul viso una bellezza serena, gentile, una maravigliosa maturità di bellezza, che si rivelava pure in certe inflessioni della voce, in certe appassionate esitanze della parola, quasi ella avesse una pienezza di cose da dire e volesse assolutamente astenersene. Questo formava la maggior tortura di Pietro Borgagli, gli produceva un senso di stanchezza, di acredine, di sordo terrore insieme. E pensando all'avvenire, egli levava gli occhi dai bianchi fogli che aveva davanti e che stentava a coprire di quella caratteristica scrittura, rivelatrice, una volta, dell'agile vivacità del suo pensiero di artista. E fissava a lungo la cara silenziosa, che si dondolava lievemente su la poltrona con le bianche mani aperte sui ginocchi, e gli occhi che non vedevano, eppur fissi lontano, nello spazio, quasi guardassero, intenti, una dolorosa visione. Quella mattina, tutt'a un tratto, ella gli disse: — Come dev'esser bella questa fine di aprile alla Roccetta!
  • 81. Gli parve ch'ella intendesse di dirgli: — Andiamo ad isolarci di più! — Per quanto vivessero segregati, ricevendo lui pochi amici, lei, e raramente, una o due signore, intimissime, che non potevano farle sentire l'offesa della compassione, pure qualcosa della vita esteriore penetrava fino a loro, anche coi confusi rumori della via dove ferveva fino a tardi la vita cittadina. Diana non potè accorgersi dell'oscuramento del viso, del gesto d'impazienza provocati dalle sue parole. Credè che suo marito, immerso nel lavoro, non avesse ben udito, e ripetè: — Come dev'esser bella questa fine di aprile alla Roccetta! — Se vuoi, vi andremo domani... domani l'altro... — egli rispose. — Domani... Grazie!... Non ti dispiace? — Perchè dovrebbe dispiacermi, se fa piacere a te? — Grazie!... Domani! La villetta, con l'intonaco azzurro sbiadito dal tempo e dalle pioggie, era stata fabbricata dall'avo di Pietro in cima a quella roccia che, da ponente, scendeva quasi a picco su la vallata sassosa, coperta più in là di erbe, di piante selvatiche, di alberi di ulivi. Su la terrazza che permetteva di affacciarsi senza pericolo da quel lato, al ritorno del loro viaggio di nozze, i giovani sposi avevano passato serate deliziosissime, al lume di luna, in soavi colloqui, in più soavi silenzi, durante i quali i loro cuori si erano detti quel che le parole non avrebbero saputo mai dire! E ogni volta che vi erano tornati, il maggior loro godimento era stato il rivivere le indimenticabili serate di allora — Ricordi? — E tu, ricordi? — quasi niente più potesse raggiungere le deliziose impressioni di quel passato. Due sere dopo, attorno ad essi, su la terrazza alitava una frescura impregnata di selvaggi profumi campestri. Il sole stava per tramontare dietro la collina dirimpetto, tra una maravigliosa gloria di nuvole dagli orli d'oro, lanciando, diritti come frecce, nel cielo di tenue smeraldo, i suoi ultimi raggi; e Pietro, assorto in questo
  • 82. spettacolo che rapidamente vaniva sotto i suoi occhi, divinò, più che non vide, il gesto di angoscia con cui Diana si passava le mani sul viso, il crollo indietro della testa che rivelava la improvvisa risoluzione scoppiatale nell'animo; e, senza un grido, si slanciò ad afferrare l'esile corpo che, scavalcata la ringhiera, stava per precipitare nell'abisso della vallata a trovarvi la morte. La misera resistette un po', si agitò, tentò di svincolarsi, ma le forti braccia di Pietro l'avevano già tirata dentro, su la terrazza, e singhiozzante, mezza svenuta, la portavano di peso in camera, deponendola sul letto. Egli non osava di dirle una parola di rimprovero, quasi l'ingiusto ribollimento di acredine contro di lei, che gli amareggiava la bocca da due giorni, avesse contribuito a spingerla alla terribile risoluzione. Ed ora tremava di rimorso davanti a quel corpo disteso sul letto e che sussultava convulso; col terrore negli occhi di quel che sarebbe avvenuto, se egli non fosse arrivato in tempo per impedirgli di precipitare nel vuoto! Diana alzò le braccia brancicando l'aria e chiamò con un fil di voce: — Pietro! Pietro! Sentendosi abbracciata e baciata impetuosamente, ella scoppiò in un pianto dirotto, di sfogo, di sollievo. E appena si fu calmata, Pietro, con dolce rimprovero, le disse su la bocca, come bacio: — Perchè? Perchè? — Per liberarti! — Di che cosa? — Di me, di me, che ti rendo infelice come uomo e come artista! — T'inganni, Diana! La disgraziata sei tu che, forse, con un altr'uomo... Ho detto forse... Nessuno avrebbe potuto amarti come ti ho amato, come t'amo ancora, come sento di poter amarti sempre più!... Lo sai che, in certi momenti, ho avuto fin la stoltezza di rallegrarmi della tua cecità, geloso che i tuoi sguardi potessero, per
  • 83. caso, vedere qualcosa.... qualcosa da menomare, da rubarmi il tuo amore? — Mi ripeti le belle cose che tu suoli scrivere.... Non mi illudi però.... Non è colpa mia, se ti ho fatto soffrire... Per questo... Oh, come sono stanca! Stanca in tutti i sensi, col sangue tutto sossopra, con improvvise nuove punture agli occhi... — Riposa. Io ti veglierò a piè del letto... — È già sera avanzata? — Sono appena le sei e mezzo. Ma prima porgimi le tue mani, così, e giurami per la santa memoria di tua madre, che non tenterai mai più, mai più, in nessuna maniera... Ti figuri dunque che io potrei sopravviverti? Tanta poca stima hai di me?... Giurami! — Te lo giuro!... Ma sarebbe stato meglio altrimenti! — Come sei crudele, Diana! Si era buttato, vestito, sul lettino accanto, per poter accorrere sùbito se Diana avesse avuto bisogno della sua assistenza. Non aveva chiuso occhio, agitatissimo. Alle altre sue preoccupazioni, si era aggiunta ora anche questa del possibile suicidio di Diana in un nuovo improvviso sconvolgimento della sua coscienza! — Ah — pensava — si ha un bel voler essere forti, scettici contro le circostanze della vita! Arrivata a un punto, qualunque fibra più resistente vien tutt'a un tratto spezzata. Nella sua prima giovinezza, egli cavava, anzi, dalle contrarietà, gran incitamento al lavoro. Aveva scritto molte delle cose più belle in momenti in cui un altro si sarebbe lasciato vincere da scoraggiamenti, da fiacchezze, da vili rinunzie. Ora — e non poteva farne colpa ai suoi trentadue anni — non aveva saputo resistere come allora, quando era solo a lottare pel suo ideale d'arte. Si sentiva diminuito. Non amava più il lavoro; non trovava in esso le consolazioni, le sodisfazioni di una volta. La strana gelosia di Diana pel passato che le sembrava di veder rivivere in ogni pagina di
  • 84. lui, lo facevano stare in guardia, in sorveglianza che un accenno, una frase non dessero pretesti di turbamenti alla povera creatura innamorata. Si sentiva impacciata la immaginazione, diminuita ogni libertà di espressioni, di sentimento, di bollori, di passioni.... Era amato, è vero, come pochi potevano lusingarsi di essere stati amati; egli avea visto realizzare il suo gran sogno di bellezza e di amore non soltanto nell'Arte ma nella Vita; e colei che si agitava di tratto in tratto su quel lettino, e che, appunto per accesso di amore, poche ore addietro, avea tentato di morire, gli ispirava tale profonda tenerezza per la quale in quel momento non gli sembrava gran sacrifizio fin la rinunzia all'Arte. — No, l'Arte non vale la vita! — ripeteva talvolta mentalmente. Si sentì chiamare con voce così concitata da farlo balzare in piedi atterrito: — Quella striscia di luce... Pietro!... E prima che potesse rendersi conto a che cosa Diana accennasse, un grido acuto di gioia risuonava nella camera: — Ti vedo!... Ti vedo! Pietro!... Pietro mio! Quel che il valente oculista aveva dubbiosamente detto: — Bisogna aver fede nella virtù medicatrice della Natura — riceveva improvvisa conferma. La quasi identica violenta impressione che aveva prodotto la cefalalgia e poi la cecità, oprando ora in senso contrario, rendeva all'organo visivo la sua funzione non distrutta, ma impedita.... — Ti vedo! Ti vedo!... Era un balbettamento; sillabe che pareva s'impigliassero tra i singhiozzi; singhiozzi che non riuscivano, tormentosamente, a sciogliersi in pianto; e mani che brancicavano quasi non fossero sicure della realtà che tornava a sorridere agli occhi... Pietro, dapprima, aveva creduto a un improvviso sconvolgimento dell'intelligenza di sua moglie, all'estremo disastro tante volte temuto in quei tristissimi giorni, nei quali era stato condannato a passare le
  • 85. giornate nella buia camera dove ella soffriva gli strazi della cefalalgia... Appena però dovè convincersi del miracolo oprato dalla Natura, una gioia infantile lo sopraffece; poi un riso convulso, poi una commozione così profonda che somigliava allo stupore.... E siccome la viva luce che inondava la camera dalla finestra spalancata, abbagliava troppo la rediviva — non seppe meglio chiamarla in quel punto — egli si affrettò a socchiudere gli scuri. La teneva tra le braccia, seduta sui ginocchi, quasi l'avesse ritrovata dopo lungo smarrimento, quasi avesse ancora paura di vedersela nuovamente portar via. E, nel silenzio, essi sentivano i battiti anelanti dei loro cuori felici. Per parecchi mesi s'immersero, con vivissimo entusiasmo, nella vita di società. — Volete rifarvi del tempo perduto! — gli dicevano amiche, e amici che non si stancavano di festeggiarli. — Hanno ancora tanta giovinezza davanti a loro! — rispose una volta la signora Marzani che non sospettava neppure dalla lontana il gran male prodotto da certe sue parole. Sì, era vero; volevano rifarsi del tempo perduto, quantunque avessero tanta giovinezza davanti a loro. Troppi e troppi mesi erano rimasti in un isolamento che ora cercavano di scancellare dalla loro memoria, tanto era increscioso. Respiravano a pieni polmoni le salsedine delle stazioni balneari, viaggiando in incognito, perchè Pietro Borgagli odiava le interviste, i fotografi delle spiaggie, nè voleva che la gente guardasse come bestia rara lo scrittore che il caso della moglie, riferito dai giornali, aveva rimesso in vista, e del quale si annunziano prossimi volumi di romanzi, di novelle, e un dramma passionale. Un giorno si era divertito a dir questo a un giovane ma importuno giornalista che lo aveva riconosciuto e additato ai bagnanti di Viareggio, costringendolo così a scappare di colà. Tutti i giornali
  • 86. avevano riportato la notizia, rallegrandosi del suo ritorno all'Arte e augurandogli nuovi gloriosi successi. Gliene scrisse, lietissimo, il suo più caro amico, Leoni. Quella lettera, che arrivava da Londra, e dove ogni parola, ogni periodo parevano agitati da affettuosissima gioia li aveva raggiunti a Venezia, quando già si preparavano a tornare a casa del troppo prolungato pellegrinaggio, e produsse su tutte e due penosissima impressione. Non osarono comunicarsi, lusingandosi l'una e l'altro di essersi ingannati. Diana aveva cominciato a notare che quella riposante tranquillità venuta dietro alle esaltazioni, alle concitazioni, ai tormenti, alla paura di risvegli del passato nel cuore del marito, alle momentanee sodisfazioni di scoprirsi illusa, al riprendere e rinnovarsi delle stesse esaltazioni, degli stessi tormenti, delle stesse paure, insieme con l'angoscia della sopravvenuta cecità, e col cupo orrore della tenebra dov'era sparito ogni sorriso di giovinezza; sì, aveva cominciato a notare che in quella riposante tranquillità c'era qualcosa di torpido, di insignificante, e che lei intanto vi si adagiava con vigliacca rassegnazione, quasi non avesse più altro da desiderare, da sperimentare, all'infuori delle giornaliere occupazioni casalinghe che in certe circostanze la prendevano forte, come non avrebbe mai creduto che potesse accaderle. Da principio le era parso che ciò significasse stanchezza della vita di alberghi, di riunioni, di teatri, di concerti; vivo bisogno di riposo, di tregua almeno. Presto si accorse che era già, invece, un senso di esaurimento, un disinteressarsi di ogni idealità, un abbandonarsi alle minute cure esteriori della comoda vita che l'agiatezza le consentiva. In quanto a suo marito, ormai, ella era assolutamente sicura di non aver niente da temere dal passato, niente dal presente e molto meno dall'avvenire. Anche lui si sentiva evidentemente stanco, vinto da un torpore, che egli non avrebbe saputo dire se fisico o intellettuale. Per poco, in certi momenti, non credeva spenta o vicina a spegnersi ogni sua
  • 87. facoltà artistica; e non ne provava nessun rimpianto, come se questo fosse un fatto ordinario, nella natura delle cose. Gli pareva di averlo osservato precedentemente in altri, che intanto o non se n'erano accorti, o avevano voluto continuare per forza a produrre, dando misero spettacolo di decadenza. Non aveva lavorato a bastanza? Ora poteva coscenziosamente riposarsi; ora poteva svagarsi leggendo i lavori degli altri, osservando la tempesta dalla spiaggia: la tempesta della critica, la tempesta del mutevole gusto del pubblico che si lasciava abbagliare dalle lustre dei ciarlatani dell'arte, e aveva quel che si meritava. Leoni, tornato da Londra, era rimasto profondamente afflitto di ritrovarlo in questo stato d'animo. Non osava di credere ai suoi orecchi, sentendolo parlare, non con profonda ironia, non con desolante scetticismo, dell'Arte che ne aveva cinto di gloria il nome; e sarebbe stato indizio di un'evoluzione che poteva riuscire feconda, perchè l'ironia, lo scetticismo sono attività dello spirito capaci di rivelarsi in splendide creazioni. C'era però nelle parole di Pietro Borgagli un'incredibile supina indifferenza. — Ma è possibile? Tu mi fai strabiliare! — Se è, vuol dire che è possibile — rispose Borgagli all'amico. — In certi momenti — in certi lucidi intervalli, forse tu pensi — me ne maraviglio anch'io. Sin dalla mia prima giovinezza ho lottato, ho fatto a pugni contro tutto e contro tutti che volevano porre ostacoli alla mia azione. Poi fu lotta diversa, inferiore, con le grandi difficoltà della forma, con le non meno grandi difficoltà dei soggetti che mi piaceva di affrontare. Vincevo perchè sentivo, fuori e dentro di me, qualcuno o qualcosa che voleva opprimermi, atterrarmi; e per ciò tutti i miei lavori, novelle, romanzi, polemiche, squillavano come trombe guerresche, avevano l'aria di correre all'assalto, anche quando erano soffuse di grande pietà, di sottile gentilezza, raggianti di poesia nella loro umile espressione di tristissima realtà....
  • 88. Welcome to our website – the perfect destination for book lovers and knowledge seekers. We believe that every book holds a new world, offering opportunities for learning, discovery, and personal growth. That’s why we are dedicated to bringing you a diverse collection of books, ranging from classic literature and specialized publications to self-development guides and children's books. More than just a book-buying platform, we strive to be a bridge connecting you with timeless cultural and intellectual values. With an elegant, user-friendly interface and a smart search system, you can quickly find the books that best suit your interests. Additionally, our special promotions and home delivery services help you save time and fully enjoy the joy of reading. Join us on a journey of knowledge exploration, passion nurturing, and personal growth every day! ebookbell.com