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种情况:

  1. 目的是修改上层空间的一维指针
  2. 指向了一个char*。如果是这种,通常后面还会跟一个int n

2. 不同点

  1. 上层空间希望子函数去读取
// 函数都不用传递地址
int a = 10; fun(a);	
char *s = "hello"; fun(s);
  1. 上层空间希望子函数去修改上层空间的值
// 函数都需要传递地址
int a = 10; fun(&a);	
char *s = "hello"; fun(&s);
  1. 上层空间希望子函数去读取一段空间
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

SUFE大二在读