0
点赞
收藏
分享

微信扫一扫

【C 语言】C 语言 函数 详解 ( 函数本质 | 顺序点 | 可变参数 | 函数调用 | 函数活动记录 | 函数设计 ) [ C语言核心概念 ]



相关文章链接​ :
1.​​【嵌入式开发】C语言 指针数组 多维数组​​
2.​​【嵌入式开发】C语言 命令行参数 函数指针 gdb调试​​
3.​​【嵌入式开发】C语言 结构体相关 的 函数 指针 数组​​
4.​​【嵌入式开发】gcc 学习笔记(一) - 编译C程序 及 编译过程​​
5.​​【C语言】 C 语言 关键字分析 ( 属性关键字 | 常量关键字 | 结构体关键字 | 联合体关键字 | 枚举关键字 | 命名关键字 | 杂项关键字)​​
6.​​【C 语言】编译过程 分析 ( 预处理 | 编译 | 汇编 | 链接 | 宏定义 | 条件编译 | 编译器指示字 )​​
7.​​【C 语言】指针 与 数组 ( 指针 | 数组 | 指针运算 | 数组访问方式 | 字符串 | 指针数组 | 数组指针 | 多维数组 | 多维指针 | 数组参数 | 函数指针 | 复杂指针解读)​​




  • ​​一. 函数本质​​
  • ​​1. 函数意义​​
  • ​​(1) 函数来源​​
  • ​​(2) 模块化程序设计​​
  • ​​2. 面向过程的程序设计​​
  • ​​(1) 程序结构​​
  • ​​3. 函数的声明和定义​​
  • ​​(1) 声明 和 定义 的区别​​
  • ​​(2) 代码示例 ( 函数 声明 和 定义区别 )​​
  • ​​二. 参数 可变参数 顺序点 类型缺省认定​​
  • ​​1. 函数参数​​
  • ​​(1) 参数分析​​
  • ​​(2) 代码示例 ( 函数参数 求值顺序 )​​
  • ​​2. 程序中的顺序点​​
  • ​​(1) 顺序点简介​​
  • ​​3. C 语言 函数 的 缺省认定​​
  • ​​(n) 标题3​​
  • ​​4.可变参数 的 定义 和 使用​​
  • ​​(1) 简介​​
  • ​​(2) 代码示例 ( 定义 使用 可变参数 )​​
  • ​​三. 函数 与 宏​​
  • ​​1. 函数 与 宏 对比案例​​
  • ​​(1) 函数 和 宏 的案例​​
  • ​​2. 函数 和 宏 的分析​​
  • ​​(1) 函数 和 宏 分析​​
  • ​​3. 函数 与 宏 的 利弊​​
  • ​​(1) 宏 优势 和 弊端​​
  • ​​(2) 函数 的 优势 和 弊端​​
  • ​​(3) 宏的无可替代性​​
  • ​​4. 总结​​
  • ​​(1) 宏 定义 和 函数 总结​​
  • ​​四. 函数的调用约定​​
  • ​​1. 函数的活动记录 分析​​
  • ​​(1) 函数的活动记录​​
  • ​​2. 函数的调用约定概述​​
  • ​​(1) 参数入栈 问题描述​​
  • ​​(2) 参数传递顺序的调用约定​​
  • ​​五. 函数设计技巧​​







一. 函数本质



1. 函数意义


(1) 函数来源


C 程序结构 由 数据 和 函数 组成;


函数是由汇编跳发展而来的​ :

  • 1.汇编操作​: 汇编语言中由一系列的指令组成, 这些指令从上到下顺序执行,
  • 2.跳转操作​: 汇编中需要做分支循环操作的时候, 就是使用跳转指令;
  • 3.指令代码模块​: 在汇编中有一组指令代码, 总是需要执行这一组代码, 需要时跳转到该代码处执行, 执行完毕后在跳转回去, 这就是一个函数的雏形;
  • 4.发展​: 跳转过来 和 跳转回去 相当于函数的 入栈 和 出栈;


