Символы и строки

До сих пор наши программы работали только с числами. Но многим программам надо работать с текстовыми данными. Для этого есть два основных объекта — символы и строки.

Символьный тип данных (паскаль)

Для хранения отдельных символов (букв, цифр, всяких знаков препинания и т.п.) в паскале есть тип данных char:

var ch:char;
— объявляет переменную, в которой можно хранить символ.

В такую переменную можно записать любой символ конструкциями следующего вида:

ch:='a';
ch:='$';
Здесь в правой части присваивания так называемые символьные константы, т.е. нужные символы, заключенные в апострофы. Здесь первая команда записывает в переменную ch символ "a", вторая — символ "доллар".

Кроме того, символы можно вводить и выводить привычными конструкциями:

read(ch);
write(ch); // не переводя строку
writeln(ch); // с переводом строки

Символьный тип данных (питон)

В питоне, чтобы сохранить символ в переменной, надо просто написать

ch = "a"
ch = "$"

и т.п.

При этом можно использовать как символы кавычек ("), так и символы апострофов ('), это не имеет значения. Главное, чтобы они были согласованы.

Вводить символы можно обычной командой input():

ch = input()

(именно прямо так)

выводить — обычным print:

print(ch)

(На самом деле, в питоне нет отдельного "типа" для символов, символ в питоне — это просто строка длины 1, про строки см. ниже. Но часто удобно думать про символы отдельно от строк.)

Коды символов (общее и для паскаля, и для питона)

На самом деле, конечно, в памяти компьютера хранятся не символы (т.е. если мы написали ch:='$'; (паскаль) или ch="$" (питон), то нигде в памяти не будет нарисован доллар). Компьютер умеет работать только с числами, и вместо символов он хранит тоже числа.

Есть общепринятая договоренность, которая каждому числу от 0 до 255 ставит в соответствие некоторый символ. Точнее, таких договоренностей есть несколько, они называется кодировки, но для латинских букв, цифр и частоупотребимых символов типа того же доллара, запятой или плюса, во всех кодировках соответствующие числа одинаковы. Для русских букв это не так: в разных кодировках им соответствуют разные числа, но это отдельная тема.

Эта общепринятая сейчас кодировка для латинских букв, цифр и частоупотребимых символов называется ASCII, иногда говорят таблица ASCII. Полностью эту таблицу (точнее, символы от 0 до 127 — эта часть собственно и называется ASCII; символы с номерами от 128 до 255 строго говоря не считаются ASCII, там как раз в разных вариантах русские буквы и т.п.) можно посмотреть, например, здесь. Здесь колонка Decimal — это номер символа, колонка Hex — номер символа, но в 16-ричной системе счисления (для тех, кто знает, что это такое, остальные игнорируйте колонку Hex), колонка Char — собственно сам символ. Пояснения: символы с номерами (кодами) до 31 включительно — это так называемые управляющие символы, они нам пока не очень интересны (равно как и символ 127); символ 32 — это пробел (в таблице написано SPACE). Остальные символы вроде понятны.

Например, символ доллар имеет номер (говорят код) 36, а символ N — 78.

Обратите внимание, что все цифры идут подряд, все заглавные буквы идут подряд, и все маленькие буквы идут подряд. Это нам будет очень полезно. (Для русских букв это выполняется не всегда.)

Что в паскале, что в питоне узнать код символа можно операцией ord, а узнать символ по коду можно операцией chr. Например:

ПаскальПитон
var i:integer;
    ch:char;
begin
read(ch);         // считали символ...
writeln(ord(ch)); // и вывели его код

i:=ord('$');      // записали в i код доллара
writeln(i);        

read(i);          // считали код
writeln(chr(i));  // и вывели соответствующий символ

ch:=chr(ord('$') + 1); 
writeln(ch);      // вывели символ, следующий за долларом
end.
ch = input()         # считали символ...
print(ord(ch))       # и вывели его код

i = ord('$')         # записали в i код доллара
print(i)             

i = int(input())     # считали код
print(chr(i));       # и вывели соответствующий символ

ch=chr(ord('$') + 1)
print(ch)            # вывели символ, следующий за долларом

В большинстве случаев точное знание кодов символов вам не надо — вы всегда можете что надо вычислить через ord. Например, если мы знаем, что в переменной ch у нас цифра (т.е. символ, соответствующий цифре) — как в переменную i записать значение этой цифры (т.е. 0, 1, 2, ..., или 9)? Т.е. как перевести цифру-символ в число?

Нам поможет то, что все цифры идут подряд. Поэтому достаточно из кода цифры вычесть код нуля:

i:=ord(ch)-ord('0');  // паскаль
i = ord(ch) - ord('0')  # питон

Обратите внимание: нам не надо знать, что код нуля — 48. Мы прямо пишем ord('0'), а не 48, компьютер сам вычислит код нуля за нас!

Сравнения символов (и паскаль, и питон)

Символы можно сравнивать операторами =, >, <, >=, <=. На самом деле сравниваются их коды:

ПаскальПитон
if ch1=ch2 then // если два символа совпадают...
    ....
if ch1>ch2 then // если код первого символа больше кода второго
    ....
if ch1 == ch2:  # если два символа совпадают...
    ....
if ch1>ch2:  # если код первого символа больше кода второго
    ....

Благодаря тому, что однотипные символы идут подряд, очень легко можно проверять тип символа. Например, чтобы проверить, является ли символ цифрой, можно написать:

if (ch>='0') and (ch<='9') then... // паскаль
if ch>='0' and ch<='9': ... # питон

Массивы и циклы (паскаль)

Массивы можно индексировать символами:

var a:array['a'..'z'] of integer;
...
a['d']:=10;

Кроме того, можно делать циклы по символам:

var ch:char;
...
for ch:='a' to 'z' do begin...

В обоих случаях порядок символов подразумевается по их кодам. Например, я могу сделать массив a:array['A'..'z'] of integer; — здесь будет по элементу для каждого символа с кодами от A до z.

Массивы и циклы (питон)

В питоне нельзя так просто, как в паскале, индексировать массивы символами и делать циклы по символам. Если вам надо сделать массив, в элементах которого хранить что-то, связанное с цифрами, то надо переходить к кодам:

a = [0] * 256  # у нас всего 256 символов
a[ord('d')] = 10  # в элемент, соответствующий d, записали 10
...
for x in range(ord('a'), ord('z')+1):
    ch = chr(x)
    print(ch)  # выводим все символы от a до z

Но вообще это продвинутая тема, сейчас пока вам не особо нужная.

Строки

Строка — это последовательность символов. Поэтому представляется естественным использовать для хранения строк массив символов:

// паскаль:
var s:array[1..1000] of char; // строка не длиннее 1000 символов
...
// питон:
s = ["T", "e", "s", "t"]
{Но так делать не надо!}

В паскале есть специальный тип данных для строк — string:

var s:string;

В питоне, чтобы записать строку в переменную, надо просто записать строку в переменную:

s = "Test"

Что в питоне, что в паскале, строка — это массив, каждым элементом которого является символ, но это не просто массив, а массив с дополнительными функциями.

Во-первых, вам не надо думать про длину строки. Паскаль и питон автоматически сами выделят под строку сколько надо памяти.

Внимание! В разных книжках по паскалю вы можете прочитать, что строки не бывают длиннее 255 символов. Это верно только в ряде вариантов паскаля. В используемом нами варианте — Free Pascal в режиме {$mode delphi} — строка может быть сколь угодно длинной — пока у программы не кончится доступная память.

Длину строки в паскале можно узнать командой length(s), в питоне — как и у массива, командой len(s):

writeln(length(s));  // паскаль
print(len(s))  # питон

Во-вторых, строки, конечно, можно считывать и выводить. На паскале это делается стандартными командами:

readln(s);
writeln(s);
(Почему readln, а не read, — см. ниже.)

На питоне — вывод обычным print, а ввод — обычным input(), никакой лишней конвертации не надо, пишете s = input():

s = input()
print(s)

В-третьих, строки можно складывать. Сложить две строки — значит приписать к одной строке другую:

ПаскальПитон
readln(s1);
readln(s2);
s:=s1+s2;
writeln(s); // выведет две строки одну за другой
s1 = input()
s2 = input()
s = s1 + s2
print(s)  # выведет две строки одну за другой

Прибавлять можно и символы:

s:=s+'A';  // паскаль
s = s + 'A'  # питон

Наконец, строковые константы — это уже привычные вам последовательности символов в апострофах (паскаль) и в кавычках (питон):

ПаскальПитон
s:='Test';
s:=s+'2';
writeln(s); // выводит Test2
s = "Test"
s = s + '2'
print(s)  # выводит Test2

На самом деле, в питоне можно использовать как апострофы (символы '), так и кавычки (символы ")

Может возникнуть вопрос, как в строковой константе ввести собственно символ апостроф или кавычку. Просто так написать 'It's a string' не получится, т.к. что паскаль, что питон подумают, что строка закончилась на втором апострофе; аналогично в питоне не сработает "Text"Text". Поэтому в паскале внутри строковых констант апострофы надо удваивать, в а питоне — приписывать символ \ перед апострофом или кавычкой. Например, чтобы записать в переменную строку "It's a string", надо написать

s:='It''s a string';  // паскаль
s = 'It\'s a string'  # питон
s = "It's a string"  # тоже питон
s = "It's a \"string\""  # тоже питон когда в строке и кавычки, и апострофы
Аналогично для записи символа "апостроф"/"кавычка" в переменную типа char:
ch:='''';  // паскаль
ch = '\''  # питон
ch = "'"  # тоже питон
ch = "\""  # тоже питон
ch = '"'  # тоже питон

Еще частный случай строки — пустая строка, т.е. строка длины ноль:

s:='';  // паскаль
s = ""  # питон

Ну и наконец, строка — это все-таки массив символов. Можно использовать все известные вам операции над массивами (писать s[i], чтобы получить доступ к i-му символу строки, и т.д. В паскале фактически других операций нет, в питоне много). Например, так можно проверить, есть ли в строке пробелы:

// паскаль
for i:=1 to length(s) do
    if s[i]=' ' then...
# питон
for i in range(len(s)):
    if s[i] == ' ':
        ...

Почему readln? (Паскаль)

До сих пор я требовал, чтобы вы всегда использовали команду read, а не readln. Но до сих пор мы работали с числами; и пробелы и переводы строк были нам просто разделителями чисел, и поэтому команда read прекрасно работала.

Но теперь нам надо особо отличать перевод строки. Когда мы считываем строку (string) с клавиатуры, нам надо считать ее до перевода строки. Поэтому нам важно различать и уметь применять команды read и readln.

Различие у них единственное: read только считывает то, что попросили, и тут же останавливается. Readln же, считав то, что попросили, дальше пропускает все введенные данные до конца строки, и пропускает этот конец строки.

Например:

var a,b,c:integer;
begin
read(a);
read(b);
read(c);
...

Пусть на вход мы подаем следующие данные:

2 3
4
Первый read считает число 2 и тут же остановится. Второй read увидит, что текущий символ пробел, пропустит его, увидит 3, считает его и остановится. Третий read увидит, что строка кончилась (на самом деле конец строки — это один или два специальных символа), перейдет на следующую строку, увидит там 4, и считает число 4.

Если же в программе были бы команды readln, то получилось бы следующее. Первый readln считывает число 2 и пропускает все остальное, что было в этой строке, в том числе и перевод строки. Второй readln сразу же видит число 4, считывает его, и пропускает все до конца строки включительно. Третий readln видит, что ничего не осталось, и потому ждет, когда вы что-нибудь введете еще.

Для чтения чисел первое поведение (с read) абсолютно логично. Поэтому если вы чистаете числа, то используйте read.

Но пусть вы читаете строки:

var s1,s2:string;
begin
read(s1);
read(s2);
...

Пусть вы вводите следующее:

abc
def

Первый read считает 'abc', увидит перевод строки, и на этом остановится. Второй read увидит, что сразу идет перевод строки — он не будет его пропускать, а просто решит, что вы решили ничего не вводить, и s2 получится пустой строкой (длины 0).

Если бы были readln'ы, то первый readln считал бы 'abc' и пропустил бы перевод строки. Поэтому второй readln увидел бы символ d, считал бы 'def' и т.д.

В общем, будьте с этим внимательны и используйте те команды, которые вам нужны. Еще пример: если вводится число, а на следующей строке — строка, например:

5
abc
то читать надо так (поймите, почему!)
readln(n);
readln(s); // тут можно и read, если больше ничего не вводится

inttostr и т.п. (Паскаль)

Есть еще четыре полезных команды:
inttostr     // integer to string - целое число в строку
strtoint     // string to integer - строку в целое число
floattostr   // float to string - вещественное число в строку
strtofloat   // string to float --- строку в вещественное число
Они переводят числа в строки и обратно. Чтобы их использовать, надо в начале программы (после {$mode delphi}, но до var) написать uses sysutils;
{$mode delphi}
uses sysutils;
begin
writeln(strtoint('2') + strtoint('55'));      // выводит 57
writeln(inttostr(23) + 'abc' + inttostr(45)); // выводит 23abc45
writeln(strtofloat('2.5') * 2);               // выводит 5.0000e0
writeln(floattostr(2.5) + 'a');               // выводит 2.5000e0a
end.

int и т.п. (Питон)

Есть еще три полезных команды:
int
str
float
Они переводят числа в строки и обратно, с int вы уже сталкивались.
print(str(23) + 'abc' + str(45)); // выводит 23abc45
print(float('2.5') * 2);               // выводит 5.0000e0
print(str(2.5) + 'a');               // выводит 2.5000e0a

pos и т.п.

Паскаль: Есть еще ряд команд, работающих со строками, про которые вы можете прочитать в книжках — pos, copy, delete (паскаль) и т.п. Лучше их не используйте. В большинстве случаев можно обойтись без них, плюс вы точно не знаете, как долго они работают.

Питон: Вы знаете ряд хитрых команд работы с массивами, и иногда будет возникать желание их использовать при работе со строками. Лучше их не используйте, пока вы точно не будете понимать не только что, но и насколько быстро они работают. В большинстве случаев можно обойтись без них (и так даже будет проще!), плюс вы точно не знаете, как долго они работают. Аналогично про другие продвинутые функции типа find.

(И паскаль, и питон) Например, пусть вам надо из строки удалить все пробелы. Можно писать примерно так (считаем, что у вас уже есть исходная строка s):

// паскаль
while pos(' ',s)<>0 do
    delete(s,pos(' ',s),1);
// питон
while s.find(" ") != -1:
    s = s[:s.find(" ")] + s[s.find(" ")+1:]  # вырезаем этот символ

Но это работает долго (поверьте мне :) ) и требует от вас помнить все эти команды (а на питоне — еще и осознавать код). Проще так:

// паскаль
s1:='';
for i:=1 to length(s) do
    if s[i]<>' ' then
        s1:=s1+s[i]; 
# питон
s1 ='';
for i in range(len(s)):
    if s[i] != ' ':
        s1 = s1 + s[i]; 

Результат лежит в s1. Поймите, как это работает.

На самом деле, на паскале (но не на питоне) есть еще один способ, без второй строки и без длительных сдвигов. Можете подумать над ним.