【C语言基础知识——函数】

函数

文章目录

      • 函数
        • 函数是什么
        • 库函数
        • 自定义函数
        • 函数的参数
        • 函数的调用
        • 函数的嵌套使用和链式访问
        • 函数的声明和定义
        • 函数的递归

函数是什么

数学中我们常见的函数的概念,在维基百科当中对函数的定义就是:子程序

  • 在计算机科学中,子程序,是大型程序中的某部分代码,由一个或多个语句块组成,它负责完成某项特定的任务,而且相对于其他的代码开说,具有相对的独立性
  • 一般会输入参数并有返回值,提供对过程的封装和细节的隐藏,这些代码通常叫做软件库
库函数

为什么会有库函数?

  1. 我们知道在我们学习的时候,我们迫不及待的想要看到我们所打的代码的一个效果(printf)
  2. 在进行一些字符串的拷贝的时候,过于繁琐,我们就会调用(strcpy)
  3. 在需要进行一些数学的运算的时候我们需要用到平方,所以就会调用(pow)

像上面的这些描述的基础的功能,是我们在开发能够用的到的,能够支持可移植性和提高程序的效率,所以就会在C语言的基础库当中提供一些类似的函数,进而来方便程序员的开发

我们还能够看看
www.cplusplus.com
http://en.cppreference.com
MSDN 一个小的工具

常见的库函数:

IO函数
字符串操作函数:strcpy
字符操作函数:大小写字母的判断和转换
内存操作函数
时间/日期的函数
数字的函数
其他的库函数

例子:

strcpy这个函数:

#include 
#include 
int main()
{
  char arr1[]="bite!";
  char arr2[20]="######";
  strcpy(arr2,arr1);
  printf("%s",arr2);
  return 0;
}

疑问:

  1. 字符串的拷贝为什么不会保留原来的数组的字符串,我们怎么样才能够修改这个函数的逻辑,然后可以将数组的字符串可以拼接,然后就是这个函数对\0 结束符的处理方式是什么?

  2. 原字符串要比目标字符串长会不会溢出,然后溢出的话,因该怎么解决?

strcpy 函数是用来拷贝的,不可以进行字符串的拼接,接下来的学习当中应该会遇到可以实现字符串的拼接,并且还需要记得对结束符 \0 的处理方式,strcpy这个函数拷贝的时候是连着结束符 \0 一起拷贝过来的

一定会溢出,目前没有解决方案,只可以原字符串的长度小于目标的字符串,严么目标字符串的空间比原字符串的空间大,不然自己就写了一个Bug

memset函数:

#include 
#include 
#include 
int main()
{
  char arr[] = "hello world";
  memset(arr,'*',5);
  printf("%s",arr);
  //***** world
  return 0;
}

自定义函数

更加重要的就是自己定义的函数,自定义的函数和库函数是一样的,有函数名,返回值,函数的参数,但是不一样的是这些都是我们自己来设计,给我们自己很大的发展空间

ret_type fun_name(para1,*)
{
  statement; //语句项
}

ret_type  //返回的类型
fun_name  //函数名
para1    //函数的参数

// 书写一个较大值的函数

#include 

int getMAx(int x,int y)
{
  if(x>y)
  return x;
  else
  return y;
}
int main()
{
  int x = 10;
  int y = 30;
  int z = getMax(x,y);
  printf("max = %d\n",z);
  return 0;
}

一个函数不需要返回类型,void的意思是这个函数没有返回值,void是空,无的意思,他代表没有返回值

书写一个交换数字的函数不使用指针不可以的,和JavaScript当中一样,如果没有使用指针书写的话,就会存在主函数里面的变量开辟了两个空间,函数里面开辟了两个空间,他们所指的变量的地址是不同的,函数对里面空间的两个变量进行了交换,但是不能对外面的变量进行交换,因为他们的地址不同,但是如果使用指针就可以将变量的地址传进去,修改变量本身的值,这个时候操作的空间是同一个。但是高级语言是不可以访问内存地址的

