C语言模块化设计思想
一、函数概述
函数也是空间,代码空间,它也有首地址。
函数名:标记这段代码的标签,不占用空间,即地址常量。和数组名一样,对函数名取地址没有意义,所以这个行为被编译器接管了。
1. 函数的特性
- 语法特性:
输入行为、返回行为 - 逻辑特性:
接收空间(查看与修改)、反馈信息
二、函数指针
定义指针变量空间来存放这个标签:函数的首地址。(其实相当于没有指针函数、函数指针什么的概念)
int (*p0)(int); // 定义了一个指向函数的指针,这里的int可以不加参数,也可以是任意名称的int类型的变量。
int fun(int a);
p0 = fun; //让p0指向fun函数,fun是地址
p0(); //这里相当于fun(),可以当作重命名。由于它是变量,所以在工程中,可以写一个p0在这,然后在其他地方更改p0指向的函数,这样就可以不用重新编译,而是相当于“值改变”了。
1. 函数指针的应用
以第n天干什么为例,如果把要做的事写死,那么修改就要重新编译
int do_read(){}
int do_music(){}
// 函数的常量调用,通过函数名调用功能,也叫函数的静态调用
int day;
switch(day){ // 如果不想第一天do_read(),那么修改就要重新编译
case 1: // 使用switch太慢
do_read();
break;
case 2:
do_music();
break;
}
// 优化:
int (*day[2])(void); //定义两个输入为空的返回值为int类型的指针
day[0] = do_read;
day[1] = do_music;
// 如果哪天要修改,直接赋值即可
day[0] = do_music;
三、函数参数
1. 传递实参
如果上层空间想要让自己空间的值被下层空间修改,那么必须把上层空间的首地址传给子函数。
void getMemory(char **s){ // 由于s的类型是char*,所以这里得再加一层*
s = malloc(100);
strcpy(s,"hello");
}
int main(){
char *s;
getMemory(&s); // 要想修改,得传首地址,也就是需要取地址
}
当参数中出现了二级指针,有2种情况:
- 目的是修改上层空间的一维指针
- 指向了一个char*。如果是这种,通常后面还会跟一个
int n
。
2. 不同点
- 上层空间希望子函数去读取值
// 函数都不用传递地址
int a = 10; fun(a);
char *s = "hello"; fun(s);
- 上层空间希望子函数去修改上层空间的值
// 函数都需要传递地址
int a = 10; fun(&a);
char *s = "hello"; fun(&s);
- 上层空间希望子函数去读取一段空间
void fun(const int *p, int n){}
// 函数一般得传个数
int a[5]; fun(a, 5);
char buf[5]; fun1(buf); // 字符空间比较特殊,可以只传个首地址,因为它的结尾肯定是'\0'。
3. void*是什么
函数在设计时,形参接收上层空间传来的地址的类型有很多种可能,为了避免与上层空间类型的不一致,导致子函数接收类型无法明确表示,C语言提出了一个void *
的类型。
但由于类型的不明确,操作方法也无法确定,所以编译器只能将它强转为char*
类型,一个字节一个字节地处理。因此,在void*
后面必定有类似于size_t nbyte
这种数字。
void *p; // 这一句话仅仅只是说明p是一个可以存地址的盒子,但是没有说如何操作这个盒子(一次操作几位这种)
p[0]; // 这种操作时非法的,因为它没有类型,编译器不知道该怎么做
char *a = (char*)p; // 想用上一行的这种操作,需要强转
a[0]; // 强转后才能用
ssize_t read(int fildes, void *buf, size_t nbyte); // void*后面是nbyte