PointeeType *
T U, T * and U * are different types.T * is the address of an object of type T.NULL.&var returns the address of var. The return type is pointer to the type of var.*ptr, where ptr is not dereferenceable, is undefined behavior.ElemType [N]
T [N], U [N] and T [M] are different types for T U and N M.N should be compile-time constant. Otherwise it is a VLA.a &a[0], T [N] T *.Pointer to array: T (*)[N]. Array of pointers: T *[N].
struct typesA special data type consisting of a sequence of members.
struct StructName.Declare a variable: Type varName
ElemType varName[N] for array type ElemType[N].T (*varName)[N] for pointer to array type T (*)[N].Initialize a variable: = initializer
structs: = { ... }.= {[3] = 5, [7] = 4}structs: = {.mem1 = x, .mem2 = y}.If a variable is declared without explicit initializer:
static variables, they are empty-initialized:
0 for integer types,+0.0 for floating-point types,static variables, they are uninitialized, holding indeterminate values.These rules apply recursively to the elements of arrays and the members of structs.
Any use of the value of an uninitialized variable is undefined behavior.
Expressions = operators + operands.
f() + g() * h(), f() - g() + h()&& and ||: short-circuit evaluation?:, (not in a function call or in an initializer list)If the evaluation order of A and B is unspecified, and if
A and B contain a write to an object, orthen the behavior is undefined.
+, -, *, /, %
(a / b) * b + (a % b) == a always holds.+, -, * and /, the operands undergo a series of type conversions to a common type.Bitwise operators: ~, &, |, ^, <<, >>
Compound assignment operators: a op= b is equivalent to a = a op b.
Be careful with signed overflows.
p++, ++p, p--, --p, p + i, i + p, p - i, p += i, p -= i, p1 - p2.p + i == (char *)p + i * sizeof(*p)++, --
++a and --a returns the value of a after incrementation/decrementation.a++ and a-- returns the original value of a.<, <=, >, >=, ==, !=
Member access: obj.member.
Member access through pointer: ptr->member, which is equivalent to (*ptr).member.
. has higher precedence than *, so the parentheses around *ptr are necessary.if (cond) stmtif (cond) stmt1 else stmt2for (init_expr; cond; inc_expr) stmtwhile (cond) stmtdo stmt while (cond);switch (integral_expr) { ... }break and continueFunction declaration: RetType funcName(Parameters);
Function definition: RetType funcName(Parameters) { functionBody }
Argument passing:
main functionEntry point of the program (after initialization of all global and local static variables).
One of the following signatures:
int main(void) { ... }int main(int argc, char **argv) { ... }, for passing command-line arguments./* another implementation-defined signature */Return value: 0 to indicate that the program exits successfully.
<stdio.h>: scanf, printf, fgets, puts, putchar, getchar, ...<string.h>: strlen, strcpy, strcmp, strchr, ...<ctype.h>: isdigit, isalpha, tolower, ...<stdlib.h>: Several general-purpose functions: malloc/free, rand, ...<limits.h>: Defines macros like INT_MAX that describe the limits of built-in types.<math.h>: Mathematical functions like sqrt, sin, acos, exp, ...VectorA "vector" in linear algebra:
It consists of two things: A sequence of
Vectorstruct Vector {
double *entries;
size_t dimension;
};
Do not name them with x and n!
[Best practice] Use meaningful names in programs.
struct Vector create_vector(size_t n) {
return (struct Vector){.entries = calloc(n, sizeof(double)),
.dimension = n};
}
void destroy_vector(struct Vector *vec) {
free(vec->entries);
// Do we need to free(vec)?
}
Usage:
struct Vector v = create_vector(10);
// some operations ...
destroy_vector(&v);
VectorThe default copy semantics of Vector is not satisfactory:
struct Vector v = something();
struct Vector u = v;
Now u.entries and v.entries point to the same memory block!
destroy_vector(&u);
destroy_vector(&v); // undefined behavior: double free!
Vectorvoid vector_assign(struct Vector *to, const struct Vector *from) {
to->entries = malloc(from->dimension * sizeof(double));
memcpy(to->entries, from->entries, from->dimension * sizeof(double));
to->dimension = from->dimension;
}
Is this correct?
Vectorfree the memory block that is not used anymore!
void vector_assign(struct Vector *to, const struct Vector *from) {
free(to->entries); // Don't forget this!!
to->entries = malloc(from->dimension * sizeof(double));
memcpy(to->entries, from->entries, from->dimension * sizeof(double));
to->dimension = from->dimension;
}
Is this correct?
Vectorvoid vector_assign(struct Vector *to, const struct Vector *from) {
free(to->entries); // Don't forget this!!
to->entries = malloc(from->dimension * sizeof(double));
memcpy(to->entries, from->entries, from->dimension * sizeof(double));
to->dimension = from->dimension;
}
What happens if to == from?
vector_assign(&vecs[i], &vecs[j]) where i and j have a chance to be equal.Vectorvoid vector_assign(struct Vector *to, const struct Vector *from) {
free(to->entries); // Don't forget this!!
to->entries = malloc(from->dimension * sizeof(double));
memcpy(to->entries, from->entries, from->dimension * sizeof(double));
to->dimension = from->dimension;
}
What happens if to == from?
vector_assign(&x[i], &x[j]) where i and j have a chance to be equal.freed, and the data are gone.Vectorvoid vector_assign(struct Vector *to, const struct Vector *from) {
if (to == from)
return;
free(to->entries); // Don't forget this!!
to->entries = malloc(from->dimension * sizeof(double));
memcpy(to->entries, from->entries, from->dimension * sizeof(double));
to->dimension = from->dimension;
}
Why do we declare the parameters as pointers?
Vectorvoid vector_assign(struct Vector *to, const struct Vector *from) {
if (to == from)
return;
free(to->entries); // Don't forget this!!
to->entries = malloc(from->dimension * sizeof(double));
memcpy(to->entries, from->entries, from->dimension * sizeof(double));
to->dimension = from->dimension;
}
Why do we declare the parameters as pointers?
to, we need to modify it.from, this is a read-only operation. Pass the address to avoid copies.bool vector_equal(const struct Vector *lhs, const struct Vector *rhs) {
if (lhs->dimension != rhs->dimension)
return false;
for (size_t i = 0; i != lhs->dimension; ++i)
if (lhs->entries[i] != rhs->entries[i])
return false;
return true;
}
Here we use != to compare two doubles directly. It's better to use
lhs and rhs are pointers, to avoid unnecessary copies.
Vectorstruct Vector vector_add(const struct Vector *lhs, const struct Vector *rhs) {
assert(lhs->dimension == rhs->dimension);
struct Vector result = create_vector(lhs->dimension);
for (size_t i = 0; i != lhs->dimension; ++i)
result.entries[i] = lhs->entries[i] + rhs->entries[i];
return result;
}
struct Vector vector_scale(const struct Vector *lhs, double scale) {
struct Vector result = create_vector(lhs->dimension);
for (size_t i = 0; i != lhs->dimension; ++i)
result.entries[i] = lhs->entries[i] * scale;
return result;
}
For vector_add, our design is to claim that "the behavior is undefined if the vectors have different dimensions".
double vector_dot_product(const struct Vector *lhs, const struct Vector *rhs) {
assert(lhs->dimension == rhs->dimension);
double result = 0;
for (size_t i = 0; i != lhs->dimension; ++i)
result += lhs->entries[i] * rhs->entries[i];
return result;
}
double vector_norm(const struct Vector *vec) {
return sqrt(vector_dot_product(vec, vec));
}
double vector_distance(const struct Vector *lhs, const struct Vector *rhs) {
struct Vector diff = vector_minus(lhs, rhs); // Define this on your own.
return vector_norm(&diff);
}
For vector_dot_product, our design is to claim that "the behavior is undefined if the vectors have different dimensions".
Vectorvoid print_vector(const struct Vector *vec) {
putchar('(');
if (vec->dimension > 0) {
printf("%lf", vec->entries[0]);
for (size_t i = 1; i != vec->dimension; ++i)
printf(", %lf", vec->entries[i]);
}
putchar(')');
}
What if we want to increase the dimension of a Vector? Implement the related functionality that reallocates a larger block of memory when needed.
void vector_push_back(struct Vector *vec, double x) {
if (/* reallocation is needed */)
vector_grow(vec); // Implement this function
vec->entries[vec->dimension++] = x;
}
You may need to add members to struct Vector.
struct Vector {
double *entries;
size_t dimension;
};
struct Vector create_vector(size_t n);
void destroy_vector(struct Vector *vec);
void vector_assign(struct Vector *to, const struct Vector *from);
bool vector_equal(const struct Vector *lhs, const struct Vector *rhs);
struct Vector vector_add(const struct Vector *lhs, const struct Vector *rhs);
struct Vector vector_minus(const struct Vector *lhs, const struct Vector *rhs);
struct Vector vector_scale(const struct Vector *lhs, double scale);
double vector_dot_product(const struct Vector *lhs, const struct Vector *rhs);
double vector_norm(const struct Vector *vec);
double vector_distance(const struct Vector *lhs, const struct Vector *rhs);
void print_vector(const struct Vector *vec);
create_vector is not mandatory. One can easily create a Vector with some garbage values.destroy_vector is not called automatically. If we forget to call it manually, memory leak happens.Vectors to these functions. The extra & and * are annoying.vector_assign when copying a Vector, disaster will happen.Vector: Disaster is caused easily by a simple free(vec->entries);.The named functions are inconvenient: To compute
struct Vector scaled = vector_scale(&w, 2);
struct Vector added = vector_add(&v, &scaled);
return vector_dot_product(&u, &added);
Can we express it directly by return u * (v + 2 * w);?
......
We will see the solutions to these problems in C++, by data abstraction, and by OOP (object-oriented programming).
From The Design and Evolution of C++, by Bjarne Stroustrup who invented C++:
C++ is a general-purpose programming language that
- is a better C, and
- supports data abstraction, and
- supports object-oriented programming.
#include <iostream>
int main() {
std::cout << "Hello world\n";
return 0;
}