c高级可以说是就比较难了,尤其是c高级函数这是学习的难点,那么如何快速学c高级函数呢,下面总结了一些函数知识点及案例,可以快速学哦。
1.1定义
返回值类型 函数名(类型 形参, 类型 形参, ...)
{
语句
语句
return 返回值
}
函数名:标识符:用一眼要能看懂函数的功能的标识符来表示函数名
返回值类型:需要返回的返回值类型
当不需要返回值时,返回值类型为void,return后边需要加返回值
当返回值类型为指针时,称为指针函数
形参: 接收外部参数的局部变量
不需要接收参数时, 写为void
6.2函数的调用
变量 = 函数名(实参1, 实参2, ......);
实参:传给函数的变量或常量值。
运行过程:为形参分空间,把实参赋值给形参
运行函数中的程序
返回调用函数,运行调用点的下一行
补充:多窗口:vsp
1.3函数的作用范围
1.3.1 提前声明,扩大定义域(extern)
实例:
1.3.2 在文件中,调用另一个文件中定义的函数
编译命令:gcc a.c b.c -g:编译两个文件的时候,执行依然是a.out
1.3.3. 禁止别的文件调用本文件的函数(static)
注意:用了statics的话,不同文件中是可以存在相同的文件名的。
stati将函数的作用范围限制在本文件中
1.4变量的作用范围(这里详细的打开内存分配的部分及逆行讲解)
1.4.1. 定义
全局变量:定义在函数外的变量,也可以理解为定义在{}外的变量。
局部变量:定义在函数内的变量,也可以理解为定义在{}内的变量。
需要定义在{}的开始。
1.4.2 作用域(默认作用范围)
全局变量:从定义开始到文件的结束
局部变量:从定义开始到与之前一个对应的{ }结束。
注意:小作用范围的变量,屏蔽大作用范围的变量
解释:
这里的扩大作用范围是针对全局变量来说的,局部变量不能扩大
方法:1)使用的位置在定义之前
2)使用的位置子其他文件
3)限制全局变量的作用范围到默认作用域
4)函数的作用域是从定义开始,到本文件结束(针对文件来说)
实例:
1.5内存的分配
1.5.1.程序未运行时(size a.out可以看到信息)
text:存放CPU可执行的机器指令,由于程序被经常使用,防止其被意外修改,代码区通常是只读的。
data: 存放被初始化的全局变量、静态变量(全局静态变量和局部静态变量)、常量数据(如字符串常量)。
Bss:存放未初始化的全局变量 (初始化成0,计算机并不认为这是做了初始化操作,所以初始化成0的全局变量会被放在bss区中)
注意:BSS区的数据在程序开始执行之前被内核初始化为0或空指针(NULL)。
具体:
1.5.2.程序运行时
程序运行时占用5个区:代码区、初始化数据区/静态数据区、未初始化数据区、堆区、栈区
(1)代码区(text)
代码区指令根据程序设计流程依次执行,对于顺序指令,则只会执行一次,如果反复,则需使用跳转指令,如果进行递归,则需借助栈来实现。
代码区包括操作码和要操作的对象(或对象的地址引用),如果是立即数(即具体的数值,如2),将直接包含在代码中;如果是局部数据,将在栈中分配空间,然后引用该数据的地址;如果是BSS区和数据区,在代码中同样引用该数据的地址。
(2) 全局初始化数据区/静态数据区(data)
在编译的时候就会初始化,并且只初始化一次。上面已经说过,在程序编译时,该区域已经被分配好了,这块内存在程序的整个运行期间都存在,当程序结束时,才会被释放。
(3)未初始化数据 区(BSS):在运行时改变其值。初始化0不算初始化。
(4)栈区(stack)
存放函数的参数值和局部变量,由编译器自动分配释放,其操作方式类似于数据结构的栈。其特点是不需要程序员去考虑内存管理的问题,很方便;同时栈的容量很有限,在Linux系统中,栈的容量只有8M,并且当相应的范围结束时(如函数),局部变量就不能再使用。
(5)堆区(heap)
有些操作对象只有在程序运行时才能确定,这样编译器在编译时就无法为他们预先分配空间,只有程序运行时才分配,这就是动态内存分配。堆区就是用于动态内存分配(如malloc的动态内存分配),堆在内存中位于bss区和栈区之间,一般由程序员申请和释放(free释放或程序停止)。
实例:
注意:
(1):堆区的内存需要程序员自己释放。
(2):栈区在范围结束时,编译器就会释放了,所以要注意栈区中数据的生命周期。
(3):栈区容量有限,注意不要使用超大局部变量,比如超大数组。
(4):bss区中的数据,你不进行初始化的话,系统会帮我们初始化成0。但其他区域的数据,系统不会为我们做什么,尤其注意堆区和栈区中的数据,未经初始化的变量则有可能是脏数据。
1.6变量的生命周期
1.6.1分类
1. data和bss中的变量的生命周期:程序的开始到程序的结束。==>全局变量,静态(全局/局部)变量
2. 栈中的变量的生命周期:函数的开始到函数的结束。==>局部变量,形参
6.6.2对静态局部变量的说明:
有时希望函数中的局部变量的值在函数调用结束后不消失而保留原值,即其占用的存储单元不释放,在下一次该函数调用时,该变量保留上一次函数调用结束时的值。这时就应该指定该局部变量为静态局部变量(static local variable)。
(1) 静态局部变量在静态存储区内分配存储单元。在程序整个运行期间都不释放。而自动变量(即动态局部变量)属于动态存储类别,存储在动态存储区空间(而不是静态存储区空间),函数调用结束后即释放。
(2) 为静态局部变量赋初值是在编译时进行值的,即只赋初值一次,在程序运行时它已有初值。以后每次调用函数时不再重新赋初值而只是保留上次函数调用结束时的值。而为自动变量赋初值,不是在编译时进行的,而是在函数调用时进行,每调用一次函数重新给一次初值,相当于执行一次赋值语句。
(3) 如果在定义局部变量时不赋初值的话,对静态局部变量来说,编译时自动赋初值0(对数值型变量)或空字符(对字符型变量)。而对自动变量来说,如果不赋初值,则它的值是一个不确定的值。这是由于每次函数调用结束后存储单元已释放,下次调用时又重新另分配存储单元,而所分配的单元中的值是不确定的。
(4) 虽然静态局部变量在函数调用结束后仍然存在,但其他函数是不能引用它的,也就是说,在其他函数中它是“不可见”的。
具体解释:
初始化全局变量未初始化全局变量局部变量静态全局变量静态局部变量
占用存储空间data
bss栈datadata
初始化次数初始化一次
初始化一次每次调用都初始化初始化一次初始化一次
作用域默认作用域,可扩大默认作用域,可扩大默认作用域默认作用域默认作用域
生命周期程序开始->程序结束 程序开始->程序结束定义开始,函数结束程序开始->程序结束程序开始->程序结束
补充:register变量
register int r_local = 6;要求编译器尽量放在寄存器,register变量是不能做取地址操作的
register使用寄存器变量会提升速度,也并不是说我们尽可能地多地定义register变量就能加快程序的运行速度,毕竟CPU中寄存器是有限的,
如果你把变量指定为register变量,意味着可用于别的用途的寄存器就减少了,如程序运算产生的中间结果,它们的应用又很频繁,
在寄存器不足的情况下,只好借助于内存,这样反倒会降低程序的运算速度。
在现今的C版本中,大多已没有定义register变量的必要,因为编译程序忽略register修饰符,而根据寄存器的使用情况和变量的情况决定是否把变量解释为register变量。
1.7函数的传参
1.7.1 指针参数
补充:还有种应用场景,就是需要使用结构体作为参数的时候,因为形参消耗的是栈的空间,栈的空间是有限的,如果结构体比较大,一般不建议直接将结构体作为形参,而是通过指针对其进行访问。
1.7.2 数组参数
int atoil(char str[10])
int atoil(char str[])
int atoil(char *str)
这三种定义的形式不同,但是达到的效果是一模一样的,不管使用哪一种定义,编译器都会将其转换成第三种形式。
1.8指针函数 VS 函数指针
1.8.1 指针函数(pointfunc.c)
确定方法:
(1)确定目标类型 int buf[5]
(2)确定指针类型 在目标类型基础上加* 加在变量名位置 int (*)[5]
(3)定义指针变量 int (*p)[5]
实例:
1.8.2 函数指针:首先是指针,指向的是函数。
(1) 定义
void swap(int *a, int *b)
void (*pfunc)(int *, int *);
(2) 赋值
pfunc = &swap;
pfunc = swap; (函数名, 就是函数的指针常量)
注意:函数类型和函数指针类型兼容,上边两种定义形式可以随便选择使用
(3) 调用
(*pfunc)(&a, &b);//将swap理解成函数
pfunc(&a, &b); //将swap理解成函数指针,这两种调用随便选择,跟上边的定义是没有关系的。
实例:
补充:strcmp 比较字符串大小 qsort 可以快速排序
用法:
shrcmp
strcmp()函数是通过两个字符串一个一个字符比较的(最多比较次数为第二个参数的长度+1)
例如strcmp("hello","here");
首先比较第一个字符'h'= 'h'相等
接着比较第二个字符'e'= 'e'相等
比较第三个字符 'l'>'e',返回一个正值
如果字符串完全相等会回0
qsort:
qsort是万能数组排序函数,必须要学会使用,简单的数组自然不用说,这里主要讨论一下字符串数组的使用。
首先看一下qsort的原型:
void qsort(void *base, size_t nmemb, size_t size,
int(*compar)(const void *, const void *));
正确使用这个函数要注意几点:
1.base要传数组的首地址
2.size传的是每个元素的大小
3.正确编写compar函数
下面是实际应用:
一个字符串数组:*str[MAX],假设里面现在保存了n个字符串了。
首先要正确理解什么是字符串数组,简单的说,可以理解成它就是一个数组,只不过其中的元素是一串字符串,而访问这些字符串,得用指针,也就是它们的地址,比如*name[]={"james","henry"},那么访问其中的字符串就是name[0],name[1]...这里就有个容易混淆的地方了,对于字符串数组,那么每个元素的大小到底是多少呢?对name[0]来说,到底是字符串“james”的长度5,还是char*的大小4呢?答案应该是4,因为字符串数组里面保存的是各个字符串的指针,所以回到上面所说的第二点注意,用qsort的时候应该要传sizeof(char *);
第二,编写compar函数比较字符串有地方要注意:
不能把strcmp本身传给qsort,即不能写strcmp(p,q),因为形参是const void*类型,同理,写成strcmp((char *)p, (char *)q);也是无效的;正确的写法应该是:strcmp(*(char **)p, *(char **)q);先强制转换成char**,在用*减少一层间接寻址操作:
int compar_words(const void *p, const void *q)
{
return strcmp(*(char **)p, *(char **)q);
}
对于上面的应用,最后使用qsort应该是这样:
qsort(str, n, sizeof(char *), compar);
实例:比较数组中的字符串的大小
1.9递归函数
定义:直接或间接调用自己
注意:递归程序的效率比较低,空间占用的也很多,唯一的理由是比较容易看懂。
例: 利用函数递归实现n!
1. 写出递归通用项:n! = n * (n - 1)!
2. 写出递归结束条件:n = 1
3. 代码实现:假设函数已经写好,可以用调用,不要去想计算机执行的具体过程
4. 单步执行, 掌握执行流程
实例:实现阶乘 将输入的字符串反向输出。