[C]指针与数组

指针与数组

指针是一种保存变量地址的变量,在C中指针使用十分广泛,指针常常是表达式某个计算的唯一途径,且相比其他方法,指针可生成更高效、紧凑的代码。但指针和goto语句一样,会导致程序难以理解,故使用指针需更谨慎。

指针与地址

the C programme指出:指针是能够存放一个地址的一组存储单元(通常是两个或4个字节),如果c类型是char,且p是指向c的指针,则可用下图表示它们之间的关系:

在这里插入图片描述
一元运算符&可用于取一个对象的地址,如下

char c;
char *p = &c;
//访问*p 相当于访问c

将把c的地址赋值给变量p,称p为"指向"c的指针。地址运算符&只能应用于内存中的对象,即变量与数组元素。它不能作用于表达式、常量或register类型的变量。而一元运算符*是间接寻址或间接引用运算符,当它作用于指针时,将访问指针所指向的对象

值得注意,指针只能指向某种特定类型的对象,即每个指针都必须指向某种特定的数据类型。一个例外情况是指向void类型的指针可存放指向任何类型的指针,但它不能间接引用其自身

指针与函数参数

C语言中是以传值的方式将参数值传递给调用函数,因此被调用函数不能直接修改主调函数中变量的值;但可将被调用函数中参数声明为指针通过指针间接访问它们指向的操作数,指针参数使得被调用函数能够访问和修改主调函数中对象的值。

void swap(int x, int y) /*WRIONG*/
{	
	int temp = x;
	x = y;
	y = temp;
}
swap(a,b);

由于参数采用传值方式,上述swap函数不会影响到调用它的例子中参数a和b的值,仅仅交换了a和b的副本的值。
可是主调程序将指向所要交换的变量的指针传递给被调用函数,即:

void swap(int *px, int *py){
	int temp = *px;
	*px = *py;
	*py = temp;

}
swap(&a,&b);

在这里插入图片描述
指针参数使得被调用函数能够访问和修改主调函数中对象的值。

指针和数组

C语言中,指针和数组之间的关系十分密切,通过数组下标所能完成的任何操作够可通过指针来实现。一般,用指针编写的程序比用数组下标编写的程序执行速度快,但用指针实现的程序理解较难。

数组声明及使用指针访问数组元素

//定义一个长度为10的数组a
int a[10];
//定义一个指向整型对象的指针
int *p =NULL;
//指针p指向数组a的第0个元素 
p = &a[0];
int x;
//赋值语句
x = *p;
*(p+i) 等价于a[i]

p的值为数组元素a[0]地址
在这里插入图片描述
若p指向数组中的某个特定元素,那根据指针运算的定义,p+1将指向下一个元素,p+i将指向p所指向数组元素之后的第i个元素,而p-i将指向p所指向数组元素之前的第i个元素

在这里插入图片描述
"指针加1"意味着,p+1指向p所指向的对象的下一个对象,相应地,p+i指向p所指向的对象之后的第i个对象
当把数组名传递给一个函数时,实际上传递的是该数组第一个元素的地址。即数组名是数组的首元素地址,是一个常量;也可将指向子数组起始位置的指针传递给函数,及将数组的一部分传递给函数,如下

int a[] = {0,12,3,4,5,5};
printf("a = %p\n",a); //%p:数据按十六进制输出
printf("&a[0] = %p\n",&a[0]);
//a = 10;//err数组名只是常量,不能修改
fun(a+2);//等价于fun(&a[2])

fun(int *s){ //声明等价于fun(int s[])

}

数组本身就是依靠指针来实现的,所以数组是指针的典型应用。

1 使用指针访问数组

int buf[6] = {0,1,2,3,4,5};
//1 下标访问
int i = 0;
for(i = 0;i<sizeof(buf);i++)
    printf("%d\n.",buf[i]);

2 指针常量访问

int i = 0;
for(i = 0;i<sizeof(buf);i++)
    printf("%d\n.",*(buf+i));

3 指针变量访问

int i = 0;
int *p = buf;
for(i = 0;i<sizeof(buf);i++)
    printf("%d\n.",*(p+i));

注意:2 指针常量访问数组中,*(buf)是错误的,buf++等价于buf=buf+1,但是buf是常量,不能被赋值。

3中指针变量访问数组,*(p+i)可以写成p++;而1中下标访问本质上还是通过地址访问。
从内存角度理解指针访问数组的实质,数组中各个元素在内存中是相连的,空间地址是连续的,且每个元素的类型相同,每个元素的空间大小一样。根据数组的特点,只要知道其中一个元素的首地址,很很容易推算出其它元素空间的首地址。

#include <stdio.h>
int main(void){
	int buf[6] = {0,1,2,3,4,5};
	int *p = NULL;
	p = &buf[3];
	if(p != NULL){
		p--;
		printf("*p=%%d\n",*p);
		printf("buf[3]=%d\n",buf[3]);
	} 	
	return 0;
} 

结果

*p=2
buf[3]=3

指针与数组类型的匹配问题

int buf[5];
int *p =NULL;
p = buf;    //正确

因为p是int类型,而buf等价于&buf[0],地址类型都是int。而下面做法是有问题的

int buf[5];
int *p =NULL;
p = &buf;    //错误

因为p是int类型,但是&buf是数组首地址,是int()[5]类型, 所以不对。
buf、&buf[0]、&buf异同
buf:有两层含义,一是数组名,sizeof(buf)时,buf就是数组名的含义;二是等价于&buf[0],表示数组第一个元素的首字节地址,是一个常量值。
buf[0]:第一个元素的空间,可以对其进行读写操作,即可当左值读,又可当右值写。
&buf[0]:等价buf,是一个地址常量,只能作为左值。
&buf:表示数组首地址,是一个地址常量,同样只能作为右值。

