第19课|字符串处理

7 阅读7分钟

本课目标

完成本课后,你将能够:

  • 掌握固定字符串 String 与无界字符串 Unbounded_String 的使用
  • 熟练进行字符串的查找、替换、分割与连接
  • 理解字符串与数值的相互转换
  • 学会使用 Ada.Strings.FixedAda.Strings.BoundedAda.Strings.Unbounded 三套处理方案
  • 应用字符串处理解决实际问题(如解析配置文件、文本分析)

一、Ada 字符串家族

Ada 提供三种字符串类型,适应不同场景:

类型特点适用场景
String内置(无额外包)固定长度,编译时确定长度已知的小字符串
Bounded_StringAda.Strings.Bounded长度有上限,运行时可变长度有限但可变
Unbounded_StringAda.Strings.Unbounded长度无上限,动态分配长度未知或变化频繁
-- 固定字符串(最常用)
S1 : String (1 .. 10);              -- 长度固定为10
S2 : constant String := "Hello";    -- 长度由字面量决定,不可变

-- 无界字符串(最灵活)
with Ada.Strings.Unbounded;
use Ada.Strings.Unbounded;
S3 : Unbounded_String := To_Unbounded_String ("Hello");
S4 : Unbounded_String := Null_Unbounded_String;

二、固定字符串(String)

2.1 声明与初始化

-- 声明并初始化
S1 : String := "Ada";
S2 : String (1 .. 5) := "Hello";
S3 : String (1 .. 3) := ('A', 'd', 'a');  -- 字符数组形式

-- 未初始化,需后续赋值
S4 : String (1 .. 20);
S4 (1 .. 5) := "World";

2.2 字符串连接

使用 & 运算符:

Full : constant String := "Hello" & " " & "World";  -- "Hello World"
Prefix : String := "Ada";
Result : String := Prefix & " is great";            -- "Ada is great"

2.3 字符串比较

直接使用关系运算符(按字典序):

if "abc" < "abd" then   -- True
if "Ada" = "ADA" then   -- False(大小写敏感)
if "Ada" /= "ada" then  -- True

2.4 字符串属性与遍历

S : String := "Ada";
Len : constant Integer := S'Length;  -- 3
First : constant Integer := S'First;  -- 1
Last : constant Integer := S'Last;    -- 3

-- 遍历字符
for I in S'Range loop
   Ada.Text_IO.Put (S (I));
end loop;

三、无界字符串(Unbounded_String)

3.1 基本操作

with Ada.Strings.Unbounded;
with Ada.Strings.Unbounded.Text_IO;  -- 用于输入输出

use Ada.Strings.Unbounded;

procedure Unbounded_Demo is
   U : Unbounded_String;
begin
   -- 赋值
   U := To_Unbounded_String ("Hello");
   U := U & " World";                -- 连接
   U := "New " & U;                  -- 连接结果再赋值
   
   -- 输出
   Ada.Strings.Unbounded.Text_IO.Put_Line (U);
   
   -- 获取固定字符串形式
   declare
      Fixed : String := To_String (U);
   begin
      Ada.Text_IO.Put_Line (Fixed);
   end;
end Unbounded_Demo;

3.2 常用函数

函数作用
To_Unbounded_String (S : String)将固定字符串转为无界字符串
To_String (U : Unbounded_String)将无界字符串转为固定字符串
Length (U : Unbounded_String)返回字符数
Append (U : in out Unbounded_String; S : String)追加
& 运算符连接(返回新无界字符串)
"=""/=""<"比较操作(与固定字符串和无界字符串均可比较)

3.3 输入输出

with Ada.Strings.Unbounded.Text_IO;

procedure U_IO is
   U : Unbounded_String;
begin
   Ada.Text_IO.Put ("Enter text: ");
   Ada.Strings.Unbounded.Text_IO.Get_Line (U);
   Ada.Strings.Unbounded.Text_IO.Put_Line ("You entered: " & U);
end U_IO;

四、字符串查找与索引

4.1 基本查找

使用 Ada.Strings.Fixed 中的函数(针对固定字符串):

with Ada.Strings.Fixed;

procedure Search_Demo is
   S : String := "Hello Ada World";
   Pos : Natural;
