文档章节

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

fzyz_sb
 fzyz_sb
发布于 2013/09/15 11:19
字数 2804
阅读 100
收藏 0

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

程序输出:


© 著作权归作者所有

共有 人打赏支持
fzyz_sb
粉丝 408
博文 209
码字总数 447144
作品 0
武汉
程序员
《程序员面试宝典》精华 编程语言部分

《程序员面试宝典》精华 编程语言部分 正所谓取其精华,去其糟粕。本文谨记录下《程序员面试宝典》一些关键的知识点、易错点,对于一些虽然重要但书中没有解释清楚的地方不做记录。当然这里的...

modernizr
2014/08/11
410
2
第七章 右左法则----复杂指针解析

首先看看如下一个声明: 这是一个会让初学者感到头晕目眩、感到恐惧的函数指针声明。在熟练掌握C/C++的声明语法之前,不学习一定的规则,想理解好这类复杂声明是比较困难的。 C/C++所有复杂的...

北极心
2016/08/11
39
0
C语言-第七章、用指针实现程序的灵活设计

7_1 指针的基本概念 _1.1 地址与指针 C语言中通常把地址成为指针。 _1.2 内存单元的指针与内存单元的内容 _1.3 变量的指针 变量的指针就是变量的地址 _1.4 直接访问与间接访问 7_2 指向变量的...

南风末
2016/11/04
11
0
阅读《C陷阱与缺陷》的知识增量

看完《C陷阱与缺陷》,忍不住要重新翻一下,记录一下与自己的惯性思维不符合的地方。记录的是知识的增量,是这几天的流量,而不是存量。 这本书是在ASCI C/C89订制之前写的,有些地方有疏漏。...

modernizr
2014/08/11
368
2
阅读《C陷阱与缺陷》的知识增量

看完《C陷阱与缺陷》,忍不住要重新翻一下,记录一下与自己的惯性思维不符合的地方。记录的是知识的增量,是这几天的流量,而不是存量。 这本书是在ASCI C/C89订制之前写的,有些地方有疏漏。...

modernizr
2014/06/30
613
7

没有更多内容

加载失败,请刷新页面

加载更多

Univalsal_ImageLoader源码结构与创建者模式 初步小结

最近在回归看Univalsal_ImageLoader源码,本想自己也实现试试写一个,看源码是为了学习看能否使用,助于自己可以写出有自己逻辑结构的代码。 首先我们初始化ImageLoader的配置初始化的时候,...

DannyCoder
52分钟前
0
0
计算卷积神经网络浮点数运算量

前言 本文主要是介绍了,给定一个卷积神经网络的配置之后,如何大概估算它的浮点数运算量。 相关代码:CalFlops,基于MXNet框架的 Scala 接口实现的一个计算MXNet网络模型运算量的demo。 正文...

Ldpe2G
今天
3
0
Sql语言与MySql数据库

1. 数据库简介 1. 数据库,就是存储数据的仓库,只能通过sql语言来访问,数据库也是一个文件系统。通常,MySQL、Oracle等数据库,也被称为关系型数据库,其保存的不仅仅只是数据,还包括数据...

江左煤郎
今天
3
0
IDEA 取消自动import .*

打开设置 > Editor > Code Style > Java > Scheme Default > Imports ① 将 Class count to use import with "*" 改为 99 (导入同一个包的类超过这个数值自动变为 * ) ② 将 Names count ......

乔老哥
今天
4
0
PostGIS学习笔记(开篇)

PostGIS事实上算是笔者开始写博客的第一篇内容。而事实上那篇博文的内容并不丰富,笔者对PostGIS的了解仍然不多,然而17年在OSGeo课程学习时对PostGIS又有了进一步了解,并逐步发现它的强大。...

胖胖雕
今天
4
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部