Zadania

  1. q_sqrt.c: stwórz program, który wykorzystuje funkcję Q_sqrt z implementacji gry Quake II Arena (komentarze możesz pominąć). Zmień implementację funkcji tak, by „evil floating point bit level hacking” zostało zrealizowane z użyciem union. Program przyjmuje od użytkownika liczbę i wypisuje jej pierwiastek, używając funkcji Q_rsqrt (zwróć uwagę, że funkcja zwraca odwrotność pierwiastka)

  2. struct_size.c: stwórz program, który zawiera strukturę test, o następujących polach: 4 liczby 8 bitowe i 2 liczby 64 bitowe. Przy pomocy operatora sizeof i makra offsetof zbadaj jak kolejność pól wpływa na rozmiar struktury. Zapisz jakie rozmiary struktur można stworzyć (i przykładowe ułożenie pól jakie pozwala na taki rozmiar)

  3. any.h, any.c: stwórz bibliotekę, która definiuje typ any pozwalający na przechowywanie wielu różnych typów:

    1. typedef struct any { /* zdefiniowane przez osobę studencką */ } any_t;, które wykorzystuje do przechowywania liczb i ciągów znaków union

    2. any_t any_from_int(int x); - funkcja tworząca wartość any z wartości liczbowej

    3. any_t any_from_str(char const* s); - funkcja tworząca wartość any z ciągu znaków

    4. any_t* any_add(any_t *target, any_t rhs); - dodaje do target wartość rhs, w zależności od typu (zwraca target):

      1. jeśli obie wartości są liczbami to wynikiem jest suma liczb

      2. jeśli obie wartości są ciągami to wynikiem jest konkatenacja ciągów

      3. jeśli jedna z wartości jest ciągiem, a druga liczbą wynikiem jest konkatenacja wartości liczbowej do ciągu

    5. any_t* any_mul(any_t *target, any_t rhs); - mnoży do target wartość rhs, w zależności od typu (zwraca target):

      1. jeśli obie wartości są liczbami to wynikiem jest iloczyn liczb

      2. jeśli obie wartości są ciągami to wynikiem jest konkatenacja ciągów

      3. jeśli jedna z wartości jest ciągiem, a druga liczbą wynikiem jest `n`krotne powtórzenie ciągu

    6. void any_free(any_t a); - czyszczące zaalokowaną pamięć jeśli taka była potrzebna

    7. void any_print(any_t a); - wypisujące wartość na standardowe wyjście (wraz z znakiem nowej linii)

Przykładowy program wykorzystujący any.h
#include "any.h"

int main()
{
	any_t a = any_from_int(3);
	any_t b = any_from_int(4);
	any_t s = any_from_str("hello");

	any_print(a); // wypisuje: 3
	any_print(s); // wpisuje: hello

	any_mul(&s, a);
	any_print(s); // wypisuje: hellohellohello

	any_mul(&b, a);
	any_print(b); // wypisuje: 7

	any_sum(&a, s);
	any_print(&a); // wypisuje 3hellohellohello

	any_sum(&s, b);
	any_print(&s); // wypisuje hellohellohello7

	any_free(a);
	any_free(b);
	any_free(s);

	return 0;
}

Struktury (struct)

Struktury w języku C służą do przechowywania kilku wartości różnych typów (w przeciwieństwie do tablic, które przechowują wiele wartości jednego typu).

Przykład deklaracji i użycia struktury:

#include <stdio.h>

struct point
{
	float x;
	float y;
};

void move(struct point *p)
{
	// Odwołanie do pola przez wskaźnik
	p->x += 1;

	// Można też tak
	(*p).y += 1;
}

int main()
{
	// Definicja:
	struct point p;

	// Odwołanie do pola przez wartość
	p.x = 10;

	move(&p);

	printf("%f,%f\n", p.x, p.y);

	// użycie literału
	p = (struct point) { 10, 20 };

	// wskazywanie pól w trakcie użycia literału (nie trzeba zachowywać kolejności!)
	p = (struct point) { .y = 20, .x = 10 };

	return 0;
}

typedef

Słowo kluczowe typedef umożliwia deklarację aliasów dla typów w języku C:

typedef char const* string;
// string to teraz alias dla char const*
string hello = "hello, world";

Zwyczajowo aliasy stworzone z użyciem typedef posiadają suffix _t.

Wykorzystywany jest zazwyczaj w celu uniknięcia stosowania prefixu struct przy każdym użyciu typu struktury.

struct point
{
	int x, y;
};

typedef struct point point_t;

point_t p = { .x = 10, .y = 20 };

Powyższy kod można skrócić mieszając definicję struktury z definicją jej aliasu:

typedef struct point
{
	int x, y;
} point_t;

point_t p = { .x = 10, .y = 20 };

Struktura anonimowa

Struktury można definiować anonimowo:

#include <stdio.h>

struct
{
	float PI;
	float TAU;
	float E;

} constants = {
	.PI = 3,
	.TAU = 6,
	.E = 3,
};

