C语言-函数

1.函数

1.函数的定义

函数名是该函数的地址,所以在取址时可以不写&,直接写函数名,但是为了区分,最后写上

image-20230421092620162

void print_c() ; // 声明函数

int main()  
{
	print_c() ;	
}

void print_c()
{
	printf(" ###### \n") ;
 	printf("##     ##\n") ;
	printf("##        \n") ;
	printf("##         \n") ;
	printf("##      ##\n") ;
	printf(" #######   \n") ;
}



1.2函数的声明

image-20230421092754208

1.3函数的参数和返回值

案例,求累加和

int sum(int n) ;

int main()  
{
	int n ;
	scanf("%d",&n) ;
	printf("%d",sum(n)) ;
}


int sum(int n)
{
	int res ;
	for(int i = 1; i <= n ; i++)
	{
		res += i ;
	}
	return res ;
}

2.参数和返回值

2.1形参和实参

形式参数和实际参数

形参:只有在函数调用的内部有效,是在定义函数时写的参数

实参:是真正调用时,传递的参数为实参

2.2传值和传址

2.2.1 传递的变量为值

void swap(int x , int y) ;

void swap(int x, int y)
{
	printf("in swap 互换前: x = %d, y = %d\n",x,y) ;
	int temp = x ;
	x = y ;
	y = temp ;
	printf("in swap 互换后:x = %d, y = %d\n", x,y) ;
}


int main()
{
	int x = 3 , y = 5;
	printf("in main 互换前: x = %d, y = %d\n",x,y) ;
	swap(x,y) ;
	printf("in main 互换后: x = %d, y = %d\n",x,y) ;
}

image-20230421095427374

2.2.2 传递的变量为指针


void swap(int *x , int *y) ;

void swap(int *x, int *y)
{
	printf("in swap 互换前: x = %d, y = %d\n",*x,*y) ;
	int temp = *x ;
	*x = *y ;
	*y = temp ;
	printf("in swap 互换后:x = %d, y = %d\n", *x,*y) ;
}


int main()
{
	int x = 3 , y = 5;
	printf("in main 互换前: x = %d, y = %d\n",x,y) ;
	swap(&x,&y) ;
	printf("in main 互换后: x = %d, y = %d\n",x,y) ;
}


注意

可以看出以上的两个结果,是不一样的

  • 在传递的变量是值的时候,swap函数之后,发现在main函数中,x,y的值并没有改变。
    • 这是因为每个函数都有独立的作用域,简单的理解就是,每个函数内部都是互相独立的。他们的变量只在函数内部生效,不同的函数,不在同一个作用域里面,是不同的两组变量
  • 但是,址传递的时候,main函数中x,y的值也会改变,址传递,是将这两个数的地址直接互换的,像任意门一样,给拉过来了,不考虑作用域。

2.3传数组

注意:传数组,并不是将整个数组传递过去的,通过下面的案例可以发现。在get_array函数里改变数组的值,在main函数中有发生了改变。说明并不存在将整个数组作为参数传递的这么一个方式。 传递的是数组的第一个元素的地址

	
void get_array(int a[10]) ;

void get_array(int a[10])  // 这里接受的只是数组的第一个元素的地址。
{
	a[5] = 520 ;   
	for(int i = 0; i < 10 ; i ++)
	{
		printf("a[%d] = %d\n",i,a[i]);
	}

}


int main()
{
	int  a[10] = {1,2,3,4,5,6,7,8,9,0} ;
	get_array(a) ;
	printf("在main函数里再打印此意	\n") ;

	for(int i = 0 ; i < 10 ; i++)
	{
		printf("a[%d] = %d\n",i,a[i]);  // 发现再main函数里,a[5]的值也发生了改变
									//说明并不存在将整个数组作为参数传递的这么一个方式。 
	}

}

2.3.1传数组传递的是第一个元素的地址

void get_array(int b[10]) ;

void get_array(int b[10])
{
	printf("size of b = %d\n",sizeof(b)) ;
}


int main()
{
	int  a[10] = {1,2,3,4,5,6,7,8,9,0} ;
	printf("size of a = %d\n",sizeof(a)) ; 
	get_array(a) ;

}

通过值结果可以证明,传递的并不是整个数组而是数组的第一个元素的地址。

2.4可变参数

如果要有可变参数,需要有一个头文件。

image-20230421104141630

int printf(const char* format,…)
int scanf(const char *format,…)

就拿 printf 来说吧,它除了有一个参数 format 固定以外,后面的参数其个数和类型都是可变的,用三个点“…”作为参数占位符

