☰
小菜的C语言之路第三课:从Add函数到数组,理解参数、下标与运算符
2026/10/4 8:53:33 网站建设 项目流程

前两课认识变量与输入输出,这一课继续讨论函数、数组和运算符。我原来的切入点很简单:怎样把两数相加独立出来,怎样保存并打印 1 到 10。这里保留这条路线,把容易含糊的地方补实。

1. 从相加到函数:真正抽出来的是什么

我原来写“直接在 main 中相加并不能称为函数”,这句话不准确:main本身就是函数。区别是有没有把相加抽成一个可复用的功能。

#include<stdio.h>staticintAdd(intx,inty){returnx+y;}intmain(void){intm=20,n=30;intsum=Add(m,n);printf("sum=%d\n",sum);printf("again=%d\n",Add(1,2));return0;}

输出sum=50、again=3。像我当时想到的工厂类比一样:输入材料,内部处理,返回产品。不过程序里还需要明确输入、输出的类型以及使用前提。

部分在本例中的意思
int返回类型返回一个 int 值
Add函数名字
int x, int y形参,由本次调用提供值
Add(m, n)实参表达式 m 和 n 的值传入
return x + y计算并返回结果

有返回值不等于必须先保存到变量。可以保存,也可以直接交给printf;单写Add(m, n);在语法上可以,但丢弃了结果,纯相加函数这样调用通常没意义。

本例要求两数之和能由 int 表示。“数学上两个整数相加还是整数”不能证明它能装进固定范围的 C 类型;有符号整数溢出不能默认当成正常回绕。

值传递:函数内的形参不等于调用者变量

#include<stdio.h>staticintincrement(intx){++x;returnx;}intmain(void){intvalue=10;intnext=increment(value);printf("value=%d, next=%d\n",value,next);return0;}

输出value=10, next=11。修改形参没有修改调用者的 value。这里先理解值传递;想改调用者对象,需要后续学习指针,而不是以为变量名相同就共享对象。

2. 数组:10 是元素数量,不是最后一个下标

我当时的问题是“要存 1 到 10,难道定义十个变量?”数组让同类型数据能够用统一规则访问。

#include<stdio.h>staticintsum_array(constintvalues[],size_tcount){inttotal=0;for(size_ti=0;i<count;++i)total+=values[i];returntotal;}intmain(void){intarr[]={1,2,3,4,5,6,7,8,9,10};size_tcount=sizeofarr/sizeofarr[0];for(size_ti=0;i<count;++i)printf("%d%c",arr[i],i+1==count?'\n':' ');printf("count=%zu, sum=%d\n",count,sum_array(arr,count));return0;}

输出:

1 2 3 4 5 6 7 8 9 10 count=10, sum=55

有效下标为 0 到 9。循环写i < count,不是i <= count;越界不能因为程序没立刻崩溃就当作合法。

sizeof arr / sizeof arr[0]在这里成立,因为 arr 是完整数组。函数形参中的数组写法会调整为指针类型,不能在 sum_array 里用同一公式猜传入数组长度,因此显式传入 count。上面的求和例子也要求总和在 int 范围内。

数组初始化还要分清:int a[4] = {0};四个零;int b[4] = {-1};为 -1、0、0、0,不是四个 -1。

3. 运算符不要只背名字:先试三个容易混淆的地方

除法取决于操作数类型

#include<stdio.h>intmain(void){printf("%d,%d,%d\n",5/2,-5/2,5%2);printf("%.1f,%.1f\n",5.0/2,(double)(5/2));return0;}

输出:

2,-2,1 2.5,2.0

C11 的整数除法截去小数部分,向零截断,因此 -5/2 为 -2,不是向下取整得到 -3。(double)(5/2)转换发生在整数除法之后,已经丢掉的 .5 不会回来。除数为零是另一条必须避免的边界。

=、==,以及逻辑短路

=赋值,==比较。&&和||做逻辑运算并短路;&和|是位运算,不能随手替换。

#include<stdio.h>intmain(void){intdenominator=0;if(denominator!=0&&10/denominator>1)puts("large");elseputs("skipped");return0;}

输出skipped。左边为假,右边没有求值,所以没有执行除零。把&&改成&就失去这个保护,不能拿错误版本来运行猜结果。

自增不是把所有副作用塞进一行

i += 1、++i、i++在单独一条语句里都可以递增,但作为表达式时返回的值不同。入门时分开写最清楚:先保存旧值,再递增,再使用新值。不要构造i = i++这种未定义行为来比较编译器输出。

其余类别可以作为后续索引:关系> >= < <= != ==;位操作& | ^ ~;移位<< >>;地址与解引用& *;条件?:;下标、调用及成员访问[] () . ->。先学清典型含义,再研究类型转换、优先级和求值顺序,比一次背完整清单更有用。

4. 本次验证与下一步

上面完整程序使用 C11 和编译警告检查,分别核对 50/3、值传递、十元素求和、负数除法与短路输出。测试不用越界、溢出或未定义自增去制造“神奇效果”。

新人可以带走函数拆分、显式数组长度和正确循环边界;进一步学习时,则要追问每个接口的前提:数据范围是多少,数组长度由谁提供,表达式是否会执行危险操作。这些不是附加细节,而是程序正确性的组成部分。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询