Programowanie w C (index)


Programowanie w C (5) - tablice, stringi

ELASTYCZNA TABLICA

Standard ISO C90 nie zezwala na definiowanie tablic o rozmiarze, który jest zmienną, a nie stałą. Bardziej elastyczne tablice potrzebne są m.in. gdy: (1) tworzymy tablicę o rozmiarze określonym w czasie wykonywania, (2) tworzymy tablicę o dużym rozmiarze. Za pomocą jawnej alokacji pamięci możemy w języku C ominąć ograniczenie stałego rozmiaru tablicy.


#include <stdlib.h>   /* malloc(), calloc() */
#include <string.h>   /* memset() */

#define MAXLINE  80

int n;  /* dynamiczny rozmiar tablicy jednowymiarowej */
int *tablica;   /* wskaznik do poczatku tablicy */
char line[MAXLINE];

/* Wczytanie rozmiaru tablicy.  */
printf("Podaj rozmiar tablicy: ");
fgets(line, sizeof(line), stdin);
sscanf(line, "%d", &n);

/* Alokacja pamięci na tablicę.  */

tablica = (int *) malloc(n * sizeof(int));

/* malloc() NIE inicjalizuje pamięci zerami.
Można od razu wymusić inicjalizację zerami:
tablica = (int *)calloc(n, sizeof(int));
Możemy też ręcznie wstawić zera do danego obszaru
pamięci (funkcja memset jest bardzo szybka):
memset(tablica, '\0', n*sizeof(int));
*/
if (tablica == NULL) {
    fprintf(stderr, "Error: brakuje pamieci\n");
    exit(1);
}
/* Zwolnienie pamięci. */
free(tablica);
tablica = NULL;   /* dobry zwyczaj */

WCZYTYWANIE NAPISÓW

Zastosowanie funkcji scanf() - brak kontroli nad wielkością wczytywanego napisu. Funkcja scanf() jako granice napisu przyjmuje białe znaki. Problem: czytamy tylko jedno pole/słowo; w stdin ZAWSZE zostaje znak nowej linii i następne słowa.


#define MAXLINE 80
char imie[MAXLINE];
scanf("%s", line);

Zastosowanie fgets() [kontrola ilości wczytywanych znaków; wczytujemy też znak końca linii, jeżeli się zmieści] i ręczne usuwanie znaku nowej linii [ale jeżeli znak nowej linii nie zmieścił się w tablicy, to usuniemy czarny znak na końcu]. Problem: w stdin mogą zostać nieprzeczytane znaki, jeżeli się nie zmieszczą w macierzy.


#define MAXLINE 80
char imie[MAXLINE];
fgets(imie, sizeof(imie), stdin);
imie[strlen(imie)-1] = '\0';   /* obcinamy '\n' */

Zastosowanie fgets() [kontrola ilości wczytywanych znaków] do wczytywania znaków do tymczasowej macierzy, a następnie bezpieczne czytanie jednego słowa za pomocą sscanf() Problem: w stdin mogą zostać nieprzeczytane znaki, jeżeli się nie zmieszczą w macierzy.


#define MAXLINE 80
char line[MAXLINE];  /* tymczasowa macierz */
char imie[MAXLINE];
fgets(line, sizeof(line), stdin);
sscanf(line, "%s", imie);  /* to już jest bezpieczne */

Bardziej elastyczne rozwiązanie to funkcja wczytująca całą linię do dynamicznego bufora, który powiększa się w czasie wczytywania znaków. Znak końca wiersza '\n' jest również wczytywany, a cały napis jest zakończony znakiem '\0'. W razie niepowodzenia z alokacją pamięci funkcja zwraca NULL.


#include <stdlib.h>
char *read_string(void)
{
int i, c;
int size = 2; /* aktualny rozmiar bufora */
char *p, *q;   /* bufory na dane */

p = (char *) malloc(size * sizeof(char));  /* poczatkowy bufor */
if (p == NULL) {
    return NULL;
}
i = 0;
while ((c = fgetc(stdin)) != EOF) {
        p[i] = c;
    ++i;
        if (i == size) {
        size *= 2;
                q = realloc(p, size*sizeof(char));
        if (q == NULL) {
            free(p);  /* zwalniamy to co juz mamy */
            return NULL;
        } else {
            p = q;
        }
    }
    if (c == '\n') break;   /* dla bezpieczenstwa na koncu po realloc() */
}
p[i] = '\0';
return p;
}
/* Korzystanie z funkcji
char *napis;
napis = read_string();
...
free(napis);   Zwalniamy pamięć
napis = NULL;   Dobry zwyczaj
*/