三个…代表的意思是作为参数的占位符

#include<stdio.h>
#include<stdarg.h>

int sum(int n ,...) ;//第一个参数是指定后面有多少个参数

int sum(int n,...)
{
	int i , sum = 0 ;
	va_list vap ;  // 可变的列表,定义为vap

	va_start(vap,n) ;// 初始化这个参数列表 ,需要两个参数,一个是valist,一个是n

	for(i = 0 ; i < n ; i++)
	{
		sum += va_arg(vap,int) ; //获取list参数里的,每个的值,要写清楚类型

	}
	va_end(vap) ; // 收尾工作,关闭参数列表

	return sum ;
}

int main()
{
	int res ;

	res = sum(3,1,2,3) ;
	printf("res = %d\n",res) ;

}

3.指针函数和函数指针

3.1指针函数

image-20230421150821637

案例
char *getWord(char c) ;

char *getWord(char c)  //返回的值是字符串,通常用char类型的指针来定义字符串 
{
	switch(c)
	{
		case 'a' : return "apple" ;  // 其实返回的是字符串首字母的地址 
		case 'b' : return "banana" ;
		case 'c' : return "cat" ;
		case 'd' : return "dog" ;
		default:  return "none" ;
	}
}

int main()
{
	char input ;
	printf("请输入一个字母\n") ;
	scanf("%c",&input) ;
	
	printf("%s\n",getWord(input)) ;

}

不要返回局部变量的地址

3.2函数指针

函数指针 返回值类型 (*p)(传递的参数的类型)

int square(int num) ;

int square(int num)
{
	return num*num ;
}

int main()
{
	int num ; 
	int (*fp)(int) ; // 这里是等价于int square的,所以下面可以相等。 定义的是函数指针 
 //整形的返回值, 整形的参数,函数指针要与函数的各部分相对应。 
	scanf("%d",&num) ; 
	
	fp = square ;	
	printf("%d * %d = %d\n", num,num,(*fp)(num)) ; 
}

3.3函数指针作为参数

可以这样理解,理解为函数指针作为参数就是,该指针指向了某个函数,例如下面的,指向了add 和 sub

int add(int , int) ;
int sub(int , int) ;
int calc(int (*fp)(int , int),int,int); // 函数指针


int add(int num1 , int num2 )
{
	return num1+num2 ;
}

int sub(int num1,int num2)
{
	return num1-num2 ;
}

int calc(int (*fp)(int ,int),int num1, int num2) //函数指针作为参数 ,关键的一步。有点类似于java中的各个方法调用的意思。hhh
{
	return (*fp)(num1,num2) ;
}

int main()
{
	printf("3 + 5 = %d\n",calc(add,3,5)) ; 
	printf("3 - 5 = %d\n",calc(sub,3,5)) ;
	
}

3.4函数指针作为返回值

注意 函数指针作为返回值的时候,需要定义一个函数指针来接受该返回值

函数指针作为返回值,这个返回值又会指向其他的函数,比如下面的例子,指向了add和sub

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-d36XtIfC-1683818810356)(null)]


int add(int num1 , int num2 )
{
	return num1+num2 ;
}

int sub(int num1,int num2)
{
	return num1-num2 ;
}

int calc(int (*fp)(int , int), int num1 , int num2) // 函数指针作为参数
{
	return (*fp)(num1,num2) ;
}

int (*select(char op))(int ,int) // 函数指针作为返回值,指向的函数的参数个数类型要保持一致。
{
	switch(op)
	{
		case '+' : return add;
		case '-' : return sub ;
	}
}

int main()
{
	int num1, num2 ;
	char  op ;
	int (*fp)(int ,int) ; // select是函数指针,这里定义一个函数指针来接受 
	
	printf("请输入一个式子(如1+3):") ;
	scanf("%d%c%d",&num1,&op,&num2) ;
	 
	fp = select(op) ;
	printf("%d %c %d = %d \n",num1,op,num2,calc(fp,num1,num2)) ; 
	 
}

3.5函数指针作为参数和作为返回值之间的差别

  • 定义的形式上

    • 作为参数:int calc(int (*fp)(int ,int),int num1, int num2)
    • 作为返回值:int (*select(char op))(int ,int)
  • 意义:

    • 作为参数是:调用了某个其他的函数,参数的类型是要保持一致的

    • 作为返回值:是指向了某个其他的函数,在main函数中需要有函数指针接受这个返回值,参数类型也要跟指向的其他函数保持一致

4.局部变量和全局变量

4.1全局变量

在主函数外定义的变量,就是全局变量了。

