CAPL 编程系列第十五期-第十九期回顾答案与分析

第十五期 答案与分析

  1. 答案: 主要目的是提高代码的重用性和实现程序的模块化
  2. 答案: 包含:返回值类型 (return_type)、函数名 (FunctionName)、形式参数列表 (parameter_list) 和函数体 ({ Function body })
  3. 答案: 应该声明为 void
  4. 答案: 函数定义中的参数称为形式参数 (形参);函数调用时传递的参数称为实际参数 (实参)
  5. 答案: 用于结束当前函数的执行,并可选地将一个值返回给函数调用者
  6. 答案: result 的值是 25分析: 调用 calculate(5) 时,形参 x 接收到值 5。函数体计算 x * x5 * 5 得到 25,并通过 return 语句返回。这个返回值 25 被赋给 result
  7. 答案: 属于局部作用域。这些变量仅在函数体内部有效
  8. 答案: 不能完全正确地输出 Pi 的值。分析: 函数 getPi() 返回的是 float 类型的值 3.14,而 write() 函数使用了 %d (整数占位符)。类型不匹配会导致输出错误(很可能输出 0 或其他无意义整数)。应使用 %f 占位符
  9. 答案: 不会立即执行。函数体内的代码只在函数被调用 (Call) 时执行
  10. 答案: 不会输出 "Hello"。分析: printMsg; 只是提到了函数名,并没有构成一个函数调用。正确的函数调用需要加上小括号:printMsg();

第十六期 答案与分析

  1. 答案: 可以省略。省略 void 是 CAPL 的一个特点,函数名前无类型即表示无返回值
  2. 答案: 不可以省略。即使函数没有参数,函数名后面的空的小括号 () 也必须保留
  3. 答案: 函数重载允许在同一作用域内定义多个同名的函数。这些重载的函数必须具有相同的返回值类型,但它们的形式参数列表必须不同(参数个数、类型或顺序不同)
  4. 答案: 不构成合法的重载。分析: 虽然函数名相同 (add) 且参数列表不同,但它们的返回值类型不同 (int vs float)。CAPL 的函数重载要求返回值类型必须相同
  5. 答案: 调用 add(5, 10) 时会执行 int add(int a, int b) 函数。调用 add(data) 时会执行 int add(int numbers[]) 函数。分析: 编译器根据调用时提供的实参类型和数量来匹配最合适的重载版本
  6. 答案: 函数内部对形参数组的修改会影响到调用者传入的原始实参数组。这种传递方式类似于引用传递(或地址传递)
  7. 答案: 不能。CAPL 函数无法直接将数组作为返回值类型
  8. 答案: 通常采用的方法是:调用者先准备好一个数组,然后将这个数组作为参数传递给函数,函数在内部修改(填充) 这个数组的内容
  9. 答案: 返回值类型是 void (或省略) 是因为该函数的设计目的不是返回一个新的数组,而是直接修改(填充) 调用者传入的 numbers 数组。它是通过修改作为参数传递进来的数组来“传递”结果的
  10. 答案: 作用是立即结束当前函数的执行。通常用在 void 类型的函数中,当满足某个条件需要提前退出函数时使用

第十七期 答案与分析

  1. 答案: 两种类型:timer (秒级定时器) 和 msTimer (毫秒级定时器)。主要区别在于时间单位不同
  2. 答案: 必须在全局变量区 variables { ... } 内声明
  3. 答案: 需要对应定义一个 on timer TimerName { ... } 事件处理块,其中 TimerName 是声明的定时器变量名
  4. 答案: 作用是设置一个定时器,在指定的 DelayTime 时间之后触发一次对应的 on timer 事件。它只触发一次
  5. 答案: 作用是取消 (停止) 一个已经启动的定时器,使其不再触发
  6. 答案: 关键代码应该写在定时器的 on timer 事件处理块内部。在该事件处理块的末尾,再次调用 setTimer(timerName, 500); 来设置下一次 500ms 后的触发
  7. 答案: 专门用于毫秒级 (msTimer) 定时器。优点是更简洁,只需调用一次即可启动周期性触发,无需在 on timer 事件中手动重新设置
  8. 答案: 几乎立即 (延迟为 0) 会第一次被触发。之后会以 500 毫秒的频率周期性触发
  9. 答案: 不会被再次触发。分析: setTimer 只设置一次触发。如果 on timer 事件处理完后没有再次调用 setTimer,该定时器就处于非活动状态,不会再自动触发
  10. 答案: 不会继续触发。分析: 按下 'b' 键调用 cancelTimer(timerA) 会使定时器 timerA 失效。即使 on timer timerA 内部有 setTimer(timerA, 100),但在 cancelTimer 执行后,on timer timerA 事件本身就不会再被触发了,因此内部的 setTimer 也不会被执行

