C与指针——函数(一)

知识点简述

1.函数的参数

1.C的所有参数均以“传值调用”方法进行传递,即函数将获得参数值的一份拷贝。传递给函数的标量参数是传值调用的。
若被传递的参数是一个数组名,并在函数中使用下标引用该数组的参数。函数将访问调用程序的数组元素,数组不会被复制,此为“传址调用”。传递给函数的数组参数行为上像传址调用。
2.形式参数列表包括变量名和它们的类型声明;代码块包含了局部变量的声明和函数调用时需要执行的语句。

EX1.在数组中寻找一个整型值

#include<stdio.h>

int *find_int(int key,int array[],int array_len);
int main()
{
	int k=9;
	int a[]={1,8,6,7,9,3,2};
	int al=8;
	int *t=0;
	t=find_int(k,a,al);
	printf("%d",t);
	return 0;
}
int *find_int(int key,int array[],int array_len)
{
	int i;
	
	//对于数组中的每个位置
	for(i=0;i<array_len;i+=1)
	//检查这个位置的值是否为需要查找的值
	if(array[i]==key)
	return &array[i];
	
	return NULL; 
}

运行结果:
在这里插入图片描述
分析:
当key=1时,程序运行结果为6487584
当key=8时,程序运行结果为6487588
当key=6时,程序运行结果为6487592
显而易见,一个int型数据占四个字节,而6487600为特定整型值9在机器中的存储位置。学过汇编或者正在学汇编的同学,对这块的知识点会更易理解些。

EX2.奇偶效验

#include<stdio.h>
int even_parity(int value,int n_bits);

int main(){
	int v=15; 
	int n=8,t;
	t=even_parity(v,n);
	printf("%d",t);
	return 0;
}

//对值进行偶校验 
int even_parity(int value,int n_bits)
{
	int parity=0;
	
	//计数值中值为1的位的个数
	while(n_bits>0){
		parity+=value&1;
		value>>=1;
		n_bits-=1;
	} 
	
	//若计数器的最低位是0,返回TURE(表示1的位数为偶数个)
	return (parity%2)==0; 
 } 

运行结果:
在这里插入图片描述
分析: 在这个函数中,第一个参数在二进制位模式中判断1的个数是否为偶数,第二个参数指定第一个参数中有效位的数目。此函数在执行过程中会破坏这两个参数的值,但因参数通过传值调用,所以函数所用的值为实际参数的一份拷贝,破坏这份拷贝并不会影响原先的值。

EX3.整数交换

#include<stdio.h>
void swap(int *x,int *y);
int main()
{
	int a=5;
	int b=9;
	swap(&a,&b); //注意调用函数时,应该使用取地址符 
	printf("%d %d",a,b);
	return 0;
}
void swap(int *x,int*y)
{
	int temp;
	temp=*x;
	*x=*y;
	*y=temp;
}

运行结果:
在这里插入图片描述
因为函数期望接受的参数是指针,所以我们应该在调用函数的时候使用取地址符号。

EX4.将一个数组设置为零

#include<stdio.h>
void clear_array(int array[],int n_elements);
int main()
{
	int a[]={1,5,6,7,9};
	int n=0;
	printf("删除前数组为:");
	for(int i=0;i<5;i++)
	{
	 printf("%d ",a[i]);
	}
	printf("\n");
	printf("删除后数组为:");
	for(n=5;n>0;n--) //注意这里删除后数组输出的程序编写
	{
	 clear_array(a,n);
	 printf("%d ",a[n]);
	}
	return 0;
}
void clear_array(int array[],int n_elements)
{
	//从数组中最后一个元素开始,逐个清除数组中的所有元素。
	//注意前缀自减避免了越出数组边界的可能性
	while(n_elements>0)
	array[--n_elements]=0; 
}

运行结果:
在这里插入图片描述
在这里补充输出数组的三种方式:

//下标法 
#include<stdio.h>
int main()
{
  int a[]={1,2,3,4,5};
  for(int i=0;i<5;i++){
  	printf("%d ",a[i]);
  }
  return 0;
}

//数组名计算法
#include<stdio.h>
int main()
{
  int a[]={1,2,3,4,5};
  int *p=a;
  for(int i=0;i<5;i++){
  	printf("%d ",*(p+i));
  }
  return 0;
}

//指针变量法
#include<stdio.h>
int main()
{
  int a[]={1,2,3,4,5};
  int *p;
  for(p=a;p<(a+5);p++){
  	printf("%d ",*p);
  }
  return 0;
} 

运行结果均为:
在这里插入图片描述

2.ADT和黑盒

C可以用于设计和实现抽象数据类型(ADT,abstract data type),因为它可以限制函数和数据定义的作用域。这个技巧也被称为黑盒(black box) 设计。

ADT基本思想: 模块具有功能说明和接口说明,前者说明模块所执行的任务,后者定义模块的使用。但模块用户并不需要知道模块实现的任何细节,除定义好的接口之外,用户不能以任何方式访问模块。

EX5.地址列表模块

//用于维护一个地址列表的抽象数据类型 
#include <stdio.h>
#include <string.h>

/*
** 地址列表模块的声明 
** 数据特征
** 各种数据的最大长度(包括结尾的NUL字节)和地址的最大数量 
*/ 
#define	NAME_LENGTH	30		//允许出现的最长名字
#define	ADDR_LENGTH	100		//允许出现的最长地址 
#define	PHONE_LENGTH	11		//允许出现的最长电话号码 
#define	MAX_ADDRESSES	1000		//允许出现的最多地址个数 

