Upload
others
View
3
Download
0
Embed Size (px)
Citation preview
Organización de ComputadorasOrganización de ComputadorasDepto. Cs. e Ing. de la Comp.Depto. Cs. e Ing. de la Comp.Universidad Nacional del SurUniversidad Nacional del Sur
El Lenguaje de El Lenguaje de Programación CProgramación C
(Pt. 2)(Pt. 2)
Organización de ComputadorasOrganización de Computadoras 22
CopyrightCopyrightCopyright © 2011-2015 A. G. Stankevicius
Se asegura la libertad para copiar, distribuir y modificar este documento de acuerdo a los términos de la GNU Free Documentation License, Versión 1.2 o cualquiera posterior publicada por la Free Software Foundation,sin secciones invariantes ni textos de cubierta delantera o trasera.
Una copia de esta licencia está siempre disponible enla página http://www.gnu.org/copyleft/fdl.html.
Organización de ComputadorasOrganización de Computadoras 33
ContenidosContenidosIntroducción al paradigma imperativo.
Sentencias de control.
Entrada y salida estándar.
Pasaje de parámetros.
Tipos de datos estructurados.
Gestión de la memoria dinámica.
Estructuras de datos dinámicas.
Gestión de archivos.
Organización de ComputadorasOrganización de Computadoras 44
PunterosPunterosLas variables declaradas de tipo puntero representan direcciones de memoria.
En todo momento se debe tener clara la diferencia entre una variable de tipo puntero y el dato alojadoen la dirección por él apuntada.
Los punteros se declaran con un asterisco delante del identificador de la variable:
int *px, y; /* px es un puntero e y es un entero */
Organización de ComputadorasOrganización de Computadoras 55
Operaciones sobre punterosOperaciones sobre punterosEn relación a los punteros, existen dos operadores fundamentales:
Desreferenciamiento: si px es un puntero, la expresión *px denota al contenido apuntado por el puntero(es decir, el valor almacenado en la dirección referenciada por el puntero).
Enreferenciamiento: si x es una variable, la expresión &x denota a la dirección de memoria donde se encuentra almacenado el valor de x.
Organización de ComputadorasOrganización de Computadoras 66
Operaciones sobre punterosOperaciones sobre punteros
int x = 10, y = 5;
int *px;
px = &x;
*px = &y;
px = 1234; // ¿tendrá sentido?
10
?5
1000:1001:1002:
int xint yint *px
1001
10005
int xint yint *px
1000:1001:1002:
Organización de ComputadorasOrganización de Computadoras 77
Aritmética sobre punterosAritmética sobre punterosLos punteros se comportan como cualquier otra tipo elemental, es decir, admiten toda la gama de operaciones aritméticas.
La clave para entender esto radica en quelos punteros almacenan en esencia direccionesde memoria, en otras palabras, enteros positivos.
Las operaciones aritméticas se comportan de manera inteligente, teniendo en cuenta el tipo base al cual apuntan los punteros.
Organización de ComputadorasOrganización de Computadoras 88
Aritmética sobre punterosAritmética sobre punterosint vector[5] = {10, 20, 30, 40, 50};
int *pv = vector; // aquí, ¿qué denota *pv?
*pv = 15; // ¿y aquí?
*(pv + 3) += 5;
char string[250] = {'M', 'a', 'r', 'í', 'a'};
char *ps = &string[2];
*(ps + 2) = 'o'; // ¿en qué cambió string?
Organización de ComputadorasOrganización de Computadoras 99
Pasaje por referenciaPasaje por referenciaLos punteros permiten simular un pasajede parámetros por referencia:
int reset(int *a, int b) {
*a = 0; b = 0; // *a y b son reseteados.
}
void main() {
int x = 1, y = 1;
reset(&x, y); // x vale 0, pero y vale 1.
}
Organización de ComputadorasOrganización de Computadoras 1010
Pasaje por referenciaPasaje por referenciaint a = 10, b = 20;
void cambiar(int p, int *q) {
p += 2;
*q += p;
}
int main () {
cambiar(a, &b);
printf(“a vale %i y b vale %i”, a, b);
}
Organización de ComputadorasOrganización de Computadoras 1111
Punteros genéricosPunteros genéricosPara declarar un puntero genérico, el cualsea capaz de apuntar datos de distintos tipos, se debe apelar al tipo void.
Este tipo solo puede figurar como tipo base de un punteros, es decir, declarar a una variable común de tipo void carece de sentido.
void *px; // válido.
void x; // inválido.
Organización de ComputadorasOrganización de Computadoras 1212
Definición de arreglosDefinición de arreglosLa definición de arreglos y matrices se realiza indicando las dimensiones entre corchetes:
// un arreglo de 25 enteros
int a[25]
// una matriz de reales de 4x4
float vx[4][4];
// un arreglo de carácteres
char string[250];
Organización de ComputadorasOrganización de Computadoras 1313
Indexado de arreglosIndexado de arreglosA diferencia de otros lenguajes, en C la primer componente de un arreglo ocupa la posición 0:
int i, vector[10];
for (i = 0; i < 10; i++) { // ¡empieza en 0
vector[i] = i; // y termina en 9!
}
Organización de ComputadorasOrganización de Computadoras 1414
Inicialización de arreglosInicialización de arreglosLos arreglos en C pueden inicializarse al mismo tiempo que son definidos:
int vector[5] = {0, 1, 2, 3, 4};
int matrix[2][3] = { {1, 2, 3},
{4, 5, 6} };
char cadena1[250] =
{'H', 'o', 'l', 'a', '!'};
char cadena2[250] = “Hola!”;
// ¿será cadena1 == cadena2?
Organización de ComputadorasOrganización de Computadoras 1515
Cadenas de caracteresCadenas de caracteresEn este lenguaje las cadenas de caracteres son simples arreglos de caracteres que terminanen un caracter nulo ('\0'):
char cadena1[250] =
{'H', 'o', 'l', 'a', '!', '\0'};
char cadena2[250] = “Hola!”;
/* ahora cadena1 y cadena2
resultan equivalentes. */
Organización de ComputadorasOrganización de Computadoras 1616
Asignación de arreglosAsignación de arreglosEl operador de asignación no puede utilizarse con arreglos ni con cadenas de caracteres.
int a[5], b[5];
a[0] = 1;
a[2] = 3;
b = a; // ¡Error!
No confundir con la inicialización de arreglos,la cual si está permitida:
int c[5] = {0, 3, 2, 0, 0};
Organización de ComputadorasOrganización de Computadoras 1717
Funciones de libreríaFunciones de libreríaC dispone de una gran cantidad de funcionesde librería sobre cadenas de caracteres (presentes en la librería strings):
strcat(): para concatenar strings.
strcpy(): para copiar strings.
strcmp(): para comparar dos strings lexicográficamente.
strlen(): para determinar la longitud de un string.
strstr(): para buscar un string en otro.
Organización de ComputadorasOrganización de Computadoras 1818
Entrada estándar de stringsEntrada estándar de stringsEl modificador “%s” permite lectura de cadenas de caracteres y las finaliza con NUL:
char str[25];
scanf(“%s”, str);
/* lee hasta encontrar un white-space */
Para leer un string sin incluir nueva línea y agregando NUL al final de la cadena se utiliza el modificador: “%[^\n]”:
scanf(“%[^\n]”, str);
Organización de ComputadorasOrganización de Computadoras 1919
Entrada estándar de stringsEntrada estándar de strings¿Que és un white-space en C?
' ' (0x20) space (SPC)
'\t' (0x09) horizontal tab (TAB)
'\n' (0x0a) newline (LF)
'\v' (0x0b) vertical tab (VT)
'\f' (0x0c) feed (FF)
'\r' (0x0d) carriage return (CR)
Organización de ComputadorasOrganización de Computadoras 2020
Arreglos y punterosArreglos y punterosUna variable de tipo arreglo se asimila aun puntero a su primer componente.
En consecuencia, puede ser utilizada como tal:
int *pb, *pc;
int a[5] = {10,20,30,40,50};
pb = a;
*pb = 15;
pc = &a[3];
*pc += 5;
10 50403020a:
pb: pc:
15 45
Organización de ComputadorasOrganización de Computadoras 2121
Pasaje de arreglosPasaje de arreglosAl pasar un arreglo como argumento, se debe dejar su primera dimensión sin especificar:
void times(int vector[], int matrix[][4]) {
...
}
int main() {
int a[12], b[4][4];
times(a, b);
}
Organización de ComputadorasOrganización de Computadoras 2222
Pasaje de arreglosPasaje de arreglosCabe señalar que, producto de esta omisión,no resulta del todo trivial determinar la longitud de los arreglos recibidos como argumento.
Una alternativa relativamente directa consisteen agregar un parámetro adicional que informeel valor de la dimensión faltante.
Otra posibilidad es usar alguna variable globalpara comunicar ese dato.
Una tercer propuesta es acordar que el arreglo tenga un tamaño predeterminado, haciendo uso por casode una constante para denotar ese tamaño.
Organización de ComputadorasOrganización de Computadoras 2323
Usando un parámetro extraUsando un parámetro extravoid mostrar(int vx[], int size) {
int i;
for (i = 0; i < size; i++)
printf(“Elto nro. %i = %i\n”, i, vx[i]);
}
int main() {
int a[5] = {10, 20, 30, 40, 50};
mostrar(a,5);
}
Organización de ComputadorasOrganización de Computadoras 2424
Usando una variable globalUsando una variable globalint size = 5;
void mostrar(int vx[]) {
int i;
for (i = 0, i < size, i++)
printf(“Elto nro. %i = %i\n”, i, vx[i]);
}
int main() {
int a[5] = {10, 20, 30, 40, 50};
mostrar(a);
}
Organización de ComputadorasOrganización de Computadoras 2525
RegistrosRegistrosFrecuentemente hace falta manipular múltiples datos de una cierta entidad.
Por caso, nombre, apellido y número de registrode un alumno.
También, frecuentemente hay que retener datos de múltiple entidades.
¿Qué estructura de datos resulta más conveniente usar?
¿Múltiples arreglos, uno para cada “atributo”de las entidades?
Organización de ComputadorasOrganización de Computadoras 2626
Definición de un registroDefinición de un registroLos registros son un tipo de dato estructurado compuesto de un conjunto de campos, los que son de otros tipos (básicos o complejos) y que se les asocia una etiqueta a cada uno:
struct etiqueta {
tipo1 campo1;
tipo2 campo2;
...
} variable1, variable2, ...;
Organización de ComputadorasOrganización de Computadoras 2727
Definición de un registroDefinición de un registrostruct persona {
char nombre[20];
int edad;
float altura;
} profesor, alumnos[10];
struct persona unprofesor = {“Juan Perez”, 32, 1.82};
struct persona *palumno;
Organización de ComputadorasOrganización de Computadoras 2828
Acceso a los camposAcceso a los camposLos campos se acceden de dos maneras:
Usando el operador punto (.), si es un registro.
O bien, usando el operador flecha (->), si se tratade un puntero a un registro.
struct persona el, *ella, todos[20];
printf(“Nombre: %s\n”, el.nombre);
todos[2].edad = 20;
ella = &todos[2];
printf(“Su edad es %d\n”, ella->edad);
Organización de ComputadorasOrganización de Computadoras 2929
Pasaje de registrosPasaje de registrosEn general, ninguna estructura de datos compleja debe ser pasada por valor .
La razón es que esto implica el copiado en tiempode ejecución de mucha información.
Por ende, los registros no deben ser pasados como argumentos, al menos no de forma directa.
En otras palabras, siempre conviene pasar por valor un puntero a la estructura en cuestión.
En todas las arquitecturas, los punteros ocupan apenas unos bytes.
Organización de ComputadorasOrganización de Computadoras 3030
Definición de enumeradosDefinición de enumeradosLos enumerados son conjuntos de constantes numéricas definidas por el usuario.
enum colores {rojo, verde, azul};
enum colores fondo, borde = verde;
enum booleano { falso = 0,
verdadero = 1 };
enum booleano condicion = falso;
Organización de ComputadorasOrganización de Computadoras 3131
Definición de tipos de datosDefinición de tipos de datosPara definir nuevos tipos de datos a partir de otros ya definidos se usa la sentencia typedef:
typedef int booleano;
typedef struct persona tPersona;
typedef struct punto {
int coordenadas[3];
enum colores color;
} tPunto;
tPunto plano[3];
Organización de ComputadorasOrganización de Computadoras 3232
Modificadores de variablesModificadores de variablesLa declaración de variables aceptalos siguientes modificadores:
static: el valor de la variable se debe conservar entre las llamadas a la función.
register: la variable es almacenada, de ser posible, en un registro del procesador, en vez de hacerloen memoria principal.
volatile: la variable puede ser modificadapor un proceso exterior.
const: la variable no puede ser modificada,sólo inicializada.
Organización de ComputadorasOrganización de Computadoras 3333
Modificadores de variablesModificadores de variables#include <stdio.h>
void count() {
static int acc = 0;
printf(“%d\n”, acc++);
}
int main() { // ¿qué valores se imprimen?
count(); count(); count();
return 0;
}
Organización de ComputadorasOrganización de Computadoras 3434
Modificadores de variablesModificadores de variablesconst int max = 10;
int letra(const char *text, char l) {
int i, acc = 0;
for (i=0; i < max && text[i]; i++)
if(text[i] == l)
acc++;
return acc;
}
Organización de ComputadorasOrganización de Computadoras 3535
Declaraciones constantesDeclaraciones constantesExisten dos formas de declarar una variable como constante:
const int x = 5;
int const x = 5;
Pero, al tratarse de punteros no da lo mismo:
const char * origen;
char * const origen;
Consejo práctico: ¡leer la declaración siempre de atrás para adelante!
Organización de ComputadorasOrganización de Computadoras 3636
Modificadores de funcionesModificadores de funcionesLas funciones también pueden ser declaradas con otros modificadores:
static: esta es una restricción de enlace. Denotaque sólo se puede usar dentro del archivo de código fuente actual.
extern: la variable o función en cuestión será declarada pero no será definida (su definiciónserá provista en otro archivo fuente).
inline: la función debe ser expandida íntegramente al ser invocada (es decir, no se va a generar un salto a la función).
Organización de ComputadorasOrganización de Computadoras 3737
Modificadores de funcionesModificadores de funcionesuno.c
static void f() {
...
}
void g() {
f();
}
dos.c
extern void f();
extern void g();
int main() {
g();
f(); // ¡Error!
}
Organización de ComputadorasOrganización de Computadoras 3838
Memoria dinámicaMemoria dinámicaLa declaración de toda variable reservaun espacio de tamaño previamente conocidoen el registro de activación en curso.
El espacio reservado para las variables de tiposde dato elementales coincide con lo retornadopor la función sizeof().
El espacio reservado para las variables de tipos de dato estructurados coincide con la suma del espacio reservado para cada uno de sus componentes.
Por otra parte, también es posible gestionarla reserva y liberación de memoria dinámica.
Organización de ComputadorasOrganización de Computadoras 3939
Memoria dinámicaMemoria dinámicaC cuenta con las siguientes funciones para la gestionar la asignación de memoria dinámica:
void* malloc(size_t): esta función intenta reservar la cantidad indicada de memoria dinámica.
free(void*): esta función libera la porciónde memoria dinámica que comienza donde se indica.
void* realloc(void*, size_t): esta función reajusta al tamaño solicitado el espacio de memoria dinámica que comienza donde se indica.
Organización de ComputadorasOrganización de Computadoras 4040
Memoria dinámicaMemoria dinámicaAl terminar de hacer uso de grandes estructuras dinámicas enlazadas se debe tener cuidado al liberar el espacio que ocupaban.
La invocación a free() sólo libera el espacio reservado por el malloc() que generóel puntero pasado como argumento.
Si una estructura se armó mediante múltiples invocaciones a malloc(), su espacio deberáser retornado mediante múltiples invocacionesa free().
¡IMPORTANTE!
Organización de ComputadorasOrganización de Computadoras 4141
Memoria dinámicaMemoria dinámicaint *i;
char *c;
struct persona *p;
i = (int *) malloc(sizeof(int));
c = (char *) malloc(sizeof(char));
p = (struct persona *)
malloc(sizeof(struct persona));
free(i);
c = (char *) realloc(c, sizeof(char) * 9);
Organización de ComputadorasOrganización de Computadoras 4242
Java vs. CJava vs. CHasta ahora los lenguajes de programación Java y C han compartido bastantes similitudes, sobre todo a nivel de sintaxis.
En este punto aparece una diferencia que estamos obligados a tener siempre en cuenta:
Java se hace cargo por completo de la recuperación de la memoria dinámica asignada a un objeto cuyo uso finalizó.
C, en contraste, deja esa tarea por completoen manos del programador.
Organización de ComputadorasOrganización de Computadoras 4343
ListasListasRecordemos las principales característicasde la estructura de datos dinámica “lista”:
Es una estructura de datos dinámica.
El espacio ocupado por sus elementos es asignadoa medida que se necesite.
Cada elemento apunta al siguiente, determinandouna relación lineal.
Puede ser simple o doblemente enlazada.
Permite implementar pilas y colas.
Organización de ComputadorasOrganización de Computadoras 4444
ListasListasstruct celda {
tipo elemento;
struct celda *sig;
};
elto sig
elto sig
elto sig
elto sig
Organización de ComputadorasOrganización de Computadoras 4545
ListasListasRecordemos algunas de las particularidadesde esta estructura de datos:
Los elementos usualmente son todos del mismo tipo.
Por lo general, cada celda es creada por separado invocando repetidamente a la función malloc().
Cada celda apunta a la siguiente.
La última celda apunta a NULL.
La lista completa se representa mediante un puntero al primer elemento.
Organización de ComputadorasOrganización de Computadoras 4646
Posición directa vs. indirectaPosición directa vs. indirectaLa posición de un elemento denota su ubicación dentro de la lista.
Existen principalmente dos variantes parael concepto de posición:
Posición directa: la posición se denota medianteun puntero a la celda conteniendo el elemento deseado.
Posición indirecta: la posición se denota medianteun puntero a una celda conteniendo un punteroa la celda conteniendo el elemento deseado.
Organización de ComputadorasOrganización de Computadoras 4747
Posición directa vs. indirectaPosición directa vs. indirectaConsiderando el elemento elto, el puntero pd representa su posición directa y pi su posición indirecta:
- sig
- sig
elto sig
- sig
pi
pd
Organización de ComputadorasOrganización de Computadoras 4848
Inserción en listasInserción en listasVeamos cómo agregar una nueva celda *cel a continuación del elemento en la posición *pd (usando el concepto de posición directa):
struct celda *cel, *pd;
10 sig
25 sig
20 sig
30 sig
pd
cel
Organización de ComputadorasOrganización de Computadoras 4949
¿cómo hago paraacceder a esta celda?
Inserción en listasInserción en listasVeamos cómo agregar una nueva celda *cel antes que el elemento en la posición *pd (usando el concepto de posición directa):
struct celda *cel, *pd;
10 sig
15 sig
20 sig
30 sig
pd
cel
Organización de ComputadorasOrganización de Computadoras 5050
Inserción en listasInserción en listasPara agregar un elemento a una lista antesde otro hace falta poder acceder al elemento anterior en la lista.
Esto se puede resolver de varias formas, siempre pagando algún costo:
Recorriendo la lista desde el principio.
Haciendo uso de posición indirecta en vez de directa.
Implementando una lista doblemente enlazada.
¿Qué costo pago en cada caso?
Organización de ComputadorasOrganización de Computadoras 5151
Fases de la compilaciónFases de la compilaciónCompilación (traducción):
Preprocesado.
Generación de código assembler.
Generación de código objeto.
Enlazado o vinculación:
Enlazado con otros archivos objeto.
Enlazado con librerías estándar.
Generación del ejecutable.
Organización de ComputadorasOrganización de Computadoras 5252
.c
.exe
.c .s
.o
.c.c.o
preprocesadogeneración de
código assembler
compilación ensamblado
enlazado
librerías estándaresy del usuario
.a.a
Fases de la compilaciónFases de la compilación
Organización de ComputadorasOrganización de Computadoras 5353
Directrices al preprocesadorDirectrices al preprocesadorLas directrices al preprocesador son interpretadas antes de la compilación:
#define: define una nueva constante o macrodel preprocesador.
#include: indica que se debe incluir el contenidode otro archivo en el punto indicado.
#ifdef, #ifndef: señala el comienzo de un bloquede preprocesamiento condicionado.
#endif: marca el fin de un bloquede preprocesamiento condicionado.
Organización de ComputadorasOrganización de Computadoras 5454
Constantes y macrosConstantes y macrosEl preprocesador permite asociar valores constantes a ciertos identificadores que serán expandidos antes de la compilación propiamente dicha:
#define variable valor-cte
Análogamente, las macros son funciones que han de ser expandidas antes de la compilación:
#define macro(args, ...) def-función
Organización de ComputadorasOrganización de Computadoras 5555
Constantes y macrosConstantes y macros#define PI 3.14
#define CANT 5
#define AREA(rad) PI * rad * rad
#define MAX(a, b) (a > b ? a : b)
int main() {
int i; float vector[CANT];
for(i = 0; i < CANT; i++)
vector[i] = MAX(i * 5.2, AREA(i));
}
Organización de ComputadorasOrganización de Computadoras 5656
Archivos de encabezamientoArchivos de encabezamientoLos archivos de encabezamientos incluidos mediante la directiva #include suelen tenerla extensión “.h”.
Las funciones declaradas en los archivosde encabezamiento no incluyen sus implementaciones.
Las variables que allí aparezcan están declaradas como extern, ya que su definición ha de figurar en otro archivo fuente.
Organización de ComputadorasOrganización de Computadoras 5757
Preprocesado condicionalPreprocesado condicionalPara incluir código cuya compilación dependa de ciertas circunstancias, se puede hacer uso de las siguientes directivas:
#ifdef variable
<bloque de sentencias>
#endif
#ifndef variable
<bloque de sentencias>
#endif
Organización de ComputadorasOrganización de Computadoras 5858
Preprocesado condicionalPreprocesado condicional#define DEBUG
int main() {
int i, acc;
for (i = 0; i < 10; i++)
acc = i * i - 1;
#ifdef DEBUG
printf(“fin del bucle: %d”, acc);
#endif
}
Organización de ComputadorasOrganización de Computadoras 5959
Argumentos en líneaArgumentos en líneaEn C torna simple acceder a los argumentos suministrados en la línea de comandos:
void main(int argc, char *argv[]) {
int i;
printf(“%d argumentos”, argc);
for (i = 1; i < argc; i++) {
printf(“%d: %s\n”, i, argv[i]);
}
}
Organización de ComputadorasOrganización de Computadoras 6060
Argumentos en líneaArgumentos en líneaCabe acotar que la convención es que el primer argumento es el nombre del programa que se está ejecutando.
printf(“Invocado como: %s”,argv[0]);
Es decir, los argumentos en las las restantes posiciones (de 1 a argc-1), son los argumentos en la línea de comando efectivos.
Nótese que los argumentos se reciben como cadenas de caracteres, incluso al tratarse de números.
Organización de ComputadorasOrganización de Computadoras 6161
Gestión de archivosGestión de archivosC comparte la filosofía UNIX en el sentidode considerar prácticamente todo comosi fuera un archivo:
La lectura de información desde un archivo convencional no difiere del ingreso de datosa través del el teclado.
La escritura de información hacia un archivo convencional no difiere de la salida de datospor pantalla.
Organización de ComputadorasOrganización de Computadoras 6262
Funciones de libreríaFunciones de libreríaC cuenta con diversas funciones de librería sobre archivos:
FILE* fopen(char*, char*): apertura de un archivo para lectura o escritura.
int fclose(FILE*): cierre de un archivo.
int fprintf(FILE*, ...): escritura con formato.
int fscanf(FILE*, ...): lectura con formato.
int feof(FILE*): determina si se ha alcanzadoel final de un archivo.
Organización de ComputadorasOrganización de Computadoras 6363
Archivos especialesArchivos especialesTodo programa asocia tres constantes de tipo FILE* por defecto a los siguientes archivos:
stdin: entrada estándar (descriptor 0).
stdout: salida estándar (descriptor 1).
stderr: error estándar (descriptor 2).
Por caso:
fprintf(1, “Usuario: ”);
fscanf(0, “%s”, usuario);
fprintf(stderr, “Error: No válido”);
Organización de ComputadorasOrganización de Computadoras 6464
Conversión de tiposConversión de tiposC cuenta con diversas funciones de libreríapara asistir al programador en la conversión entre tipos de datos:
int atoi(char*): traduce de strings a enteros.
long atol(char*): traduce de strings a enteros largos.
double atof(char*): traduce de strings a reales.
char* itoa(char*, int): traduce de enterosa strings.
Organización de ComputadorasOrganización de Computadoras 6565
Código enrevezadoCódigo enrevezadoC, fiel a su filosofía minimalista, permite como válido código un tanto llamativo, a saber:
int a[5];
int b = 3;
a[1] = 101; // a[1] = 101
2[a] = 102; // a[2] = 102
a[b] = 103; // a[3] = 103
++b[a]; // a[3] = 104
(++b)[a] = 104; // a[4] = 104