文档章节

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

fzyz_sb
 fzyz_sb
发布于 2013/09/15 11:19
字数 2804
阅读 100
收藏 0
点赞 0
评论 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
粉丝 404
博文 209
码字总数 447144
作品 0
武汉
程序员
《程序员面试宝典》精华 编程语言部分

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

modernizr ⋅ 2014/08/11 ⋅ 2

第七章 右左法则----复杂指针解析

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

北极心 ⋅ 2016/08/11 ⋅ 0

阅读《C陷阱与缺陷》的知识增量

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

modernizr ⋅ 2014/08/11 ⋅ 2

阅读《C陷阱与缺陷》的知识增量

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

modernizr ⋅ 2014/06/30 ⋅ 7

C语言-第七章、用指针实现程序的灵活设计

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

南风末 ⋅ 2016/11/04 ⋅ 0

转:再再再论指针

  一直以来想把2005年写的《再再论指针》修改一下,因为经过了这么多年,对C/C++的理解与05年相比又有了一些进展。但公司的工作一直很忙,没有时间进行修改工作。直到10月初的长假...

IMGTN ⋅ 2012/06/20 ⋅ 1

入职学习(2)--一个程序员的成长史(22)

看了代是雄对这几个问题的回复之后,唐师傅叫代是雄先熟悉一下办公的电脑及一些办事流程,他要找一些资料作为培训计划中的材料。 代是雄先到公司的IT网站上去逛了一下,发现上面的东西相当的...

zhouzxi ⋅ 2017/01/12 ⋅ 0

C++快速入门

只学过Java的我最近笔试接触到各种C++的题目,于是稍微恶补了一下C++的基础部分,以下内容是基于《21天学通C++》的部分读书笔记,按照章节写的。 第二章 C++程序的组成部分 ①#include 预处理...

waffle930 ⋅ 2016/10/02 ⋅ 0

Head First C学习日志 第七章 高级函数 创建函数指针

在c语言中,函数名也是指针变量 函数名是指向函数的指针,当你创建一个叫test的函数的同时,也会创建一个test指针变量,变量中保存了函数的地址。该变量位于常量段。 如何创建函数指针? 假设...

AlexTuan ⋅ 2016/02/29 ⋅ 0

做游戏,学编程(C语言) 网易云课堂MOOC视频

应一些同学的要求,把这学期上C语言编程课的讲课视频录制剪辑,上传到网易云课堂,感兴趣的朋友可以在线观看,欢迎多提宝贵意见。 MOOC视频链接:http://study.163.com/course/introduction....

童晶 ⋅ 2017/11/07 ⋅ 0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

从 Confluence 5.3 及其早期版本中恢复空间

如果你需要从 Confluence 5.3 及其早期版本中的导出文件恢复到晚于 Confluence 5.3 的 Confluence 中的话。你可以使用临时的 Confluence 空间安装,然后将这个 Confluence 安装实例升级到你现...

honeymose ⋅ 今天 ⋅ 0

用ZBLOG2.3博客写读书笔记网站能创造今日头条的辉煌吗?

最近两年,著名的自媒体网站今日头条可以说是火得一塌糊涂,虽然从目前来看也遇到了一点瓶颈,毕竟发展到了一定的规模,继续增长就更加难了,但如今的今日头条规模和流量已经非常大了。 我们...

原创小博客 ⋅ 今天 ⋅ 0

MyBatis四大核心概念

本文讲解 MyBatis 四大核心概念(SqlSessionFactoryBuilder、SqlSessionFactory、SqlSession、Mapper)。 MyBatis 作为互联网数据库映射工具界的“上古神器”,训有四大“神兽”,谓之:Sql...

waylau ⋅ 今天 ⋅ 0

以太坊java开发包web3j简介

web3j(org.web3j)是Java版本的以太坊JSON RPC接口协议封装实现,如果需要将你的Java应用或安卓应用接入以太坊,或者希望用java开发一个钱包应用,那么用web3j就对了。 web3j的功能相当完整...

汇智网教程 ⋅ 今天 ⋅ 0

2个线程交替打印100以内的数字

重点提示: 线程的本质上只是一个壳子,真正的逻辑其实在“竞态条件”中。 举个例子,比如本题中的打印,那么在竞态条件中,我只需要一个方法即可; 假如我的需求是2个线程,一个+1,一个-1,...

Germmy ⋅ 今天 ⋅ 0

Springboot2 之 Spring Data Redis 实现消息队列——发布/订阅模式

一般来说,消息队列有两种场景,一种是发布者订阅者模式,一种是生产者消费者模式,这里利用redis消息“发布/订阅”来简单实现订阅者模式。 实现之前先过过 redis 发布订阅的一些基础概念和操...

Simonton ⋅ 今天 ⋅ 0

error:Could not find gradle

一.更新Android Studio后打开Project,报如下错误: Error: Could not find com.android.tools.build:gradle:2.2.1. Searched in the following locations: file:/D:/software/android/andro......

Yao--靠自己 ⋅ 昨天 ⋅ 0

Spring boot 项目打包及引入本地jar包

Spring Boot 项目打包以及引入本地Jar包 [TOC] 上篇文章提到 Maven 项目添加本地jar包的三种方式 ,本篇文章记录下在实际项目中的应用。 spring boot 打包方式 我们知道,传统应用可以将程序...

Os_yxguang ⋅ 昨天 ⋅ 0

常见数据结构(二)-树(二叉树,红黑树,B树)

本文介绍数据结构中几种常见的树:二分查找树,2-3树,红黑树,B树 写在前面 本文所有图片均截图自coursera上普林斯顿的课程《Algorithms, Part I》中的Slides 相关命题的证明可参考《算法(第...

浮躁的码农 ⋅ 昨天 ⋅ 0

android -------- 混淆打包报错 (warning - InnerClass ...)

最近做Android混淆打包遇到一些问题,Android Sdutio 3.1 版本打包的 错误如下: Android studio warning - InnerClass annotations are missing corresponding EnclosingMember annotation......

切切歆语 ⋅ 昨天 ⋅ 0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

返回顶部
顶部