The C Programming language(三)指针

通常情况下,机器的一个字节可以存放一个char类型的数据,两个相邻的字节存储单元可以存储一个short类型的数据,而4个相邻的字节存储单元可存储一个long类型的数据,指针是能够存放一个地址的一组存储单元(通常是两个或4个字节)。因此如果c的类型是char,并且p是指向c的指针。
在这里插入图片描述
一元运算符&可用于取一个对象的地址(地址运算符&只能应用于内存中的对象,即变量与数组元素,不能作用于表达式、常量或register类型的变量)
p=&c;(把c的地址给变量p,称p为指向c的指针)

一元运算符*是间接寻址或间接引用运算符,当它作用于指针时,将访问指针所指向的对象。

int x = 1,y = 2,z[10];
int *ip;//ip是指向int类型的指针(表明表达式*ip的结果是int类型)
ip=&x;//ip指向x
y=*ip;//y的值现在为1
*ip=0;//x的值现在为0
ip=&z[0];//ip现在指向z[0]

指针只能指向某种特定类型的对象,也就是,每个指针都必须指向某种特定的数据类型(一个例外情况是指向void类型的指针可以存放指向任何类型的指针,但它不能间接引用自身)
在调用有参函数时,主调函数和被调函数之间由数据传递关系,在定义函数时函数名后面括号中的变量名称为形式参数-”形参“;在主调用函数中调用一个函数时,函数后面括号的参数为”实参“,在调用函数过程中,系统会把实参的值传递给被调用函数的形参。由于C语言是以传值的方式将参数值传递给被调用函数,因此,被调用函数不能直接修改主调函数中变量的值。

void Exchg1(int x, int y) 
{
int tmp;
tmp=x;
x=y;
y=tmp;
printf(“x=%d,y=%d/n”,x,y)
}
void main()
{
int a=4,b=6;
Exchg1 (a,b) ;
printf(“a=%d,b=%d/n”,a,b)
}

其中输出x=6,y=4;而a=4,b=6;
上述的函数并不会影响参数a和b的值,该函数仅仅交换了a和b的副本的值。
可以使主调程序将指向所要交换的变量的指针传递给被调用函数

Exchg2(int *px, int *py)
{
int tmp=*px;
*px=*py;
   *py=tmp;
print(*px=%d,*py=%d/n”,*px,*py);
}
main()
{
int a=4;
int b=6;
      Exchg2(&a,&b);
      Print(“a=%d,b=%d/n”, a, b);
}

由于一元运算符&用来取变量的地址,这样&a就是一个指向变量a的指针。函数的所有参数都声明为指针,并且通过这些指针来间接访问它们指向的操作数。
在这里插入图片描述
指针与数组
声明
int a[10];
定义了一个长度为10的数组a,即定义一个由10个对象组成的集合,这10个对象存储在相邻的内存区域中,名字分别为a[0]…a[9]
在这里插入图片描述

int *pa;//说明是一个指向整型对象的指针
pa=&a[0];//将指针pa指向数组a的第0个元素,即pa的值是数组元素a[0]的地址
在这里插入图片描述
赋值语句x=* pa ;//把数组元素a[0]中的内容复制到变量x中

如果pa指向数组中的某个元素,那么pa+1将指向下一个元素,而pa-i将指向pa所指向数组元素之前的第i个元素。如果指针pa指向a[0],那么*(pa+1)引用a[1]的内容,pa+i是数组元素a[i]的地址。
在这里插入图片描述
根据定义,数组类型的变量或表达式的值是数组第0个元素的地址。执行赋值语句pa=&a[0];后,pa和a具有相同的值,因为数组名所代表的就是该数组最开始的一个元素的地址,即赋值语句可以写出pa=a;,

对数组元素a[i]的引用也可以写成*(a+i)的形式。如果分别施加地址运算符&,即&a[i]和a+i的含义也是相同的。a+i是a之后第i个元素的地址。相应的如果pa是一个指针,那么pa[i]与 *(pa+i)也是等价的。也就是通过数组和下标的表达式可等价通过指针和偏移量实现。(但是指针是一个变量,在c语言中pa=a,pa++都是合法的,但数组名不是变量,因此类似a=pa和a++形式的语句是非法的)

