c语言qsort_《C语言要点》第六章 模块化——函数

C语言读书摘录笔记,笔记内容绝大部分摘录整理自李春葆、李筏驰老师编著的《直击招聘——程序员面试笔试C语言深度解析》一书,少部分来自于网络博客及网上资源(尽量保留了资源原始链接)

6.1 函数基础

6.1.1函数的定义与调用

  1. 函数的定义
  • 默认的函数类型为int
  • void型函数无返回值,不能包含带返回值的return语句;其他类型的函数至少包含一个return语句
  • 函数的定义不能嵌套,即不能在一个函数体内又包含另一个函数的定义。这就保证了每一个函数是一个独立的、功能单一的程序单元
  • 复合语句(用花括号{}括起来的语句)申明的变量的作用域只在复合语句中,出了复合语句就不起作用。复合语句中的变量名和复合语句外面的变量即使同名也不是同一变量。

2. 函数的调用

    • 实参(argument)
      全称为"实际参数",是在调用时传递给函数的参数。实参可以是常量、变量、表达式、函数等, 无论实参是何种类型的量,在进行函数调用时,它们都必须具有确定的值, 以便把这些值传送给形参。 因此应预先用赋值,输入等办法使实参获得确定值
    • 形参(parameter)
      全称为"形式参数" ,由于它不是实际存在变量,所以又称虚拟变量。是在定义函数名和函数体的时候使用的参数,目的是用来接收调用该函数时传入的参数。在调用函数时,实参将赋值给形参。因而,必须注意实参的个数,类型应与形参一一对应,并且实参必须要有确定的值。没有形参时,圆括号也不可省;多个参数之间应用逗号分隔。参数包括参数名和参数类型
      来源:牛客网

6.1.2 函数的返回值与return语句

  1. return的语句功能:返回调用函数(终止该函数的执行),并将return语句中表达式的值带给调用函数
  2. return语句中表达式的类型与函数的类型不一致时则以函数类型定义为准,系统自动进行类型转换
  3. C语言中可以使用不带表达式的语句直接返回,C++必须使用带表达式的return语句返回
  4. return 语句不能返回局部变量的地址,因为该地址中存放的局部变量在函数执行完毕后被释放,但可以返回静态局部变量的地址,因为静态局部变量的空间不是在栈帧中,而是在静态数据区,即使栈帧退栈了,它仍然存在

6.1.3 函数的声明

  1. 函数声明语句也称为函数原型
  2. 如果调用一个函数出现在该函数的定义之前,则在调用前必须对该函数进行声明
  3. 如果函数原型放在调用函数定义的内部,则该声明仅对该调用函数有效
  4. fun()与fun(void)声明的区别:对于前者,编译器编译时不检查该函数调用的参数传递情况;对于后者,括号中有void,编译器编译时会严格检查该函数调用时的参数传递情况,如果带参数调用,则会编译错误或者警告

6.1.4 外部函数与内部函数

  1. 函数默认类型是外部函数,其作用域是整个源程序,即:除了可被本源文件中的其他函数调用外,还可被其他源文件中的函数调用(其他源文件调用时,需要对被调用的外部函数用extern语句进行声明)
  2. 内部函数,也称为静态函数,使用static关键字定义,其作用域局限于定义它的源文件内部,即:只能被本源文件中的函数调用,不能被统一程序的其他源文件中的函数调用,其有以下优点:
  • 其他源文件中可以定义相同名字的函数,不会发生冲突
  • 静态函数不能被其他源文件所用,达到“隐藏”目的

6.1.5 函数间的参数传递

参数传递有两种方式:传值传地址

  1. 传值方式
    一个函数调用另一个函数时直接将实参的值传递给对应的形参,这称为传值方式,对应的形参称为值参数。传值方式实现了把数据由调用函数传递给被调用函数。由于数据在传递方(实参方)和被传递方(形参方)占用不同的内存空间(函数的形参属于自动变量,函数执行完毕后自动释放),所以形参在被调用函数中无论如何变化都不会影响调用函数中相应实参的值,也就是说调用函数时实参和形参之间是单向的从实参到形参的值传递。
  2. 传地址方式
    如果要通过一个函数fun改变某个实参y (对应形参为x, 数据类型为Type )的值,需要在fun 形参表定义为Type *x, 在调用函数的语句中指定为&y (取y 的地址)。这样此时形参变量指向的内存地址与实参的地址相同,所以通过解引用形参变量指针进行的操作等同于对实参进行操作。

6.1.5 函数调用的实现原理

大多数CPU上的程序使用栈空间来支持函数调用操作。单个函数调用操作所使用的函数调用栈被称为栈帧(stack frame) 结构。每次函数调用时都会相应地创建一帧,保存返问地址、函数形参和局部变量值等,并将该帧压入调用栈。若在该函数返回之前又发生新的调用,则同样要将与新函数对应的一帧进栈,成为栈顶。函数一旦执行完毕,对应的帧便出栈(此时局部变量的生命周期结束),控制权交还给该函数的上层调用函数,并按照该帧中保存的返回地址确定程序中继续执行的位置。

  • 函数调用要点
    • 栈空间中每个栈帧的大小是有限的,所以在—个函数中不要定义很大空间的数组,否则可能会导致栈溢出,程序崩溃。
    • 每个栈帧对应着一个未运行完的函数。栈帧中保存了该函数的返回地址和局部变量,每个函数的每次调用,都有它自己独立的一个栈帧,这个栈帧维护着函数调用所需要的各种信息。函数的返回地址和参数,保存当前函数调用前的“断点”信息,也就是函数调用前的指令位置,以便在函数返回时能够恢复到函数被调用前的代码区中继续执行指令。函数栈帧的大小并不固定,一般与其对应函数的局部变量多少有关。函数运行过程中,其栈帧大小也是在不停变化的!

