Strings and Character Arrays

This content was generated with the assistance of AI and should be verified against the official documentation before being relied on in production.

The reference material consulted while preparing these pages is listed in the section bibliography.

Written against MATLAB R2026b. The string class and its functions need MATLAB, not GNU Octave 11.3.0, which has only character arrays: a double-quoted literal in Octave is still a char row vector. The sessions that show output below are therefore the character-vector ones, run in Octave 11.3.0 (output labelled as such; MATLAB’s layout may differ). Everything that uses a string is checked line by line against the R2026b documentation and is labelled "not run here".

There are two ways to represent text, and they coexist in every real program. A character vector, written 'plate', is the original MATLAB text type: a row of char elements. A string array, written "plate", arrived in R2016b with the string function; double-quoted literals followed in R2017a (confirmed from secondary sources, not stated on the current documentation pages). A string array holds whole pieces of text as elements, of varying length, with no padding. New code should use strings; character vectors remain everywhere in older code and in many function interfaces. The sections below cover both, how they differ, and how to move between them. Regular expressions are on Regular Expressions and Patterns, and the general array rules are on Matrices and Arrays.

flowchart TD A[Text to store] --> B{One piece or many?} B -->|many pieces, any lengths| S[string array with square brackets] B -->|one piece| C{Need to index single characters?} C -->|yes, for example a DNA sequence| H[char vector in single quotes] C -->|no| S2[string scalar in double quotes] S --> N[Use strlength, not length] S2 --> N H --> L[length and indexing work per character] S -.->|only for an old interface| X[convertStringsToChars or cellstr]

String arrays and character vectors

"Hello, world" is a 1-by-1 string array (a string scalar) that stores 12 characters. 'Hello, world' is a 1-by-12 char array: each character is an element, so indexing and slicing work per character. A quote is written twice inside the matching delimiter: "They said, ""Welcome!"" and waved." and 'it''s'.

str = "Hello, world";             % string scalar, 1-by-1
chr = 'Hello, world';             % char vector, 1-by-12
class(str), class(chr)           % "string" and "char"
size(str), size(chr)             % [1 1] and [1 12]
names = ["Mercury","Gemini","Apollo"; "Skylab","Skylab B","ISS"];
size(names)                      % 2-by-3 string array

Not run here: the string class does not exist in Octave. Per the documentation, str is a 1-by-1 string and chr is a 1-by-12 character vector, and the string matrix names is 2-by-3, each element keeping its own length with no padding. The strlength of the matrix is [7 6 6; 6 8 3].

The practical split is:

Aspect String array ("...") Character vector ('...')

Introduced

R2016b (string), double-quoted literals R2017a

The original text type

Element

One whole piece of text

One character

Several pieces of text

One array, any lengths

Cell array (cellstr) or a padded character matrix

Number of characters

strlength

length or numel

Empty

"" is a 1-by-1 string with 0 characters, and isempty("") is false

'' is 0-by-0 and isempty is true

Missing value

<missing>, created with missing or string(missing)

None (use NaN-like sentinels or an empty vector)

Append

+ or strcat (square brackets add elements)

Square brackets ([a b]) or strcat

Typical home

New code, text data, table variables

File names in older code, single characters, legacy APIs

Functions that accept character vectors also accept strings, and the reverse is true for the text functions, so most calls work either way. The documentation recommends strings for new code. Character vectors keep two real uses: representing data encoded as characters, where you want the characters as elements, and compatibility with older code and with interfaces that insist on char. Interfaces that accept only char (some older functions and classes) can be fed with convertStringsToChars or char, which are covered below.

Character vectors in practice

The next session runs in Octave 11.3.0. It shows the character-vector basics: indexing, concatenation with square brackets, doubled quotes, and the numeric codes under the characters.

chr = 'Hello, world';
class(chr), size(chr), length(chr)
seq = 'GCTAGAATCC';
seq(4:6)
seq2 = [seq 'ATTAGAAACC']
q = 'it''s'
double('Az')
char([72 105])
s = ['Hello' ' ' 'World']

