Skip to main content

[探索 5 分鐘] i++ 與 ++i 的功能與性能是否相同 ? 用程式範例介紹 (c#)

值相同, 但我們知道這兩個 Foo 操作是不同的
Foo( x++ ); // post-increment
Foo( ++x ); // pre-increment
大家比較常用的 i++, x++... 我們稱為 post-increment, 是在這一行程式完成之後, 再進行 +1 的動作, 反之稱之為 pre-increment。所以其實上面例子可以理解為如下 (功能上, 非編譯器行為), 更囉嗦的版本。
// Foo ( x++ )
Foo( x ); 
x = x + 1; 

// Foo ( ++x )
x = x + 1; 
Foo( x ); 
wiki 的例子更清楚, 請特別注意 y 的值。
int  x, y;
// Increment operators
x = 1;
y = ++x;    // x is now 2, y is also 2
y = x++;    // x is now 3, y is 2
這樣看完應該更無懸念了, post-increment 不是等號左邊跑完進行 ++, 也不是等號右邊跑完進行 ++, 就是這一行整個跑完才進行 ++。
// Copy one array to another
void copy_array(float *src, float *dst, int n)
{
    while (n-- > 0)        // Loop that counts down from n to zero
        *dst++ = *src++;   // Copies element *(src) to *(dst),
                           //  then increments both pointers
}
以上是說明功能上的不同; 在場景上, 陣列或指標的操作很適合 post-increment, 有「同一行, 同時完成操作並與結束往下一個位置移動, 的簡潔代碼」設計 。性能呢 ? Foo( ++x ) 跟 Foo ( x++ ) 各跑 1 千萬次, 結果是
Foo( ++x ) 這個 pre-increment 呼叫版本性能穩定領先, 非常微小的領先。

附帶一提, 就單一行性能來比較 ++x 與 x++,  性能上沒有顯著差異。跑 1000 萬次還看不太出來, 要到 1 億次才互有領先。

所以看來團隊內統一寫法即可。

迴圈操作

針對迴圈內的操作, 這兩個究竟有沒有差別 ?
for (int i = 0; i < 5; i++)
for (int i = 0; i < 5; ++i)
結論就是: 沒有差別。 迴圈內都是拿 i 累進的值來操作。也就是 i 依序是
0, 1, 2, 3, 4
在很多語言, 退出迴圈之後還可以拿到區域變數 i, 若拿的到並列印出來, 就會是
5

為了確定大家真的了解, 請試著回答以下迴圈, 每次的 ( n, i ) 值為何 ?
// Test 1 - (n, i) pair of i++ version :
int n = -1;
for (int i = 0; i < 5; n = i++) {
    Console.WriteLine(Tuple.Create<int, int>(n, i) + ",");
}
// Test 2 - (n, i) pair of ++i version :
int n = -1;
for (int i = 0; i < 5; n = ++i) {
    Console.WriteLine(Tuple.Create<int, int>(n, i)) + ",";
}
以第一個 Test 1 版本來說, 如果
  • 猜 (1, 1), (2, 2), (3, 3), (4, 4), (5, 5) ... 的, 請到各大線上的基礎程式課程, 用力再複習一次
  • 猜 (0, 0), (1, 1), (2, 2), (3, 3), (4, 4), (5, 5) ... 的, 不錯, 這個接近 Test 2 版本的答案
  • 不猜 ? 

來看答案, 是否跟你想的一樣呢 ?

再用 JavaScript 版本來確認, 是一致的結果,

複習一下好了, The Java™ Tutorials 針對 Loop 的介紹
for (initialization; termination;
     increment) {
    statement(s)
}
When using this version of the for statement, keep in mind that:
  • The initialization expression initializes the loop; it's executed once, as the loop begins.
  • When the termination expression evaluates to false, the loop terminates.
  • The increment expression is invoked after each iteration through the loop; it is perfectly acceptable for this expression to increment or decrement a value.
For 三段的撰寫格式中,第一段 initialization 只會於一開始被執行一次; 第二段 termination 是判斷跳脫迴圈的判斷式, 每次都會判斷 (每次一開始); 第三段 increment 則會於每次迴圈執行結束後執行 (每次的結尾)。這也解釋了為何一開始 n 是初值 -1 進入迴圈, 而不是 n = ++i 或 n = i++ 的結果進入迴圈。

注意 n = i++ 的版本, n 值在第三段 increment 拿到的是尚未 i + 1 的版本 !

運算子多載 

有個特例。有些語言如 C++,  具有運算子多載 (operator overloading) 的編程設計。你可以把 ++ 這個運算子用別的操作內容來取代 (你高興的話把 ++ 改為 -- )也可以, 尤其類別物件本來就是要交代 + 或 ++ 在這個類別究竟代表什麼 (如果需要)。

假設有個 2 維矩陣類別 Matrix2d, 想要進行矩陣 A++ 或 ++A (矩陣內每個元素值 +1)
A =
    x1    y1
    x2    y2

A++ =
    x1+1    y1+1
    x1+1    y1+1

++A =
    x1+1    y1+1
    x1+1    y1+1

C++ 可能這麼設計
Matrix2d& Matrix2d::operator++()  // ++A
{
    this.x1 ++;
    this.x2 ++;
    this.y1 ++;
    this.y2 ++;
    return *this;
}

Matrix2d Matrix2d::operator++(Matrix2d& value) // A++
{
    Matrix2d temp(value);
    temp.x1 ++;
    temp.x2 ++;
    temp.y1 ++;
    temp.y2 ++;
    return temp;
}
如此一來, 先 ++ 跟後 ++, 就會導致進入不同實作, A++ 產生了無法被編譯器忽略的 temp 變數, 會有成本速度較慢 , 呼叫越多次則兩者性能會越拉越開。

參考資料

  • https://en.wikipedia.org/wiki/Increment_and_decrement_operators
  • https://en.wikipedia.org/wiki/Operator_overloading
  • https://docs.oracle.com/javase/tutorial/java/nutsandbolts/for.html
  • https://stackoverflow.com/questions/467322/is-there-any-performance-difference-between-i-and-i-in-c
  • https://stackoverflow.com/questions/24886/is-there-a-performance-difference-between-i-and-i-in-c

Comments