mirror of
https://github.com/bkthomps/Containers.git
synced 2025-11-16 12:34:47 +00:00
Add documentation (#107)
This commit is contained in:
29
README.md
29
README.md
@@ -5,20 +5,35 @@
|
||||
[](https://github.com/bkthomps/Containers/blob/master/LICENSE)
|
||||
|
||||
# Containers
|
||||
This library provides various containers. Each container has utility functions to manipulate the data it holds. This is an abstraction as to not have to manually manage and reallocate memory.
|
||||
This library provides various containers. Each container has utility functions
|
||||
to manipulate the data it holds. This is an abstraction as to not have to
|
||||
manually manage and reallocate memory.
|
||||
|
||||
Inspired by the C++ standard library; however, implemented using C with different function interfaces as the C++ standard library but with the same container names.
|
||||
Inspired by the C++ standard library; however, implemented using C with
|
||||
different function interfaces as the C++ standard library but with the same
|
||||
container names.
|
||||
|
||||
## Setup
|
||||
It is possible to compile this library as either static `.a` or dynamic `.so`:
|
||||
1. A static library is slightly faster than a dynamic one, however, if the library is modified, the entire project codebase which uses it will need to be recompiled.
|
||||
2. A dynamic library can be changed without recompiling the codebase, assuming no function definitions have changed.
|
||||
1. A static library is slightly faster than a dynamic one, however, if the
|
||||
library is modified, the entire project codebase which uses it will need to be
|
||||
recompiled.
|
||||
2. A dynamic library can be changed without recompiling the codebase, assuming
|
||||
no function definitions have changed.
|
||||
|
||||
The installation process is as follows:
|
||||
1. Clone this repository and navigate to it.
|
||||
2. Run `make static_clang`/`make static_gcc` or `make dynamic_clang`/`make dynamic_gcc` for either a static or dynamic library.
|
||||
3. Then, you can copy-paste `containers.h` and `containers.a`/`containers.so` into your project to include the containers.
|
||||
4. Finally, you remember to link the library by including `containers.a -ldl`/`containers.so -ldl` as an argument.
|
||||
2. Run `make static_clang`/`make static_gcc` or
|
||||
`make dynamic_clang`/`make dynamic_gcc` for either a static or dynamic library.
|
||||
3. Then, you can copy-paste `containers.h` and `containers.a`/`containers.so`
|
||||
into your project to include the containers.
|
||||
4. Finally, you remember to link the library by including
|
||||
`containers.a -ldl`/`containers.so -ldl` as an argument.
|
||||
|
||||
## Documentation
|
||||
For high-level documentation and usage, visit the
|
||||
[documentation](documentation.md) page. For in-depth documentation, visit the
|
||||
[code docs](https://codedocs.xyz/bkthomps/Containers/) page.
|
||||
|
||||
## Container Types
|
||||
The container types that this library contains are described below.
|
||||
|
||||
126
documentation.md
Normal file
126
documentation.md
Normal file
@@ -0,0 +1,126 @@
|
||||
# Documentation
|
||||
For setup and compilation instructions visit the [readme](README.md). For
|
||||
in-depth documentation, visit the
|
||||
[code docs](https://codedocs.xyz/bkthomps/Containers/) page.
|
||||
|
||||
## General Overview
|
||||
Each container has an initialization function which returns the container
|
||||
object. For a deque, this would be `deque_init()` which returns a `deque`. The
|
||||
returned object is a pointer to an internal struct which contains data and
|
||||
book keeping information. However, this is abstracted away to reduce mistakes,
|
||||
and since it is not stored in the most easy manner. More information about the
|
||||
initialization type of function is presented below in its own section.
|
||||
|
||||
Once this object is initialized, it is possible to manipulate it using the
|
||||
provided functions, which have in-depth documentation available. Each container
|
||||
has adding and retrieval type functions, and each type of container has its own
|
||||
specific set of function interfaces, which are explained in-depth at the
|
||||
function level in the code docs link above. More high-level information will be
|
||||
explained in its own section below.
|
||||
|
||||
Finally, each container will have to be destroyed to free the memory associated
|
||||
with it.
|
||||
|
||||
# Container Initialization
|
||||
When creating a container, first you must decide what type of data you wish to
|
||||
store in it (or types for the case of a map). Then, you must either decide if
|
||||
you wish to store a copy of the data in the container, or a pointer to it. The
|
||||
benefit of a copy is that you don't have to manage the memory, and it is easier
|
||||
to reason. However, it might only be reasonable to store copies if the data type
|
||||
is relatively cheap to copy. Using pointers, you can either store references to
|
||||
automatic variables (make sure the lifetime stays valid while the data is
|
||||
stored), or dynamically-allocated variables (make sure to free at some point).
|
||||
Either way, you must pass in a pointer of what you wish to store, be it a
|
||||
pointer to a value, or a pointer to a pointer. Keep in mind that for some
|
||||
containers, changing data which is stored will cause undefined behavior. Fret
|
||||
not, as all containers document their potential undefined behavior in the
|
||||
function documentation comments.
|
||||
|
||||
Going back to the initialization function and ways we can initialize containers
|
||||
and and store data, we can use an example of a `deque`:
|
||||
```
|
||||
deque a = deque_init(sizeof(int)); /* OK: cheap to copy */
|
||||
deque b = deque_init(sizeof(struct expensive_data)); /* Bad: valid, but expensive to copy */
|
||||
deque c = deque_init(sizeof(struct expensive_data *)); /* OK: pointer to expensive data, but be careful about memory management */
|
||||
```
|
||||
|
||||
Also, if the arguments which you passed in to the initialization function are
|
||||
invalid, or the system is out of memory, the initialization function may return
|
||||
NULL. Therefore, it is good to check for a return of NULL from the
|
||||
initialization functions.
|
||||
|
||||
# Container Operations
|
||||
Next, the two basic container manipulation functions of containers are to add
|
||||
and remove elements. To add elements, pass a pointer of the element you wish to
|
||||
copy to the add function. To remove an element, pass a pointer to a variable
|
||||
which can store the size of element that is stored in the container.
|
||||
|
||||
Using the `deque` example, adding and removal can be done this way (if storing
|
||||
int):
|
||||
```
|
||||
deque d = deque_init(sizeof(int));
|
||||
...
|
||||
int add = 5;
|
||||
int rc = deque_push_back(d, &add); /* 5 has been added to the back of the deque */
|
||||
...
|
||||
int retrieve;
|
||||
int rc = deque_pop_back(&retrieve, d); /* retrieve now is equal to 5 */
|
||||
...
|
||||
```
|
||||
|
||||
Functions can fail for various reasons, such as the provided index argument
|
||||
being out of bounds, or the system running out of memory. The in-depth
|
||||
documentation linked above provides the exhaustive list of return codes for each
|
||||
function, which are present in the `errno.h` header file. For example, an
|
||||
invalid argument would return `-EINVAL`, and on success 0 would be returned.
|
||||
|
||||
# Comparators and Hash Functions
|
||||
The associative containers and the priority queue require the user to initialize
|
||||
the container with a comparator, and the unordered associative containers also
|
||||
require a hash function to be passed in. State should not be modified in
|
||||
comparators or in hash functions, or else it would lead to undefined behavior.
|
||||
|
||||
When a comparator function is called, two arguments are passed in, being two
|
||||
elements to compare. The comparator must return 0 is they are equal, a negative
|
||||
value if the first is less than the second, and a positive value is the first is
|
||||
greater than the second. To be valid, a comparator must obey the following
|
||||
rules:
|
||||
1. Reflexive: `arg_1 == arg_1` and `arg_2 == arg_2` must always be true
|
||||
2. Symmetric: if `arg_1 == arg_2` is true, then `arg_2 == arg_1` is true
|
||||
3. Transitive: if the comparator is called twice with three distinct objects,
|
||||
the first time being `(arg_1, arg_2)` and the second `(arg_2, arg_3)`, then if
|
||||
`arg_1 == arg_2` and `arg_2 == arg_3` then `arg_1 == arg_3` must be true
|
||||
4. Consistent: the truth of `arg_1 == arg_2` shall never change over time if
|
||||
both arguments do not change
|
||||
|
||||
When a hash function is called, it is provided one argument, and must hash its
|
||||
attributes. A hash is a mapping from the object to a number. Two objects which
|
||||
are equal must always result in the same hash being produced. The inverse is not
|
||||
required, but should be sufficiently improbable (meaning, two distinct objects
|
||||
may produce the same hash, but it should be very rare).
|
||||
|
||||
Using unordered set as an example (and storing int), the comparator and hash
|
||||
function can be passed in as follows when initializing:
|
||||
```
|
||||
unordered_set_init(sizeof(int), hash_int, compare_int)
|
||||
```
|
||||
|
||||
A comparator can be as follows:
|
||||
```
|
||||
static int compare_int(const void *const one, const void *const two)
|
||||
{
|
||||
const int a = *(int *) one;
|
||||
const int b = *(int *) two;
|
||||
return a - b;
|
||||
}
|
||||
```
|
||||
|
||||
And a hash function can be as follows:
|
||||
```
|
||||
static unsigned long hash_int(const void *const key)
|
||||
{
|
||||
unsigned long hash = 17;
|
||||
hash = 31 * hash + *(int *) key;
|
||||
return hash;
|
||||
}
|
||||
```
|
||||
Reference in New Issue
Block a user