如果不对全局变量初始化,,那么它会自动初始化为0

!! 不要大量的使用全局变量

4.2 extern关键字

void func()
{
	extern cnt ;
	cnt++ ;
} 
int cnt = 0 ;

int main()
{
	func() ;
	printf("%d",cnt) ;
}

5.作用域和链接属性

5.1作用域

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-OY3JJGOz-1683818810426)(null)]

5.1.1代码块作用域

很容易理解,这里就不举例子了。

5.1.2文件作用域

省略

5.1.3原型作用域

声明函数的时候可以不用和定义的函数的参数名一样,只写参数的类型就好了。

5.2链接属性

不同函数定义在不同的文件中,但是并不影响main函数的调用声明的时候 extern int example写在文件头就可以了。就可以用example这个函数。

就是可以把函数放在另一个文件,需要的时候就可以调用,这样可以使得主函数main不那么杂乱

只有具备文件作用域的标识符才能拥有external或internal的链接属性其他作用域的标识符都是none属性

如果函数前面加上static,该函数只能在本文件中调用,其他文件不能调用,因为此时它变为了internal属性。

6.定义和声明

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-iSmTQGSg-1683818810390)(null)]

7.变量的生存期和存储类型

7.1生存期

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-mI9dj3sp-1683818814002)(null)]

7.2存储类型

作用域和生存期其实是由存储类型决定的。

c语言中提供了5种不同的存储类型

  • auto
  • register
  • static
  • extern
  • typedef

7.2.1 auto

自动变量拥有代码块作用域,空链接属性,自动存储期

7.2.2 register

寄存器变量

7.2.3 static静态局部变量

static声明变量,可以将exernal的变量变为internal的变量,多文件的作用域变为单文件的作用域

static声明局部变量,可以将局部变量指定为静态局部变量。

static使得局部变量具有静态存储期,所以他的生存期与全局变量一样,直到程序结束才释放。

案例
void func()
{
	int cnt = 0 ;
	printf("%d\n",cnt) ;
	cnt++ ;
}

int main()
{
	for(int i = 0 ; i<10 ; i++)
	{
		func();
	}
}
上面的cnt没有加static ,生存期为自动存储期,通过调用函数可以发现,输出的值全为0,因为每次调用结束就释放cnt,并不会进行累加


void func()
{
	static int cnt = 0 ;
	printf("%d\n",cnt) ;
	cnt++ ;
}

int main()
{
	for(int i = 0 ; i<10 ; i++)
	{
		func();
	}
}

通过结果对比可以发现,static使得局部变量有了静态存储期,跟全局变量一样,知道最终程序结束才释放。

7.2.4 static 和 extern

8.递归

属于算法的范畴,详细笔记参考算法对应的章节。

8.1实现汉诺塔

//递归的方式实现汉诺塔

void hanoi(int n, char x ,char y , char z)
{
	if(n == 0) return ;
	hanoi(n-1,x,z,y);   // 最后将第二根柱子的所有盘移动到第三根柱子 
	printf("%c --> %c\n",x,z) ; // 第一根柱子移动到第三根柱子 
	hanoi(n-1,y,x,z) ;  //第一根的所有柱子移动到第二根柱子,然后将最大的盘移动到第三根柱子。 
}

int main()
{
	int n ;
	printf("请输入汉诺塔的层数") ;
	scanf("%d",&n) ;
	hanoi(n,'x','y','z') ;
}

9.快速排序

参考算法笔记,分治那一章
知道最终程序结束才释放。

7.2.4 static 和 extern

[外链图片转存中…(img-BSU0Q0mT-1683818807141)]

8.递归

属于算法的范畴,详细笔记参考算法对应的章节。

8.1实现汉诺塔

[外链图片转存中…(img-PsUpNiqt-1683818807141)]

[外链图片转存中…(img-qd6PtX4L-1683818807141)]

//递归的方式实现汉诺塔

void hanoi(int n, char x ,char y , char z)
{
	if(n == 0) return ;
	hanoi(n-1,x,z,y);   // 最后将第二根柱子的所有盘移动到第三根柱子 
	printf("%c --> %c\n",x,z) ; // 第一根柱子移动到第三根柱子 
	hanoi(n-1,y,x,z) ;  //第一根的所有柱子移动到第二根柱子,然后将最大的盘移动到第三根柱子。 
}

int main()
{
	int n ;
	printf("请输入汉诺塔的层数") ;
	scanf("%d",&n) ;
	hanoi(n,'x','y','z') ;
}

9.快速排序

参考算法笔记,分治那一章

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值