【C语言学习笔记】五、函数

(八)函数

随着程序规模的变大,我们经常会在开发中遇到下面的问题:
(1)main函数太长,看晕。
(2)代码前后关联度高,修改代码往往牵一发而动全身,比如就是修改个变量名,后面就得跟着修改多处。
(4)为了在程序中多次实现某功能,不得不重复多次写相同的代码。

所以,我们要封装,第一层封装就是函数。比如printf就是一个函数,就是把一个实现标准输出这个功能的代码封装成一个函数print,然后我们就不需要知道到底是怎么实现标准输出的,只要在需要用这个功能的时候,调用函数printf即可。
printf是C语言标准库给我们提供的一个函数,除此之外,标准库还提供有处理字符串的、有进行数学计算的、输出输入的、进程管理的信号、接口管理等函数。

所以,当我们把代码段根据其功能封装成一个个不同的函数,这种做法通俗的理解就是:把大的任务给分解成若干个小任务,然后实现的做法。书面一点的说法就是:模块化程序设计,就是把main函数给拆解了,根据功能封装成若干个函数,然后main函数调用功能函数。
下面代码示例:打印一个大写的C字母

这种用函数实现的方式,main函数就显得很清晰了。

1、函数的定义
函数必须先定义再调用。
函数定义的语法:类型名 函数名(参数列表){函数体}
(1)类型名指函数的返回值的类型。如果这个函数不打算返回任何数据,类型就是void类型,前面讲过这个关键字,是无类型、空类型的意思。如果不写类型名,默认返回的是整型int类型。
(2)函数名就是函数的名字,尽量见名知意。
(3)参数列表:是指定参数的类型和名字。同时也就知道函数有几个参数了。如果这个函数没有参数,小括号里面空着就行,但小括号不能省。

2、函数体
就是函数功能实现的具体过程。

3、函数的声明
函数声明declaration,就是告诉编译器要使用这个函数。
函数声明是可以省略。但省略了,你就得注意,你的main函数一定要在它调用的功能函数的后面,因为代码是从上往下编译执行的,这样就可以先运行功能函数再运行main函数。如何你不小心把功能函数放到了main函数后面,你也不在main函数前面加功能函数的声明,那执行main函数的时候就会报错,说找不到功能函数。如果你加了声明,即使main函数在功能函数后面,编译器也不会报错。比如上图中,把main函数和print_C函数位置对调,也不会报错。
函数声明是一个声明语句,所以后面的分号不能丢!

4、函数的参数和返回值
有时候,有的函数,是需要接受用户传入的数据的,此时函数就需要参数。参数可以有多个、参数的类型也可以各不相同。

函数的返回值通常是函数返回的结果,有时也可以是函数的执行结果,比如函数执行成功返回true,失败返回false。如果你的函数确实不需要返回什么,比如上面打印字母C的例子,那你定义函数的时候就用void这个返回类型。

案例1:编写一个sum函数,由用户输入参数n,计算1+2+...+n的结果并返回。

A:这个地方n自减的操作还可以写到do里面:do{res += n; n--;}while(n>0)也是可以的。而且上面的写法中自减和比较符号中间可以不用空格。

案例2:编写一个函数max,接收2个整型参数,并返回它们中的较大的值。

5、形参和实参
函数定义的时候写的参数就是形参。形参仅仅是一个占位符而已,只是告诉编译器要腾出一个空间而已,实际并没有具体的数值,此时数值还是随机的。
在函数被调用的时候,我们要传给这个函数的参数,那这个参数就是实参。此时实参的值就会传递给形参,并且只能单向传递。此时内存空间才会真正的存入这个数值。当函数调用结束后,立刻就会释放内存。所以形参只是在函数内部有效,出了函数就无效了。所以,实参和形参的功能就是用于数据传输的。

6、传值、传址、传数组
(1)先看一个传值的代码:

这个代码说明,在C语言中,每个函数都是独立的,函数自己的变量只在自己函数体内有效。不同函数之间是不能直接去访问对方的变量的!也所以两个函数的参数名都一样,也不会发生冲突,因为它们各自是各自的作用域,互不干涉互不冲突。

(2)传址
因为指针也是一个变量,所以我们也可以通过参数的形式,把指针传递给函数。所以就也有指针参数这个概念。下面代码展示如何传址:

