函数(C语言)

一、函数概述

1、函数

函数:一堆代码的集合,用一个标签(函数名)去描述它

目的:复用化

函数与数组:都是一段连续空间。
区别:函数具备3要素。

指针、数组2要素:大小、读取方式
int *p;
int a[100];

函数具备3要素:
1、函数名(标签、地址)
2、输入参数(输入):承上 / 启下(反向修改)
3、返回值(输出):启下
在定义函数时,必须将3要素告知编译器。

int fun(int, int, char)   // 输入参数的顺序是有严格要求的
{ xxx }

2、如何用指针描述函数?

char *p;   // 指针
char (*p)[10];   // 数组

int (*p)(int, int, char);   // 函数
// 必须与函数的输入参数、输出参数是一样的,函数名替换为指针

3、定义函数与调用函数

int fun(int a,char b)   // 定义函数
{
    xxxx
}
int main()
{
    fun(10,2);   // 调用函数
}

// 类似:
char buf[100];   // 定义数组
buf[10];   // 调用

例1:

// 001.c
#include <stdio.h>
int main()
{
    int (*myshow)(const char *, ...);   // 这样定义的话,myshow读内存的方法和printf是一致的了
    printf("hello world!\n");

    myshow = printf;   // 它们读内存的方法是一样的,因此不进行强制类型转换
    myshow("=========\n");
    return 0;
}

// man 3 printf
int printf(const char *format, ...);

// 注意:不一定非要用printf这样的函数,只要效果一样,可以自己定义!(这个在嵌入式开发中可能会很常用)

二、输入参数

1、调用者与被调者、实参与形参

函数调用时有2个对象:调用者和被调者。

调用者:

函数名(要传递的数据)            //实参:要传递的真实的数据

被调者:函数的具体实现

函数的返回值  函数名(接收的数据)        //形参:函数的具体实现/被调者中,接收数据的形式
{
    xxx
}

传递过程:实参传递数据给形参

传递形式:拷贝(按位逐一赋值的过程)

例2:

// 002.c
#include <stdio.h>

void myswap(int buf) // 在myswap函数中预留了4个字节来等待接收数据
{
    printf("buf is %x\n", buf);
}

int main()
{
    int a = 20;
    myswap(0x123); // 整型常量,占4B(32bit)
    return 0;
}

例3:

// 003.c
#include <stdio.h>

void myswap(int buf) // 在myswap函数中预留了4个字节来等待接收数据
{
    printf("buf is %x\n", buf);
}

int main()
{
    int a = 20;
    myswap(a); // 对变量来说,也是逐一赋值拷贝的过程
    return 0;
}

例4:

// 004.c
#include <stdio.h>

void myswap(char buf) // 在myswap函数中预留了1个字节来等待接收数据
{
    printf("buf is %x\n", buf);
}

int main()
{
    int a = 20;
    myswap(0x1234); // 发送了4个字节(32bit)的数据
    return 0;
}
接收端根据其能力,取其能接收的最多的字节

例5:

// 005.c
#include <stdio.h>

void myswap(int buf)
{
    printf("buf is %x\n", buf);
}

int main()
{
    int a = 20;
    char *p = "hello world!";
    printf("p is %x\n", p);
    myswap(p);
    return 0;
}
不管是传递值也好,地址也好,拷贝的过程都是逐一赋值

2、值传递与地址传递

见:《值传递与地址传递(C语言)》

3、连续空间的传递

为了解决内存空间而提出的解决方案。

场景:
在主函数中有大量连续空间,若是通过值传递,则在形参中也要分配一样大的空间,才能拷贝过去,这对空间的占用会非常大。
若只需要传递连续空间的首地址,就可以进行操作。

实际开发中使用的更多。

(1)数组
数组名 --- 标签

int abc[10];
// 实参
fun(abc)    // 直接用地址传递,abc是一个标签/地址。
// 形参:
void fun(int *p)       
void fun(int p[10])    // 这种写法也可以!
// 10只是给人看的,告诉人有10个int大小的空间;
// C语言编译器不会管这个10,C语言编译器只是把p当做一个地址。

数组的函数与函数之间的调用,使用地址传递

