指针与一维数组

通过指针访问内存,执行速度快于通过数组名访问内存,同时可以通过指针移动的方式访问数组
任何通过数组下标所能完成的操作都可以通过指针来实现
指针,数组名都是地址,都可以访问数组元素
缺点是,用指针实现的程序理解稍微困难,不够直观\

数组元素值和地址的等价引用形式,例:
int a[4]={1,2,3,4};
int *pa=a;                   等价于初始化int *pa=&a[0]; 因为a中存放的地址就是数组中首元素地址
pa=a,相当于用数组名给指针赋值,pa就相当于数组名这一指针,可以用指针名加下标的方式表示元素

如果pa指向数组中某个特定元素,那么根据指针运算的定义,pa+i指向pa所指对象之后的第i个对象,pa-i指向pa所指对象之前第i个对象
此时:a[i],pa[i],(a+i),(pa+i)是完全等价的
对应地址:&a[i],&pa[i],(a+i),(pa+i)也是完全等价的
无论数组中元素的类型或长度是什么,以上结论都成
产生这种现象的原因在于,数组名存储的是数组连续空间的首地址本质上就是一个指针常量
实际上,c在计算a[i]的时候就是现将其转换为*(a+i)的形式再进行求值,因此在程序中完全等价,同理&a[i]和a+i也是一样
不过数组名是指针常量不可修改指向,因此不能给数组名赋值,也无法进行单目运算等操作

如:pa=a和pa++都是合法的,但类似于a=pa和a++形式的语句都是非法的

总结:

简而言之,一个通过数组和下标实现的表达式都可以等价地通过指针和偏移量实现
获取数组元素首地址的方式:数组名或数组首元素地址
通过首地址访问数组元素的方式:

  • 数组元素直接访问,即下标变量的方式

  • 指针加偏移量类型的间接地址访问

  • 数组名作为指针值的直接地址访问

  • 把指针看作数组名的下标变量方式

5种访问数组元素的方法示例程序:
#include<stdio.h>
int main()
{
    int sum1,sum2,sum3,sum4,sum5;
    int iarray[]={1,4,2,7,13,32,21,48,16,30};
    int *iptr,n,i;
    sum1=sum2=sum3=sum4=sum5=0;
    const int size=sizeof(iarray)/sizeof(*iarray);
    for(n=0;n<size;n++)
    {
	sum1+=iarray[n];
	sum2+=iptr[n];
	sum3+=*(iarray+n);
	sum4+=*(iptr+n);
	sum5+=*iptr++;
    }
    printf("%d %d %d %d %d",sum1,sum2,sum3,sum4,sum5);
}

指针与二维数组

c中按行优先顺序存放数据,对二维数组而言,数组a[i]代表的是第i行元素的首地址
对二维数组而言,a[i]代表一个一维数组,相当于一个数组名

二维数组的行

假设a代表二维数组首地址,也是第0行的首地址
&a[i]或a+i代表行地址,每次加1移动一行
*(a+i)或a[i]不代表具体元素,仍然是一个地址

二维数组的列

对于每一个一维数组a[i]或*(a+i),a[i]+j或*(a+i)+j表示一维数组a[i]中第j个元素的地址,即&a[i][j]
(a[i]+j)或(*(a+i)+j)表示一维数组a[i]中第j个元素的值,即a[i][j]
a[i]或a+i表示列地址,每次加1移动一个元素

用指针快速访问二维数组,到底增1是增行还是增列(增列是由于在指针数组中增1相当于换了一个数组)由指针类型决定,例:
int (*p)[4];p=a;        此时p->*->[4]->int 表示定义指针指向一个整型数组,数组有4个元素,这时指针+1代表增加一个整型数组的长度
int *p=*a;              此时指针指向*a即&a[0],这时指针+1表示只增加一个整型
int *p[4];              此时定义的是一个长度为4的指针数组

注意,括号(包括()和[]) 的优先级最高,因此int(* p)[4]表明的是一个int指向int[4]的数组,说明是一个二维数组级别指针
但如果不加圆括号的话变成int
p[4],此时表示的是一个int4*的数组,是一个指针数组(一个指向指针的指针)

