☰
CAPL的数据类型(一)
2026/10/2 6:36:14 网站建设 项目流程

系列文章目录

CAPL的数据类型(一)

CAPL的数据类型(二)


文章目录

系列文章目录

前言

一、数值类型及其格式化打印

1. 数值类型

2. 格式化打印

二、数组

1. 数组的初始化和访问

2. 求数组的大小

3. 数组作为参数传递

4. 字符数组

三、枚举类型

1. 枚举类型的定义

2. 枚举类型的应用

3. 枚举类型的语法拓展

四、结构体类型

1. 结构体类型定义

2. 结构体变量初始化

3. 结构体变量成员访问与赋值

4. 结构体数组

5. 结构体作为函数参数

6. 结构体嵌套

7. 结构体字节对齐

8. 字节对齐应用

总结


前言

CAPL(Communication Access Programming Language)是用于CANoe、CANalyzer等车载网络仿真与测试工具的编程语言,广泛应用于汽车电子系统开发中的通信协议测试与自动化脚本编写。其数据类型设计兼顾实时性、可读性与嵌入式系统的资源限制,是理解CAPL程序逻辑的基础。


一、数值类型及其格式化打印

1. 数值类型

CAPL 语言中的数值类型和 C 语言中的数值类型相似,主要有以下 3 点区别:

1. CAPL 语言中的 byte,word,dword,qword 数值类型和 C 语言中的 unsigned char,unsigned int,unsigned long,unsigned int64 数据类型是相对应的;

无符号数:

  • byte -> unsigned char,1 byte
  • word -> unsigned int,2 bytes
  • dword -> unsigned long,4 bytes
  • qword -> unsigned int64, 8 bytes

有符号数:

  • char -> char,1 byte
  • int -> short,2 bytes
  • long -> long,4 bytes
  • int64 -> long long,8 bytes

浮点数:

  • float -> double,8 bytes
  • double -> double,8 bytes

2. CAPL 中的 float 和 double 不区分单精度和双精度,两者都是双精度,都占 8 字节,可以认为没有区别;

3. CAPL 语言中的 int 类型占两个字节,且和编译器无关;

2. 格式化打印

在 CAPL 语言中,通过 write 函数来打印内容,进行格式化输出;

但是 CAPL 编译器不会检测打印格式是否正确,如果打印格式是错误的,打印出来的内容就是非预期的,编译器仍然不会报错。所以使用这个函数要注意打印格式是否正确;

有符号显示:

  • int -> %d
  • long -> %d 或者 %ld
  • int64 -> %lld 或者 %I64d

无符号显示:

  • byte/word -> %u
  • dword -> %lu
  • qword -> %llu 或者 %I64u

十六进制显示:

  • byte/word/int -> %x
  • dword/long -> %lx
  • qword/int64 -> %llx 或者 %I64x

十六进制大写显示:

  • byte/word/int -> %X
  • dword/long -> %lX
  • qword/int64 -> %llX 或者 %I64X

浮点数显示:

  • float/double -> %g 或者 %f

字符/字符串显示:

  • character -> %c
  • string -> %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 range;

3. 数组作为参数传递

当数组作为形参时,函数内部更改形参数组的元素值,会同步更改函数体外面的数组;

可以类比 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,不包含返回 0;

on 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)); }

分别设置字节对齐数为 1,2,4:

8. 字节对齐应用

通过 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 语言的数据类型,包括基本类型,数组,枚举和结构体,并通过代码示例演示数据类型的使用方法。

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

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

立即咨询