Here are some examples. Some declare individual variables, some declare arrays, some declare functions, and some are fancy.
int X; X is an integer. int const X = 3; X is a constant integer with value 3. int * P; P is a pointer to an integer. int * * P; P is a pointer to a pointer to an integer. int * const P = &A; P is a constant pointer to an integer A. const int * P; P is a pointer to a constant integer. const int * const P = &N; P is a constant pointer to a constant integer N. void * P; P is a pointer of an unspecified kind. int F(); F is a function (of no arguments) which returns an integer. int F(char); F is a function which has one char argument and returns an int. int * F(); F is a function (of no arguments) which returns a pointer to an integer. void (* P)(int); P is a pointer to a function which has one argument, an integer, and returns void. float (* P)(int *); P is a pointer to a function which has one argument, a pointer to an integer, and returns a float. int A[37]; A is an array of 37 integers. int A[37][13]; A is a 2-dimensional array of integers with 37 rows and 13 columns. int * A[37]; A is an array of 37 pointers to integers. int (* P)[37]; P is a pointer to an array of 37 integers. int * (* P)[37]; P is a pointer to an array of 37 pointers to integers. float (* A[37])(int); A is an array of 37 pointers to functions, each taking one argument, an integer, and returning a float. bool (* F)(char, char); F is a pointer to a bool-valued function of two char arguments. int * F(char, char); F is a function of two char arguments, returning a pointer to an int.
Some useful principles
If we have an asterisk, *, then a pointer is involved somehow.
If we have brackets, [], then an array is involved somehow.
If we have parentheses, (), on the right side of the expression, then a function is involved somehow. If you see a list of types inside parentheses, those are the types of arguments for a function.
If we have parentheses, (), on the left side of the expression, then they are being used to change the usual order of interpretation.
If a function is involved, then its return type will be leftmost part of the overall expression; "void" is a dead giveaway.
If we have "Type * Name..." then * goes with Type, not with Name; that is, we will have a pointer to a Type.
If we have "Type (* Name).." then whatever else may be involved, Name is a pointer.
A pointer of type "void *" must be recast as some other kind of pointer when we use it.
We can usually use the "go right, go left, alternately" rule: Start with the name, go right until you find a delimiter, go left, etc. Look at the above examples using that concept.
There is almost no limit to complexity. (That is not a challenge!) No matter how much skill you develop at parsing, someone will come up with something beyond you.
If you feel uncomfortable with the idea of a pointer to a pointer (or a pointer to a pointer to a pointer), you are far from alone.
In C++, there seems to be no way to return a function as the return value of a function--yet--but we could (using a cast) return a void pointer whose value points to an existing function.
A function cannot return an array as its value, but it can return a pointer to the first element in an array.
There are some additional modifiers, such as "long", "short", "signed" and "unsigned". We can also have "reference variables", but these are not recommended. The modifier "static" has to do with whether something is permanent or temporary, not with its identity.
About constants
If "const" appears more than once, then more than one thing is constant.
If P is a pointer to a constant type, then P cannot be used to change the value of *P, but P itself can be changed.
If P is a constant pointer to a non-constant type, then P can be used to change the value of *P, but P itself cannot be changed.
Anything constant must be initialized when it is defined, as there will be no other opportunity.
Arguments of functions may be listed as constant, but this affects whether the function is allowed to change the value of the argument, not whether the corresponding actual argument (in the calling function) is constant.
Typedef: convenient shorthand
Consider using "typedef" to make it easier to work with really complex cases. For instance, we could have
typedef int * Pint; typedef unsigned long int BigUInt;which means that
Pint B[20]; BigUInt H;gives us an array of 20 pointers to integers and one unsigned long integer variable.
This is just an alias mechanism, but it can be helpful.