说明:如果传给函数的是一个指针,那如果这个函数如果改变了指针指向的地址的值,那这个地址的值就永久改变了。别的函数再用这个地址的值,就是已经改变了值的值了。

(3)传数组
可以给函数传值、传址,当然就也可以传数组了,下面代码展示传数组:

我们把上面的代码改动一下:

当get_array函数改变数组的某个元素的值后,那这个元素的值就永久改变了。其他函数比如main函数再读这个数组的时候,数组的那个元素的值就和原来的不一样了。
所以:给get_array函数传数组的时候,其实传递给get_array函数的仅仅是数组的首地址,也就是只是传了一个地址而已。所以对地址里的值进行改变,就是永久改变了。不管谁调用,就都是改变后的值了。

可见,给函数传数组参数的时候,其实就是传入了一个数组首元素的地址。
这里传入的参数b[10],形式上看着是传入了一个10个元素的数组,但其实仅仅是一个首元素的地址,所以sizeof的时候内存占用仅仅是8个字节,也就是一个地址存放的空间大小。
所以,给函数传数组,也就相当于给函数传了一个地址。

7、可变参数 variable argument
这里只讲怎么定义一个支持可变参数的函数。
要实现一个函数可以传入可变参数,就要添加一个头文件:#include <stdarg.h>
这个头文件需要下面四个东西:
va_list
va_start
va_arg
va_end
其中,va_list是一个类型,va_start和va_arg和va_end是3个宏。

下面是一个案例展示如果定义一个可变参数的函数:sum函数

A:一看一个函数里面的参数是...,就表示这个函数是一个可变参数函数,就是这个函数的参数不确定。
其中,第一个参数是指定后面有多少个参数,后面用...三个点作为占位符,表示参数的值还不知道呢
再比如我们的printf函数,就也是一个可变参数的函数,所以printf函数的参数肯定是:printf(char n, ...)
sum(3,1,2,3)第一个参数3就表示sum函数有3个参数,然后功能是实现后面3个参数1,2,3的和。所以要求和就得用for循环。

B:就是用va_list这个类型定义一个参数列表。列表名是vap。这里实质是定义一个字符指针的类型。
C:va_start是个宏,我们定义完参数列表后,就把定义的参数列表名传给宏va_start,作为宏va_start的第一个参数。
va_start需要2个参数,另一个就是定义函数sum中的参数n,作为宏va_start的第二个参数传入。
这个步骤就是初始化参数列表。实质上也就是对B定义的字符指针的一个计算而已。这里面的原理放到宏定义部分讲解。
D:va_arg宏,就是获取每一个参数的值。里面的int是每个参数的类型,这个类型也必须写。比如前面举例的printf函数,如果是%d,这里的参数就是int类型,如果是%c,这里就是char类型。
E:关闭参数列表。

8、指针函数 和 函数指针


指针函数是指针的函数,是一个函数。是指用指针变量作为函数返回值的一个函数。
函数指针是函数的指针,是一个指针。是一个指向函数的指针。
可以类比指针数组和数组指针,同一个的套路。

(1)下面是一个指针函数的示例:用户输入一个字符,程序返回一个字符串。

我们现在是要从get_word函数里获得一个字符串(代码C),但C语言中并没有定义字符串类型的,所以我们都是通过一个char指针函数get_word来指向一个字符串的。

A行代码里的第一个char表示函数的返回值的类型,这里定义的是一个指针函数,那这个char就表示这个指针函数指向的地址里存的值是一个char。如果一个char类型的指针指向的是一个字符串,那这个char指针的值其实就是这个字符串首字母的地址,而在C语言中,读取字符串时,默认的约定就是读到\0,所以只要知道字符串首字母的地址,就能读到字符串的全部字符。所以这个例子中,我们定义的就是一个char类型的指针函数get_word,我们就可以接收一个返回的字符串B。

所以,B中代码上看,虽然返回的是一个个字符串,但实际上指针函数get_vord返回的是字母A或者字母b、c、d、g这些首字母的地址。这些地址解引用后就是一个个字符串。

(2)不要返回局部变量的指针!!! 

这里无法运行出来正确的结果是因为:函数get_word的返回值是变量str1 str2 str3,这些变量是get_word函数自己体内的局部变量,当 main函数调用get_word的时候,get_word函数自己体内的局部变量已经被释放了,就是内存中已经销毁了,就是临时分配的地址已经不存在了,自然就也无法读取那个地址里面的值了。
但上上个例子return 字符串就可以呢?因为字符串并没有被定义到get_word函数体内。字符串在C语言中是存放在常量区,是一个不可变的量。所以它的地址依然存在,并且值也没有改变。

