Заметки по книге - Си


Структуры

Структура - это совокупность нескольких переменных, часто различных типов, сгруппированных под единым именем для удобства обращения.

Пример создания структуры


struct point {
    int x;
    int y;
};

Обьявление структуры, после которой нет списка переменных, не выделяет никакой памяти для объектов, а просто описывает форму или “шаблон” структуры.

Использование структуры с начальными значениями


struct point {
    int x;
    int y;
};

int main (){

    struct point pt = { 100, 200 };
    printf("x = %d; y = %d\n", pt.x, pt.y);

    return 0;
}

...
x = 100; y = 200
...

Тоже самое только при объявлении структуры создается переменная


struct point {
    int x;
    int y;
} pt;

int main (){

    pt.x = 10;
    pt.y = 20;
    printf("x = %d; y = %d\n", pt.x, pt.y);

    return 0;
}
...
x = 10; y = 20
...

Анонимная структура. Всегда должна иметь одну или несколько обьектов, иначе не имеет смысл


struct {
    int x;
    int y;
} point1, pt2;

int main (){

    point.x = 10;
    point.y = 20;
    printf("x = %d; y = %d\n", point.x, point.y);

    return 0;
}

Пример вложеных структур


struct point {
    int x;
    int y;
};

struct rect {
    struct point pt1;
    struct point pt2;
};

int main (){

    struct rect screen = { {0,0}, {100,200} };
    printf("x1 = %d; y1 = %d\nx2 = %d; y2 = %d\n", screen.pt1.x, screen.pt1.y, screen.pt2.x, screen.pt2.y);

    return 0;
}
...
x1 = 0; y1 = 0
x2 = 100; y2 = 200
...

Структуры можно

  • копирование или присваивание как целого
  • взятие адреса &
  • обращения к элементам

Указатели на структуры


struct point {
    int x;
    int y;
};

int main (){

    struct point pt, *pp = &pt;

    return 0;
}

Если pp указывает на структуру point, то *pp - эта сама структура, а (*pp).x и (*pp).y эл. структуры.
Пусть pp - указатель на структуру, тогда ссылка на эл. структуры выполняется след. образом

pp->x, тоже самое что и (*pp).x

Пусть имеется структура


struct {
    int len;
    char *str;
} str1, *p = &str1;

int main (){

    p->len = 6;
    p->str = "hello";

    char c;
    while ( ( c = *p->str++ ) != '\0'){
        printf("%c-", c );
    }

    return 0;
}

Определение новых типов aka “Псевдоним”

Создание псевдонима


int main(){
    typedef int integer;
    integer n = 10;
    printf("%d\n", n);
}
...
10
...

Все что будет находиться между “typedef” и последним словом, для наглядности начинающегося с большой буквы, будет использоваться как ссылка. Удобно в структурах


typedef struct tnode {
    char *word;
    int count;
    struct tnode *left;
    struct tnode *right;
} Treenode;

Treenode *add_tree(Treenode *, char *);

Указатель на функцию от двух аргументов типа char *, возвращающую int

Используется как указатель на функцию


TODO:

Объеднинения

Объединение = это переменная, которая может содержать объекты различных типов и размеров (но не одновременно).


union myunion {
    int integer;
    float flot;
} uvar;

int main(){
    uvar.flot = 10.1;
    uvar.integer = 10;
    union myunion *ptr = &apm;uvar;
    printf("%d; %f\n", ptr->integer, ptr->flot);
}

...
10; 0.000000
Для закрытия данного окна нажмите ВВОД...
...

Извлекать данные можно только того типа, какие были помещенены при последнем обращении к переменной

##############################################

Упражнения

Упражнение 2.1. Напишите программу для определения диапазонов переменных типов char, short, int и long (как signed так и unsigned), путем вывода соответствующих значений из заголовочных файлов, а также с помощью непостредственного вычисления.

Упражнение 2.2. Напишите цикл эквивалентный, не используя операции && и ||

Упражнение 2.3. Напишите функцию htoi(s), которая преобразует строку шестьнадцатеричных цифр (учитывая необязательные элементы 0x или 0X) в ее целочисленный эквивалент. В число допустимых цифр входят десятичные цифры от 0 до 9, а также буквы a-f и A-F.


int htoi(char []);

int main(){

    char str[] = "0xabcdef0";

    if (0){ // debug
        int number = (int)strtol(str, NULL, 16);
        int res = htoi(str);
        printf("result = %d; number = %d\n", res, number);
    } else {
        int res = htoi(str);
        printf("Исходная строка = '%s'; целое число = %d\n", str, res);
    }
    
    return 0;
}

