C语言中指针的理解2:指针与数组
C语言中指针的理解2:指针与数组
1.深入学习数组
1.1 从内存角度来理解数组
从内存角度讲,数组变量就是一次分配多个同类型的变量,并且这些变量在内存中的存储单元是连续依次分布的。
分开定义多个变量,比如int a, b, c, d;
和一次定义一个数组int a[4];
,这两种定义方法相同点是都定义了4个int
型变量,而且这4个变量都是独立的单个使用的。不同点是单独定义时a
、b
、c
、d
在内存中的地址不一定相连,但是定义成数组后,数组中的4个元素地址肯定是连续依次相连的。
数组中多个变量虽然必须单独访问,但是因为它们的地址是连续分布的,因此很适合用指针来操作,于是数组和指针之间的关系就叫天生纠结在一起的。
1.2 从编译器角度来理解数组
从编译器角度来讲,数组变量也是变量,和普通变量和指针变量并没有本质区别。变量的本质就是一个地址,这个地址在编译器中决定具体数值,具体数值和变量名绑定,变量类型决定这个地址的延续长度。
1.3 数组中几个关键符号的理解
int a[5]={0,1,2,3,4};
a
、a[0]
、&a
、&a[0]
这4个符号搞清楚了,数组相关的很多问题都有答案了。理解这些符号的时候要和左值右值结合起来,也就是搞清楚每个符号分别做左值和右值时的不同含义。
-
a
就是数组名。a
做左值时表示整个数组的所有空间:5×4=20字节,又因为C
语言规定数组操作时要每个元素独立单个操作,不能整体操作数组,所以a
不能做左值。a
做右值表示数组首元素,也就是a[0]
的首地址,首地址就是起始地址,就是4个字节中最开始第一个字节的地址,a
做右值等同于&a[0]
; -
a[0]
表示数组的首元素,也就是数组的第0个元素。a[0]
做左值时表示数组第0个元素对应的连续4字节内存空间。做右值时表示数组第0个元素的值,也就是数组第0个元素对应的内存空间中存储的那个数。 -
&a
就是数组名a
取地址,字面意思来看就应该是数组的地址。&a
不能做左值,因为&a
实质是一个常量而不是变量因此不能赋值,所以自然不能做左值。&a
做右值时表示整个数组的首地址。 -
&a[0]
字面意思就是数组第0个元素的首地址,[]
的优先级要高于&
,所以a
先和[]
结合再取地址。做左值时表示数组首元素对应的内存空间,做右值时表示数组首元素的值,也就是数组首元素对应的内存空间中存储的那个数值。做右值时&a[0]
等同于a
。
解释:为什么数组的地址是常量?因为数组是编译器在内存中自动分配的。当我们每次执行程序时,运行时都会帮我们分配一块内存给这个数组,只要完成了分配,这个数组的地址就定好了,本次程序运行直到终止都无法再改了。在程序中只能通过
&a
来获取这个分配的地址,却不能去用赋值运算符修改它。
2.指针与数组的天生姻缘
2.1 指针方式访问数组元素
数组元素使用时不能整体访问,只能单个访问。访问方式有2种:
- 数组形式:
数组名[下标];
,注意下标从0开始。 - 指针形式:
*(指针+偏移量);
,如果指针是数组首元素地址,比如a
或者&a[0]
,那么偏移量就是下标。指针也可以不是首元素地址而是其他哪个元素的地址,这时候偏移量就要结合实际情况考虑了。
举个栗子:
#include<stdio.h>
int main(int argc,char**argv)
{
int a[5]={0,1,2,3,4};
for(int i=0;i<5;++i)
{
//数组形式访问元素
printf("%d. ",a[i]);
}
printf("\n");
int* p = &a[1]; //p指向数组的第2个元素a[1]
*++p = 3; //++优先级高于*,所以p先加1,指向a[2],然后解引用,修改的就是a[2]
for(int i=0;i<5;++i)
{
printf("%d. ",a[i]);
}
printf("\n");
return 0;
}
输出:
0. 1. 2. 3. 4.
0. 1. 3. 3. 4.
数组下标方式和指针方式均可以访问数组元素,两者的实质其实是一样的,在编译器内部都是用指针方式来访问数组元素的。数组下标方式只是编译器提供给编程者一种壳(语法糖)而已,所以用指针方式来访问数组才是本质的做法。
2.2 从内存角度理解指针访问数组的实质
数组的特点就是:数组中各个元素的地址是连续的,而且数组还有一个很大的特点(其实也是数组的一个限制)就是数组中各个元素的类型比较相同。类型相同就决定了每个数组元素占几个字节是相同的,比如int
数组中每个元素都占4字节,没有例外。这两个特点就决定了只要知道数组中一个元素的地址,就可以很容易推算出其他元素的地址。
2.3 指针和数组类型的匹配问题
int a[5];
int *p;
p = a; // 类型匹配
p = &a; // 类型不匹配。p是int *,&a是整个数组的指针,也就是一个数组指针类型,不是int指针类型,所以不匹配
(2)&a、a、&a[0]从数值上来看是完全相等的,但是意义来看就不同了。从意义上来看,a和&a[0]是数组首元素首地址,而&a是整个数组的首地址;从类型来看,a和&a[0]是元素的指针,也就是int 类型;而&a是数组指针,是int ()[5];类型。
2.4 总结:指针类型决定了指针如何参与运算
指针参与运算时,因为指针变量本身存储的数值是表示地址的,所以运算也是地址的运算。指针参与运算的特点是:指针变量+1,并不是真的地址加1,而是地址+1*sizeof(指针类型)
;比如int *
指针,+1就实际表示地址+4,如果是char *指针,则+1就表示地址+1;如果是double *指针,则+1就表示地址+8.
(2)指针变量+1时实际不是加1而是加1×sizeof(指针类型),主要原因是希望指针+1后刚好指向下一个元素,而不希望错位。
3.指针、数组与sizeof运算符
3.1 sizeof运算符的使用
sizeof
是C
语言的一个运算符,它不是函数,虽然用法很像函数。因为在不同平台下各种数据类型所占的内存字节数不尽相同,比如int
类型在32位系统中为4字节,在16位系统中为2字节。所以程序中可以使用sizeof
来判断变量或者数据类型在当前环境下占几个字节。
举个栗子:
#include<stdio.h>
#include<string.h>
int main(int argc,char**argv)
{
char str[] = "hello";
printf("size of str = %d.\n",sizeof(str));
printf("size of str[0] = %d.\n",sizeof(str[0]));
printf("strlen of str = %d.\n",strlen(str) );
char *p=str;
printf("sizeof(p) = %d.\n",sizeof(p));
printf("sizeof(*p) = %d.\n",sizeof(*p));
printf("strlen(p) = %d.\n", strlen(p));
return 0;
}
输出:
size of str = 6.
size of str[0] = 1.
strlen of str = 5.
sizeof(p) = 8.
sizeof(*p) = 1.
strlen(p) = 5.
32位系统中所有指针占4个字节,64位系统中所有指针占8个字节,不管是什么类型的指针。
strlen()
是一个C
库函数,用来返回一个字符串的长度,字符串的长度是不计算字符串末尾的'\0'
的。
3.2 sizeof使用的一些细节
1.
sizeof
测试一个变量本身,和sizeof
测试这个变量的类型,结果是一样的。
举个栗子:
#include<stdio.h>
int main(int argc,char**argv)
{
int n=10;
printf("sizeof(n) = %d.\n",sizeof(n));//等价于sizeof(int)
printf("sizeof(int) = %d.\n",sizeof(int));
return 0;
}
输出:
sizeof(n) = 4.
sizeof(int) = 4.
inf func,sizeof(b) = 8.
2.
sizeof(数组名)
的时候,数组名不做左值也不做右值,纯粹就是数组名的含义。那么sizeof(数组名)
实际返回的是整个数组所占用以字节为单位的内存空间。
举个栗子:
#include<stdio.h>
int main(int argc,char**argv)
{
int b[100];
printf("sizeof(b) = %d.\n",sizeof(b));
return 0;
}
输出:
sizeof(b) = 400.
3.函数传参的时候形参是可以用数组的,但是函数形参是数组时,实际传递是不是整个数组,而是数组的首元素首地址,也就是说函数传参用数组来传,实际相当于传递的是指针,该指针指向数组的首元素首地址。这里
func(b)
中的b
等价于作右值,实际传递的就只有一个指针,所以这里的func()
其实等价于下面func2()
的写法。在一些需要传递数组的函数中,经常看到的写法是void func(int*a,int n);
,其中n
就是数组的大小,这是因为数组大小是不能直接通过传递数组来传递的,必须额外单独传递。
举个栗子:
#include<stdio.h>
void func(int b[])
{
printf("in func,sizeof(b) = %d.\n",sizeof(b));
}
void func2(int* b)
{
printf("in func,sizeof(b) = %d.\n",sizeof(b));
}
int main(int argc,char**argv)
{
int b[100];
func(b);
return 0;
}
输出:
in func,sizeof(b) = 8.
4.指针定义与
define
和typedef
结合的时候,定义的类型结果不一样,sizeof结果自然也不一样。
#include<stdio.h>
#define dpChar char*
typedef char* tpChar;
int main(int argc,char**argv)
{
dpChar p1,p2; //等价于char* p1,p2; p1是char*类型,p2是char类型
tpChar p3,p4; //等价于char* p1;char* p2; p1,p2都是char*类型
return 0;
}
上一篇: java架构-Linux Centos7 初始化配置
下一篇: java并发中的延迟初始化