(3)函数指针
我们先定义一个函数指针后,然后再给这个函数指针传递一个“已经被定义了的函数名”,就可以通过函数指针调用函数了。
下面代码示例一个“求一个数的平方”的例子:

A:定义一个名字叫square的函数;这个函数的参数是int类型,参数名是num;这个函数的返回值是int类型的;这个函数的功能是计算传入的整型参数的平方和并返回。所以返回的也是一个整型。

B:我不想在main函数里面直接调用square函数,我想定义一个函数指针,指向square函数,然后用函数指针调用aquare函数:B中,第一个int表示函数指针fp指向的函数的返回值是int类型的。第二个int表示函数指针fp指向的函数的参数也是一个整型的。

C:C行代码还可以写成:fp=&square;也是可以的!
这里square函数可以直接赋值给函数指针fp是因为:定义fp的时候,定义fp指向的函数,其参数和返回值都是整型,而square函数正好是参数和返回值都是整型,所以完全匹配,所以可以直接赋值。
函数名在C语言中,经过运算后得到的其实就是这个函数的地址!既然是个地址,就当然可以直接赋值给一个指针了。

D:D处还可以这样写:fp(num) 但不建议这么写,因为这样写很容易误解fp是个函数名,而不是一个指针名。 读你代码的人就会很崩溃。

9、将函数指针当作参数来使用
就是将函数指针作为函数的参数进行传递,代码示例:

A是函数calc的一个声明

B:函数calc的第一个参数就是一个函数指针(也就是一个地址参数)--这个函数指针(也就是地址参数)指向一个函数(函数名本质就是一个地址)--指向的函数需要符合这些条件:函数的参数是2个并且都是int类型的、函数的返回值也必须是int类型的。所以在调用函数calc时(C),第一个参数可以给calc传入add函数或者sub函数(因为这两个函数名本质就是一个地址)。而且因为add函数和sub函数都符合calc第一个参数的要求。函数calc第一个参数要求的就是传入一个地址、并且这个地址是一个别的函数的地址、并且别的函数的参数必须是2个且是Int类型且返回值也是int类型。

函数calc有3个参数,第一个参数就是上面说的是一个函数指针,第二个参数是int a ,第三个参数是int b 。

函数calc实现的功能是:把传入calc函数的第二个和第三个参数传递给第一个参数指向的函数,并返回那个函数的指向结果。

其实流程很清晰也不难,就是车轱辘话正着说说反着说说,如果你被说晕了,就别听我的你自己品代码即可。

10、将函数指针作为返回值来使用
场景:让用户输入一个表达式,然后程序根据用户输入的运算符来决定调用add还是sub函数进行运算。
代码实现思路:写一个函数名是select的函数:函数有1个char参数、返回值是一个函数指针。这个指针指向的函数也是有2个int参数、返回值是int类型。然后再把函数指针当作参数传递给calc函数,calc函数再计算。下面是代码示例:

11、局部变量和全局变量

从前面我们写的例子中可以看到:不同函数定义的变量之间是无法互相访问的。
下面示例显示:即使是一个函数内部,一些符合语句,比如for循环语句里面,临时定义的循环条件的变量也是和函数内部其他变量之间是隔离的。

C99标准规定:for语句的第一个表达式是可以定义变量的。所以编译的时候得加-std=c99,否则会报错。
从这个例子可以看出for循环中的变量和函数体内其他的变量互不干涉、互不冲突!原因就是它们的作用域是不同的!

  • 局部变量:在函数里面定义的变量叫局部变量
  • 全局变量:在函数外边定义的变量叫外部变量,也叫全局变量。
    当我们要在多个函数中使用一个共同的变量,那我们就把这个变量定义为一个全局变量。下面是代码简单示例:

说明:
(1)如果没有对全局变量进行初始化,比如上面例子中写成:int sum; 那变量sum就会自动初始化为0。
(2)如果函数内部存在一个和全局变量名字相同的局部变量,没关系,编译器不会报错,并且编译器还会在函数中屏蔽全局变量,就是在这个函数中全局变量不起作用。

