C程序设计语言 chapter 5 指针与数组

本文深入探讨了C语言中的指针概念,包括基本定义、地址运算、与数组及函数的关系等核心内容。并通过实例展示了如何利用指针进行高效的数据处理。

指针是一种保存变量地址的变量。


5.1 指针与地址

通常情况下,机器的一个字节可以存放char类型的数据 两个相邻的字节可以存储一个short类型的数据

指针是能够存放一个地址的一组存储单元(通常是两个或4个字节)


一元运算符&可用于取一个对象的地址,因此:  p=&c  把c的地址赋值给p p为指向c的指针

地址运算符&只能应用于内存中的对象,即变量与数组元素,不能作用于表达式、常量或register变量。


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

int x=1,y=2,z[10];
int *ip;	/*ip是指向int类型的指针*/

ip=&x;		/*ip指向x*/
y=*ip;		/*y=1*/
*ip=0;		/*x=0*/
ip=&z[0];	/*ip现在指向z[0]*/

int *ip  这样声明时为了便于记忆。该声明语句表明表达式*ip的结果是int类型。

对函数的声明也采取这种方式  double *dp,atof(char *);


我们应该注意,指针只能指向某种特定类型的对象,也就是说每个指针都必须指向某种特定的数据类型。

如果指针ip指向整型变量x,那么x出现的任何上下文中都可以使用*ip


一元运算符*和&的优先级比算术运算符的优先级高。

y=*ip+1   将把ip指向的对象的值取出并加1,然后赋值给y

*ip+=1  将ip指向的对象的值加1   等同于++*ip 或 (*ip)++    (括号必须加,因为*和++这样的一元运算符遵循从右至左的结合顺序)


指针也是变量,可以直接使用。

iq=ip   则把ip中的值拷到iq中,这样,指针iq也指向ip所指的对象。


5.2 指针与函数参数

#include <stdio.h>

void swap(int *px,int *py);
int main(){
	int a=1;
	int b=2;
	swap(&a,&b);
	printf("%d\n",a);
	printf("%d\n",b);

}

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

主调程序将指向变量的指针传递给swap

swap函数的参数声明为指针,并且通过指针访问指向的操作数

5.3 指针与数组

在c语言中,指针和数组之间的关系十分密切。

通过数组下标所能完成的任何操作都可以通过指针来实现。


int a[10]   定义了一个长度为10的数组a    a[i]表示该数组的第i个元素。

如果pa的声明为  int *pa;

则说明它是一个指向整型对象的指针  那么,赋值语句pa=&a[0]  指向数组的第0个元素   pa的值为数组元素a[0]的地址

x=*pa  把数组a[0]的值复制到x中


*(pa+i) 是数组元素a[i]的内容


pa=&a[0] 也可以写成  pa=a


但是数组名和指针之间有一个不同之处。 指针是一个变量,因此,在C语言中,pa=a和pa++是合法的

但是 a=pa和a++是不合法的


5.4 地址算术运算

一个不完善的存储分配程序   alloc(n)返回一个指向连续n个字符存储单元的指针

afree(p) 释放已分配的存储空间


#define ALLOCSIZE 1000

static  char allocbuf[ALLOCSIZE];
static char *allocp=allocbuf;

char *alloc(int n){
	if(allocbuf+ALLOCSIZE-allocp>=n){
		allocp+=n;
		return allocp-n;
	}
	else 
		return 0;
}

void afree(char *p){
	if(p>=allocbuf&&p<allocbuf+ALLOCSIZE)
		allocp=p;
}


指针的减法运算是有意义的:如果p和q指向同数组中的元素,且p<q,那么q-p+1就是位于p和q指向的元素之间的元素的数目

5.5 字符指针与函数

练习5-4 编写函数strend(s,t)。如果字符串t出现在s的尾部,则返回1,否则返回0;

#include <stdio.h>

int strend(char *s,char *t);

int strend(char *s,char *t){
	char *temp1=s;  //用temp1保存在比较前的s
	char *temp2=t; //用temp2保存比较前的t
	for(;*s!='\0';s++){
		temp1=s;
		for(;*t!='\0'&&*t==*s;t++,s++);
		if(*s=='\0'&&*t=='\0')
			return 1;
		s=temp1;
		t=temp2;  //因为此时t可能已经往前走了,所以需要还原
	}
	return 0;
}

int main(){
	char *s="hello";
	char *t="llo";
	char *u="h";
	printf("%d\n",strend(s,t));
	printf("%d\n",strend(s,u));
}

练习5-5

/*练习5-5 实现strncpy、strncat、strncmp,它们最多对参数字符串中的前n个字符进行操作。*/
#include <stdio.h>

