从 0 开始学习 C 语言:使用函数的注意事项

这次与大家分享一些我对 C 语言中函数的简单理解,希望能帮助有疑惑的同学更好的使用这个强大的语言特性。大家在高中的时候都求过数学函数的表达式,其实编程语言的函数与数学表达式实际上是一样的,我们都可以将其理解为一个有输入输出的「功能黑箱」,看下图:

就类似给函数一个 x 值,可以求结果一样,给函数一些输入参数,也可以得到相同的输出结果。

模块化编程

为什么要使用函数呢?其实是为了更好的维护软件的功能以及实现模块化编程,你可以想下如果把所有的功能都写在 main 函数中,那该多么可怕,估计没人会读你写的代码。

通过使用函数可以使我们的软件逻辑性更强,单独的功能写在单独的函数中,还可以方便后期复用等等,好处还有很多实在列举不完。其中比较重要的作用要说:模块化编程代码复用了,建议你去找实际的项目看看代码组成,自然就理解了。

函数的副本机制

很多初学者都搞不清楚函数的副本机制,例如下面的交换函数:

#include <stdio.h>

void swap(int a, int b) {
	printf("Address a = 0x%X, Address b = 0x%X\n", &a, &b);
	int x = a;
	a = b;
	b = a;
}

int main(void) {
	int m = 1;
	int n = 2;
	printf("Address m = 0x%X, Address n = 0x%X\n", &m, &n);
	printf("Before: m = %d, n = %d\n", m, n);
	swap(m, n);
	printf("  Swap: m = %d, n = %d\n", m, n);
	getchar();
	return 0;
}
复制代码

这个函数是错误的,因为函数在进行参数传递的时候,会将传入参数 m,n 的值拷贝给函数的形式参数 a,b,因此在函数内部交换的是形式参数 a,b 的值,而不是交换传递时 m,n 变量的值。

函数的副本机制从内存的角度来说就是:在函数进行参数传递的时候,实参和形参的内存地址是不同的。这是这个例子的输出结果,每个人的机器可能都不同:

Address m = 0x2BFB34, Address n = 0x2BFB28
Before: m = 1, n = 2
Address a = 0x2BFA50, Address b = 0x2BFA54
  Swap: m = 1, n = 2
复制代码

可以看到变量 m 和 a,以及 n 和 b 的内存地址均不同,因此对值的交换也是不起作用的。但是使用指针就完全不同了,看下面这个例子:

#include <stdio.h>

void swap(int *pa, int *pb) {
	printf("Address pa = 0x%X, Address pb = 0x%X\n", &pa, &pb);
	printf("Address *pa(&m) = 0x%X, Address *pb(&n) = 0x%X\n", pa, pb);
	int x = *pa;
	*pa = *pb;
	*pb = x;
}

int main(void) {
	int m = 1;
	int n = 2;
	printf("Address m = 0x%X, Address n = 0x%X\n", &m, &n);
	printf("Before: m = %d, n = %d\n", m, n);
	swap(&m, &n);
	printf("  Swap: m = %d, n = %d\n", m, n);
	getchar();
	return 0;
}
复制代码

这个函数才是正确的交换函数,为什么呢?因为我们这里传递的是 m,n 的内存地址,即传递的是指针,所以在函数内部对指针解除引用就可以直接访问这个地址的内容了,这是指针提供的特性。

那么是否就意为着传递指针就没有副本机制了呢?这也是错误的,指针也是变量,本质上与 int 没有区别,只是特性不同罢了。

上面例子在传递指针的时候,也是将 m,n 的地址拷贝给 pa,和 pb,也是存在副本机制的,只不过这里拷贝的是地址而不是值,在内部通过指针的解引用操作可以直接访问 m,n 的内存地址,进而进行交换。

因为指针非常重要,所以建议你自己运行这个程序,可以看到输出结果中 pa 指向的是 m 的内存地址,而 &pa 是指针变量 pa 的内存地址,这两个千万不要搞混了。