Octave 11.3.0 output:

ans = char
ans =

    1   12

ans = 12
ans = AGA
seq2 = GCTAGAATCCATTAGAAACC
q = it's
ans =

    65   122

ans = Hi
s = Hello World

A character vector is a numeric row underneath, so double('Az') gives the character codes and char of those codes gives the text back. This is also why 'a' + 1 is the number 98 and not the text b: arithmetic on char gives a double. With a string, "a" + 1 is the text "a1" (the number is converted and appended), so + means something different for the two types.

Character matrices. Stacking character vectors with ; needs rows of the same length. In MATLAB a vertical concatenation of rows with different lengths is an error (the identifier is MATLAB:catenate:dimensionMismatch; not run here, wording varies by release). Octave pads such rows with blanks instead, so do not rely on it. Use char (or the older strvcat) to pad to a common width on purpose:

z = char('abc','de')
size(z)

Octave 11.3.0 output:

z =

abc
de

ans =

   2   3

char('abc','de') is a 2-by-3 character matrix, the second row padded with one blank. strvcat does the same but drops empty inputs, and is listed as not recommended on its documentation page. A character matrix is the book-era way of holding several pieces of text; today the right container is a string array, and cellstr is the bridge to the cell-array form (see Cell Arrays).

Missing strings

A string array can hold a missing value, displayed <missing> and created with string(missing) or missing converted to a string. It is the text counterpart of NaN: it marks text that is not known, which differs from an empty string "" (known and empty). ismissing(str) finds missing elements. Like NaN, a missing string is not equal to anything, itself included, so == and strcmp return false for it, and isequal on two missing strings is false. strlength returns NaN for a missing element. Not run here; this follows the documentation of ismissing and missing, and the page "Test for Empty Strings and Missing Values" linked from the strings FAQ. Character vectors have no missing value. The wider treatment of missing data is on Missing Data.

Concatenation and length

The two types concatenate differently, and this is the most common source of surprise.

  • Square brackets on character vectors join them into a longer vector: ['Hello' 'World'] is 'HelloWorld'.

  • Square brackets on strings build a bigger array: ["Hello" "World"] is a 1-by-2 string array, not one longer string.

  • + on strings appends text: "Hello" + " " + "World" is "Hello World". A non-text operand that can be converted is converted: "temperature is " + 21.6667 + "C" gives "temperature is 21.6667C".

  • strcat appends whether the inputs are strings or character vectors, but for character-vector inputs it removes trailing whitespace of each argument; for string and cell inputs it keeps it.

  • append is the documentation’s recommended function, because it treats string arrays, character vectors and cell arrays of character vectors consistently: append("Today's ",tempText).

The next session, run in Octave 11.3.0, shows the strcat whitespace rule for character vectors and for cells (the cell form keeps the blank):

strcat('a ', 'b')
strcat({'a '}, 'b')

Octave 11.3.0 output:

ans = ab
ans =
{
  [1,1] = a b
}

Length. length(str) of a string scalar is always 1, because it counts elements; strlength(str) counts characters. length returns the size of the longest dimension, so for a string array it is a number of strings. strlength also works on character vectors and on cell arrays of character vectors, so it is the single safe choice when you do not know which type you were given. An empty string is not an empty array: isempty("") is false, size("") is [1 1], while isempty('') is true. The documentation’s recommended test for "no text" is strlength(x) == 0, which is right for both types.

str = "Hello World";  chr = 'Hello World';
length(str)          % 1   (one element)
strlength(str)       % 11
length(chr)          % 11
strlength(chr)       % 11
isempty("")          % false (a 1-by-1 string)
isempty('')          % true
strlength("") == 0   % true

Not run here. The same lines on a character vector (length, numel, isempty, size('')) were run in Octave 11.3.0 and gave 11, 11, 1 and 0 0.

The string functions