// преобразует строку шестьнадцатеричных цифр в целое число
int htoi(char s[]){
    int i, n = 0;

    if ( !(s[0] == '0' && ( s[1] == 'x' || s[1] == 'X')) )
        return 0;

    for ( i = 2; s[i] != '\0'; i++ ){
        if ( s[i] >= '0' && s[i] <= '9' ){
            n = n * 16 + ( s[i] - '0' );
        } else if (( s[i] >= 'a' && s[i] <= 'f' ) ){
            n = n * 16 + ( s[i] - 'a' + 10);
        } else if ( s[i] >= 'A' && s[i] <= 'F' ){
            n = n * 16 + ( s[i] - 'A' + 10);
        }
    }

    return n;
}

...
Исходная строка = '0xabcdef0'; целое число = 180150000
[Finished in 0.1s]
...

Упражнение 2.4. Напишите альтернативную версию функции squeeze(s1, s2), которая бы удаляла из строки s1 все символы, встречающиеся в строке s2


// Удаляет символы из s1 все символы из строки s2
void my_squeeze( char s1[], char s2[] );

int main(){
    char str1[] = "hello, world!";
    char str2[] = "hwo";
    my_squeeze(str1, str2);
    printf("Result = '%s'\n", str1);
    return 0;
}

void my_squeeze( char s1[], char s2[] ){
    int i, j, k;
    for ( i = 0; s2[i] != '\0'; i++ ){
        for ( j = k = 0; s1[j] != '\0'; j++ ){
            if ( s1[j] != s2[i] ){
                s1[k++] = s1[j];
            }
        }
        s1[k] = '\0';
    }
}

...
Result = 'ell, rld!'
[Finished in 0.2s]
...

Упражнение 2.5. Напишите функцию any(s1, s2), возвращаюшаую номер первой позиции в строке s1, в которой находиться какой-либо из символов строки s2, либо -1, если строка s1 не содержит ни одного символа из s2.


// Возвращает позицию первого символа s1 из строки s2
int any( char s1[], char s2[] );

int main(){
    char str[] = "hello, world!";
    char any_simbols[] = "sa45sae";
    int res = any(str, any_simbols);
    printf("%d", res);
    return 0;
}

int any( char s1[], char s2[] ){

    for (int i = 0; s2[i] != '\0'; i++){
        for (int j = 0; s1[j] != '\0'; j++){
            if ( s1[j] == s2[i] )
                return j;
        }
    }
    return -1;
}

...
1[Finished in 0.1s]
...

Упражнение 2.10. Перепишите функцию lower, которая преобразует буквы в верхнем регистре к нижнему, с использованием условного выражения вместо конструкции if-else


char lower(char);

int main(){
    char old_char = 'G';
    char new_char = lower(old_char);
    printf("%c\n", new_char);
    return 0;
}

// преобразует символ в нижний регистр
char lower(char c){

    if (c  >= 'A' && c <= 'Z')
        return c + 'a' - 'A';

    return c;
}

Упражнение 3.1. В нашем двоичном поиске каждый цикл содержит две проверки, тогда как достаточно было бы одной ( зато их бы потребовалось бы больше снаружи цикла). Напишите версию функции с одной проверкой внутри цикла и сравните быстродействие.

Упражнение 3.2. Напишите функцию под именем escape(s, t), которая бы преобразовывала символы наподобие конца строки и табуляции в управляющие последовательности языка C, такие как \n \t в процессе копирования строки t в строку s.


void escape( char s[], char t[] );

int main(){

    char str1[20] = "Hello ";
    char str2[20] = "wo\n\t";

    escape(str1, str2);
    printf("%s\n", str1);

    return 0;
}

void escape( char s[], char t[] ){

    int i, j;
    char c;
    i = j = 0;
    while ( s[++i] != '\0' );
    while ( ( c = t[j++] ) != '\0' ){
        switch (c){
            case '\n':
                s[i++] = '\\';
                s[i++] = 'n';
                break;
            case '\t':
                s[i++] = '\\';
                s[i++] = 't';
                break;
            default:
                s[i++] = c;
        }
    }
}

...
Hello wo\n\t
[Finished in 0.1s]
...

Упражнение 3.3. Напишите функцию expand(s1, s2), которая бы разворачивала сокращенную запись наподобие a-z в строке s1 в полный список abc…xyz в строку s2

Упражнение 3.5. Напишите функцию itob(n, s, b) которая бы преобразовывала целое число n в его символьное представление в системе счисления с основанием b и помещала результат в строку s. Например вызов itob(n, s, 16) должен представлять n в виде 16-ричного числа.

Упражнение 3.6. Напишите версию itoa принимающую три аргумента вместо двух. Третий аргумент будет минимальной шириной поля; преобразованное число следует дополнить пробелами слева, если оно недостаточно длинное, чтобы заполнить это поле.

Упражнение 4.1. Напишите функцию strindex(s, t), которая бы возвращала индекс самого правого вхождения строки t в s, либо -1, если такой строки в s нет

Упражнение 4.3. При наличии базовой структуры программы усовершенстрование калькулятора уже не представляет особых трудностей. Реализуйте операцию взятия остатка (%) и работу с отрицательными числами.

