C和指针---第七章:函数

7.1 函数定义

函数的定义就是函数体的实现。函数体就是一个代码块,它在被调用时执行。与函数体定义相反,函数声明出现在函数被调用的地方。函数声明向编译器提供该函数的相关信息,用于确保函数被正确的调用。所以函数声明包含在.h文件中,而定义包含在.c文件中,而我们只需要#include ".h"文件即可。

#include <stdio.h>

int *find_int( int key, int array[], int array_len )
{
	int i;

	for ( i = 0; i < array_len; i++ ){
		if ( array[i] == key ){
			return &array[i];
		}
	}

	return NULL;
}
备注:传递数组的时候,我们无法确定数组的长度,所以要把数组的长度当作一个参数传递进去,所以在函数内部计算数组的长度是错误的:
int len = sizeof(array) / sizeof(*array);
则len永远等于1.

7.2 函数声明

当编译器遇到一个函数调用时,它产生代码传递参数并调用这个函数,而且接受该函数返回的值(如果有的话)。但编译器是如何知道该函数期望接受的是什么类型和多少数量的参数呢?如何知道该函数的返回值(如果有的话)类型呢?

答案是:通过函数声明

7.2.1 原型

向编译器提供函数信息由两种方式:

1. 如果同一源文件的前面已经出现了该函数的定义,编译器就会记住它的参数数量和类型,以及函数的返回值类型。接着,编译器便可以检查该函数的所有后续调用(在同一个源文件中),确保它们是正确的。

2. 使用函数原型。原型总结了函数定义的起始部分的声明,向编译器提供有关该函数应该如何调用的完成信息。使用原型最方便的方法是把原型置于一个单独的文件,当其他源文件需要这个函数的原型时,就是用#include指令包含该文件。这个技巧避免了错误键入函数原型的可能性(下面的例子1说明这个问题),它同时简化了程序的维护任务,因为这样只需要该原型的一份物理拷贝。如果原型需要修改,你只需要修改它的一处拷贝。

例子1:

使用函数原型的危险方法:

void a()
{
	int *func( int *value, int len );
}
void b()
{
	int func(int len, int *value );
}
其中,func具有代码块作用域,编译器无法判断它们是否为同一个函数原型(因为编译函数结束时候会丢掉原型信息)。下面的方法更为安全:
#include "func.h"
void a()
{
	//
}
void b()
{
	//
}
文件func.h包含了下面的函数原型:
int *func( int *value, int len );
从几个方面看,这个技巧比前一个方法更好:

1. 现在函数原型具有文件作用域,所以原型的一份拷贝可以作用于整个源文件,较之在该文件每次调用前单独书写一份函数原型要容易的多。

2. 现在函数原型只书写一次,这样就不会出现多份原型的拷贝之间的不匹配现象

3. 如果函数的定义进行了修改,我们只需要修改原型,并重新编译所有包含了该原型的源文件即可。

4. 如果函数的原型同时也被#include指令包含到定义函数的文件中,编译器就可以确认函数原型与函数定义的匹配。

备注:一个没有参数的函数原型应该写成这样:

int *func( void );
关键字void提示没有任何参数,而不是表示它有一个类型为void的参数。

7.2.2 函数的缺省认定

当程序调用一个无法见到原型的函数时,编译器便认为该函数返回一个整型值。对于那些并不返回整型值的函数,这种认定可能会引起错误。

7.3 函数的参数

两个规则:

1. 传递给函数的标量参数是传值调用的。

2. 传递给函数的数组参数在行为上就像它们是通过传址调用的那样。

程序7.2 奇偶校验

int even_parity( int value, int n_bits )
{
	int parity = 0;

	while ( n_bits > 0 ){
		parity += value & 1;
		value >>= 1;
		n_bits -= 1;
	}

	return (parity % 2) == 0;
}
这个函数的有趣特性是在它的执行过程中,它会破坏者两个参数的值。但因为参数是通过传值调用的,函数所使用是实际参数的一份拷贝。破坏这份拷贝并不会影响原先的值。

程序7.3a 整数交换:无效版本

void swap( int x, int y )
{
	int temp;

	temp = x; 
	x = y;
	y = temp;
}
为了访问调用程序的值,你必须向函数传递指向你希望修改的变量的指针。接着函数必须对指针使用简介访问操作,修改需要修改的变量。
void swap( int *x, int *y )
{
	int temp;

	temp = *x; 
	*x = *y;
	*y = temp;
}

因为函数期望接受的参数是指针,所以我们可以这样调用它:

swap ( &a, &b );
以前我曾傻乎乎写下下面这段代码,并认为是正确的:
void swap( int *x, int *y )
{
	int temp;

	temp = x; 
	x = y;
	y = temp;
}
但后面发现,x,y虽然表示的是地址,但是它们为常量,不能当作左值,即不能被赋值。

程序7.4 将一个数组设置为零