(3)C语言规定:定义变量可以在需要的时候再定义。就是说定义变量的时候不一定非得在程序一开始的地方定义。
那如果写了一堆函数后,才想起定义一个全局变量,那前面的函数怎么办?用extern关键子,extern关键子就是告诉编译器:“这个变量我们后面把定义补上了,你先别报错,后面有”。代码示例:

会有人说,真是闲得慌,干嘛放后面,放前面不就啥事没了嘛?!其实这是必要的,因为当我们有多个源文件时,多个源文件要使用同一个变量时,就需要这样操作。在多文件编程中经常会出现。

(4)不要大量使用全局变量。这是一个好的建议。因为:

  • 使用全局变量会使你的程序占用更多的内存,因为全局变量从被定义的时候开始,直到程序退出才会被释放。而局部变量仅是当函数被调用完成之后,就立刻释放了。用C进行底层驱动开发或者嵌入式开发时,内存都是寸土寸金,所以慎用全局变量。

  • 大量使用全局变量会降低程序的可读性。因为大量的全局变量容易和函数中的局部变量名字相同,虽然函数内部可以屏蔽全局变量,但当程序很大的时候,很容易就混淆了每个变量的作用域,而不知道全局变量到底被哪个函数修改过了。

  • 大量使用全局变量会提供程序的耦合性,致使程序是牵一发而动全身,别人难以修改和维护。在模块化程序设计下,我们应该是尽可能地设计一些内聚性强、耦合性弱的模块,就是说,要求函数的功能尽可能的单一,每个函数只实现一个功能,并且与其他函数之间的相互影响尽可能地少。这样改哪个函数不会牵扯到其他函数。

12、作用域
当变量被定义在程序的不同位置时,它的作用范围时不一样的,这个作用范围就是我们所说的作用域。

C语言编译器可以确认4种不同类型的作用域:

(1)代码块作用域
block scope, 就是一个花括号{}里面的范围。比如说整个函数体就是一个代码块作用域。比如函数体内的复合语句也是一个代码块作用域。
在代码块中定义的变量,具有代码块作用域。作用范围就是从变量定义的位置开始,到标志该代码块结束的右大括号}处。
尽管函数的形式参数不在花括号内定义,但其同样具有代码块作用域,隶属于包含函数体的代码块。

(2)文件作用域
任何在代码块之外声明的标识符都具有文件作用域,作用范围是从它们的声明位置开始,到文件的结尾处都是可以访问的。
也就是我们说的全局变量、函数名,这些都时文件作用域。

(3)原型作用域
prototype scope, 原型作用域只适用于那些在函数原型中声明的参数名。函数在声明的时候可以不写参数的名字,但参数类型是必须要写的。其实函数原型的参数名还可以随便写一个名字,不必与形参相匹配,但这样做没有任何意义。

(4)函数作用域
function scope,函数作用域只适用于goto语句的标签,作用将goto语句的标签限制在同一个函数内部,以及防止出现重名标签。

13、定义和声明

  • 当一个变量被定义的时候,编译器会为变量申请内存空间并填充一些值。
  • 当一个变量被声明的时候,编译器就会知道该变量被定义在其他地方。意思是此处没有这个变量也别报错,继续往下找会找到这个变量的定义的。或者说,声明是告诉编译器该变量及其类型已经存在,不需要再为此申请内存空间。
  • 局部变量既是定义又是声明。
  • 定义只能来一次,否则就叫重复定义某个同名变量,一定会出现报错。而声明可以有很多次。

14、链接属性
对于GCC来说,执行gcc test.c 命令,其实就是将你的源文件转化为可执行文件,也就是依次执行下面四个步骤:

  • 预处理(Pre-Processing)-- 对 C 语言进行预处理,生成 test.i 文件
  • 编译(Compiling)-- 将上一步生成的 test.i 文件编译生成汇编语言文件,后缀名为 test.s
  • 汇编(Assembling)-- 将汇编语言文件 test.s 经过汇编,生成目标文件,后缀名为 test.o
  • 链接(Linking)-- 将各个模块的 test.o 文件链接起来,生成最终的可执行文件

链接的过程是将目标文件和相关的库文件,比如你源文件中有printf函数,但你自己其实没有写printf函数,printf函数是在stdio.h文件里面的,所以编译器就会将目标文件和库文件合并、链接,得到可执行程序。