版权声明:本文为CSDN博主「YYtengjian」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。

    • 当—个函数多次调用时,每次调用都会创建一个栈帧,为同名的局部变量分配空间,但它们的地址是不同的,它们之间也没有关系。

6.1.7 函数调用时参数的求值顺序

如果所有实参表达式的求值没有二义性,那么从右往左求值和从左往右求值结果是相同的。人们一般认为是从右往左顺序求值的。但,如果出现二义性,输出结果便是不确定的。例如:

#include <stdio.h>
int main()
{
    int a=l;
    printf("%d,%d,%dn",a++,++a,a++);
    return 0;
}

此程序在VC++中输出“2,2,1”,但在Dev C++中输出“3,4,1”,这是因为printf函数的实参存在二义性,因为在函数的所有参数赋值之后且在函数的第一条语句执行之前有一个顺序点,而参数间的逗号处没有顺序点,任意两个顺序点之间的副作用的求值次序都是不确定的,这里有3 个副作用,所以输出结果不确定。

  • 见笔记第一章1.2.1中第4点表达式求值的副作用
  • C语言参考手册:求值顺序

6.1.8 atexit()函数

即使main()函数终止以后仍然可以执行一些代码,这需要使用stdlib.h 头文件中的atexit()函数。一般来说,如果在main()中调用某个函数,程序的执行会跳转到该函数并执行它。在执行该函数后控制权又交还给main()函数。

当使用了atexit() 函数以后,进程的执行可以简单地理解为当main()函数终止后跳转到atexit()函数,然后再也不会返回到main()函数。atexit()函数的使用格式如下:

atexit(函数名);

由于atexit()函数是按后进先出的方式注册这些函数的,因此最后注册的函数先调用

6.2 数组作为函数参数

以二维数组为例:

int a[M][N]; int (*pa)[n] = a ;
void func(int a[][N]); void func(int a[M][N]);//函数声明
func(a); func(pa);//调用

6.3 指针数组作为函数参数

当指针数组作为实参时,对应的形参应当是一个指向指针的指针变量.例如以下三种形式:

void func(int *a[]);
void func(int *a[N]);
void func(int **a);

6.4 指针函数和函数指针

6.4.1 指针函数

例如:int *func(int a,float x);

解释

  • 定义指针型函数时前面的*号与“数据类型”相结合,表示此函数是指针型函数。上例,定义func()函数时首部中的int* 是一个整体,表示该函数返回的是整型变量的地址
  • 在程序中不要使用数组名接收指针型函数的返回值,因为数组名为地址常量,不能向它赋值

6.4.2 函数指针

函数的存储首地址又称为函数的执行入口地址, C 规定函数的首地址就是函数名。当指针变量保存函数的入口地址时它就指向了该函数,所以称这种指针变量为指向函数的指针变量,简称为函数指针.

定义函数指针的一般格式:函数类型(*函数指针名)(形参表);

解释

  • 在定义函数指针变量时,“函数指针名“两边的圆括号不能省略,它表示函数指针名先与*结合,即为指针变量,然后再与后面的”(形参表)”相结合,表示该指针变量指向函数。如果少了前面的一组括号,则变为函数类型 *函数名(形参表)它表示返回值为地址值(指针)的函数
  • 函数指针变量的类型是被指向的函数类型
  1. 给函数指针赋值格式:函数指针名=函数名;
  2. 通过函数指针调用函数格式:(*函数指针)(实参表);
  3. 函数指针的作用主要体现于在函数间传递函数,这种传递不是传递任何数据,而是传递函数的执行地址,或者说是传递函数的调用控制。当函数在两个函数间传递时,调用函数的实参应该是被传递函数的函数名,而被调用函数的形参应该是接收函数地址的函数指针
  4. 函数指针的用处:
  • 使用函数指针的目的是为了增加执行函数的通用性,特别是在可能调用的函数可变的情况下,可以动态设置内容,有灵活性。如:排序的qsort函数需要传入比较的函数指针,来确定排序是从大到小还是从小到大,如下:
void qsort(void *base, size_t nitems, size_t size, int (*compar)(const void *, const void*));
//其中参数compar——用来比较两个元素的函数,即函数指针(回调函数)

6.2.3 两个函数指针实例

1.实例一:

f223164c503c796cadb139ceab430418.png
  1. 实例二:
void ( *func(void (*p)(void *),void *x) ) (void *);

解释

5f49819bf87228b27b55d8895c883a21.png

6.5 递归函数

  • 递归函数又称自调用函数,其特点是在函数内部调用自己。C 规定不允许函数递归定义,即不允许在一个函数体中定义另一个函数,但可以递归调用。在执行递归函数时将反复调用其自身,每调用一次就进入新的一层。
  • 一般地, 一个递归函数定义由两个部分组成,即递归结束情况递推关系情况。递推关系就是把一个不能或不好直接求解的“大问题“转化成一个或儿个“小问题”来解决,再把这些“小问题”进一步分解成更小的“小问题”来解决(即递推),如此分解,直到每个“小问题”都可以直接解决(此时分解到递归结束情况)。
  • Silencht:《C语言要点》第一章 程序设计基础——变量
  • Silencht:《C语言要点》第二章 数据处理——控制结构
  • Silencht:《C语言要点》第三章 内存操作——指针
  • Silencht:《C语言要点》第四章 数据组织——数组
  • Silencht:《C语言要点》第五章 数据结构II——结构体与联合体
  • Silencht:《C语言要点》第七章 位操作——位运算和位域
  • Silencht:《C语言要点》第八章 编译前的处理——预处理
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值