在书写C++代码时,往往为了令代码更加简洁高效、提高代码可读性,会对定义的函数有一些特殊的要求:比如不传递不必要的参数,以此来让函数的参数列表尽可能简短。

  当一个函数需要访问一个数组元素时,出于上述原因,往往也希望令传入的参数尽可能的少(至少我是这样...)。

  首先,引出一个例子,对于std::vector<typename>来说,往往只需要传递一个参数就足够了(当只涉及单独访问该vector时的确如此),比如要编写一个show函数,这个函数的功能是打印传入容器的所有元素,并用空格将这些元素分隔开来。那么当传入容器为vector时,这个show函数就会简单无比:

 void show(const std::vector<int> &ivec) {
for (size_t i = ; i < ivec.size(); ++i)
std::cout << ivec[i] << " ";
std::cout << std::endl;
}

  可以看到,由于标准库中的vector包含size成员,使得我们很容易获取这个vector对象的大小,进而方便对这个vector进行访问。此时,这个函数只需要一个参数就可以完成容器的访问工作(当然,对于这种容器,也可以用迭代器进行访问,此方式下函数的参数个数不变)。

  那么... 内置数组呢?

  很遗憾,答案是:不行。

  在《C++ Primer 5th》中,作者指出:“因为数组是以指针的形式传递给函数的,所以一开始函数并不知道数组的确切尺寸,调用者应该为此提供一些额外的信息。”(中文版第193页,英文版第216页)  

  书中继而阐述了三种函数访问数组的方式(原文为:“管理指针形参的三种技术”):

  1. 使用标记指定数组长度

    通俗地说,就是在传入的这个数组中,含有一些标记数组结束的元素,比如C风格字符串(const char *),显式地以'\0'字符作为结尾标识符。那么当遍历到一个'\0'时,即可认定这个字符串结束,此时判定访问结束。但显而易见,这样的访问方式不具有普适性,毕竟很多容器并不会包含一个指定的结尾标识,甚至一些容器都不能保证存入元素的顺序固定。

    这里,当然也可以预定数组就是指定大小的(例如在源文件中声明const LEN = 20; 或者预处理 #define LEN 20),尽管这样和上述方法一样,可以使得函数只需要一个形参:

#define LEN 20
// const int LEN = 20; void show(const char *cp) {
if (cp)
while (*cp)
std::cout << *cp++;
} void show(const int lst[]) {
for (size_t i = ; i < LEN; ++i)
std::cout << lst[i] << " ";
std::cout << std::endl;
}

    但是,这样的程序是不具有普遍性的,我们可以很肯定地说,我们的数组一定不会正好包含20或更少的元素。

  2. 使用标准库规范

    在标准库中,对数组定义了begin和end方法,和迭代器不同的是,函数返回的是指针类型;和迭代器相同的是,用法基本一致...

void show(const int *beg, const int *end) {
for (auto iter = beg; iter != end; ++iter)
std::cout << *iter << std::endl;
std::cout << std::endl;
} /* 很遗憾,玩儿不转
void show(const int lst[]) {
for (auto beg = std::begin(lst); beg != std::end(lst); ++beg)
std::cout << *beg << " ";
std::cout << std::endl;
}
*/

    可以看到,这时的show函数,至少需要两个参数。当然,说到begin和end函数,很多人都会想到上面第二个这种方式,然而很遗憾,这种方式不行!此时,只能通过显式地调用

show(std::begin(lst), std::end(lst));

    来实现第一个函数的调用。但... 这并未达到“调用函数只包含一个参数”的期望。

    至于这里的第二种方式不行的原因,我认为是:在C++调用函数时,数组被自动转换为数组元素类型的指针类型(比如int lst[]就被转换成了int *lst),这个过程是如此自然,就像是内置类型默认类型转换一般(比如sum = 1 + 2.0,这里的1就自然从int类型被转换成了double类型)。因此,函数对这样一个int *类型,自然无从对之继续调用begin和end函数以得到其首尾位置(这里重点是无法得到尾位置)。故... 玩儿不转。

  3. 显式传递一个表示数组大小的形参

    这可能是每个学过C/C++的人最常用的方法了。通过显式控制访问元素的个数,保证指针前进的深度:

void show(const int lst[], size_t length) {
for (size_t i = ; i < length; ++i)
std::cout << lst[i] << " ";
std::cout << std::endl;
}

    形式很简单,无须解释。这里需要注意的就是形参处的length的类型是size_t的,因此调用者传入一个负值,很有可能就美滋滋了。当然,也可以修改为其他类型,但这个问题不容忽视(见之前博客)。

    我想说的是,这种方式下,依然没达到“调用函数只包含一个参数”的期望...

    可能只有强迫症才会有这样的纠结吧,但很欣慰的是,通过这个纠结,将调用函数访问数组的三种方式系统地进行了说明阐述(方式2的第二种形式真的让我很惋惜...)。

    当然,如果读者有更好的方式(如果能只传入一个参数即可访问任意类型任意大小的数组,看在我纠结这么久的份儿上,千万要告诉我...),欢迎评论区分享,若有写的不足的地方,敬请评论区斧正,在此感谢。

最新文章

  1. Windows7SP1补丁包(Win7补丁汇总) 32位/64位版 更新截至2016年11月
  2. ZOJ 3812 We Need Medicine(dp、背包、状态压缩、路径记录)
  3. HTML其他基本格式说明
  4. LVS-HA
  5. 正则Match
  6. inflate方法与findViewById的区别
  7. poj1006
  8. delete、update忘加where条件误操作恢复过程演示
  9. [转] tomcat组成及工作原理
  10. 浅谈href=#与href=javascript:void(0)的区别
  11. mx51 IPU 透明处理
  12. Quartz学习——Quartz大致介绍(一)
  13. 无限分级Repeater递归实现:读取一次数据库,使用LINQ2SQL技术,支持排序&amp;amp;显示隐藏
  14. bzoj 3528: [Zjoi2014]星系调查
  15. 安卓自定义日期控件(仿QQ,IOS7)
  16. catkin-make: command not found 错误解决
  17. SpringBoot数据库集成-Mybatis
  18. POJ 3264 Balanced Lineup(模板题)【RMQ】
  19. JPA中建立数据库表和实体间映射小结
  20. NET Core小细节杂记

热门文章

  1. npm报错This is probably not a problem with npm. There is likely additional logging
  2. 安卓基础(AndroidViewModel)
  3. 关于package.json和package-lock.json的区别
  4. 高次arccos积分
  5. dfs(迷宫)
  6. 【Python下进程同步之互斥锁、信号量、事件机制】
  7. scrapy import CrawlSpider 报错
  8. 常用工具api等链接
  9. 创业学习--《预判行业机会》--B-2.预判模块---HHR计划--以太一堂
  10. Catalyst 3850 升级-1