Упражнение 4.4. Добавте в программу реализацию команды для вывода верхнего элемента стека без его удаления, для создания в стеке дубликата этого элемента и для обмена местами двух верхних элементов. Также реализуйте команду очистки стека.

Упражнение 4.5. Добавьте реализацию библиотечных математических функций sin, exp и pow.

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

Упражнение 4.7. Напишите функцию ungets(s) возвращающую в поток целую строку символов.

Упражнение 4.8. 4.9. 4.10. - пффф

Глава 1. Вводный курс


$ cat hello.c
#include <stdio.h\>

int main (){
    printf("hello, world!\n");
    return 0;
}

$ cc hello.c
$ ./a.out 
hello, world!

$ cat fat.c
#include <stdio.h\>

// Вывод таблицы температур по Фаренгейту и Цельсию

int main (){
    float fahr, celsius;
    int lower, upper, step;

    lower = 0;      // нижняя граница температур
    upper = 300;    // верняя граница температур
    step = 20;      // величина шага

    while ( fahr <= upper ){
        celsius = (5.0/9.0) * (fahr - 32.0); // c = (5/9)(f - 32)
        printf("%3.0f %6.2f\n", fahr, celsius);
        fahr = fahr + step;
    }

    return 0;
}

$ ./a.out 
...
  0 -17.78
 20  -6.67
 40   4.44
 60  15.56
 80  26.67
100  37.78
120  48.89
140  60.00
160  71.11
180  82.22
200  93.33
220 104.44
240 115.56
260 126.67
280 137.78
300 148.89
...
  • %3.0f - вывести вещественое число в поле шириной не менее 3 символа без десятичной части
  • %6.2f - вывести вещественое число в поле шириной не менее 6 символа и вывод двух цифр после точки

$ cat fatv2.c

// Вывод таблицы температур по Фаренгейту и Цельсию (v2)
int main (){
    float fahr;

    for ( fahr = 0; fahr <= 300; fahr += 20 )
        printf("%3.0f %6.2f\n", fahr, (5.0/9.0)*(fahr-32));

    return 0;
}

$ ./a.out
...
  0 -17.78
 20  -6.67
 40   4.44
 60  15.56
 80  26.67
100  37.78
...

Упражнение 1.5. Доработайте программу преобразования температур так, чтобы она выводила таблицу в обратном порядке, т.е. от 300 градусов до нуля


$ cat fatv3.c

// Вывод таблицы температур по Фаренгейту и Цельсию (v3)
int main (){
    float fahr;

    for ( fahr = 300; fahr >= 0; fahr -= 20 )
        printf("%3.0f %6.2f\n", fahr, (5.0/9.0)*(fahr-32));

    return 0;
}


$ ./a.out
...
300 148.89
280 137.78
260 126.67
240 115.56
220 104.44
200  93.33
...

$ cat fatv4.c

#define LOWER 0
#define UPPER 300
#define STEP 20

// Вывод таблицы температур по Фаренгейту и Цельсию (v4)
int main (){
    float fahr;

    for ( fahr = LOWER; fahr <= UPPER; fahr += STEP )
        printf("%3.0f %6.2f\n", fahr, (5.0/9.0)*(fahr-32));

    return 0;
}


$ ./a.out
...
  0 -17.78
 20  -6.67
 40   4.44
 60  15.56
 80  26.67
100  37.78
...

1.5.1 Копирование файлов


$ cat get-putchar.c

// копирование входного потока в выходной
int main(){
    char c;
    
    while ( (c = getchar()) != EOF )
        putchar(c);

    return 0;
}

$ ./a.out 
...
1
1
2
2
g
g
...

Упражнение 1.6. Проверьте, что выражение getchar() != EOF действительно равно 1 или 0;


while ( (c = getchar()) != EOF ){
        printf("%d\n", getchar() != EOF);
        putchar(c);
    }

$ ./a.out 
...
e
1
eq
1
qd
1
...

PS. В конце файла EOF будет 1 т.е. true

Упражнение 1.7. Напишите программу вывода значения константы EOF


int main(){
    printf("%d\n", EOF);
    printf("%d\n", getchar());
    return 0;
}

$ ./a.out
...
-1
1
49
...

1.5.2 Подсчет символов


$ cat count_char.c

// подсчет кол-ва символов в входном потоке
int main(){

    long count = 0;
    while ( getchar() != EOF )
        ++count;
    printf("%ld", count);

    return 0;
}

$ ./a.out 
...
1
2
3
4
...

PS символ перевода строки тоже считается

1.5.2 Подсчет строк


$ cat count_str.c

// подсчет кол-ва строк в входном потоке
int main(){

    long count = 0;
    char c;

    while ( (c = getchar()) != EOF )
        if ( c == '\n' )
            ++count;

    printf("%ld\n", count);

    return 0;
}

$ ./a.out 
...
hello 
world
2
...