void strncpy(char *s,char *t,int n);  //将t中最多前n个字符进行操作
void  strncat(char *s,char *t,int n);
int strncmp(char *s,char *t,int n);

#include <stdio.h>

void strncpy( char *s, char *t, int n )
{
	while ( n-- && ( *s++ = *t++ ) )		//&&的优先级高于=,所以要加上括号
		;
	*s = '\0';
}

void strncat(char *s,char *t,int n){
	while(*s!='\0'){
		s++;
	};
	while((*s++=*t++)&&n--);
	*s='\0';
}

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

int main(){
	char s[20]="now is";
	char t[]=" the time";
	//strncpy(s,t,10);
	//printf("%s\n",s);
	strncat(s,t,5);
	printf("%s\n",s);
}

5.6 指针数组和指向指针的指针

例题  对不同长度的字符串进行排序

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

#define MAXLINE 5000

char *lineptr[MAXLINE];

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

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

int main(){
	int nlines;//输入的行数

	if((nlines=readlines(lineptr,MAXLINE))>=0){
		qsort(lineptr,0,nlines-1);
		writelines(lineptr,nlines);
		return 0;
	}else
	{
		printf("error:input too big to sort");
		return -1;
	}
}

#define MAXLEN 1000
int getline(char *,int );
extern char *alloc(int);

