i
,
j
,
k
)
for loop indices are
idiomatic
g++ -o flinstones fred.cc barney.cc
g++
command is
driver
program that invokes other programs
-v
flag shows subcommands
-save-temps
flag keeps intermediate files
.o
file)
fred.c
uses something defined in
barney.c
so must contain declaration
import
statements—function bodies are ignored)
#include
,
#define
,
#ifdef
)
#include
directive means
include
the file into the input stream
.h
extension
.o
)
file is not pure machine language; it is a
data structure
composed of
sections
extern
symbols
with pointers to the placeholders in the text section
extern
symbol
.o
file
T a[N];
T* p;
sizeof(a)
: N * sizeof(T)
sizeof(p)
: size of pointer
p = a
is the identical to
p = &a[0]
p + n
is defined as
(int)p + n * sizeof(T)
,
but
(unsigned long)p + n * sizeof(T)
typedef unsigned int ptrdiff_t;
or
typedef unsigned long ptrdiff_t;
in stdlib.h
size_t
is defined
p + n
is identical to
&a[n]
*(p+n)
is identical to
a[n]
n[a]
p - i
is legal, provided
p = &a[j]
and
i ≤ j
i ≤ j
in the general case
i > j
program behavior is "undefined", which means
Bad Things
can happen
p[-i]
is identical to
*(p - i)
and hence legal as long as it's in range
++p
is intended to be identical to
p = p + 1
which is (size_t)p + sizeof(T)
sizeof(char)
is 1 by definition
p + n
really is p + n when p is
char*
for (int i = 0; i < N; i++) {
do_something_with(a[i]);
}
and
for (T* P = a; p != a + N; ++p) {
do_something_with(*p);
}
for (Container::iterator P = container.begin(); p != container.end() + N; ++p) {
do_something_with(*p);
}
T a[]
syntax for parameter or declaration:
incomplete information for definition
T a[N][M]
sizeof(a)
: N * M * sizeof(T)
char
, int
, float
, ...)
and composite (array, struct/class)
data types
std::vector
or
std::string
over arrays
(type) expression
double x = 2.71;
int n = (int) x;
Foo* p = make_foo();
Bar* q = (Bar *) p;
static_cast<type>(expression)
:
change the bits
reinterpret_cast<type>(expression)
:
change the interpretation
const_cast<type>(expression)
:
change the constness only
dynamic_cast<type>(expression)
:
cast base pointer to derived pointer, but only if thing base
pointer is pointing to is a derived object
enum ShapeKind {
KIND_CIRCLE,
KIND_SQUARE
}
struct Shape {
ShapeKind kind_tag;
}
struct Circle {
ShapeKind kind_tag;
Point center;
int radius;
struct Square;
ShapeKind kind_tag;
Point top_left;
Point bottom_right;
};
// ...
Shape* shape;
// ...
switch (shape->kind_tag) (
case KIND_CIRCLE:
Circle* circle = reinterpret_cast(shape);
// ...
break;
case KIND_SQUARE:
Square* square = reinterpret_cast(shape);
// ...
break;
default:
oops("unrecognized shape");
}
Forgetting about the memory allocator for the moment, we have this problem that we want to count the frequency of occurrences of words in a text. Your pointy-haired boss has told you to use linked lists. You took CSS 342 and know what linked lists are, so you grudgingly accept the mission.
The
payload
of your linked list consists of two elements: a string
representing the word and an
int
holding the tally.
You know that strings in C are represented in an array of
char
values terminated by the special NUL value (also
written as
'\0'
,
but never
NULL
).
You ask your PHB what is the longest word in his vocabulary and he
tells you, 19.
You come up with an initial design for your linked list element:
struct ListNode {
char data[20];
int count;
struct ListNode* next;
};
20 is a magic number so you decide to make it look a bit
more sensible:
#define LONGEST_WORD 19
struct ListNode {
char data[LONGEST_WORD + 1];
int count;
struct ListNode* next;
};
Now you write your function to allocate and construct a node:
Clearly you weren't paying attention in your Software Engineering
class because you didn't do anything about handling the (rare)
case where
#include <cstring>
ListNode* allocate_node(char *word) {
ListNode* new_node = malloc(sizeof(ListNode));
strcpy(new_node->data, word);
new_node->count = 1;
new_node->next = NULL;
}
malloc()
fails and returns
NULL
.
Of course, if your boss didn't have a phobia about C++, you might
write this:
struct ListNode {
ListNode (char *word) : count(1), next(NULL) {strcpy(data, word);}
static ListNode* allocate(char * word) {
return new ListNode(word);
}
char data[LONGEST_WORD + 1];
int count;
struct ListNode* next;
};
ListNode node = ListNode::allocate("foobar");
Of course, your PHB forgot that some people know even longer
words. What happens when you try to create a
ListNode
with a longer word?
You overflow the array and start scribbling all over
count
and then
next
and then who-knows-what.
You try to fix this by changing around the order of elements:
struct ListNode {
struct ListNode* next;
int count;
char data[LONGEST_WORD + 1];
};
That way, you're only scribbing over who-knows-what. Okay, so
that's not exactly an improvement. So you decide there's never
going to be a 100-character word.
#define NODE_SLOP 100
ListNode* allocate_node(char *word) {
ListNode* new_node = malloc(sizeof(ListNode) + NODE_SLOP);
strcpy(new_node->data, word);
new_node->count = 1;
new_node->next = NULL;
}
Now, as long as the word is less that 120 characters or so, you're only scibbling over your own slop.
Finally, we come up with this robust solution:
struct ListNode {
struct ListNode* next;
int count;
char data[0];
};
ListNode* allocate_node(char *word) {
ListNode* new_node = malloc(sizeof(ListNode) + strlen(word) + 1);
strcpy(new_node->data, word);
new_node->count = 1;
new_node->next = NULL;
}
Much to your surprise, this is actually legal C/C++ code,
enshrined in the standard document. In fact, the compiler may
allocate some space in
ListNode
for
data
to give it a unique address in case you ever take the address of
the data field.
Coming soon. Watch this space.
Java has new/... (garbage collection) C++ has new/delete C has malloc/free