(2)结构体
结构体变量

struct abc{int a; int b; int c;};
struct abc buf;

// 值传递
fun(buf);    // 实参
void fun(struct abc a1)    // 形参

// 地址传递     
fun(&buf)    // 实参
void fun(struct abc *a2)    // 形参

结构体的函数与函数之间的调用,使用地址传递

4、字符空间与非字符空间的操作

见:《字符空间与非字符空间的操作(C语言)》


三、返回值

1、它是提供启下功能的其中一种表现形式。

(1)启下功能的2大表现形式:

  • 返回值
  • 输入参数的地址传递

(2)效果不同:

通过函数返回值返回一个值,该值与返回值类型一样;
通过输入参数“启下”,根据被调函数内部处理的不同,可能反向修改后的值很不同。

(3)通过返回值的“启下”方式,可以改成通过输入参数的地址传递“启下”的方式!

案例1:返回一个基本数据类型

  // 通过返回值“启下”
  int fun1(void);
  int a = 0;
  a = fun1();    // a会改变

  // 通过输入参数的地址传递来“启下”
  void fun2(int *p);   // 想返回一个值,就把值的地址*写在输入参数处
  int a = 0;
  fun2(&a);    // a会改变

案例2:想要返回2个值(“启下”2个值):

int[2] fun(void);    // 错误!没有这种写法。
int fun(int *);    // 返回值只能返回1个,另1个使用输入参数的地址传递来反向修改!

案例3:返回一个地址(指针)

// 通过返回值“启下”
int *fun1(void);    // 返回值为1个地址(指针)
int *p;
p = fun1();

// 通过输入参数的地址传递来“启下”
void fun2(int **p);    // 想返回一个指针*,就把指针*的地址**写在输入参数处

总结:

输入参数中若是int *p,则反向修改一个值。

输入参数中若是int **p,则反向修改一个地址。

字符空间与非字符空间的操作(C语言)

2、基本语法

返回类型:基本数据类型、地址(指针)类型(只能返回连续空间类型,不能返回数组)。

返回值只能是1个。

调用者:

a = fun();    // 调用者用 = 去接收这个值
// 调用者如果不用 = 去接收被调者的返回值的话,被调者的返回值就消失掉了。   当然,可以通过汇编语言的一些方式去找回它。

被调者:

int fun()
{
    return num;    // 被调者返回一个值
}

被调者返回一个值,调用者接收了这个值,过程本质:拷贝

例6:

// 006.c
#include <stdio.h>
int fun(void)
{
    return 0x123;    // 返回一个int值
}

int main()
{
    int ret;
    ret = fun();
    printf("ret is %x\n", ret);
    return 0;
}
结果是123

例7:

// 007.c
#include <stdio.h>
char fun(void)
{
    return 0x123;    // 返回一个char值:低8位:23
}

int main()
{
    int ret;
    ret = fun();
    printf("ret is %x\n", ret);
    return 0;
}
返回char值(低8位:23)

例8:

// 008.c
#include <stdio.h>
int fun(void)
{
    int a = 0x123;
    int *p = &a;
    return p;    // 指针也是int,32位,也可以返回
}

int main()
{
    int ret;
    ret = fun();
    printf("ret is %x\n", ret);
    return 0;
}
指针也是int,也可以返回

3、函数返回地址(指针)类型

返回地址,返回的就是连续空间。

int *fun();    // 返回一个地址(指针)

需要先考虑一个问题:地址指向的合法性。

例9:

// 009.c
#include <stdio.h>

char *fun(void)
{
    char buf[] = "hello world";    // buf是局部变量
    return buf;    // fun函数一旦return后,buf就消失了
}

int main()
{
    char *p;    // 申请一个一模一样的地址类型,去接收
    p = fun();
    printf("p is %s\n", p);    // 看一下指针是啥
    return 0;
}
警告:函数返回了一个局部变量。p指向了一个乱七八糟的东西,而不是hello world

作为函数的设计者(程序员),必须保证函数返回的地址所指向的空间是合法的,比如常量区、数据段区和堆区(不是局部变量一般就没问题)。

例10:

// 010.c
#include <stdio.h>

