Указатели и строки языка Си

Указатели

Указатель - переменная, содержащая адрес некоторого объекта в оперативной памяти (ОП). Смысл применения указателей - косвенная адресация объектов в ОП, позволяющая динамически менять логику программы и управлять распределением ОП.

Основные применения:

· работа с массивами и строками;

· прямой доступ к ОП;

· работа с динамическими объектами, под которые выделяется ОП.

Описание указателя имеет общий вид:

тип *имя;

то есть, указатель всегда адресует определённый тип объектов! Например,

int *px; // указатель на целочисленные данные

char *s; //указатель на тип char (строку Си)

Опишем основные операции и действия, которые разрешены с указателями:

1. Сложение/вычитание с числом:

px++;

//переставить указатель px на sizeof(int) байт вперед

s--;

//перейти к предыдущему символу строки

//(на sizeof(char) байт, необязательно один)

2. Указателю можно присваивать адрес объекта унарной операцией "&":

int *px; int x,y;

px=&x;

//теперь px показывает на ячейку памяти со

// значением x

px=&y; //а теперь – на ячейку со значением y

3. Значение переменной, на которую показывает указатель, берется унарной операцией "*" ("взять значение"):

x=*px; //косвенно выполнили присваивание x=y

(*px)++; //косвенно увеличили значение y на 1

Важно! Из-за приоритетов и ассоциативности операций C++ действие

*px++;

имеет совсем другой смысл, чем предыдущее. Оно означает "взять значение y (*px) и затем перейти к следующей ячейке памяти (++)"

Расшифруем оператор

x=*px++;

Если px по-прежнему показывал на y, он означает "записать значение y в x и затем перейти к ячейке памяти, следующей за px". Именно такой подход в классическом Си используется для сканирования массивов и строк.

Приведём пример связывания указателя со статическим массивом:

int a[5]={1,2,3,4,5};

int *pa=&a[0];

for (int i=0; i<5; i++) cout << *pa++ << " ";

или

for (int i=0; i<5; i++) cout << pa[i] << " ";

Эти записи абсолютно эквиваленты, потому что в Си конструкция a[b] означает не что иное, как *(a+b), где a - объект, b – смещение от начала памяти, адресующей объект. Таким образом, обращение к элементу массива a[i] может быть записано и как *(a+i), а присваивание указателю адреса нулевого элемента массива можно бы было записать в любом из 4 видов

int *pa=&a[0];

int *pa=&(*(a+0));

int *pa=&(*a);

int *pa=a;

Важно! При любом способе записи это одна и та же операция, и это - не "присваивание массива указателю", это его установка на нулевой элемент массива.

4. Сравнение указателей (вместо сравнения значений, на которые они указывают) в общем случае может быть некорректно!

int x;

int *px=&x, *py=&x;

if (*px==*py) ... //корректно

if (px==py) ... //некорректно!

Причина – адресация ОП не обязана быть однозначной, например, в DOS одному адресу памяти могли соответствовать разные пары частей адреса "сегмент" и "смещение".

5. Указатели и ссылки могут использоваться для передачи функциям аргументов по адресу (то есть, для "выходных" параметров функций), для этого есть 2 способа

Способ 1, со ссылочной переменной C++

void swap (int &a, int &b) {

int c=a; a=b; b=c;

}

//...

int a=3,b=5; swap (a,b);

Этот способ можно назвать "передача параметров по значению, приём по ссылке".

Способ 2, с указателями Cи

void swap (int *a, int *b) {

int c=*a; *a=*b; *b=c;

}

//...

int a=3,b=5; swap (&a,&b);

int *pa=&a; swap (pa,&b);

Передача параметров по адресу, прием по значению.

Указатели и строки языка Си

Как правило, для сканирования Си-строк используются указатели.

char *s="Hello, world";

Это установка указателя на первый байт строковой константы, а не копирование и не присваивание!

Важно!1. Даже если размер символа равен одному байту, эта строка займёт не 12 (11 символов и пробел), а 13 байт памяти. Дополнительный байт нужен для хранения нуль-терминатора, символа с кодом 0, записываемого как '\0' (но не '0' – это цифра 0 с кодом 48). Многие функции работы с Си-строками автоматически добавляют нуль-терминатор в конец обрабатываемой строки:

char s[12];

strcpy(s,"Hello, world");

//Вызвали стандартную функцию копирования строки

//Ошибка! Нет места для нуль-терминатора

сhar s[13]; //А так было бы верно!

2. Длина Си-строки нигде не хранится, её можно только узнать стандартной функцией strlen(s), где s – указатель типа char *. Для строки, записанной выше, будет возвращено значение 12, нуль-терминатор не считается. Фактически, Си-строка есть массив символов, элементов типа char.

Как выполнять другие операции со строками, заданными c помощью указателей char *? Для этого может понадобиться сразу несколько стандартных библиотек. Как правило, в новых компиляторах C++ можно подключать и "классические" си-совместимые заголовочные файлы, и заголовки из более новых версий стандарта, которые указаны в скобках.

Файл ctype.h (cctype) содержит:

1) функции is* - проверка класса символов (isalpha, isdigit, ...), все они возвращают целое число, например:

char d;

if (isdigit(d)) {

//код для ситуации, когда d - цифра

}

Аналогичная проверка "вручную" могла бы быть выполнена кодом вида

if (d>='0' && d<='9') {

2) функции to* - преобразование регистра символов (toupper, tolower), они возвращают преобразованный символ. Могут быть бесполезны при работе с символами национальных алфавитов, а не только латиницей.

Модуль string.h (cstring) предназначен для работы со строками, заданными указателем и заканчивающимися байтом '\0' ("строками Си"). Имена большинства его функций начинаются на "str". Часть функций (memcpy, memmove, memcmp) подходит для работы с буферами (областями памяти с известным размером).

Примеры на работу со строками и указателями.

1. Копирование строки

char *s="Test string";

char s2[80];

strcpy (s2,s);

//копирование строки, s2 - буфер, а не указатель!

2. Копирование строки с указанием количества символов

char *s="Test string";

char s2[80];

char *t=strncpy (s2,s,strlen(s));

cout << t;

Функция strncpy копирует не более n символов (n - третий параметр), но не запишет нуль-терминатор, в результате чего в конце строки t выведется "мусор". Правильно было бы добавить после strncpy следующее:

t[strlen(s)]='\0';

то есть, "ручную" установку нуль-терминатора.

3. Копирование строки в новую память

char *s="12345";

char *s2=new char [strlen(s)+1];

strcpy (s2,s);

Здесь мы безопасно скопировали строку s в новую память s2, не забыв выделить "лишний" байт для нуль-терминатора.

4. Приведём собственную реализацию стандартной функции strcpy:

char *strcpy_ (char *dst, char *src) {

char *r=dst;

while (*src!='\0') {

*dst=*src; dst++; src++;

}

*dst='\0';

return r;

}

Вызвать нашу функцию можно, например, так:

char *src="Строка текста";

char dst[80];

strcpy_ (&dst[0],&src[0]);

Сократим текст функции strcpy_:

char *strcpy_ (char *dst, char *src) {

char *r=dst;

while (*src) *dst++=*src++;

*dst='\0';

return r;

}

5. Сцепление строк – функция strcat

char *s="Test string";

char *s2;

char *t2=strcat (s2,strcat(s," new words"));

Так как strcat не выделяет память, поведение такого кода непредсказуемо!

А вот такое сцепление строк сработает:

char s[80]; strcpy (s,"Test string");

char s2[80];

strcat (s," new words");

strcpy (s2,s);

char *t2=strcat (s2,s);

То есть, всегда должна быть память, куда писать - статическая из буфера или выделенная динамически.

6. Поиск символа или подстроки в строке.

char *sym = strchr (s,'t');

if (sym==NULL) puts ("Не найдено");

else puts (sym); //выведет "t string"

//для strrchr вывод был бы "tring"

char *sub = strstr (s,"ring");

puts (sub); //выведет "ring"

7. Сравнение строк – функции с шаблоном имени str*cmp - "string comparing"

char *a="abcd",*b="abce";

int r=strcmp(a,b);

//r=-1, т.к. символ 'd' предшествует символу 'e'

//Соответственно strcmp(b,a) вернет в данном случае 1

//Если строки совпадают, результат=0

8. Есть готовые функции для разбора строк - strtok, strspn, strсspn - см. пособие, пп. 7.1-7.3

9. Преобразование типов между числом и строкой - библиотека stdlib.h (cstdlib)

char *s="qwerty";

int i=atoi(s);

//i=0, исключений не генерируется!

Из числа в строку:

1) itoa, ultoa - из целых типов

char buf[20];

int i=-31189;

char *t=itoa(i,buf,36);

//В buf получили запись i в 36-ричной с.с.

2) fcvt, gcvt, ecvt - из вещественных типов

Наши рекомендации