/*readlines函数  读取输入行*/
int readlines(char *lineptr[],int maxlines){
	int len,nlines;
	char *p,line[MAXLEN];

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

/*writelines函数:写输出行*/
void writelines(char *lineptr[],int nlines){
	int i;
	for(i=0;i<nlines;i++)
		printf("%s\n",lineptr[i]);
}

int getline(char *line,int lim){
	int c,i;
	for(i=0;i<lim-1&&(c=getchar())!=EOF&&c!='\n';++i)
		*line++=c;
	if(c=='\n'){
		*line++=c;
		++i;
	}
	*line='\0';
	return i;

}


/*qsort函数:按递增顺序对v[left]...v[right]进行排序*/
void qsort(char *v[],int left,int right){
	int i,last;
	void swap(char *v[],int i,int j);
	if(left>=right)
		return;
	swap(v,left,(left+right)/2);
	last=left;
	for(i=left+1;i<=right;i++)
		if(strcmp(v[i],v[left])<0)
			swap(v,++last,i);
	swap(v,left,last);
	qsort(v,left,last-1);
	qsort(v,last+1,right);
}

/*swap函数:交换v[i]和v[j]*/
void swap(char *v[],int i,int j){
	char *temp;
	temp=v[i];
	v[i]=v[j];
	v[j]=temp;
}

5.7 多维数组

#include <stdio.h>
static char daytab[2][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=0;
	leap=(year%4==0&&year%100!=0||year%400==0);
	if(month<1||month>12)
		return -1;
	if(day<1||day>daytab[leap][month])
		return -1;
	for(i=0;i<month;i++)
		day+=daytab[leap][i];
	return day;
}

/*month_day函数:将某年中第几天的日期表示形式转换为某年某月的表示形式*/
void monty_day(int year,int yearday,int *pmonth,int *pday){
	int i,leap=0;
	if(year<1){
		*pmonth=-1;
		*pday=-1;
		return;
	}
	leap=(year%4==0&&year%100!=0||year%400==0);
	for(i=0;yearday>daytab[leap][i];i++)
		yearday-=daytab[leap][i];
	if(i>12&&yearday>daytab[leap][i]){
		*pmonth=-1;
		*pday=-1;
	}else{
	*pmonth=i;
	*pday=yearday;
	}
}

int main(){
	printf("%d",day_of_year(7,2,29));
}


如果将二维数组作为参数传递给函数,那么在函数的参数声明中必须指明数组的列数,和行数没有太大关系。

因为函数调用时传递的是一个指针,它指向由行向量构成的一维数组

f(int daytab[2][13]){.....}

可以写成    f(int daytab[][13]) {......}

也可以写成   f(int (*daytab)[13]) {.....}        表明参数是一个指针,它指向具有13个整型元素的一维数组

5.8 指针数组的初始化

/*month_name函数:返回第n个元素的名字*/

char *month_name(int n){
	static char *name[]={
		"Illegal name","January","Feb","Mar","Apr","May",
		"June","July","Aug","Sep","Oct","Nov","December"
	};
	return (n<1||n>12)?name[0]:name[n];
}

5.9 指针数组和多维数组

要注意指针数组和多维数组的区别

练习5-9  用指针方式代替数组下标方式改写函数day_of_year和month_day

#include <stdio.h>
static char daytab[2][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 leap=0;
	char *p;
	leap=(year%4==0&&year%100!=0||year%400==0);
	if(month<1||month>12)
		return -1;
	if(day<1||day>daytab[leap][month])
		return -1;
	p=daytab[leap];
	while(month--)
	day+=*++p;
	return day;
}

/*month_day函数:将某年中第几天的日期表示形式转换为某年某月的表示形式*/
void monty_day(int year,int yearday,int *pmonth,int *pday){
	int leap=0;
	char *p;
	if(year<1){
		*pmonth=-1;
		*pday=-1;
		return;
	}
	leap=(year%4==0&&year%100!=0||year%400==0);
	p=daytab[leap];
	while (yearday>*++p)
	{
		yearday-=*p;
	}
	*pmonth=p-*(daytab+leap);
	*pday=yearday;
}

int main(){
	printf("%d",day_of_year(7,2,29));
}

5.10 命令行参数

在支持C语言的环境中,可以在程序开始执行时将命令行参数传给程序。调用主函数main时,它带有两个参数,第一个参数(习惯上称为argc,用于参数计数)的值表示运行程序时命令行中参数的数目;第二个参数(称为argv,用于参数向量)是一个指向字符串数组的指针,其中每个字符串对应一个参数。通常用多级指针处理这些字符串。


argv[0]的值是启动该程序的程序名,因此argc的值至少为1

第一个可选参数为argv[1],最后一个为argv[argc-1]

lingwai ,ANSI标准要求argv[argc]的值必须为一个空指针


#include <stdio.h>

/*回显程序命令行参数:版本2*/
main(int argc,char *argv[]){

	while(--argc)
		printf("%s%s",*++argv,(argc>1)?" ":"");
	printf("\n");
	return 0;
}


也可以将printf语句写成下列形式:

printf((argc>1)?"%s ":"%s",*++argv);


第二个例子: 增强4.1节中pattern查找程序的功能。

在4.1节中,我们将pattern内置到程序中了。效仿程序grep中的实现方法改写该程序。

通过命令行的第一个参数指定pattern

#include <stdio.h>
#include <string.h>
#define MAXLINE 1000

int getline(char *line,int max);

int main(int argc,char *argv[]){
	char line[MAXLINE];
	int found=0;

	if(argc!=2)
		return -1;
	else
	{
		while (getline(line,MAXLINE))
		{
			if(strstr(line,argv[1])!=NULL){
				printf("%s",line);
				found++;
			}
		}
	}
	return found;
}


另一个例子 

UNIX系统中的C语言程序有一个公共的约定:以负号开头的参数表示一个可选标志或参数。 假定用-x表示打印所有与模式不匹配的文本行,用-n表示打印行号

例如:

find -x -n [pattern]

将打印所有与模式不匹配的行,并在每个打印行的前面加上行号


改写后的程序如下

#include <stdio.h>
#include <string.h>
#define MAXLINE 1000

int getline(char *line,int max);

/*find函数:打印所有与第一个参数指定的模式相匹配的行*/
main(int argc,char *argv[]){
	char line[MAXLINE];
	long lineno=0;
	int c,except=0,number=0,found=0;

	while(--argc>0&&(*++argv)[0]=='-')  /*在处理每个可选参数前,argc进行自减运算,argv执行自增运算。 如果没有错误, 则argc表示还没有处理的参数数目
										argv指向未处理参数中的第一个。 所以此时argc的值为1,*argv指向pattern*/
		while (c=*++argv[0])
			switch (c)
		{
			case 'x':
				except=1;
				break;
			case 'n':
				number=1;
				break;
			default:
				printf("find: illegal option %c\n",c);
				argc=0;
				found=-1;
				break;
		}
	if(argc!=1)
		printf("usage: find -x -n pattern\n");
	else
		while (getline(line,MAXLINE))
		{
			lineno++;
			if((strstr(line,*argv)!=NULL)!=except){
				if(number)
					printf("%ld:",lineno);
				printf("%s",line);
				found++;
			}
		}
		return found;
}


注意:

(*++argv)[0]和*++argv[0]


*++argv是一个指向参数字符串的指针, 因此 (*++argv)[0]是它的第一个字符  还可以写成 **++argv

因为[]与操作数的结合比*和++高,所以

*++argv[0] 是遍历一个特定的参数串

练习

练习5-10   从命令行输入逆波兰表达式并计算

#include <stdio.h>
#include <math.h>

#define MAXOP 100 //表达式的最大长度
#define NUMBER '0' //标记数字

//对栈的操作
void push(double); //入栈
double pop(void); //取出
int getop(char []);  //获取操作数或符号
void  ungets(char []);


int main(int argc,char *argv[]){
	char s[MAXOP];
	double op2;
	while (--argc)
	{
		ungets(" "); //表示该参数结束
		ungets(*++argv); //参数放入缓存
		switch (getop(s))
		{
		case NUMBER:
			push(atof(s));
			break;
		case '+':
			push(pop()+pop());
			break;
		case '*':
			push(pop()*pop());
			break;
		case '-':
			op2=pop();
			push(pop()-op2);
			break;
		case '/':
			op2=pop();
			if(op2==0.0)
				printf("error");
			else 
				push(pop()/op2);
			break;
		default:
			printf("error:unknown command");
			argc=1;
			break;
		}
	}
	printf("\t%.8g\n",pop());
}

/*对栈的操作*/
#include <stdio.h>
#define MAXVAL 100

int sp=0;
double val[MAXVAL];

/*把f压入到栈中*/
void push(double f){
	if(sp<MAXVAL)
		val[sp++]=f;
	else
		printf("error:stack full");
}

/*弹出并返回栈顶值*/
double pop(void){
	if(sp>0)
		return val[--sp];
	else{
		printf("error: stack empty");
		return 0.0;
	}
}

/*getop.c*/
#include <ctype.h>
#include <string.h>
#include <stdio.h>

int getop(char s[]){
	int c;
	while ((*s=c=getch())!=' '||c=='\t');
	*++s='\0';
	if(!isdigit(c)&&c!='.')
		return c;
	if(isdigit(c))
		while (isdigit(*s++=c=getch()))
			;
	if(c=='.')
		while (isdigit(*s++=c=getch()))
			;
	*s='\0';
	if(c!=EOF)
		ungetch(c);
	return NUMBER;
}

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

#define BUFSIZE 100

char buf[BUFSIZE];
int bufp=0;

int getch(void){
	return (bufp>0)? buf[--bufp]:getchar();
}
void ungetch(int c){
	if(bufp<BUFSIZE)
		buf[bufp++]=c;
	else
		printf("too many characters");
}
void ungets(char s[]){
	int len=strlen(s);
	void ungetch(int);
	while(len>0)
		ungetch(s[--len]);
}



练习 5-11

修改程序entab和detab(第一章中的函数),使它们接受一组作为参数的制表符停止位。如果启动程序时不带参数,则使用默认的制表符停止位设置。


settab函数  将参数设置到tab[]中。

tabpos函数   返回tab[i]是否是'\t'

entab函数    改写自原来程序

#include <stdlib.h>

#define MAXLINE 1000
#define TABINC 8
#define YES 1
#define NO 0

/*settab: set tab stops in array tab*/
void settab(int argc,char *argv[],char *tab){
	int i,pos;

	if(argc<=1){
		for(i=0;i<MAXLINE;i++){
			if(i%TABINC==0)
				tab[i]=YES;
			else
			{
				tab[i]=NO;
			}
		}
	}
	else
	{
		for(i=0;i<MAXLINE;i++)
			tab[i]=NO;
		while (--argc)
		{
			pos=atof(*++argv);
			if(pos>0&&pos<=MAXLINE)
				tab[pos]=YES;
		}
	}
}

#define MAXLINE 1000
#define YES 1

int tabpos(int pos,char *tab){
	if(pos>MAXLINE)
		return YES;
	else
		return tab[pos];
}

#include <stdio.h>
#define MAXLINE 1000
#define TABINC 8
#define YES 1
#define NO 0

void settab( int argc,char *argv[],char *tab);
int tabpos(int pos,char *tab);
void detab(char *tab);


int main(int argc,char *argv[]){
	char tab[MAXLINE+1];

	settab(argc,argv,tab);
	detab(tab);
	return 0;
}
void detab(char *tab){
	int c,pos;

	pos=1;

	while ((c=getchar())!=EOF)
	{
		if(c=='\t'){
			putchar(' ');
			while (tabpos(pos++,tab)!=YES)
			{
				putchar(' ');
			}
		}
		else if(c=='\n'){
			putchar(c);
			pos=1;
		}else{
			putchar(c);
			pos++;
		}
	}
}
void entab(char *tab){
	int c,pos;
	int nb=0;
	int nt=0;

	for(pos=1;(c=getchar())!=EOF;++pos){
		if(c==' '){
			if(tabpos(pos,tab)!=NO)
				++nb;
			else{
				nb=0;
				++nt;
			}
		}else
		{
			for(;nt>=0;--nt)
				putchar('\t');
			if(c=='\t')
				nb=0;
			else{
				for(;nb>=0;--nb)
					putchar(' ');

			}
			putchar(c);
			if(c=='\n')
				pos=0;
			else if(c=='\t')
			{
				while (tabpos(pos,tab)!=YES)
				{
					pos++;
				}
			}
		}
	}
}



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值