The text functions work on string arrays (and, with the same names, on character vectors and cell arrays of character vectors unless noted). Most return a result of the same type as their input. Each call below is checked against the reference page named in the Source line (not run here); the examples use a line of the sensors.csv log (columns Time, SensorID and Temperature) of the heatflow scenario.

label = "2026-01-15 10:00:30,S2,22.1";
parts = split(label, ",")                     % ["2026-01-15 10:00:30"; "S2"; "22.1"]
id    = parts(2);   value = str2double(parts(3));
joined = join(["Dirichlet","Neumann","Periodic"], ", ")
clean  = strip("   plate   ")                 % "plate"
padded = pad("S2", 6)                         % "S2    " (padded on the right)
up   = upper("heat");   low = lower("FLOW");
swapped = replace("plate.csv", ".csv", ".json")
tf1 = contains(label, "S2")                   % true
tf2 = contains(label, "s2", IgnoreCase=true)  % true
tf3 = startsWith(label, "2026-")              % true
tf4 = endsWith(label, ["22.1","22.0"])        % true (any pattern)
inner = extractBetween(label, " ", ",")       % "10:00:30"
longer = insertAfter("plate.json", "plate", "_v2")  % "plate_v2.json"
backwards = reverse("abc")                    % "cba"

Not run here. The behaviour of each function:

Call What it does

split(str,delimiter)

Splits at each delimiter; the pieces go down the first non-singleton dimension (a column for a scalar input). splitlines and strsplit are related forms.

join(str,delimiter)

Combines the elements of a string array along a dimension, putting the delimiter between them. strjoin is the equivalent for cell arrays of character vectors.

strip(str)

Removes leading and trailing whitespace; a second argument chooses the side ("left", "right", "both") and a third a character to strip. strtrim is the older equivalent.

pad(str,n)

Pads to at least n characters; the side and the padding character are optional arguments.

upper(str), lower(str)

Case conversion. Use lower on both sides before comparing when case does not matter.

replace(str,old,new)

Replaces every occurrence of old; old and new can be arrays of equal size to replace several. strrep is the older form. replaceBetween and eraseBetween change text between markers.

contains(str,pat)

True where the pattern occurs; IgnoreCase=true (name-value syntax, R2021a) ignores case. The pattern can be a pattern object (R2020b).

startsWith(str,pat), endsWith(str,pat)

True where the text begins or ends with a pattern; they take an array of patterns.

extractBetween(str,start,stop)

Substring between two markers; extractAfter and extractBefore take one marker, and extract takes a pattern.

insertAfter(str,pat,new)

Inserts text after a pattern; insertBefore is the mirror.

reverse(str)

Reverses the characters of each string.

count(str,pat), erase(str,pat)

Count occurrences, or delete them.

Two rules apply across the family. First, the text and the patterns can each be a string array, a character vector or a cell array of character vectors, and the type of the result follows the text (the first argument), not the patterns: string text gives a string result. For character-vector text, some functions return a character vector (strip, upper, replace) and others a cell array of character vectors (split, extractBetween), so check the reference page when the exact output type matters. Second, a missing string flows through as missing rather than raising an error. Not run here.

The character-vector equivalents are available in every release. This session ran in Octave 11.3.0 on the same CSV line:

label = '2026-01-15 10:00:30,S2,22.1';
parts = strsplit(label, ',')
strjoin({'a','b','c'}, ', ')
strtrim(sprintf('  padded \t\n'))
upper('heat'), lower('FLOW')
strrep('plate.csv', '.csv', '.json')
strfind('abcabc', 'bc')
idx = strfind(label, ',');
label(idx(1)+1:idx(2)-1)
fliplr('abc')

Octave 11.3.0 output:

parts =
{
  [1,1] = 2026-01-15 10:00:30
  [1,2] = S2
  [1,3] = 22.1
}

ans = a, b, c
ans = padded
ans = HEAT
ans = flow
ans = plate.json
ans =

   2   5

ans = S2
ans = cba

strsplit returns a cell array of character vectors, strjoin takes one, strtrim removes surrounding whitespace, strrep replaces substrings and strfind returns the start indices of every match. Each of these accepts strings in MATLAB. fliplr reverses any row vector, which is how a character vector was reversed before reverse existed.

Formatting text: sprintf, compose and format specifiers

sprintf turns data into text under the control of a format. It returns a character vector, whereas compose returns a string array and applies a format to each row of its input. The functions that take a format are compose, num2str, sprintf, fprintf and the error functions assert, error, warning and MException, so one set of rules serves display, files and messages. The syntax lines are:

txt = sprintf(formatSpec, A1, ..., An)
str = compose(formatSpec, A)
fprintf(formatSpec, A1, ..., An)

A formatting operator starts with %. Its fields, from left to right, are an optional numeric identifier, flags, field width, precision, subtype and the conversion character, which is the only required field.

Conversion Meaning

%d

Signed decimal integer. A non-integer value makes MATLAB switch to exponential or fixed notation (Octave printed 3.5 for sprintf('%d', 3.5)).

%f

Fixed-point notation, six decimals by default.

%e, %E

Exponential notation with a lowercase or uppercase e.

%g, %G

The more compact of %e and %f, without insignificant zeros.

%s

Text; also a string array element.

%c

A single character.

%o, %x, %X, %u

Octal, hexadecimal (lowercase, uppercase) and unsigned decimal notation. The subtype b (double) or t (single) treats the input as floating point instead of an integer (%bx).

The flags are - (left-justify), + (always print a sign; for text, right-justify), a space (insert a blank before the value), 0 (pad with zeros) and # (alternate forms, such as the 0x prefix). The width is the minimum number of characters; the precision follows a period (digits after the point for %f and %e, significant digits for %g; for %s it truncates the text). A numeric identifier such as %2$s takes an argument out of order. Special characters in the format are written with escape sequences such as \n and \t; a literal percent sign is %%. sprintf translates escape sequences in its format, so sprintf("a\nb") and sprintf('a\nb') both contain a line break, whereas a plain double-quoted literal "a\nb" keeps the backslash (not run here). Use newline for a portable line break in a string.

The session below was run in Octave 11.3.0 with the format examples from the documentation page:

A = pi*ones(1,3);
txt = sprintf('%f | %.2f | %12f', A)
sprintf('%d   %f   %e   %X', 46*ones(1,4))
sprintf('%g   %.2g   %f   %.2f', pi*50*ones(1,4))
sprintf('right-justify: %12.2f\nleft-justify: %-12.2f|', 12.3, 12.3)
sprintf('sign: %+12.2f', 12.3)
sprintf('Pad with zeros: %012.2f', 5.2)
sprintf('%s has %d nodes', 'plate', 120)
sprintf('%d,', [1 2 3])
sprintf('%d %s', 1, 'a', 2, 'b')
sprintf('%c%c%c', 72, 105, 33)
sprintf('%5.1f%%', 99.5)

Octave 11.3.0 output:

txt = 3.141593 | 3.14 |     3.141593
ans = 46   46.000000   4.600000e+01   2E
ans = 157.08   1.6e+02   157.079633   157.08
ans = right-justify:        12.30
left-justify: 12.30       |
ans = sign:       +12.30
ans = Pad with zeros: 000000005.20
ans = plate has 120 nodes
ans = 1,2,3,
ans = 1 a2 b
ans = Hi!
ans =  99.5%

Three behaviours follow from the output. The format is reused until all the data is consumed ('%d,' with [1 2 3] prints three items), so one sprintf can render a whole vector. Data are taken in column order, and a number given to %s or a character given to %d is converted to its code or its text. Numbers that do not fit the operator, such as a fraction passed to %d, change the notation; choose %f or %g for non-integers.

compose is the string-array counterpart and is the one to use when the format and the data are arrays:

rows = compose("Node %d has temperature %.1f", [1 21.5; 2 22.0])

Not run here. Per the documentation, compose applies the format to each row of the numeric input and returns a string column, here "Node 1 has temperature 21.5" and "Node 2 has temperature 22.0".

Conversions

  • string(x) converts numbers, datetime, duration, categorical and char to strings; string(pi) is "3.1416", and string(missing) is the missing string. strings(n,m) makes an empty string array.

  • char(x) converts strings, cell arrays and numeric codes to a character array; char(["ab","cde"]) is a 2-by-3 padded matrix, and char([72 105]) is 'Hi'. convertStringsToChars and convertCharsToStrings convert many arguments at once and are the tools for updating old interfaces.

  • cellstr(x) converts a string array or a character matrix to a cell array of character vectors, removing trailing blanks of a character matrix. It is a legacy bridge: use it when an old function demands a cell array of character vectors.

  • num2str(x) formats numbers as text, with a precision argument (num2str(pi,8)) or a format (num2str(x,'%10.5f')); int2str rounds first, and mat2str writes a matrix as an expression that can be read back.

  • str2double(s) parses text to a number and returns NaN for text it cannot parse, with no error. It accepts a string, a character vector or a cell array, and is the safe parser. str2num evaluates its argument (it calls eval), so use it only on trusted text.

  • isstring, ischar, iscellstr and isStringScalar test the type.

This session ran in Octave 11.3.0 (the string, strlength and compose calls are not run here):

num2str(pi), num2str(pi,8), num2str([1 2 3])
mat2str([1 2;3 4.5])
int2str(2.7)
str2double('3.14'), str2double('abc'), str2double({'1','x','2.5'})
cellstr(['ab ';'cde'])
char({'ab','cde'})
disp(['Total: ' num2str(42)])

Octave 11.3.0 output:

ans = 3.1416
ans = 3.1415927
ans = 1  2  3
ans = [1 2;3 4.5]
ans = 3
ans = 3.1400
ans = NaN
ans =

   1.0000      NaN   2.5000

ans =
{
  [1,1] = ab
  [2,1] = cde
}

ans =

ab
cde

Total: 42

The bad parse str2double('abc') is NaN with no error, which is convenient for reading messy files and dangerous when the NaN is not checked afterwards. cellstr trimmed the padding blank of 'ab '.

The conversion between the numeric and the text view of the same data is covered further on Type Identification and Conversion.

Comparing text

For strings, == and ~= compare whole strings and return a logical array: "abc" == "abc" is true, and ["a","b"] == "a" is [true false] through implicit expansion (see Implicit Expansion and Compatible Sizes). A character vector on one side is treated as a string, so "abc" == 'abc' is true (not run here). The relational operators < and > also work on strings, and sort them alphabetically.

For character vectors, == is the numeric, element-by-element comparison of the character codes. It works only when both operands have the same size (or one is a scalar) and otherwise fails with MATLAB:sizeDimensionsMustMatch ("Arrays have incompatible sizes for this operation"; Octave words it nonconformant arguments). Even when the sizes agree the result is an array, so if 'abc' == 'abd' is false only because the condition needs every element true. Use a comparison function instead:

  • strcmp(a,b) is a case-sensitive whole-text comparison that returns one logical, or an array when one argument is a cell array (strcmp('Dirichlet',{'Dirichlet','Neumann'})). It works on both types, and it does not raise an error for different lengths. isequal also gives one answer.

  • strcmpi ignores case; strncmp and strncmpi compare the first n characters.

  • Use contains, startsWith and endsWith to test for parts, and matches (a string function) or strcmp against a list for membership; ismember(text, list) also works.

strcmp('abc',{'abc','x','abc'})
strcmpi('ABC','abc')
strncmp('heatflow', 'heat', 4)
any(strcmp('Dirichlet', {'Dirichlet','Neumann'}))

Octave 11.3.0 output:

ans =

  1  0  1

ans = 1
ans = 1
ans = 1

The MATLAB documentation’s recommended form for a conditional on text is if strcmp(kind, "Dirichlet") or, with a string scalar, if kind == "Dirichlet". Do not use == between character vectors of unknown length: if kind == 'Dirichlet' errors when kind has a different length, and gives a misleading answer when it has the same length and only some characters match.

Unicode and encodings

Text in MATLAB is Unicode. A char element is one UTF-16 code unit, so characters outside the basic plane (many emoji, for example) occupy two elements of a character vector, and length counts code units. Reading and writing files is where encodings matter: functions such as fopen, readlines, fileread and readtable take an encoding option, and files that are not UTF-8 need it stated. unicode2native converts text into the bytes of a named encoding and native2unicode converts bytes back to text; use them when talking to a binary protocol or a legacy file. Not run here; Octave 11.3.0 treats a char as a byte, which is a visible difference for non-ASCII text, so Octave results for such text should not be taken as MATLAB behaviour.

Legacy forms from the books

Older books show a character-vector toolkit. Some of it is still current and some is not recommended.

Book-era form Today

strmatch(str,A)

Listed as not recommended in the R2026b documentation. Use startsWith, strcmp or strncmp. (It finds rows of a character matrix or cells that begin with str; Octave 11.3.0 prints strmatch is obsolete and still returns the match.)

findstr(a,b)

Listed as not recommended. Use strfind (or contains for a yes/no answer). Octave 11.3.0 warns findstr is obsolete.

strvcat

Not recommended. Use char (to keep empty rows) or a string array.

strtrim, strrep, strsplit, strjoin, strfind

Still valid and accept strings. Prefer strip, replace, split, join and contains in new code.

eval(['x' num2str(k) ' = 1;'])

Building variable names in text. Use a cell array, a structure or a table instead (see Syntax, Statements and Variables).

disp(['Total: ' num2str(x)])

Still works for scalars. With strings, "Total: " + x or compose/sprintf is shorter.

cellstr and cell arrays of character vectors

Fine for old interfaces; new code uses string arrays (the FAQ recommends against cell arrays of strings).

The session ran in Octave 11.3.0:

strmatch('app', ['apple';'apply';'bears'])
findstr('abcabc','bc')

Octave 11.3.0 output (both calls first print an "obsolete" warning):

ans =

   1
   2

ans =

   2   5

Pitfalls

  • length of a string is 1. Use strlength.

  • + between strings joins text, between character vectors adds numbers. 'a' + 1 is 98 and "a" + 1 is "a1".

  • Square brackets with strings make an array, not a longer string. Use +, append or strcat.

  • isempty("") is false. Test strlength(x) == 0.

  • == on character vectors is element-wise and needs equal sizes (MATLAB:sizeDimensionsMustMatch). Use strcmp.

  • Double quotes in command form. cd "C:\Program Files" is an error because the quotes become part of the text; use cd('C:\Program Files') or cd 'C:\Program Files' (the FAQ shows the error text).

  • Cell arrays of strings. contains({"Venus","Earth"},"Earth") raises an error because most functions do not accept a cell array of strings; use a string array.

  • strcat drops trailing blanks of character vectors but not of strings or cells.

  • str2double returns NaN rather than an error for text that is not a number; check with isnan.

  • str2num and eval run code. Do not apply them to untrusted text.

  • A missing string is not equal to itself, as NaN is not.

  • Octave differences. There is no string class in Octave 11.3.0, a double-quoted literal is a character vector there, and vertical concatenation of unequal rows pads instead of failing; code that must run in both uses character vectors.

Where older books differ

The older MATLAB books present text only as character arrays: single quotes, [a b] concatenation, strcmp, strcat, strrep, findstr, strmatch, num2str, sprintf and eval-built strings, with cell arrays of character vectors for lists. The string class (R2016b) and double-quoted literals (R2017a) are absent, as are contains, startsWith, split, join, compose and the pattern objects of R2020b. The character-vector material is still correct; strmatch and findstr are the parts that are not recommended. Prefer string arrays in new code, and keep character vectors for single characters, data encoded as characters and old interfaces.