跳到主要内容
版本:release

串类型(string, buffer)

1 摘要

本章节介绍GS语言中的串类型,包括字符串(string)、缓冲区(buffer)。涵盖对应类型变量创建、核心特性、常用方法、性能陷阱等内容。适用于GS初学者或需要回顾GS串类型相关内容的同学。通过阅读本文档,您将掌握串类型GS语言中的串类型,包括字符串(string)、缓冲区(buffer)的基础视使用,区分二者的使用场景。

2 string 类型

string为字符串,是一个连续存放字符的容器,其与 buffer的却别是string指向的字符串部分放在字符串池中。

string类型的对象一旦被创建,其值就不能被更改。任何看似修改字符串的操作(如拼接、替换),实际上都是创建了一个​​全新的​​ String对象,原始的字符串保持不变。这是它最重要的特性。在 GS 中,string 属于引用类型。

2.1 string 声明

string 变量在声明且未赋予初始值时其默认值为nil,string 变量的声明、初始化、默认值的示例如下:

string str0;                         // 默认值为 nil
string str1 = "Hello World"; // 使用双引号,只能包括一行字符串

writeln(str0);
writeln(str1);

示例2-1:string 变量声明、初始化、及默认值

输出结果如下:

nil
Hello World

2.2 string 创建

string 类型变量的常见创建方法大致是以下几类:

  • 字面量创建,直接向 string类型变量赋值字符串常量如:"Hello"

  • 多行字符串创建,在 GS 中允许以 """P...."""P 的格式定义多行任意格式的字符串,其中字母P可以为[a-z]和[A-Z]的任意字母,但要保证前后一致。

  • 格式化创建,如调用 sprinf 格式化创建

  • 变量序列化方法,如 json.save save_string 等方法

string类型的变量创建方法如下:

// 1.字面量创建
string str0 = "Hello World"; // 使用双引号,只能包括一行字符串

// 2. 多行字符串创建
// 使用"""P ... """P; 的方式可以定义多行任意格式字符串
string str1 = """P
Hello World!
This is my first program,
thank you!
"""P;

// 多行字符串中字母P可以为[a-z]和[A-Z],任意字母无差别,但要保证前后一致。
string str2 = """A
Hello World!
This is external string test.
"""A;

// 3. 格式化方法创建
string str3 = sprintf("%s from str:%d", str0, 4);

// 4. 序列化方法创建
string str4 = save_string({1:"Hello", 2:"Hi"});

writeln(str0);
writeln(str1);
writeln(str2);
writeln(str3);
writeln(str4);

示例2-2:常见的字符串创建方式

对应的输出如下:

Hello World
Hello World!
This is my first program,
thank you!

Hello World!
This is external string test.

Hello World from str:4
{1:"Hello",2:"Hi"}

2.2 string 输入

由于 driver shell 的存在,大多数情况下是不需要自行处理输入的。若有需求请使用 import gs.lang.io;导入用于处理输入输出的 GS 脚本以获取string输入,示例如下:

// mixed input_string(string prompt = EMPTY_STRING, bool echo = true, int max_size= 4096);  // 原型
// 返回值如果是nil表示EOF,数字表示出错,否则就是string
import gs.lang.io;

string str = "";
int total_size = 0; // 总长度
int i = 0;
while (str = input_string("Please input>", false, 1024*1024)) // 输入字符串
{
if(str == "\r\n") // 字符串只有回车符号,停止循环
break;
printf("Driver get input string:%s\n", str); // 输出获取的输入字符串
total_size += str.length(); // 计算获取到的字符串总长度
}
printf("Total get string size is %d\n", total_size);

示例2-3:string 输入使用示例

    示例会不断获取输入字符串并将其输出出来,知道获取到只有回车符号的字符串,循环停止。请自行尝试。

2.3 string 常见操作

接下来我们学习一些常见的数组操作内容,

2.3.1 string 获取长度

可以通过调用 length 函数获取字符串的长度,示例如下:

string str = "Tired today.";
write(str.length()); // 获取字符串长度

示例2-4:获取数组的长度

输出的结果如下:

12

2.3.2 string 拼接字符串

    GS 支持以+ 运算符拼接字符串,示例如下:

string str1 = "Hello";
string str2 = "GS";
writeln(str1 + str2); //字符串拼接为 "HelloGS"

示例2-5:字符串拼接

输出的结果如下:

HelloGS

2.3.3 string 访问字符

string字符串中字符以 [index]索引的形式访问,取字符得到的结果为整型数据,示例如下:

string s = "GS";
int char = s[0]; //获取字符串成员字符
write(char);

示例2-6:字符串成员字符访问

输出的结果如下:

71

2.3.4 string 比较字符串

GS 支持以 == 运算符或 system.string.strcmp 函数调用的形式比较字符串。

  • == 返回 bool 类型用于指示两个字符串是否相等

  • system.string.strcmp 函数返回整型,当返回整型值为0时代表比较的字符串相等

示例如下:

string s1 = "GS";
string s2 = "GS";
string s3 = "gs";
writeln(s1 == s2); // 字符串相等
writeln(s1 == s3); // 字符串不等

writeln(system.string.strcmp(s1, s2)); // 字符串相等
writeln(system.string.strcmp(s1, s3)); // 字符串不等

示例2-7:比较字符串

输出结果如下:

true
false
0
-1

2.3.5 string 获取子串

GS 支持以范围表达式或成员函数 string.get_range 函数调用的形式获取字符串子串:

  • 简单介绍下范围表达式含义,假设已有字符串 string s = "ABCDEFGHI"

    • 范围表达式形式为 [start_idx..end_idx]

    • 范围表达式中正数从 0 开始,倒数从 1开始

    • 范围表达式中倒数前带有 < 符号而正数前没有

    • s[0..3] : 代表从正数第0个字符到正数第3个字符构成的子串

    • s[2..<3]:代表从正数第2个字符到倒数第3个字符构成的子串

    • s[<5..<3]:代表从倒数第5个字符到倒数第3个字符构成的子串

  • string.get_range(pos,count) 成员函数为从 pos 位置起始获取count个字符构成子串

提示

再次强调范围表达式中正数从 0 开始倒数从 1开始

string s = "ABCDEFGHI";
writeln(s[0..3]); // 从正数索引0->字母'A'到正数索引3->字母'D' 构成的子串
writeln(s[2..<3]); // 从正数索引2->字母'C'到倒数个数3->字母'G' 构成的子串
writeln(s[<5..<3]); // 从倒数个数5->字母'E'到倒数个数3->字母'G' 构成的子串
writeln(s.get_range(7,2)); // 从正数索引7->字母'H'开始取两个字符构成的子串。

示例2-8:获取字符串子串示例

输出结果如下:

ABCD
CDEFG
EFG
HI

2.3.6 string 查找子串

    调用 string.find 成员函数查找(从左至右初次匹配的)子串的起始索引位置,当字符串中查找的子串不存在时查找函数返回非法索引 -1 ,示例如下:

