一.函数的定义与使用
函数:一个完成特定功能的代码模块,其程序代码独立,通常要求有返回值,也可以是空值
一般形式:
<数据类型><函数名称>(<形式参数说明>){
语句序列;
return[(<表达式>)];
}
#include <stdio.h>
void show()
{
printf("hello world!");
}
int main(int argc ,char *argv[])
{
show();
return 0;
}
题目:定义求x的n次方值的函数
二.函数的声明
在C语言中,函数的定义顺序是有讲究的:默认情况下,只有后面定义的函数才可以调用前面定义过的函数。如果想把函数的定义写在main函数后面,而且main函数能正常调用这些函数,那就必须在main函数的前面进行函数的声明。
声明格式:
返回值类型 函数名 (参数1, 参数2, ...)
#include<stdio.h>
int sum(int a, int b);//函数声明
int main()
{
int c = sum(1, 4);//函数调用
printf("%d",c);
return 0;
}
// 函数的定义
int sum(int a, int b) {
return a + b;
}
如果只有函数的声明,而没有函数的定义,那么程序将会在链接时出错。
三.函数的参数传递
参数传递方式:全局变量,复制传递,地址传递
1)全局变量:函数体外说明的变量
2)复制传递(值传递):调用函数将实参传递个被调用函数,被调用函数将创建同类型的形参并用实参初始化。形参是新开辟的存储空间,因此,在函数中改变形参的值,不会影响到实参。
3) 地址传递(指针传递):实参作为变量的地址(数组的指针),而形参为同类型的指针。被调用函数中对形参的操作,将直接改变实参的值
函数的参数,形式参数,被当作该函数内的局部变量,如果与全局变量同名它们会优先使用
#include <stdio.h>
void swap(int *x,int *y);
int main(int argc ,char *argv[])
{ int a=10;
int b=20;
printf("before:%d %d\n",a,b);
swap(&a,&b);
printf("after:%d %d\n",a,b);
return 0;
}
void swap(int *x,int *y)
{ int t;
t= *x;
*x =*y;
*y=t;
}
四.局部变量与全局变量
局部变量:变量只是在程序的局部范围内有效;
局部变量定义在那些位置:
- 函数的开头;
- 函数内的复合语句内定义;
- 形式参数;
- 函数中间(非开头);
#include <stdio.h>
void main()
{
}
void fun1(char l0) //形式参数:只在此函数有效;
{
char l1 = 'a'; //函数的开头:在本句以下的函数内有效;
{
char l2 = 'b'; //函数的复合语句内定义:只能在本复合语句内切本行以下有效;
}
char l3 = 'c'; //函数中间(非开头):只能在本行以下有效;
}
全局变量:变量,可以在全局范围内有意义的变量;所谓全局也并不是真正的全局,而是在定义处以下的范围内才是有效的;
全局变量定义的位置:
- 文件开头;
- 函数前;
- 函数后;
- 文件结尾;
#include <stdio.h>
char Global_1 = 'A'; //源文件开头:在此行以下的所有定义的函数都有效;
void main()
{
}
char Global_2='B'; //函数后和函数前:此全局变量在此行之前是无效,只能在此之下的函数中有效;
void fun1()
{
char l1 = 'a';
{
char l2 = 'b';
}
char l3 = 'c';
}
char Global_3 = 'B';//程序结尾:程序结尾的全局变量是没有意义的;
全局变量与局部变量在内存中的区别:
1)全局变量保存在内存的全局存储区中,占用静态的存储单元;
2)局部变量保存在栈中,只有在所在函数被调用时才动态地为变量分配存储单元。
五.指针函数与函数指针
指针函数:一个函数的返回值为地址量的函数
一般形式:
<数据类型>*<函数名称>(<参数说明>){
语句序列;
}
int main1(int x,int y);
int* main1(int x ,int y);//()的优先级大于*,先是函数,返回一个指针
返回值:全局变量的地址/static变量的地址/字符串常量的地址/堆的地址,不可返回局部变量的地址
函数指针:指向函数的指针变量
通常我们说的指针变量是指向一个整型、字符型或数组等变量,而函数指针是指向函数。函数指针可以像一般函数一样,用于调用函数、传递参数。
函数指针类型的声明:
<数据类型> (*<函数指针名称>)(<参数说明列表>)
int main1(int x,int y);
int (*p)(int x,int y );//()的优先级大于*,先是指针,指向一个函数
以下实例声明了函数指针变量 p,指向函数 max:
#include <stdio.h>
int max(int x, int y)
{
return x > y ? x : y;
}
int main(void)
{
/* p 是函数指针 */
int (* p)(int, int) = & max; // &可以省略
int a, b, c, d;
printf("请输入三个数字:");
scanf("%d %d %d", & a, & b, & c);
/* 与直接调用函数等价,d = max(max(a, b), c) */
d = p(p(a, b), c);
printf("最大的数字是: %d\n", d);
return 0;
}
请输入三个数字:1 2 3
最大的数字是: 3
六.递归函数
递归函数:一个函数的函数体中直接或间接调用该函数本身,分递推阶段和回归阶段
C 语言支持递归,即一个函数可以调用其自身。但在使用递归时,程序员需要注意定义一个从函数退出的条件,否则会进入死循环。递归函数在解决许多数学问题上起了至关重要的作用,比如计算一个数的阶乘、生成斐波那契数列,等等
void recursion()
{
statements;
... ... ...
recursion(); /* 函数调用自身 */
... ... ...
}
int main()
{
recursion();
}
七.空指针与野指针
空指针:声明指针后,让它指向空(如0,NULL)表示没有指向任何地址。
后果:如果对空指针解引用,程序会崩溃
规避空指针
1)如果对空指针使用delete运算符,系统将忽略该操作,不会异常。所以内存被释放后,也应该把指针指向空。
2)在函数中,应该有判断形参是否为空指针的代码,保证程序的健壮性。
int* p=0;
delete p;
野指针:指向的不是一个有效(合法)的地址
指针变量也是变量,是变量就可以任意赋值。但是,任意数值赋值给指针变量没有意义,因为这样的指针就成了野指针,此指针指向的区域是未知(操作系统不允许操作此指针指向的内存区域)。野指针不会直接引发错误,操作野指针指向的内存区域才会出问题。
int a = 100;
int *p;
p = a; //把a的值赋值给指针变量p,p为野指针, ok,不会有问题,但没有意义
p = 0x12345678; //给指针变量p赋值,p为野指针, ok,不会有问题,但没有意义
*p = 100; //对野指针进行赋值操作就不可以了
野指针的成因
1)指针未初始化:指针变量刚被创建时不会自动成为NULL指针,它的缺省值是随机的,它所指的空间是随机的
2.)指针越界访问:指针指向的范围超出了合理范围,或者调用函数时返回指向栈内存的指针或引用,因为栈内存在函数结束时会被释放
3 )指针释放后未置空:有时指针在free或delete后未赋值 NULL,便会使人以为是合法的。其实它们只是把指针所指的内存给释放掉,但并没有把指针本身忘记。此时指针指向的就是无效内存。释放后的指针应立即将指针置为NULL,防止产生“野指针”
int main()
{
int * p;//指针变量有操作系统随机赋值,未指向一个具体空间
*p = 20;
//改后:
//int *p =NULL;
//int a = 10;
//p = &a;
int arr[10] = {0};
int *p = arr;
for(int i = 0; i <= 11; i++)//i<=10
{
*(P++) = i;//当指针指向的范围超出数组arr的范围,p变成野指针。
}
int *p = NULL;
p = malloc(10 * sizeof(int));
if (!p)
{
return;
}
//p = NULL;避免野指针
free(p);
return 0;
}
规避野指针
1)初始化指针
2)避免指针越界
3)开辟的指针释放后置为NULL
4) 避免返回局部变量的地址
int * t1()
{
int a = 20;
return &a;
}
int main()
{
int *p = NULL;
p = t1();
printf("%d\n", *p);
return 0;
}