C程序设计语言--第五章:指针与数组
博客专区 > fzyz_sb 的博客 > 博客详情
C程序设计语言--第五章:指针与数组
fzyz_sb 发表于4年前
C程序设计语言--第五章:指针与数组
  • 发表于 4年前
  • 阅读 107
  • 收藏 2
  • 点赞 0
  • 评论 0

腾讯云 技术升级10大核心产品年终让利>>>   

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

5.1 指针与地址

通常的机器都有一系列连续编号或编址的存储单元,这些存储单元可以单个进行操纵,也可以连续成组的方式操纵.指针是能够存放一个地址的一组存储单元(通常是两个或四个字节).

一元运算符&可用于取一个对象的地址.

p = &c;
将把c的地址赋值给变量p,我们称p为"指向"c的指针.地址运算符&只能应用于内存中的对象,即变量与数组元素.它不能作用域表达式,常量或register类型的变量.

但是,如果我们非要读取地址100上的数据呢?则可以用void *来显式的声明指针进行赋值.

#include <stdio.h>

int main(void)
{
	int *p = ( void * )100;

	printf("%d\n", p );
	printf("%d\n", *p );

	return 0;
}
p的值为100,但是*p的值却是未定义的.

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

int x = 1;
int y = 2; 
int z[ 10 ];
int *ip = NULL;

ip = &x;		/* ip现在指向x */
y = *ip;		/* y的值现在为1 */
*ip = 0;		/* x的值现在为0 */
ip = &z[ 0 ];	        /* ip现在指向z[ 0 ]*/
我们应该注意:指针只能指向某种特定类型的对象,也就是说,每个指针都必须指向某种特定类型的数据类型(一个例外的情况是指向void类型的指针可以存放指向任何类型的指针,但它不能间接引用其自身.)

如果iq,ip均是指向int的指针,那么

iq = ip;
将把ip中的值拷贝到iq中,这样,指针iq也将指向ip指向的对象.这种方法通常用于建立临时变量iq来代替ip进行运算.

5.2 指针与函数参数

由于C语言是以传值的方式将参数值传递给被调用函数,因此,被调用函数不能直接修改主调用函数中变量的值.但是,如果我们把变量的地址传递进去,无法修改地址的情况下修改地址上存储的变量的值,则可以达到类似修改了参数值的效果.

下列swap函数无任何效果:

void swap( int x, int y )
{
	int temp = x;
	x = y;
	y = temp;
}
但是,我们声明参数为指针,则可以达到效果:
void swap( int *px, int *py )
{
	int temp = *px;
	*px = *py;
	*py = temp;
}
书上有个例子,读取输入流中的数字,但是并不通用,因为你会发现:当输入流中加入一个非空格或者数字的字符(比如字母)的时候,那么程序就这样死循环了----因为它会不断的getch这个字母,ungetch这个字母:
#include <ctype.h>
#include <stdio.h>

#define SIZE 128
char buffer[ SIZE ];
int	index;

int getch( void )
{
	return index > 0 ? buffer[ --index ] : getchar();
}
void ungetch( int iValue)
{
	if ( index >= SIZE ){
		printf("error: buffer is full\n");
	}
	else{
		buffer[ index++ ] = iValue;
	}
}

int getint( int *pn )
{
	int c, sign;

	while ( isspace( c = getch() ) ){
		;
	}
	if ( !isdigit( c ) && c != EOF && c != '+' && c != '-' ){
		ungetch( c );
		return 0;
	}
	sign = ( c == '-' ) ? -1 : 1;
	if ( c == '+' || c == '-' ){
		c = getch();
	}
	for ( *pn = 0; isdigit( c ); c = getch() ){
		*pn = 10 * *pn + ( c - '0' );
	}
	*pn *= sign;
	if ( c != EOF ){
		ungetch( c );
	}
	return c;
}

int main(void)
{
	int n;
	int array[10];

	for ( n = 0; n < 10 && getint( &array[ n ] ) != EOF; n++ )
		;

	for ( n = 0; n < 10; n++ ){
		printf("%d--", array[ n ] );
	}

	return 0;
}
程序输出:

习题5-1:

对于此习题,个人并不推荐将'+'或者'-'写回到输入流中,而是应该用某种技巧忽略非数字的符号来达到目的,否则当写回输入流后,会导致数组的n位置变成了垃圾值:

#include <ctype.h>
#include <stdio.h>

#define SIZE 128
char buffer[ SIZE ];
int	index;

int getch( void )
{
	return index > 0 ? buffer[ --index ] : getchar();
}
void ungetch( int iValue)
{
	if ( index >= SIZE ){
		printf("error: buffer is full\n");
	}
	else{
		buffer[ index++ ] = iValue;
	}
}

int getint( int *pn )
{
	int c, sign;

	while ( isspace( c = getch() ) ){
		;
	}
	if ( !isdigit( c ) && c != EOF && c != '+' && c != '-' ){
		ungetch( c );
		return 0;
	}
	sign = ( c == '-' ) ? -1 : 1;
	if ( c == '+' || c == '-' ){
		int temp = c;
		c = getch();
		if ( !isdigit( c ) ){
			ungetch( temp );
			return 0;
		}
	}
	for ( *pn = 0; isdigit( c ); c = getch() ){
		*pn = 10 * *pn + ( c - '0' );
	}
	*pn *= sign;
	if ( c != EOF ){
		ungetch( c );
	}
	return c;
}

int main(void)
{
	int n;
	int array[10];

	for ( n = 0; n < 10 && getint( &array[ n ] ) != EOF; n++ )
		;

	for ( n = 0; n < 10; n++ ){
		printf("%4d\n", array[ n ] );
	}

	return 0;
}
程序输出:

即途中的a,b,c,d均占用了数组的一个元素空间.我们可以简单的忽略掉这些字母:

将if判断换成while判断即可:

if ( c == '+' || c == '-' ){
	c = getch();
	while ( !isdigit( c ) ){
		c = getch();
	}
}
则程序输出:

习题5-2:

对于习题5-2,我们进行改进,即将输入的字符串中的浮点数提取出来,并将其一一输出:

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

void showFloatNumber( char *line )
{
	char	*str = line;
	char	*temp = str;

	while ( '\n' != *line ){
		while ( !isdigit( *line ) ){
			line++;
		}
		while ( isdigit( *line ) || '.' == *line ){
			*str++ = *line++;
		}
		*str = '\0';
		str = temp;
		printf("%f\n", atof( str ) );
	}
}

int main(void)
{
	char buf[128];

	while ( NULL != fgets( buf, 128, stdin ) ){
		showFloatNumber( buf );
	}

	return 0;
}
从这里可以看出,指针的作用真的非常的强大.程序输出:

5.3 指针与数组

pa为数组,则

pa[ i ]<==>*( pa + i )
简而言之,一个通过数组和下标实现的表达式可等价的通过指针和偏移量实现.

但是,数组名和指针之间有一个不同之处:指针是一个变量.因此,在C语言中,语句pa = a和pa++都是合法的.但数组名不是变量,因此,类似于a = pa和a++形式的语句是非法的.

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

#include <stdio.h>

char* my_strlen( char *s )
{
	int n;

	for ( n = 0; *s != '\0'; s++ ){
		n++;
	}

	return s;
}

int main(void)
{
	char arr[] = "hello";

	printf("%s\n", my_strlen( arr ) );
	printf("%s\n", arr );

	return 0;
}
函数进行操作的s的副本,故这段程序输出的是:

注意:第一个输出为空('\0');

我们不能改变my_strlen中s的地址!!!所以s++并为对实参s有任何的影响,但我们可以改变其内容:

#include <stdio.h>

char* my_strlen( char *s )
{
	char *temp = s;
	while ( '\0' != *s ){
		*s++ = 'h';
	}
	*s = '\0';
	s = temp;

	return s;
}