void swap1(int x,int y)
{
  int temp = 0;
  x = y;
  y = temp;

}

void swap2(int *px,int *py)
{
  int temp = 0;
  temp = *px;
  *px = *py;
  *py = temp;
}

int main()
{
  int num1 = 10;
  int num2 = 20;
  swap1(num1,num2);
  printf("Swap1: num1 = %d,num2 = %d\n",num1,num2);
  swap2(&num1,&num2);
  printf("Swap2: num1 = %d,num2 = %d\n",num1,num2);
  return 0;
}

函数的参数

实际参数( 实参 )

真实传给函数的参数,叫做实参,实参可以是:常量,变量,表达式,函数等,无论实参是何种类型的量,在进行函数的调用的时候他们必须有确定的值,以便把这些值传给形参

形式参数 ( 形参 )

形式参数是指函数名后括号中的变量,因为形式参数只有在函数被调用的过程中才实例化( 分配内存单元 ),所以叫做形式参数,形式参数当函数调用完成后就自动销毁了,因此形式参数只在函数中有效。

在上面的Swap1和Swap2函数中的参数x,y,px,py都是形式参数,在main函数里面中Swap1的num1,num2和传给Swap2函数的&num1,&num2是实际参数。

我们可以简单的理解为:形参实例化之后就相当于实参的一份临时拷贝

函数的调用
  1. 传值调用:

函数的形参和实参分别占有不同额内存块,对形参的修改不会影响实参

  1. 传址调用:
  • 传址调用是把函数外部创建变量的内存地址传递给函数参数的一种函数的调用的方式
  • 这种传参的方式可以让函数和函数外边的变量真正建立起联系,也就是说函数的内部可以直接操作函数外部的变量

练习:

  1. 写一个函数可以判断一个数是不是素数
  2. 写一个函数判断一年是不是闰年
  3. 写一个函数,实现一个整型有序的数组的二分查找
  4. 写一个函数,每调用一次这个函数,就会将num的值加1
//1. 写一个函数可以判断一个数是不是素数
#include 
int Judge(int n)
{
  for(int i = 2;i < n;i++)
  if(n%i == 0)
  printf("这个数是一个素数\n");
  else 
  printf("这个数不是一个素数\n");
}


int main()
{
  int n = 0;
  printf("please input a number\n");
  scanf("%d",&n);
  jadge(n);
  return 0;
}


//输出1~100 之间的素数

#include 

int Jadgement(int n)
{
  for(int i = 2; i < n; i++)
  {
    if( n%i ==0 )
    return 0;
  }
  return 1;
}

int main()
{
  int i = 0;

  for(Jadgement(i) == 1)
  {
  printf("%d",i);
  }
  return 0;
}

如果需要是设计一个函数在某个区间内求一些闰年,就可以参照上面的函数,然后就可以设计出来是一样的思路,但是现在自己大的代码是太不行了,被说了,哈哈哈,不能直接打印 是不是素数或者闰年,返回值1或者0就可以了,所以要转换一个思路,人家需要什么代码就书写什么代码给人家,不然人家就不会用你的代码和你写的函数

//2. 写一个函数判断一年是不是闰年

#include 

int Leapyear(int n)
{
  if( (n%4 == 0 && n%100 != 0) || (n%400 == 0) )
  printf("这一年是闰年\n");
  else
  printf("这一年不是闰年\n");

}

int main()
{
  int year = 0;
  printf("please input a year\n");
  scanf("%d",&year);
  Leapyear(year);
  return 0;
}


#include 

int is_leap_year(int y)
{
  if((y%4 == 0 && y%100 != 0) || (y%400 ==0))
  return 1;
}


}

int main()
{
  int year = 1000;
  for(year = 1000; year < 2000;year++)
  {
    if(is_leap_year == 1)
    {
    printf("%d",year);
    }
  }
  return 0;
}

//3. 写一个函数,实现一个整型有序的数组的二分查找

#include 
#include 