把数组名传递给一个函数时,实际传递的是数组第一个元素的地址。在被调用函数中,该参数是一个局部变量,因此,数组名参数必须是一个指针,也就是一个存储地址值的变量。

int strlen(char *s)
{
int n;
for(n=0;*s!='\0';s++)
	n++;
	return n;
}

由于s是一个指针,所以对其执行自增运算是合法的,s++运算仅对该指针在strlen函数中私有副本进行自增运算。而在函数定义中,char s[] 和 char *s 是等价的。

地址算术运算
将指针、数组和地址的算术运算集成一起是c语言的优点:
例如下面的一个存储分配程序,由两个函数组成。第一个函数alloc(n)返回一个指向n的连续字符存储的指针。第二个函数afree§释放已分配的存储空间。alloc与afree以栈的方式(后入先出)进行存储空间的管理。(最容易的实现方法是让alloc函数对一个大字符数组allocbuf中的空间进行分配,由于函数alloc和afree处理的对象是指针而不是数组下标,可以将数组声明为static类型,对外不可见)

#define ALLOCSIZE 10000//可用空间大小
static char allocbuf[ALLOCSIZE];//alloc使用的存储区
static char *allocp;
allocp=allocbuf;//使用指针allocp指向allocbuf中的下一个空闲单元,allocp存储的是allocbuf[0]的地址
void *alloc(int n)
{
if(allocbuf+ALLOCSIZE-allocp>=n)
{allocp += n;//将allocp指针存储的地址增加n
return allocp-n;
else
return 0;
}
void afree(char *p)//释放p指向的存储区
{if(p >= allocbuf && p< allocbuf +ALLOCSIZE)
allocp=p;
}

在这里插入图片描述
如果空闲空间足够,则分配存储空间后allocp的值至多比allocbuf的尾端地址大1。如果存储空间可以满足,alloc将返回一个指向所需大小的字符块首地址的指针;如果申请无法满足,alloc必须返回0以说明没有足够的空闲空间可供分配(0永远不是有效的数据地址)。
指针和整数之间不能相互转换,但0例外:常量0可以赋值给指针,指针也可以和常量0进行比较。程序中常用符号常量NULL代替常量0.
而在计算p+n时,n将根据p指向的对象的长度按比例缩放,而p指向的对象的长度取决于p的声明。例如,如果int类型占4个字节的存储空间,那么对应的n按4的倍数来计算。
而指针的减法运算:如果p和q指向相同数组中的元素,且p<q,那么q-p+1就是p、q之间元素的数目。

有效的指针运算包括相同类型指针之间的赋值运算;指针同整数之间的加法或减法运算;指向相同数组中元素的两个指针间的减法或比较运算;将指针赋值为0或指针与0之间的比较运算。
其他所有形式的指针运算都是非法的:例如两个指针的加法、乘法、除法、移位运算;指针同float或double类型之间的加法运算;不经强制类型转换而直接将指向一种类型对象的指针赋值给指向另一种类型对象的指针的运算(void类型除外)。

字符指针与函数
字符串常量是一个字符数组,例如:"I am a string"由于字符数组以空字符‘\0’结尾,字符串常量占据的存储单元比双引号的字符数大1;
例如:printf(“hello,world\n”);
实际上是通过字符指针访问该字符串的,在上述语句中,printf接受一个指向字符数组第一个字符的指针。
除了作为函数参数外,字符串常量还有其他用法:
如:char *pmessage;
pmessage = "now is the time“;
将一个指向该字符数组的指针赋值给pmessage,该过程并没有进行字符串的复制,而只是涉及到指针的操作。

下面两个定义之间有很大的差别:
char amessage[]= “now is the time”;
char *pmessage = “now is the time”;
上述声明中,amessage是一个仅仅存放初始化字符串以及空字符’\0’的一维数组,数组中的单个字符可以进行修改,但amessage 始终指向同一个存储位置。
另一方面,pmessage是一个指针,其初值指向一个字符串常量,之后可以被修改以指向其他地址,但是如果试图修改字符串的内容,结果是没有定义的。
在这里插入图片描述
以标准库中两个有用的函数为例:
第一个函数strcpy(s,t)把指针t指向的字符串复制到指针s指向的字符串。如果使用语句s=t实现该功能,其实质上只是拷贝了指针,并没有复制字符。
1、通过数组方式实现:

void strcpy(char *s,char *t)
{
	int i;
	i=0;
	while((s[i] = t[i]) != '\0')
	i++;
}

2、用指针方法实现:

void strcpy(char *s,char *t)
{
	while((*s=*t) != '\0')
	{
	s++;
	t++;
	}
}

或者使用另一种方式

void strcpy(char *s,char *t)
{
	while((*s++ = *t++) != '\0')
	;
	}

使用后缀运算符++表示读取该字符后才改变t的值

第二个函数是字符串比较函数strcmp(s,t)。比较s和t,并且根据s依照字典顺序小于、等于或大于t的结果分别返回负整数、0或正整数。该返回值是第一个不相等字符的插值
1、通过数组的方式实习

int strcmp(char *s,char *t)
{int i;
for(i=0;s[i]==t[i];i++)
{
if(s[i]=='\0')
{
	return 0;
	}
return s[i]-t[i];
}
}

2、通过指针的方式实现

int strcmp(char *s,char *t)
{
for(;*s == *t;*s++,*t++)
{
	if(*s=='\0')
		return 0;
	return *s-*t;
	}
}

下面两个表达式:
*p++ = val;//将val压入栈
val = * - - p;//在读取指针p指向的字符之前先对p执行自减运算,将栈顶元素弹出到val中

指针数组以及指向指针的指针
如果待排序的文本行首尾相连地存储在一个长字符数组中,那么每个文本行可通过指向它的第一个字符的指针来访问,这些指针本身可以存储在一个数组中,将指针传递给函数strcmp 就可实现对这两个文本行的比较。当交换次序两个文本行时,实际上交换的是指针数组中与这两个文本行对应的指针,而不是两个文本行的本身。
在这里插入图片描述
即排序过程分为三个步骤:
(1)读取所有输入行
(收集和保存每个文本行的字符,并建立一个指向这些文本行的指针的数组,还需要统计输入的行数,在输入行数过多时,返回某个表示非法行数的数值,例如-1)
(2)对文本进行排序
(3)按次序打印文本行
(按照指针数组中的次序依次打印这些文本行即可)

#include <stdio.h>
#include <string.h>

#define MAXLINES 5000
char *lineptr[MAXLINES]; //指向文本行的指针数组

int readlines(char *lineptr[],int nlines);
void qsort(char *lineptr[], int left, int right);
void writelines(char *lineptr[],int nlines);

int main()
{int nlines;//读取的输入行数目
	if((nlines = readlines(lineptr,MAXLINES))>=0)
	{qsort(lineptr,0,nlines-1);
	writelines(lineptr,nlines);
	return 0;
	}
	else
	{printf("too big to sort\n");
	return 1;
	}
}

#define MAXLEN 1000//每个输入文本行最大长度
int getline(char *,int);
char *alloc(int);

int readlines(char *lineptr[],int maxline)
{
int len,nlines;
char *p,line[MAXLEN];
nlines = 0;
while((len = getline(line,MAXLEN))> 0)
if(nlines>=maxline || (p = alloc(len)) == NULL)
	return -1;
else{
	line[len-1] = '\0';//删除换行符
	strcpy(p,line);
	lineptr[nlines++] = p;
	}
return nlines;
}

void writelines(char *lineptr[], int nlines)
{
int i;
for(i=0;i<nlines;i++)
	printf("%s\n",lineptr[i]);
}

qsort函数在之后补充;

char *lineptr[MAXLINES] 表示lineptr是一个具有MAXLINES个元素的一维数组,其中数组每一个元素是一个指向字符类型对象的指针,lineptr[i]是一个字符指针,而 *lineptr[i]是指针指向的第i文本行的首字符。

多维数组
日期转换的问题:把某月某日这种日期表示形式转换为某年中第几天的表示形式,反之亦然。
例如3月1日是非闰年的第60天,闰年的第61天。
函数day_of_year将某月某日的日期转换成某一年的第几天。
函数month_day则执行相反的转换。

对闰年和非闰年来说,每个月的天数不同,所以,将这些天数分别存放一个二维数组的两行中比在计算过程中判断2月有多少天更容易。

static char daytab[2][13]//两行13列 = {
{0,31,28,31,30,31,30,31,31,30,31,30,31}.
{0,31,29,31,30,31,30,31,31,30,31,30,31}};
//
int day_of_year(int year,int month,int day)
{  //将某月某日的日期转换成某一年的第几天
	int i,leap;
	leap = year%4 == 0 &&year%100 !=0 || year%400 ==0 ;
	for(i=1; i < month;i++)
		day += daytab[leap][i];
	return day;
}
//将某年中第几天的日期表示形式转换为某月某日的表示形式
void month_day(int year,int yearday ,int *pmonth ,int *pday)
{
int i,leap;
leap = year%4 == 0 && year%100 |= 0 || year%400 ==0for (i =1 ;yearday > daytab[leap][i];i++)
	yearday -=daytab[leap][i];
*pmonth=i;
*pday=yearday;
}

