最近挺忙的,可不知道为啥,还是愿意把正事丢在一边,琢磨着为自己找点乐子。前两天由于看到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”。