要详细了解整个过程,看这篇文章:编译器的工作流程,《带你学C带你飞》(语法篇),C\C++交流,鱼C论坛 - Powered by Discuz! 写的非常非常棒,每一步都是眼见为实!

一个大型的程序都有好多个源文件构成,而且还会根据不同的功能存放在不同文件夹里面。那么在不同文件中的同名标识符,编译器是根据链接属性来处理的。

  • 在C语言中链接属性有3种:
    (1)external, 外部的,多个文件中声明的同名标识符表示同一个实体。
    (2)internal, 内部的,单个文件中声明的同名标识符表示同一个实体。
    (3)none, 无,声明的同名标识符被当作独立不同的实体。
    只有文件作用域的标识符(比如函数名、全局变量)才能拥有external或者internal的链接属性,其他作用域的标识符都是none属性。
    默认情况下,具备文件作用域的标识符拥有external属性。也就是说,该标识符允许跨文件访问。对于external属性的标识符,无论在不同文件中声明多少次,表示的都是同一个实体。只要编译的时候把所有文件都包含进去,编译器就会去所有的文件里面去查找。

    所以:函数名的作用域是文件范围的,所以函数名有external链接属性的,所以当编译器在源文件中找不到这个函数名的时候,编译器会去其他文件找。同理,上述例子中的全局变量sum也是。
  • 使用static关键子可以使得原先拥有external属性的标识符变为internal属性。
    但是要注意:
    (1)使用static关键字修改链接属性,只对具有文件作用域的标识符生效,对于拥有其他作用域的标识符是另一种功能,我们后面讲。
    (2)链接属性只能修改一次,也就是说一旦将标识符的链接属性变为internal,就无法改回external了。
    当我们想保护某个全局变量不被别的文件随意修改的话,我们就给这个全局变量加上static关键子,这样这个变量就只能是在这个文件中可以被修改,别的文件不能修改。

15、生存期
C语言中的变量有2种生存期:
(1)静态存储期,static storage duration
(2)自动存储期,automatic storage duration

  • 具有文件作用域的变量属于静态存储期,函数也属于静态存储期。属于静态存储期的变量在程序执行期间将一直占据存储空间,直到程序关闭才释放。
  • 具有代码块作用域的变量,一般情况下,属于自动存储期。属于自动存储期的变量在代码块结束时就自动释放存储空间。

16、存储类型
前面讲作用域、链接属性、生存期,其实都是由变量的存储类型来定义的。
存储类型其实是指 存储变量值的存储类型,C语言提供了5种不同的存储类型:auto、register、static、extern、typedef

(1)自动变量 auto

  • 在代码块中声明的变量默认的存储类型都是自动变量,使用auto关键字来描述。
    所以,函数中的形参、局部变量、以及复合语句中定义的局部变量,都是自动变量。
    所以,自动变量拥有代码块作用域、自动存储期、空链接属性(none)。

  • 但是,由于auto存储类型是默认的,所以,不写auto也是没关系的。只有你以防万一这个局部变量和全局变量同名了,要屏蔽同名的全局变量的时候,你就加上auto,就是强调这个一个局部变量,这样你的代码就更加清晰,比如我们把前面的test14的代码改一下:

(2)寄存器变量 register
寄存器是放置在cpu的内部的,比内存离cpu更近,cpu和寄存器中的数据交互可以说几乎没有任何延时。
当一个变量被声明为寄存器变量,那这个变量就有可能被存放到cpu的寄存器中。 说“可能”是因为还有编译器这一关,你虽声明了,但编译器不一定就给你放,编译器有自己的一套自动优化算法,所以也可能被放到寄存器中也可能没被放。没放就被自动视为普通的自动变量auto。

  • 寄存器变量和自动变量很多方面都是一样的,比如,都拥有代码块作用域、自动存储期、空链接属性。
  • 当一个变量被声明为寄存器变量时,你就无法通过取址操作符获得该变量的地址。这是自然的嘛,取址取的是内存的地址,你现在放到寄存器了,怎么可能从内存中取到地址呢?! 而且寄存器就在cpu里面,默认的,寄存器地址是不允许你去获取的!你如果去获取寄存器的地址,是会报错的。