(2) 模块化程序设计

模块化程序设计 :

  • 1.思想​: 复杂问题拆解, 将一个复杂问题拆解成一个个的简单问题, 这些简单问题就可以作为一个个的函数来编写;
  • 2.C语言程序​: 将一个复杂的程序拆解成一个个模块 和 库函数;


一个复杂的 C 语言程序有几十上百万行代码, 这些代码可以分解成若干模块来实现, 即分解成一个个的函数来实现.




2. 面向过程的程序设计


(1) 程序结构

面向过程程序设计思想 :

  • 1.中心​: 整体的设计 以 过程 为中心;
  • 2.问题分解​: 将复杂问题分解为若干容易解决的问题;
  • 3.函数体现​: 面向过程 的思想在 C 语言 中的核心就是 函数;
  • 4.分解函数​: 复杂问题 分解后的过程可以分为一个个函数一步步实现;



3. 函数的声明和定义


(1) 声明 和 定义 的区别

声明和定义的区别 :

  • 1.声明​: 程序中 声明 只是​告诉编译器 某个 实体 存在​, 这个实体可以是​变量​或者​函数​等;
  • 2.定义​: 程序中定义 指的就是​某个实体 ( 函数 或 变量 ) 的实际意义​;


在 test_1.c 中定义变量 int i = 10; 这是定义了 int 类型的变量, 需要为该变量分配内存空间;
在 test_2.c 中声明变量 extern int i; 这是声明了 int 类型的变量, 变量定义在了别的文件中, 不必为该变量分配内存空间;



(2) 代码示例 ( 函数 声明 和 定义区别 )

代码示例 :

  • 1.代码 test_1.c​:
#include <stdio.h>

//声明 : 声明外部变量, 该值是在其它文件中定义的
extern int global_int;

//声明 : 声明函数 plus, 该函数定义在下面
int plus(int i, int j);

int main()
{
//声明 : 声明函数 square, 如果不声明编译时会报错, 该声明只在 main 函数中有效果, 在main函数之外使用该方法就会报错
int square(int i);

//使用函数 square, 如果没有声明, 编译会报错
global_int = 3;
printf("%d\n", square(global_int));

//使用函数 plus, 如果没有声明编译会报错
printf("%d\n", plus(1, 2));

return 0;
}

//定义 : 定义函数 plus
int plus(int i, int j)
{
return i + j;
}

//定义 : 定义函数 square
int square(int i)
{
return i * i;
}
  • 2.代码test_2.c​:
//定义 : 定义变量, 在这里需要为变量分配内存空间
int global_int;
  • 3.编译运行结果​:
    【C 语言】C 语言 函数 详解 ( 函数本质 | 顺序点 | 可变参数 | 函数调用 | 函数活动记录 | 函数设计 ) [ C语言核心概念 ]_函数与宏





二. 参数 可变参数 顺序点 类型缺省认定



1. 函数参数


(1) 参数分析

函数参数分析 :

  • 1.本质​: 函数参数的本质 与 局部变量 基本相同, 这两种数据都存放在栈空间中 ( 中间隔着 返回地址 寄存器 EBP 数据 )​​详情参考上一篇博客内存管理​​ ;
  • 2.参数值​: 函数调用的​初始值​是 函数调用时的​实参值​;

函数参数的求值顺序​ ​(盲点)​ :

  • 1.实现​: 函数参数的求值顺序​依赖 编译器的实现​;
  • 2.操作数顺序没有在规范中​: C 语言规范中没有规定函数参数必须从左到右进行计算赋值;
  • 3.运算符编程注意点​: C语言中​大多数的运算符的操作数求值顺序也是不固定的​,​依赖于编译器的实现​;
  • 4.示例​: 如 int ret = fun1() * fun2(); fun1 和 fun2 函数哪个先执行, 哪个后执行 不一定;