string s = "AACDEFGHI";
writeln(s.find("EFG")); // 返回成功找的的子串的起始索引 4
writeln(s.find("HIJ")); // 无法找到的子串,返回非法索引 -1
writeln(s.find("A"); // 从左至右初次匹配字符 'A' 的索引为 0

示例2-9:获取字符串子串起始索引

输出结果如下:

4
-1
0

2.3.6 string 替换子串

    调用 string.replace从左至右替换所有匹配的子串。示例如下:

string str = "AAAAA";
write(str.replace("AA", "BB")); // 从左至右替换匹配的子串,结果应为 "BBBBA"

示例2-10:字符串替换示例

输出结果如下:

BBBBA

2.3.7 字符串分割

    string.explode 方法用于制定分隔符号将字符串分割为多个子串,并将分割的结果放在数组中,示例:

string s = "apple,banana,orange";
write(s.explode(",")); // 以逗号为分割符号进行分割

示例2-11:字符串的分割

示例输出结果如下:

[ /* sizeof() == 3 */
"apple",
"banana",
"orange",
]

2.3.8 去除空格

    string.trim 方法用于去除字符串两端的空格(不会去除字符串中间的空格),示例:

string s = "   Hello, GS!   ";
write(s.trim()); // "Hello, GS!"

示例2-13:去除字符串的左右空格

示例输出结果如下:

Hello, GS!

2.3.9 string 类型转换

     使用 mixed.to_string方法可以将其他数据类型转换为字符串,如整数,浮点数。示例如下:

int int_num = 88888;
float real_num = 6666.666;
bool flag = true;
writeln(int_num.to_string());
writeln(real_num.to_string());
writeln(flag.to_string());

示例2-14:字符串转换

示例输出的结果如下:

88888
6666.67
true

2.4 string 不可变性

    string类型的对象一旦被创建,其值就不能被更改。任何看似修改字符串的操作(如拼接、替换),实际上都是创建了一个​​全新的​ String对象。现在我们具体举例说明:

string original = "apple";  // 初始字符串
original[0]='o'; // 尝试将索引0处字符'a'修改为字符'o'

示例2-15:字符串不可变性

对应的输出如下:

Error(-1): Failed to do assign index like string[int].

可以看到string字符串禁止修改单个字符。

2.5 string 结束符

    需要注意的是string 类型中的实际字符串数据是不保证以字符串结束符 /0结尾的,字符串数据直接通过长度信息指示结尾而不是字符串结束符。

    通常的使用字符串string的情况下是不需要考虑该问题的,在跨语言ffigsbind调用的过程中string 类型的结束符会被自动添加。

3 buffer

    buffer解释为缓存,其内部维持一个连续的线性储存空间。可以用类似数组索引的方式访问buffer中储存的字节。

    向buffer的每个字节写入的数值范围是 0~255(即无符号的一字节整数)。尝试写入超出范围的数值将抛出异常。

3.1 buffer 声明

buffer 变量在声明且未赋予初始值时其默认值为nil,buffer 变量的声明、初始化、默认值的示例如下:

buffer buf0;
buffer buf1 = (buffer)"It's Friday!!!";// 强制类型转换会从 string 创建 buffer
writeln(buf0);
writeln(buf1);

示例3-1:buffer声明示例

示例输出结果如下:

nil
49 74 27 73 20 46 72 69-64 61 79 21 21 21 It's Friday!!!..

3.2 buffer创建

在 GS 中可以通过 :

  • buffer.allocate函数创建指定大小的buffer

  • string 转换构建

  • 通过 save_buffer 序列化其他类型的 GS 变量

示例如下:

buffer buf0 = buffer.allocate(16);
buffer buf1 = (buffer)"It's Friday!!!";
buffer buf2 = save_buffer(["g-bits", "jszx", 8888]);
writeln(buf0);
writeln(buf1);
writeln(restore_value(buf2));

示例3-2:buffer 的创建

示例输出结果如下:

00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00    ................

49 74 27 73 20 46 72 69-64 61 79 21 21 21 It's Friday!!!..

[ /* sizeof() == 3 */
"g-bits",
"jszx",
8888,
]

    需要注意的是通过string->buffer强制类型转换从字面量string创建的buffer是只读的无法修改,若需要从字面量构建需要的修改的buffer 请为字面量string调用dup()函数拷贝。示例如下:

buffer buf = (buffer)"AAA".dup();
buf.insert(0, "B");
writeln(buf);

buffer buf1 = (buffer)"AAA";
buf1.insert(0, "B");
writeln(buf1);

示例3-2:非拷贝字面量创建的只读buffer示例

输出结果如下:

42 41 41 41                                        BAAA............

Error(-1): Can not modify read-only value.

    可以看到调用dup函数后从 string 字面量创建的buffer成功的被修改,而直接从 string字面量创建的buffer修改则直接报错。

3.3 buffer 常见操作

3.3.1 buffer 修改

GS 支持的修改 buffer 数据的方式有多种:

  • 插入 buffer.insert(pos,val)

  • 赋值 buffer.set(int pos, mixed val)

  • 向后插入 buffer.push_back(val)

  • 索引赋值 buffer_instant[idx] = val;

buffer 写入的示例如下:

buffer buf = (buffer)"Hllo.".dup();
buf.insert(1, "e"); // 在索引 1(从0起始)的位置插入字符 'e'
buf.set(5, 33); // 将索引 5(从0起始)处的字符 '.' 设置为字符 '!'(33 为字符'!'对应的ascii码)
buf.push_back(" g-bits."); // 在 buffer 串后续插入 " g-bits." 串
buf[buf.length() - 1] = '!';// 将 buffer 末尾的 字符 '.' 设置为字符 '!'
write(buf); // 输出 buf

示例3-3:buffer 写入操作示例

示例的输出结果如下:

48 65 6C 6C 6F 21 20 67-2D 62 69 74 73 21          Hello! g-bits!..

同样buffer 也支持以 + 运算符进行串拼接,示例如下:

buffer buf = (buffer)"Hello World"; // 初始化buf
buffer buf1 = (buffer)"Hello", buf2 = (buffer)" World";
buffer add_buf = buf1 + buf2; // add_buf为"Hello World"
write(add_buf);

示例 3-4:buffer 拼接操作

输出的结果如下:

48 65 6C 6C 6F 20 57 6F-72 6C 64                   Hello World.....

3.3.2 buffer 读取

    GS 支持获取 buffer的长度,取buffer特定索引处值,取子 buffer 等操作,查找特定字符索引等操作:

  • 获取长度 buffer_instance.length()

  • 查找字符索引 buffer_instance.find(mixed val) ,注意为从左至右返回第一个匹配的序列索引

  • 取子 buffer buffer_instance.get_range(int pos, int count = -1)

  • 取索引处值 buffer_instance.get(int_pos)

  • 索引表达式取索引处值 buffer_instance[pos]

  • 范围表达式获取子串 buffer_instance[begin..end] 规则与string的范围表达式相同。

示例如下:

buffer buf = (buffer)"Hello GS";
printlnf("buf length is %d", buf.length()); // 获取 buf 串的长度,应为 8
printlnf("'ll' index is %d", buf.find("ll")); // 获取 "ll" 子串的索引,应为 2
printlnf("sub buf in pos:6 len:2 is %O", buf.get_range(6,2)); // 获取在索引6处,长度为2的子串,应为 'GS'
printlnf("char at buf pos:1 is %d", buf.get(1)); // 在索引1位置的字母为 'e', ascii 表对应整型数值为 101
printlnf("char at buf pos:1 is %d", buf[1]); // 在索引1位置的字母为 'e', ascii 表对应整型数值为 101
printlnf("sub buff from 0 to 4 is %O", buf[0..4]); // 从索引 0 字母 'H'到索引 4 字母 'o' 构成的子串,应为 "Hello"

示例3-5:buffer 读取示例

示例输出结果如下:

buf length is 8
'll' index is 2
sub buf in pos:6 len:2 is 47 53 GS..............

char at buf pos:1 is 101
char at buf pos:1 is 101
sub buff from 0 to 4 is 48 65 6C 6C 6F Hello...........

3.3.4 buffer 删除

    使用 clear方法可以清空 buffer的内容。

示例如下:

buffer buf = (buffer)"It's Friday!!!".dup();
writeln(buf);
buf.clear();
writeln(buf);
writeln(buf.length());

示例3-6:使用clear清空buffer

输出结果如下:

49 74 27 73 20 46 72 69-64 61 79 21 21 21          It's Friday!!!..

<Empty buffer>
0

    使用delete_at(int pos, int n = 1)方法可以移除buffer在特定索引pos处的n个字符,示例如下:

buffer b = (buffer)"Hello need to remove buffer!".dup();
b.delete_at(6, 15); // 移除从索引6,字母'n'开始的16个字符,移除后b应为"Hello buffer!"
write(b);

示例3-7:使用delete_at移除指定字符串

输出结果如下:

48 65 6C 6C 6F 20 62 75-66 66 65 72 21             Hello buffer!...

4 串类型陷阱

4.1 访问越界

    GS中bufferstring的索引操作都是从数字 0 开始的,超出有效范围的索引的访问或修改操作均会导致报错,示例分别如下:

buffer buf = (buffer)"Hello GS";
writeln(buf[100]);

示例4-1:buffer越界访问

报错结果如下:

Error(-1): Index is out of range

同样让我们尝试下越界访问字符串中字符:

string str = "Hello GS";
writeln(str[100]);

示例4-2:string越界访问

    会得到同样的索引越界提示。

4.2 性能陷阱

    string 类型变量的修改实际上每次都会尝试一个全新的string实例,这意味着在高频的使用场景下string类型会频繁的触发内存申请和GC,会有更高的运行时消耗。尝试在循环拼接50000个字符'a',让我们看看使用bufferstring的时间消耗对比:

// 测试循环拼接 50000 次
int count = 50000;

// 1. 使用 String 拼接
int start1 = time.time_ms();
string strResult = "";
for (int i = 0; i < count; i++)
{
strResult += "a"; // 每次循环都会创建新对象
}
int time1 = time.time_ms() - start1;

// 2. 使用 buffer 拼接
int start2 = time.time_ms();
buffer bufResult = (buffer)"".dup();
for (int i = 0; i < count; i++)
{
bufResult.push_back("a");// 始终操作同一个对象
}
int time2 = time.time_ms() - start2;

printlnf("String concat time cost: " + time1 + " ms");
printlnf("Buffer pushback csot: " + time2 + " ms");
t: " + time2 + " ms");

示例4-3:串拓展耗时对比

    在我的机器上输出如下:

String concat time cost: 159 ms
Buffer pushback csot: 1 ms

    可以看到,string类型的耗时比buffer足足高出了两个量级。所以在需要高频修改串数据的场景,考虑使用buffer会为我们带来更好的性能表现。

  • 一个小拓展,在上述示例bufResult.push_back(a);语句后添加语句if(i % 8 == 0) writeln(bufResult.capacity());尝试一窥buffer的内存拓展机制

5 扩展阅读

    现在我们已经了解的串类型stringbuffer的基础使用方式,后续若需要进一步了解 GS 串类型的其他操作方法(如string 判断是否以特定字符串开头,判断字母是否均是大写,移除特定后缀)(buffer以特定类型查看或修改某一所索引处数据,正序及逆序查找子串索引等)请阅读一下文档链接:

6 总结

在 GS 编程语言中,stringbuffer是处理文本与二进制数据的核心类型,理解其特性和适用场景对编写高效代码至关重要。

特性string 类型buffer 类型
​可变性​不可变可变
​主要用途​文本处理、显示、消息传递二进制数据处理、高性能字符串操作、I/O 操作
​性能特点​修改操作产生新对象,可能开销较大直接修改,高效,适合频繁操作
​内存使用​可能产生较多临时对象通常预先分配,更节省内存

​📌 选择建议​

  • ​操作少量文本数据​​或需要​​字符串常量​​时,使用 string

  • 需要进行​​大量字符串拼接、修改​​,或处理​​二进制数据、网络数据包、文件内容​​时,使用 buffer

​⚠️ 注意事项​

使用 buffer时需注意​​缓冲区溢出​​和​​字符编码​​问题。在多线程环境下共享和修改同一个 buffer时,需要自行处理同步问题。

希望这份总结能帮助你更好地理解和使用 GS 语言中的 stringbuffer类型。接下来让我们来了解GS中的容器类型 array