(3)静态局部变量 static

  • static 用于描述具有文件作用域的变量或函数的时候,表示“将其链接属性从external改为internal属性”,此时“具有文件作用域的变量或函数”也就从多文件共享变成了单文件独享的属性(就是只有这个变量或函数所在的文件内,这个变量或函数才能被访问,其他文件不能访问)。这样是我们前面讲static关键字的时候讲的。

  • 当static 用于声明局部变量的时候,那么就可以将局部变量指定为静态局部变量。
    默认情况下,局部变量是auto存储类型的,是拥有代码块作用域、自动存储期、空链接属性(none)的。如果用static来声明局部变量,那这个局部变量就具有了静态存储期,与全局变量一样,要一直到程序结束才会被释放。

其实这里面有个坑:在第一个程序里,如果你int a; 就是不对a初始化赋值的话,哪怕赋值是0,也得赋值,虽然"局部变量如果没有初始化,就和全局变量一样,默认初始化为0",你打印出来的结果也和循环递增效果是一样的!不信你可以试试。但我认为虽然效果一样,但底层的逻辑不一样!因为默认赋0值,和你自己赋0值或者其他值是不一样的。如果第一次循环结束把a的值从默认的0改到1,那第二次循环的时候,虽然是默认赋值,但就默认的是1而非0了,所以打印出来的效果也是递增的效果。

(4)extern 关键字
extern关键字是用于告诉编译器这个变量或函数是在别的地方已经定义过了,现在没找到,先别报错,往后找找,或者往别的文件里找找。加上extern关键子,代码会非常清晰。

test24中的int sum; 只是被声明了一下,而没有定义,如果这里写int sum=123;就是又声明又定义了,那编译运行就会报错,说变量sum被重复定义了。
所以这个代码逻辑是不是就特别不宜别人读,改成下面的代码,别人就一看就非常明白其中的逻辑了:

小结:

  • 变量的定义是定义,初始化是初始化,是2个步骤,是2个互不相关的两步骤。如果你只定义了,那初始化的值到底是什么呢,是默认值0?编译器会先去别的地方找找有没有被初始化,如果没有才默认为0,如果有就按照有的值初始化。
  • 使用auto或者register关键字声明的变量具有自动存储期,而使用static或者extern关键字声明的变量具有静态存储期。

(5)typedef
auto,static, extern都是内存存储。register是寄存器存储。而typedef与内存存储、寄存器存储都没有关系,是用于为数据类型定义一个别名,所以后面讲结构体的时候专门讲解。

17、递归
在函数内部你可以调用其他函数,当然你也可以调用自身,这种函数调用自身的操作就是递归。
递归必须要有结束条件,否则程序将崩溃。

上面代码中如果是for(count--)就会被打印6次。

  • 递归案例1:递归求阶乘,写一个求阶乘的函数

说明:普通程序员用迭代、天才程序员用递归。但是我们建议宁可做一个普通程序员。一是阅读和维护你代码的程序员也是普通程序员。二是,程序代码以实现目标为前提,而递归在程序的执行效率上也并无优势,所以尽量不用。
但是,有的问题用递归去解决会有豁然开朗的感觉,所以视情况而定。

  • 递归案例2:汉诺塔

  • 递归案例3:快速排序
    基本思想是:通过一趟排序将待排序数据分隔成独立的两部分,其中一部分的所有元素均比另一部分的元素小,然后分别对这两部分继续进行排序,重复上述步骤直到排序完成。

#include <stdio.h>

void quick_sort(int array[], int left, int right);
void quick_sort(int array[], int left, int right){
        int i=left , j = right;
        int temp;
        int pivot;
        pivot = array[(left+right)/2];
        while(i<=j){
                while(array[i]<pivot){
                        i++;
                }
                while(array[j]>pivot){
                        j--;
                }
                if(i<=j){
                        temp = array[i];
                        array[i]=array[j];
                        array[j]=temp;
                        i++;
                        j--;
                }
        }

        if(left<j){
                quick_sort(array, left, j);
        }

        if(i<right){
                quick_sort(array, i, right);
        }
}

int main(){
        int array[]={73, 108, 111, 118, 101, 70, 105, 115, 104, 67, 46,99, 111, 109};
        int i, length;
        length = sizeof(array)/sizeof(array[0]);
    
        quick_sort(array, 0, length-1);
    
        printf("排序结果是:");
        for(i=0; i<length;i++){
                printf("--%d", array[i]);
        }
        putchar('\n');

        return 0;
} 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

宝贝儿好

6元以上者可私信获PDF原文档

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值