知识点简述
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地址列表模块的程序,还未编写主程序进行实现,自己也有敲出主程序,但总觉得对于完全实现这个函数的功能而言还是含有略多的偏差,所以在这里,看懂这个关于“抽象数据类型”以及“黑盒子”的含义与程序即可,等继续后面的知识点时,如遇更好的实现方法,会重新补上。
除了自身的病患或亲友离去的痛苦是真实的,其他的痛苦都是你自己的价值观带给你的。