int main(void)
{
	char arr[] = "hello";

	printf("%s\n", my_strlen( arr ) );
	printf("%s\n", arr );

	return 0;
}
程序输出:

来对"我们不能改变s的地址"做个解释:这里不是说改变就有错误,而是你改变s的地址,改的是副本的地址,而不是原来实参的地址:

#include <stdio.h>

//char arr[] = "world";
char* my_strlen( char *s )
{
	char arr[] = "world";
	char *p = arr;
	s = p;

	return s;
}

int main(void)
{
	char arr[] = "hello";

	printf("%s\n", my_strlen( arr ) );
	printf("%s\n", arr );

	return 0;
}
程序输出:

之所以第一个字符串为乱码是因为s指向的是一个临时的局部变量,当程序从函数中返回的时候,这个局部变量已经销毁了....

5.4 地址算术运算

我们来编写类似于malloc和free的堆栈版本alloc和afree函数,进行地址算术运算的讨论:

#define ALLOCSIZE	10000

static char allocbuf[ ALLOCSIZE ];

static char *allocp = allocbuf;

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

void afree( char *p )
{
	if ( p >= allocbuf && p < allocbuf + ALLOCSIZE ){
		allocp = p;
	}
}
指针与整数之间不能相互转换,但0是例外:常量0可以赋值给指针,指针也可以和常量0进行比较.但是,指向不同数组的元素的指针之间的算术或比较运算没有定义(这里有个特例:指针的算术运算中可使用数组最后一个元素的下一个元素的地址).

我们可以通过指针的地址运算来改写strlen函数:

int strlen( char *s )
{
	char *p = s;

	while ( '\0' != *p ){
		p++;
	}

	return p - s;
}
这里要注意:p最后的值是'\0',所以只要返回p - s, 而不是返回p - s + 1.

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

5.5 字符指针与函数

字符串常量是一个字符数组.例如:

"I am a string"
在字符串的内部表示中,字符数组以空字符'\0'结尾.字符串常量可通过一个指向其第一个元素的指针访问.
char *pmessage;
pmessage = "now is the time";
将把一个指向字符数组的指针赋值给pmessage.该过程并没有进行字符串的复制,而只是涉及到指针的操作.C语言没有提供将整个字符串作为一个整体进行处理的运算符.

下面两个定义之间有很大的差别:

char amessage[] = "hello world";/*定义一个数组*/
char *pmessage = "hello world";/*定义一个指针*/
上述声明中,amessage是一个仅仅足以存放初始化字符串以及空字符'\0'的一维数组.数组中的单字符可以进行修改,但amessage始终指向一个存储位置.另一方面,pmessage是一个指针,其初值指向一个字符串常量,之后它可以被修改以指向其他地址.但如果试图修改字符串的内存,结果是没有定义的.
#include <stdio.h>

int main(void)
{
	char amessage[] = "hello world";
	char *pmessage = "hello world";

	amessage[ 2 ] = 'm';
	pmessage[ 2 ] = 'm';

	printf("%s\n", amessage );
	printf("%s\n", pmessage );

	return 0;
}
程序出现未定义行为,因为pmessage[ 2 ] = 'm'本身是未定义的.

以下两个有用的函数来讨论数组和指针方面的关联:

strcpy数组的实现:

void strcpy( char *s, char *t )
{
	int i;

	i = 0;
	while ( ( s[ i ] = t[ i ] ) != '\0' ){
		i++;
	}
}
指针实现:
void strcpy( char *s, char *t )
{
	while ( *s++ = *t++ )
		;
}
strcmp数组的实现:
int strcmp( char *s, char *t )
{
	int i;

	for ( i = 0; s[ i ] == t[ i ]; i++ ){
		if ( '\0' != s[ i ] ){
			return 0;
		}
	}

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

	return *s - *t;
}
这里不能将for循环改写为:
while ( *s++ == *t++ )
是因为当寻找到不相等的元素的时候,自增操作导致return回去的是不相等字符的下一个元素之差.

习题5-3:

