)
系列文章目录CAPL的数据类型一CAPL的数据类型二文章目录系列文章目录前言一、数值类型及其格式化打印1. 数值类型2. 格式化打印二、数组1. 数组的初始化和访问2. 求数组的大小3. 数组作为参数传递4. 字符数组三、枚举类型1. 枚举类型的定义2. 枚举类型的应用3. 枚举类型的语法拓展四、结构体类型1. 结构体类型定义2. 结构体变量初始化3. 结构体变量成员访问与赋值4. 结构体数组5. 结构体作为函数参数6. 结构体嵌套7. 结构体字节对齐8. 字节对齐应用总结前言CAPLCommunication Access Programming Language是用于CANoe、CANalyzer等车载网络仿真与测试工具的编程语言广泛应用于汽车电子系统开发中的通信协议测试与自动化脚本编写。其数据类型设计兼顾实时性、可读性与嵌入式系统的资源限制是理解CAPL程序逻辑的基础。一、数值类型及其格式化打印1. 数值类型CAPL 语言中的数值类型和 C 语言中的数值类型相似主要有以下 3 点区别1. CAPL 语言中的 byteworddwordqword 数值类型和 C 语言中的 unsigned charunsigned intunsigned longunsigned int64 数据类型是相对应的无符号数byte - unsigned char1 byteword - unsigned int2 bytesdword - unsigned long4 bytesqword - unsigned int64 8 bytes有符号数char - char1 byteint - short2 byteslong - long4 bytesint64 - long long8 bytes浮点数float - double8 bytesdouble - double8 bytes2. CAPL 中的 float 和 double 不区分单精度和双精度两者都是双精度都占 8 字节可以认为没有区别3. CAPL 语言中的 int 类型占两个字节且和编译器无关2. 格式化打印在 CAPL 语言中通过 write 函数来打印内容进行格式化输出但是 CAPL 编译器不会检测打印格式是否正确如果打印格式是错误的打印出来的内容就是非预期的编译器仍然不会报错。所以使用这个函数要注意打印格式是否正确有符号显示int - %dlong - %d 或者 %ldint64 - %lld 或者 %I64d无符号显示byte/word - %udword - %luqword - %llu 或者 %I64u十六进制显示byte/word/int - %xdword/long - %lxqword/int64 - %llx 或者 %I64x十六进制大写显示byte/word/int - %Xdword/long - %lXqword/int64 - %llX 或者 %I64X浮点数显示float/double - %g 或者 %f字符/字符串显示character - %cstring - %s十六进制显示打印示例on key a { byte a 0x01; word b 0xab20; dword c 0x10203040; qword d 0x1020304050607080LL; write(a 0x%x, a); write(b 0x%x, b); write(c 0x%lx, c); write(d 0x%llx, d); }十进制数显示打印示例on key b { int a 32767; long b 214783647; int64 c 123456778902LL; write(a %d, a); write(b %ld, b); write(c %lld, c); }浮点数打印示例on key c { double a 345.541236732; float b 1023.2893042123; write(a %f, a); write(b %f, b); write(b %.2f, b); // 保留两位小数 }字符及字符串打印示例on key d { char a A; char b 65; char c[100] Hello World!; write(a %c, a); write(b %c, b); write(c %s, c); }二、数组数组是一组固定大小且元素类型相同的数据集合数组在内存上是连续的。1. 数组的初始化和访问数组的定义及初始化示例on key a { int intArray[10]; // 数组的定义 byte byteArray[3] {0x01, 0x02, 0x03}; // 数组的就地初始化 dword dwordArray[3] {0x11223344, 0x55667788}; // 数组的部分初始化 }数组可以根据索引/下标进行访问和赋值数组的访问和赋值示例on key b { int intArray[2] {1, 2}; write(array[0] %d, intArray[0]); // 根据下标进行访问 intArray[1] 10; // 根据下标进行修改 write(array[1] %d, intArray[1]); }2. 求数组的大小CAPL 中可以使用 elCount() 函数返回数组的大小如果是多维数组返回最外层数组的大小elCount() 函数示例on key c { int a[2] {1, 2}; int b[3][4] { {1, 2, 3, 4}, {5, 6, 7, 8}, {9, 0, 1, 2} }; write(elCount(a) %d, elCount(a)); write(elCount(b) %d, elCount(b)); }一旦数组发生越界访问CANoe 就会报错invalid array range3. 数组作为参数传递当数组作为形参时函数内部更改形参数组的元素值会同步更改函数体外面的数组可以类比 java 语言进行理解Java 语言中引用类型作为形参传递时函数内部更改引用类型变量指向的对象的值会影响到外部的变量本身基本类型则不受影响。基本类型作为形参传递时会被复制一份在复制的变量上进行修改函数运行结束复制的变量的生命周期结束不影响外部的变量引用类型作为参数传递的时候虽然引用也会被复制一份但是指向的对象还是原来的对象。函数内部针对变量进行修改时如果修改的是堆上的内容就会影响到外部的变量如果修改的是引用的指向则不会影响外部的变量函数内部更改形参数组的元素值等价于修改堆上的对象会影响到外部的变量。实际上 CAPL 语言传数组传的是地址的别名修改数组等同于修改内存地址中保存的内容示例代码on key d { long i; int a[2] {1, 2}; write(调用函数前, 数组的值:); for(i 0; i elCount(a); i) write(a[%ld] %d, i, a[i]); function(a); // 调用函数 write(调用函数后, 数组的值:); for(i 0; i elCount(a); i) write(a[%ld] %d, i, a[i]); } void function(int a[]) { long i; for(i 0; i elCount(a); i) a[i] 1; }4. 字符数组字符数组的初始化示例on key 1 { char a[5] {h, e, l, l, o}; // 就地初始化数组 char b[6] hello; write(%s, a); write(%s, b); }二维字符数组的初始化示例on key 2 { long i; char fruit[3][200] { Apple, Orange, Banana }; for(i 0; i elCount(fruit); i) write(fruit[%d]: %s, i, fruit[i]); }求字符串的长度示例elCount() 函数是求数组的大小strlen() 是求字符数组内字符串的长度on key 3 { char a[200] Apple; write(字符串的长度: %d, strlen(a)); }字符串的格式化输出示例snprintf() 函数是 CAPL 中常用的格式化输出字符串的函数语法和 write() 函数一样最多支持 64 个参数函数返回值是字符串的长度on key 4 { long size; char text[200]; char sig[20] vhSpeed; double speed 60.5; size snprintf(text, elCount(text), 车速信号名: %s; 车速信号值: %.1f, sig, speed); write(return size %d, size); write(text: %s, text); }注意英文字符的长度是 1中文字符的长度是 2字符数组赋值示例字符数组初始化的时候可以直接赋值后续不能直接赋值。赋值必须使用 CAPL 内置的字符串函数 strncpy() 或者 snprintf()on key 5 { char a[200] Apple; char b[200]; strncpy(a, orange, elCount(a)); // 赋值方式 1 write(a %s, a); snprintf(b, elCount(b), orange); // 赋值方式 2 write(b %s, b); }三、枚举类型枚举类型是用户自定义的数据类型通常使用枚举类型能提高代码的可读性和可维护性使用枚举类型的形参更能限制用户调用函数时的输入避免用户传非法值代码更加安全1. 枚举类型的定义枚举类型默认第一个成员的值为 0也可以手动指定第一个成员的值后续成员依此递增。on key 1 { // 定义方式一 enum DAY { MON 1, TUE, WED, THU, FRI, SAT, SUN }; enum DAY day; }on key 2 { // 定义方式二 enum DAY { MON 1, TUE, WED, THU, FRI, SAT, SUN } day; }on key 3 { // 定义方式三 enum { MON 1, TUE, WED, THU, FRI, SAT, SUN } day; }2. 枚举类型的应用枚举类型的最重要的作用就是能够提高代码的可读性和可维护性1. 作为常数使用on key 4 { int value; enum Bool { kFalse, kTrue }; if(value kTrue) write(value kTrue); }2. 搭配 switch-case 使用 和 作为函数形参搭配 switch-case 使用代码的可读性更强作为函数形参能够限制用户传入的内容只能是枚举类型定义的内容使函数的调用更加安全variables { enum DAY { MON 1, TUE, WED, THU, FRI, SAT, SUN } day; } on key 5 { enumTest(MON); } void enumTest(enum DAY day) { switch(day) { case MON: write(Monday); break; case TUE: write(Tuesday); break; case WED: write(Wednesday); break; case THU: write(Thursday); break; case FRI: write(Friday); break; case SAT: write(Saturday); break; case SUN: write(Sunday); break; default: write(Error); } }3. 作为函数返回值variables { enum Bool { kFalse, kTrue }; } enum Bool getStatus(int flag) { if(flag) return kTrue; else return kFalse; }3. 枚举类型的语法拓展CAPL 语言枚举类型语法和 C 一致枚举类型本质上也是整型数据因此可以通过算术运算转换为整型on key 6 { int i; enum State { KEY_OFF, KEY_ON } state; state KEY_OFF; i state 1; write(i %d, i); }Name() 方法可以通过 Name() 方法获取当前枚举变量的名称on key 6 { int i; enum State { KEY_OFF, KEY_ON } state; state KEY_OFF; write(state 变量当前的状态为: %s, state.Name()); }containsValue() 方法可以通过 containsValue() 方法判断当前枚举变量中是否包含某个数值的成员包含返回 1不包含返回 0on key 6 { int i; enum State { KEY_OFF, KEY_ON } state; write(state 变量是否包含值为 1 的成员: %d, state.containsValue(1)); write(state 变量是否包含值为 2 的成员: %d, state.containsValue(2)); }四、结构体类型结构体是一种复合数据类型由一系列具有相同或者不同类型的数据构成的数据集合1. 结构体类型定义代码示例on key 1 { struct Student { char name[20]; int id; char gender; int age; int score; } stu1; }on key 2 { struct Student { char name[20]; int id; char gender; int age; int score; }; struct Student stu; }2. 结构体变量初始化代码示例on key 3 { struct Student { char name[20]; int id; char gender; int age; int score; }; struct Student stu1 { Caocao, 123, A }; struct Student stu2 { name Machao, id 124, gender A }; }3. 结构体变量成员访问与赋值结构体通过访问运算符 . 来访问结构体成员代码示例on key 4 { struct Student { char name[20]; int id; char gender; int age; int score; } stu1; // 结构体成员变量赋值 stu1.id 123; stu1.gender A; stu1.age 20; strncpy(stu1.name, Caocao, elCount(stu1.name)); // 结构体成员变量访问 write(stu1.name: %s, stu1.name); write(stu1.id: %d, stu1.id); write(stu1.gender: %c, stu1.gender); write(stu1.age: %d, stu1.age); }4. 结构体数组结构体数组是多个相同类型的结构体元素组成的数组。在实际应用中往往需要结构体数组保存一组数据。示例代码on key 5 { long i; struct Student { char name[20]; int id; char gender; int age; int score; }; struct Student stu[3] { {Caocao, 123, A}, {Machao, 124, A}, {Zhangfei, 125, A} }; for(i 0; i elCount(stu); i) write(name: %s; id: %d; gneder: %c, stu[i].name, stu[i].id, stu[i].gender); }5. 结构体作为函数参数结构体也可以作为函数的参数和数组类型一样在函数内部修改结构体的成员变量的值函数外部的结构体变量的值也会发生变化。因为函数内部结构体和外部变量结构体指向的内存地址是相同的。代码示例variables { struct Student { char name[20]; int id; char gender; int age; int score; }; struct Student stu {Caocao, 123, A}; } on key 6 { write(调用函数前, 结构体成员 name %s, stu.name); structTest(stu); write(调用函数后, 结构体成员 name %s, stu.name); } void structTest(struct Student stu) { strncpy(stu.name, Zhangfei, elCount(stu.name)); }6. 结构体嵌套结构体嵌套要注意先定义成员结构体类型再定义结构体类型代码示例on key 7 { struct Score { int chinese; int math; int english; }; struct Student { char name[20]; int id; char gender; int age; struct Score score; } stu; strncpy(stu.name, Caocao, elCount(stu.name)); stu.score.chinese 90; stu.score.math 99; }7. 结构体字节对齐计算机的内存空间是按照字节划分的。但是变量并不是按照顺序逐个字节排列的。变量是按照字节对齐的规则去排列的。CAPL 字节对齐函数库__size_of(aligned-type)返回结构体按内存的字节数__alignment_of(aligned-type)返回结构体的对齐字节__offset_of(struct-type, member)返回结构体成员在内存中的偏移量字节对齐规则默认情况下字节对齐由结构体中数据类型最大的成员决定如果结构体的后一个成员的字节比前一个成员的数据类型字节大则后一个成员的内存地址的偏移量 前一个成员内存地址偏移量 后一个成员的字节大小如果结构体的后一个成员的字节比前一个成员的数据类型字节小则后一个成员的内存地址的偏移量 前一个成员内存地址偏移量 前一个成员的字节大小结构体占内存的字节数 最后一个成员的额偏移量 最后一个成员的额字节大小代码示例on key 8 { struct LongPoint { byte x; qword y; byte z; int k; }; write(结构体占内存字节数: %d, __size_of(struct LongPoint)); write(结构体的字节对齐: %d, __alignment_of(struct LongPoint)); write(结构体的元素 x 内存地址偏移量: %d, __offset_of(struct LongPoint, x)); write(结构体的元素 y 内存地址偏移量: %d, __offset_of(struct LongPoint, y)); write(结构体的元素 z 内存地址偏移量: %d, __offset_of(struct LongPoint, z)); write(结构体的元素 k 内存地址偏移量: %d, __offset_of(struct LongPoint, k)); }按照字节对齐的规则分析字节对齐的大小是可以通过 _align() 设置的代码示例on key 9 { _align(2) struct LongPoint { byte x; qword y; byte z; int k; }; write(结构体占内存字节数: %d, __size_of(struct LongPoint)); write(结构体的字节对齐: %d, __alignment_of(struct LongPoint)); write(结构体的元素 x 内存地址偏移量: %d, __offset_of(struct LongPoint, x)); write(结构体的元素 y 内存地址偏移量: %d, __offset_of(struct LongPoint, y)); write(结构体的元素 z 内存地址偏移量: %d, __offset_of(struct LongPoint, z)); write(结构体的元素 k 内存地址偏移量: %d, __offset_of(struct LongPoint, k)); }分别设置字节对齐数为 1248. 字节对齐应用通过 memcpy_h2n() 函数将结构体数据赋值给 byte 数组on key 0 { long i; byte data[100]; struct LongPoint { byte x; qword y; byte z; int k; }; struct LongPoint test { x 0x01, y 0x1122334455667788LL, z 0x55, k 0x0102 }; memcpy_h2n(data, test); for(i 0; i elCount(test); i) write(地址偏移量: %d; 值: 0x%x, i, data[i]); }总结本文简单介绍了 CAPL 语言的数据类型包括基本类型数组枚举和结构体并通过代码示例演示数据类型的使用方法。