begin
   -- 查找子串
   Pos := Ada.Strings.Fixed.Index (S, "Ada");
   if Pos > 0 then
      Ada.Text_IO.Put_Line ("Found at position" & Integer'Image (Pos));
   end if;
   
   -- 从指定位置开始查找
   Pos := Ada.Strings.Fixed.Index (S, "o", Ada.Strings.Forward, 3);
   
   -- 反向查找
   Pos := Ada.Strings.Fixed.Index (S, "o", Ada.Strings.Backward);
end Search_Demo;

4.2 判断前缀/后缀

if Ada.Strings.Fixed.Head (S, 4) = "Hell" then ...  -- 取前4个字符
if Ada.Strings.Fixed.Tail (S, 5) = "World" then ...  -- 取后5个字符

-- Ada 2012 更方便的扩展(需 GNAT)
-- if S'Starts_With ("Hello") then ...

4.3 无界字符串的查找

Ada.Strings.Unbounded 提供类似函数:

U : Unbounded_String := To_Unbounded_String ("Find the needle in haystack");
Pos : Natural := Index (U, "needle");   -- 返回位置

五、字符串替换与修剪

5.1 替换子串

with Ada.Strings.Fixed;

procedure Replace_Demo is
   S : String := "Hello World";
   Result : String (1 .. S'Length);
begin
   -- 替换指定范围
   Result := S;
   Result (6 .. 10) := "Ada  ";  -- 注意长度匹配
   
   -- 使用 Replace_Slice
   Result := Ada.Strings.Fixed.Replace_Slice (S, 6, 10, "Ada");
   -- "Hello Ada"
end Replace_Demo;

5.2 删除与修剪

S : String := "   Hello   ";
-- 删除首尾空格
Trimmed : String := Ada.Strings.Fixed.Trim (S, Ada.Strings.Both);
-- Trim 参数:Left(左侧)、Right(右侧)、Both(两侧)

-- 删除指定范围
Short : String := Ada.Strings.Fixed.Delete (S, 1, 3);

5.3 大小写转换

Ada 标准库不直接提供,可通过 Ada.Characters.Handling 逐个字符转换:

with Ada.Characters.Handling;
use Ada.Characters.Handling;

function To_Upper (S : String) return String is
   Result : String (S'Range);
begin
   for I in S'Range loop
      Result (I) := To_Upper (S (I));
   end loop;
   return Result;
end To_Upper;

六、字符串与数值转换

6.1 数值转字符串

-- 方法1:使用 'Image 属性
S1 : String := Integer'Image (42);    -- " 42"(前导空格)
S2 : String := Float'Image (3.14);    -- " 3.14000E+00"

-- 方法2:使用 Ada.Strings.Fixed.Trim 去除空格
S3 : String := Ada.Strings.Fixed.Trim (Integer'Image (42), Ada.Strings.Left);
-- "42"

6.2 字符串转数值

-- 使用 'Value 属性
N : Integer := Integer'Value ("42");
F : Float := Float'Value ("3.14");

-- 安全转换(捕获异常)
declare
   N : Integer;
begin
   N := Integer'Value (Input_String);
exception
   when Constraint_Error =>
      Ada.Text_IO.Put_Line ("无效数字");
end;

6.3 使用 Ada.Text_IO 进行转换

-- 从字符串读取数值(需要临时文件或 Ada.Strings.Fixed 等技巧)
-- 更常用的是直接使用 'Value

七、正则表达式简介(GNAT.Regpat)

GNAT 提供 GNAT.Regpat 包支持正则表达式,用于复杂模式匹配。

with GNAT.Regpat;

procedure Regex_Demo is
   use GNAT.Regpat;
   Pattern : Pattern_Matcher := Compile ("[0-9]+");  -- 匹配数字串
   S : String := "The answer is 42.";
   Matches : Match_Array (0 .. 0);
begin
   Match (Pattern, S, Matches);
   if Matches (0) /= No_Match then
      declare
         Num_Str : String := S (Matches (0).First .. Matches (0).Last);
      begin
         Ada.Text_IO.Put_Line ("Found number: " & Num_Str);
      end;
   end if;
end Regex_Demo;

注意:正则表达式功能强大但依赖于 GNAT 扩展,跨平台移植性需注意。


八、完整示例:配置文件解析器

-- 文件名: config_parser.adb
-- 功能:解析简单的 key=value 配置文件

with Ada.Text_IO;
with Ada.Strings.Fixed;
with Ada.Strings.Unbounded; use Ada.Strings.Unbounded;
with Ada.Strings.Unbounded.Text_IO;

procedure Config_Parser is
   
   type Config_Entry is record
      Key   : Unbounded_String;
      Value : Unbounded_String;
   end record;
   
   Max_Entries : constant := 100;
   Config_List : array (1 .. Max_Entries) of Config_Entry;
   Entry_Count : Integer := 0;
   
   -- 去除字符串首尾空格
   function Trim (S : String) return String is
   begin
      return Ada.Strings.Fixed.Trim (S, Ada.Strings.Both);
   end Trim;
   
   -- 解析一行 "key = value"
   procedure Parse_Line (Line : String) is
      Eq_Pos : Natural;
      Key_Str, Value_Str : String (1 .. Line'Length);
      Key_Len, Value_Len : Integer;
   begin
      -- 查找等号
      Eq_Pos := Ada.Strings.Fixed.Index (Line, "=");
      if Eq_Pos = 0 then
         return;  -- 跳过无效行
      end if;
      
      -- 提取 key 和 value
      Key_Str (1 .. Eq_Pos - 1) := Line (Line'First .. Eq_Pos - 1);
      Key_Len := Eq_Pos - 1;
      Value_Str (1 .. Line'Length - Eq_Pos) := Line (Eq_Pos + 1 .. Line'Last);
      Value_Len := Line'Length - Eq_Pos;
      
      -- 去除空格后存储
      Entry_Count := Entry_Count + 1;
      Config_List (Entry_Count) := (
         Key   => To_Unbounded_String (Trim (Key_Str (1 .. Key_Len))),
         Value => To_Unbounded_String (Trim (Value_Str (1 .. Value_Len)))
      );
   end Parse_Line;
   
   -- 根据 key 获取 value
   function Get_Value (Key : String) return String is
   begin
      for I in 1 .. Entry_Count loop
         if To_String (Config_List (I).Key) = Key then
            return To_String (Config_List (I).Value);
         end if;
      end loop;
      return "";
   end Get_Value;
   
   -- 从文件加载配置
   procedure Load_Config (Filename : String) is
      File : Ada.Text_IO.File_Type;
      Line : String (1 .. 200);
      Last : Integer;
   begin
      Ada.Text_IO.Open (File, Ada.Text_IO.In_File, Filename);
      while not Ada.Text_IO.End_Of_File (File) loop
         Ada.Text_IO.Get_Line (File, Line, Last);
         Parse_Line (Line (1 .. Last));
      end loop;
      Ada.Text_IO.Close (File);
   end Load_Config;
   
begin
   -- 假设有配置文件 config.txt,内容如:
   -- host = localhost
   -- port = 8080
   -- debug = true
   Load_Config ("config.txt");
   
   Ada.Text_IO.Put_Line ("Host: " & Get_Value ("host"));
   Ada.Text_IO.Put_Line ("Port: " & Get_Value ("port"));
   Ada.Text_IO.Put_Line ("Debug: " & Get_Value ("debug"));
end Config_Parser;

九、本课总结

  • Ada 有三种字符串类型:String(固定长度)、Bounded_String(有限可变)、Unbounded_String(无限可变)
  • Unbounded_String 最灵活,适合大多数动态字符串需求
  • Ada.Strings.Fixed 提供固定字符串的查找、替换、修剪等操作
  • Ada.Strings.Unbounded 提供无界字符串的类似操作
  • 字符串与数值转换通过 'Image'Value 实现
  • GNAT 正则表达式扩展支持复杂模式匹配

十、课后练习

  1. 字符串反转:编写函数 Reverse (S : String) return String,返回反转后的字符串。

  2. 无界字符串统计:从标准输入读取多行文本,统计总字符数、单词数(以空格分隔)、行数。

  3. 配置文件扩展:修改配置解析器,支持注释行(以 # 开头)和空行跳过。

  4. 数值转换:编写程序读取用户输入的字符串,尝试转换为整数,若失败则提示重新输入。

  5. 简单分词器:给定一个字符串,按空格分割成单词列表,并去除标点符号。


十一、下节预告

第20课|泛型编程入门

我们将:

  • 理解泛型的概念与优势
  • 掌握泛型包的声明与实例化
  • 学习泛型子程序的编写
  • 应用泛型实现通用容器(如栈、队列)

关键术语表

String:Ada 内置的固定长度字符串类型

Unbounded_String:可动态扩展的字符串类型,定义在 Ada.Strings.Unbounded

Bounded_String:长度有上限的可变字符串,定义在 Ada.Strings.Bounded

To_Unbounded_String:将固定字符串转换为无界字符串的函数

To_String:将无界字符串转换为固定字符串的函数

Index:查找子串位置的函数

Trim:删除字符串首尾空格或指定字符的函数

Replace_Slice:替换字符串中某一段的函数

'Image:将数值转换为字符串的属性

'Value:将字符串转换为数值的属性

GNAT.Regpat:GNAT 提供的正则表达式包