//接口函数。给出一个名字,查找对应的地址。 
char const *lookup_address( char const *name );

//给出一个名字,查找对应的电话号码 
char const *lookup_phone( char const *name );

//每个地址的三个部分,分别保存于三个数组的对应元素中 
static	char	name[MAX_ADDRESSES][NAME_LENGTH];
static	char	address[MAX_ADDRESSES][ADDR_LENGTH];
static	char	phone[MAX_ADDRESSES][PHONE_LENGTH];

/*
**	这个函数在数组中查找一个名字并返回查找到的位置的下标 
**	如果这个名字在数组中并不存在,函数返回-1 
*/
static int find_entry( char const *name_to_find )
{
	int	entry;

	for( entry = 0; entry < MAX_ADDRESSES; entry += 1 )
		if( strcmp( name_to_find, name[ entry ] ) == 0 )
			return entry;

	return -1;
}

/*
**	给定一个名字,查找并返回对应的地址 
**	如果名字没有找到,函数返回一个NULL指针
*/
char const *lookup_address( char const *name )
{
	int	entry;

	entry = find_entry( name );
	if( entry == -1 )
		return NULL;
	else
		return address[ entry ];
}

/*
**	给定一个名字,查找并返回对应的电话号码 
**	如果名字没有找到,函数返回一个NULL指针
*/
char const *lookup_phone( char const *name )
{
	int	entry;

	entry = find_entry( name );
	if( entry == -1 )
		return NULL;
	else
		return phone[ entry ];
}

数组赋值可以通过以下几种方法:
1、定义的时候直接赋初值(用{});
2、用循环语句依次给数组各元素赋值;
3、将数组放在一个结构体里面,通过结构体的引用给数组整体赋值;(数组本身不能直接整体赋值)

3.递归

递归函数就是直接或间接调用自身的函数。C通过运行时堆栈支持递归函数的实现。
在一个递归函数的执行过程中,当函数被调用时,它的变量的空间是创建于运行时堆栈上的。
以前调用的函数的变量仍保留在堆栈上,但由于被新函数的变量掩盖,所以不能被访问。
给字符集里连续的数字加上字符常量‘0’,其结果便是对应于这个数字的ASCII字符。

EX6.将二进制整数转换为字符

/*
** 接受一个整型值(无符号) 
** 把它转换为字符串并打印它,前导零被删除 
*/
#include <stdio.h>
void binary_to_ascii( unsigned int value );
int main(){
	unsigned int v=4267;
	binary_to_ascii(v);
	return 0;
} 
void binary_to_ascii( unsigned int value )
{
	unsigned int quotient;

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

运行结果:
在这里插入图片描述
递归所需要的两个特性:
1.存在限制条件,当符合这个条件时,递归便不再继续;
2.每次递归调用之后越来越接近这个限制条件。

EX7.递归/迭代计算阶乘

//递归计算n的阶乘
#include<stdio.h>
long factorial( int n );
int main(){
	printf("%d",factorial(5));
	return 0;
}
long factorial( int n )
{
	if( n <= 0 )
		return 1;
	else
		return n*factorial( n - 1 );
}

//迭代计算n的阶乘
#include<stdio.h>
long factorial( int n );
int main(){
	printf("%d",factorial(5));
	return 0;
}
long factorial( int n )
{
	int	result = 1;
	while( n > 1 ){
		result *= n;
		n -= 1;
	}
	return result;
}

运行结果:
在这里插入图片描述
EX8.用递归/迭代计算斐波那契数

斐波那契数列(Fibonacci sequence),又称黄金分割数列、因数学家列昂纳多·斐波那契(Leonardoda Fibonacci)以兔子繁殖为例子而引入,故又称为“兔子数列”。
Fibonacci sequence指的是这样一个数列 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233,377,610,987,1597,2584,4181,6765,10946,17711,28657,46368…这个数列从第3项开始,每一项都等于前两项之和。
在数学上,斐波那契数列以如下被以递推的方法定义:F(1)=1,F(2)=1, F(n)=F(n-1)+F(n-2)(n>=3,n∈N*)

#include<stdio.h>

long fibonacci( int n );
int main(){
	printf("%d",fibonacci(10));
	return 0;
}
//递归计算第n个斐波那契数的值。
long fibonacci( int n )
{
	if( n <= 2 )
		return 1;
	return fibonacci( n - 1 ) + fibonacci( n - 2 );
}

//迭代计算第n个斐波那契数的值。
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;
}

运行结果:
在这里插入图片描述
对于Fibonacci数的求解问题,递归的方法更倾向于Fibonacci数的抽象定义,但如果使用一个简单的循环,例如第二个程序的迭代方法,则计算的效率会的大大提升。
所以,当我们在使用递归方式实现一个函数时,最好先衡量一下使用递归所带来的好处是否抵得过它的代价。

这一部分的知识大多都是之前老师重点强调过的,当然依旧涉及指针的知识,在这里便会与最近学的汇编联系起来,对于指针的源地址和偏移地址也有了更进一步的理解与体会。
例5地址列表模块的程序,还未编写主程序进行实现,自己也有敲出主程序,但总觉得对于完全实现这个函数的功能而言还是含有略多的偏差,所以在这里,看懂这个关于“抽象数据类型”以及“黑盒子”的含义与程序即可,等继续后面的知识点时,如遇更好的实现方法,会重新补上。

除了自身的病患或亲友离去的痛苦是真实的,其他的痛苦都是你自己的价值观带给你的。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值