我正在寻找一个简单的例子来说明如何转换这个基本的嵌套
循环到递归函数
'简单'的例子,当然。 (为什么不呢?)
我认为之前提交的所有 3 个贡献都可以。我相信我已经在我的系统上编译并运行了它们中的每一个。
向 rcgldr 致敬 - 该提交文件在实现尾递归方面脱颖而出。 (我没有在其他人身上确认这个问题。)
幻数:
for 循环有 3 个“参数”:开始、停止和步幅
for (int v = start; v < stop; v += stride)
因为 for 循环具有严格的布局,所以可以容忍幻数,并且
也许是想要的。恕我直言,您可以使用 2 个幻数(0 和 -1)。
在具有更灵活布局(其他循环类型、递归等)的代码中,不鼓励使用这种幻数。
可测试性:
C 风格与 C++ 风格
- 到目前为止,只提交了 C 风格的函数。
- 如果您的 C++ 代码没有类,何必呢?
我的环境
Ubuntu 15.10,g++-5,GNU Emacs 26,性能测量使用 -O3
(调试使用-O0)
-
我的构建选项:
g++-5 -m64 -O3 -ggdb -std=c++14 -Wall -Wextra -Wshadow -Wnon-virtual-dtor
-pedantic -Wcast-align -Wcast-qual -Wconversion -Wpointer-arith
--Wunused Woverloaded-virtual
-O3 dumy542.cc -o dumy542
代码文件名“dumy542.cc”
总结
感兴趣的演示
-
任何第三个 cmd-line-param(即 'm')的存在都会启用文本输出
./dumy542 3 15 m - 运行 3 x 15,报告持续时间,
和3行文本输出
./dumy542 3 60 m - 适合我的全屏
注意 - 尝试使用“大”输出启用测试输出将触发断言,以方便您恢复。 (您可以删除它)
第一部分代码介绍了迭代代码和 2 个尾递归代码示例。所有都基于提交的代码,我已经重构了
使用 ssDiag(支持抑制文本输出)和更“水平”的显示技术。
注意 - 已添加断言(在这些示例中)以防止编译器优化代码。
测试包装(使用 C++ 计时测量)如下。
#include <chrono>
// 'compressed' chrono access --------------vvvvvvv
typedef std::chrono::high_resolution_clock HRClk_t; // std-chrono-hi-res-clk
typedef HRClk_t::time_point Time_t; // std-chrono-hi-res-clk-time-point
typedef std::chrono::nanoseconds NS_t; // std-chrono-nanoseconds
using namespace std::chrono_literals; // support suffixes like 100ms, 2s, 30us
#include <iostream>
#include <iomanip>
#include <sstream>
#include <string>
#include <cassert>
uint64_t Kount = 1; // diagnostic only
// C style C++ function by MGT
// refactored (by DMoen) to use ssDiag and 'horizontal' display
void iterate_MGT(int x, int y, std::stringstream* ssDiag )
{
for (int i = x; i > 0; i--) // magic numbers xStop (0) & xStride (-1)
{
if(nullptr != ssDiag) { (*ssDiag) << "\n i=" << i << ": "; }
else { assert(++Kount); } // with side-effects
for (int j = y; j > 0; j--) // magic numbers yStop (0) & yStride (-1)
{
if(nullptr != ssDiag) { (*ssDiag) << j << " "; }
else { assert(++Kount); } // with side-effects
}
}
}
// C style C++ function(s) by 'rcgldr'
// refactored (by DMoen) to use ssDiag and 'horizontal' display
void recurseY(int x, int y, std::stringstream* ssDiag)
{
if(y <= 0) return; // magic number yStop (0)
if(nullptr != ssDiag) { (*ssDiag) << y << " " ; }
else { assert(++Kount); } // with side-effects
recurseY(x, y-1, ssDiag); // magic number y-stride (-1)
}
void recurseX(int x, int y, std::stringstream* ssDiag)
{
if(x <= 0) return; // magic number xStop (0)
if(nullptr != ssDiag) { (*ssDiag) << "\n i=" << x << ": " ; }
else { assert(++Kount); } // with side-effects
recurseY (x, y, ssDiag);
recurseX (x-1, y, ssDiag); // magic number x-stride (-1)
}
// C++ class - created from tail recursive code (submitted by 'rcgldr')
// by DMoen to use ssDiag and 'horizontal' display
class Recursive_t
{
public:
static void recurse (int x, int y, std::stringstream* ssDiag)
{
Recursive_t rcgldr(x, y, ssDiag); // ctor
rcgldr.recurseX(); // go
} // dtor on exit
private:
int m_x;
int m_y;
int m_yStart;
std::stringstream* m_ssDiag;
enum Constraints : int // no need for magic numbers
{
xStop = 0, xStride = -1,
yStop = 0, yStride = -1,
ConstraintsEnd
};
Recursive_t () = delete; // disallow default ctor
Recursive_t (int xStart, int yStart, std::stringstream* ssDiag)
: m_x (xStart)
, m_y (yStart)
, m_yStart (yStart)
, m_ssDiag (ssDiag)
{
}
void recurseX() // outer loop
{
if(m_x <= xStop) return;
if(nullptr != m_ssDiag) { (*m_ssDiag) << "\n i=" << m_x << ": " ; }
else { assert(++Kount); } // with side-effects
recurseY();
m_x += xStride;
recurseX();
}
void recurseY() // inner loop
{
if(m_y <= yStop) { m_y = m_yStart; return; }
if(nullptr != m_ssDiag) { (*m_ssDiag) << m_y << " " ; }
else { assert(++Kount); } // with side-effects
m_y += yStride;
recurseY();
}
}; // class Recursive_t
测试代码和main:
class T542_t
{
int m_xStart;
int m_yStart;
std::string m_rslt;
std::stringstream* m_ssRslt;
std::string m_diag;
std::stringstream* m_ssDiag;
const uint m_capacity;
public:
T542_t()
: m_xStart(0)
, m_yStart(0)
//
// m_rslt - default std::string ctor ok
, m_ssRslt (nullptr)
// m_diag - default std::string ctor ok
, m_ssDiag (nullptr)
, m_capacity (1000)
{
m_rslt.reserve(m_capacity);
m_ssRslt = new std::stringstream(m_rslt); // always
//
m_diag.reserve(m_capacity);
// m_ssDiag only when needed
resetTest();
m_rslt.clear();
assert(m_rslt.capacity() == m_capacity);
assert(m_rslt.empty());
assert(m_rslt.size() == 0);
}
~T542_t() = default;
int exec (int argc, char* argv[])
{
switch (argc)
{
case 2: // one parameter
{
m_xStart = std::stoi(std::string(argv[1]));
m_yStart = m_xStart;
return (test("\n one command line parameter 'N': duration rslts only")); // NOTE 1
}
break;
case 3: // two parameters
{
m_xStart = std::stoi(std::string(argv[1]));
m_yStart = std::stoi(std::string(argv[2]));
return (test("\n two command line parameters, X & Y: duration rslts only")); // NOTE 1
} break;
case 4: // three parameters
{
m_xStart = std::stoi(std::string(argv[1]));
m_yStart = std::stoi(std::string(argv[2]));
m_diag.clear();
assert(m_diag.capacity() == m_capacity);
m_ssDiag = new std::stringstream(m_diag);
return (test("\n three command line parameters, X & Y: duration + intermediate results")); // NOTE 1
} break;
case 1:
default :
{
std::cerr
<< "\n Usage: "
<< " argc (" << (argc-1) << ") not expected, use 1, 2, or 3 cmd line parameters.\n"
//
<< "\n 2 (one parameter) : test with NxN, show duration only\n"
<< "\n 3 (two parameters) : test with MxN, show duration only\n"
//
<< "\n 5 (three parameters) : test with MxN, show intermediate results\n"
//
<< "\n other : Usage\n"
<< std::endl;
return 0;
}
} // switch
assert(m_xStart);
assert(m_yStart);
} // exec (int argc, char* argv[])
private:
int test(std::string note)
{
int64_t a_lim = m_xStart * m_yStart;
assert(a_lim > 0); // wrap around possible: example: 3 x 1234567890
std::cout << note << ", Using " << m_xStart << " & " << m_yStart
<< " (" << digiComma(std::to_string(a_lim))
<< ")\n" << std::endl;
if(nullptr != m_ssDiag) { // when displaying output, make sure
assert (m_yStart <= 80); // short line and small total
assert((m_xStart * m_yStart) <= 2000);
}
if(true) // for consistent start up - ubuntu app 'load' delay? cache flush?
{
assert(nullptr != m_ssRslt);
(*m_ssRslt) << "\n \n ::xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx ";
testRecurse2 (); testRecurse1 (); testIterate ();
resetTest(); // discard results
}
testIterate (); showTest(); resetTest();
testRecurse1 (); showTest(); resetTest();
testRecurse2 (); showTest(); resetTest();
return 0;
} // int test()
void showTest()
{
std::cout << m_ssRslt->str() << std::endl;
if(nullptr != m_ssDiag)
std::cout << m_ssDiag->str() << std::endl;
}
void testIterate() // C style C++ function, submitted by MGT, iterative function
{
assert(nullptr != m_ssRslt);
(*m_ssRslt) << "\n (C style C++ function)\n ::iterate_MGT (int, int, ss&) ";
Time_t start = HRClk_t::now();
::iterate_MGT (m_xStart, m_yStart, m_ssDiag); // invoke the C++ function
//^^ --- file scope
auto duration_ns = std::chrono::duration_cast<NS_t>(HRClk_t::now() - start);
(*m_ssRslt) << " duration: " << std::setw(10)
<< digiComma(std::to_string(duration_ns.count())) << " ns ";
} // testIterate()
void testRecurse1 () // C style C++ function, submitted by rcgldr, tail-recursive
{
assert(nullptr != m_ssRslt);
(*m_ssRslt) << "\n (C style C++ function)\n ::recurseX (int, int, ss&) ";
Time_t start = HRClk_t::now();
::recurseX(m_xStart, m_yStart, m_ssDiag);
//^^ --- file scope
auto duration_ns = std::chrono::duration_cast<NS_t>(HRClk_t::now() - start);
(*m_ssRslt) << " duration: " << std::setw(10)
<< digiComma(std::to_string(duration_ns.count())) << " ns ";
} // void testRecurse1 ()
void testRecurse2 () // C++ class, tail-recursive, method
{
assert(nullptr != m_ssRslt);
(*m_ssRslt) << "\n (C++ class method)\n Recursive_t::recurse (int, int, ss&) ";
Time_t start = HRClk_t::now();
Recursive_t::recurse(m_xStart, m_yStart, m_ssDiag);
//^^^^^^^^^ class scope
auto duration_ns = std::chrono::duration_cast<NS_t>(HRClk_t::now() - start);
(*m_ssRslt) << " duration: " << std::setw(10)
<< digiComma(std::to_string(duration_ns.count())) << " ns ";
} // void testRecurse2 ()
void resetTest() { resetRslt(); resetDiag(); Kount = 1; }
void resetRslt() { m_rslt.clear(); // clear bufer
if (nullptr != m_ssRslt) { m_ssRslt->str(m_rslt); m_ssRslt->clear(); } }
void resetDiag() { m_diag.clear(); // clear buffer
if (nullptr != m_ssDiag) { m_ssDiag->str(m_diag); m_ssDiag->clear(); } }
// convenience - insert comma's into big numbers for readability
std::string digiComma(std::string s)
{ //vvvvv--sSize must be signed int of sufficient size
int32_t sSize = static_cast<int32_t>(s.size());
if (sSize > 3)
for (int32_t indx = (sSize - 3); indx > 0; indx -= 3)
s.insert(static_cast<size_t>(indx), 1, ',');
return(s);
}
}; // class T542_t
int main(int argc, char* argv[])
{
T542_t t542;
return t542.exec(argc, argv);
}
示例结果
-
命令行:./dumy542 3 15 m
三个命令行参数,X&Y:持续时间+中间结果,使用3&15(45)
(C 风格 C++ 函数)
::iterate_MGT (int, int, ss&) 持续时间:6,319 ns
i=3:15 14 13 12 11 10 9 8 7 6 5 4 3 2 1
我=2:15 14 13 12 11 10 9 8 7 6 5 4 3 2 1
我=1:15 14 13 12 11 10 9 8 7 6 5 4 3 2 1
(C 风格 C++ 函数)
::recurseX (int, int, ss&) 持续时间:6,639 ns
i=3:15 14 13 12 11 10 9 8 7 6 5 4 3 2 1
我=2:15 14 13 12 11 10 9 8 7 6 5 4 3 2 1
我=1:15 14 13 12 11 10 9 8 7 6 5 4 3 2 1
(C++ 类方法)
Recursive_t::recurse (int, int, ss&) 持续时间:6,480 ns
i=3:15 14 13 12 11 10 9 8 7 6 5 4 3 2 1
我=2:15 14 13 12 11 10 9 8 7 6 5 4 3 2 1
我=1:15 14 13 12 11 10 9 8 7 6 5 4 3 2 1
-
命令行:./dumy542 3 12345678
两个命令行参数,X & Y:duration rslts only,使用 3 & 12345678 (37,037,034)
(C 风格 C++ 函数)
::iterate_MGT (int, int, ss&) 持续时间:46,369,155 ns
(C 风格 C++ 函数)
::recurseX (int, int, ss) 持续时间:44,099,411 ns
(C++ 类方法)
Recursive_t::recurse (int, int, ss&) 持续时间:42,898,124 ns
时长说明
- 多次运行有不同的结果,通常会改变哪个函数出现得更快。这个特定的结果表明两种递归都比迭代快。