As an assignment, I was to create an \$n\$-dimensional vector type for \$n \le 20\$. This vector only has to support addition and scalar multiplication.
I tried to write good code, with proper memory management, error reporting, and a nice API. However, I have little experience with C so I would welcome any feedback on my code, especially on whether the initialization and error handling workflow could be made more idiomatic. This is how my vector is supposed to be used:
Vector v = Vector_EMPTY;
Vector_E err_new = Vector_new(&v, 3);
if (err_new) {
... // handle error
}
// write the values into the initialized vector
double values[] = {1, 2, 3};
for (size_t i = 0; i < 3; i++) {
v.at[i] = values[i];
}
// twice_v = v + v
Vector twice_v = Vector_EMPTY;
Vector_E err_add = Vector_add(v, v, &twice_v);
if (err_add) {
... // handle error
}
... // do something useful
Vector_free(&twice_v);
Vector_free(&v);
And this is the Vector
implementation:
#include <stdbool.h>
#include <stdlib.h>
/// A n-dimensional vector type
///
/// Vector v = Vector_EMPTY;
/// Vector_E result_new = Vector_new(&v, dimensions);
/// ...
/// Vector_free(&v);
///
/// To access a dimension by index, use the `.at` field:
///
/// v.at[3] // the 4th dimension
typedef struct {
double *at;
size_t size;
bool valid;
} Vector;
#define Vector_EMPTY (Vector){NULL, 0, false};
typedef enum {
Vector_E_SUCCESS,
Vector_E_ALLREADY_INITIALIZED,
Vector_E_MALLOC,
Vector_E_DIMENSION
} Vector_E;
const char *
Vector_E_get_name(Vector_E status) {
switch (status) {
case Vector_E_SUCCESS:
return "Vector_E_SUCCESS";
case Vector_E_ALLREADY_INITIALIZED:
return "Vector_E_ALLREADY_INITIALIZED";
case Vector_E_MALLOC:
return "Vector_E_MALLOC";
case Vector_E_DIMENSION:
return "Vector_E_DIMENSION";
default:
return "Vector_E_UNKNOWN";
}
}
/// Initialize a new Vector that has been allocated outside:
///
/// Vector v = Vector_EMPTY;
/// Vector_E result = Vector_new(&v, 42);
///
/// If `Vector_new` was successful, you must call `Vector_free` to properly release memory!
///
/// Returns the following status codes: SUCCESS, ALLREADY_INITIALIZED, MALLOC
Vector_E
Vector_new(Vector *v, size_t n) {
if (v->valid) {
return Vector_E_ALLREADY_INITIALIZED;
}
double *values = calloc(n, sizeof(double));
if (!values) {
return Vector_E_MALLOC;
}
v->size = n;
v->at = values;
v->valid = true;
return Vector_E_SUCCESS;
}
/// Prepares the vector for deallocation
void
Vector_free(Vector *v) {
v->valid = false;
free(v->at);
}
/// Adds to vectors a and b into result, which must be uninitialized.
///
/// Vector result = Vector_EMPTY;
/// Vector_E result_add = Vector_add(a, b, &result);
/// ...
/// Vector_free(result);
///
/// Returns all status codes that `Vector_new` does, and DIMENSION if the dimensions of the input vectors don't match.
Vector_E
Vector_add(Vector a, Vector b, Vector *result) {
// both vectors must have same dimensionality
if (a.size != b.size) {
return Vector_E_DIMENSION;
}
size_t size = a.size;
Vector_E status_new = Vector_new(result, size);
if (status_new != Vector_E_SUCCESS) {
return status_new;
}
for (size_t i = 0; i < size; i++) {
result->at[i] = a.at[i] + b.at[i];
}
return Vector_E_SUCCESS;
}
/// Calculates the scalar product of two vectors a and b and writes the result into `result`.
///
/// double result;
/// Vector_E status = Vector_scalar_product(a, b, &result);
///
/// Returns the DIMENSION status code if the input vectors have different dimensions, otherwise SUCCESS.
Vector_E
Vector_scalar_product(Vector a, Vector b, double *result) {
if (a.size != b.size) {
return Vector_E_DIMENSION;
}
size_t size = a.size;
double my_result = 0;
for (size_t i = 0; i < size; i++) {
my_result += a.at[i] * b.at[i];
}
*result = my_result;
return Vector_E_SUCCESS;
}