Упражнение 1.8. Напишите программу для подчета пробелов, знаков табуляции и символов конца строки


$ cat count_spaces.c

// подсчет кол-ва пробелов, знаков табуляции и символов конца строки
int main(){

    long count_spaces = 0, count_tabs = 0, count_endstr = 0;
    char c;

    while ( (c = getchar()) != EOF )
        if ( c == ' ' ){
            ++count_spaces;
        } else if ( c == '\t' ){
            ++count_tabs;
        } else if ( c == '\0' ){
            ++count_endstr;
        }

    printf("Кол-во пробелов = %ld; Кол-во табуляций = %ld; Кол-во символов конца строки = %ld\n", count_spaces, count_tabs, count_endstr);

    return 0;
}

...
$ ./a.out 
dsa
    
e
 
 dsa
Кол-во пробелов = 2; Кол-во табуляций = 1; Кол-во символов конца строки 0
...

Упражнение 1.9. Напишите программу для копирования входного потока в выходной с заменой строки состоящей из одного или нескольких пробелов, одним пробелом


$ cat replace_spaces.c

int main(){

    int is_last_char_space = 0;
    char c;
    while ( (c = getchar()) != EOF ){
        if ( c == ' ' ){
            if ( !is_last_char_space ) putchar(' ');
            is_last_char_space = 1;
        } else {
            putchar(c);
            is_last_char_space = 0;
        }
    }

    return 0;
}

$ ./a.out 
...
help   mee   
help mee 
help mee   mm  ee  
help mee mm ee 
...

Упражнение 1.10. Напишите программу для копирования входного потока в выходной с заменой знаков табуляции \t, символа знака назад (backspace) на \b, а косую четрту на \.


$ cat replace_symbols.c

int main(){

    char c;
    while ( (c = getchar()) != EOF ){
        if ( c == '\t' ){
            printf("\\t");
        } else if (c == '\b'){
            printf("\\b");
        } else if (c == '\\'){
            printf("\\\\");
        } else {
            putchar(c);
        }
    }

    return 0;
}

$ ./a.out 
...
test    help    me \
test\thelp\tme \\
...

1.5.4 Подсчет строк


$ cat count_words.c

#define IN 1
#define OUT 0

// подсчет строк, слов, и символов во входном потоке

int main(){

    int count_lines, count_words, count_chars, state;
    char c;

    state = OUT;
    count_lines = count_words = count_chars = 0;

    while ( (c = getchar()) != EOF ){
        ++count_chars;
        if ( c == '\n' ) ++count_lines;
        if ( c == ' ' || c == '\n' || c == '\t' ){
            state = OUT;
        } else if ( state == OUT ){
            state = IN;
            ++count_words;
        }
    }

    printf("%d %d %d\n", count_lines, count_words, count_chars);

    return 0;
}


$ wc input.txt 
...
  5  69 446 input.txt
...
$ ./a.out < input.txt 
...
5 69 446
...

Упражнение 1.12. Напишите программу для вывода входного потока по одному слову в строке


$ cat print_in_new_line.c

int main(){

    char c;

    while ( (c = getchar()) != EOF )
        if ( c != '\n' ) printf("%c\n", c);

    return 0;
}

$ ./a.out 
...
help
h
e
l
p
hello
h
e
l
l
o
^C
...

1.6 Массивы


$ cat count_digits.c

// подсчет кол-ва цифр, пробелов, и остальных
int main(){

    int i, count_spaces, count_other;
    char c;
    int ndigit[10];

    count_spaces = count_other = 0;
    for (int i = 0; i < 10; ++i)
        ndigit[i] = 0;

    while ( (c = getchar()) != EOF ){
        if ( c >= '0' && c <= '9' )
            ++ndigit[c - '0'];
        else if ( c == ' ' || c == '\n' || c == '\t' )
            ++count_spaces;
        else
            ++count_other;
    }

    printf("digits = ");
    for (int i = 0; i < 10; ++i)
        printf(" %d", ndigit[i]);

    printf(", spaces = %d, other = %d\n", count_spaces, count_other);

    return 0;
}

$ ./a.out < count_digits.c 
...
digits =  10 3 0 0 0 0 0 0 0 1, spaces = 231, other = 447
...

Упражнение 1.13. Напишите программу для вывода гистограммы длин слов во входном потоке. Построить гистограмму с горизонтальными рядами довольно легко, а вот с вертивальными столбцами труднее.


$ cat print_histogram.v1.c

#define IN 1
#define OUT 1

int main(){

    int i, j, state, count = 0;
    char c;
    int words[100];// = {0, 1, 13, 5, 9, 12, 7, 9, 3, 5};

    state = OUT;

    for (int i = 0; i < 10; ++i)
        words[i] = 0;

    while ( (c = getchar()) != EOF ){

        if ( c == ' ' || c == '\n' || c == '\t' ){
            state = OUT;
            ++words[count];
            count = 0;
        } else if ( state == OUT ){
            state = IN;
            ++count;
        }
    }

    ++words[count];

    for (int i = 1; i < 10; ++i){
        printf("%d", i);
        for (j = 0; j < words[i]; j++)
            putchar('-');
        putchar('\n');
    }

    return 0;
}