编程时尽量不要编写的代码依赖于操作数的实现顺序​;



(2) 代码示例 ( 函数参数 求值顺序 )

代码示例 :

  • 1.代码​:
#include <stdio.h>

int fun(int i, int j)
{
printf("%d, %d\n", i, j);
}

int main()
{
int m = 1;

fun(m, m ++);
printf("%d\n", i);

/*
打印出来的结果是 2, 1 \n 2
分析 : 函数的参数的求值顺序 不是 从左到右的, 是不固定的
这个顺序是编译器制定的, 不同编译器该顺序不同
*/

return 0;
}
  • 2.编译运行结果​:
    【C 语言】C 语言 函数 详解 ( 函数本质 | 顺序点 | 可变参数 | 函数调用 | 函数活动记录 | 函数设计 ) [ C语言核心概念 ]_顺序点_02


分析 :
函数参数计算说明​ : fun(m, m ++); 进入函数体之前先计算 m 和 m++ 的值, m 和 m++ 是实参, 在计算完成之后才赋值给 i 和 j 形参;
顺序点​ : 在进入函数体前是一个顺序点, 需要将计算完毕的实参 赋值给形参;
实参 m 赋值​ : 赋值给 形参 i, 此处已经到达顺序点, m 自增操作已经反映到内存中, 因此 从 内存中获取的 i 的值是 2;
实参 m++ 赋值​ : 赋值给 形参 j, m++ 表达式的计算结果是 1, 因此 j 的值是1;




2. 程序中的顺序点


(1) 顺序点简介

顺序点介绍 :

  • 1.顺序点位置​: 顺序点存在于程序之中;
  • 2.顺序点定义​: 顺序点是 代码 执行过程中,​修改变量值 的 最晚时刻​;
  • 3.顺序点操作​: 程序运行到顺序点时,​之前的代码操作 都要反映到后续访问中​;

顺序点列举 :

  • 1.表达式结束​: 每个表达式结束都是顺序点, 以分号 “;” 结尾,​每个分号的位置都是顺序点​;
  • 2.某些表达式的运算对象计算​:​&&​,​|| (逻辑运算)​,​? :(三目运算符)​,​逗号​表达式 中​每个 运算对象计算后​是顺序点;
  • 3.函数运行前​: 函数调用并且在执行函数体之前, 所有实际参数求值完之后是一个顺序点, 如参数是表达式, 需要将表达式计算出结果;

顺序点代码示例 :

#include <stdio.h>

int fun(int i, int j)
{
printf("%d, %d\n", i, j);
}

//注意 : 这个知识点可能过时, k = k++ + k++; 在 Ubuntu 中执行结果是 5

int main()
{
//顺序点 : 在 k = 2; 表达式以分号结束, 这是一个顺序点
int k = 2;
int a = 1;

/*
顺序点 : 分号结尾处是顺序点, 该顺序点

第 1 个 k++, 计算时 k 先是 2, 自增操作到顺序点时执行;
第 2 个 k++, 计算时 k 还是 2, 自增操作到顺序点时执行;
加法计算完毕后 k 变成 4, 两次自增后变为 6
*/
k = k++ + k++;

printf("k = %d\n", k);

/*
a-- && a 进行逻辑运算,
其中 && 是顺序点, a-- 在 && 时执行 自减操作,
然后 a-- 结果变成了 0, a 的值也变成了 0, 进行逻辑与操作结果为 0
*/
printf("a--&&a = %d\n",a--&&a);


return 0;
}



3. C 语言 函数 的 缺省认定


(n) 标题3

函数缺省认定简介 :

  • 1.描述​: C 语言中 默认​没有类型的 参数 和 返回值 为 int 类型​;
  • 2.举例 :
fun(i)
{
return i
}

等价于

int fun(int i)
{
return i;
}
  • 3.代码示例​:
#include <stdio.h>

//函数缺省认定 : 没有类型的 参数 和 返回值 为 int 类型
fun(i, j)
{
return i + j;
}