总结:

二维数组首地址是行地址,要用行指针指向,行指针+1,移动一行
行指针+i表示第i行的行地址,取*就转换为列地址,转为一维数组的处理
指针访问二维数组示例程序:用指针增行和增列两种方式输出二维数组a[m][n]中的每个数据元素

#include<stdio.h>
int main()
{
    const int m=4,n=4;
    int array[m][n]={{1,2,3,4},{1,2,3,4},{1,2,3,4},{1,2,3,4}};
    int *pline,(*prow)[n],i,j;
    prow=array;
    pline=*array;
    for(i=0;i<m;i++)
    {
	for(j=0;j<n;j++)
	{
	    printf("增行 %d",prow[i][j]);       此时*(*(prow+i)+j),(*(prow+i))[j],*(prow[i]+j),prow[i][j]是等价的
	    printf("增列 %d",*(pline+i*n+j));   此时*(pline+i*n+j),pline[i*n+j]是等价的
	    printf("\n");
	}
    }
}

指针与结构体

结构体和指针结合,可以直接访问结构变量成员,也可以通过指向结构的指针进行结构成员的访问
声明结构指针的基本格式:struct 类型名 *指针名
例:typedef struct info
    {
	short num;
	char name[5];
    }info;
    info myinfo1,myinfo2,*pinfo1,*pinfo2;          定义指向结构变量的结构指针
    pinfo1=&myinfo1,pinfo2=&myinfo2;               pinfo1指向myinfo1,pinfo2指向myinfo2
    pinfo1->num=1;
    strcpy(pinfo1->name,"good");
    *pinfo2=*pinfo1;                               将myinfo2的值赋给myinfo1,结构变量之间可以相互赋值
    printf("%d %s",(*pinfo2).num,(*pinfo2).name);
    pinfo2=pinfo1;                                 将pinfo2的值赋给pinfo1,此时pinfo2指向myinfo1
    printf("%d %s",(*pinfo2).num,(*pinfo2).name);  输出结果:1 good
三种存取结构成员的方式:
  • 结构变量名下标方式
  • 通过指向结构的指针和间接运算符*
  • 通过指向结构的指针和指向成员运算符->
    以上面的例子为例:myinfo.num, (pinfo).num, pinfo->num 为等价引用形式
    注意如果使用过
    间接引用结构指针所指的结构变量再访问成员的话,必须要用括号括起来
    因为结构成员运算符’.‘的优先级高于’’,因此表达式pp.x的含义等价于*(pp.x),因为x不是指针,所以该表达式非法(原理同指针)
    结构指针的使用频率非常高,为了使用方便c提供了一种简写方式,假定p是一个指向结构体的指针,可用 p->结构成员 引用相关结构成员
如:struct rect{struct point pt1,pt2;}r,*rp=&r; 其中表达式r.pt1.x;rp->pt1.x;(r.pt1).x;(rp->pt1).x中
   操作符->只能在结构指针指向结构成员时使用

在所有运算符中,结构运算符’.‘和’->’、用于函数调用的’()‘以及用于用于下标的’[]'优先级最高,因此它们同操作数之间的结合也紧密\

如:struct{int len;char *str;}*p;++p->len;将增加len的值而不是p的值,因为隐含括号关系是++(p->len)
可以使用括号改变结合次序,如:(++p)->len将先执行p的加1操作,再对len执行操作,(p++)->len则先读取len然后再将p加1
同理,*p->str读取的是指针str所指的对象的值,*p->str++先读取指针str指向对象的值,然后再将str加1(与*s++相同)
(*p->str)++将指针str指向的对象值加1,*p++->str先读取str指向的对象的值,然后再将p加1

指针数组

由于指针也是变量,所以他们也可以像其他变量一样存储在数组中
一个数组中如果每个元素都是一个指针,则称为指针数组
每个指针基类型相同,使用前必须初始化

