
前两课认识变量与输入输出这一课继续讨论函数、数组和运算符。我原来的切入点很简单怎样把两数相加独立出来怎样保存并打印 1 到 10。这里保留这条路线把容易含糊的地方补实。1. 从相加到函数真正抽出来的是什么我原来写“直接在 main 中相加并不能称为函数”这句话不准确main本身就是函数。区别是有没有把相加抽成一个可复用的功能。#includestdio.hstaticintAdd(intx,inty){returnxy;}intmain(void){intm20,n30;intsumAdd(m,n);printf(sum%d\n,sum);printf(again%d\n,Add(1,2));return0;}输出sum50、again3。像我当时想到的工厂类比一样输入材料内部处理返回产品。不过程序里还需要明确输入、输出的类型以及使用前提。部分在本例中的意思int返回类型返回一个 int 值Add函数名字int x, int y形参由本次调用提供值Add(m, n)实参表达式 m 和 n 的值传入return x y计算并返回结果有返回值不等于必须先保存到变量。可以保存也可以直接交给printf单写Add(m, n);在语法上可以但丢弃了结果纯相加函数这样调用通常没意义。本例要求两数之和能由 int 表示。“数学上两个整数相加还是整数”不能证明它能装进固定范围的 C 类型有符号整数溢出不能默认当成正常回绕。值传递函数内的形参不等于调用者变量#includestdio.hstaticintincrement(intx){x;returnx;}intmain(void){intvalue10;intnextincrement(value);printf(value%d, next%d\n,value,next);return0;}输出value10, next11。修改形参没有修改调用者的 value。这里先理解值传递想改调用者对象需要后续学习指针而不是以为变量名相同就共享对象。2. 数组10 是元素数量不是最后一个下标我当时的问题是“要存 1 到 10难道定义十个变量”数组让同类型数据能够用统一规则访问。#includestdio.hstaticintsum_array(constintvalues[],size_tcount){inttotal0;for(size_ti0;icount;i)totalvalues[i];returntotal;}intmain(void){intarr[]{1,2,3,4,5,6,7,8,9,10};size_tcountsizeofarr/sizeofarr[0];for(size_ti0;icount;i)printf(%d%c,arr[i],i1count?\n: );printf(count%zu, sum%d\n,count,sum_array(arr,count));return0;}输出1 2 3 4 5 6 7 8 9 10 count10, sum55有效下标为 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. 运算符不要只背名字先试三个容易混淆的地方除法取决于操作数类型#includestdio.hintmain(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.0C11 的整数除法截去小数部分向零截断因此 -5/2 为 -2不是向下取整得到 -3。(double)(5/2)转换发生在整数除法之后已经丢掉的 .5 不会回来。除数为零是另一条必须避免的边界。、以及逻辑短路赋值比较。和||做逻辑运算并短路和|是位运算不能随手替换。#includestdio.hintmain(void){intdenominator0;if(denominator!010/denominator1)puts(large);elseputs(skipped);return0;}输出skipped。左边为假右边没有求值所以没有执行除零。把改成就失去这个保护不能拿错误版本来运行猜结果。自增不是把所有副作用塞进一行i 1、i、i在单独一条语句里都可以递增但作为表达式时返回的值不同。入门时分开写最清楚先保存旧值再递增再使用新值。不要构造i i这种未定义行为来比较编译器输出。其余类别可以作为后续索引关系 ! 位操作 | ^ ~移位 地址与解引用 *条件?:下标、调用及成员访问[] () . -。先学清典型含义再研究类型转换、优先级和求值顺序比一次背完整清单更有用。4. 本次验证与下一步上面完整程序使用 C11 和编译警告检查分别核对 50/3、值传递、十元素求和、负数除法与短路输出。测试不用越界、溢出或未定义自增去制造“神奇效果”。新人可以带走函数拆分、显式数组长度和正确循环边界进一步学习时则要追问每个接口的前提数据范围是多少数组长度由谁提供表达式是否会执行危险操作。这些不是附加细节而是程序正确性的组成部分。