数组与指针的区别,以及在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],然后再排序
数组与指针的区别,以及在STL中传递数组/指针的更多相关文章
- 智能指针类模板(上)——STL中的智能指针
智能指针类模板智能指针本质上就是一个对象,它可以像原生指针那样来使用. 智能指针的意义-现代C++开发库中最重要的类模板之一-C++中自动内存管理的主要手段-能够在很大程度上避开内存相关的问题 1.内 ...
- struts2:遍历自定义字符串数组,遍历Action实例所引用对象中的数组
在struts2:OGNL表达式,遍历List.Map集合:投影的使用一文中已经讲述了OGNL遍历List.Map集合等功能. 本文简单写一个遍历数组的示范程序. 1. 遍历自定义字符串数组 < ...
- java中传递数组的写法
var arr=["110","120","119"]; //如果浏览器不支持JSON,就使用json2.js,json2.js的源码放在最 ...
- json中传递数组和list
json的数据类型:List,数组,数字,字符串,逻辑值,对象,null 1.如果json传递的是数组,格式: { "name":"网站", "num ...
- JSP 向 JavaScript 中传递数组
采用隐藏标签的方式: // JSP: <% while(rs.next()) { %> <in ...
- URL地址中传递数组参数的方法
示例:http://127.0.0.1/text/index.php?links[]=ddddd&links[]=fffff var_dump($_GET); array(1) { [ ...
- 转载:STL四种智能指针
转载至:https://blog.csdn.net/K346K346/article/details/81478223 STL一共给我们提供了四种智能指针: auto_ptr.unique_ptr.s ...
- js注意点:数组比较大小方法及数组与对象的区别
(迁移自旧博客2017-04-19) 快速复制数组及数组比较大小方法 首先介绍一下复制数组的方法: var arr = ['A', 'B', 'C', 'D', 'E', 'F', 'G']; var ...
- C语言中动态分配数组
如何动态的定义及使用数组呢?记得一般用数组的时候都是先指定大小的.当时问老师,老师说是不可以的.后来又问了一位教C++的老师,他告诉我在C++里用new可以做到,一直不用C++,所以也不明白.今天在逛 ...
随机推荐
- lr设置监控本地和远程windows系统资源
LoadRunner之设置监控本地和远程Windows系统资源 一般在客户端通过LoadRunner对服务器进行压力测试,都需要实时监控服务器端的系统资源,本篇主要简单介绍一下如何设置在LoadR ...
- C++:获取指定目录下的所有文件
1.获得指定目录下的所有文件(不搜索子文件夹) 需要包含的头文件 #include <io.h> #include <string> #include <vector&g ...
- C# 处理 JSON 常用的帮助类
C#请求接口的方法,具体代码: 首先需要添加引用和第三方的组件,具体如下: 引用命名空间: using System.IO; using Newtonsoft.Json.Linq; using Sys ...
- bzoj4945
题解: 一眼看过去还以为是3-sat 其实d只有8 那么我们可以枚举每一个x选择哪一个 然后再用2-sat处理 代码: #include<bits/stdc++.h> using name ...
- LeetCode OJ:H-Index(H指数)
Given an array of citations (each citation is a non-negative integer) of a researcher, write a funct ...
- # 2018-2019-2 20165210《网络攻防技术》Exp1 PC平台逆向破解(BOF实验)
2018-2019-2 20165210<网络攻防技术>Exp1 PC平台逆向破解(BOF实验) 实验分为三个部分: 手工修改可执行文件,改变程序执行流程,直接跳转到getShell函数. ...
- Android 开发 Tip 17 -- 为什么getBackground().setAlpha(); 会影响别的控件?
转载请注明出处:http://blog.csdn.net/crazy1235/article/details/75670018 http://www.jb51.net/article/110035.h ...
- 安卓开发 报错 错误:This version of android studio is incompatible with the gradle version used. 的解决
本文的解决方法主要参考以下文章: https://blog.csdn.net/sinat_15417921/article/details/51907728 Android 开发总是会遇到各种不知道怎 ...
- Could not load the "light_rain.png" image referenced from a nib in the bundle with identifier
导入图片文件夹的时候勾选create groups
- matlab下利用K-Means进行图像分类
FIRST & BEST SOLUTION clear all; clc; I_rgb=imread('dog.jpg'); figure();imshow(I_rgb);title('原始图 ...