$ ./a.out < input.txt 
...
1-
2-------------
3-----
4---------
5------------
6-------
7---------
8---
9-----
...
$ ./a.out < cel.c 
...
1----------
2----------
3-----
4-----
5--------
6----
7---
8----
9--
...

Упражнение 1.14. Напишите программу для вывода гистограммы частот, с которыми встречаются во входном потоке различные символы.


$ cat print_histogram.v3.c
#define N 1000

void print_result(char [], int);

int main(){

    char chars[N], c;

    for (int i = 0; i < N; i++) chars[i] = 0;

    while ( (c = getchar()) != EOF )
        ++chars[c];

    print_result(chars, N);

    return 0;
}

void print_result(char arr[], int n){

    for (int i = 0; i < n; i++)
        if ( arr[i] ){
            printf("%c", i);
            for (int j = 0; j < arr[i]; j++)
                putchar('-');
            putchar('\n');
        }
}

$ ./a.out < print_histogram.v3.c 
...
----------------------------
 
!-
"--
#--
%-
'----
(--------------
)--------------
+--------
,-----
--
.-
0--------
1-
;---------------
<----
...

1.7 Функции


$ cat power.c

int power (int, int);

// тестирование power - функции возведения в степень
int main (){

    for(int i = 0; i < 10; i++)
        printf("%d %d %d\n", i, power(2, i), power(-3, i));

    return 0;
}

// power: возводит base в n-ую степень; n >= 0
int power (int base, int n){
    int p = 1;
    for (int i = 1; i <= n; i++)
        p *= base;
    return p;
}

$ ./a.out
...
0 1 1
1 2 -3
2 4 9
3 8 -27
4 16 81
5 32 -243
6 64 729
7 128 -2187
8 256 6561
9 512 -19683
...

Упражнение 1.15. Перепешите программу преобразования температур, чтобы само преобразование выполнялось функцией


cat fatv5.c

#define LOWER 0
#define UPPER 300
#define STEP 20

float celsius(float);

// Вывод таблицы температур по Фаренгейту и Цельсию (v5)
int main (){
    float fahr;

    for ( fahr = LOWER; fahr <= UPPER; fahr += STEP )
        printf("%3.0f %6.2f\n", fahr, celsius(fahr));

    return 0;
}

float celsius(float fahr){
    return (5.0/9.0)*(fahr-32);
}

1.9 Массив символов


$ cat longest-str.c

#define N 1000

int get_line(char [], int);
void copy(char [], char []);

int main(){

    int len, max = 0;
    char line[N], longest[N];

    while ((len = get_line(line, N)) > 0 ){
        if ( len > max ){
            max = len;
            copy(line, longest);
        }
    }

    if ( max > 0 )
        printf("%s", longest);
    return 0;
}

int get_line(char str[], int lim){
    int c, i;

    for (i = 0; i <= lim-1 && (c = getchar()) != EOF && c != '\n'; i++ )
        str[i] = c;

    if ( c == '\n' )
        str[i++] = c;

    str[i] = '\0';
    return i;
}

void copy(char from[], char to[]){
    int i = 0;
    while ((to[i] = from[i]) != '\0') i++;
}

$ ./a.out < longest-str.c 
...
    for (i = 0; i <= lim-1 && (c = getchar()) != EOF && c != '\n'; i++ )
...

Упражнение 1.16. Доработайте главный модуль программы, определения самой длинной строки так, чтобы она выводила правильное значение для какой угодно длины строк входного потока, на сколько это позволяет текст

???

Упражнение 1.17. Напишите программу для вывода всех строк входного потока, имеющих длину более 80 символов


$ cat longest-80-characters.c

#define N 1000
#define MAX_LEN 80

int get_line(char [], int);
void copy(char from[], char to[]);

int main(){

    int len, count = 0;
    char line[N], str[N][N];

    while ( ( len = get_line(line, N) ) > 0 ){
        if ( len >= MAX_LEN ){
            copy( line, str[count++] );
        }
    }

    for (int i = 0; i < count; ++i){
        printf("%s", str[i]);
    }

    return 0;
}

int get_line(char str[], int lim){

    int c, i;
    for (i = 0; i <= lim-1 && (c = getchar()) != EOF && c != '\n'; i++ ) str[i] = c;

    if ( c == '\n' ) str[i++] = c;
    str[i] = '\0';
    return i;
}

void copy(char from[], char to[]){
    int i = 0;
    while( ( to[i] = from[i] ) != '0' ) i++;
}