int Binarysearch(int arr[])
{
  int size = strlen(arr) - 1;
  int left = 0;
  int right = size - 1;
  int x = 0;
  printf("请输入你想要查找的数字\n");
  scanf("%d",&x);
  int mid = 0;
  while( left <= right )
  {
    mid = (left  + right)/2;
    if(arr[mid] < x)
      left = mid + 1;
    else if(arr[mid] > x )
      right = mid -1;
    else 
      printf("这个数找到了\n");
  }
  if(left > right )
  printf("找不到这个数\n");
  
}

int main()
{
  int arr[]={1,2,3,4,5,6,7,8,9,10,11,12,13,14};
  Binarysearch(arr[20]);
  return 0;
}


疑问:怎么实现代码的循环,并且怎么书写一个代码可以实现计算调用函数的次数,我本身想的就是可以在函数当中写一个num,然后计数,但是函数现在不能循环起来,就不知道应该怎么做到程序循环并且能够完成调用的计数

//4. 写一个函数,每调用一次这个函数,就会将num的值加1
//这个题其实是考相关的传址的使用!!
#include 
int Max(int x,int y)
{
  if(x > y)
  return x;
  else if(x < y)
  return y ;
  else 
  return 0; 
}

int main()
{
  int x = 0;
  int y = 0;
  printf("please input two number\n");
  scanf("%d,%d",&x,&y);
  Max(x,y);
  return 0;
}

函数的嵌套使用和链式访问

函数和函数之间是可以有机的结合在一起的

  1. 函数的嵌套

#include 
void new_line()
{
  printf("hehe\n");
}

void three_line()
{
  int i = 0;
  for( i = 0; i<3; i ++ )
  {
    new_line();
  }
}

int main()
{
  three_line();
  return 0;
}
  1. 链式访问

把一个函数的返回值作为另一个函数的参数


#include 
#include 
int main()
{
  char arr[20] = "hello!";
  int ret = strlen(strcat(arr,"bit")); //这里介绍一下strlen函数
  printf("%d\n",ret);
  return 0;
}


#include 
int main()
{
  printf("%d",printf("%d",printf("%d",43)));
  return 0;
}
//这个函数打印出来的是4321,因为printf这个函数返回的打印的长度,先打印43,然后就是长度。
函数的声明和定义

函数的声明:

  1. 告诉编译器有一个函数叫做什么,参数是什么,返回类型是什么
  2. 函数的声明一般出现在函数的使用之前,要满足先声明后使用的原则
  3. 函数的声明一般放在头文件中

函数的定义:

函数的定义是指函数的具体实现,交代函数的功能实现

//函数的声明
int Add(int ,int);

int main()
{
  int a = 10;
  int b = 20;
  int sum = 0;
  sum = Add(a,b);
  printf("%d\n",sum);
  return 0;
}

//函数的定义
int Add(int x,int y)
{
  int z = x + y;
  return z;
}


函数的递归

https://stackoverflow.com程序员的知乎

  1. 什么是递归?

程序调用自身的编程技巧叫做递归,递归作为一种算法在程序设计语言中广泛应用,一个过程或一个函数在其定义或声说明中有直接的或间接调用自身的一种方法,他通常把一个大型复杂的问题层层转换为一个与原问题相似规模较小的问题求解,递归策略只需少量的程序就可描述出解题过程所需要的多次重复计算,大大减少了程序的代码量,递归的主要思考方式在于:把事化小

  1. 递归的必要两个条件:
  • 存在限制条件,当满足这个限制条件的时候,递归边不在继续
  • 每次递归调用之后越来越接近这个限制条件
  1. 练习:

练习1:接受一个整型值(无符号),按照顺序打印他的每一位,例如:输入1234,输出1 2 3 4

//求一个区间的素数
//问题:怎么解决重复出现的问题,出现了重复出现得问题
#include 
int main()
{
  int number = 0;
  printf(""please input a number\n);
  scanf("%d",&number);
  for( ; i<=number ;i++)
  {
    for( ; j < i ;j++)
    if(i%j != 0)
    printf("%d\t",i);
  }
}