int main()
{
printf("fun(i, j) = %d\n",fun(3, 3));

return 0;
}

【C 语言】C 语言 函数 详解 ( 函数本质 | 顺序点 | 可变参数 | 函数调用 | 函数活动记录 | 函数设计 ) [ C语言核心概念 ]_函数与宏_03



4.可变参数 的 定义 和 使用


(1) 简介

可变参数简介 :

  • 1.描述​: 函数可以接收的参数个数是不定的, 根据调用的需求决定有几个参数;
  • 2.依赖头文件​: 如果要使用可变参数, 需要导入 stdarg.h 头文件;
  • 3.核心用法​:​va_list​,​va_start​,​va_end​,​va_arg​配合使用, 访问可变参数值;

可变参数示例 :

  • 1.函数名相同, 参数个数不同​: open 函数, 有两种用法, 一个有 2 个参数​int open(const char *pathname, int flags)​, 一个有三个参数​int open(const char *pathname, int flags, mode_t mode)​, C 语言中明显没有重载, 这里是用可变参数来实现的 ; 使用 man 2 open 命令查看 open 函数的文档;
    【C 语言】C 语言 函数 详解 ( 函数本质 | 顺序点 | 可变参数 | 函数调用 | 函数活动记录 | 函数设计 ) [ C语言核心概念 ]_函数活动记录_04

可变参数的注意点 :

  • 1.取值必须顺序进行​: 读取可变参数的值时, 必须从头到尾按照前后顺序读取, 不能跳过任何一个参数;
  • 2.必须确定1个参数​: 参数列表中必须有一个命名确定的参数;
  • 3.可变参数数量无法确定​: 使用 va_arg 获取 va_list 中的值时, 无法判断实际有多少个参数;
  • 4.可变参数类型无法确定​: 使用 va_arg 获取 va_list 中的值时, 无法判断某个参数是什么类型的;


依次读取可变参数时, ​注意 可变参数 的 数量 和 类型, 每个位置的参数 是 什么类型, 一定不要读取错误​, 否则会产生不可预测的后果;



(2) 代码示例 ( 定义 使用 可变参数 )

代码示例 :

  • 1.代码​:
#include <stdio.h>
#include <stdarg.h>

/*
定义可变参数 :
① 列出第一个参数 int a
② 使用 ... 表明后面有 个数不定 并且 类型不定 的 参数
*/
double avg(int arg_count, ...)
{
va_list args;
int i = 0; //循环控制变量
double sum = 0; //统计参数之和

//初始化列表, 让列表准备取值
va_start(args, arg_count);

for(i = 0; i < arg_count; i ++)
{
//从可变参数列表中获取数据, 数据类型是 int 类型
sum = sum + va_arg(args, int);
}

//结束使用可变参数列表
va_end(args);


return sum / arg_count;
}

int main()
{
//使用定义了可变参数的函数, 传入 11 个参数
printf("%f\n", avg(10, 1, 2, 3, 4, 5 , 6, 7, 8, 9, 10));
//使用定义了可变参数的函数, 传入 4 个参数
printf("%f\n", avg(3, 444, 555, 666));

return 0;
}
  • 2.编译运行结果​:
    【C 语言】C 语言 函数 详解 ( 函数本质 | 顺序点 | 可变参数 | 函数调用 | 函数活动记录 | 函数设计 ) [ C语言核心概念 ]_函数活动记录_05





三. 函数 与 宏



1. 函数 与 宏 对比案例


(1) 函数 和 宏 的案例

代码示例 :​ 分别使用 函数 和 宏 将数组数据清零;

  • 1.代码​:
#include <stdio.h>

/*
定义宏 : 这个宏的作用是将 p 目前是 void* 类型, 转为 char* 类型,
将后将每个字节的内容都设置为 0
*/
#define RESET(p, len) while(len > 0) ((char*)p)[--len] = 0;

