C++中数组和指针是两个十分常用且关系密切的数据结构,“数组即指针,指针即数组”的言论一直层出不穷。从本质上讲,数组和指针是不同的,数组是具有确定数量的元素,而指针只是一个标量值。但是,在某些情况下数组和指针又能相互转换。下面,将从多个角度分析数组和指针。
1. 数组和指针互相转换
数组能在指定情况下转换为指针,当数组在表达式中使用时,编译器将数组名转换为一个指针常量,指向数组第一个元素的地址。
int main()
{
int arr[] = {2,4,1,5,6};
int *p1 = arr;
int *p2 = &arr[0];
cout << "p1:" << p1 << ", value:" << *p1 << endl; // p1:0xffffcbd0, value:2
cout << "p2:" << p2 << ", value:" << *p2 << endl; // p2:0xffffcbd0, value:2
cout << "arr:" << arr << ", value:" << arr[0] << endl; // arr:0xffffcbd0, value:2
cout << "p1+2:" << p1+2 << ", value:" << *(p1+2) << endl; // p1+2:0xffffcbd8, value:1
cout << "arr+2:" << &arr[2] << ", value:" << arr[2] << endl;// arr+2:0xffffcbd8, value:1
}
结论:如果令int * p = arr,则p = arr = &arr[0], p+2 = arr+2 = &arr[2];
但是,有两种情况下,数组名与指针不能混为一谈。
第一种,数组作为sizeof操作符的参数时:
sizeof是一个操作符(operator),其作用是返回一个对象或类型所占的内存字节数。
sizeof(数组): 大小是数组的元素个数*元素类型所占字节数,与数组的类型信息相关,与地址信息无关;
sizeof(指针): 大小固定,32位机器全是4个字节,64位机都是8个字节。
int main() {
int arr[] = {2, 4, 1, 5, 6};
int *p = arr;
cout << sizeof(arr) << endl; // 20 : 5*sizeof(int)
cout << sizeof(*arr) << endl; // 4 : 第一个元素值的大小
cout << sizeof(&arr) << endl; // 8 : 第一个元素地址的大小
cout << sizeof(arr[0]) << endl; // 4 : 第一个元素值的大小
cout << sizeof(&arr[0]) << endl; // 8 : 第一个元素地址的大小
cout << sizeof(p) << endl; // 8 : 指针的大小
cout << sizeof(*p) << endl; // 4 : 指针所指元素值的大小
cout << sizeof(&p) << endl; // 8 : 指针的地址的大小
cout << sizeof(p[0]) << endl; // 4 : 数组第一个元素值的大小
cout << sizeof(&p[0]) << endl; // 8 : 数组第一个元素值的地址的大小
}
第二种,数组作为单目操作符&的操作数时:
&数组: 表示取数组的地址,即数组的指针;
&指针: 表示取指针的地址,即指针的指针。
2. 二维数组与指针
指针的概念其实很简单,指针难就难在与其他结构之间的牵扯,比如指针与二维数组。
第一点,定义指向二维数组的指针:
定义二维数组 int aa[2][5];
由于数组名为数组的第一个元素的地址,而二维数组aa的第一个元素为长度为5的一维数组;
因此,如果定义一个指针指向二维数组的话,该指针的长度也必须为5;
即,int (*p)[5] = aa 或者 int (*p)[5] = &aa[0], 表示长度为5的指针数组。
第二点,指针访问二维数组第一个元素中的值:
首先,*p表示二维数组中第一个元素对应的值,即长度为5的一维数组,假设为a[5];
其次,*p可看成一维数组a[5]的名,即a[5]的第一个元素的地址;
最后,如果想取aa[0][2],则可用*(*p+2) 表示。
int main() {
int aa[2][5] = {{2, 4, 1, 5, 6},{1,2,3,4,5}};
int (*p)[5] = aa;
cout << *(*p+2)<< endl; // 1
}
第三点,指针访问二维数组任意一个元素的值:
(1)使用列指针:定义一个列指针p,让它指向二维数组的第1个元素。
int main() {
int aa[2][5] = {{2, 4, 1, 5, 6},{1,2,3,4,5}};
int *p = &aa[0][0];
cout << *(p+1*5+2)<< endl;
}
首先,aa[0] 相当于 int a[5], 则p=&aa[0][0]相当于p=a[0];
其次,C语言中数组是按行优先顺序存储,而aa[i][j]前面共有i*5+j个元素,所以该二维数组的任意i行j列元素可表示为*(p+i*5+j)
(2)使用行指针:定义一个行指针p,让它指向二维数组的第1行。
int main() {
int aa[2][5] = {{2, 4, 1, 5, 6},{1,2,3,4,5}};
int (*p)[5] = aa; //也可以为p=&aa[0];
cout << *(*(p+1)+2)<< endl;
}
其中* ( *(p+i)+j)表示任意一个i行j列的元素值, *(p+i)可理解为取二维数组中第i个元素的值,即a=int[5],而*(a+j)表示一维数组a的第j个元素的值。
3. 数组作为函数参数
当数组作为函数参数传入时,作为实参的数组将在函数调用前被转换为一个指向该数组首元素的指针。数组永远不会传递给函数处理,函数内部操作传来的数组,其实都是在操作一个指针。
void fun(int arr[])
{
cout << sizeof(arr) << endl; // 8, 说明arr是指针
cout << arr << endl;
}
int main()
{
int arr[3] = {3,1,2};
cout << sizeof(arr) << endl; // 12, 说明arr是数组
cout << arr << endl;
fun(arr);
return 0;
}
4. 数组与指针嵌套使用
当数组和指针共同定义一个对象时,有以下两种情况:
第一种,int *arr[5]
即,指针的指针。
int main()
{
int *arr[3];
int a = 2;
int b = 4;
int c = 3;
arr[0] = &a;
arr[1] = &b;
arr[2] = &c;
cout << *arr[0] << endl; //2
cout << *arr[1] << endl; //4
cout << *arr[2] << endl; //3
return 0;
}
第二种,int (*arr)[5]
即,数组的指针。
int main()
{
int arr[2][5] = {{2,3,1,2,3},{3,2,5,6,7}};
int (*p)[5] = arr;
cout << "arr[1][2]:" << *(*(p+1)+2) << endl; // arr[1][2]:5
return 0;
}
5. 字符型对象与指针
第一种,字符数组与指针
int main()
{
char s[] = "hello";
char *p = s;
cout << p << endl; // p = hello
cout << *p << endl; // *p = h
cout << s << endl; // s = hello
cout << &s[0] << endl;// &s[0] = hello
cout << s[0] << endl; // s[0] = h
}
C++中cout为了省去循环输出字符数组的麻烦,cout<<p<<endl;被翻译为输出p指向的字符串值,但是p指向的依然是数组第一个元素的地址。
第二种,字符串与指针
int main()
{
string s = "hello";
string *p = &s;
cout << p << endl; // p = 0xffffcbd8
cout << *p << endl; // *p = hello
cout << s << endl; // s = hello
cout << &s[0] << endl;// &s[0] = hello
cout << s[0] << endl; // s[0] = h
}
由于string不是数组,因此指针p指向的是字符串s,定义指针的时候,也需要加取地址符号&。