Precedence dan associativity adalah dua aturan yang menentukan urutan evaluasi operator ketika beberapa operator muncul dalam satu ekspresi. Tanpa pemahaman kedua aturan ini, kita akan kesulitan memprediksi hasil 2 + 3 * 4, a & b + c, atau *p++ yang melibatkan banyak operator sekaligus. Pada Chapter ini kita akan membedah tabel precedence operator C dari yang tertinggi (postfix [] () . ->) hingga terendah (operator koma ,), serta associativity yang menentukan apakah evaluasi dilakukan kiri-ke-kanan atau kanan-ke-kiri ketika operator memiliki precedence yang sama. Kita juga akan melihat mengapa tanda kurung eksplisit tetap menjadi praktik terbaik walaupun aturan precedence sudah jelas.
Apa Itu Precedence dan Associativity #
Precedence menentukan operator mana yang lebih kuat “mengikat” operandnya. Pada ekspresi 2 + 3 * 4, perkalian * memiliki precedence lebih tinggi daripada +, sehingga 3 * 4 dievaluasi terlebih dahulu menjadi 12, baru ditambahkan ke 2 menghasilkan 14—bukan (2+3)*4 = 20.
Associativity menentukan urutan evaluasi ketika dua operator memiliki precedence yang sama dan berbagi operand. Sebagian besar operator biner di C bersifat left-associative (evaluasi kiri ke kanan), sehingga 10 - 3 - 2 berarti (10 - 3) - 2 = 5, bukan 10 - (3 - 2) = 9. Beberapa operator bersifat right-associative, antara lain operator assignment =, operator unary prefix, dan operator conditional ? :.
Tabel Ringkas Precedence #
Berikut ringkasan precedence dari tertinggi ke terendah, mengacu pada tabel resmi di cppreference.com/w/c/language/operator_precedence dan ISO/IEC 9899 §6.5:
| Tingkat | Operator | Asosiativitas |
|---|---|---|
| 1 | () [] . -> ++(postfix) --(postfix) | kiri-ke-kanan |
| 2 | ++(prefix) --(prefix) + - ! ~ *(deref) &(addr) sizeof | kanan-ke-kiri |
| 3 | * / % | kiri-ke-kanan |
| 4 | + - | kiri-ke-kanan |
| 5 | << >> | kiri-ke-kanan |
| 6 | < <= > >= | kiri-ke-kanan |
| 7 | == != | kiri-ke-kanan |
| 8 | & | kiri-ke-kanan |
| 9 | ^ | kiri-ke-kanan |
| 10 | | | kiri-ke-kanan |
| 11 | && | kiri-ke-kanan |
| 12 | || | kiri-ke-kanan |
| 13 | ?: | kanan-ke-kiri |
| 14 | = += -= *= /= %= &= |= ^= <<= >>= | kanan-ke-kiri |
| 15 | , | kiri-ke-kanan |
#include <stdio.h>
int main(void) {
int a = 10, b = 3, c = 4;
/* Operator aritmetika dasar */
int r1 = a + b * c; /* + setelah *: 10 + (3*4) = 22 */
int r2 = (a + b) * c; /* paksa urutan: (10+3)*4 = 52 */
int r3 = a - b - c; /* left-assoc: (10-3)-4 = 3 */
int r4 = a / b * c; /* left-assoc: (10/3)*4 = 12 */
int r5 = a % b + c; /* % lebih tinggi dari +: (10%3)+4 = 5 */
printf("a + b * c = %d\n", r1);
printf("(a + b) * c = %d\n", r2);
printf("a - b - c = %d\n", r3);
printf("a / b * c = %d\n", r4);
printf("a %% b + c = %d\n", r5);
return 0;
}Output yang Dihasilkan:
a + b * c = 22
(a + b) * c = 52
a - b - c = 3
a / b * c = 12
a % b + c = 5Mari kita telaah baris-baris kode di atas:
- Baris 6 (
a + b * c):*(tingkat 3) lebih tinggi dari+(tingkat 4), sehingga pengelompokan menjadia + (b * c) = 10 + 12 = 22. - Baris 8 (
a - b - c): operator-bersifat left-associative sehingga pengelompokan menjadi(a - b) - c = (10-3)-4 = 3; jika diasosiasikan ke kanan, hasilnya akan berbeda. - Baris 9 (
a / b * c):/dan*memiliki precedence sama (tingkat 3) dan bersifat left-associative, sehingga pengelompokan menjadi(a / b) * c = (10/3)*4 = 3*4 = 12. Pembagian10/3dilakukan dalam integer yang membuang pecahan menjadi3.
Precedence Lintas Kategori #
Perbedaan precedence antar kategori operator sering memunculkan kejutan. Operator unary ! dan operator bitwise & lebih rendah daripada yang dipikirkan banyak pemula. Akibatnya, a & b + c dikelompokkan sebagai a & (b + c), bukan (a & b) + c. Compiler modern mengeluarkan warning -Wparentheses untuk pola yang rawan salah baca ini.
#include <stdio.h>
int main(void) {
int a = 5, b = 3, c = 2;
/* Kombinasi aritmetika, relasional, logika */
int r1 = a + b > c * 2; /* (a+b) > (c*2) -> 8 > 4 -> 1 */
int r2 = a > b && b > c; /* (a>b) && (b>c) -> 1 && 1 -> 1 */
int r3 = a > b && b > c + 5; /* (a>b) && (b > c+5) -> 1 && (3>7) -> 0 */
int r4 = !a + b; /* (!a) + b -> 0 + 3 -> 3 (unary ! lebih tinggi dari +) */
int r5 = a & b + c; /* a & (b+c) -> 5 & 5 -> 5 (bitwise & lebih rendah dari +) */
printf("a + b > c * 2 = %d\n", r1);
printf("a > b && b > c = %d\n", r2);
printf("a > b && b > c + 5 = %d\n", r3);
printf("!a + b = %d\n", r4);
printf("a & b + c = %d\n", r5);
return 0;
}Output yang Dihasilkan:
a + b > c * 2 = 1
a > b && b > c = 1
a > b && b > c + 5 = 0
!a + b = 3
a & b + c = 5Pada baris ke-6, a + b > c * 2 menggabungkan empat tingkat precedence: * (tingkat 3) > + (tingkat 4) > > (tingkat 6) > && (tidak ada di sini). Pengelompokan menjadi (a + b) > (c * 2). Pada baris ke-10, !a + b menempatkan unary ! (tingkat 2) lebih tinggi dari + (tingkat 4) sehingga pengelompokan menjadi (!a) + b; karena a = 5 (non-zero), !a = 0, dan hasil akhir 0 + 3 = 3. Pada baris ke-11, operator bitwise & (tingkat 8) lebih rendah dari + (tingkat 4), sehingga pengelompokan menjadi a & (b + c) = 5 & 5 = 5. Pola a & b + c ini sengaja dipakai untuk memperlihatkan kejutan klasik.
Asosiativitas Kanan pada Assignment dan Ternary #
Operator assignment dan operator conditional bersifat right-associative. Sifat ini memungkinkan chained assignment a = b = c = 0 di-parse sebagai a = (b = (c = 0))—diisi dari kanan ke kiri. Demikian pula nested ternary cond1 ? a : cond2 ? b : c di-parse sebagai cond1 ? a : (cond2 ? b : c). Tanpa right-associativity, kita akan dipaksa menulis tanda kurung eksplisit.
#include <stdio.h>
int main(void) {
int arr[] = {10, 20, 30, 40, 50};
int *p = arr;
/* Postfix lebih tinggi dari prefix */
int r1 = *p++; /* *(p++): baca *p (=10), lalu p++ */
printf("*p++ -> r1=%d, *p sekarang=%d\n", r1, *p);
int r2 = *++p; /* *(++p): p dinaikkan dulu, lalu dereference */
printf("*++p -> r2=%d, *p sekarang=%d\n", r2, *p);
/* Right associativity of assignment */
int a, b, c2;
a = b = c2 = 100; /* a = (b = (c2 = 100)) */
printf("a=%d, b=%d, c=%d\n", a, b, c2);
/* Right associativity of ternary */
int n = 7;
const char *s = n > 5 ? "besar" : n > 3 ? "sedang" : "kecil";
printf("n=%d -> %s\n", n, s);
return 0;
}Output yang Dihasilkan:
*p++ -> r1=10, *p sekarang=20
*++p -> r2=30, *p sekarang=30
a=100, b=100, c=100
n=7 -> besarPada baris ke-9, *p++ di-parse sebagai *(p++) karena postfix ++ (tingkat 1) lebih tinggi dari unary prefix * (tingkat 2). Postfix p++ mengembalikan nilai lama p (alamat arr[0]) sebelum menaikkan p; nilai lama tersebut lalu didereference menjadi 10. Setelah baris ini, p menunjuk arr[1]. Pada baris ke-12, *++p di-parse sebagai *(++p); prefix ++ menaikkan p lebih dulu menjadi arr[2], baru dereference menghasilkan 30. Pada baris ke-18, nested ternary dievaluasi dari kanan: n > 5 ? "besar" : (n > 3 ? "sedang" : "kecil"); karena n = 7 > 5, cabang "besar" dipilih tanpa perlu mengevaluasi cabang kanan.
Dengan tabel precedence di tangan, kita siap melangkah ke topik berikutnya: konversi tipe yang dilakukan compiler secara otomatis saat operator bekerja pada operand bertipe berbeda.