int main()
{
	printf("PI = %f\n", constants.PI);
	return 0;
}

Anonimowe definicje często są wykorzystywane w połączeniu z typedef:

typedef struct {
	int x, y;
} point_t;

Struktura rekurencyjna

Przykład deklaracji listy jednokierunkowej:

struct list
{
	int value;
	struct list *next;
};

Unie (union)

Odpowiednik struktur, z tą różnicą, że wszystkie pola współdzielą pamięć przez rozpoczynianie się w tym samym miejscu pamięci:

#include <stdio.h>
#include <stdint.h>

union color
{
	struct { uint8_t r, g, b, a; };
	uint8_t as_components[4];
	uint32_t as_number;
};

int main()
{
	union color c;

	// Little endian
	c.as_number = 0x000000ff;

	printf("r=%d, g=%d, b=%d, a=%d\n", c.r, c.g, c.b, c.a);


	return 0;
}

Unie pozwalają na odczyt tej samej wartości w wielu postaciach bez wykorzystania wskaźników.

Typ wyliczeniowy (enum)

Typ wyliczeniowy pozwala na stworzenie dedykowanego typu liczbowego dla zbioru stałych (zazwyczaj jednego rodzaju).

Przykłady:

enum json_type
{
	JSON_NULL,
	JSON_INT,
	JSON_FLOAT,
	JSON_STRING,
	JSON_ARRAY,
	JSON_OBJECT
};

json_type j = JSON_NULL;

enum weekday
{
	MONDAY = 1,
	TUESDAY = 2,
	WEDNESDAY = 3,
	THURSDAY = 4,
	FRIDAY = 5,
	SATURDAY = 6,
	SUNDAY = 7
};

Struktury i unie - prosty polimorfizm w C

Jednym z możliwych sposobów implementacji polimorfizmu w języku C jest stworzenie struktury, która w zależności od pola rozróżniającego wartości (zazwyczaj nazwanego tag, kind, type przechowuje różną wartość). By wartości nie zajmowały nadmiernego rozmiaru pamięci współdzielone pola umieszcza się w unii.

Poniżej zaprezentowano przykładowy program, który tworzy polimorficzną strukturę shape, która może zawierać koło lub prostokąt:

#include <stdio.h>
#include <stdint.h>
#include <math.h>
#include <stdlib.h>

struct shape
{
	union
	{
		struct {
			float left, top, width, height;
		} rectangle;

		struct {
			float center_x, center_y, radius;
		} circle;
	};

	// Wyznacznik, który typ aktualnie struktura przechowuje:
	enum
	{
		SHAPE_TYPE_RECTANGLE,
		SHAPE_TYPE_CIRCLE,
	} type;

	// Wspólne wartości:
	uint32_t color;
};

struct shape from_rectangle(float left, float top, float width, float height, uint32_t color)
{
	return (struct shape) {
		.type = SHAPE_TYPE_RECTANGLE,
		.rectangle.left = left,
		.rectangle.top = top,
		.rectangle.width = width,
		.rectangle.height = height,
		.color = color,
	};
}

struct shape from_circle(float cx, float cy, float r, uint32_t color)
{
	return (struct shape) {
		.type = SHAPE_TYPE_CIRCLE,
		.circle.center_x = cx,
		.circle.center_y = cy,
		.circle.radius = r,
		.color = color,
	};
}

float area(struct shape s)
{
	switch (s.type) {
	case SHAPE_TYPE_CIRCLE:
		return M_PI * s.circle.radius * s.circle.radius;

	case SHAPE_TYPE_RECTANGLE:
		return s.rectangle.width * s.rectangle.height;

	default:
		printf("Unexpected type, crashing\n");
		exit(2);
	}
}

void move(struct shape *s, float dx, float dy)
{
	switch (s->type) {
	case SHAPE_TYPE_CIRCLE:
		s->circle.center_x += dx;
		s->circle.center_y += dy;
		break;

	case SHAPE_TYPE_RECTANGLE:
		s->rectangle.left += dx;
		s->rectangle.top += dy;
		break;
	}
}

void print(struct shape s)
{
	switch (s.type) {
	case SHAPE_TYPE_CIRCLE:
		printf("circle(x=%f, y=%f, r=%f)\n", s.circle.center_x, s.circle.center_y, s.circle.radius);
		break;

	case SHAPE_TYPE_RECTANGLE:
		printf("rectangle(x=%f, y=%f, w=%f, h=%f)\n", s.rectangle.left, s.rectangle.top, s.rectangle.width, s.rectangle.height);
		break;
	}
}



int main()
{
	struct shape s;

	s = from_rectangle(0, 0, 100, 200, 0xff0000);
	print(s);
	move(&s, 10, 20);
	print(s);
	printf("area: %f\n", area(s));

	s = from_circle(0, 0, 300, 0xff0000);
	print(s);
	move(&s, 10, 20);
	print(s);
	printf("area: %f\n", area(s));

	return 0;
}