C语言学习笔记--函数
1. C 语言中的函数
(1)函数的由来:
程序 = 数据 + 算法→C 程序 = 数据 + 函数
(2)模块化程序设计
(3)C 语言中的模块
2. 面向过程的程序设计
(1)面向过程是一种以过程为中心的编程思想
(2)首先将复杂的问题分解为一个个容易解决的问题
(3)分解过后的问题可以按照步骤一步步完成
(4)函数是面向过程在 C 语言中的体现
(5)解决问题的每个步骤可以用函数来实现
3. 声明和定义
(1)声明的意义在于告诉编译器程序单元(以下均指变量或函数)的存在
(2)定义则明确指示程序单元的意义
(3)C 语言中通过 extern 进行程序单元的声明。
(4)一些程序单元在声明时可以省略 extern(如结构体)
(5)严格意义上的声明和定义并不相同!
global.c ——注意不是头文件
#include <stdio.h>float g_var = 10.0f; //注意,这里定义为float型的struct Test {int x;int y; };void f(int i,int j) {printf("i + j = %d\n",i + j); }int g(int x) {return (int)( 2 * x + g_var); //在本文件中g_var以float型处理 }
test.c
#include <stdio.h> #include <malloc.h>//这里只是声明,告诉编译器该全局变量在外部的global.c文件己经存在!(注意,不是头文件) extern int g_var; //注意,这里声明为int。但外部定义为float型。在处理这个.c文件时,编译器会处g_var当成整型来处理,但实际上内存中是以float存储的!//这里声明为int是为了说明,声明和定义是不同的!struct Test; //在global.c文件中,对于结构体声明无须加extern。int main() {extern void f(int,int); //这里只是声明,相当于告诉编译器,这个函数在外部文件中己经存在。extern int g(int);struct Test* p = NULL; //这里合法的//这里是错误的,因为结构体是在外部定义的,虽然在编译global.c时编译器是知道这个结构体的大小的。//但由于文件是分别编译的,编译只会按本文件中定义的类型来编译。由于本文件中找不到他的定义。//所以也就无法知道该结构体实际的大小,因此会报错。这就是声明和定义的区别!//struct Test* p = (struct Test*)malloc(sizeof(struct Test));//编译器提示这是一个不完全的类型 printf("p = %p\n", p);//g_var = 10; //这里可以取消注释来观察g_var值的变化! printf("g_var = %d\n", g_var);//会把内存中浮点型的g_var当成int型来处理! f(1, 2);printf("g(3) = %d\n",g(3)); //g()函数(在global.c中),把g_var当成float型处理。free(p);return 0; }
面向过程是由上至下分解问题的设计方法
4.函数参数
(1)函数参数在本质上与局部变量相同,都在栈上分配空间
(2)函数参数的初始值是函数调用时的实参值
(3)函数参数的求值顺序依赖于编译器的实现(注意:这里指求值顺序而不是入栈顺序!)
函数参数的求值顺序
#include <stdio.h>int func(int i, int j) {printf("i = %d, j = %d\n",i, j);return 0; }int f() {printf("f() Call...\n");return 1; }int g() {printf("g() Call...\n");return 2; }int main() {int k = 1;int a = 0;func(k++,k++); //gcc、vc、bcc:2,1 printf("k = %d\n", k); //3 a = f() * g(); //*两侧的操作数顺序也不是固定的,vc、gcc:f()先被调用,然后g()return 0; }
5.程序中的顺序点
(1)程序中存在一定的顺序点。
(2)顺序点指的是执行过程中修改变量值的最晚时刻
(3)在程序到达顺序点的时候,之前所做的一切操作必须完成。
6.C 语言中的顺序点
(1)每个完整表达式结束时,即分号处
(2)&&、||、?:、逗号表达式的每个参数计算之后
(3)函数调用时所有实参求值完成后(进入函数体之前)
程序中的顺序点
#include <stdio.h>int main() {int k = 2;int a = 1;k = k++ + k++;printf("k = %d\n", k); //vc:6 = 2 + 2 + 1 + 1//gcc:5 = 2 + 3//&&是顺序点,先计算a--并更新a,所以a=0;根据短路原理后面//表达式不被判断,所以下面一行不被输出if(a-- && a){printf("a = %d\n", a); //该行不被输出! }return 0; }
7.函数参数入栈顺序
(1)函数参数的计算次序是依赖编译器实现的
(2)但入栈次序与调用约定有关
8.调用约定
(1)当函数调用发生时,参数会传递给被调用的函数,而返回值会返回函数的调用者。
(2)调用约定描述参数如何传递到栈中以及栈的维护方式(即参数传递顺序和调用栈清理)
(3)调用约定是预定义的,可理解为调用协议
(4)调用约定通常用于库调用和库开发的时候
①从右到左依次入栈:_stdcall、_cdecl、_thiscall
②从左到右依次入栈:_pascal、_fastcall
9.可变参数
(1)C 语言中可以定义参数可变的函数
(2)参数可变函数的实现依赖于 stdarg.h 头文件
①va_list:指向可变参数列表
②va_arg: 取具体参数值
③va_start:标识参数访问的开始
④va_end:标识参数访问的结束
(3)相关宏的定义
①va_list 宏:typedef char * va_list;
②_ADDRESSOF 宏:#define _ADDRESSOF(v) ( &(v) ) //用来取得变量的地址
③_INTSIZEOF 宏:#define _INTSIZEOF(n) ( (sizeof(n) + sizeof(int) - 1) & ~(sizeof(int) - 1) )
A.主要是用来计算类型大小(取整到 sizeof(int)的整数倍)。比如 sizeof(int)为4,1,2,3,4 就取 4。而 5,6,7,8 就取 8。
对 x 向 n 取整用 C 语言的算术表达就是((x+n-1)/n)*n,当 n 为 2 的幂时可以将最后二步运算换成位操作将最低 n - 1 个二进制位清 0 就可以了。
B.取整的主要目的是进行内存对齐。
④va_start 宏:#define va_start(ap,v) (ap = (va_list)_ADDRESSOF(v) + _INTSIZEOF(v)),将第 1 个可变参数的起始地址保存在 ap 变量中。
⑤va_arg 宏:#define va_arg ( *(t *)((ap += _INTSIZEOF(t)) - _INTSIZEOF(t)) )。每次调用 va_arg(ap,v)就是取出当前 ap 指向的变量,然后移到下一个
变量(移动距离由 v 的类型来决定)。
⑥va_end 宏:#define va_end ( ap = (va_list)0 )。即,将 ap 设为 NULL
(4)图解可变参数宏的实现原理
编写函数计算平均值
#include <stdio.h> #include <stdarg.h>//一般的求平均值函数 float average(int array[],int size) {int i = 0;float avr = 0;for(i = 0; i<size; i++){avr += array[i];}return avr / size; }//利用可变参数函数来实现 float averageEx(int n, ...) {int i = 0;float sum = 0;va_list args; //声明一个args指向,指向可变参数列表 va_start(args, n); //将args指向第1个可变参数for(i=0; i<n; i++){sum += va_arg(args,int);} va_end(args);return sum / n; }int main() {int array[] = {1, 2, 3, 4, 5};printf("%f\n", average(array,5));printf("%f\n", averageEx(5, 1,2,3,4,5));printf("%f\n", averageEx(4, 1,2,3,4));return 0; }
10.可变参数的限制
(1)可变参数必须从头到尾按照顺序逐个访问(1)可变参数必须从头到尾按照顺序逐个访问
(2)参数列表中至少要存在一个确定的命名参数
(3)可变参数函数无法确定实际存在的参数的数量
(4)可变参数无法确定参数的实际类型。(注意,如果 va_arg 中指定了错误的类型,那么结果是不可预料的)
11.函数与宏
(1)宏是由预处理直接替换展开的,编译器不知道宏的存在
(2)函数是由编译器直接编译的实体,调用行为由编译器决定
(3)多次使用宏会导致最终可执行程序的体积增大 ☆
(4)函数是跳转执行的,内存中只有一份函数体存在 ☆
(5)宏的效率比函数要高,因为是直接展开,无调用开销 ☆
(6)函数调用时会创建活动记录,效率不如宏
#include <stdio.h>#define RESET(p,len) \while( len > 0) \((char*)p)[--len] = 0void reset(void* p, int len) {while(len > 0)((char*)p)[--len] = 0; }int main() {int array[] = {1, 2, 3, 4, 5};int len = sizeof(array);int i= 0;for(i=0; i<5; i++){printf("array[%d] = %d\n",i, array[i]);}RESET(array, len);//reset(array, len);for(i=0; i<5; i++){printf("array[%d] = %d\n",i, array[i]);}return 0; }
12.宏的局限
(1)宏的效率比函数稍高,但是其副作用巨大
(2)宏是文本替换,参数无法进行类型检查
(3)可以用函数完成的功能,绝对不用宏
(4)宏的定义中不能出现递归定义!!!!
宏的副作用
#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 i = 1;int j = 10;//本意要计算3 * 7printf("%d\n", _MUL_(_ADD_(1, 2), _ADD_(3, 4)));//1 + 2 * 3 + 4 ==>11//本意要求min(1,10)printf("%d\n", _MIN_(i++, j)); //(i++)<(j) ? (i++):(b) //输出2return 0; }
13.宏的妙用
(1)用于生成一些常规性的代码
(2)封装函数,加上类型信息
#include <stdio.h> #include <malloc.h>#define MALLOC(type, x) (type*)malloc(sizeof(type)*x) #define FREE(p) (free(p),p = NULL)//输出格式:变量名 = 变量的值 #define LOG_INT(i) printf("%s = %d\n", #i, i) #define LOG_CHAR(c) printf("%s = %c\n", #c, c) #define LOG_FLOAT(f) printf("%s = %f\n", #f, f) #define LOG_POINTER(p) printf("%s = %p\n", #p, p) #define LOG_STRING(s) printf("%s = %s\n", #s, s)//ForEach函数 #define FOREACH(i, n) while(1){ int i = 0, l = n;for(i=0;i<l;i++) #define BEGIN { #define END }break;}int main() {int* pi = MALLOC(int,5);//己定义好MALLOC的返回类型,无须再强制转换char* str = "Hello World!";LOG_STRING(str); //打印变量名及变量的值 LOG_POINTER(pi);//k在宏内会被定义,其作用域在很小,此处无须再定义FOREACH(k, 5)BEGINpi[k] = k + 1;END//k在宏内会被定义,此处无须再定义FOREACH(k, 5)BEGINint value = pi[k];LOG_INT(value); ENDFREE(pi);LOG_POINTER(pi);return 0; }
14.函数设计原则
(1)函数从意义上应该是一个独立的功能模块
(2)函数名要在一定程度上反映函数的功能
(3)函数参数名要能够体现参数的意义
(4)尽量避免在函数中使用全局变量
void sc(char *s1, char* s1);×
void str_copy(char* dest, char* src); √
(5)当函数参数不应该在函数体内部被修改时,应加上 const 声明
(6)如果参数是指针,且仅作输入参数,则应加上 const 声明
void str_copy(char* dest, const char* src);
(7)不能省略返回值的类型。如果没有返回值,应声明为 void。
(8)对参数进行有效性检查,特别是指针参数的检查尤为重要
(9)不要返回指向“栈内存”的指针,因为栈内存在函数体结束时被自动释放
(10)函数体的规模要小,尽量控制在 80 行代码之内
(11)相同的输入对应相同的输出,避免函数带有“记忆”功能
(12)避免函数有过多的参数,参数个数尽量控制在 4 个以内
(13)有时候函数不需要返回值,但为了增加灵活性,如支持链式表达,可以附加返回值
char s[64];
int len = strlen(strcpy(s, "Hello")); //当中的 strcpy 返回缓冲区 s 的地址。
(14)函数名和返回值类型在语义上不可冲突
char c = getchar(); //getchar 的返回值实际上是 int 类型,而不是 char。与函数名不符。
参考资料:
www.dt4sw.com
http://www.cnblogs.com/5iedu/category/804081.html
转载于:https://www.cnblogs.com/CoderTian/p/5915044.html
C语言学习笔记--函数相关推荐
- C语言学习笔记--函数与指针
1. 函数类型 (1)C 语言中的函数有自己特定的类型,这个类型由返回值.参数类型和参数个数共同决定.如 int add(int i,int j)的类型为 int(int,int). (2)C 语言中 ...
- C语言学习笔记——函数
1.函数的介绍 为完成某一功能的程序指令(语句)的集合,称为函数 在C语言中,函数分为: 自定义函数.系统函数 2.函数的定义 2.1函数的基本语法 返回类型 函数名(形参列表){执行语句...; / ...
- C语言学习笔记——函数的嵌套
函数的嵌套 编程案例:输入4个数找出最大的 #include <stdio.h>int getMaxDataFromTwo(int x,int y) {int bigger;bigger ...
- c语言中void arrout,c语言学习笔记(数组、函数
<c语言学习笔记(数组.函数>由会员分享,可在线阅读,更多相关<c语言学习笔记(数组.函数(53页珍藏版)>请在人人文库网上搜索. 1.数组2010-3-29 22:40一维数 ...
- c语言学习笔记【结构体02】结构体指针变量与结构体变量的函数参数,C语言学习笔记结构体02结构体指针变量与结构体变量的函数参数.docx...
C 语言学习笔记[结构体02]结构体指针变量与结构体变量 的函数参数 C 语言学习笔记之结构体指针变量一提指针,那可 是 C 语言的核心了,有多少学子曾拜倒在指针的脚下.单纯的说指针,其实并不难,但是 ...
- r语言c函数怎么用,R语言学习笔记——C#中如何使用R语言setwd()函数
在R语言编译器中,设置当前工作文件夹可以用setwd()函数. > setwd("e://桌面//") > setwd("e:\桌面\") > ...
- C语言学习笔记10-指针(动态内存分配malloc/calloc、realloc、释放free,可变数组实现;Tips:返回指针的函数使用本地变量有风险!;最后:函数指针)
C语言:指针 1. 指针:保存地址的变量 *p (pointer) ,这种变量的值是内存的地址. 取地址符& 只用于获取变量(有地址的东西)的地址:scanf函数-取地址符 地址的大小 ...
- c语言注释语句执行吗,C语言学习笔记之C语言概念解析(附资料分享)每一个语句都必须以分号结尾但预处理命令函数头和花括号“}”之后不能加分号...
[[怪兽爱C语言]C语言学习笔记之C语言概念解析(附资料分享)]https://toutiao.com/group/6582429294901854728/?iid=15906422033&a ...
- C语言学习笔记(8)函数
C语言学习笔记(8)函数 函数 函数主要是用于将解决复杂的编程问题分解为数个可以分开进行解决的小问题,通过函数分装,最后在拼连在一起.有利于多人协作开发. 函数同样需要先声明后调用,声明符号为 voi ...
最新文章
- 个性化推荐系统研究热点之用户画像
- 想要提升网站曝光率应避免哪些错误设置的出现?
- ORACLE TEXT FILTER PREFERENCE(四)
- 固特异发布人工智能球形轮胎:自动变形、自我修复、超级灵活
- LC和RC滤波电路分析
- Flash Builder 使用
- 服务器设置系统盘分页,服务器设置系统盘分页
- Redis客户端工具安装
- 机器学习笔记 - 什么是t-SNE?
- 信号系统服务器,四大导航系统信号介绍
- 使用Laya引擎开发微信小游戏(上)
- 如何进入设有密码的计算机
- android viewpager
- mysql聚簇索引和非聚簇索引
- Java SE基础(更新中)
- 云和恩墨祝各位“女神”节日快乐!
- 智能大数据SMART准则(读书笔记)
- html、css 实现轮播图的静态样式
- 以SimpleWiFi串口转WiFi模块S2WM02为例说明ModbusTCP与ModbusRTU区别
- windows编译安卓源码记录
热门文章
- 关于vhr项目部署所遇到的问题总结,Failed to execute goal org.apache.maven.plugins:maven-surefire-plugin
- linux制作光盘镜像文件
- 关于数据仓库和OLAP的问题!
- Trufun Kant Studio 2008面向VS.NET的开发应用
- win7旗舰恢复出厂设置_iphone12怎么恢复出厂设置 iphone12还原所有设置方法介绍
- 如何将树莓派设置为WiFi热点
- Java——IO基础
- React Native将license修改为MIT,与React保持一致
- a标签js阻止跳转_前端笔试知识点:阻止冒泡
- java 解析p12_java读取*.p12证书的信息 | 学步园