/*
定义函数 : 也是将 p 指向的 len 字节的内存置空
*/
void reset(void* p, int len)
{
while(len > 0)
{
((char*)p)[--len] = 0;
}
}

int main()
{
//1. 定义两个数组, 用函数 和 宏 不同的方式重置数据
int array1[] = {1, 2, 3};
int array2[] = {4, 5, 6, 7};

//2. 获取两个数组大小
int len1 = sizeof(array1);
int len2 = sizeof(array2);

//3. 定义循环控制变量
int i = 0;

//4. 打印两个数组处理前的数据
printf("打印array1 : \n");
for( i = 0; i < 3; i ++)
{
printf("array1[%d] = %d \n", i, array1[i]);
}

printf("打印array2 : \n");
for( i = 0; i < 4; i ++)
{
printf("array2[%d] = %d \n", i, array2[i]);
}

//5. 使用宏的方式处理数组1
RESET(array1, len1);

//6. 使用函数的方式处理数组2
reset(array2, len2);

//7. 打印处理后的数组
printf("打印处理后的array1 : \n");
for( i = 0; i < 3; i ++)
{
printf("array1[%d] = %d \n", i, array1[i]);
}

printf("打印处理后的array2 : \n");
for( i = 0; i < 4; i ++)
{
printf("array2[%d] = %d \n", i, array2[i]);
}


return 0;
}
  • 2.编译运行结果​:
    【C 语言】C 语言 函数 详解 ( 函数本质 | 顺序点 | 可变参数 | 函数调用 | 函数活动记录 | 函数设计 ) [ C语言核心概念 ]_函数与宏_06


虽然看起来 函数 和 宏实现了相同的功能, 但是它们有很大的区别;




2. 函数 和 宏 的分析


(1) 函数 和 宏 分析

函数 和 宏 分析 :

  • 1.宏处理​: 宏定义是在预处理阶段直接进行宏替换, 代码直接复制到宏调用的位置, 由于宏在预处理阶段就被处理了, 编译器是不知道宏的存在的;
  • 2.函数处理​: 函数是需要编译器进行编译的, 编译器有决定函数调用行为的义务;
  • 3.宏的弊端 ( 代码量 )​: 每调用一次宏, 在预处理阶段都要进行一次宏替换, 会造成代码量的增加;
  • 4.函数优势 ( 代码量 )​: 函数执行是通过跳转来实现的, 代码量不会增加;
  • 5.宏的优势 ( 效率 )​: 宏 的执行效率 高于 函数, 宏定义是在预编译阶段直接进行代码替换, 没有调用开销;
  • 6.函数的弊端 ( 效率 )​: 函数执行的时候需要跳转, 以及创建对应的活动记录( 栈 ), 效率要低于宏;



3. 函数 与 宏 的 利弊


(1) 宏 优势 和 弊端


宏的优势和弊端​ : 宏的​执行效率要高于函数​, 但是​使用宏会有很大的副作用​, 非常容易出错, 下面的例子说明这种弊端;


代码示例 :

  • 1.代码​:
#include <stdio.h>

#define ADD(a, b) a + b
#define MUL(a, b) a * b
#define _MIN_(a, b) ((a) < (b) ? (a) : b)

int main()
{
int a = 1, b = 10;

//宏替换的结果是 : 2 + 3 * 4 + 5, 最终打印结果是 19
printf("%d\n", MUL(ADD(2, 3), ADD(4, 5)));

//宏替换的结果是 ((a++) < (b) ? (a++) : b), 打印结果是 2
printf("%d\n", _MIN_(a++, b));


return 0;
}
  • 2.编译运行结果​ :
    【C 语言】C 语言 函数 详解 ( 函数本质 | 顺序点 | 可变参数 | 函数调用 | 函数活动记录 | 函数设计 ) [ C语言核心概念 ]_可变参数_07
  • 3.查看预编译文件​ : 使用 ​gcc -E test_1.c -o test_1.i​ 指令, 将预编译文件输出到 test_1.i 目录中; ​下面是预编译文件的一部分​ ;
