这一章在干嘛?

MATLAB 处理数值是主业,但它和外界打交道(文件名、用户输入、HTTP 响应、CSV 头、plot 标题……)几乎都要靠字符串。本章会打通两个关键认知:① 字符串本质上就是一个字符行向量;② MATLAB 提供了把字符串和数字互相转换、把字符串切片/拼接/查找/替换/比较的全套函数。学完后,你能把一段乱七八槽的文本拆成你想要的样子,也能把数字漂亮地嵌进文本里。

7.1 字符与字符串:其实就是字符向量

7.2 字符串操作函数:从拼接、删空到查找替换

7.3 字符串的「is」判定函数

7.4 字符串与数字的相互转换

7.5 字符串处理的常见错误 Top 5

7.6 综合实战:把字符串工具用到工程图里

7.7 字符串处理速查清单

7.8 字符串处理的 C 程序员视角

7.1 字符与字符串

在 MATLAB 里,字符串(string)就是放在单引号 '...' 里的一串字符。它更准确的名字叫 字符向量(character vector),因为从底层看,字符串就是一个 的行向量,向量里每个元素都是一个字符(character)。

如果你写过 C 语言,可以这样类比:MATLAB 的字符串 ≈ C 里的 char str[],但访问字符的方式不再是 str[i] 也不是 *str 而是 str(i) —— 圆括号是 MATLAB 一以贯之的下标符号。

7.1.1 创建字符串变量

>> word = 'cat';
>> length(word)
ans =
     3

三种创建方式:

  1. 直接赋值(最常用):

    >> greeting = 'Hello there';
  2. 从键盘读取(必须带第二个参数 's'):

    >> s = input('Enter a string: ', 's')
    Enter a string: Hi
    s =
    Hi

    第二参数 's' 告诉 input 把用户的输入当作字符串读进来;不写就是默认当表达式求值(输入 x+1 就会真的去算 x+1,那是个大坑)。

  3. 拼接其他字符串得到(见 7.2.1)。

常见坑:空字符串和空格字符串不是一回事

>> length(' ')     % 一个空格
ans =
     1
>> length('')      % 空字符串(两个单引号紧贴)
ans =
     0

这点在 C 习惯里特别容易犯:把 '' 当成”空指针 NULL”是不对的,MATLAB 里 '' 就是一个长度为 0 的字符向量,是个合法值。打印它看起来像没东西,但它确实存在。

7.1.2 字符串就是字符向量

这个性质让 MATLAB 的字符串处理和数值向量高度统一:

>> sent = 'Hello there';
>> length(sent)
ans =
    11
>> sent(1)        % 取第一个字符
ans =
H
>> sent(4:8)      % 取子串
ans =
lo th
>> sent'          % 转置 → 列向量
ans =
H
e
l
l
o
 
t
h
e
r
e

一句话总结字符串本质

'cat' 在 MATLAB 眼里和 [99 97 116] 完全等价 —— 后者是用 ASCII 码写出来的同一个东西。理解这一点,后面所有字符串函数都没那么神秘。

字符串里的空格也是合法字符'lo th' 中间的空格算第 4 个字符,参与计数。

7.1.3 字符矩阵:把多个字符串摞成矩阵

如果你有一组等长的字符串,可以把它们放进一个二维矩阵里:

>> wordmat = ['Hello'; 'Howdy']
wordmat =
Hello
Howdy
>> size(wordmat)
ans =
     2     5

这形成了一个 的字符矩阵,每行一个字符串。等长是硬性要求 —— MATLAB 不像 cell array(见第 8 章)那样宽容。

>> ['Hello'; 'Goodbye']      % 长度不一致就报错
Error using vertcat
Dimensions of matrices being concatenated are not consistent.

字符矩阵 vs. 字符串数组

教材里把 'Hello'/'Howdy' 这种叫做 “character matrix”,第 8 章会介绍更现代的「字符串数组(string array)」和「cell array of strings」。本章只讲字符向量/字符矩阵。生产代码里,推荐优先用字符串数组(双引号 "...",R2016b 之后)或 cell array of strings(第 8.1.3 节),它们能装不同长度的字符串。


7.2 字符串操作函数

MATLAB 提供了一大批字符串函数,下面按使用场景分组讲解。每个分类里都给了能跑的示例,建议你在 MATLAB 里都过一遍。

7.2.1 拼接:把几段字符串粘起来

拼接(concatenation)有两种方向:横向(首尾相接)和纵向(摞成多行)。

横向拼接有三种等价写法:

>> first = 'Bird'; last = 'house';
 
% 写法 1:方括号
>> [first last]
ans =
Birdhouse
 
% 写法 2:strcat 函数
>> strcat(first, last)
ans =
Birdhouse
 
% 写法 3:sprintf(后面专门讲)