$ ./a.out < longest-80-characters.c
...
    for (i = 0
...

Упражнение 1.18. Напишите программу для удаления лишних пробелов и табуляций в хвосте каждой поступающей строки входного потка, которая бы также удаляла полностью пустые строки

Упражнение 1.19. Напишите функцию reverse(str), которая переписывает свой строковый аргумент str в обратном порядке.

1.10 Внешние переменные


$ cat longest-str.v2.c
#define N 1000

int max = 0;
char line[N], longest[N];

int get_line(void);
void copy(void);

// Вывод самой длинной строки. спец версия
int main(){

    int len;
    extern int max;
    extern char longest[];

    while ((len = get_line()) > 0 ){
        if ( len > max ){
            max = len;
            copy();
        }
    }

    if ( max > 0 )
        printf("%s", longest);
    return 0;
}

int get_line(void){
    int c, i;
    extern char line[];

    for (i = 0; i <= N-1 && (c = getchar()) != EOF && c != '\n'; i++ )
        line[i] = c;

    if ( c == '\n' )
        line[i++] = c;

    line[i] = '\0';
    return i;
}

void copy(void){
    int i = 0;
    extern char line[], longest[];
    while ((longest[i] = line[i]) != '\0') i++;
}

$ ./a.out < longest-str.v2.c
...
// Вывод самой длинной строки. спец версия
...

Использование глобальных переменных оочень плохо!

Упражнение 1.20. Напишите программу detab, которая бы заменяла символы табуляции во входном потоке соответствующим кол-вом пробелов до следующий границы табуляции.


$cat 1.20.change_tabs.c
#define N 100
#define COUNT_TABS 4

int main(){

    char str[N];
    char c;
    int need_tabs = 0;

    while ((c = getchar()) != EOF){

        if ( c == '\t' ){
            if ( !need_tabs ){
                need_tabs = 1;
                for (int i = 0; i < COUNT_TABS; i++){
                    putchar(' ');
                }
            } else {
                need_tabs = 0;
                putchar(c);
            }
        } else {
            putchar(c);
        }
    }

    return 0;
}

$ ./a.out 
...
test    test    test    test
test    test    test    test
...

Упражнение 1.21. Напишите программу entab, которая заменяла пустые строки, состоящие из одних пробелов, строками, содержащими минимальное кол-во табуляций и дополнительных пробелов - так чтобы заполнять то же пространство. Используйте те же параметры табуляции, что и в программе detab. Если для заполнения места до следующей границы табуляции требуется один пробел или один символ табуляции, то что следует предпочесть?

Упражнение 1.22. Напишите программу для сворачивания слишком длинных строк входного потока в две или более короткие строки после последнего не пустого символа, встречающийся после n-ым столбцом длинной строки. Постарайтесь, чтобы ваша программа обрабатывала очень длинные строки корректно, а также удаляла лишние пробелы и табуляции перед указанным столбцом.

Упражнение 1.23. Напишите программу для удаления всех комментариев из программы на C. Позаботьтесь о корректной обработке символьных констант и строк в двойных кавычках. Вложенные комментарии в C не допускаются

Упражнение 1.24. Напишите программу для выполнения примитивной синтаксической проверки программ на C, такие как непарные круглые, квадратные и фигурные скобки. Незабудь о одинарных и двойных кавычках, управляющих символах и комментариях.

Глава 2. Типы данных

Упражнение 2.1. Напишите программу для определения диапазонов переменных типов char, short, int и long (как signed так и unsigned), путем вывода соответствующих значений из заголовочных файлов, а также с помощью непостредственного вычисления.

Константы


#define VAR 1       // константа целого типа
#define CHAR 'a'    // константа символьного типа

// Справочники
enum months {
    JAN = 1,
    FEB
};

int main(){

    printf("%d; %c; %d\n", VAR, CHAR, JAN);

    return 0;
}

$ ./a.out
...
1; a; 1
...

Операции отношения и логические операции

Упражнение 2.2. Напишите цикл эквивалентный, не используя операции && и  

for (int i = 0; i <= N-1 && (c = getchar()) != EOF && c != '\n'; i++ )    

Преобразование типов

atoi - преобразует строку в целое число


int atoi(char s[]);

int main(){

    char str[] = "456";
    int res;
    res = atoi(str);
    printf("%d\n", res);
    return 0;
}


// преобразует строку в целое число
int atoi(char s[]){
    int i, n = 0;
    for (i = 0; s[i] >= '0' && s[i] <= '9'; i++){
        n = n * 10 + (s[i] - '0');
    }
    return n;
}

...
456
[Finished in 0.1s]
...

lower - преобразует символ в нижний регистр


char lower(char);

int main(){
    char old_char = 'G';
    char new_char = lower(old_char);
    printf("%c\n", new_char);
    return 0;
}

// преобразует символ в нижний регистр
char lower(char c){
    if (c  >= 'A' && c <= 'Z')
        return c + 'a' - 'A';

    return c;
}

...
g
[Finished in 0.1s]
...

Инкремент/Декремент


// удаляет все символы c из строки s
void squeeze ( char s[], int c);

int main(){
    char str[] = "hello, world!";
    squeeze(str, 'l');
    printf("%s\n", str);
    return 0;
}

void squeeze ( char s[], int c){
    int i, j;

    for ( i = j = 0; s[i] != '\0'; i++ ){
        if ( s[i] != c ){
            s[j++] = s[i];
        }
    }
    s[j] = '\0';
}

...
heo, word!
[Finished in 0.1s]
...

// Добавляет строку t к строке s
void mystrcat ( char s[], char t[]);

int main(){
    char str1[] = "hello, ";
    char str2[] = "world!";
    mystrcat(str1, str2);
    printf("\n%s\n", str1);
    return 0;
}

void mystrcat ( char s[], char t[]){
    int i, j;
    i = j = 0;
    while ( s[i] != '\0' ) i++;
    while ( ( s[i++] = t[j++] ) != '\0' );
}

Глава 3

Конструкция else-if


// поиск x, в массиве arr, размера s
int binsearch(int x, int arr[], int s);

int main(){
    int arr[] = {1,2,3,5,6,10,12,15,20};
    int x = 10;
    int arr_size = (int)(sizeof(arr)/sizeof(arr[0]));
    int result = binsearch(x, arr, arr_size);
    printf("%d", result);
    return 0;
}

int binsearch(int x, int arr[], int s){
    int low, high, mid;
    low = 0;
    high = s - 1;

    while ( low <= high ){
        mid = (low+high)/2; // 4, 6, 5
        printf("low = %d; high = %d; mid = %d;\n", low, high, mid);

        if ( x < arr[mid] ){ // f, t, f
            high = mid - 1; // , 5
        } else if ( x > arr[mid] ){ // t, f, 
            low = mid + 1; // 5, 
        } else {
            return mid; // t
        }
    }
    return -1;
}

...
low = 0; high = 8; mid = 4;
low = 5; high = 8; mid = 6;
low = 5; high = 5; mid = 5;
5[Finished in 0.1s]
...

Циклы while и for


// Преобразовывает строку в число
int atoi( char s[] );

int main(){

    char str[] = "  -123";
    int res = atoi(str);
    printf("%d\n", res);

    return 0;
}

int atoi( char s[] ){
    int i, n, sign;

    for( i = 0; isspace(s[i]); i++ );
    sign = ( s[i] == '-' ) ? -1 : 1;
    if (s[i] == '+' || s[i] == '-') i++;

    for ( n = 0; isdigit(s[i]); i++){
        n = 10 * n + (s[i] - '0');
    }

    return sign * n;
}

...
-123
[Finished in 0.1s]
...

Циклы do while

Break и continue


// Удаляет пустые символы в конце строки
int trim(char str[]);

int main(){
    char str[] = "hello    \n   \t   \t\t";
    trim(str);
    printf("%s", str);

    return 0;
}

int trim(char str[]){
    int n;

    for ( n = strlen(str) - 1; n >= 0; n-- ){
        if ( str[n] != ' ' && str[n] != '\t' && str[n] != '\n' ) break;
    }
    str[++n] = '\0';
    return n;
}

    // Вывести только четные числа
    int a[] = {1,2,3,4,5,65,3,23,321,5,6,8,9,9,2};

    for ( int i = 0; i < 15; i++ ){
        if (a[i] % 2) continue;
        printf("%d = %d\n", i, a[i]);
    }

Глава 4

Функции


int get_line(char line[], int limit);
int str_index(char source[], char search[]);

int main(){

    char pattern[] = "hi";
    char line[MAX_LINE];
    int count = 1;

    if (0){ // debug
        get_line(line, MAX_LINE);
        printf("source = %s; pattern = %s\n", line, pattern);
        printf("\n\nFound? %d\n", str_index(line, pattern) );
    } else {
        while ( get_line(line, MAX_LINE) > 0 ){
            if ( str_index(line, pattern) >= 0 ){
                printf("str = %d; FOUND IN: '%s'", count++, line);
            }
        }
    }


    return 0;
}

// Считывает строку и возвращает ее длину
int get_line(char str[], int lim){
    int c, i;

    i = 0;
    while ( --lim > 0 && ( c = getchar() ) != EOF && c != '\n' ){
        str[i++] = c;
    }

    if ( c == '\n' ) str[i++] = c;
    str[i] = '\0';

    return i;
}

// Возвращает индекс подстроки
int str_index(char s[], char t[]){
    int i, j, k;

    for ( i = 0; s[i] != '\0'; i++ ){
        for (j = i, k = 0; t[k] != '\0' && s[j] == t[k]; j++, k++);
        if ( t[k] == '\0' && k > 0 ) return i;
    }
    return -1;
}

...
avis@avis-PC[22:41:30]:~/develop/doc/learn/books/c/4$ ./a.out 
testhitest
source = testhitest
; pattern = hi


Found? 4
...

int get_line(char line[], int limit);
double atof(char s[]);

int main(){

    char pattern[] = "hi";

    if (1){ // debug
        char line[] = "      -1000.123223";
        printf("%f\n", atof(line) );
    } else {
        // char line[MAX_LINE];
        // while ( get_line(line, MAX_LINE) > 0 ){
        // }
    }


    return 0;
}

// Преобразует строку в число типа double
double atof(char s[]){
    double res, power;
    int i, sign;

    for ( i = 0; isspace(s[i]); i++ ); // Пропуск пробелов

    sign = ( s[i] == '-' ) ? -1 : 1;
    if ( s[i] == '+' || s[i] == '-' ) i++; // Пропуск знака

    for ( res = 0.0; isdigit( s[i] ); i++ ){
        res = 10.0 * res + ( s[i] - '0' );
    }
    if ( s[i] == '.' ) i++; // Пропуск десятичной точки

    for ( power = 1.0; isdigit(s[i]); i++ ){
        res = 10.0 * res + ( s[i] - '0' );
        power *= 10;
    }

    return sign * res / power;
}

// Считывает строку и возвращает ее длину
int get_line(char str[], int lim){
    int c, i;

    i = 0;
    while ( --lim > 0 && ( c = getchar() ) != EOF && c != '\n' ){
        str[i++] = c;
    }

    if ( c == '\n' ) str[i++] = c;
    str[i] = '\0';

    return i;
}

...
-1000.123223
[Finished in 0.1s]
...

Внешние переменные

Калькулятор с обратной польской нотацией


#include <stdio.h\>
#include <stdlib.h\>
#include <ctype.h\>
#define MAXOP 100       // макс размер операнда
#define BUFSIZE 100     // макс размер буфера
#define MAXVAL 100      // макс размер стека
#define IS_NUMBER '0'   // флаг что число
#define DEBUG 0

char buf[BUFSIZE];
int bufp = 0;

int sp = 0; // stack pointer
double stack[MAXVAL];

int getop(char s[]);
char getch();
void ungetch( char c );
void push( double op );
double pop();
double gettop(); // Возвращает вершину стека

int main(){
    int type;
    double op2;
    char str[MAXOP];

    while ( ( type = getop(str) ) != EOF ){
        switch (type){
            case IS_NUMBER:
                push(atof(str));
                break;
            case '+':
                push( pop() + pop() );
                break;
            case '*':
                push( pop() * pop() );
                break;
            case '-':
                op2 = pop();
                push( pop() - op2 );
                break;
            case '/':
                op2 = pop();
                if ( op2 != 0.0 ){
                    push( pop() / op2 );
                } else {
                    printf("error: zero divisor\n");
                }
                break;
            case '\n':
                printf("\t%.8g\n", gettop());
                break;
            default:
                printf("error: unknown command %s\n", str);
                break;
        }
        if ( type != '\n' && DEBUG){
            printf("sp = %d; in stack\n", sp);
            for ( int i = 0; i < MAXVAL; i++ ) printf("%g ", stack[i]);
            printf("\n");
        }
    }

    return 0;
}

// Извлекает операнд или знак операции
int getop(char s[]){
    int i, c;
    i = 0;

    while ( ( s[0] = c = getch() ) == ' ' || c == '\t' );

    s[1] = '\0';

    if ( !isdigit(c) && c != '.' ) return c; // не число

    if ( isdigit(c) ){ // Накопление целой части
        while ( isdigit(s[++i] = c = getch()) );
    };

    if ( c == '.' ){
        while ( isdigit(s[++i] = c = getch() ));
    }

    s[i] = '\0';

    if (c != EOF) ungetch(c);
    return IS_NUMBER;
}

// Ввод символа
char getch(){
    return ( bufp > 0 ) ? buf[--bufp] : getchar();
}

// Возвращение символа
void ungetch( char c ){
    if ( bufp >= BUFSIZE ){
        printf("ungetch: too many characters\n");
    } else {
        buf[bufp++] = c;
    }
}

void push( double op ){
    if ( sp < MAXVAL ){
        stack[sp++] = op;
    } else {
        printf("push: stack full, can't push %g\n", op);
    }
}

double pop(){
    if ( sp > 0 ){
        return stack[--sp];
    } else {
        printf("pop: stack is empty\n");
        return 0.0;
    }
}

double gettop(){
    if ( sp > 0 ){
        return stack[sp-1];
    } else {
        printf("pop: stack is empty\n");
        return 0.0;
    }
}
...
$ ./a.out 
1
    1
2
    2
+
    3
2+
    5
1+
    6
5-
    1
4+
    5
20*
    100
0.45649/
    219.06285
^C
...

Область действия

/home/avis/develop/doc/learn/books/c/main/build-main-Desktop-Debug/main