int main()
{
int a = 1, b = 10;

printf("%d\n", 2 + 3 * 4 + 5);
printf("%d\n", ((a++) < (b) ? (a++) : b));


return 0;
}


(2) 函数 的 优势 和 弊端

函数的优缺点 :

  • 1.函数优势​: 函数调用需要​将实参传递给形参​, 没有宏替换这样的副作用;
  • 2.弊端 ( 效率低 )​: 函数执行​需要跳转​, 同时也需要​建立活动对象对象 ( 如 函数栈 ) 来存储相关的信息​, 需要牺牲一些性能;


(3) 宏的无可替代性

宏 定义 优势 :

  • 1.宏参数不限定类型​: 宏参数 可以是 任何 C 语言 的实体类型, 如 int, float, char, double 等;
  • 2.宏参数可以使类型名称​: 类型的名称也可以作为宏的参数;
//宏定义 : 实现分配 n 个 type 类型空间, 并返回 type 类型指针
#define MALLOC(type, n) (type*)malloc(n * sizeof(type))

//分配 5 个 float 大小的动态空间, 并将首地址存放在 指针 p 中;
float *p = MALLOC(int, 5);



4. 总结


(1) 宏 定义 和 函数 总结

宏定义 和 函数 小结 :

  • 1.宏定义​: 宏 的 参数 可以 是 C 语言中 的 任何类型的 ( 优势 ) , 宏的执行效率 高 ( 优势 ), 但是容易出错 ( 弊端 );
  • 2.函数​: 函数 参数 的 类型是固定的, 其 执行效率低于宏, 但是不容易出错;
  • 3.宏定义 和 函数之间的关系​: 这两者不是竞争对手, 宏定义可以实现一些函数无法实现的功能;





四. 函数的调用约定



1. 函数的活动记录 分析


(1) 函数的活动记录

活动记录概述​ : 函数调用时 将 下面一系列的信息 记录在 活动记录中 ;

  • 1.临时变量域 :​ 存放一些运算的临时变量的值, 如自增运算, 在到顺序点之前的数值是存在临时变量域中的;


    后置操作 自增 原理 : i++ 自增运算 进行的操作​ :
    ( 1 ) 生成临时变量​ : 在内存中生成临时变量 tmp ;
    ( 2 ) 临时变量赋值​ : 将 i 的值赋值给临时变量, tmp = i ;
    ( 3 ) 进行加 1 操作​ : 将 i + 1 并赋值给 i;

    示例​ : 定义函数 fun(int a, int b), 传入 fun(i, i++), 传入后 获取的实参值分别是 2 和 1;
    在函数传入参数达到顺序点之后开始取值, 函数到达顺序点之后, 上面的三个步骤就执行完毕, ​形参 a 从内存中取值​, i 的值是2, ​形参 b 从临时变量域中取值​, 即 tmp 的值, 取值是 1;


  • 2.局部变量域 :​ 用于存放 函数 中定义 的局部变量, 该变量的生命周期是局部变量执行完毕;

  • 3.机器状态域 :​ 保存 函数调用 之前 机器状态 相关信息, 包括 寄存器值 和 返回地址, 如 esp 指针, ebp 指针;
  • 4.实参数域 :​ 保存 函数的实参信息 ;
  • 5.返回值域 :​ 存放 函数的返回值 ;



2. 函数的调用约定概述


(1) 参数入栈 问题描述

参数入栈问题​ : 函数参数的计算次序是不固定的, 严重依赖于编译器的实现, 编译器中函数参数入栈次序;

  • 1.参数传递顺序​: 函数的参数 实参传递给形参 是从左到右传递 还是 从右到左传递;
  • 2.堆栈清理​: 是函数的调用者清理 还是 由 函数本身清理 ;