两者在「前后无空格」时没区别。但处理有空格的字符串时差异就大了:

>> str1 = 'xxx';
>> str2 = '    yyy';        % 前导空格
>> [str1 str2]              % 方括号:原样拼接
ans =
xxx    yyy
>> length(ans)
ans =
    12
 
>> strcat(str1, str2)       % strcat:先去掉 str1 的尾部空格,str2 的前导空格保留
ans =
xxx    yyy
>> length(ans)
ans =
     9

常见坑:strcat 会吃掉尾部空格

拼接时如果末尾的空格很重要(比如格式对齐),用方括号;否则用 strcat 更干净,避免出现意外的尾部空格。这个规则要背。

纵向拼接char 函数 —— 它会自动用空格补齐短行:

>> greetmat = char('Hello', 'Goodbye')
greetmat =
Hello  
Goodbye
>> size(greetmat)
ans =
     2     7

这正好解决了 7.1.3 节末尾 ['Hello'; 'Goodbye'] 报错的场景。

7.2.2 定制字符串:blanks 与 sprintf

blanks(n) 生成 n 个空格的字符串。看不出,但 length 能验证:

>> b = blanks(4);
>> length(b)
ans =
     4

blanks 单独用没意义,它真正的用途是在拼接时插入若干空格

>> first = 'Bird'; last = 'house';
>> [first blanks(5) last]      % 中间插 5 个空格
ans =
Bird     house

