1. 程式人生 > 其它 >C++變數和STL容器的交換

C++變數和STL容器的交換

變數交換

現在有兩個int型變數xy,需要將xy的值進行交換

臨時變數版

這樣顯然是不行的:

x=y;
y=x;

因為當x被賦值為y,那麼xy所表示的均為原來y的值,原來x表示的資料將丟失

不難想到使用一個臨時變數tmp暫存其中一方的資料:

int tmp=x;
x=y;
y=tmp;

算術運算版

之前的方法需要使用額外的變數來實現,那麼有沒有不使用額外變數的方法呢?當然是有的:

x=x+y;
y=x-y;
x=x-y;

語句非常對稱也非常精妙,第一句將x賦值為xy的和,這樣原來的x仍然可以通過x-y表示出來,而交換後的y就是原來的x,所以第二句賦值語句就將y賦值成了原來的x,此時x

仍然表示原來兩個變數的和,所以交換後的x也即原來的y的值就是x-y

位運算版

x^=y;
y^=x;
x^=y;

這個可以說是 xor 藝術了,原理與上一種類似,但是由於是位運算,運算速度要優於前面一種

彙編版

__asm
{
    mov eax,[x]
    xchg eax,[y]
    mov [x],eax
}

同樣也是三行,不過之前的如果編譯成組合語言肯定不止三行,所以這個版本在效能和佔用空間上碾壓前三者

不過應該不會有人這麼用的,僅供觀賞,要慎用

函式版

C++中提供了用於變數交換的swap函式,只需要把兩個變數傳入即可:

swap(x,y)

swap函式的內部實現:

template <class _Ty>
_NODISCARD constexpr remove_reference_t<_Ty>&& move(_Ty&& _Arg) noexcept { // forward _Arg as movable
    return static_cast<remove_reference_t<_Ty>&&>(_Arg);
}

#if _HAS_CXX17
template <class _Ty, enable_if_t<is_move_constructible_v<_Ty> && is_move_assignable_v<_Ty>, int> _Enabled>
#else // ^^^ _HAS_CXX17 / !_HAS_CXX17 vvv
template <class _Ty, int _Enabled>
#endif // _HAS_CXX17
_CONSTEXPR20 void swap(_Ty& _Left, _Ty& _Right) noexcept(
    is_nothrow_move_constructible_v<_Ty>&& is_nothrow_move_assignable_v<_Ty>) {
    _Ty _Tmp = _STD move(_Left);
    _Left    = _STD move(_Right);
    _Right   = _STD move(_Tmp);
}

可以發現其內部機理與第一種方法類似,只是用了泛型並且增加了一些修飾

STL容器交換

兩個物件之間的交換,我們就沒必要考慮時間複雜度的常數問題,而應該考慮與資料量的相關性

首先先考慮兩個普通的int陣列x[1048576]y[1048576]的交換,樸素的做法是對其中的每一個元素做一次交換:

for(int i=0;i<1048576;i++) swap(x[i],y[i]);

swap函式也提供了兩個陣列交換的介面,與上面的實現過程一致

swap(x,y)

這樣的時間複雜度是 \(O(n)\) ,換個角度思考,兩個陣列事實上只是兩塊記憶體中的連續單元,不像同一個陣列中兩個元素進行交換會影響結果,它們交換與否絲毫不影響演算法的正確性,內容的交換等效於對它們的變數名進行交換,但是C++又不支援動態更改變數名,所以我們需要使用指標或引用,交換時也僅需交換指標或引用:

int *p1=x,*p2=y;
swap(p1,p2);

正是基於這個思想,stl的許多容器進行swap操作的時間複雜度都是 \(O(1)\),包括:vectorlistmapsetdeque
priority_queuequeuestack 在開C++11時為 \(O(1)\),否則為 \(O(n)\)

參考資料