一定时时刻刻记住下面的结论:

  1. 一个 int 变量有自己的内存地址,也有自己存储的整数值
  2. 一个指针变量有自己的内存地址,也有自己存储的指向地址值
  3. 变量在内存中都有自己的地址和其存储的内容

函数的参数传递顺序

VC 和 gcc 编译器计算函数参数的顺序都是从右向左,这个特点一点要记住,面试可能会问到。你可以使用下面这个程序去验证,程序比较简单,留作给你的思考(不去思考就等于浪费时间看 cd 这篇文章了):

#include <stdio.h>

void foo(int a, int b) {
	printf("a = %d\n", a);
	printf("b = %d", b);
}

int main(void) {
	int n = 1;
	foo(n, n++);
	getchar();
	return 0;
}
复制代码

今天就分享一点函数的注意事项,下次见了,Bye

本文原创发布于微信公众号「cdeveloper」,编程、职场,人生,关注并回复关键字「linux」、「机器学习」等获取免费学习资料。

转载于:https://juejin.im/post/5b17927051882513ae4882ae

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
C语言编程注意事项 1. 每个程序中一定包含main()函数, 尽管C语言中对函数命名没有限制。 2. printf函数永远不会自动换行, 只能用\n来实现, 回车键进行的换行在编译中会出现错误信息。 3. 在vs2008等平台中编译、测试要加 system("pause"); 来暂停dos自动退出引起的printf无法显示。 4. 所有自定义变量必须声明才能使用。 5. 每行一般只写一条语句, 在运算符两边加一个空格, 便于阅读。 6. 整数除法将会自动舍位, 不进行四舍五入的操作。 7. for(初始化部分;条件部分;增长部分) 比while 更适用于初始化和增长步长都是单条语句的情况。 8. 使用 #define 名字替换文本对部分"幻数" 赋予意义便于阅读 #define结尾无需;号来结束。 9. EOF(end of file)表示没有字符输入时定义在stdio.h 头文件中 EOF不等于\n 换行等。 10. 由于!= 的优先级大于 = ,因此如果对判断中存在变量赋值时 应对赋值加() 例如: while((c = getchar()) != EOF)。 11. getchar() 用于用户输入直至键入回车键。 12. 变量名以字母和数字组成, (下划线"_"被默认为字符, 以下划线为首写字母的为库类变量名), 变量常以小写字母开头,内部变量名前31位有效, 外部变量名至少前6位保持唯一性。 13.在C语言中是区分字母大小写的。 14. 一个字符常量为一个整数, 用''单引号括起来。例如: '0' 为48, 它与0没有任何关系。 15. ANSI C语言的转义符: \a 响铃符; \b 回退符; \f 换页符; \n 换行符; \r 回车符; \t 横向制表符 ; \v 纵向制表符; \\ 反斜杠; \? 问号; \' 单引号; \" 双引号; \ooo 八进制数; \xhh 十六进制数; '\0' 表示0, 即(null)。 16. 'x'与"x"的区别: 'x'表示一个整数, 字母x在其字符集中对应的数值, "x"表示包含一个字符x 以及一个结束符'\0'。 17. const在声明变量中起限制作用, 该限定变量的值不能被修改。 18. %运算符不能应用于float或double类型。 19. char类型转换为int型时, 因为无法判断它是signed还是unsigned,这样其结果有可能为负数, 所以转换时尽量指定signed和unsigned限制符。 20. ++i与i++ 的不同之处: ++i是先 +1再使用i, i++ 是先使用i再 +1。 21. 三元运算符"?:" 第一个表达式的括号不是必须的, 但是由于三元表达式的优先级非常低, 因此我们还是将第一个表达式用()括起来以便于阅读和理解。 22. C语言中可以使用递归 (即函数调用自身函数), 这样做并不节省储存空间也不加快执行速度, 只是使程序紧凑便于理解。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值