Your teacher is correct - an array expression may not be the target of an assignment. However, what's happening in
int b[2] = a;
is that an initializer for an array in a declaration must be a brace-delimited sequence of values. The expression a is not a brace-delimited sequence, hence the error.
What your teacher is talking about is situations like
int b[N];
...
b = some_expression;
This is not allowed - you cannot assign to an array using the = operator.
You can assign to individual array elements (as long as those elements are not arrays themselves):
b[i] = some_value;
b[j] = a[i];
Except when it is the operand of the sizeof or unary & operators, or is a string literal used to initialize a character array in a declaration, an expression of type "N-element array of T" will be converted ("decay") to an expression of type "pointer to T" and the value of the expression will be the address of the first element of the array.
The declaration
int a[][3] = {1, 2, 3, 4, 5, 6};
creates a 2-element array of 3-element arrays of int. Graphically:
+---+
a: | 1 | a[0][0]
+---+
| 2 | a[0][1]
+---+
| 3 | a[0][2]
+---+
| 4 | a[1][0]
+---+
| 5 | a[1][1]
+---+
| 6 | a[1][2]
+---+
Thus, when the expression a appears in the line
int (*ptr)[3] = a;
it is converted from type "2-element array of 3-element array of int" to "pointer to 3-element array of int", or int (*)[3], and the value of the expression is the address of a[0][0]. Since you declare ptr as a pointer to a 3-element array of int, the initialization works, and ptr points to the first element of the array (a[0]):
+---+
a: | 1 | a[0][0] <--- ptr
+---+
| 2 | a[0][1]
+---+
| 3 | a[0][2]
+---+
| 4 | a[1][0]
+---+
| 5 | a[1][1]
+---+
| 6 | a[1][2]
+---+
Since ptr points to a 3-element array of int, ptr+1 will point to the next 3-element array of int (a[1]):
+---+
a: | 1 | a[0][0] <--- ptr
+---+
| 2 | a[0][1]
+---+
| 3 | a[0][2]
+---+
| 4 | a[1][0] <--- ptr + 1
+---+
| 5 | a[1][1]
+---+
| 6 | a[1][2]
+---+
Edit:
A handy chart for declarations involving arrays, functions, and pointers:
T *a[N]; // a is an array of pointers to T
T (*a)[N]; // a is a pointer to an array of T
T *f(); // f is a function returning a pointer to T
T (*f)(); // f is a pointer to a function returning T
Things can get even more complicated:
T (*f[N])(); // f is an array of pointers to functions returning T
T (*a())[N]; // a is a function returning a pointer to an array of T
-Wallto your Makefile or compilation command).