Разделение строки на массив строк в СИ

Здравствуйте.Нужна помощь по Си.Нужно написать код который который мог бы разделять строку, веденную пользователем ,на слова и эти слова заносились в массив строк.
Вот тут я ввожу строку и заношу ее в str.
#include
#include
#include
int main ( void )
<
char * str ;
str = malloc ( sizeof ( * str ) * 100 )
gets ( str ) ;
printf ( «%s» ,str ) ;
return 0 ;
>
На сколько я понимаю далее нужно будет объявить массив строк и выделить под него память(как это делается не знаю) .Разделить str с помощью команды strtok и циклом занести все в новосозданный массив строк.
Си грубо не знаю.Делал похожие задание на шарпе 2 года назад.Честно говоря попроще было=)
Кто может настрочите код пожалуйста(думаю он будет не большой). Заранее благодарен.
1 ответ
21 октября 2016 года
65 / / 17.01.2011
#include
#include
#include
typedef struct <
char ** arr ;
size_t cnt ;
> array_t ;
#define array_init(a) (a).arr = NULL, (a).cnt = 0
void array_clear ( array_t * a ) ;
int array_resize ( array_t * a, size_t n ) ;
void array_strtok ( array_t * a, char * s, const char * delim ) ;
int main ( void ) <
array_t a ;
size_t i ;
char s [ 128 ] ;
printf ( «Enter str: » ) ;
scanf ( «%127[^ \n ]» , s ) ;
array_init ( a ) ;
array_strtok ( & a, s, » \r \n \t . » ) ;
for ( i = 0 ; i < a. cnt ; ++ i )
puts ( a. arr [ i ] ) ;
array_clear ( & a ) ;
return 0 ;
>
/* разбиение строки на слова */
void array_strtok ( array_t * a, char * s, const char * delim ) <
char * i, * j ;
size_t m, n = 0 ;
/* подсчитываем кол-во слов */
for ( i = s ; * i ; i = j ) <
while ( * i && ( strchr ( delim, * i ) ! = NULL ) )
++ i ;
if ( ! array_resize ( a, n ) )
return ;
m = 0 ;
for ( i = strtok ( s, delim ) ; i ! = NULL ; i = strtok ( NULL , delim ) )
a — > arr [ m ++ ] = strdup ( i ) ;
>
/* удаление массива */
void array_clear ( array_t * a ) <
size_t i ;
if ( a — > arr ! = NULL ) <
for ( i = 0 ; i < a - >cnt ; ++ i )
free ( a — > arr [ i ] ) ;
free ( a — > arr ) ;
a — > arr = NULL ;
a — > cnt = 0 ;
>
>
/* выделение памяти под массив */
int array_resize ( array_t * a, size_t n ) <
size_t i ;
array_clear ( a ) ;
a — > arr = ( char ** ) malloc ( n * sizeof ( char * ) ) ;
if ( a — > arr == NULL )
return 0 ;
for ( i = 0 ; i < n ; ++ i )
a — > arr [ i ] = NULL ;
a — > cnt = n ;
return 1 ;
>
Работа с массивами в языке Си
На этой странице относительно подробно рассказывается о статических и динамических массивах. Краткое изложение основных моментов и описание методов поиска ошибок доступны при нажатии на кнопки выше. Двумерные массивы описаны на этой странице.
Массив – это линейно упорядоченная совокупность однотипных элементов. Массив определяется типом элементов (int, double, . ) и длиной. Доступ к элементам осуществляется по индексу – порядковому номеру элемента массива. Логически первый элемент массива имеет индекс ноль. В языке Си существуют статические массивы, число элементов в которых должно быть известно в момент компиляции программы, и динамические массивы, размер которых задается в процессе выполнения программы, то есть может зависеть от входных данных. Эти два типа отличаются только методом создания массива, поэтому сначала рассмотрим статические массивы.
Статические массивы
Способы объявления статических массивов
Объявление статического массива отличается от объявления обычной переменной только указанием количества элементов массива. Например, следующее объявление означает, что именем points называется массив из 100 действительных чисел.
double points[100];
В некотором смысле можно считать, что такое объявление переменной points создает 100 переменных, которые называются points[0], points[1], . points[99]. Плюс к этому, «имена» этих переменных можно вычислять: points[1], points[0+1] или points[k-1] имеют одно значение (если k=2).
В реальных программах следует избегать явного использования числовых констант в объявлениях массива (и других частях программы). Если нам нужно объявить два массива, которые теоретически могут иметь разный размер, например,
double points[100]; int students[100];
то в дальнейшем, если возникнет необходимость увеличить один из массивов, будет сложно отличить одну константу от другой. Особенно это верно при обработке элементов массива (см. ниже). Правильным считается использование директив препроцессора для присвоения константам «говорящих» имен. Например:
#define NPOINTS 100 #define NSTUDENTS 100 . double points[NPOINTS]; int students[NSTUDENTS];
Объявление массива может быть совмещено с присвоением значений его элементам. Например,
double points[] = ;
создает массив из четырех действительных чисел с указанными значениями. Заметим, что в данном случае число элементов массива в квадратных скобках не указывается. Компилятор самостоятельно вычисляет длину по списку начальных значений. В программе можно вычислить длину такого массива, разделив его размер на размер одного элемента (пример ниже).
Работа с элементами массива
Для доступа к элементу массива достаточно знать его имя и порядковый номер элемента. В языке Си элементы массива индексируются начиная с нуля, то есть в массиве из двух элементов корректными являются индексы 0 и 1. Если массив имеет имя array, то его k -й элемент записывается как array[k] . Это выражение может использоваться как для получения значения элемента массива, так и для его изменения, если оно стоит в левой части оператора присваивания. Рассмотрим для примера следующую программу.
#define NPOINTS 100 int main() < double points[NPOINTS]; int k; points[0] = 0.1; for(k=1; k < NPOINTS; k++) < points[k] = 0.1 + points[k-1]; >return 0; >
Эта программа заполняет массив действительных чисел значениями 0, 0.1, 0.2 и так далее. Отметим, что макропеременная NPOINTS используется как при объявлении массива, так и в качестве верхней границы цикла по всем его элементам. Если размер массива нужно будет изменить, то достаточно исправить одну строчку в программе (#define).
Пример работы с массивом, который задан с начальными значениями:
int main() < double points[] = ; int k; int npoints = sizeof(points)/sizeof(points[0]); for(k=0; k < npoints; k++) < printf("points[%d] = %lf\n", k, points[k]); >return 0; >
Типичная ошибка при работе с массивами состоит в указании неправильного индекса. Если в приведенной выше программе переменная цикла k будет пробегать значения от 0 до npoints включительно, то поведение программы, вообще говоря, может быть любым. Наиболее вероятным поведением является вывод на экран какого-то значения, но может возникнуть и критическая ошибка, которая приведет к аварийной остановке программы.
Представление массива в памяти и адресная арифметика
В памяти ЭВМ элементы массива записаны последовательно без пропусков. Имя массива является указателем на его начальный элемент (с индексом 0). Поскольку в массиве все элементы имеют одинаковый тип, то зная адрес начала массива (A), размер одного элемента (size) и индекс k можно вычислить адрес размещения k-ого элемента: A + k*size. Если требуется получить значение k-ого элемента массива, то достаточно выполнить одно умножение (k*size), одно сложение (A + k*size) и загрузить значение из памяти по только что вычисленному адресу. Таким образом, обращение к элементу массива очень эффективно и сложность этой операции не зависит от величины индекса k: получение (или изменение) значения нулевого элемента столь же эффективно, как и миллионного.
Хорошо, адрес начала массива мы знаем — это его имя, индекс нам известен, но как узнать size (размер одного элемента)? Чуть ниже мы узнаем как это сделать, но для работы с указателями на элементы массива это не требуется! В языке Си к указателям можно прибавлять целые числа. Например, если есть указатель double *a; , то значением выражения a+9 будет адрес десятого (еще раз вспомним, что массивы индексируются с нуля!) элемента массива, который начинается с адреса a . Компилятор сам понимает, что a является указателем на double и прибавляет нужное значение.
Обратной стороной последовательно хранения элементов в памяти является сложность вставки нового значения с сохранением порядка следования элементов. Например, если в массив нужно добавить новое значение по индексу 0, то чтобы «освободить» место все элементы массива придется сдвинуть на одну позицию. Ясно, что сложность этой операции зависит от длины массива. Чем больше длина, тем дольше выполняется это действие.
Передача массива в функцию
Функция может получать на вход массив. В действительности в функцию передается адрес начала массива и его длина. Прототип функции может быть оформлен либо так:
int print_array(double x[], int len);
int print_array(double *x, int len);
Эти варианты являются эквивалентными. Некоторые программисты предпочитают первый (квадратные скобки показывают, что формальный параметр функции является массивом), другие — второй (имя массива является указателем на нулевой элемент). Естественно, что функция может иметь и другие параметры, в том числе, другие массивы. Это только пример.
Рассмотрим возможную реализацию функции распечатывания массива.
#include int print_array(double x[], int len) < int k; for(k = 0; k < len; k++) < printf("x[%d] = %lf\n", k, x[k]); >return 0; >
При вызове функции в качестве аргумента нужно передавать имя массива и его длину.
int main() < double points[] = ; int npoints = sizeof(points)/sizeof(points[0]); print_array(points, npoints); return 0; >
Внимание! Если функция print_array изменит значение элемента массива x (например, в цикле будет написано x[k]=0; ), то изменятся значения и в массиве points функции main. Элементы массива при вызове функций не копируются! Функция получает на вход адрес памяти, где записаны элементы массива. Эта память «общая» для вызывающей и вызываемой функции.
Динамические массивы: malloc и free
Статические массивы имеют одно существенное ограничение: размер массива должен быть известен в момент компиляции программы. В большинстве задач размер данных становится известным только в момент выполнения программы. Например, вы написали программу для обработки списка друзей или подписчиков в социальной сети. У одного пользователя друзей мало, а у другого — очень много. Какое значение выбрать для длины массива друзей? 200? 1000? Миллион? Если константа будет очень большой, чтобы «заведомо» (посмотрите как росло число пользователей Интернет) устраивать всех пользователей, то для подавляющего числа пользователей это приведет к излишним затратам памяти. Захотите ли Вы поставить на свой телефон программу, которая при запуске займет всю его память с сообщением: «А вдруг у тебя миллион друзей. Нет? Всего 12?! Неплохо, прямо как у Oушена! А y Трампа миллион. «? [Друзей не должно и не может быть так много, но это к делу не относится.] Чтобы избежать таких ситуаций нужно уметь выделять минимально необходимое количество памяти.
- выделение памяти под массив;
- освобождение памяти, когда она больше не требуется.
Стандартная библиотека языка Си содержит несколько функций для работы с динамической памятью. Нам понадобятся две: malloc (memory allocation — выделение памяти) и free (освобождение). Для использования этих удивительных функций нужно в программе подключить заголовочный файл . Пример программы приведен ниже. Сначала посмотрим, что делают эти функции.
malloc: динамическое выделение памяти
Прототип: void *malloc(size_t size); Параметры: size — беззнаковое целое число, размер запрашиваемой памяти в байтах. Возвращает: Адрес начала выделенной памяти или NULL, если не удалось выделить память. Функция malloc возвращает указатель типа void * — это «абстрактный» указатель на память, который может быть приведен к указателю на любой тип. Функция malloc не может сразу возвращать указатель нужного типа, так как она используется для создания разных массивов, а в прототипе нужно указать конкретный тип возвращаемого значения.
Для выделения памяти под массив из n элементов типа T, где в T могут быть стандартные типы int , double и т.п., необходимо знать размер значения T в байтах. Для определения этой величины в языке Си есть специальный оператор sizeof , который в момент компиляции программы вычисляет нужное значение. Например, массив из n целых чисел будет занимать n*sizeof(int) байт памяти.
Таким образом, для создания динамического массива некоторого типа, например с массива целых чисел, нужно использовать команду вида:
int length; int *points; // . получили значение length (длина массива) points = (int *)malloc(length * sizeof(int));
Если нужен другой тип данных, допустим double , то int заменяется на нужное имя ( double ) в трех местах (кроме первой строки, так как длина массива всегда является целым числом).
free: освобождение памяти
Функция free позволяет освободить область памяти, которая ранее была выделена программе при вызове malloc .
Прототип void free(void *ptr); Параметры: ptr — указатель, который был получен в результат вызова malloc.
В качестве аргумента функции free может использоваться только тот адрес, который был получен в результате вызова malloc. Нельзя создать статический массив и «освободить» его функцией free. Адрес может быть освобожден только один раз. Если два раза подряд вызвать функцию free с одним и тем же аргументом, то это приведет к аварийному завершению программы.
Пример программы с динамическим массивом
В качестве иллюстрации описанных методов рассмотрим программу, которая динамически выделяет память под массив и считывает его.
#include #include int main() < int npoints; double *points; int k; scanf("%d", &npoints); /* npoints получает значение в момент выполнения программы */ points = (double *)malloc(npoints*sizeof(double)); /* Выдели память для хранения npoints элементов, каждый размера sizeof(double) */ if(points == NULL) < printf("Произошла ошибка. Запросили слишком много памяти??\n"); return -1; >/* Считываем данные с использованием адресной арифметики */ k = 0; while(k < npoints && scanf("%lf", points+k) == 1) < k++; >/* Работаем с points как с обычным массивом */ /* Например, вызываем функцию print_array(points, npoints) */ free(points); /* Освободили память */ return 0; >
Функции, которые возвращают массив
Иногда бывает удобно сделать функцию, которая возвращает динамически созданный массив. Примером может служить функция считывания массива из файла. Такая функция может получать на вход файловую переменную ( FILE * ) и должна вернуть в вызывающую функцию массив значений. Например, массив действительнах чисел. Попробуем ее реализовать.
Во-первых, нужно понять, какой прототип должна иметь такая функция. Она должна вернуть два значения: адрес выделенной памяти и длину массива. Как мы уже знаем, несколько значений можно вернуть используя указатели. Длина массива имеет тип int . Значит параметр функции будет иметь тип int * (адрес, по которому нужно записать значение). Массив — это адрес нулевого элемента, то есть double * . Значит параметр будет иметь тип double ** — «указатель на указатель». Мы должны передать адрес (одна звездочка), по которому нужно записать результат вызова malloc, который имеет тип double * . В результате получаем следующий прототип:
int read_array(FILE *input, double **array, int *length);
Собственно возвращаемое значение функции ( int ) может быть кодом ошибки. Если функция вернет 0, то это означает успешное выполнение. Любое ненулевое значение означает ошибку.
Теперь можно рассмотреть структуру тела функции (для наглядности в приведенном ниже коде отсутствуют проверки успешности считывания и корректности данных).
int read_array(FILE *input, double **array, int *length) < double *arr; int arr_length, k; /* Считываем массив: сначала длину, потом элементы */ fscanf("%d", &arr_length); arr = (double *)malloc(arr_length * sizeof(double)); for(k = 0; k < arr_length; k++) fscanf("%lf", arr + k); /* Копируем результат по заданным адресам */ *length = arr_length; *array = arr; return 0; >
String. To Char Array Метод
Некоторые сведения относятся к предварительной версии продукта, в которую до выпуска могут быть внесены существенные изменения. Майкрософт не предоставляет никаких гарантий, явных или подразумеваемых, относительно приведенных здесь сведений.
Копирует знаки данного экземпляра в массив знаков Юникода.
Перегрузки
Копирует знаки из указанной подстроки данного экземпляра в массив знаков Юникода.
Копирует знаки данного экземпляра в массив знаков Юникода.
ToCharArray(Int32, Int32)
Копирует знаки из указанной подстроки данного экземпляра в массив знаков Юникода.
public: cli::array ^ ToCharArray(int startIndex, int length);
public char[] ToCharArray (int startIndex, int length);
member this.ToCharArray : int * int -> char[]
Public Function ToCharArray (startIndex As Integer, length As Integer) As Char()
Параметры
startIndex Int32
Начальная позиция подстроки в данном экземпляре.
Длина подстроки в данном экземпляре.
Возвращаемое значение
Массив знаков Юникода, элементами которого являются length знаков данного экземпляра начиная с позиции startIndex .
Исключения
Значение параметра startIndex или length меньше нуля.
startIndex + length больше длины этого экземпляра.
Примеры
В следующем примере подстрока в строке преобразуется в массив символов, а затем перечисляется и отображается элемент массива.
// Sample for String::ToCharArray(Int32, Int32) using namespace System; using namespace System::Collections; int main() < String^ str = "012wxyz789"; array^arr; arr = str->ToCharArray( 3, 4 ); Console::Write( "The letters in '' are: '", str ); Console::Write( arr ); Console::WriteLine( "'" ); Console::WriteLine( "Each letter in '' is:", str ); IEnumerator^ myEnum = arr->GetEnumerator(); while ( myEnum->MoveNext() ) < Char c = safe_cast(myEnum->Current); Console::WriteLine( c ); > > /* This example produces the following results: The letters in '012wxyz789' are: 'wxyz' Each letter in '012wxyz789' is: w x y z */
// Sample for String.ToCharArray(Int32, Int32) using System; class Sample < public static void Main() < string str = "012wxyz789"; char[] arr; arr = str.ToCharArray(3, 4); Console.Write("The letters in '' are: '", str); Console.Write(arr); Console.WriteLine("'"); Console.WriteLine("Each letter in '' is:", str); foreach (char c in arr) Console.WriteLine(c); > > /* This example produces the following results: The letters in '012wxyz789' are: 'wxyz' Each letter in '012wxyz789' is: w x y z */
// Sample for String.ToCharArray(Int32, Int32) let str = "012wxyz789" let arr = str.ToCharArray(3, 4) printf $"The letters in '' are: '" printf $"" printfn "'" printfn $"Each letter in '' is:" for c in arr do printfn $"" (* This example produces the following results: The letters in '012wxyz789' are: 'wxyz' Each letter in '012wxyz789' is: w x y z *)
' Sample for String.ToCharArray(Int32, Int32) Class Sample Public Shared Sub Main() Dim str As String = "012wxyz789" Dim arr() As Char arr = str.ToCharArray(3, 4) Console.Write("The letters in '' are: '", str) Console.Write(arr) Console.WriteLine("'") Console.WriteLine("Each letter in '' is:", str) Dim c As Char For Each c In arr Console.WriteLine(c) Next c End Sub End Class ' 'This example produces the following results: 'The letters in '012wxyz789' are: 'wxyz' 'Each letter in '012wxyz789' is: 'w 'x 'y 'z '
Комментарии
Этот метод копирует символы в части строки в массив символов. Чтобы создать строку из диапазона символов в массиве символов, вызовите String(Char[], Int32, Int32) конструктор .
Параметр startIndex отсчитывается от нуля. То есть индекс первого символа в экземпляре строки равен нулю.
Если length значение равно нулю, возвращаемый массив пуст и имеет нулевую длину. Если этот экземпляр имеет null значение или пустая строка («»), возвращаемый массив пуст и имеет нулевую длину.
Чтобы создать массив байтов, содержащий закодированные символы в части строки, создайте экземпляр соответствующего Encoding объекта и вызовите его GetBytes(String, Int32, Int32, Byte[], Int32) метод . Ниже перечислены некоторые стандартные кодировки, доступные в .NET.
| Кодирование | Объект |
|---|---|
| ASCII | ASCIIEncoding |
| UTF-7 | UTF7Encoding |
| UTF-8 | UTF8Encoding |
| UTF-16 | UnicodeEncoding |
| UTF-32 | UTF32Encoding |
Как строку занести в массив c
Напишите программу, которая объединяет и распечатывает две строки, введенные с терминала. Для ввода строк используйте функцию gets(), а для их объединения strcat(). В другом варианте используйте sprintf(result,»%s%s«,s1,s2);
2.21.
Модифицируйте предыдущую программу таким образом, чтобы она выдавала длину (число символов) объединенной строки. Используйте функцию strlen(). Приведем несколько версий реализации strlen:
/* При помощи индексации массива */ int strlen(s) char s[]; < int length = 0; for(; s[length] != '\0'; length++); return (length); >/* При помощи продвижения указателя */ int strlen(s) char *s; < int length; for(length=0; *s; length++, s++); return length; >/* При помощи разности указателей */ int strlen(register char *s) < register char *p = s; while(*p) p++; /* ищет конец строки */ return (p - s); >
Разность двух указателей на один и тот же тип — целое число:
если TYPE *p1, *p2; то p2 - p1 = целое число штук TYPE лежащих между p2 и p1 если p2 = p1 + n то p2 - p1 = n
Эта разность может быть и отрицательной если p2 < p1, то есть p2 указывает на более левый элемент массива.
2.22.
Напишите оператор Си, который обрубает строку s до длины n букв.
if( strlen(s) > n ) s[n] = '\0';
Первое сравнение вообще говоря излишне. Оно написано лишь на тот случай, если строка s короче, чем n букв и хранится в массиве, который также короче n, т.е. не имеет nого элемента (поэтому в него нельзя производить запись признака конца).
2.23.
Напишите функции преобразования строки, содержащей изображение целого числа, в само это число. В двух разных вариантах аргумент-адрес должен указывать на первый байт строки; на последний байт. Ответ:
#define isdigit(c) ('0' atoi(s) register char *s; < register int res=0, neg=0; for(;;s++)< switch(*s)< case ' ': case '\t': continue; case '-': neg++; case '+': s++; >break; > while(isdigit(*s)) res = res * 10 + *s++ - '0'; return( neg ? -res : res ); > int backatoi(s) register char *s; < int res=0, pow=1; while(isdigit(*s))< res += (*s-- - '0') * pow; pow *= 10; >if(*s == '-') res = -res; return res; >
2.24.
Можно ли для занесения в массив s строки «hello» написать
char s[6]; s = "hello"; или char s[6], d[] = "hello"; s = d;
Ответ: нет. Массивы в Си нельзя присваивать целиком. Для пересылки массива байт надо использовать функцию strcpy(s,d). Здесь же мы пытаемся изменить адрес s (имя массива — это адрес начала памяти, выделенной для хранения массива), сделав его равным адресу безымянной строки «hello» (или массива d во втором случае). Этот адрес является константой и не может быть изменен!
Заметим однако, что описание массива с инициализацией вполне допустимо:
char s[6] = "hello";
char s[6] = < 'h', 'e', 'l', 'l', 'o', '\0' >;
char s[] = "hello";
char s[] = < "hello" >;
В этом случае компилятор резервирует память для хранения массива и расписывает ее байтами начального значения. Обратите внимание, что строка в двойных кавычках (если ее рассматривать как массив букв) имеет длину на единицу больше, чем написано букв в строке, поскольку в конце массива находится символ ‘\0‘ — признак конца, добавленный компилятором. Если бы мы написали
char s[5] = "hello";
то компилятор сообщил бы об ошибке, поскольку длины массива (5) недостаточно, чтобы разместить 6 байт. В третьей строке примера написано s[], чтобы компилятор сам посчитал необходимую длину массива.
Наконец, возможна ситуация, когда массив больше, чем хранящаяся в нем строка. Тогда «лишнее» место содержит какой-то мусор (в static-памяти изначально — байты \0).
char s[12] = "hello"; содержит: h e l l o \0 ? ? ? ? ? ?
В программах текстовой обработки под «длиной строки» обычно понимают количество букв в строке НЕ считая закрывающий байт ‘\0‘. Именно такую длину считает стандартная функция strlen(s). Поэтому следует различать такие понятия как «(текущая) длина строки» и «длина массива, в котором хранится строка«: sizeof(s). Для написанного выше примера эти значения равны соответственно 5 и 12.
Следует также отличать массивы от указателей:
char *sp = "bye bye"; sp = "hello";
будет вполне законно, поскольку в данном случае sp — не имя массива (т.е. константа, равная адресу начала массива), а указатель (переменная, хранящая адрес некоторой области памяти). Поскольку указатель — это переменная, то ее значение изменять можно: в данном случае sp сначала содержала адрес безымянного массива, в котором находится «bye bye»; затем мы занесли в sp адрес безымянного массива, хранящего строку «hello». Здесь не происходит копирования массива, а происходит просто присваивание переменной sp нового значения адреса.
Предостережем от возможной неприятности:
char d[5]; char s[] = "abcdefgh"; strcpy(d, s);
Длины массива d просто не хватит для хранения такой длинной строки. Поскольку это ничем не контролируется (ни компилятором, ни самой strcpy, ни вами явным образом), то при копировании строки «избыточные» байты запишутся после массива d поверх других данных, которые будут испорчены. Это приведет к непредсказуемым эффектам.
Некоторые возможности для контроля за длиной строк-аргументов вам дают функции
strncpy(d,s,len); strncat(d,s,len); strncmp(s1,s2,len).
Они пересылают (сравнивают) не более, чем len первых символов строки s (строк s1, s2). Посмотрите в документацию! Напишите функцию strncmp (сравнение строк по первым len символам), посмотрев на функцию strncpy:
char *strncpy(dst, src, n) register char *dst, *src; register int n; < char *save; for(save=dst; --n >= 0; ) if( !(*dst++ = *src++))< while(--n >= 0) *dst++ = '\0'; return save; > return save; >
int strncmp(register char *s1, register char *s2, register int n) < if(s1 == s2) return(0); while(--n >= 0 && *s1 == *s2++) if(*s1++ == '\0') return(0); return((n < 0)? 0: (*s1 - *--s2)); >
2.25.
В чем ошибка?
#include stdio.h> /* для putchar */ char s[] = "We don't need no education"; main()< while(*s) putchar(*s++); >
Ответ: здесь s — константа, к ней неприменима операция ++. Надо написать
char *s = "We don't need no education";
сделав s указателем на безымянный маccив. Указатель уже можно изменять.
2.26.
Какие из приведенных конструкций обозначают одно и то же?
char a[] = ""; /* пустая строка */ char b[] = "\0"; char c = '\0'; char z[] = "ab"; char aa[] = < '\0' >; char bb[] = < '\0', '\0' >; char xx[] = < 'a', 'b' >; char zz[] = < 'a', 'b', '\0' >; char *ptr = "ab";
2.27.
Найдите ошибки в описании символьной строки:
main() < char mas[] = ; /* "sort" ? */ printf("%s\n", mas); >
Ответ: строка должна кончаться ‘\0’ (в нашем случае printf не обнаружив символа конца строки будет выдавать и байты, находящиеся в памяти после массива mas, т.е. мусор); инициализированный массив не может быть автоматическим — требуется static:
main() < static char mas[] = ; >
Заметим, что
main()
законно, т.к. сама строка здесь хранится в статической памяти, а инициализируется лишь указатель на этот массив байт.
2.28.
В чем ошибка? Программа собирается из двух файлов: a.c и b.c командой
cc a.c b.c -o ab a.c b.c -------------------------------------------------- int n = 2; extern int n; char s[] = "012345678"; extern char *s; main() < f()< f(); s[n] = '+'; printf("%s\n", s ); >>
Ответ: дело в том, что типы (char *) — указатель, и char[] — массив, означают одно и то же только при объявлении формального параметра функции:
f(char *arg) <. >f(char arg[])
это будет локальная переменная, содержащая указатель на char (т.е. адрес некоторого байта в памяти). Внутри функции мы можем изменять эту переменную, например arg++. Далее, и (char *) и char[] одинаково используются, например, оба эти типа можно индексировать: arg[i]. Но вне функций они объявляют разные объекты! Так char *p; это скалярная переменная, хранящая адрес (указатель):
-------- ------ p:| *--|----->| '0' | char -------- | '1' | char .
тогда как char a[20]; это адрес начала массива (а вовсе не переменная):
------ a:| '0' | char | '1' | char .
В нашем примере в файле b.c мы объявили внешний массив s как переменную. В результате компилятор будет интерпретировать начало массива s как переменную, содержащую указатель на char.
------ s:| '0' | \ это будет воспринято как | '1' | / адрес других данных. | '2' | .
И индексироваться будет уже ЭТОТ адрес! Результат — обращение по несуществующему адресу. То, что написано у нас, эквивалентно
char s[] = "012345678"; char **ss = s; /* s - как бы "массив указателей" */ /* первые байты s интерпретируются как указатель: */ char *p = ss[0]; p[2] = '+';
Мы же должны были объявить в b.c
extern char s[]; /* размер указывать не требуется */
Вот еще один аналогичный пример, который пояснит вам, что происходит (а заодно покажет порядок байтов в long). Пример выполнялся на IBM PC 80386, на которой
sizeof(char *) = sizeof(long) = 4 a.c b.c -------------------------------------------------- char s[20] = ; extern char *s; main() < f()< /*печать указателя как long */ f(); printf( "%08lX\n", s ); >>
печатается 04030201.
2.29.
Что напечатает программа?
static char str1[ ] = "abc"; static char str2[4]; strcpy( str2, str1 ); /* можно ли написать str2 = str1; ? */ printf( str1 == str2 ? "равно":"не равно" );
Как надо правильно сравнивать строки? Что на самом деле сравнивается в данном примере?
Ответ: сравниваются адреса массивов, хранящих строки. Так
char str1[2]; char str2[2]; main()< printf( str1 < str2 ? ""); >
печатает , а если написать
char str2[2]; char str1[2];
то напечатается >.
2.30.
Напишите программу, спрашивающую ваше имя до тех пор, пока вы его правильно не введете. Для сравнения строк используйте функцию strcmp() (ее реализация есть в главе «Мобильность»).
2.31.
Какие значения возвращает функция strcmp() в следующей программе?
#include main() < printf("%d\n", strcmp("abc", "abc")); /* 0 */ printf("%d\n", strcmp("ab" , "abc")); /* -99 */ printf("%d\n", strcmp("abd", "abc")); /* 1 */ printf("%d\n", strcmp("abc", "abd")); /* -1 */ printf("%d\n", strcmp("abc", "abe")); /* -2 */ >
2.32.
В качестве итога предыдущих задач: помните, что в Си строки (а не адреса) надо сравнивать как
if( strcmp("abc", "bcd") < 0) . ; if( strcmp("abc", "bcd") == 0) . ; вместо if( "abc" < "bcd" ) . ; if( "abc" == "bcd" ) . ;
и присваивать как
char d[80], s[80]; strcpy( d, s ); вместо d = s;
2.33.
Напишите программу, которая сортирует по алфавиту и печатает следующие ключевые слова языка Си:
int char double long for while if
2.34.
Вопрос не совсем про строки, скорее про цикл: чем плоха конструкция?
char s[] = "You're a smart boy, now shut up."; int i, len; for(i=0; i < strlen(s); i++) putchar(s[i]);
Ответ: в соответствии с семантикой Си цикл развернется примерно в
i=0; LOOP: if( !(i < strlen(s))) goto ENDLOOP; putchar(s[i]); i++; goto LOOP; ENDLOOP: ;
Заметьте, что хотя длина строки s не меняется, strlen(s) вычисляется на КАЖДОЙ итерации цикла, совершая лишнюю работу! Борьба с этим такова:
for(i=0, len=strlen(s); i < len; i++ ) putchar(s[i]);
for(i=0, len=strlen(s); len > 0; i++, --len ) putchar(s[i]);
Аналогично, в цикле
while( i < strlen(s)). ;
функция тоже будет вычисляться при каждой проверке условия! Это, конечно, относится к любой функции, используемой в условии, а не только к strlen. (Но, разумеется, случай когда функция возвращает признак "надо ли продолжать цикл" - совсем другое дело: такая функция обязана вычисляться каждый раз).
2.35.
Что напечатает следующая программа?
#include main()
2.36.
Что напечатает следующая программа?
main() < static char name[] = "Константин"; char *pt; pt = name + strlen(name); while(--pt >= name) puts(pt); >
2.37.
Что напечатает следующая программа?
char str1[] = "abcdef"; char str2[] = "xyz"; main()
str=xyzdef a=def str=xxyzef a=zef
2.38.
Что печатает программа?
char *s; for(s = "Ситроен"; *s; s+= 2) < putchar(s[0]); if(!s[1]) break; >putchar('\n');
2.39.
Что напечатает программа? Рассмотрите продвижение указателя s, указателей элементов массива strs[]. Разберитесь с порядком выполнения операций. В каких случаях ++ изменяет указатель, а в каких - букву в строке? Нарисуйте себе картинку, изображающую состояние указателей - она поможет вам распутать эти спагетти. Уделите разбору этого примера достаточное время!
#include /* определение NULL */ /* Латинский алфавит: abcdefghijklmnopqrstuvwxyz */ char *strs[] = < "abcd","ABCD","0fpx","159", "hello","-gop","A1479",NULL >; main()
Печатается:
#1 0 b bcd strs[0]="bcd" #2 1 A ABCD strs[1]="ABCD" #3 2 A 0fpx strs[2]="px" #4 2 1 1fpx strs[3]="69" #5 2 1 2fpx strs[4]="hello" #6 2 g gpx strs[5]="iop" #7 3 p 159 px strs[6]="89" #8 4 6 hello 69 #9 5 h hop #10 6 i A1479 #11 6 B 1479 1479 B1479 #12 6 2 479 479 #13 6 7 89 89
Учтите, что конструкция
char *strs[1] = < "hello" >;
означает, что в strs[0] содержится указатель на начальный байт безымянного массива, содержащего строку "hello". Этот указатель можно изменять! Попробуйте составить еще подобные примеры из *, ++, ().
© Copyright А. Богатырев, 1992-95
Си в UNIX