void strcat( char *s, char *t )
{
	while ( '\0' != *s ){
		s++;
	}
	while ( *s++ = *t++ ){
		;
	}
}
习题5-4:
int strend( char *s, char *t )
{
	char *temp = t;

	while ( '\0' != *s ){
		s++;
	}
	while ( '\0' != *t ){
		t++;
	}
	while ( temp <= t ){
		if ( *t != *s ){
			return 0;
		}
		t--;
		s--;
	}

	return 1;
}
习题5-5:
#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 ( '\0' != *s ){
		s++;
	}
	while ( ( *s++ = *t++ ) && n-- )
		;
	*s = '\0';
}

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

int main(void)
{
	char s[128] = "hello";
	char t[] = "helld";

	strncpy( s, t, 4 );
	printf("%s\n", s );

	strncat( s, t, 5 );
	printf("%s\n", s );

	printf("%d\n", strncmp( s, t, 3 ) );
	return 0;
}
程序输出:

习题5-6:

getline函数:

int getline( char *s, int lim )
{
	int ch;
	char *temp = s;
	while ( --lim > 0 && ( ch = getchar() ) != EOF && ch != '\n' ){
		*s++ = ch;
	}
	if ( '\n' == ch ){
		*s++ = ch;
	}
	*s = '\0';

	return s - temp;
}
atoi函数:
#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>

int my_atoi( char *s )
{
	int n = 0;
	int sign = 1;
	while ( isspace( *s ) ){
		s++;
	}
	if ( '-' == *s || '+' == *s ){
		sign = ( '-' == *s ) ? -1 : 1;
		s++;
	}
	while ( isdigit( *s ) ){
		n = 10 * n + ( *s - '0' );
		s++;
	}

	return sign * n;
}
int main(void)
{
	printf("%d\n", my_atoi("-12345") );

	return 0;
}
程序输出:

itoa函数 + reverse函数:

#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>

void reverse( char *s )
{
	char *temp = s;

	while ( '\0' != *temp ){
		temp++;
	}
	temp--;
	while ( s < temp ){
		char ch = *s;
		*s++ = *temp;
		*temp-- = ch;
	}
}

void my_itoa( int n, char *s )
{
	char *temp = s;
	int sign = 0;
	if ( n < 0 ){
		n = -n;
		sign = 1;
	}

	while ( n / 10 ){
		*temp++ = n % 10 + '0';
		n /= 10;
	}
	*temp++ = n + '0';
	if ( sign ){
		*temp++ = '-';
	}
	*temp = '\0';

	reverse( s );
}

int main(void)
{
	char str[128];
	my_itoa( -12345, str );
	printf("%s\n", str );

	return 0;
}
程序输出:

strindex函数:

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

int strindex( char *s, char *t )
{
	char *tempForS = s;
	char *tempForT = t;
	int len = strlen( t );
	while ( '\0' != *s ){

		while ( *t == *s ){
			t++;
			s++;
		}
		if ( '\0' == *t ){
			return s - tempForS - len;
		}
		if ( '\0' == *s ){
			return -1;
		}
		s++;
		t = tempForT;
	}

	return -1;
}

int main(void)
{
	char str[] = "hello world i love this world";
	char dst[] = "this";

	printf("%d\n", strindex( str, dst ) );

	return 0;
}
程序输出:

getop函数:

int getop( char *s )
{
	int c;

	while ( ( *s = c = getch() ) == ' ' || c == '\t' )
		;
	s++;
	*s = '\0';

	if ( !isdigit( c ) && c != '.' ){
		return c;
	}

	if ( isdigit( c ) ){
		while ( isdigit( *s = c = getch() ) ){
			s++;
		}
	}
	if ( '.' == c ){
		while ( isdigit( *s = c = getch() ) ){
			s++;
		}
	}
	*s = '\0';
	if ( c != EOF ){
		ungetch( c );
	}

	return NUMBER;
}
5.6 指针数组以及指向指针的指针
#include <stdio.h>
#include <string.h>