ZADANIE 5.1

W katalogu domowym utworzyć podkatalog wektory. Utworzyć w nim pliki Makefile i main.c postaci:


/*
* main.c
*
* Program ilustrujacy korzystanie z tablic. 
*/

#include <stdio.h>

void wczytaj(int v[], int n) 
{ 
int i;

for (i = 0; i < n; ++i) {
    printf("Podaj kolejny element tablicy: ");
    scanf("%d", &v[i]);
}
return;
}

void wypisz(int v[], int n) 
{ 
int i;

for (i = 0; i < n; ++i)
    printf("v[ %d ]= %d\n", i, v[i]);
return;
}

int main(void) 
{
const int N = 5;
int tablica[N];

wczytaj(tablica, N);
printf("\nPodales nastepujace liczby:\n");
wypisz(tablica, N);
return 0;
}

Skompilować i uruchomić program.

Zamiast wczytywać elementy macierzy, wypełnić ją kolejnymi liczbami całkowitymi (kolejnymi potęgami dwójki, wartościami silni, liczbami Fibonacciego).

ZADANIE 5.2

W programie z poprzedniego zadania wszystkie funkcje umieścić w osobnych plikach i zmodyfikować plik Makefile.

Poprawić program dopisując następujące funkcje:

Deklaracje funkcji powinny mieć postać:


int suma(int v[], int n);
int minimum(int v[], int n);
int maksimum(int v[], int n);
void odwracanie(int v[], int n);

Rozważyć iteracyjne i rekurencyjne wersje funkcji.

ZADANIE 5.3

W katalogu domowym utworzyć podkatalog sortowanie. Utworzyć w nim pliki bubblesort.c i shellsort.c z funkcjami służącymi do sortowania tablic.


/*
* bubblesort.c
*
* Sortowanie tablicy metoda babelkowa (v[] rosnaco).
*/

#include "main.h"

void bubblesort(int v[], int n) 
{
int i, j, tmp;

for (i = 1; i < n; ++i)
    for (j = 1; j < n; ++j)
        if (v[j-1] > v[j]) {
            tmp = v[j-1];
            v[j-1] = v[j];
            v[j] = tmp;
        }
return;
}

/*
* shellsort.c
*
* Porzadkowanie v[] rosnaco.
* Autor algorytmu: D. L. Shell (1959).
* W fazie poczatkowej porownuje sie elementy oddalone od siebie,
* a nie sasiadujace, jak w prostych metodach zamiany.
* Celem jest wyeliminowanie duzego balaganu, aby w pozniejszych
* fazach bylo mniej do zrobienia. Odlegosci miedzy porownywanymi
* elementami zmniejszaja się stopniowo do 1 i od tej chwili
* mamy metode sasiednich zamian. 
*/

#include "main.h"

void shellsort(int v[], int n) 
{
int gap;       /* odstep pomiedzy porownywanymi elementami */
int i, j, tmp;

for (gap = n/2; gap > 0; gap /= 2)
    for (i = gap; i < n; ++i)   /* wzdluz elementow tablicy */
/* porownywanie pary elementow oddalonych od siebie o "gap" */
        for (j = i-gap; j >= 0; j -= gap)
            if (v[j] > v[j+gap]) {
                tmp = v[j];
                v[j] = v[j+gap];
                v[j+gap] = tmp;
            }
return;
}

Stworzyć odpowiednie pliki main.c, main.h i Makefile, aby powstał program sortujący tablice jedną z dostępnych metod. Wczytywanie tablicy i wypisywanie zawartości tablicy zapisać jako osobne funkcje.

W programach bardziej zaawansowanych do sortowania można wykorzystać biblioteczną funkcję qsort() po zadeklarowaniu

#include <stdlib.h>

# Sortowanie przez qsort().
qsort(v, N, sizeof(int), cmp_int);

Potrzebna jest dodatkowa funkcja porównująca liczby całkowite.


