C语言-基础入门-学习笔记(6):函数
一. 概述
对于非常长的程序,由于分模块很多,所以需要对程序分节、分章甚至分篇处理。
1. 模块化编程
所谓模块化编程,是指将程序划分为一系列功能相互独立的模块,再以模块为单元进行开发,最后合并到主程序的编程方法。
范例1
下面将学习笔记(5)中的例子进行模块化改写:
原版为:
#include <stdio.h>
#define SIZE 4
int main(void){
int i,j;
int array[SIZE][SIZE] = {{0,1,2,3},
{4,5,6,7},
{8,9,10,11},
{12,13,14,15}};
int transpose[SIZE][SIZE] = {0};
printf("primary array:\n");
for(i=0;i<SIZE;i++){
for(j=0;j<SIZE;j++){
printf("\t%-4d",array[i][j]);
}
printf("\n");
}
/*进行转置处理*/
for(i=0;i<SIZE;i++){
for(j=0;j<SIZE;j++){
transpose[j][i] = array[i][j];
}
}
printf("transpose array:\n");
for(i=0;i<SIZE;i++){
for(j=0;j<SIZE;j++){
printf("\t%-4d",transpose[i][j]);
}
printf("\n");
}
return 0;
}
模块化后:
#include <stdio.h>
#define SIZE 4
/*打印数组*/
void print_array(int array[SIZE][SIZE]){
int i,j;
for(i=0;i<SIZE;i++){
for(j=0;j<SIZE;j++){
printf("\t%-4d",array[i][j]);
}
printf("\n");
}
}
/*矩阵转置*/
void transpose_array(int array[SIZE][SIZE],int transpose[SIZE][SIZE]){
int i,j;
for(i=0;i<SIZE;i++){
for(j=0;j<SIZE;j++){
transpose[j][i] = array[i][j];
}
}
}
int main(void){
int array[SIZE][SIZE] = {{0,1,2,3},
{4,5,6,7},
{8,9,10,11},
{12,13,14,15}};
int transpose[SIZE][SIZE] = {0};
printf("primary array:\n");
print_array(array);
transpose_array(array,transpose);
printf("\nFinal array:\n");
print_array(transpose);
return 0;
}
2. 定义函数
函数的定义由函数声明和函数体两部分组成。函数声明又可以分为:由函数返回值类型、函数名、参数列表、函数体,以及函数操作符5个部分组成。标准形式如下:
函数返回值类型 函数名(参数类型1 参数名1,参数类型2 参数名2,……){
/*函数体*/
变量定义;
函数操作;
return语句;
}
3. 调用函数
函数调用表达式由函数名、函数操作符和逗号表达式三部分组成。表示方式如下:
函数名(参数表达式1,参数表达式2,……)
函数调用语句中的参数表达式,由于具有实际值,也被称为“实际参数”,简称“实参”;与此同时,函数声明和函数定义中的参数,在函数被调用前,不占用内存中的存储空间,有形无实,所以被称为“形式参数”,简称为“形参”。
函数调用方式分为三种:
- 单独成句
print_array(array);
transpose_array(array,transpose);
- 子表达式
b = square(5);
print_array(array),transpose_array(array,transpose)
- 函数实参
c = max(a,min(a,b));
d = max(a,min(a,b));
范例2
#include <stdio.h>
void print_data(const int a,const int b){
printf("x = %d,y = %d\n",a,b);
}
int sum(const int a,const int b){
int tmp;
tmp = a + b;
return tmp;
}
int dif(const int a,const int b){
int tmp;
tmp = a - b;
return tmp;
}
int pro(const int a,const int b){
int tmp;
tmp = a * b;
return tmp;
}
int main(void){
const int x = 3;
const int y = 9;
int tmp;
print_data(x,y);
tmp = sum(x,y);
printf("x + y = %d\n",tmp);
tmp = dif(x,y);
printf("x - y = %d\n",tmp);
tmp = pro(x,dif(x,y));
printf("x * (x - y) = %d\n",tmp);
return 0;
}
对程序进行优化后:
范例3
#include <stdio.h>
void print_data(const int a,const int b){
printf("x = %d,y = %d\n",a,b);
}
int sum(const int a,const int b){
return a + b;
}
int dif(const int a,const int b){
return a - b;
}
int pro(const int a,const int b){
return a * b;
}
int main(void){
const int x = 3;
const int y = 9;
print_data(x,y);
printf("x + y = %d\n",sum(x,y));
printf("x - y = %d\n",dif(x,y));
printf("x * (x - y) = %d\n",pro(x,dif(x,y)));
return 0;
}
二. 函数声明
1. 声明的形式
完整的函数声明定义了一个功能模块的接口,可以作为单独语句使用,形式如下:
函数值类型 函数名(参数类型1 形参1,参数类型2 形参2,……);
void print_data(const int a,const int b)
int sum(const int a,const int b)
函数可以视为一个变量,函数声明即变量声明,函数名即变量名。
2. 声明与定义
函数声明确定了一个函数的接口,告诉编译器该函数的函数名、函数值类型,以及形参列表中形参的个数和顺序,而函数定义则确立了一个函数的功能,不仅仅包含了函数声明所有的信息,还包含了形参的名字和函数体。
函数被调用前必须有函数声明,如果函数定义发生在函数调用之后,那么,必须在函数调用前使用单独函数声明语句。
范例4
#include <stdio.h>
void hello_world(void);
int main(void){
hello_world();
return 0;
}
void hello_world(void){
printf("Hello World!\n");
}
三. 函数值与形参列表
1. 函数值类型
可以是整型、浮点型、字符型,也可以是自定义类型(typedef的结果)。但是函数值类型不能是数组型,也不能是函数型。对于需要返回数组型或函数型的函数值,可以使用指针来声明函数值类型。
当函数值类型定义为void型时,该函数没有返回值。例如下面的例子是错的:
void set_a(int a);
b = set_a(2);
2. 形参列表
必须为每个参数的声明类型和参数名。例如:
函数值类型 函数名(参数类型1 形参1,参数类型2 形参2,……);
- 为了提高程序的可读性,建议使用带参数名的声明方式。void copy_value(int dst,int src)
- 要声明一个无形参的函数时,建议使用void型作为函数列表内容,以显式地说明不需要参数。 int hello_mark(void)
- 调用函数后,每个形参都初始化为相对应的实参。参数类型1 形参1 = 实参1;
void print_data(const int a,const int b)l
print_data(x,y);
可以理解为
const int a = x;
const int b = y;
3. 函数返回值
当函数返回值类型不为void型时,函数返回值就可以作为函数调用表达式中的一个操作数来使用。
- 函数可以使用return语句将函数内某个值带到函数外。
范例5
#include <stdio.h>
int cube(const int x){
return x * x * x;
}
int factorial(int x){
int rst = 1;
for(;x>0;--x){
rst *=x;
}
return rst;
}
int main(void){
const int v = 7;
printf("cube(7) = %d\n",cube(v));
printf("factorial(7) = %d\n",factorial(v));
return 0;
}
- 通过在函数内部使用一个标志变量来记录执行的分支,最后用return语句将该值返回。在函数外部通过检查该标志的值,就可以判断函数执行了哪一个分支。
范例6
#include <stdio.h>
#define MAX_SIZE 8
const int TRUE = 1;
const int FALSE = 0;
/*检查一个数组中是否存在目标数*/
int search_data(const int array[],const int size,const int target){
int i = 0;
for(i = 0;i < size ;i++){
if(target == array[i])
return TRUE;
}
return FALSE;
}
int main(void){
int q[MAX_SIZE] = {7,5,0,89,12,4,31,54};
int x = 0;
int i = 0;
printf("Elements in the array:\n");
for(i=0;i<MAX_SIZE;i++){
printf("%4d",q[i]);
}
printf("\nPlease input the target number:");
scanf("%d",&x);
if(TRUE == search_data(q,MAX_SIZE,x)){
printf("%4d exists in this array.\n",x);
}else{
printf("Can't find %d in this array.\n",x);
}
return 0;
}
4. const 形参
很多形参在声明中使用了const限定词,其作用是避免只读变量被修改。
这里要注意,定义const型的形参时,要确保该变量在函数执行过程中中不会被修改,如果出现自增自减等对参数自身值进行改变时,不要使用const进行定义。
范例7
#include <stdio.h>
const int YELLOW = 1;
const int RED = 2;
void set_yellow(const int colour){
colour = YELLOW;
}
void set_red(int colour){
colour = RED;
}
int main(void){
int wall_colour = 0;
set_yellow(wall_colour);
set_red(wall_colour);
return 0;
}
这里由于语句colour = YELLOW; ,而前面是将形参colour设置为了const,出现了冲突,所以编译不能通过。
四. 函数体
函数体一般由变量定义、函数操作和return语句三部分组成。
1. 变量定义
变量定义就是定义函数中需要用到的变量。一个程序块(放在花括号内的复合函数)中的变量定义必须放在这个程序块的最前面。
下面的变量j和变量i,max属于不同的程序块,但都放在程序块的最前端,因此,本函数的变量定义没有错。
int get_max(int a[SIZE][SIZE]){
int i;
int max = a[0][0];
for(i=0;i<SIZE;i++){
int j;
for(j=0;j<SIZE;j++){
if(max < a[i][j])
max = a[i][j];
}
}
return max;
}
2. 检查形参
在函数体中对形参进行操作前,有必要先检查形参值的合法性。例如:如果对数组元素进行操作,必须在访问数组元素前,对数组索引进行检查,看其是否在合法的范围内:
int deal_element(int array[SIZE],int index){
if(index >= SIZE || index < 0){
printf("Error index when get_ele().\n");
}
return 0;
}
3. return 语句
return 语句一般由关键字return 和表达式两部分组成,如下:
return 表达式;
如果省略了return语句,编译器会自动返回一个相应类型的随机值。
返回值可以是一个,可以是空(renturn;),也可以是多个(设置多个return)。
范例8
#include<math.h>
#include <stdio.h>
/*向下取整*/
int floor_new(const double d){
return d;
}
/*向上取整*/
int ceiling_new(const double d){
int f = floor(d);
return d == f?f:f+1;
}
int main(void){
double data;
printf("Please input a double number: ");
scanf("%lf",&data);
printf("floor(%f) = %d\n",data,floor_new(data));
printf("ceiling(%f) = %d\n",data,ceiling_new(data));
return 0;
}
五. main函数
main函数时C语言中最特殊的函数,它是C程序的入口。
main的返回值必须为int,而形参列表可以为空,也可以带两个形参。 标准声明形式如下:
int main(void)
省略函数值类型是不推荐的,而声明为void型则是错误的。
完整的main声明如下:
int main(int argc,char ** argv)
范例9
#include <stdio.h>
int main(int argc,char *argv[]){
int i = 0;
printf("There are %d arguments.\n",argc);
for(i=0;i<argc;i++){
printf("%d: [%s]\n",i,argv[i]);
}
return 0;
}
练习1
对该范例进行函数封装。
#include <stdio.h>
#include <string.h>
#define MAX_STRING 200
int main(void){
int i = 0;
int j = 0;
char str[MAX_STRING] = {0};
int length = 0;
char tmp = 0;
int start = 0;
int end = 0;
printf("Input riginal string:\n");
gets(str);
length = strlen(str);
start = 0;
end = length - 1;
while(start < end){ //将整个字符串进行翻转
tmp = str[start];
str[start] = str[end];
str[end] = tmp;
++start; //采取两端向中缩进的原则
--end;
}
printf("Step 1:\n");
printf("%s\n",str);
i=0;
start=0;
while(i<length){
//翻转其中一个单词
if(str[i] != ' '){ //寻找单词的开头
start = i;
while(str[i] != ' ' && str[i] != '\0') //寻找单词的结尾
++i;
end = i -1;
while(start < end){ //对于单词进行翻转
tmp = str[start];
str[start] = str[end];
str[end] = tmp;
++start;
--end;
}
}
++i;
}
printf("Step 2:\n");
printf("%s\n",str);
return 0;
}
封装如下:
//将代码进行函数封装
#include <stdio.h>
#include <string.h>
#define MAX_STRING 200
void reverse_string(char str[MAX_STRING],int start,int end){
char tmp = 0;
//将整个字符串进行翻转
while(start < end){
tmp = str[start];
str[start] = str[end];
str[end] = tmp;
++start; //采取两端向中缩进的原则
--end;
}
}
int main(void){
int i = 0;
int j = 0;
char str[MAX_STRING] = {0};
int length = 0;
char tmp = 0;
int start = 0;
int end = 0;
i = 0;
printf("Input riginal string:\n");
gets(str);
length = strlen(str);
start = 0;
end = length - 1;
reverse_string(str,0,length - 1);
printf("Step 1:\n");
printf("%s\n",str);
i=0;
start=0;
while(i<length){
//翻转其中一个单词
if(str[i] != ' '){ //寻找单词的开头
start = i;
while(str[i] != ' ' && str[i] != '\0') //寻找单词的结尾
++i;
reverse_string(str,start,i - 1);
}
++i;
}
printf("Step 2:\n");
printf("%s\n",str);
return 0;
}
练习2
//判断输入整数是否为素数,请使用函数实现
#include <stdio.h>
#include <math.h>
int input_data(void){
int data = 0;
do{
printf("Please input a positive integer: ");
scanf("%d",&data);
}while(data <= 0);//如果输入的数小于或等于0,那么就一直重新输入
return data;
}
int is_prime(const int data){
int i = 0;
int max = sqrt(data);
for(i=2;i<=max;i++){
if(data % i ==0) //将2到这个数的平方根作为除数,检查这个数是否能整除其中某个数
return -1;
}
return 0;
}
int main(void){
int data = 0;
int flag = 0;
data = input_data();
flag = is_prime(data);
if(0 == flag){ //当为合数时返回-1,当为素数时返回0,因此可以作为标志位进行判断
printf("%d is a prime number.\n",data);
}else{
printf("%d is not a prime number.\n",data);
}
return 0;
}
练习3
//证明任何一个而大于6的偶数都可以表示成两个素数之和
#include <stdio.h>
#include <math.h>
int input_data(void){
int data = 0;
do{
printf("Please input a even integer (>=6):");
scanf("%d",&data);
}while(data < 6 || data % 2 == 1);//只要不是大于6或者为偶数,就一直循环
return data;//将输入的数返回出来
}
int is_prime(const int data){
int i = 0;
int max = sqrt(data);
for(i = 2;i<=max;i++){
if(data % i == 0)//进行素数的判断
return -1;
}
return 0;
}
int goldbach_conjecture(const int n){
int a = 0;
int mid = n/2; //将mid定义为n的一半
for(a=3;a<=mid;a +=2){
if(0 == is_prime(a)){ //a为其中的一个素数
if(0 == is_prime(n-a)){ //n-a为另外一个素数
printf("%d can be devided into two prime number:%d and %d\n",n,a,n-a);
return 0;
}
}
}
return -1;
}
int main(void){
int n = 0;
int flag = 0;
n = input_data();
flag = goldbach_conjecture(n);
if(0 == flag){
printf("The Goldbach's conjecture is correct!\n");
}else{
printf("The Goldbach's conjecture is wrong!\n");
}
return 0;
}