数组daytab 必须在函数day_of_year 和 month_day的外部声明,这样两个函数都可以使用数组。(之所以将daytab的元素声明为char类型,是为了说明在char类型的变量存放较小的非字符整数也是合法的)
在C语言中,二维数组实际上是一种特殊的一维数组,每个元素也是一个一维数组。
因此,数组下标应写成:daytab[i][j]
注意:如果将二维数组作为参数传递给函数,那么在函数的参数声明中必须指明数组的列数。数组的行数没有太大关系。函数调用时传递的是一个指针,指向由行向量构成的一维数组,其中每个行向量是具有13个整型元素的一维数组。
函数声明如下:
f(int daytab[2][13]) {…}
也可以写成
f(int daytab[][13]){…}
以及
f(int (*daytab)[13]){…}
这种声明形式表明参数是一个指针,指向具有13个整型元素的一维数组。由于方括号[]的优先级高于 *的优先级,所以上述声明必须使用圆括号。如果去掉括号,则声明变成 int *daytab[13] 相当于声明一个数组,该数组有13个元素,其中每个元素都是一个指向整型对象的指针,除数组的第一维可以不指定大小外,其余各维都必须明确指定大小。

指针数组的初始化
编写一个函数month_name(n),返回一个指向第n月名字的字符串指针,下例中,month_name函数中包含一个私有的字符串数组,当它被调用时,返回一个指向正确元素的指针。