/*
* cmp_int.c
*/

#include "main.h"

int cmp_int(const void *data1, const void *data2) 
{
int *d1 = (int *) data1;
int *d2 = (int *) data2;

if (*d1 < *d2)
    return -1;
else if (*d1 > *d2)
    return 1;
else
    return 0;
}

ZADANIE 5.4

W katalogu domowym utworzyć podkatalog getline. Utworzyć w nim pliki Makefile i getline.c postaci:


/*
* getline.c
*
* Program czyta zbior wierszy i wypisuje najdluzszy. K i R str. 53.
*/

#include <stdio.h>

int getline(char napis[], int limit);
void copy(char dest[], char source[]);

int main(void) 
{
const int MAXLINE = 80;  /* dozwolona dlugosc wiersza */
int len;                 /* dlugosc biezacego wiersza */
int best;                /* poprzednia maksymalna dlugosc */
char line[MAXLINE];      /* biezacy wiersz z wejscia */
char bestline[MAXLINE];  /* przechowywany maksymalny wiersz */

best = 0;
while ((len = getline(line, MAXLINE)) > 0)
    if (len > best) {
        best = len;
        copy(bestline, line);
    }
if (best > 0)     /* znaleziono wiersz */
    printf("%s", bestline);
printf("\n%d\n", best);
return 0;
}

/*
* Wczytuje wiersz do s[], podaje jego dlugosc. 
* Wczytywanie bedzie przerwane po wypelnieniu tablicy.
*/
int getline(char napis[], int limit) 
{
int c, i;

for (i = 0; (i < limit-1) && (c = fgetc(stdin)) != EOF 
    && (c != '\n'); ++i)
    napis[i] = c;
if (c == '\n') {
    napis[i] = c;
    ++i;
}
napis[i] = '\0';
return i;
}

/* Przepisuje source[] do dest[]. */
/* dest[] musi byc dostatecznie duze. */
void copy(char dest[], char source[]) 
{
int i;

i = 0;
while ((dest[i] = source[i]) != '\0')
    ++i;
return;
}

Skompilować i uruchomić program.

Poprawić funkcję getline() tak, aby program zawsze poprawnie wypisywał rozmiar dowolnie długich wierszy i tylko tyle tekstu, ile jest możliwe.

ZADANIE 5.5

W katalogu domowym utworzyć podkatalog komentarz. Utworzyć w nim pliki Makefile i komentarz.c z programem usuwającym wszystkie komentarze z dowolnego programu w języku C. Należy pamiętać o właściwym traktowaniu stałych znakowych i napisowych. Komentarze języka C nie mogą być zagnieżdżone.

ZADANIE 5.6

W katalogu domowym utworzyć podkatalog napisy. Utworzyć w nim pliki Makefile, main.h, main.c, stringlen.c i reverse.c postaci:


/*
* main.c
*/

#include "main.h"

int main(void) 
{
char zdanie[] = "abcdefgh"; /* inicjalizowanie tablicy znakowej */

printf("%s\n", zdanie);
reverse(zdanie);
printf("%s\n", zdanie);
return 0;
}

/*
* stringlen.c
*
* Funkcja zwracajaca dlugosc stringu. K i R str. 65.
*/

#include "main.h"

int stringlen(char s[]) 
{
int i = 0;

while (s[i] != '\0')
    ++i;
return i;
}

/*
* reverse.c
*
* Funkcja odwracajaca napis. K i R str. 93.
*/

#include "main.h"

void reverse(char s[]) 
{
int znak, i, j;

for (i = 0, j = stringlen(s)-1; i < j; ++i, --j) {
    znak = s[i];
    s[i] = s[j];
    s[j] = znak;
}
return;
}

/*
* main.h
*
* Plik naglowkowy.
*/

#include <stdio.h>

int stringlen(char s[]);

void reverse(char s[]);

Skompilować i uruchomić program. Poprawić program, aby wypisywał komunikaty informacyjne.

Poprawić program, aby prosił użytkownika o podanie napisu. W tym wypadku należy wcześniej ustalić rozmiar tablicy zdanie. Zbadać ograniczenia wywołania scanf("%s", zdanie). Zbadać, co mówią o wielkości napisu funkcje stringlen() i sizeof().


Programowanie w C (index)