【C 语言】C 语言 函数 详解 ( 函数本质 | 顺序点 | 可变参数 | 函数调用 | 函数活动记录 | 函数设计 ) [ C语言核心概念 ]_函数活动记录_08


参数入栈 栈维护 问题示例 :

  • 1.多参数函数定义​ : 定义一个函数 fun(int a, int b, int c) , 其中有 3 个参数;
  • 2.函数调用​ : 当发生函数调用时 fun(1, 2, 3), 传入三个 int 类型的参数, 这三个参数肯定有一个传递顺序, 这个传递顺序可以约定;
    • ( 1 ) 从左向右入栈​ : 将 1, 2, 3 依次 传入 函数中 ;
    • ( 2 ) 从右向左入栈​ : 将 3, 2, 1 依次 传入 函数中 ;
  • 3.栈维护​ : 在 fun1() 函数中 调用 fun2() 函数, 会创建 fun2() 函数的 活动记录 (栈), 当 fun2() 函数执行完毕 返回的时候, 该 fun2 函数的栈空间是由谁 ( fun1 或 fun2 函数 ) 负责释放的;


函数参数计算次序依赖于编辑器实现, 函数参数入栈的顺序可以自己设置;



(2) 参数传递顺序的调用约定

函数参数调用约定 :

  • 1.函数调用行为​: 函数调用时​参数​传递给 被调用的 函数,​返回值​被返回给 调用函数 ;
  • 2.调用约定作用​: 调用约定 是 用来规定 ① 参数 是通过什么方式 传递到 栈空间 ( 活动记录 ) 中, ② 栈 由谁来 清理 ;
  • 3.参数传递顺序 ( 右到左 )​: 从右到左入栈使用 __stdcall, __cdecl, __thiscall 关键字, 放在 函数返回值之前;
  • 4.参数传递顺序 ( 左到右 )​: 从左到右入栈使用 __pascal, __fastcall 关键字, 放在 函数返回值之前;
  • 5.调用堆栈的清理工作​:​① 调用者负责清理调用堆栈​;​② 被调用的函数返回之前清理堆栈​;





五. 函数设计技巧



函数设计技巧 :

  • 1.避免使用全局变量​: 在函数中尽量避免使用全局变量, 让函数形成一个独立功能模块;
  • 2.参数传递全局变量​: 如果必须使用到全局变量, 那么多设计一个参数, 用于传入全局变量;
  • 3.参数名称可读性​: 尽量不要使用无意义的字符串作为参数变量名;
  • 4.参数常量​: 如果参数是一个指针, 该指针仅用于输入作用, 尽量使用 const 修饰该指针参数, 防止该指针在函数体内被修改;
//这里第二个参数仅用于输入, 不需要修改该指针, 那么就将该参数设置成常量参数
void fun(char *dst, const char* src);
  • 5.返回类型不能省略​: 函数的返回类型不能省略, 如果省略了返回值, 那么返回值默认 int;
  • 6.参数检测​: 在函数开始位置, 需要检测函数参数的合法性, 避免不必要的错误, 尤其是指针类型的参数;
  • 7.栈内存指针​: 返回值 绝对不能是 局部变量指针, 即 指针指向的位置是 栈内存位置, 栈内存在返回时会销毁, 不能再函数运行结束后使用 ;
  • 8.代码量​: 函数的代码量尽量控制在一定数目, 50 ~ 80 行, 符合模块化设计规则;
  • 9.输入输出固定​: 函数在输入相同的参数, 其输出也要相同, 尽量​不要在函数体内使用 static 局部变量,​这样函数带记忆功能, 增加函数的复杂度;
  • 10.参数控制​: 编写函数的时候, 函数的参数尽量控制在 4 个以内, 方便使用;
  • 11.函数返回值设计​: 有时候函数不需要返回值, 或者返回值使用指针参数设置, 但是为了增加灵活性, 可以附加返回值; 如 支持 链式表达式 功能;


举报

相关推荐

0 条评论