定义一个指针数组并初始化,例:
char *proname[]={"Fortran","C","C++"};     指针数组中每个元素为字符串常量的地址

proname声明的是一个一维数组,数组元素是字符指针,通过字符串列表实现,列表中每个字符串常量赋值给数组相应位置的字符指针
第i个字符串的所有字符存储在存储器的某个位置,指向它的指针存储在proname[i]中

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

对c的初学者而言,很容易混淆二维数组与指针数组的区别,因为两者有时可以实现相同的功能

如:int a[10][20],*b[10];

从语法角度讲,a[i][j]和b[i][j]都是对一个整型对象的合法引用
但a是一个真正的二维数组,它分配了200个int类型长度的存储空间,并通过常规的“矩阵”式的下标计算公式20*i+j计算得到a[i][j]的位置
而对b来说该声明仅仅要求分配10个整型指针的空间,并且没有进行初始化,指针数组的初始化必须以显式进行,如静态初始化或表达式初始化
假定b每个元素均指向一个具有20个元素的数组,则编译器需要为它分配200个整型空间和10个整型指针空间
指针数组的优势在于指针所指向的元素长度可以不同(因为在指针数组中存储的可以是整型标量或整型数组的首元素地址值,类似于链表的结构)
虽然以上讨论是借助于整型,但到目前为止指针数组最频繁的用处是存放具有不同长度的字符串常量(初始化较为方便,并且是固定的数据结构)

如:char*name[]={"Illegal month","Jan","Feb","Mar"};
例:假设有100个字符串,其中90个字符串长度为2,10个长度为50
   使用二维字符数组存放:const int m=50,n=100;char str[n][m];
   总存储空间=50*100=5000B
   使用指针数组存放:const int n=50;char *pstr[n];
   每个地址占4字节,总存储空间为指针空间+字符串实际存储空间=4*100+90*2+10*50=1080B
   使用指针数组可以存放不规则的字符串长度,每个字符串实际多长就占多大的存储空间,存储空间可不要求连续(链表的思想)
   而二维字符数组的每个元素长度必须相同,在定义时就已确定并分配存储空间
指针数组示例程序:对n个国家的国名按照字母顺序排序:
#include <stdio.h>
#include <stdlib.h>
#define N 5
void sort(char *name[]) //排序
{
    int i,j;
    char *sp; //用于临时交换
    for(i = 0;i < N-1;i++) //利用选择排序法进行排序
    {
	for(j = i+1;j < N;j++)
	{
	    if(strcmp(name[i],name[j]) > 0)`<span style="font-family: Arial, Helvetica, sans-serif;">`//当name[i]>name[j]时进行交换来排序①
	    {
		sp = name[i];
		name[i] = name[i+1];
		name[i+1] = sp;
	    }
	}
    }
}
void prins(char *put[]) //输出
{
    int i;
    for(i = 0;i < N;i++)
    printf("%s ",put[i]); //不能写*put[i]②
}
int main() {
    char *spa[] = {"China","America","Australia","France","Germany"};
    sort(spa);
    prins(spa);
    getch();
    return 0;
}

结构数组

所谓结构数组,是指数组中的每个元素都是一个结构体
声明结构数组和声明结构变量的方式类似,基本格式为struct {…}结构数组名[数组长度];

如:struct key{
	char *word;
	int count;
   }keytab[NKEYS]; 或单独声明struct key keytab[NKEYS];

这种结构数组的初始化方法同前面所述的初始化方法类似——在定义的后面通过一个用圆括号括起来的初值表进行初始化

如:keytab[]={
    "auto",0,"break",0,"case",0,"char",0,"const",0,
    "continue",0,"default",0,"unsigned",0,"void",0,"volatile",0,"while",0
   };

注意,初值要与结构成员相对应以成组的方式列出,更精确的做法是将每一行(即每个结构)初值都在花括号中

如:keytab[]={
    {"auto",0},{"break",0},{"case",0},{"char",0},{"const",0},
    {"continue",0},{"default",0},{"unsigned",0},{"void",0},{"volatile",0},{"while",0}
   };