第十八期 答案与分析

  1. 答案: 关键字是 message
  2. 答案: message 0x7DF responseMsg;
  3. 答案: responseMsg.dlc = 8;。使用 .dlc 选择器
  4. 答案: responseMsg.byte(0) = 0x02;。使用 .byte() 选择器,索引为 0
  5. 答案: 需要注意字节序:赋给 .word(index) 的值的低字节存储在 byte(index)高字节存储在 byte(index + 1)。所以 msg.word(0) = 0xABCD; 执行后,msg.byte(0) 的值是 0xCDmsg.byte(1) 的值是 0xAB
  6. 答案: 内置函数是 output()
  7. 答案: 发出的报文 DLC 很可能是 0,数据域为空。分析: 如果不显式设置 DLC,对于通过 ID 声明且 DBC 中未定义的报文,DLC 默认为 0
  8. 答案: 可以在声明报文变量时使用花括号 {} 初始化列表,在其中通过选择器(如 dlc = 8, byte(0) = 0xAA)赋初值
  9. 答案: msg.byte(1), msg.byte(2), msg.byte(3) 的值都是 0分析: 初始化列表只为 dlc, byte(0)byte(4) 赋了值,其他字节默认为 0
  10. 答案: 发送出的报文 msgB 的第一个字节是 0xAA。这说明 message 变量的赋值行为类似于值拷贝结构体赋值msgB = msgA 会将 msgA 的当前所有属性(包括数据字节)复制一份给 msgB 。(注:虽然 CAPL 底层实现细节未知,但从行为上看是值拷贝,而非共享同一块内存的引用。

第十九期 答案与分析

  1. 答案: 主要优点是:
    • 可读性更好: 使用有意义的名称代替难记的 ID
    • 自动获取 DLC: 无需手动设置 .dlc
    • 方便访问信号: 可以直接通过信号名操作数据,而不是操作字节
  2. 答案: message EngineData msg; msg.EngineSpeed = 2500; (或者 msg.EngineSpeed.phys = 2500;)
  3. 答案: 操作的是信号的物理值 (Physical Value)。CAPL 会利用加载的 DBC 文件信息自动处理底层的位提取/填充、字节序、系数和偏移量转换等
  4. 答案: 原始值应该是 1505分析: 物理值 = 原始值 * Factor + Offset。反推:原始值 = (物理值 - Offset) / Factor = (150.5 - 0) / 0.1 = 1505。
  5. 答案: 因为通过信号名操作的是具有业务含义的物理值,符合人的思维习惯,并且屏蔽了底层复杂的位运算和字节处理,大大降低了编程难度和出错率
  6. 答案: 不能分析: 使用 msg.SignalName 这种语法的前提是 CAPL 能够识别 SignalName,而这种识别依赖于已加载的 DBC 文件。没有 DBC,CAPL 不知道 EngineSpeed 是什么
  7. 答案: 使用花括号初始化列表,并指定 信号名 = 物理值。基本结构:message MsgName var = { SignalName1 = Value1, SignalName2 = Value2, ... };
  8. 答案: myMsgOnOff 信号的值会是其默认初始值,通常是 0分析: 初始化列表只显式初始化了 EngineSpeed,其他信号会取默认值(通常是 0 或对应类型的零值)
  9. 答案: 可以实现信号值的动态模拟,例如:模拟发动机转速、车速的平稳上升或下降;模拟传感器信号的周期性波动;模拟开关信号的定时切换等
  10. 答案: 总线上会发出两条报文。第二条报文中 SignalA 的值是 20分析: output(msg) 函数会立即将 msg 变量当前的状态发送出去。第一个 output 发送时 SignalA 是 10;之后 SignalA 被修改为 20,第二个 output 发送的是修改后的状态