int *month_name(int n)
{
static char *name[] = {
"Illegal month",
"january","February","March","April","May","June","July","August","September","October","November","December"};
return (n<1 || n>12} name[0] :name[n];
}

指针与多维数组
一个数组,若其元素均为指针类型数据,称为指针数组
二维数组与指针数组之间的区别:
int a[10][20];
int *b[10];
从语法角度讲,a[3][4] 和 b[3][4] 都是对一个int对象的合法引用。但a是一个真正的二维数组,分配了200个int类型长度的存储空间,并且通过20 x row +col 计算得到元素a[row] [col]的位置。但是对于b来说,该定义仅仅分配了10个指针,并没有对它们初始化,初始化必须以显式的方式进行,比如静态初始化或通过代码初始化。如果b的每个元素都指向一个具有20个元素的数组,编译器需要分配200个int类型长度的存储空间以及10个指针的存储空间。(但指针的优势在于每一行可以不同)
1、指针数组的声明和图形化描述
在这里插入图片描述
2、二维数组的声明和图形化描述
在这里插入图片描述
命令行参数
在支持C语言的环境中,可以在程序开始执行时将命令行参数传递给程序。调用主函数main时,它带有两个参数。第一个参数(argc,用于参数计数)的值表示运行程序命令行中参数的数目;第二个参数(argv,用于参数向量)是一个指向字符串数组的指针,其中每个字符串对应一个参数。

