Zadania
-
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życiemunion
. Program przyjmuje od użytkownika liczbę i wypisuje jej pierwiastek, używając funkcjiQ_rsqrt
(zwróć uwagę, że funkcja zwraca odwrotność pierwiastka) -
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 operatorasizeof
i makraoffsetof
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) -
any.h
,any.c
: stwórz bibliotekę, która definiuje typany
pozwalający na przechowywanie wielu różnych typów:-
typedef struct any { /* zdefiniowane przez osobę studencką */ } any_t;
, które wykorzystuje do przechowywania liczb i ciągów znakówunion
-
any_t any_from_int(int x);
- funkcja tworząca wartość any z wartości liczbowej -
any_t any_from_str(char const* s);
- funkcja tworząca wartość any z ciągu znaków -
any_t* any_add(any_t *target, any_t rhs);
- dodaje dotarget
wartośćrhs
, w zależności od typu (zwraca target):-
jeśli obie wartości są liczbami to wynikiem jest suma liczb
-
jeśli obie wartości są ciągami to wynikiem jest konkatenacja ciągów
-
jeśli jedna z wartości jest ciągiem, a druga liczbą wynikiem jest konkatenacja wartości liczbowej do ciągu
-
-
any_t* any_mul(any_t *target, any_t rhs);
- mnoży dotarget
wartośćrhs
, w zależności od typu (zwraca target):-
jeśli obie wartości są liczbami to wynikiem jest iloczyn liczb
-
jeśli obie wartości są ciągami to wynikiem jest konkatenacja ciągów
-
jeśli jedna z wartości jest ciągiem, a druga liczbą wynikiem jest `n`krotne powtórzenie ciągu
-
-
void any_free(any_t a);
- czyszczące zaalokowaną pamięć jeśli taka była potrzebna -
void any_print(any_t a);
- wypisujące wartość na standardowe wyjście (wraz z znakiem nowej linii)
-
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;
}