void clear_array( int array[], int n_elements )
{
	while ( n_elements > 0 ){
		array[--n_elements] = 0;
	}
}
n_elements是一个标量参数,所以它是传值调用的,在函数中修改它的值并不会影响调用程序中的对应参数。另一方面,函数确实把调用程序的数组的所有元素设置为0.数组参数的值是一个指针,下标引用实际上是对这个指针执行间接访问操作。

这个例子同时说明了另外一个特性。在声明数组参数时不指定它的长度是合法的,因为函数并不为数组元素分配内存。间接访问操作将访问调用程序中的数组元素。这样,一个单独的函数可以访问任意长度的数组。但是,函数并没有办法判断数组参数的长度,所以如果韩寒苏需要这个值,必须作为参数显示的传递给函数。

7.5 递归

递归函数就是直接调用自身的函数。

例子:把一个整数从二进制形式转换为可打印的字符形式。比如我们输入4267,则输出'4', '2', '6', '7'.

#include <stdio.h>

void binary_to_ascii( unsigned int value )
{
	unsigned int quotient;

	quotient = value / 10;
	if ( quotient != 0 ){
		binary_to_ascii(quotient);
	}
	putchar( value % 10 + '0' );
}

int main(void)
{
	binary_to_ascii(4267);
	printf("\n");

	return 0;
}
程序输出:

备注:

'0' + 0 = '0';
'1' + 0 = '1';
'2' + 0 = '2';
则类似:
'0' - '0' = 0;
'1' - '0' = 1;
'2' - '0' = 2;
7.5.2 递归与迭代(请不要滥用递归)

程序7.7a 递归计算阶乘

long factorial( int n )
{
	if ( n <= 0 ){
		return 1;
	}
	else{
		return n * factorial( n - 1 );
	}
}
程序7.7b 迭代计算阶乘
long factorial( int n )
{
	int result = 1;
	while ( n > 1 ){
		result *= n;
		n -= 1;
	}

	return result;
}
备注:如果迭代的过程够清晰,请用迭代代替递归。

程序7.8a 用递归计算斐波那契数

long fibonacci( int n )
{
	if ( n <= 2 ){
		return 1;
	}

	return fibonacci( n - 1 ) + fibonacci( n - 2 );
}
备注:计算fibonacci( n - 1 )的时候,连fibonacci( n - 2 )都计算了。

迭代的效率比递归高了几十万倍:

long fibonacci( int n )
{
	long result;
	long previous_result;
	long next_older_result;

	result = previous_result = 1;

	while( n > 2 ){
		n -= 1;
		next_older_result = previous_result;
		previous_result = result;
		result = previous_result + next_older_result;
	}

	return result;
}
7.6 可变参数列表

我们可以使用stdarg宏来编写可变参数列表:

程序7.9b 计算标量参数的平均值:正确版本

#include <stdarg.h>

float average( int n_values, ...)
{
	va_list var_arg;
	int count;
	float sum = 0;

	va_start( var_arg, n_values );

	for ( count = 0; count < n_values; count++ ){
		sum += va_arg( var_arg, int );
	}

	va_end( var_arg );

	return sum / n_values;
}
1. 类型va_list配合va_start, va_arg, va_end使用

2. va_start初始化包含两个参数:第一个参数是va_list变量的名字。第二个参数是省略号前最后一个有名字的参数。va_start的初始化过程把var_arg变量设置为指向可变参数部分的第一个参数。

3. 为了访问参数,需要使用va_arg,这个宏接受两个参数:va_list变量和参数列表中下一个参数的类型。

4. 最后,当访问完毕最后一个可变参数之后,我们需要调用va_end。


习题:

1. 

#include <stdio.h>

int hermite( int n, int x )
{
	if ( n <= 0 ){
		return 1;
	}
	else if ( 1 == n){
		return 2 * x;
	}
	else{
		return 2 * x * hermite( n - 1, x ) - 2 * ( n - 1 ) * hermite( n - 2, x );
	}
}

int main(void)
{
	printf("%d\n", hermite(3, 2));

	return 0;
}

程序输出:

2. 

#include <stdio.h>

int gcd( int m, int n )
{

	if ( 0 == n || 0 == m % n ){
		return n;
	}
	else{
		return gcd( n, m % n );
	}
}

int main(void)
{
	int m = 123;
	int n = 345;
	printf("%d\n", gcd( m, n ));

	return 0;
}

程序输出:

3.

#include <stdio.h>

int ascii_to_integer( char *string )
{
	int number = 0;
	while ( *string != '\0' ){
		if ( (*string < '0' ) || ( *string > '9' ) ){
			return 0;
		}
		number *= 10;
		number += *string - '0';
		string++;
	}

	return number;
}

int main(void)
{
	printf("%d\n", ascii_to_integer( "1234" ));
	printf("%d\n", ascii_to_integer( "12h9" ));
	printf("%d\n", ascii_to_integer( "4321" ));

	return 0;
}

程序输出:

4.

#include <stdio.h>
#include <stdarg.h>