最简单的例子是程序echo,将命令行参数显示在屏幕的一行上,其中命令行中各参数之间用空格隔开
命令 echo hello, world
将打印下列输出:hello, world

argv[0]的值是启动该程序的程序名,因此argc的值至少为1;如果argc的值为1,则说明程序名后面没有命令行参数。在上面的例子中,argc的值是3,argv[0]、argv[1]和argv[2]的值分别为"echo"、"hello,“以及"world”.第一个可选参数为argv[1],最后一个可选参数为argv[argc-1],并且argv[argc]的值必须为一空指针。
在这里插入图片描述

#include<stdio.h>
int main(int argc, char *argv[])
{
	int i;
	for(i=1;i<argc;i++)
	printf("%s%s",argv[i],(i<argc-1)?" ":"");
	printf("\n");
	return 0;
}

由于argv是一个指向指针数组的指针,所以可以通过指针而非数组下标的方式处理命令行参数。

#include<stdio.h>
int main(int argc, char *argv[])
{
	while(--argc>0)
		printf("%s%s",*(++argv),(argc>1)?" ":"");
		printf("\n");
		return 0;
}

因为argv是一个指向参数字符串数组起始位置的指针,所以自增运算(++argv)使得最开始指向argv[1]而非argv[0]。每执行一次自增运算,就使得argv指向下一个参数,*argv就是指向那个参数的指针。与此同时,argc执行自减运算,当它变成0时,就完成了所有参数的打印。

命令行执行是在terminal模式下和UNIX的命令行环境,可以找到程序可执行代码,或者下载命令行工具,使用gcc 编译程序。

指向函数的指针
函数本身不是变量,但是可以定义指向函数的指针。这种类型的指针可以被赋值、存放在数组中、传递给函数以及作为函数的返回值等。
如果在程序中定义一个函数,在编译时,编译系统为函数代码分配一段存储空间,这段存储空间的起始地址成为这个函数的指针。
(定义一个指向函数的指针变量,用来存放某一函数的起始地址,这意味着此指针变量指向该函数)
例如:int (*p)(int,int);
定义p是一个指向函数的指针变量,可以指向函数的类型为整型且有两个整型参数的函数。p的类型用
int( * )(int,int)表示。
下例:用函数指针变量调用函数
1、通过函数名调用函数

#include<stdio.h>
int main()
{	int max(int,int);//函数声明
	int a,b,c;
	scanf("%d%d",&a,&b);
	c=max(a,b);
	printf("the max number is%d",c);
	return 0;
}
int max(int x,int y)
{int c;
	if(x>y)
	c=x;
	else
	c=y;
return c;
}

2、通过指针变量访问它所指向的函数

#include<stdio.h>
int main()
{
	int max(int,int);
	int (*p)(int,int);//定义指向函数的指针变量 
	int a,b,c;
	p=max;//使p指向max函数
	scanf("%d%d",&a,&b);//scanf函数格式后面应当是变量地址,而不是变量名 
	c=(*p)(a,b);//通过指针变量调用max 函数
	printf("the max number is%d",c);
	return 0; 
}
int max(int x,int y)
{int c;
	if(x>y)
	c=x;
	else
	c=y;
return c;
}

可以看到1、2的max函数是一致的,不同处在于main函数中调用max函数的方法。
int(*p)(int,int);//最前面的int表示这个函数值(函数返回的值)是整型的。最后面的括号中有两个int,表示这个函数有两个int型参数。注意 *p两侧的括号不能省略,表示p先与 *结合,是指针变量,然后再与后面的()结合,()表示是函数,即表明指针变量指向函数。如果写成int *p(int,int); ,由于()优先级大于 *,相当于声明一个p函数,这个函数的返回值是指向整型变量的指针。
而赋值语句”p=max“的作用是将函数max的入口地址给指针变量p,但是注意:指向函数的指针变量不能进行算术运算

用函数名调用函数,只能调用所指定的一个函数,而通过指针变量调用函数,可以根据不同情况先后调用不同的函数。

发布了54 篇原创文章 · 获赞 4 · 访问量 1015

猜你喜欢

转载自blog.csdn.net/buzhiquxiang/article/details/103846840