buf与&buf的值相等, 但含义不同。printf("%p\n",buf)与printf("%p\n",&buf)打印出的结果相同,表明他们的值相等。但是printf("%p\n",buf+1)与printf("%p\n",&buf+1)的打印结果完全不同,因为他们含义完全不同,buf表示数组第一个元素的首字节地址,加1加的是一个元素空间的大小。&buf表示数组首地址,加1加的是整个数组空间大小,数组首地址主要用于构建多维数组,对于一维数组,数组首地址没有太大的使用意义。
总结:指针类型决定了指针如何参与运算
指针变量在运算时,变量存放的就是一个地址值,所以做的事地址运算;
地址+1,加的是一个数组元素空间的大小,如下所示;

int buf[5];
int *p = buf;
//p+1等价于p+1*sizeof(int)

指针数组和数组指针

什么是指针数组?从文字角度来理解,一般放在前面的事修饰语,放在后面是主语。指针数组的实质是一个数组,且该数组里面的元素全部是指针变量,故叫指针数组。而数组指针的实质是一个指针,因为该指针指向的是一个数组,故称为数组指针。

C语言编译器是怎么区分指针和数组呢?指针在于这个星号*。
数组就在于p后面有中括号[]。要搞清楚你定义的符号是谁,第一步:找核心,第二步:找结合(注意符号优先级)。
如*int p;核心是p,p跟谁结合?两个选择,一个是星号*,一个是分号,根据一般规律,分号不结合的,因此p与星号结合成p,左边是int,右边是分号,因为分号不结合,因此p与int结合表示p这个指针指向int类型的数据。又如int p[5];中,核心是p,p左边是int,右边是[],根据优先级,p与括号[]结合成数组。p[]左边是int,右边是分号,因为分号不结合,所以p[]与int结合表示数组中的元素是int型的。

注意:若核心和星号*结合,表示核心是指针;若核心和中括号[]结合,表示核心是数组;如果核心和小括号结合,表示核心是函数。遇到优先级问题,第一查优先级表,第二,先记住中括号[]等几个优先级比较高的符号即可

#include<stdio.h>
int main(int argc,char *argv[]){
  int *p = NULL;
  int a[4];
  p = a;
  return 0;
}

p是一个int类型的指针,让它指向一个数组名(数组名做右值表示数组首元素的首地址),左右类型匹配,编译器不会告警。 下面代码,两类型不匹配,GCC编译时会出现告警:

#include<stdio.h>
int main(int argc,char *argv[]){
  int *p = NULL;
  int a[4];
  p = &a;    //两类型不匹配,GCC编译时会出现告警
  return 0;
}

改进代码

#include<stdio.h>
int main(int argc,char *argv[]){
  int a[4];
  int (*p1)[4];     //数组指针
  p1 = &a;
  return 0;
}

p1是int()[]类型,而&a也是int()[]类型,编译时不会告警。

数组指针与指针数组的区别

数组指针(也称行指针)
定义: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+1;p跨过行a[0][]指向了行a[1][]

所以数组指针成称为指向一维数组的指针,即行指针

指针数组
指针数组指每个数组元素为指针变量;定义 int *p[n]; [ ]优先级高,先与p结合成为一个数组,再由int *说明这是一个整型指针数组,它有n个指针类型的数组元素。这里执行p+1是错误的,这样赋值也是错误的:p = a;

因为p是个不可知的表示(p类似常指针及指针数组数组名是一个常量),只存在p[0]、p[1]、p[2]、…、p[n-1],而且它们分别是指针变量可以用来存放变量地址。但可以这样p = a;这里p表示指针数组第一个元素的值,a的首地址的值。如果要将二维数组赋给一指针数组:

#include<stdio.h>
int main(int argc,char *argv[]){
  int *p[3];	//表示一个一维数组内存放三个指针变量,分别是p[0]、p[1]、p[2] 
  int a[3][4];
  int i;
  for(i=0;i<3;i++){
  	p[i] = a[i];	//分别赋值
	printf("%d.\n",p[i]); 
  } 
  return 0;
}

运行结果

6487472.
6487488.
6487504.

两者的区别:数组指针只是一个指针变量,似乎是C语言里专门用来指向二维数组的,它占有内存中一个指针的存储空间指针数组是多个指针变量以数组形式存在内存中,占有多个指针的存储空间。另外,用来指向二维数组时,其引用和用数组名引用都是一样的。如要表示数组中i行j列一个元素:(p[i]+j)、(p+i)+j)、((p+i))[j]、p[i][j]。

二维数组的内存是内存管理分配的,我们不需要知道怎么分配的内存,但需要知道分配之后内存分布规律,便于更深刻理解的二维数组

优先级:()> []> *

复杂声明

//argv:pointer to char 指向字符的指针
char **argv;
//daytab:pointer to array[13] of int 指向int类型数组的指针
int (*daytab)[13];
//daytab:array[13] of pointer to int 指向int类型指针数组
int *daytab[13];
//comp:function returning pointer to void 函数返回指向void指针
void *comp();
//comp:pointer to function returning void 返回指向void类型的函数指针
void (*comp)();
//x: function returning pointer to array[] of pointer to function returning char
//函数返回 指向返回字符类的函数指针的数组[]的指针 ,what fuck!
char (*(*x())[]) (); 
//x: array[3] of pointer to function returningpointer to array[5] of char
//
char (*(*x[3]))[5]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值