数组与指针的区别,以及在STL中传递数组/指针

数组和指针在作为实参传入T[] 或T*的形参时没有区别

void f(int pi[]) { cout << sizeof(pi) << endl; }
int a[5] = { 1,2,3,4,5 };
f(a);

上述代码输出的是4(32位系统)或8(64位系统),总之不是sizeof(int) * 5(数组大小)。

为什么明明形参是数组形式的int [],实际上和指针形式的int *无异呢?关键原因就在于,数组是不能作为左值的。

也就是说,你不能定义两个数组,比如int a[10], b[10];然后用a = b; 来给a赋值。

而函数的实参传给形参是要做1次赋值的,虽然很多书上区分了所谓的传值和传地址,但实际上两者是一样的,传地址不过是在指针类型之间进行赋值,见下述代码

void f1(int i) { i = 100; }
void f2(int* pi) { *pi = 100; }

int main()
{
     int i = 0, j = 0, k = 0;
     f1(i);  // 类似于 int i0 = i; i0 = 100;
     f2(&j); // 类似于 int* p = &j; int* p0 = p; *p0 = 100;
     return 0;
}

但是,在C++里传递数组也是可行的,见下面代码

template <int N> void f(int (*pi)[N]) { cout << sizeof(*pi) << endl; }

int main()
{
     int a[3] = { 1,2,3 };
     f(&a);
     return 0;
}

输出结果是12,即sizeof(int)*3。

像这样蹩脚的传递数组指针,当然,更简单的方式是使用引用,把f()的参数*pi改成&pi,然后sizeof(*pi)改成sizeof(pi);main()里的f(&a)可以直接用f(a),两者本质上是一样的,只不过简化了代码。虽然需要强调的是引用和指针(以及java等语言中的引用)也有一些差异,这里不详细讨论。

这样的做法本质是像下面这样。

int a[3] = { 1,2,3 };

int (*p)[3] = a;  // 形参传递给实参
cout << sizeof(p) << endl;  // 函数体内的代码

OK,这里就可以正视数组和指针的区别了,对于数组int a[N];

a的类型是int [N],不能作为左值(也就是不能直接给a赋值),那么&a取得的则是指向int [N]的指针,表示为int (*)[N],大小为N*sizeof(int)。

其实指针本质上就是地址,之所以有各种各样的指针类型,是为了让编译器了解,你如果想用*p来取得指针指向的对象,到底该读取多大内存?

char s[5] = "1234";
char* pch = &s[0];
cout << *pch << endl;  // 1
short* psh = &s[0];
cout << *psh << endl;  // 12849
int* pi = (int*)&s[0];
cout << *pi << endl;     // 875770417

如果把s[3]和s[4]都置为‘\0‘(对应ASCII码为0),那么*pi和*psh结果一样,都是12849。注意,这里是因为我的机器是小端的,数据的低位保存在内存的低地址中,我让高位变成0了,自然就和没有高位没区别了。

0x0000abcd  int*   或char (*)[4]

0x       abcd  short*或char (*)[2]

0x          cd   char*

打个比方,内存中布局就类似上面那样(abcd是我随便定的值),3个指针都指向同样的地址,但是类型不同导致编译器了解你在解引用(*p)时,到底想从内存中读取多少,到底是从当前地址开始1位,2位还是4位?

再来看看在STL里的应用,STL的一个重要概念是迭代器,而指针也是一种迭代器(随机访问迭代器),在STL函数库(<algorithm>)中,往往接收的类型是用2个迭代器表示的数据范围(比如数组int a[3]; 用a和a+3就能表示首尾),而STL提供了传递函数的方法(可以是函数对象、函数指针或lambda表达式),这些函数接收的参数却不是迭代器,而是迭代器解引用后的对象。

因为我们实际上是要对容器中存储的数据进行操作,而不是去对数据存放的位置进行操作,取得存放位置的目的只是为了取得数据,这些工作在STL函数内部就完成了,调用者只需要告诉STL函数该如何处理数据即可(不需告诉STL函数怎么取得地址)。比如下面给出的for_each示例代码