#include 
void resolve(int);
int main()
{
  int number = 0;
  printf("please input a number\n");
  scanf("%d",&number);
  resolve(number);
  return 0;
}
void resolve(int x)
{
  if(x>9)
  {
    resolve(x/10);
  }
  printf("%d\t",x%10);
}

练习2:编写函数不允许创建临时变量,求字符串的长度

#include 
#include 

int my_strlen(char*  str)
{
    int count = 0;
    while( *str != '\0')
    {
      count++;
      str++;
    } 
  return count;
}

int main()
{
  char arr[] = "bit";
  //int len = strlen(arr);       //求字符串的长度
  //printf("%d\n",len);

  int len = my_strlen(arr);
  printf("%d\n",len);
  //arr是数组,数组传参,传过去的不是整个数组,而是第一个元素的地址。

}


//不使用临时变量,就需要使用递归函数来解决这和个问题
现在进行大事化小

my_strlen(“bit”);
1+my_strlen(“it”);
1+1+my_strlen(“t”);

#include 
#include 
int my_strlen(char* str)
{
  if( *str != '\0' )
  return 1+my_strlen(str+1);
  else
  return 0;
}

int main()
{
  char arr[] = "bit";
  int len = my_strlen(arr);
  printf("%d\n",len);
  return 0;

  
}


递归与迭代

练习3:求n的阶乘(不考虑溢出)

#include 

int factorial(int n)
{
  if(n <= 1)
  return 1
  else
  return n*factorial(n-1);
}

int main()
{
  int number = 0;
  printf("please input a number\n");
  scanf("%d",&number);
  int num = factorial(number);
  printf("%d\n",num);  
  return 0;
}

阶乘和循环其实可以互换!

练习4:求第n个斐波那契数(不考虑溢出)

#include 

int Fit(int n)
{
  if(n>0)
  {
   if( 0 < n <= 2)
   return 1;
   else
   return Fit(n-1) + Fit(n-2);
  } 
}

int main()
{
  int num = 0;
  printf("please input a number\n");
  scanf("%d",&num);
  int sum = Fit(num);
  return 0;
}

但是我们发现了一些问题:

  1. 如果需要计算第50个斐波那契数的时候,他的计算过程太慢
  2. 如果需要计算10000的阶乘的时候,就会出现栈溢出

递归和循环都可以解决问题的时候,就需要对比那一个更加好多方面考虑,考虑他的计算时间还有就是考虑他是否溢出

我们如何解决问题呢?

  1. 将递归改为非递归
  2. 使用static对象替代nostatic局部对象,在递归函数的设计中,可以使用static对象代替nostatic局部对象(即找对象),这不仅可以减少每次递归调用和返回时产生和释放nostatic对象的对象的开销,而且static对象还可以保存递归调用的中间状态,并且可为各个调用层所调用。

在斐波那契数列的计算当中,可以写一个程序关于使用三个新的变量不断的循环计算,可以大大的加快程序的运行速度

#include 

int Fac(int n)
{
  int a = 1;
  int b = 1;
  int c = 1;
  while(n>2)
  {
    c = a+b;
    a = b;
    b = c;
    n--;
  }
  return c;
}

int main()
{
  int num = 0;
  printf("please input a number\n");
  scanf("%d",&num);
  int sum = Fac(num);
  printf("%d",sum);
  return 0;
}

提示:

  • 许多问题是以递归的形式及逆行解释的,这只是因为它比非递归的形式更加清晰
  • 但是这些问题的迭代实现往往比递归实现效率更高,虽然代码的可读性稍微差些
  • 当一个问题相当复杂,难以用迭代实现时,此时递归实现的简洁性可以弥补他运行所带来的开销

加强训练:

  1. 汉诺塔问题
  2. 青蛙跳台阶问题

你可能感兴趣的:(c++,c语言)