最近挺忙的,可不知道爲啥,還是願意把正事丟在一邊,琢磨着爲自己找點樂子。前兩天由於看到Java版一個帖子,竟然越想越好玩,於是又拾起了偶初學BASIC語言時就寫得爛熟的一道題目:打印自然數1到10。如果用C++語言來寫,會有多少種寫法呢?<?xml:namespace prefix = o ns = "urn:schemas-microsoft-com:office:office" />
(1)直接循環
大多數人的第一反應應該是這樣的吧:
#include <iostream>
using namespace std;
int main() {
for(int i = 1; i <= 10; ++i) {
cout << i << endl;
}
}
這是最“直接的”想法。
(2)直接循環的STL“擺酷”版
XX級的STL玩家常常像強迫症一樣迴避着手寫循環。OK,也沒啥不好:
#include <iostream>
#include <iterator>
#include <algorithm>
using namespace std;
int main() {
int a[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
copy(a, a + sizeof(a) / sizeof(*a), ostream_iterator<int>(cout, "/n"));
}
好了,爲了節省一點空間,後面的代碼中將不再包含相應的頭文件和using指示。無非就是“#include <iostream>”和“using namespace std;”而已。
(3)硬編碼
對於思維比較調皮的人來說,對此題目的第一反應可能不是上面的第一種,而是這種:
int main() {
cout << 1 << endl;
cout << 2 << endl;
cout << 3 << endl;
cout << 4 << endl;
cout << 5 << endl;
cout << 6 << endl;
cout << 7 << endl;
cout << 8 << endl;
cout << 9 << endl;
cout << 9 << endl;
cout << 10 << endl;
}
答案符合要求,完全沒有問題。而且它很可能是這裏的所有的方法中效率最高的一個(沒有實測,只是猜測),當然,我們可以把前面9條語句中的“endl”改成一般的'/n',從而使效率更高一些(沒有實測,也是猜測)。
(4)方便拷貝的硬編碼
上面的代碼看上去長一點,實際上很容易編寫,只需要寫出第一條輸出語句,然後Ctrl-C/Ctrl-V……,再把每一行上的數字改一改就行了。不過我們也可以不改數字的,只是需引入一個變量:
int main() {
int i = 0;
cout << ++i << endl;
cout << ++i << endl;
cout << ++i << endl;
cout << ++i << endl;
cout << ++i << endl;
cout << ++i << endl;
cout << ++i << endl;
cout << ++i << endl;
cout << ++i << endl;
cout << ++i << endl;
}
這樣只管Ctrl-C/Ctrl-V……即可,V完之後一行代碼都不用改。
說到這裏,還想起曾在BOOST的基數排序實現中,看到用手工展開的多條(256條)賦值語句取代循環,來爲一個數組中的各元素賦值。我想,他們這麼做的原因是想通過減少目標代碼中的跳轉指令來提高處理器的流水線性能,畢竟使用基數排序的人肯定都是爲了獲得高性能,否則還不如直接用快速排序甚至冒泡排序排一下了事。不過那些代碼在我機器上測下來,性能提升並不明顯。
(5)進一步方便拷貝
拷貝9行語句,不如拷貝10對括號更簡單呢。(不同意?那算了,偶不強求,畢竟無論拷貝多少字符都不用咱們費勁)。只是想介紹一下這種實現,主函數裏我們只需要拷貝10對括號:
struct A {
static int i;
const A& operator()() const {
cout << ++i << endl;
return *this;
}
};
int A::i;
int main() {
A()()()()()()()()()()();
}
(6)遞歸
循環和遞歸本就是一對兄弟,既然可以用循環解決,那也應該可以用遞歸解決。這個程序太簡單了,簡單到我們不想單獨寫個遞歸函數供主函數調用,幹粹直接遞歸主函數得了。正好C++標準支持main函數擁有一個整型參數,就用它了:
int main(int argc, char* argv[]) {
cout << argc << endl;
if(argc < 10) {
main(argc + 1, 0);
}
}
就這麼簡單一小程序,寫一寫,調一調,過了就行了。您別擡槓,只要您別擡槓,那麼當main作爲程序入口第一次被調用時,接受到的argc參數很可能就是1,於是正符合我們的需要。
(7)不傳參數的遞歸
還是覺得(6)不地道?也是,argc和argv都是有特殊含義和特殊用途的,還是不要亂來的好。要不咱還是單獨寫個遞歸函數,比如叫f,然後從主函數中調用這個f,那樣看起來肯定是賢惠了許多。不過,即使用遞歸,咱們也可以不傳參數:
int main() {
static int i;
cout << ++i << endl;
if(i < 10) {
main();
}
}
(8)對象數組
那天網友光遠告訴我他想到了另外一種方法,確實挺妙的,先贊一個。
struct A {
static int i;
A() { cout << ++i << endl; }
};
int A::i;
int main() {
A a[10];
}
這裏利用的是C++構造對象數組的相關機制。
當然,我們也可以把輸出語句放在析構函數中,在輸出較果上不會有任何區別。
如果您喜歡把數組分配在堆上,那隻需要把主函數中那條語句改成:
new A[10];
只是動態申請的資源用完了最好釋放掉,不管怎樣,這至少是個好習慣。只有從小養成誰申請,誰釋放的好習慣,長大了才能建設四個現代化,不是麼?
delete[] new A[10];
而且,假如我們想利用的是析構函數而不是構造函數,那就必需加delete[]。
(9)使用模板
也別光想着運行時的遞歸方式,我們還可以考慮一下“編譯時的遞歸”,C++支持模板,模板的實例化是在編譯時進行的(或者有時編譯連接交叉進行,但那只是編譯器的技術細節,不影響我們討論的問題實質),因此可以用於構造“編譯時的遞歸過程”,實質上就是讓編譯器幫我們自動生成一組符合特定結構的代碼,然後這些自動生成出來的代碼被編譯進可執行文件中,在運行時輸出我們想要的結果。用模板解決這個題目肯定有多種方法,下面是我所想到的第一種:
template<int i>
struct A : public A<i + 1> {
~A() { cout << i << endl; }
};
template<>
struct A<11> {};
int main() {
A<0>();
}
爲啥要使用析構函數來輸出?沒啥,就是因爲構造函數在前面已經用過一次了,換換口味而已。不過,這一次使用構造和析構的輸出效果是不一樣的。所以,如果要利用構造函數,所需的改動不止一處。
根據C++構造對象的機制,我們也可以不通過繼承的方式,而是通過定義成員變量。只需把上面的第一個模板改爲:
template<int i>
struct A {
A<i + 1> a;
~A() { cout << i << endl; }
};
(10)還是模板
下面這個是我想到的另一種使用模板的實現方式,它可能是這10種方式中最妖冶的一個了,應該只有色狼纔會喜歡。:)
template<int i>
struct A {
A<i + 1> operator->() {
cout << i << endl;
return A<i + 1>();
}
};
template<>
struct A<11> {
void dummy() {}
A<11>* operator->() {
return this;
}
};
int main() {
A<1>()->dummy();
}
上面這些實現中,有好多隻能固定打印1到10。只有那些利用循環、運行時遞歸以及其它運行時特性的(比如動態申請的數組),才能真正做到“根據用戶輸入的N來打印出自然數1到N”。