04 Practise

Практика 4

На этой практике разбираемся с указателями, строками и массивами.

После этой практики вы должны уметь без Google объяснить: - что такое указатели и как их использовать - что такое массивы и как их использовать - в чем разница между указателями и массивами

Примеры

Просто указатели

Запустите программу и посмотрите что будет выведено на экран.

#include <stdio.h>

int main(void) {
	int a = 10; // это обычная переменная
	int* b; // это объявление указателя на int
	// & - операция взятия указателя
	b = &a; // это присвоение указателю b адреса переменной a
	// * - операция разыменования указателя. То есть взятия значения по адресу
	int c = 32 + *b; // *b - это получение данных лежащих в памяти по адрему b.
	printf("&c=%p\tс=%d\n",&c, c);
	*b = 100500; // можно изменить объект в памяти по указателю
	printf("&a=%p b=%p\ta=%d, *b=%d\n", &a, b, a, *b); // значение переменной изменилось, так как изменили сам объект в памяти по адресу, на который показывает указателю.
	b = &c; // можно поменять адрес, на который смотрит указатель
	printf("b=%p &c=%p\t*b=%d\n",b, &c, *b); // %p - распечатать адрес в виде 16-ричного числа.
	return 0;
}

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

&c=0x7ffeec08a8cc	с=42
&a=0x7ffeec08a8dc b=0x7ffeec08a8dc	a=100500, *b=100500
b=0x7ffeec08a8cc &c=0x7ffeec08a8cc	*b=42

Указатели как аргументы функции

С одной такой функцией вы знакомы. Это scanf.

#include <stdio.h>

//функция, чтобы считать число с консоли
void read_int(int* var) {
	// обычно мы пишем scanf("%d", &i), потому что i - это переменная, но не указатель
	scanf("%d", var); // обратите внимание, что var - это указатель
}

int main(void) {
	int digit;
	read_int(&digit);
	printf("%d\n", digit);
	return 0;
}

Указатели и массивы

#include <stdio.h>

int main(void) {
	short arr[10]; // создадим массив на стеке
	// заполним массив числами 10,20...100
	for (int i = 0; i<10;i++){
		arr[i] = (i+1)*10;
	}
	short* ptr; // объявим указатель на int
	ptr = &arr[0]; // положим в ptr указатель на первый элемент массива arr
	
	printf("%p == %p\n", arr, ptr); // arr и ptr по указывают на одно и то же место в памяти

	// если к указателю прибавить/удалить число, то мы получим смещение в памяти на sizeof(POINTER TYPE).
	// Внезапно *(ptr+i) == ptr[i]
	printf("%p\t%d\t%d\n", ptr+1, *(ptr+1), ptr[1]);
	printf("%p\t%d\t%d\n", ptr+2, *(ptr+2), ptr[2]);
	printf("%p\t%d\t%d\n", ptr+3, *(ptr+3), ptr[3]);

	// грязые хаки
	printf("==========\n");
	int* sh_ptr = (int*)ptr;
	printf("%d %d\n", sh_ptr[0], sh_ptr[1]);
	sh_ptr[0] = 1000500;
	printf("%d %d\n", ptr[0], ptr[1]);
}

Результат работы программы. Адреса могут быть другими.

0x7ffeec9a78c0 == 0x7ffeec9a78c0
0x7ffeec9a78c2	20	20
0x7ffeec9a78c4	30	30
0x7ffeec9a78c6	40	40
==========
1310730 2621470
17460 15

Задания

  1. Во всех заданиях действует запрет на использование библиотечных функций, кроме malloc, calloc, realloc, free, printf, scanf.
  2. Вам не требуется выделять память динамически в этих задачах.
  3. Вся логика задания должна находиться в функциях.
  4. В функции main ТОЛЬКО тестирование работоспособности функции.
  5. Прототипы функций менять запрещено.
  6. Вспомогательные функции создавать можно.

Задание 1. Изменение аргументов

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

Напишите программу с функций с именем magic, которая принимает на вход указатель на переменную типа int и изменяет ее значение на число 42. В main функции проверьте, что измеение действительно произошло.

Задание 2. Swap

Напишите функцию для 2 аргументов, которая меняет местами их значеня.

Прототип функции

void swap_int(int *a, int *b)

Задание 3. Strlen

Напишите функцию, которая вычисляет длину строки. Используйте информацию о том, что строки в Си всегда заканчиваются ЭТИМ. (кстати чем?).

Прототип функции:

unsigned int str_length(const char* s)

Задание 4. Задом наперёд

Напишите функцию, которая печатает на консоль строку задом наперёд. Использовать printf запрещено. Можно использовать только циклы и putchar.

Прототип функции

void print_reverse(const char s*)

Ожидаемое поведение:

При вызове print_reverse("hello"); должно быть напечатано в консоль olleh.

Задание 5. Std

Напишите функцию, которая считает среднеквадратичное отклонение для последовательности чисел. Для вычисления отклонения вам надо будет посчитать среднее значение последовательности чисел. Оформите это тоже в виде функции.

Формула для вычисления:

$$S=\sqrt{\frac{1}{n}\sum_{i=1}^n\left(x_i-\bar{x}\right)^2}.$$

$$\bar{x} = \frac{1}{n}\sum_{i=1}^n x_i = \frac{1}{n} (x_1+\ldots+x_n).$$

Прототип функции:

float std(const float* arr, unsigned int length)

Ожидаемое поведение

float arr[] = {1.f,1.f,1.f,1.f};
std(arr, 4) == 0;
float arr2[] = {1.f,2.f,3.f,1.f}
std(arr2, 4) == 0.82915619758885; // в некотором смысле, так как float нельзя сравнивать на равенство

Задание 6. Strcopy

Напишите функцию для копирования одной строки, на которую указывает src, в буффер, на который указывает dest. Терминальный нулевой байт копируется тоже.

Прототип функции:

void str_copy(char* dest, const char* src)

Ожидаемое поведение

int main(void) {
	char* src = "Hello world.";
	char dst[13];
	str_copy(dst, src);
	dst[0] == 'H';
	dst[11] == '.';
	printf("src=%s dst=%s\n", src, dst); // должно работать и выводить строки корректно
}

Задание 7. Strstr

Напишит функцию, которая находит первое вхождение подстроки в строке или NULL, если вхождения нет. Функция возвращает указатель на символ начала вхождения подстроки.

Прототип функции

char *strstr(const char* str, const char* substr );

Ожидаемое поведение

#include <stdio.h>
 
void find_str(char const* str, char const* substr) {
    char* pos = strstr(str, substr);
    if(pos) {
        printf("found the string '%s' in '%s' at position: %ld\n", substr, str, pos - str);
    } else {
        printf("the string '%s' was not found in '%s'\n", substr, str);
    }
}
 
int main(void) {
    char* str = "one two three";
    find_str(str, "two");
    find_str(str, "");
    find_str(str, "nine");
    find_str(str, "n");
 
    return 0;
}
Выход
found the string 'two' in 'one two three' at position: 4
found the string '' in 'one two three' at position: 0
the string 'nine' was not found in 'one two three'
found the string 'n' in 'one two three' at position: 1

Задание 8. Atoi

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

Требования:

  • строка может начинаться с - и +
  • если строка не является числом, то верните 0
  • нельзя использовать массивы

Прототип функции

int _atoi(const char* str);

Ожидаемое поведение

#include <stdio.h>

int main(void) {
	_atoi("42") == 42;
	_atoi("-10") == 10;
	_atoi("-10bsf") == 0;
	_atoi("-10+123") == 0;
	_atoi("+51") == 51;
}

Домашнее задание.

Читать главу 5 K&R.