template<class InputIterator, class Function>
  Function for_each(InputIterator first, InputIterator last, Function fn)
{
  while (first!=last) {
    fn (*first);
    ++first;
  }
  return fn;      // or, since C++11: return move(fn);
}

对于数组int a[N];只需要写std::for_each(a, a + N, [](int& i) { i = rand(); });就可以批量生成随机数了(当然,实现这个功能有个更好的函数是generate)

不过对于二维数组来说,一切都要更为复杂了,虽然很少用到,但是在处理二维字符数组表示的C风格字符串列表时,还是需要知道这点。

char str[M][N];

其中M是字符串数量上限,N是字符串长度上限,假如要批量打印str表示的字符串列表,代码如下

std::for_each(&str[0], &str[M], [](char (&s)[N]) { printf("%s\n", s); }

还是按照上面的分析方法,str类型是char [M][N],那么str[0]类型是char [N],&str[0]类型是char (*)[N],是指向N个字符的指针类型,也就是说对这样类型的指针p,每次执行++p时会移动N个字节(sizeof(char)),str[0][0](即*str[0])移动N个字节就到了str[0][N],也就是str[M][0](即*str[1])。

那么迭代器的类型是char (*)[N],那么解引用的类型就是char (&)[N],即引用一个长度为N的字符数组。

再回顾之前说的,数组在作为实参传递给形参T[]时,实际上传递的是数组的首地址,也就是说,printf接收的实际上是&s[0],即char*类型,因此可以用%s输出。

然而实际上还有个陷阱,比如我要对这些字符串进行排序。

std::sort(&str[0], &str[M],
     [](char (&s1)[N], char (&s2)[N]) { return strcmp(s1, s2) < 0; }

比如这里传入的迭代器是指针类型char (*)[N],而自定义排序函数接收的也是解引用的类型char (&)[N],编译时却报了几十行错误。我昨天在测试ls -l的排序方法时在这里纠结了很久。

原因在于std::sort的内部实现,对于这种比较排序,内部实现经常有交换操作。

比如模板参数Iter表示迭代器类型,在函数内部可能就会有这样的代码

typedef std::iterator_traits<Iter>::value_type T; // 迭代器指向对象的类型
Iter it1, it2;
if (*it1 > *it2)
{     // 交换迭代器指向的对象
      T temp = *it1;
      *it1 = *it2;
      *it2 = temp;
}

这里的Iter是char (*)[N]的话,类型T就是char [N],也就是数组类型。

问题来了,再回顾我这篇博客全篇唯一加红加粗的文字——数组是不能作为左值的。

那么,如果这里的类型T是char*呢?当然就是可以交换了,只不过原数组还是没变。

解决方式即定义一个长度为M的数组,数组元素类型是char (*)[N],然后再排序

时间: 2024-09-30 04:41:20

数组与指针的区别,以及在STL中传递数组/指针的相关文章

C/C++中传递数组参数的问题

我真是太菜了,本来我是今天打算好好搞搞dynamic_programming一系列的经典问题,结果只是停留在把数组连乘的原理给看懂了而已. 原因嘛: 1.当我想要动手实现时,为了给函数和变量起个专业点儿的名字,特意去看了一些细节的命名规范问题: 2.呦西,命名规范解决了,但是名字怎么取才能看起来逼格高一些呢,不是说好的代码不需要注释,只看命名就行了么.然后我发现我想表达的变量名字对应的单词我不会,于是我又去查单词了: 3.呦西+1,命名总算搞定了,但是这个参考代码里面,传递参数为什么用vecto

【前端】vue项目 url中传递数组参数

[问题情景] 我在项目中使用了一个iframe,引入另一个项目,想通过动态修改iframe的src使iframe中的页面动态展示,在这个过程中,我碰到了一个问题,就是我往url传递数组参数的时候,接受到的是[object object],这使我读不出我传递的参数.但是我百度谷歌了一把,都没有找到很好的解决方案.下面附上我的解决方案. [解决方案] 传递参数的页面 let testArray = [{a:1},{b:2},{c:3}]; let testStr = encodeURICompone

java中传递数组的写法

var arr=["110","120","119"]; //如果浏览器不支持JSON,就使用json2.js,json2.js的源码放在最下面 arr=JSON.stringify(arr);//序列化 $.ajax({ url:"sendArray", type:"post", dataType:"text", data:{"phones":arr}, succe

指针数组和数组指针的区别

数组指针(也称行指针)定义 int (*p)[n];()优先级高,首先说明p是一个指针,指向一个整型的一维数组,这个一维数组的长度是n,也可以说是p的步长.也就是说执行p+1时,p要跨过n个整型数据的长度. 如要将二维数组赋给一指针,应这样赋值:int a[3][4];int (*p)[4]; //该语句是定义一个数组指针,指向含4个元素的一维数组. p=a;        //将该二维数组的首地址赋给p,也就是a[0]或&a[0][0] p++;       //该语句执行过后,也就是p=p+

深入理解数组与指针的区别

在大一刚开始学习C的我们也许并没有真正的理解数组与指针,其实C的精华部分便是指针与内存的分配这一块. 那是充其量我们能够知道数组与指针肯定不是完全等价的,相同点就是:对数组的引用总是转化为对指针的引用,而不同点呢就是数组名是常量而指针是变量仅此而已,随着我们资历不断的提升,我们么更加进一步的去理解它,从他的本质去即内存的分配与访问去理解它. 好了,首先呢我们必须明白一个概念在C语言中,一个变量的声明和定义有什么区别. 我们知道定义只是一个特殊的声明. 定义:只能出现在一个地方,创建新对象,同时确

js注意点:数组比较大小方法及数组与对象的区别

(迁移自旧博客2017-04-19) 快速复制数组及数组比较大小方法 首先介绍一下复制数组的方法: var arr = ['A', 'B', 'C', 'D', 'E', 'F', 'G']; var aCopy = arr.slice(); aCopy; // ['A', 'B', 'C', 'D', 'E', 'F', 'G']; 这样就成功复制数组了,是不是很神奇? slice()就是对应String的substring()版本,它截取Array的部分元素,然后返回一个新的Array: v

一步一步认识C++STL中的迭代器

一步一步认识C++STL中的迭代器 "指针"对所有C/C++的程序员来说,一点都不陌生.在接触到C语言中的malloc函数和C++中的new函数后,我们也知道这两个函数返回的都是一个指针,该指针指向我们所申请的一个"堆".提到"堆",就不得不想到"栈",从C/C++程序设计的角度思考,"堆"和"栈"最大的区别是"栈"由系统自动分配并且自动回收,而"堆&quo

PHP中的数组

一.数组的基础 php数组的分类 按照下标的不同,php分为关联数组与索引数组: 索引数组:下标从零依次增长(以前那种) 关联数组:下标为字符串格式,每个下标字符串与数组的值一一关联对应(有点儿像对象的键值对) [关于关联数组和索引数组]1.数组中可同时存在索引数组和关联数组: array(1,"one"=>2,3,5=>4,5,6,"9"=>7,8,"haha"=>9); 2.数组中所有的索引数组,如果不加指定,会去掉关

【转载】C++ 值传递、指针传递、引用传递详解

原文链接:http://www.cnblogs.com/yanlingyin/ 值传递: 形参是实参的拷贝,改变形参的值并不会影响外部实参的值.从被调用函数的角度来说,值传递是单向的(实参->形参),参数的值只能传入, 不能传出.当函数内部需要修改参数,并且不希望这个改变影响调用者时,采用值传递. 指针传递: 形参为指向实参地址的指针,当对形参的指向操作时,就相当于对实参本身进行的操作 引用传递: 形参相当于是实参的“别名”,对形参的操作其实就是对实参的操作,在引用传递过程中,被调函数的形式参数