一个更冷门的用法是 disp(blanks(n)') —— 把 n 个空格转置成列向量打印,输出 n 个空行,用来在命令窗口分段:

>> disp(blanks(4)')            % 相当于 4 次换行
(视觉上空 4 行)

sprintf 才是字符串生成的灵魂 —— 它和 C 里的 printf 一模一样,但结果是字符串而不是打印到屏幕:

>> sent1 = sprintf('The value of pi is %.2f', pi)
sent1 =
The value of pi is 3.14
 
>> sent2 = sprintf('Some numbers: %5d, %2d', 33, 6)
sent2 =
Some numbers:    33,  6
 
>> length(sent2)
ans =
    23

对比 C 习惯fprintfprintfsprintfsprintf,二者格式符通用(%d%f%.2f%s\n)。区别只在于 sprintf 返回字符串,fprintf 打印到屏幕

sprintf 最常见的杀手锏应用:把运行时才知道的数嵌进字符串,传给 plottitlexlabelinput 等函数:

% 数据文件:第一列是实验编号,后面是数据
load expnoanddata.dat
experNo = expnoanddata(1);                 % 第一个数是实验编号
data    = expnoanddata(2:end);             % 后面是实际数据
plot(data, 'ko')
xlabel('Sample #')
ylabel('Weight')
title(sprintf('Data from experiment %d', experNo))

执行后得到的图,标题里的 123 是从数据里自动填进去的:

sprintf 把实验编号动态写进 plot 标题里

进阶用法:定制 input 提示语

>> username = input('Please enter your name: ', 's');
Please enter your name: Bart
>> prompt = sprintf('%s, Enter your id #: ', username);
>> id_no = input(prompt)
Bart, Enter your id #: 177
id_no = 177

sprintf vs fprintf vs input 的小套路

sprintf 生成字符串 → 交给 input 当提示语,是「模板化提问」最干净的写法。如果你用 fprintf('%s, Enter your id #: ', username); id_no = input(''); 也能跑,但提示语和读输入分离,逻辑不如 sprintf + input 紧凑。

下面这个完整的「线段中点」例子,把 sprintf 的精髓展示得淋漓尽致:

% midpoint.m 主脚本
[x1, y1] = entercoords('first');
[x2, y2] = entercoords('second');
midx = findmid(x1, x2);
midy = findmid(y1, y2);
fprintf('The midpoint is (%.1f, %.1f)\n', midx, midy)
function [xpt, ypt] = entercoords(word)
prompt = sprintf('Enter the x coord of the %s endpoint: ', word);
xpt = input(prompt);
prompt = sprintf('Enter the y coord of the %s endpoint: ', word);
ypt = input(prompt);
end
function mid = findmid(pt1, pt2)
mid = 0.5 * (pt1 + pt2);
end

跑一遍:

>> midpoint
Enter the x coord of the first endpoint: 2
Enter the y coord of the first endpoint: 4
Enter the x coord of the second endpoint: 3
Enter the y coord of the second endpoint: 8
The midpoint is (2.5, 6.0)

'first''second' 作为变量传给函数,函数内部用 sprintf 把它塞进提示语 —— 这是写可复用交互代码的惯用模式。

7.2.3 去除空白:deblank 与 strtrim

从 7.2.1 看到,char(...) 会在短字符串末尾补空格,凑齐矩阵列数。有时你需要的是原始字符串本身。

deblank(s):只去掉尾部的空格。

>> names = char('Sue', 'Cathy', 'Xavier');   % 注意:3 行被 char 补成了 3x6 矩阵
>> name1 = names(1, :)                      % 第 1 行
name1 =
Sue  
>> length(name1)
ans =
     6
>> name1 = deblank(name1);                  % 去掉尾部补的两个空格
>> length(name1)
ans =
     3

strtrim(s):去掉头部和尾部的空格,但保留中间的空格。

>> strvar = [blanks(3) 'xx' blanks(2) 'yy' blanks(4)];
>> strtrim(strvar)
ans =
xx yy                  % 头 3 个空格 + 尾 4 个空格没了,中间的 2 个还在
>> length(ans)
ans =
     6

常见坑

  • deblank 不会去掉字符串中间的空格;
  • strtrim 同样不会去掉中间的多余空格;
  • 想把 “Hello world”(中间多个空格)合并成 “Hello world”,需要自定义函数(教材习题 22 就是这个)。

7.2.4 大小写转换:upper 与 lower

>> mystring = 'AbCDEfgh';
>> lower(mystring)
ans =
abcdefgh
>> upper(ans)
ans =
ABCDEFG

在比较字符串大小写不敏感的场景里特别好用(见下一节)。

7.2.5 比较字符串:为什么不能用 ==

初学者最容易犯的错:把字符串当作数比 ==

>> 'cat' == 'car'      % 这是按字符逐个比较 ASCII 码,返回一个向量,不是布尔值!
ans =
     1     1     0

你想得到「两个字符串是不是相等」的布尔结果,必须用 strcmp== 只会按 ASCII 码逐字符比,返回一个 0/1 向量。这是 C 习惯最容易栽跟头的地方。

>> word1 = 'cat';  word2 = 'car';
>> word3 = 'cathedral';  word4 = 'CAR';
>> strcmp(word1, word3)       % 完全相等才返回 1
ans =
     0
>> strcmp(word2, word4)       % 'car' 和 'CAR' 大小写不同
ans =
     0

一族比较函数:

函数行为
strcmp(a,b)完全相等返回 1
strcmpi(a,b)忽略大小写比较
strncmp(a,b,n)只比较前 n 个字符
strncmpi(a,b,n)只比前 n 个,忽略大小写
>> strcmpi(word2, word4)      % 忽略大小写
ans =
     1
>> strncmp(word1, word3, 3)   % 前 3 个字符都是 'cat'
ans =
     1

7.2.6 查找、替换、分割

这一类函数是字符串处理的主力军,处理日志、解析文件、做文本分析全靠它们。

strfind(haystack, needle):在 haystack 中找 needle,返回所有出现位置的下标

>> strfind('abcde', 'd')
ans =
     4
 
>> strfind('abcdeabcdedd', 'd')
ans =
     4     9    11    12
 
>> strfind('abcdeabcde', 'ef')    % 找不到返回空向量
ans =
     []

注意参数顺序:先大后小。记反了会出现奇怪的下标。

经典套路:统计某字符出现次数 = length(strfind(...))

>> phrase = 'Hello, and how are you doing?';
>> length(strfind(phrase, ' '))    % 数空格
ans =
     5
 
% 先 trim 再数,避免把首尾空白也算进去
>> length(strfind(strtrim(phrase), ' '))
ans =
     5

strrep(str, old, new):把 str所有 old 替换成 new

>> strrep('abcdeabcde', 'e', 'x')
ans =
abcdxabcdx

oldnew 长度可以不同;找不到 old 时原样返回。

strtok(str):把字符串按第一个分隔符切成两段。默认分隔符是空白字符

>> [word, rest] = strtok('Hello there')
word =
Hello
rest =
 there                 % 注意:rest 里包含了分隔符(那个前导空格)!

常见坑:rest 里 包含分隔符

如果想继续切第二段,记得手动跳过那个分隔符

>> [area, rest] = strtok('703-987-1234', '-')
area = '703'
rest = '-987-1234'
>> rest = rest(2:end)        % 去掉开头的 '-'
rest = '987-1234'

自定义分隔符

>> [word, rest] = strtok('Hello there', 'l')
word =
He                      % 在第一个 'l' 之前停下
rest =
llo there               % 注意:从 'l' 开始,后面都算 rest

实战例子:用 strtok 拆日期

function [d, m, y] = separatedate
    [d, rest] = strtok(date, '-');     % date 返回类似 '10-Dec-2015'
    [m, y]   = strtok(rest, '-');
    y = y(2:end);                       % 去掉 y 字符串里开头的 '-'
end
>> [d, m, y] = separatedate
d = '10'
m = 'Dec'
y = '2015'

7.2.7 把字符串当命令执行:eval

eval(str) 把字符串 str 当作 MATLAB 命令执行:

>> x = [2 6 8 3];
>> eval('plot(x)')     % 真的把字符串当 plot 调用

下面是一个交互式选择图类型的例子:

>> x = [9 7 10 9];
>> whatplot = input('What type of plot?: ', 's');
What type of plot?: bar
>> eval([whatplot '(x)'])        % 把用户输入 'bar' 和 '(x)' 拼成 'bar(x)' 然后执行
>> title(whatplot)
>> xlabel('Student #')
>> ylabel('Quiz Grade')

执行 plot(x) 得到原始折线图:

eval('plot(x)') 直接画出折线

执行 bar(x) 得到柱状图:

用户输入 bar 后 eval 自动生成柱状图

eval 能用,但能不碰就别碰

教材原话:「eval is very powerful, but it is usually more efficient to avoid using it.」原因是:① 难调试(出错了只看到字符串);② 有安全风险(用户输入的字符串会被执行);③ 几乎所有 eval 的需求都能用更具体的函数/函数句柄替代。生产代码里能不用就不用,这是工程实践里的硬规则。


7.3 字符串的「is」判定函数

MATLAB 的 isxxx 系列函数判断是否满足某个条件,对字符串来说主要回答两类问题:

函数作用
isletter(s)逐字符判断是不是字母(字母→1)
isspace(s)逐字符判断是不是空白字符
isstrprop(s, 'alphanum')逐字符判断是否字母+数字
ischar(v)整个 v 是不是字符向量(字符串)

它们都逐字符返回一个 0/1 向量:

>> isletter('EK127')
ans =
     1     1     0     0     0           % E、K 是字母,后面都不是
 
>> isspace('a b')
ans =
     0     1     0                       % 只有空格是 1
 
>> isstrprop('AB123.4', 'alphanum')
ans =
     1     1     1     1     1     0     1    % 小数点不是字母数字

isstrprop 还能查更多类别,比如 'digit''upper''lower''punct' 等,输入 help isstrprop 看完整列表。

>> vec = 3:5;
>> ischar(vec)              % vec 是数值向量,不是字符串
ans =
     0

用 isxxx 做防御性编程

处理用户输入、读文件前,先用 ischar / iscellstr 验证一下类型,能避免大量莫名其妙的错误。第 8 章 iscellstr(判断 cell array 是不是全字符串)也是同类思路。


7.4 字符串与数字的相互转换

字符串和数字是两个完全不同的类型。123(数值)参与算术运算,'123'(字符串)则不行。MATLAB 提供一组专门的转换函数。

数字 → 字符串

>> num = 38;
>> s1 = int2str(num)        % 整数转字符串
s1 =
38
>> length(num)              % num 是 1 个数(标量)
ans =
     1
>> length(s1)               % s1 是 2 个字符
ans =
     2

注意命令窗口里数字和字符串的对齐方式不同 —— 字符串左对齐,数字右对齐。视觉上就能区分。

>> num2str(3.456789)        % 默认 4 位小数
ans =
3.4568
 
>> num2str(3.456789, 3)     % 指定有效位数
ans =
3.46
 
>> num2str(3.456789, '%6.2f')  % 和 sprintf 兼容的格式字符串
ans =
  3.46

num2strsprintf 是一对好搭档 —— 用 sprintf 配合 %d/%f 也能转,但 num2str 更专门、调用更简单。

字符串 → 数字

>> num = str2double('123.456')
num =
   123.4560

如果字符串里有多个数(用空格分隔),str2num 会一次转成整个向量:

>> mystr = '66 2 111';
>> numvec = str2num(mystr)
numvec =
    66     2   111
>> sum(numvec)
ans =
   179

优先用 str2double,少用 str2num

教材原文:「str2double is a better function to use in general than str2num, but it can only be used when a scalar is passed」。str2num 还能执行字符串里的表达式(str2num('1+2') 会返回 3),这特性既是便利也是安全隐患。生产代码里永远用 str2double,避免恶意输入触发任意执行。

实战:解析 “53d” / “3.1r” 这种带单位的输入

下面这个脚本把第 7 章几乎所有工具都串起来了:读字符串 → 切片 → 字符串转数字 → 用 sprintf 拼回字符串打印:

% angleDorR.m
degrad = input('Enter angle and d/r: ', 's');   % 比如 '53d' 或 '3.1r'
angle  = degrad(1:end-1);                       % 切出数字部分
dorr   = degrad(end);                           % 切出最后一个字符
 
% 校验 dorr 必须是 'd' 或 'r'
while dorr ~= 'd' && dorr ~= 'r'
    disp('Error! Enter d or r with the angle.')
    degrad = input('Enter angle and d/r: ', 's');
    angle  = degrad(1:end-1);
    dorr   = degrad(end);
end
 
% 关键的一步:字符串 → 数字
anglenum = str2double(angle);
 
fprintf('The sine of %.1f ', anglenum)
if dorr == 'd'
    fprintf('degrees is %.3f.\n', sind(anglenum))   % sind 接受「角度」
else
    fprintf('radians is %.3f.\n', sin(anglenum))    % sin 接受「弧度」
end

跑一下:

>> angleDorR
Enter angle and d/r: 3.1r
The sine of 3.1 radians is 0.042.

>> angleDorR
Enter angle and d/r: 53t
Error! Enter d or r with the angle.
Enter angle and d/r: 53d
The sine of 53.0 degrees is 0.799.

如果忘了 str2double(angle),直接对字符串 angle 调用 sin,MATLAB 会报错 Undefined function 'sin' for input arguments of type 'char'数字和字符串必须显式转换,这是本章最容易踩的坑。

用 strjoin / strsplit 拼接与分割字符串数组

字符串数组(双引号 "...")和字符串数组的拼接、分割在 R2016b 之后用 strjoinstrsplit 极方便:

>> strjoin(["August", "14", "2016"], "-")
ans =
    "August-14-2016"
 
>> strsplit("August-14-2016", "-")
ans =
  1×3 string 数组
    "August"    "14"    "2016"

注意双引号 "..." 是字符串数组(string 类型),单引号 '...' 是字符向量(char 类型)。两套生态函数大多通用,少数细节差异(比如第 8 章才会讲的 cellstr vs 字符串数组)。


一图总结:把字符串相关函数串起来

flowchart TD
    A[需要处理一段文本] --> B{什么需求?}
    B --> C[拼接/生成字符串] --> C1[["sprintf / strcat / 方括号 / blanks / char"]]
    B --> D[比较字符串] --> D1[["strcmp / strcmpi / strncmp"]]
    B --> E[查找替换分割] --> E1[["strfind / strrep / strtok / strsplit"]]
    B --> F[去空白/改大小写] --> F1[["deblank / strtrim / upper / lower"]]
    B --> G[判断字符类型] --> G1[["isletter / isspace / isstrprop / ischar"]]
    B --> H[字符串与数字互转] --> H1[["int2str / num2str / str2double / str2num"]]
    B --> I[执行字符串里的命令] --> I1[["eval(慎用)"]]

7.5 字符串处理常见错误 Top 5

锚点 id:#s5(错误清单节)

下面列出的五个错误是真实的教学 / 工作中反复栽的坑。每个都给出最小复现 + 修法

错误 1:用 == 比较字符串

>> s1 = 'cat'; s2 = 'car';
>> if s1 == s2, disp('equal'), else, disp('not equal'), end

结果既不是 'equal' 也不是 'not equal' —— 因为 s1 == s2 返回的是 [1 1 0](按字符逐个比较),在 if 的逻辑判定里任何非零元素都视为真。所以这条分支永远走 equal。这才是隐藏 bug 的可怕之处:它不报错,但行为完全错。

修法

>> if strcmp(s1, s2), disp('equal'), else, disp('not equal'), end

错误 2:input 忘了第二个参数 's'

>> x = input('Enter: ')        % 默认把输入当表达式
Enter: 1+2
x =
     3                         % 真的算成了 3,不是字符串 '1+2'

如果是想要字符串,必须显式传 's'。如果用户输错了表达式名(比如一个未定义变量),MATLAB 还会抛 Undefined function or variable 错误。

修法

>> s = input('Enter: ', 's')   % 强制当字符串
Enter: 1+2
s =
1+2                           % 拿到字符串 '1+2'

错误 3:strfind 参数顺序记反

>> strfind('e', 'abcde')      % 想在 'abcde' 里找 'e',但参数顺序错了
ans =
     1

结果毫无意义 —— strfind(needle, haystack) 把单字符 'e' 当 haystack(只有 1 个字符),needle 是 'abcde'(5 个字符),找不到,返回下标 1(其实是 'e''abcde' 里第一个出现的位置的近似,但数学上不正确)。

修法:永远记 strfind(长串, 短串),先大后小。如果颠倒,应该用 strfind('abcde', 'e')

>> strfind('abcde', 'e')
ans =
     5                         % 正确:'e' 在 'abcde' 第 5 个位置

错误 4:strtok 拿到 rest 后忘了去分隔符

>> [first, rest] = strtok('A,B,C', ',')
first =
A
rest =
,B,C                          % 注意:rest 包含了开头的 ','
 
% 如果继续用 rest 去比较 'B',会失败
>> strcmp(rest, 'B')
ans =
     0

修法:要么 rest = rest(2:end) 砍掉第一个字符,要么用 strsplit 一次切干净:

>> parts = strsplit('A,B,C', ',')
parts =
  1×3 cell 数组
    {'A'}    {'B'}    {'C'}
 
>> strcmp(parts{2}, 'B')       % 直接对 cell 元素比较
ans =
     1

错误 5:num2str 结果再被 input 当数字读

>> n = 3.14;
>> s = num2str(n);
>> x = input(s)                % 把 '3.14' 喂给 input 求值
x =
    3.1400                     % 拿到的是数字,不是字符串

有时候你想让用户确认这个数字(比如在交互界面里),但不小心把格式化字符串又送回了 input。这会让你的「字符串确认」变成「数字求值」。

修法:要么别再 input,要么强制 's'

>> x = input(s, 's')           % 强制当字符串
x =
3.14

错误总结表

错误症状修法
== 比字符串if 永远为真strcmp
input's'输入被当表达式求值's'
strfind 参数反返回的下标无意义永远 (长串, 短串)
strtok 没去分隔符后续比较总失败rest(2:end) 或用 strsplit
字符串回灌 input字符串变回数字's'

7.6 综合实战:把字符串工具用到工程图里

下面两个练习把本章学到的字符串函数用在真实的可视化任务里。它们原本是教材练习题,做完后能直观感受到「字符串 + sprintf + subplot + 循环」这组套路如何把工程数据画漂亮。

7.6.1 同一个函数,不同采样点数 —— plotsin

下面这个函数画两张子图,演示同一段正弦曲线在 5 个点和 30 个点下的视觉差异。采样越密曲线越光滑,这是数值分析最直观的体验。

function plotsin(n1, n2)
    % 在 [0, 2*pi] 上分别用 n1 和 n2 个点画 sin
    subplot(2,1,1)
    x1 = linspace(0, 2*pi, n1);
    plot(x1, sin(x1))
    title(sprintf('%d points', n1))        % 标题里的数字自动生成
 
    subplot(2,1,2)
    x2 = linspace(0, 2*pi, n2);
    plot(x2, sin(x2))
    title(sprintf('%d points', n2))
end

调用 plotsin(5, 30) 得到:

只用 5 个点画 sin:折线明显是个三角波,看不出正弦的弧度

用 30 个点画 sin:曲线平滑,能看出一个完整周期的正弦形状

sprintf + subplot 是出图标配

sprintf 把动态数字塞进标题,subplot 把多张小图摆在一张 Figure 里 —— 这两个工具一组合,做数据对比图(不同年份、不同参数、不同算法)就完全不费劲。

7.6.2 读文件 + 字符串命名 + subplot:avehighs.dat 三个地点的温度

数据文件 avehighs.dat 三行,每行一个地点:地点编号 + 12 个月平均最高温度:

432 33 37 42 45 53 72 82 79 66 55 46 41
777 29 33 41 46 52 66 77 88 68 55 48 39
567 55 62 68 72 75 79 83 89 85 80 77 65

下面脚本读取这个文件,用 for 循环画三张子图,标题里用 sprintf 把地点编号嵌进去

load avehighs.dat                  % 读进矩阵,每行对应一个地点
[r, c] = size(avehighs);           % r=3 行,c=13 列
 
for i = 1:r
    location_no = avehighs(i, 1);          % 第 1 列是地点编号
    temps = avehighs(i, 2:end);            % 第 2~13 列是 12 个月温度
 
    subplot(r, 1, i)                       % 3 行 1 列布局
    plot(1:12, temps, 'o')                 % 横轴是月份 1~12
    title(sprintf('Location %d', location_no))
    ylabel('Ave high temps')
    xlabel('Month')
    ylim([30 90])                          % 统一 y 轴便于比较
end

运行结果得到三张子图,地点编号自动嵌进标题里:

地点 432 的月度平均最高温:典型温带曲线,7~8 月达峰约 82

地点 777 的月度平均最高温:曲线峰值(约 88)出现在 7 月附近

地点 567 的月度平均最高温:全年温度偏高,峰值约 89,冬季最低也在 55 以上

三张子图放在一起对比,能直观看出 567 这个地点全年都比 432、777 高 —— 这就是 sprintf 让标题「活」起来的好处。

三个核心套路的组合

这一节把三件事组合在一起:① sprintf 动态生成标题;② subplot(r,1,i) 自动布局多张图;③ for i=1:r 用同一个循环体处理多个对象。把它们刻进肌肉记忆里,几乎所有”批量出图”的需求都能搞定。


7.7 字符串处理速查清单

学完本章,你应该把这张表印在脑子里 —— 写代码时「我该用哪个」基本可以直接对表查:

任务推荐函数关键提醒
创建字符串'xxx'""(R2016b+)字符向量 vs 字符串数组是两个东西
拼接字符串[a b]strcat(a,b)char(...)strcat 砍尾部空格;char 自动补齐
生成格式化字符串sprintf(...)完整支持 fprintf 全部格式符
取子串s(a:b)字符串就是向量,下标 1 开始
字符串长度length(s)空格算字符;'' 长度为 0
比较字符串strcmp(s1,s2)永远别用 ==
忽略大小写比较strcmpi(s1,s2)大小写不敏感
比较前 n 个字符strncmp / strncmpi
找子串位置strfind(s, sub)参数顺序:先大后小
替换子串strrep(s, old, new)全替换,找不到就原样返回
按分隔符切strtok(s, delim)rest 里包含分隔符
去尾部空格deblank(s)不去头部,不去中间
去头尾空格strtrim(s)不去中间
大写 / 小写upper(s) / lower(s)
数字 → 字符串int2str / num2strnum2str 默认 4 位小数
字符串 → 数字str2double(s)优选;str2num 会执行表达式,不安全
判断是不是字母isletter(s)逐字符返回 0/1
判断是不是空白isspace(s)空格、tab、换行都算
判断是不是字符向量ischar(v)
字符串属性查询isstrprop(s, 'alphanum'/'digit'/...)详见 help isstrprop
执行字符串命令eval(s)慎用,优先用 feval + 函数句柄
拼接字符串数组strjoin(cstr, delim)默认空格分隔
拆字符串为 cellstrsplit(s, delim)R2013a+

7.7.1 一段代码看尽字符串工程范式

下面这段「批量读文件 + 自动出表」综合了 sprintf 格式化、sprintf 生成文件名、循环批量处理:

% 假设有 file1.dat、file2.dat、...、file5.dat 共 5 个文件
% 每个文件里是一列数据,现在画一张汇总折线图
figure; hold on;
for i = 1:5
    fname = sprintf('file%d.dat', i);     % 动态生成文件名
    data  = load(fname);                  % 读进来
    plot(data, 'DisplayName', sprintf('Trial %d', i))
end
hold off;
legend('Location', 'best');
title(sprintf('Summary of %d trials', 5))
xlabel('Sample index'); ylabel('Reading');

工程里 70% 的「循环出图」场景都长这样。把它背下来,每次写代码时改改变量名就行。


7.8 字符串处理的 C 程序员视角

如果你写过 C,对照这张表会更顺:

C 的写法MATLAB 等价备注
char str[] = "cat";s = 'cat';字符串字面量
strlen(str)length(s)C 的 \0 不计入,MATLAB 全部计入
str[i]s(i+1)MATLAB 下标从 1 开始,这是最常见的迁移坑
strcmp(a, b)strcmp(a, b)同名同语义,返回 0 表示相等
strncmp(a, b, n)strncmp(a, b, n)同上
sprintf(buf, fmt, ...)s = sprintf(fmt, ...)用法几乎一致
printf(fmt, ...)fprintf(fmt, ...)用法几乎一致
strchr(str, c)strfind(str, c)返回所有出现位置,C 只返回第一个
strtok(str, delim)[tok, rest] = strtok(str, delim)MATLAB 返回两个值
strstr(haystack, needle)strfind(haystack, needle)MATLAB 的 strfind 更通用
atoi(str) / atof(str)str2double(str)str2num 不等价(C 没有等价的)

三处最容易从 C 习惯踩进来的坑

  1. 下标从 1 开始s(1) 是第一个字符,不是 s(0)
  2. == 不能比整串字符串:C 里 == 在 char* 上是指针比较,MATLAB == 是逐字符向量化。要「相等」必须用 strcmp
  3. 空字符串是合法值'' 不是 NULL,长度为 0 但 isempty('') 也是 1。处理输入时别混。

7.9 教材习题精选解读

下面挑几道教材习题(编号沿用原书)写详解,做完能巩固本章所有函数。

习题 14:plotsin —— 用 sprintf 给每张子图动态起名

函数签名 plotsin(n1, n2):在 上分别用 个点画 ,子图标题写点数。

function plotsin(n1, n2)
    % subplot(2, 1, 1):上子图
    subplot(2, 1, 1)
    x1 = linspace(0, 2*pi, n1);          % n1 个均匀点
    plot(x1, sin(x1))
    title(sprintf('%d points', n1))       % 标题动态生成
 
    % subplot(2, 1, 2):下子图
    subplot(2, 1, 2)
    x2 = linspace(0, 2*pi, n2);
    plot(x2, sin(x2))
    title(sprintf('%d points', n2))
end

要点:sprintf 在这里做了三件事 —— 把点数(一个整数)格式化成字符串,嵌进 '%d points' 模板,再喂给 title 作为图标题。如果改成 [30 100 500](多张子图),整个循环体几乎不用改就能复用,这就是 sprintf + subplot 套路的妙处。

习题 22:rid_multiple_blanks —— 多空格合并为单空格

'Hello and how are you?'(多空格)变成 'Hello and how are you?'(单空格)。

不能用 strrep(不知道有几个空格),也不能用 strtok(只切第一个)。最干净的写法是 strsplit + strjoin

function out = rid_multiple_blanks(s)
    parts = strsplit(s);                 % 按空白切成 cell
    % 滤掉空 cell(连续空白会产生空元素)
    parts = parts(~cellfun(@isempty, parts));
    out = strjoin(parts, ' ');           % 用单空格重拼
end
 
% 测试
>> rid_multiple_blanks('Hello  and   how are you?')
ans =
'Hello and how are you?'

关键技巧strsplit 默认按任意空白切,多个连续空白会产生空字符串元素,必须 cellfun(@isempty, parts) 滤掉再 strjoin

习题 25:crypt —— 提取每词首字母

密文 'The early songbird tweets' → 明文 'Test'(每个单词首字母)。

function m = crypt(estring)
    words = strsplit(strtrim(estring));  % 切词
    m = '';                              % 累加器初始化为空字符串
    for i = 1:length(words)
        m = [m words{i}(1)];             % 取每词首字符,拼起来
    end
end
 
>> crypt('The early songbird tweets')
ans =
'Test'

要点m 累加器从 ''(空串)开始,每次把新字符接上去。这是 MATLAB 字符串拼接最直接的形式。生产代码里大量拼接字符串应该用 sprintf 或预分配字符串数组提高性能。

习题 27:用循环批量读 file1.dat / file2.dat / …

data = [];
for i = 1:5
    fname = sprintf('file%d.dat', i);    % 动态文件名
    if exist(fname, 'file') == 2         % 检查文件是否存在
        d = load(fname);                 % 读进来
        data = [data; d];                % 追加(数据量小时 OK,大数据用预分配)
    else
        warning('跳过 %s(文件不存在)', fname);
    end
end

要点exist(fname, 'file') == 2 是 MATLAB 检查文件是否存在的惯用写法(返回 2 表示文件存在)。sprintf('file%d.dat', i) 把整数拼成文件名 —— 这是「批量读文件」的最核心一行。

习题 34:wordscramble —— 把单词字母随机打乱

function out = wordscramble(word)
    n = length(word);
    idx = randperm(n);                   % 1..n 的随机排列
    out = word(idx);                     % 用随机下标重排字符向量
end
 
>> wordscramble('fantastic')
ans =
'safntcait'

要点randperm(n) 是「n 个不重复随机下标」的官方写法。配合字符串本质是字符向量这一点,直接 word(idx) 就能打乱。

习题 29:var1 和 var2 都是 “123”,加 1 后行为完全不一样

>> var1 = 123;            % 数值标量
>> var2 = '123';          % 字符向量,长度 3
>> var1 + 1
ans = 124                 % 数值加法
 
>> var2 + 1
ans =
   50    51    52         % 逐字符 ASCII 码 + 1:'1'=49+1=50, '2'=50+1=51, '3'=51+1=52

要点:数字 123 和字符串 '123' 在 MATLAB 里是两种完全不同的东西 —— 加 1 一个做加法,一个按字符逐个转 ASCII。这道题考查的是「字符串/数值类型混淆」的根因,务必动手在 MATLAB 里试一遍,记忆会非常深。

习题 30:clock vs date 验证年份一致

clock 返回当前时间的 6 元向量,date 返回日期字符串。两者都包含年份信息,但格式完全不同:

% clock 返回 [year month day hour minute second]
>> fix(clock)
ans =
   2013     4    25    14    25    49
 
% date 返回 '25-Apr-2013'
>> date
ans =
25-Apr-2013
 
% 比较年份:clock 是数字,date 是字符串 → 必须转换
this_year_num = fix(clock);              % 第一个元素就是年份
this_year_str = date(end-3:end);         % 'date' 字符串的最后 4 个字符
this_year_from_str = str2double(this_year_str);
 
if this_year_num(1) == this_year_from_str
    disp('年份一致!')
else
    disp('出了什么问题?')
end

要点:日期字符串里切年份的两种方式 —— date(end-3:end) 直接切片,或者用 strtok(date, '-') 拆三次再拼。在第 9 章文件 I/O 之后,这种”读文件 → 解析日期字符串 → 转回数字”的流程会更频繁地出现。


本章通关标准

  1. 能用一句话解释 字符串在 MATLAB 里就是字符向量,并写出两种访问字符的方式(下标、转置)。
  2. 至少会用 sprintfstrfindstrrepstrtok 这四个函数,理解它们的典型场景。
  3. 遇到 “53d” 这种带单位的输入,能用字符串切片 + str2double + 数值计算 + fprintf 格式化输出这套组合拳把它解析出来。
  4. 知道为什么 == 不能直接比字符串,会用 strcmp / strcmpi 做大小写敏感/不敏感的比较。