char *fun(void)
{
    return "hello world";    // 双引号字符串在常量区,常量区生命周期不会随着函数的返回而消失,其地址在return后不会被改变
}

int main()
{
    char *p;
    p = fun();
    printf("p is %s\n", p);
    return 0;
}
成功打印出hello world

作为函数的使用者,定义一个与函数返回类型一模一样的地址类型去接收就可以了。

int *fun();    // 函数声明
int *p = fun();    // 定义一个一模一样的类型去接收

4、函数返回类型内部实现

(1)返回基本数据类型

第一种:返回一个具体的基本数据类型值

基本数据类型 fun(void)
{
    基本数据类型 ret;    // 定义一个一模一样类型的变量
        // 对ret做了一定的赋值操作等处理
    return ret;    // 将变量返回
}

int fun()
{
    int ret;
    ret = 5;
    return ret;    // 返回一个具体的int值:5
}

第二种:返回一个函数状态标识(成功与否的标识)

(2)返回地址(指针)类型

作为函数的设计者(程序员),必须保证函数返回的地址所指向的空间是合法的,比如常量区、数据段区和堆区(不是局部变量一般就没问题)。

例10:第一种:返回常量区(整个程序结束,其生命周期才结束)

这种方式在工程上意义不大。因为通过调用函数获取一个常量的话,还不如直接将该常量赋值呢。

例11:第二种:static局部变量,再返回静态化后的数据段区

// 011.c
#include <stdio.h>

char *fun(void)
{
    static char buf[] = "hello world";    // 把局部变量静态化,该变量就到了数据段
    return buf;
}

int main()
{
    char *p;    // 申请一个一模一样的地址类型,去接收
    p = fun();
    printf("p is %s\n", p);    // 看一下指针是啥
    return 0;
}
成功返回hello world

例12:第三种:返回堆区(malloc申请、free释放)

// 012.c
#include <stdio.h>
#include <string.h>
#include <stdlib.h>

char *fun(void)
{
    char *s = (char *)malloc(100);    // 1、申请空间(多大空间、强转类型)
    // malloc申请空间,返回的是void *,因此使用时必须要强制转换成具体的类型
    strcpy(s, "hello world");    // 2、申请完后初始化,初始化后去用
    return s;
}

int main()
{
    char *p;
    p = fun();
    printf("p is %s\n", p);    
    free(p);    // 3、用完后释放掉
    // free的是p,不是s。因为s在fun函数return时就消失了,而p存的是s中的值
    return 0;
}
成功返回hello world
// man malloc:
#include <stdlib.h>

void *malloc(size_t size);
void free(void *ptr);

// malloc使用三部曲:
// 1、申请空间(多大空间、强转类型)
// 2、申请完后初始化,初始化后去用
// 3、用完后释放掉

小结:

  • 输入参数中的*具有四义性(传递值、地址、字符空间、非字符空间),这种多义性可以通过不同的修饰符来区分。

《字符空间与非字符空间的操作(C语言)》

  • 返回值中的*具有二义性(静态数据段区、堆区)。
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,723评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,485评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,998评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,323评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,355评论 5 374
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,079评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,389评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,019评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,519评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,971评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,100评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,738评论 4 324
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,293评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,289评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,517评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,547评论 2 354
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,834评论 2 345

推荐阅读更多精彩内容

  • 原文地址:C语言函数调用栈(一)C语言函数调用栈(二) 0 引言 程序的执行过程可看作连续的函数调用。当一个函数执...
    小猪啊呜阅读 4,590评论 1 19
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,600评论 18 139
  • 第5章 引用类型(返回首页) 本章内容 使用对象 创建并操作数组 理解基本的JavaScript类型 使用基本类型...
    大学一百阅读 3,212评论 0 4
  • 刚开始被刺激到要学游泳,但觉得学游泳很可怕,一想起来就觉得水太冷,学不会,一直抗拒。家里人也反对,没有任何原...
    kalinsame阅读 360评论 0 0
  • 羽扇纶巾,是谋士;折扇飘衣,是侠(仙)士。 01 自小看惯了“武侠”,再看cosplay那一套,简直有点像小孩子过...
    驿路奇奇阅读 794评论 17 26