int max_value(int nValue, ...)
{
	va_list var_arg;
	int maxValue = nValue;
	int tempValue;
	va_start(var_arg, nValue);

	while ( ( tempValue = va_arg( var_arg, int ) ) > 0 ){
		if ( tempValue > maxValue ){
			maxValue = tempValue;
		}
	}

	va_end( var_arg );

	return maxValue;
}

int main(void)
{
	printf("%d\n", max_value( 2, 4, 6, 8, 10, 1, 3, 5, 7, 9, -1 ));

	return 0;
}

程序输出:

5.

#include <stdio.h>
#include <stdarg.h>

//只实现%d,不实现类似%3d这种复杂的运算
int print_integer( char* argument, ...)
{
	va_list var_arg;
	int count = 0;
	int i = 0;
	va_start( var_arg, argument );
	while ( *argument != '\0' ){
		if ( *argument == 'd' ){
			count++;
		}
		argument++;
	}
	for ( i = 0; i < count; i++ ){
		putchar( va_arg( var_arg, int ) + '0' );
		putchar(' ');	//为了打印好看点
	}
	va_end( var_arg );

	return count;
}

int print_float( char* argument, ...)
{
	va_list var_arg;
	int count = 0;
	int i = 0;
	va_start( var_arg, argument );
	while ( *argument != '\0' ){
		if ( *argument == 'f' ){
			count++;
		}
		argument++;
	}
	for ( i = 0; i < count; i++ ){
		printf( "%3f", va_arg( var_arg, float ) );	//但va_arg依旧把参数当作int来对待,而非当作float来对待,所以这里打印有误
		putchar(' ');	//为了打印好看点
	}
	va_end( var_arg );

	return count;
}

int print_str( char *argument, ... )
{
	va_list var_arg;
	int count = 0;
	int i = 0;
	char *str;
	va_start( var_arg, argument );
	while ( *argument != '\0' ){
		if ( *argument == 's' ){
			count++;
		}
		argument++;
	}

	for ( i = 0; i < count; i++ ){
		str = va_arg( var_arg, char* );
		while ( *str != '\0' ){
			putchar(*str);
			str++;
		}
		putchar(' ');
	}

	va_end( var_arg );

	return count;
}

int print_char( char *argument, ...)
{
	va_list var_arg;
	int count = 0;
	int i = 0;
	va_start( var_arg, argument );
	while ( *argument != '\0' ){
		if ( *argument == 'c' ){
			count++;
		}
		argument++;
	}

	for ( i = 0; i < count; i++ ){
		putchar( va_arg(var_arg, char ) );
		putchar(' ');
	}

	va_end( var_arg );

	return count;
}

int main(void)
{
	print_integer("%d%d%d", 1, 2, 3);
	printf("\n");

	print_float("%f", 1.23);	//这里输出有误
	printf("\n");

	print_str("%s%s%s", "hello", "world", "python");
	printf("\n");

	print_char("%c%c%c", 'i', 'm', 'u');
	printf("\n");

	return 0;
}

程序输出:

6. 

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

#define MAX_NUMBER 128

void written_amount( unsigned int amount, char **buffer )
{
	char *num[19] = {"ONE", "TWO", "THREE", "FOUR", "FIVE", "SIX", "SEVEN", "EIGHT", "NINE", "TEN",
	"ELEVEN", "TWELVE", "THIRTEEN", "FOURTEEN", "FIFTEEN", "SIXTEEN", "SEVENTEEN", "EIGHTEEN", "NINTEEN"};
	char *largeNum[8] = {"TWENTY", "THIRTY","FOURTY","FIFTY","SIXTY","SEVENTY","EIGHTY","NINTY"};
	int tempNum = 0;
	int i = 0;
	if ( amount >= 1000 ){
		tempNum = amount / 1000;
		if ( tempNum > 100 ){
			buffer[i++] = num[tempNum / 100 - 1];
			buffer[i++] = " HUNDRED";
			tempNum %= 100;
		}
		if ( tempNum < 20 ){
			buffer[i++] = num[tempNum - 1];
		}
		else{
			buffer[i++] = largeNum[tempNum / 10 - 2];
			buffer[i++] = num[tempNum % 10 - 1];
		}
		buffer[i++] = " THOUSAND ";
	}
	amount %= 1000;
	if ( amount > 100 ){
		buffer[i++] = num[amount / 100 - 1];
		buffer[i++] = " HUNDRED ";
		amount %= 100;
	}
	if ( tempNum < 20 ){
		buffer[i++] = num[tempNum - 1];
	}
	else{
		buffer[i++] = largeNum[tempNum / 10 - 2];
		buffer[i++] = num[tempNum % 10 - 1];
	}
	buffer[i] = '\0';
}

int main(void)
{
	char *arr[MAX_NUMBER];
	int i = 0;
	written_amount(16312, arr);
	for ( i = 0; i < MAX_NUMBER; i++ ){
		if ( arr[i] == '\0' ){
			break;
		}
		printf("%s", arr[i]);
	}

	return 0;
}

程序输出:


转载于:https://my.oschina.net/voler/blog/161431

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值