#define MAXLINES 5000

char *lineptr[ MAXLINES ];

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

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

int main(void)
{
	int nlines;

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

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

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;
}

void writelines( char *lineptr[], int nlines )
{
	while ( nlines-- > 0 ){
		printf("%s\n", *lineptr++ );
	}
}

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 );
}

void swap( char *v[], int i, int j )
{
	char *temp;

	temp = v[ i ];
	v[ i ] = v[ j ];
	v[ j ] = temp;
}
5.7 多维数组

我们考虑一个日期转换的问题:把某月某日这种日期表示形式转换为某年中的第几天的表示形式,反之亦然.

习题5-8:

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 )
{
	if ( year <= 0 || month < 1 || month > 12 || day < 0 || day > 31 ){
		return -1;
	}
	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 == 0;
	if ( leap && yearday > 366 ){
		return;
	}
	else if ( yearday > 365 ){
		return;
	}
	for ( i = 1; yearday > daytab[ leap ][ i ]; i++ ){
		yearday -= daytab[ leap ][ i ];
	}
	*pmonth = i;
	*pday = yearday;
}
5.8 指针数组的初始化
char *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 ];
}
5.9 指针与多维数组

习题5-9:

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;
	char *p;

	leap = year % 4 == 0 && year % 100 != 0 || year % 400 == 0;
	p = daytab[ leap ];
	while ( --month ){
		day += *++p;
	}

	return day;
}

void month_day( int year, int yearday, int *pmonth, int *pday )
{
	int i, leap;
	char *p;

	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(void)
{
	int month;
	int day;
	int newday = day_of_year( 2013, 10, 1 );
	month_day( 2013, newday, &month, &day );

	printf("%d--%d\n", month, day );

	return 0;
}
程序输出:

5.10 命令行参数

调用主函数main时,它带有两个参数.第一个参数argc的值表示运行程序时命令行中参数的数目;第二个参数argv是一个指向字符串数组的指针,其中每个字符串对应一个参数.且ANSI标准要求argv[ argc ]的值必须为一个空指针.

程序echo的作用是将命令行参数进行回显.比如我们输入echo hello, world 则打印出hello, world

程序echo的第一个版本将argv看成是一个字符指针数组:

#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;
}
第二个版本则是指针的指针:
#include <stdio.h>

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

	return 0;
}
习题5-10:
#include <stdio.h>

int main(int argc, char *argv[] )
{
	int buf[ 128 ];
	int index = -1;
	int result;
	int i;
	for ( index = 0; index < 128; index++ ){
		buf[ index ] = 0;
	}
	index = -1;
	while ( --argc > 0 ){
		++argv;
		if ( '+' == ( *argv )[ 0 ] ){//我不知道为什么不能用"+" == *argv进行比较
			result = buf[ index ];
			index--;
			buf[ index ] += result;
		}
		else if ( '-' == ( *argv )[ 0 ] ){
			result = buf[ index ];
			index--;
			buf[ index ] -= result;
		}
		else if ( '*' == ( *argv )[ 0 ] ){
			result = buf[ index ];
			index--;
			buf[ index ] *= result;
		}
		else if ( '/' == ( *argv )[ 0 ] ){
			result = buf[ index ];
			if ( 0 == result ){
				printf("error: zero to divisor\n");
				break;
			}
			index--;
			buf[ index ] /= result;
		}
		else{
			buf[ ++index ] = atoi( *argv );
		}
	}

	printf("%d\n", buf[ 0 ] );

	return 0;
}
程序输出:

习题5-11:

信心被一点一点击垮了.....根本不知道如何下手..........



共有 人打赏支持
粉丝 363
博文 209
码字总数 447144
×
fzyz_sb
如果觉得我的文章对您有用,请随意打赏。您的支持将鼓励我继续创作!
* 金额(元)
¥1 ¥5 ¥10 ¥20 其他金额
打赏人
留言
* 支付类型
微信扫码支付
打赏金额:
已支付成功
打赏金额: