Верно ли утверждение: Ключевое слово struct обязательно при объявлении типа «структура»?
да
нет
Объем памяти, занимаемый структурой, равен:
максимальному объему памяти составляющих полей
сумме объемов памяти полей (если исключить из рассмотрения особенности, связанные с выравниванием адресов памяти)
1 килобайту
Отметьте утверждения, несправедливые для объединения:
объем памяти, занимаемый объединением, равен максимальному объему памяти составляющих полей
поля объединения помещаются в одно и то же место памяти
компоненты объединения обязательно имеют одинаковый тип
Какие термины обозначают сложную структуру данных?
Структура
Массив
Таблица
Константа
Структуры в программировании нужны для …
объединения компонентов разного типа в одну переменную в соответствии с логикой задачи
уменьшения длины программы
Верно ли утверждение: «ключевое слово typedef обязательно при объявлении типа «структура»?
да
нет
Отметьте свойства, неправильные для структур:
компоненты структуры могут иметь одинаковый тип
в любой программе должны быть структуры
компоненты структуры могут иметь разный тип
все компоненты структуры имеют один и тот же тип
компонентой структуры может быть массив
компоненты структуры хранятся в последовательных ячейках оперативной памяти
число компонентов структуры может быть бесконечно
структуры нельзя использовать в подпрограмме
к компоненте структуры следует обращаться, используя ее имя
компонентой структуры может быть структура
Отметьте свойства, характерные для массивов
элемент массива может иметь один номер, а может иметь и несколько номеров
элементы массива хранятся в последовательных ячейках оперативной памяти
массивы необходимы для организации циклов
все элементы массива имеют один и тот же тип
все элементы массива имеют одинаковое имя и различаются номерами
массив состоит из конечного числа элементов
подпрограммы без массивов невозможны
число элементов массива может быть бесконечно
в любой программе должны быть массивы
Отметьте операции, являющиеся операциями выбора компоненты структурированной переменной:
>>
<-
. (точка)
->
Допустима ли инструкция: char *s=»text»?
да
нет
Имеется фрагмент кода:
void main()
{char a[10]=»слово»;
Какое значение имеет элемент массива a[5]?
0
русская буква «о»
неопределенное
В языке Си константа ‘a’ занимает … байтов памяти.
2
1
256
Строковый литерал «text» занимает … байтов памяти.
4
2
256
5
Какое значение вернет функция strcmp(s1, s2), если s1 и s2 объявлены так: char *s1=»abba», char *s2=»aaaa»;
отрицательное значение
положительное значение
0
Какое значение вернет функция strcmp(s1, s2), если s1 и s2 объявлены так:
char *s1=»8″, char *s2=»1000″;
положительное значение
отрицательное значение
0
Допустима ли инструкция: s=»text», если s описано так: char s[6]?
нет
да
Сколько байтов памяти занимает переменная str, объявленная с помощью инструкции: char str[]=»слово»?
6
5
0
256
Строковый литерал «1234567890» занимает … байтов памяти.
256
10
9
11
Имеется фрагмент кода:
void main()
{char a[10]=»слово»;
Какое значение имеет элемент массива a[6]?
неопределенное
русская буква «о»
пробел
0
В языке Си константа «a» занимает … байтов памяти.
2
1
256
3
Какое значение вернет функция strcmp(s1, s2), если s1 и s2 объявлены так: char *s1=»5″, char *s2=»100″;
отрицательное значение
0
положительное значение
1.
char s[] = "text";
Объявление массива s
типа char []
и инициализация этого массива строковым литералом "text"
. Т. е. s
— это просто массив из пяти символов: t
, e
, x
, t
, .
Вы можете менять его:
s[0] = 'n'; /* s: "next" */
Но не можете переприсвоить сам массив s
(это же массив):
char s[] = "text"; /* OK */
s = "another text"; /* Это ошибка. */
2.
char *s = "text"; /* до C++11 */
const char *s = "text"; /* начиная C++11 */
Объявление указателя s
типа char *
и присваивание ему указателя на первый элемент строкового литерала "text"
. Попытка изменить этот строковой литерал (s[0] = 'a'
, например), — это неопределенное поведение.
Однако сам указатель переприсваивать можно:
const char *p = "text"; /* OK */
p = "another text"; /* OK */
Начиная с C++11 строковые литералы могут быть прямо присвоены только const char *
(т. е. только указателям на константный char
).
3.
std::string s = "text";
Создание объекта s
класса std::string
и присваивание ему const char *
1. Т. е. s
— это не массив и не указатель, а объект.
Класс строк в свою очередь содержит множество различных возможностей: копирование, сравнение, конкатенация, изменение, поиск подстрок и так далее. Чего строки в стиле C (массивы), конечно лишены (если не принимать во внимание <cstring>
).
Что лучше использовать?
Использовать нужно то, что больше подходит для конкретной задачи. У каждой обговоренной выше «строки» есть свои области применения.
- Выполняется неявное приведение типов: присваивается не
const char [N]
, что является типов для всех строковых литералов, а именно указатель. Также см. оператор присваивания класса строк.
Improve Article
Save Article
Like Article
Improve Article
Save Article
Like Article
Consider below two statements in C. What is the difference between the two?
char s[] = "geeksquiz"; char *s = "geeksquiz";
Below are the key differences:
The statements ‘char s[] = “geeksquiz”‘ creates a character array which is like any other array and we can do all array operations. The only special thing about this array is, although we have initialized it with 9 elements, its size is 10 (Compiler automatically adds ‘’)
C
#include <stdio.h>
int
main()
{
char
s[] =
"geeksquiz"
;
printf
(
"%lu"
,
sizeof
(s));
s[0] =
'j'
;
printf
(
"n%s"
, s);
return
0;
}
Output:
10 geeksquiz
The statement ‘char *s = “geeksquiz”‘ creates a string literal. The string literal is stored in the read-only part of memory by most of the compilers. The C and C++ standards say that string literals have static storage duration, any attempt at modifying them gives undefined behavior.
s is just a pointer and like any other pointer stores address of string literal.
C
#include <stdio.h>
int
main()
{
char
*s =
"geeksquiz"
;
printf
(
"%lu"
,
sizeof
(s));
return
0;
}
Output:
8
Running above program may generate a warning also “warning: deprecated conversion from string constant to ‘char*’”. This warning occurs because s is not a const pointer, but stores address of the read-only location. The warning can be avoided by the pointer to const.
C
#include <stdio.h>
int
main()
{
const
char
*s =
"geeksquiz"
;
printf
(
"%lu"
,
sizeof
(s));
return
0;
}
Last Updated :
15 Mar, 2023
Like Article
Save Article
В C можно использовать строковый литерал в таком объявлении:
char s[] = "hello";
или такой:
char *s = "hello";
Так в чем разница? Я хочу знать, что на самом деле происходит с точки зрения продолжительности хранения, как во время компиляции, так и во время выполнения.
12 ответов
разница здесь в том, что
char *s = "Hello world";
будет "Hello world"
на только для чтения части памяти, и что делает s
указатель на это делает любую операцию записи в этой памяти незаконной.
как делать:
char s[] = "Hello world";
помещает строку литерала в память только для чтения и копирует строку во вновь выделенную память в стеке. Таким образом, делая
s[0] = 'J';
юридические.
во-первых, в аргументах функции, они полностью эквивалентны:
void foo(char *x);
void foo(char x[]); // exactly the same in all respects
в других контекстах, char *
выделяет указатель, в то время как char []
выделяет массив. Вы спросите, куда ведет веревка в первом случае? Компилятор тайно выделяет статический анонимный массив для хранения строкового литерала. Итак:
char *x = "Foo";
// is approximately equivalent to:
static const char __secret_anonymous_array[] = "Foo";
char *x = (char *) __secret_anonymous_array;
обратите внимание, что вы никогда не должны пытаться изменить содержимое этого анонимного массива с помощью этого указателя; эффекты не определены (часто означает crash):
x[1] = 'O'; // BAD. DON'T DO THIS.
использование синтаксиса массива напрямую выделяет его в новую память. Таким образом, модификация безопасна:
char x[] = "Foo";
x[1] = 'O'; // No problem.
однако массиве только живет столько, сколько его масштабы появилось, так что, если вы делаете это в функции, не возвращать или передавать указатель на этот массив — сделать копию с strdup()
или аналогичные. Если массив выделен в глобальной области, конечно, никаких проблем.
это заявление:
char s[] = "hello";
создает один объект — a char
массив размера 6, называемый s
, инициализируется значениями 'h', 'e', 'l', 'l', 'o', ''
. Где этот массив выделяется в памяти, и как долго он живет, зависит от того, где объявление. Если объявление находится внутри функции, оно будет жить до конца блока, в котором оно объявлено, и почти наверняка будет выделено в стеке; если оно находится вне функции, оно будет наверное храниться в «инициализированном сегменте данных», который загружается из исполняемого файла в записываемую память при запуске программы.
С другой стороны, это заявление:
char *s ="hello";
создает два объекты:
- a только для чтения массив из 6
char
s, содержащий значения'h', 'e', 'l', 'l', 'o', ''
, который не имеет имени и имеет статическая продолжительность хранения (это означает, что она живет для вся жизнь программы); и - переменная типа указатель на символ, называется
s
, который инициализируется местоположением первого символа в этом неназванном массиве только для чтения.
неназванный массив только для чтения обычно находится в сегменте «текст» программы, что означает, что он загружается с диска в память только для чтения вместе с самим кодом. Расположение s
переменной указателя в памяти зависит от того, где объявление (как и в первом примере).
учитывая заявления
char *s0 = "hello world";
char s1[] = "hello world";
предположим следующую гипотетическую карту памяти:
0x01 0x02 0x03 0x04 0x00008000: 'h' 'e' 'l' 'l' 0x00008004: 'o' ' ' 'w' 'o' 0x00008008: 'r' 'l' 'd' 0x00 ... s0: 0x00010000: 0x00 0x00 0x80 0x00 s1: 0x00010004: 'h' 'e' 'l' 'l' 0x00010008: 'o' ' ' 'w' 'o' 0x0001000C: 'r' 'l' 'd' 0x00
строковый литерал "hello world"
в 12-элемент массива char
(const char
в C++) со статической продолжительностью хранения, что означает, что память для него выделяется при запуске программы и остается выделенной до завершения программы. Попытка изменить содержимое строкового литерала вызывает неопределенное поведение.
в линия
char *s0 = "hello world";
определяет s0
как указатель на char
с автоматической продолжительностью хранения (что означает переменную s0
существует только для области, в которой он объявлен) и копирует адрес строкового литерала (0x00008000
в этом примере) к нему. Обратите внимание, что с s0
указывает на строковый литерал, он не должен использоваться в качестве аргумента для любой функции, которая попытается его изменить (например,strtok()
, strcat()
, strcpy()
, etc.).
в линия
char s1[] = "hello world";
определяет s1
как 12-элементный массив char
(длина берется из строкового литерала) с автоматической продолжительностью хранения и копирует содержание литерала массива. Как вы можете видеть на карте памяти, у нас есть две копии строку "hello world"
; разница в том, что вы можете изменить строку, содержащуюся в s1
.
s0
и s1
взаимозаменяемы в большинстве контекстов; вот исключения:
sizeof s0 == sizeof (char*)
sizeof s1 == 12
type of &s0 == char **
type of &s1 == char (*)[12] // pointer to a 12-element array of char
вы можете переназначить переменную s0
чтобы указать на другой строковый литерал или другую переменную. Вы не можете переназначить переменную s1
чтобы указать на другой массив.
С99 проект N1256
существует два совершенно разных использования литералов массива:
-
инициализации
char[]
:char c[] = "abc";
это «больше магии», и описанного в 6.7.8/14 «инициализация»:
массив символьного типа может быть инициализирован символьным строковым литералом, необязательно
заключенный в скобки. Последовательные символы символьного строкового литерала (в том числе
завершающий нулевой символ, если есть номер или если массив неизвестного размера) инициализировать
элементы массива.так что это просто ярлык для:
char c[] = {'a', 'b', 'c', ''};
как и любой другой обычный массив,
c
может быть изменен. -
везде еще: он генерирует:
- безымянный
- массив char какой тип строковых литералов в C и C++?
- со статическим хранением
- это дает UB при изменении
поэтому, когда вы пишете:
char *c = "abc";
это похоже на:
/* __unnamed is magic because modifying it gives UB. */ static char __unnamed[] = "abc"; char *c = __unnamed;
обратите внимание на неявное приведение от
char[]
tochar *
, что всегда законно.тогда, если вы измените
c[0]
, вы также измените__unnamed
, который является UB.это задокументировано в 6.4.5 «строковые литералы»:
5 в фазе перевод 7, байт или код нулевое значение добавляется к каждому многобайтовых
последовательность символов, которая является результатом строкового литерала или литералов. Многобайтовый символ
затем последовательность используется для инициализации массива статической длительности и длины хранения
достаточно, чтобы содержать последовательность. Для символьных строковых литералов, элементы массива имеют
введите char и инициализируются с отдельными байтами многобайтового символа
последовательность.[ ..]6 Не указано, являются ли эти массивы различными при условии, что их элементы имеют
нужных значений. Если программа пытается изменить такой массив, поведение
не определено.
6.7.8/32 «инициализация» дает прямой пример:
пример 8: декларация
char s[] = "abc", t[3] = "abc";
определяет» простые » объекты массива символов
s
иt
элементы которого инициализируются символом строковый литерал.эта декларация идентична
char s[] = { 'a', 'b', 'c', '' }, t[] = { 'a', 'b', 'c' };
содержимое массивов можно изменять. С другой стороны, декларация
char *p = "abc";
определяет
p
С типом » указатель на char «и инициализирует его, чтобы указать на объект с типом» массив char » длиной 4, элементы которого инициализируются символьным строковым литералом. Если сделана попытка использоватьp
изменить содержимое массива, поведение неопределено.
GCC 4.8 x86-64 реализация ELF
программа:
#include <stdio.h>
int main() {
char *s = "abc";
printf("%sn", s);
return 0;
}
компиляция и декомпиляция:
gcc -ggdb -std=c99 -c main.c
objdump -Sr main.o
вывод содержит:
char *s = "abc";
8: 48 c7 45 f8 00 00 00 movq x0,-0x8(%rbp)
f: 00
c: R_X86_64_32S .rodata
заключение: магазины GCC char*
его в , не на .text
.
если мы сделаем то же самое для char[]
:
char s[] = "abc";
получаем:
17: c7 45 f0 61 62 63 00 movl x636261,-0x10(%rbp)
поэтому он хранится в стеке (относительно %rbp
).
обратите внимание, однако, что сценарий компоновщика по умолчанию ставит .rodata
и .text
в том же сегменте, который имеет разрешение execute, но без разрешения на запись. Это можно наблюдать с помощью:
readelf -l a.out
, который содержит:
Section to Segment mapping:
Segment Sections...
02 .text .rodata
27
автор: Ciro Santilli 新疆改造中心 六四事件 法轮功
char s[] = "hello";
объявляет s
быть массивом char
который достаточно долго, чтобы удерживать инициализатор (5 + 1 char
s) и инициализирует массив путем копирования членов данного строкового литерала в массив.
char *s = "hello";
объявляет s
быть указателем на один или несколько (в данном случае больше)char
s и указывает его непосредственно на фиксированное (только для чтения) местоположение, содержащее литерал "hello"
.
char s[] = "Hello world";
здесь s
— это массив символов, который при желании можно перезаписать.
char *s = "hello";
строковый литерал используется для создания этих символьных блоков где-то в памяти, которые этот указатель s
указывает на. Здесь мы можем переназначить объект, на который он указывает, изменив это, но пока он указывает на строковый литерал, блок символов, на который он указывает, не может быть изменен.
в качестве дополнения учтите, что, поскольку для целей только для чтения использование обоих идентично, вы можете получить доступ к символу, индексируя либо с []
или *(<var> + <index>)
формат:
printf("%c", x[1]); //Prints r
и:
printf("%c", *(x + 1)); //Prints r
очевидно, если вы попытаетесь сделать
*(x + 1) = 'a';
вы, вероятно, получите ошибку сегментации, как вы пытаетесь получить доступ только для чтения памяти.
3
автор: Nick Louloudakis
просто добавить: вы также получаете разные значения для их размеров.
printf("sizeof s[] = %zun", sizeof(s)); //6
printf("sizeof *s = %zun", sizeof(s)); //4 or 8
Как упоминалось выше, для массива ''
будет выделен в качестве конечного элемента.
char *str = "Hello";
выше устанавливает str, чтобы указать на литерал «Hello», который жестко закодирован в двоичном образе программы, который помечен как только для чтения в памяти, означает, что любое изменение в этом строковом литерале незаконно, и это вызовет ошибки сегментации.
char str[] = "Hello";
копирует строку во вновь выделенную память в стеке. При этом внесение каких-либо изменений в него допускается и законно.
means str[0] = 'M';
изменит str на «Mello».
Для больше деталей, пожалуйста пройдите аналогичный вопрос:
почему я получаю ошибку сегментации при записи в строку, инициализированную «char *s», но не » char s []»?
по делу:
char *x = "fred";
x-это lvalue — его можно назначить. Но в случае:
char x[] = "fred";
x не является lvalue, это rvalue — вы не можете назначить ему.
в свете комментариев здесь должно быть очевидно, что: char * s = » привет» ;
Это плохая идея,и ее следует использовать в очень узкой области.
Это может быть хорошей возможностью указать, что» правильность const «- это»хорошая вещь». Когда и где вы можете, используйте ключевое слово » const «для защиты вашего кода от» расслабленных «абонентов или программистов, которые обычно наиболее» расслаблены», когда в игру вступают указатели.
достаточно мелодрамы, вот чего можно достичь при украшении указателей «const».
(Примечание: нужно читать объявления указателя справа налево.)
Вот 3 различных способа защитить себя при игре с указателями:
const DBJ* p means "p points to a DBJ that is const"
— то есть объект DBJ не может быть изменен через p.
DBJ* const p means "p is a const pointer to a DBJ"
— то есть вы можете изменить объект DBJ через p, но вы не можете изменить сам указатель p.
const DBJ* const p means "p is a const pointer to a const DBJ"
— то есть вы не можете изменить сам указатель p и не можете изменить объект DBJ через p.
ошибки, связанные с попытками мутаций const-ant, перехватываются во время компиляции. Для const нет пространства выполнения или штрафа скорости.
(предполагается, что вы используете компилятор C++, конечно ?)
—DBJ
Лекции по C/C++: указатели и строки Си
Указатель — это переменная, содержащая адрес некоторого объекта в оперативной памяти (ОП). Смысл применения указателей — косвенная адресация объектов в ОП, позволяющая динамически менять логику программы и управлять распределением ОП.
Основные применения:
- работа с массивами и строками;
- прямой доступ к ОП;
- работа с динамическими объектами, под которые выделяется ОП.
Описание указателя имеет следующий общий вид:
тип *имя;
то есть, указатель всегда адресует определённый тип объектов! Например,
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
«. Именно такой подход в классическом Си используется для сканирования массивов и строк.
Вот пример, с точностью до адресов памяти показывающий это важное различие. Комментарием приведены значения и адреса памяти переменных x
и y
, а также значение, полученное по указателю px
и адрес памяти, на который он показывает. Обратите внимание, что после выполнения второго варианта кода значение, полученное по указателю, стало «мусором», так как он показывал на переменную, а не на нулевой элемент массива.
#include <cstdio> int main() { int x=0,y=1; int *px=&y; printf ("nx=%d on &%p, y=%d on &%p, *px=%d on &%p",x,&x,y,&y,*px,px); x=(*px)++; //после первого запуска замените на x=*px++; printf ("nx=%d on &%p, y=%d on &%p, *px=%d on &%p",x,&x,y,&y,*px,px); /* Действие (*px)++ x=0 on &002CFC14, y=1 on &002CFC08, *px=1 on &002CFC08 x=1 on &002CFC14, y=2 on &002CFC08, *px=2 on &002CFC08 Действие *px++ x=0 on &0021F774, y=1 on &0021F768, *px=1 on &0021F768 x=1 on &0021F774, y=1 on &0021F768, *px=-858993460 on &0021F76C */ getchar(); return 0; }
Приведём пример связывания указателя со статическим массивом:
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 с кодом 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
) предназначен для работы со строками, заданными указателем и заканчивающимися байтом ''
(«строками Си»). Имена большинства его функций начинаются на «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)]='';
то есть, «ручную» установку нуль-терминатора.
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!='') { *dst=*src; dst++; src++; } *dst=''; 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=''; 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
— см. пособие, пп. 8.1-8.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
— из вещественных типов
Работа с динамической памятью
Как правило, описывается указатель нужного типа, который затем связывается с областью памяти, выделенной оператором new
или си-совместимыми функциями для управления ОП.
1. Описать указатель на будущий динамический объект:
int *a; //Надёжнее int *a=NULL;
2. Оператором new
или функциями malloc
, calloc
выделить оперативную память:
a = new int [10];
или
#include <malloc.h> //stdlib.h, alloc.h в разных компиляторах //... a = (int *) malloc (sizeof(int)*10);
или
a = (int *) calloc (10,sizeof(int));
В последнем случае мы выделили 10 элементов по sizeof(int)
байт и заполнили нулями ''
.
Важно! Не смешивайте эти 2 способа в одном программном модуле или проекте! Предпочтительней new
, кроме тех случаев, когда нужно обеспечить заполнение памяти нулевыми байтами.
3. Проверить, удалось ли выделить память — если нет, указатель равен константе константе NULL
из стандартной библиотеки (в ряде компиляторов null
, nullptr
, 0
):
if (a==NULL) { //Обработка ошибка "Не удалось выделить память" }
4. Работа с динамическим массивом или строкой ничем не отличается от случая, когда они статические.
5. Когда выделенная ОП больше не нужна, её нужно освободить:
delete a; //Если использовали new
free (a); //Пытается освободить ОП, //если использовали malloc/calloc
Важно! Всегда старайтесь придерживаться принципа стека при распределении ОП. То есть, объект, занявший ОП последним, первым её освобождает.
Пример. Динамическая матрица размером n*m.
const int n=5,m=4; int **a = new (int *) [n]; for (int i=0; i<n; i++) a[i] = new int [m];
После этого можно работать с элементами матрицы a[i][j]
, например, присваивать им значения. Освободить память можно было бы так:
for (int i=n-1; i>-1; i--) delete a[i]; delete a;
Рекомендуемые задачи
1. Написать собственную функцию работы со строкой, заданной указателем, сравнить со стандартной.
2. Написать собственную функцию для работы с одномерным динамическим массивом, заданным указателем.
3. Написать свои версии функций преобразования строки в число и числа в строку.
Оглавление серии лекций
05.11.2015, 08:12 [22942 просмотра]
К этой статье пока нет комментариев, Ваш будет первым