系列文章目录
CAPL的数据类型(一)
CAPL的数据类型(二)
文章目录
系列文章目录
[1. 数值类型](#1. 数值类型)
[2. 格式化打印](#2. 格式化打印)
[1. 数组的初始化和访问](#1. 数组的初始化和访问)
[2. 求数组的大小](#2. 求数组的大小)
[3. 数组作为参数传递](#3. 数组作为参数传递)
[4. 字符数组](#4. 字符数组)
[1. 枚举类型的定义](#1. 枚举类型的定义)
[2. 枚举类型的应用](#2. 枚举类型的应用)
[3. 枚举类型的语法拓展](#3. 枚举类型的语法拓展)
[1. 结构体类型定义](#1. 结构体类型定义)
[2. 结构体变量初始化](#2. 结构体变量初始化)
[3. 结构体变量成员访问与赋值](#3. 结构体变量成员访问与赋值)
[4. 结构体数组](#4. 结构体数组)
[5. 结构体作为函数参数](#5. 结构体作为函数参数)
[6. 结构体嵌套](#6. 结构体嵌套)
[7. 结构体字节对齐](#7. 结构体字节对齐)
[8. 字节对齐应用](#8. 字节对齐应用)
前言
CAPL(Communication Access Programming Language)是用于CANoe、CANalyzer等车载网络仿真与测试工具的编程语言,广泛应用于汽车电子系统开发中的通信协议测试与自动化脚本编写。其数据类型设计兼顾实时性、可读性与嵌入式系统的资源限制,是理解CAPL程序逻辑的基础。
一、数值类型及其格式化打印
1. 数值类型
CAPL 语言中的数值类型和 C 语言中的数值类型相似,主要有以下 3 点区别:
- 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
-
CAPL 中的 float 和 double 不区分单精度和双精度,两者都是双精度,都占 8 字节,可以认为没有区别;
-
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
十六进制显示打印示例:
cpp
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);
}
十进制数显示打印示例:
cpp
on key 'b'
{
int a = 32767;
long b = 214783647;
int64 c = 123456778902LL;
write("a = %d", a);
write("b = %ld", b);
write("c = %lld", c);
}
浮点数打印示例:
cpp
on key 'c'
{
double a = 345.541236732;
float b = 1023.2893042123;
write("a = %f", a);
write("b = %f", b);
write("b = %.2f", b); // 保留两位小数
}
字符及字符串打印示例:
cpp
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. 数组的初始化和访问
数组的定义及初始化示例:
cpp
on key 'a'
{
int intArray[10]; // 数组的定义
byte byteArray[3] = {0x01, 0x02, 0x03}; // 数组的就地初始化
dword dwordArray[3] = {0x11223344, 0x55667788}; // 数组的部分初始化
}
数组可以根据索引/下标进行访问和赋值;
数组的访问和赋值示例:
cpp
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() 函数示例:
cpp
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 语言传数组传的是地址的别名,修改数组等同于修改内存地址中保存的内容;
示例代码:
cpp
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. 字符数组
字符数组的初始化示例:
cpp
on key '1'
{
char a[5] = {'h', 'e', 'l', 'l', 'o'}; // 就地初始化数组
char b[6] = "hello";
write("%s", a);
write("%s", b);
}
二维字符数组的初始化示例:
cpp
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() 是求字符数组内字符串的长度;
cpp
on key '3'
{
char a[200] = "Apple";
write("字符串的长度: %d", strlen(a));
}
字符串的格式化输出示例:
snprintf() 函数是 CAPL 中常用的格式化输出字符串的函数,语法和 write() 函数一样,最多支持 64 个参数,函数返回值是字符串的长度;
cpp
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();
cpp
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,也可以手动指定第一个成员的值,后续成员依此递增。
cpp
on key '1'
{
// 定义方式一
enum DAY
{
MON = 1, TUE, WED, THU, FRI, SAT, SUN
};
enum DAY day;
}
cpp
on key '2'
{
// 定义方式二
enum DAY
{
MON = 1, TUE, WED, THU, FRI, SAT, SUN
} day;
}
cpp
on key '3'
{
// 定义方式三
enum
{
MON = 1, TUE, WED, THU, FRI, SAT, SUN
} day;
}
2. 枚举类型的应用
枚举类型的最重要的作用就是能够提高代码的可读性和可维护性;
1. 作为常数使用
cpp
on key '4'
{
int value;
enum Bool
{
kFalse,
kTrue
};
if(value == kTrue)
write("value = kTrue");
}
2. 搭配 switch-case 使用 和 作为函数形参
搭配 switch-case 使用,代码的可读性更强;
作为函数形参,能够限制用户传入的内容,只能是枚举类型定义的内容,使函数的调用更加安全;
cpp
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. 作为函数返回值
cpp
variables
{
enum Bool
{
kFalse, kTrue
};
}
enum Bool getStatus(int flag)
{
if(flag)
return kTrue;
else
return kFalse;
}
3. 枚举类型的语法拓展
CAPL 语言枚举类型语法和 C 一致;
枚举类型本质上也是整型数据,因此可以通过算术运算转换为整型:
cpp
on key '6'
{
int i;
enum State
{
KEY_OFF, KEY_ON
} state;
state = KEY_OFF;
i = state + 1;
write("i = %d", i);
}
Name() 方法:
可以通过 Name() 方法获取当前枚举变量的名称;
cpp
on key '6'
{
int i;
enum State
{
KEY_OFF, KEY_ON
} state;
state = KEY_OFF;
write("state 变量当前的状态为: %s", state.Name());
}
containsValue() 方法:
可以通过 containsValue() 方法判断,当前枚举变量中是否包含某个数值的成员,包含返回 1,不包含返回 0;
cpp
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. 结构体类型定义
代码示例:
cpp
on key '1'
{
struct Student
{
char name[20];
int id;
char gender;
int age;
int score;
} stu1;
}
cpp
on key '2'
{
struct Student
{
char name[20];
int id;
char gender;
int age;
int score;
};
struct Student stu;
}
2. 结构体变量初始化
代码示例:
cpp
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. 结构体变量成员访问与赋值
结构体通过访问运算符 "." 来访问结构体成员;
代码示例:
cpp
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. 结构体数组
结构体数组是多个相同类型的结构体元素组成的数组。在实际应用中,往往需要结构体数组保存一组数据。
示例代码:
cpp
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. 结构体作为函数参数
结构体也可以作为函数的参数,和数组类型一样,在函数内部修改结构体的成员变量的值,函数外部的结构体变量的值也会发生变化。
因为函数内部结构体和外部变量结构体指向的内存地址是相同的。
代码示例:
cpp
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. 结构体嵌套
结构体嵌套要注意,先定义成员结构体类型,再定义结构体类型;
代码示例:
cpp
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):返回结构体成员在内存中的偏移量;
字节对齐规则:
默认情况下,字节对齐由结构体中数据类型最大的成员决定;
如果结构体的后一个成员的字节比前一个成员的数据类型字节大,则后一个成员的内存地址的偏移量 = 前一个成员内存地址偏移量 + 后一个成员的字节大小;
如果结构体的后一个成员的字节比前一个成员的数据类型字节小,则后一个成员的内存地址的偏移量 = 前一个成员内存地址偏移量 + 前一个成员的字节大小;
结构体占内存的字节数 = 最后一个成员的额偏移量 + 最后一个成员的额字节大小;
代码示例:
cpp
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() 设置的,代码示例:
cpp
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 数组;
cpp
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 语言的数据类型,包括基本类型,数组,枚举和结构体,并通过代码示例演示数据类型的使用方法。