Serwis Edukacyjny
w I-LO w Tarnowie
obrazek

Materiały dla uczniów liceum

  Wyjście       Spis treści       Wstecz       Dalej  

obrazek

Autor artykułu: mgr Jerzy Wałaszek
Konsultacje: Wojciech Grodowski, mgr inż. Janusz Wałaszek

©2024 mgr Jerzy Wałaszek
I LO w Tarnowie

obrazek

Warsztat

Kurs języka C

Bity

SPIS TREŚCI
Podrozdziały

Bity są najważniejsze

Gdy zaczniesz programować mikrokontrolery, bardzo szybko odkryjesz tę podstawową prawdę. Mikrokontroler jest urządzeniem bitowym, nie tylko bity przetwarza, lecz również jest sterowany na poziomie bitów. Dlatego bity muszą stać się twoją drugą naturą. Musisz dokładnie wiedzieć, jak przetwarzać informację bitową. Bity będziesz przesyłał do portów i rejestrów mikrokontrolera, bity będziesz z tych portów i rejestrów odczytywał. Musisz wiedzieć, jak je identyfikować, ustawiać, zerować, negować, itp.

W tym rozdziale zajmiemy się podstawowymi operacjami na bitach w języku C. Na szczęście są one bardzo dobrze obsługiwane.

Aby prezentować informację w postaci bitów, zdefiniujemy funkcję pbn(), która przyjmuje argument typu unsigned, a następnie wyświetla zadaną liczbę bitów tego argumentu. Wyjaśnienie użytych w niej operacji znajdziesz dalej w tym rozdziale. jej działanie opiera się na tym, iż wszelkie dane są wewnętrznie reprezentowane bitami. Nie musimy zatem dokonywać żadnych konwersji, po prostu odczytujemy stan kolejnych bitów, zamieniamy go w cyfry 0 lub 1 w kodzie ASCII i wyświetlamy w oknie konsoli.

/*
 Bity
 (C)2016 mgr Jerzy Wałaszek
 Data utworzenia: 16.10.2016
*/

#include <stdio.h>
#include <stdlib.h>
#include <locale.h>

// Wyświetla b końcowych bitów argumentu v
//----------------------------------------
void pbn(unsigned v, unsigned b)
{
  unsigned maska;

  for(maska = 1 << (b - 1); maska; maska >>= 1)
    if(v & maska) putchar('1');
    else          putchar('0');
}

int main()
{
  unsigned i;

  setlocale(LC_ALL,"");

  for(i = 0; i <= 16; i++)
  {
    pbn(i,5);
    putchar('\n');
  }

  return 0;
}

Na początek:  podrozdziału   strony 

Stałe

Stałe (ang. constants) ogólnie są wartościami, których w programie nie wolno zmieniać. Z kilkoma stałymi już spotkaliśmy się. Np. zapis 126 jest stałą dziesiętną. Uporządkujmy tę wiedzę.

Stałe całkowite dziesiętne – decimal integer constants

Składają się z ciągu cyfr 0...9, które można poprzedzić znakiem + lub -. Stałe te reprezentują dziesiętne liczby całkowite, czyli takie, do których jesteśmy przyzwyczajeni.

/*
 Stałe
 (C)2016 mgr Jerzy Wałaszek
 Data utworzenia: 17.10.2016
*/

#include <stdio.h>
#include <stdlib.h>
#include <locale.h>

int main()
{
  setlocale(LC_ALL,"");

  printf("%d %d\n\n",+5,-8);

  return 0;
}

Jeśli stała jest wielocyfrowa, to pierwszą cyfrą nie może być cyfra 0.

Stałe całkowite ósemkowe – octal integer constants

Czasem pewne wartości wygodniej jest zapisywać programiście w systemie ósemkowym. Do tego celu służą stałe ósemkowe, oktalne. Zapisujemy je przy pomocy cyfr 0...7. Pierwszą cyfrą musi być cyfra 0, inaczej stała będzie rozpoznana jako dziesiętna. Cyfry można poprzedzić znakami + lub -.

/*
 Stałe
 (C)2016 mgr Jerzy Wałaszek
 Data utworzenia: 17.10.2016
*/

#include <stdio.h>
#include <stdlib.h>
#include <locale.h>

int main()
{
  unsigned a = 0377;

  setlocale(LC_ALL,"");

  printf("OCT = %o DEC = %d\n", a, a);

  return 0;
}

Stałe całkowite szesnastkowe – hexadecimal integer constants

Dane zapisywane w komórkach 8-bitowych lepiej wyglądają w systemie szesnastkowym. Dlatego system ten jest bardzo popularny wśród programistów. Stałą szesnastkową, heksadecymalną, zapisujemy z prefiksem 0x lub 0X, po którym umieszczamy cyfry szesnastkowe, czyli znaki 0...9 i A...F lub a...f. Stałą można poprzedzić znakami + lub -.

/*
 Stałe
 (C)2016 mgr Jerzy Wałaszek
 Data utworzenia: 17.10.2016
*/

#include <stdio.h>
#include <stdlib.h>
#include <locale.h>

int main()
{
  unsigned a = 0x1f;

  setlocale(LC_ALL,"");

  printf("HEX = %X DEC = %d\n", a, a);

  return 0;
}

Stałe całkowite dwójkowe – binary integer constants

Dane binarne możemy zapisywać za pomocą stałych binarnych. Składają się one z prefiksu 0b lub 0B, po którym umieszczamy cyfry 0..1 tworzące wartość binarną. Przed stałą można umieścić znaki + lub -.

/*
 Stałe
 (C)2016 mgr Jerzy Wałaszek
 Data utworzenia: 17.10.2016
*/

#include <stdio.h>
#include <stdlib.h>
#include <locale.h>

// Wyświetla b końcowych bitów argumentu v
//----------------------------------------
void pbn(unsigned v, unsigned b)
{
  unsigned maska;

  for(maska = 1 << (b - 1); maska; maska >>= 1)
    if(v & maska) putchar('1');
    else          putchar('0');
}

int main()
{
  unsigned a = 0b10111;

  setlocale(LC_ALL,"");

  printf("BIN = ");
  pbn(a, 5);
  printf(" DEC = %d\n", a);

  return 0;
}

Stałe zmiennoprzecinkowe – floating point constants

Liczby dziesiętne ułamkowe zapisujemy za pomocą stałych zmiennoprzecinkowych. Stałą mogą poprzedzać znaki + lub -. Sama stała składa się z części całkowitej, kropki pełniącej rolę przecinka dziesiętnego, części ułamkowej. Dodatkowo może pojawić się litera e lub E z liczbą całkowitą, która określa wykładnik potęgowy liczby 10, przez co jest mnożona wcześniejsza liczba. Część całkowita lub ułamkowa może być pominięta, jeśli wynosi zero. Oto kilka przykładów stałych zmiennoprzecinkowych:

1.5 (równe 1,5)
.5 (równe 0,5)
2. (równe 2,0)
-3.14 (równe -3,14)
1e3 (równe 1 · 103 = 1000)
-7.12e-5 (równe -7,12 · 10-5 = -0,0000712)
/*
 Stałe
 (C)2016 mgr Jerzy Wałaszek
 Data utworzenia: 17.10.2016
*/

int main()
{
  float a = .5;

  setlocale(LC_ALL,"");

  printf("FP = %f\n", a);

  return 0;
}

Stałe znakowe – character constants

Do przedstawiania pojedynczych znaków ASCII używamy stałych znakowych, które powstają przez umieszczenie znaku w apostrofach. Taka stała jest równoważna liczbowo kodowi ASCII danego znaku.

/*
 Stałe
 (C)2016 mgr Jerzy Wałaszek
 Data utworzenia: 17.10.2016
*/

#include <stdio.h>
#include <stdlib.h>
#include <locale.h>

int main()
{
  char a = 'Z';

  setlocale(LC_ALL,"");

  printf("Znak %c ma kod ASCII %d\n", a, a);

  return 0;
}

W stałej znakowej można umieszczać znaki specjalne, które zapisujemy przy pomocy znaku backslash: \. Ich znaczenie zależy od urządzenia służącego do wyświetlania tekstu. Najczęściej używane znaki, to:

\n przejście na początek nowego wiersza
\r powrót na początek bieżącego wiersza
\t tabulacja pozioma
\ooo znak o kodzie ósemkowym, np. \245
\xhh znak o kodzie szesnastkowym, np. \xf4
\\ znak \
\' znak apostrofu
\" znak cudzysłowu

Stałe łańcuchowe – string constants

W programach napisanych w języku C często posługujemy się różnymi tekstami, np. wypisując wyniki obliczeń lub opisując dane do wprowadzenia. Do tego celu używamy stałych łańcuchowych, które nazywa się również stałymi tekstowymi. Stałą łańcuchową tworzy się przez umieszczenie tekstu w cudzysłowach, np. "Jacek i Agata". Wartością stałej łańcuchowej jest adres pierwszego znaku w tekście. Stałe łańcuchowe można przypisywać zmiennym wskaźnikowym lub używać do inicjalizacji tablicy znakowej. W tekście można używać znaki specjalne, które podaliśmy dla stałych znakowych.

/*
 Stałe
 (C)2016 mgr Jerzy Wałaszek
 Data utworzenia: 17.10.2016
*/

#include <stdio.h>
#include <stdlib.h>
#include <locale.h>

int main()
{
  char * a = "Aleksander IX"; // wskaźnik
  char b[] = "Jan Kowalski"; // tablica znakowa

  setlocale(LC_ALL,"");

  printf("Tekst nr 1: %s\n"
         "Tekst nr 2: %s\n", a, b);

  return 0;
}

Tutaj muszę wyjaśnić różnicę pomiędzy stałą znakową 'A' a stałą łańcuchową "A", ponieważ uczniowie często je mylą. Różnica jest zasadnicza. Wartością stałej znakowej 'A' jest kod ASCII litery A, czyli 65. Wartością stałej łańcuchowej "A" jest adres literki A, czyli wskaźnik do komórki pamięci, która przechowuje ten znak. Dodatkowo łańcuch "A" zajmuje w pamięci 2 komórki, ponieważ po A jest umieszczany znak NUL (o kodzie ASCII równym zero), który pełni rolę znacznika końca tekstu. Wynika z tego jasno, że 'A' jest czymś zupełnie innym od "A" i nie można stosować ich zamiennie. Zapamiętaj to na przyszłość.

Stałe zmienne – constant variables

Brzmi to dziwnie, ale coś takiego istnieje w języku C. Jeśli definicję zmiennej poprzedzimy słowem const, to powstanie zmienna, której możemy w definicji nadać wartość, lecz wartości tej program nie będzie już mógł zmieniać.

/*
 Stałe
 (C)2016 mgr Jerzy Wałaszek
 Data utworzenia: 17.10.2016
*/

#include <stdio.h>
#include <stdlib.h>
#include <locale.h>

int main()
{
  const float pi = 3.1415629535;

  setlocale(LC_ALL,"");

  pi++; // tu otrzymasz błąd!!!

  return 0;
}

Po co nam zmienne, których wartości nie daje się zmieniać? Musisz uwierzyć na słowo, czasem są potrzebne. Poza tym pozwalają one zastępować wartości liczbowe, które są długie, a musimy je często używać w programie. Użycie const przy definicji takiej zmiennej gwarantuje nam, że w programie jej wartość nie ulegnie zmianie, np. przez nieuwagę.

Stałe symboliczne – symbolic constants

Istnieje jeszcze jedna możliwość zdefiniowania stałej za pomocą preprocesora. Przypomnijmy: preprocesor jest częścią kompilatora, która wstępnie przetwarza plik źródłowy programu zanim zostanie on poddany kompilacji. Preprocesor wyszukuje w tekście źródłowym swoje dyrektywy i je wykonuje. Np. praktycznie każdy program w języku C posiada na początku dyrektywę #include, która dołącza plik z definicjami. Tych dyrektyw jest więcej.

Za pomocą dyrektywy #define możesz zdefiniować dowolną nazwę i przypisać jej dowolny tekst. Działa to w ten sposób, iż po zdefiniowaniu takiej nazwy preprocesor zastąpi wszystkie jej wystąpienia w programie tekstem, który tej nazwie przypiszemy. Składnia polecenia jest następująca:

#define nazwa dowolny tekst

Reguły tworzenia nazw stałych symbolicznych są takie same jak dla zmiennych. Programiści stosują zwykle nazwy zbudowane z dużych liter i tobie też proponuję przyjąć tę konwencję.

/*
 Stałe
 (C)2016 mgr Jerzy Wałaszek
 Data utworzenia: 17.10.2016
*/

#include <stdio.h>
#include <stdlib.h>
#include <locale.h>

#define POWITANIE "Witamy w C\n"
#define MAXU 4294967295

int main()
{
  setlocale(LC_ALL,"");

  printf(POWITANIE);
  printf("Maksymalna liczba bez znaku to %u\n", MAXU);

  return 0;
}

Dyrektywa #define pochodzi z początków stosowania języka C. Często jest bardzo użyteczna w rękach doświadczonego programisty. Jednak stwarza różne problemy, chociażby takie, że zamiana stałej symbolicznej na tekst odbywa się poza kompilatorem – kompilator nawet się nie dowie, że w programie była taka stała, ponieważ preprocesor zastąpi ją tekstem zanim plik źródłowy przekaże do kompilacji.

Obecnie zaleca się używanie stałych zmiennych zamiast stałych symbolicznych. Zmienne są obiektami, które tworzy program i kompilator może sprawdzić poprawność ich użycia. Oczywiście nikt cię do tego nie zmusi.

Wyrażenie stałe

Wyrażenie stałe (ang. constant expression) jest wyrażeniem, którego wartość zależy tylko od użytych w nim stałych. Wartość takiego wyrażenia jest wyliczana w trakcie kompilacji. Dzięki temu bez względu na złożoność wyrażenia w czasie wykonania komputer nie traci już czasu na obliczenia, ponieważ całe wyrażenie zostaje zastąpione jego wartością. Na przykład:
a = 5 * (12 - 2) / 10;

zostaje zastąpione w czasie kompilacji przez:
a = 5;

Z tego względu programiści często używają wyrażeń stałych, ponieważ są one bardziej czytelne od ich wartości, a mamy je za darmo, bez utraty szybkości działania lub zwiększenia wielkości programu, co jest bardzo istotne w mikrokontrolerach posiadających niewiele pamięci.


Na początek:  podrozdziału   strony 

Operatory bitowe

Cechą charakterystyczną operatorów bitowych jest to, że działają one na poszczególnych bitach swoich argumentów. Na początek bitowe funkcje logiczne.

Bitowa negacja

Operator negacji bitowej ~ daje w wyniku argument ze zmienionymi na przeciwne wartościami wszystkich bitów:

b ~b
0 1
1 0

Na przykład:

~ 0011010111
  1100101000

Uruchom program:

/*
 Bity
 (C)2016 mgr Jerzy Wałaszek
 Data utworzenia: 19.10.2016
*/

#include <stdio.h>
#include <stdlib.h>
#include <locale.h>

// Wczytuje liczbę dwójkową
//-------------------------
char rb()
{
  char w = 0, *p, t[9];

  printf("Liczba binarna (max 8 cyfr) = ");
  scanf("%8s",t);
  for (p = t; *p; p++)
  {
    w += w;
    w += *p - '0';
  }

  return w;
}

// Wyświetla v jako liczbę dwójkową
//--------------------------------
void pb(char v)
{
  unsigned maska;

  for(maska = 0b10000000; maska; maska >>= 1)
    if(v & maska) putchar('1');
    else          putchar('0');
}

int main()
{
  char a = rb();

  setlocale(LC_ALL,"");

  printf("\n~ "); pb(a);
  printf("\n-----------\n");
  printf(" "); pb(~a);
  printf("\n\n");

  return 0;
}

Bitowa alternatywa

Jest to funkcja dwuargumentowa. Odpowiadające sobie bity obu argumentów są poddawane operacji logicznej alternatywy:

a b a | b
0 0 0
0 1 1
1 0 1
1 1 1

Na przykład:

  1110001001
| 0011010110
  1111011111

Uruchom program:

/*
 Bity
 (C)2016 mgr Jerzy Wałaszek
 Data utworzenia: 19.10.2016
*/

#include <stdio.h>
#include <stdlib.h>
#include <locale.h>

// Wczytuje liczbę dwójkową
//-------------------------
char rb()
{
  char w = 0, *p, t[9];

  printf("Liczba binarna (max 8 cyfr) = ");
  scanf("%8s",t);
  for (p = t; *p; p++)
  {
    w += w;
    w += *p - '0';
  }

  return w;
}

// Wyświetla v jako liczbę dwójkową
//--------------------------------
void pb(char v)
{
  unsigned maska;

  for(maska = 0b10000000; maska; maska >>= 1)
    if(v & maska) putchar('1');
    else          putchar('0');
}

int main()
{
  char a = rb();
  char b = rb();

  setlocale(LC_ALL,"");

  printf("\n "); pb(a);
  printf("\n| "); pb(b);
  printf("\n-----------");
  printf("\n "); pb(a | b);
  printf("\n\n");

  return 0;
}

Bitowa koniunkcja

Podobnie jak alternatywa bitowa jest to operacja dwuargumentowa na odpowiadających sobie bitach obu argumentów.

a b a & b
0 0 0
0 1 0
1 0 0
1 1 1

Na przykład:

  1110001001
& 0011010111
  0010000001

Uruchom program:

/*
 Bity
 (C)2016 mgr Jerzy Wałaszek
 Data utworzenia: 19.10.2016
*/

#include <stdio.h>
#include <stdlib.h>
#include <locale.h>

// Wczytuje liczbę dwójkową
//-------------------------
char rb()
{
  char w = 0, *p, t[9];

  printf("Liczba binarna (max 8 cyfr) = ");
  scanf("%8s",t);
  for (p = t; *p; p++)
  {
    w += w;
    w += *p - '0';
  }

  return w;
}

// Wyświetla v jako liczbę dwójkową
//--------------------------------
void pb(char v)
{
  unsigned maska;

  for(maska = 0b10000000; maska; maska >>= 1)
    if(v & maska) putchar('1');
    else          putchar('0');
}

int main()
{
  char a = rb();
  char b = rb();

  setlocale(LC_ALL,"");

  printf("\n "); pb(a);
  printf("\n& "); pb(b);
  printf("\n-----------");
  printf("\n "); pb(a & b);
  printf("\n\n");

 return 0;
}

Bitowa różnica symetryczna

W języku C dla bitów zdefiniowana jest dodatkowa funkcja, zwana różnicą symetryczną lub sumą modulo 2. Jest to operacja dwuargumentowa wykonywana na odpowiadających sobie bitach obu argumentów. Tabelka jest następująca:

a b a ^ b
0 0 0
0 1 1
1 0 1
1 1 0

Na przykład:

  1110001001
^ 0011010111
  1101011110

Uruchom program:

/*
 Bity
 (C)2016 mgr Jerzy Wałaszek
 Data utworzenia: 19.10.2016
*/

#include <stdio.h>
#include <stdlib.h>
#include <locale.h>

// Wczytuje liczbę dwójkową
//-------------------------
char rb()
{
  char w = 0, *p, t[9];

  printf("Liczba binarna (max 8 cyfr) = ");
  scanf("%8s",t);
  for (p = t; *p; p++)
  {
    w += w;
    w += *p - '0';
  }

  return w;
}

// Wyświetla v jako liczbę dwójkową
//--------------------------------
void pb(char v)
{
  unsigned maska;

  for(maska = 0b10000000; maska; maska >>= 1)
    if(v & maska) putchar('1');
    else          putchar('0');
}

int main()
{
  char a = rb();
  char b = rb();

  setlocale(LC_ALL,"");

  printf("\n "); pb(a);
  printf("\n^ "); pb(b);
  printf("\n-----------");
  printf("\n "); pb(a ^ b);
  printf("\n\n");

  return 0;
}

Przesunięcie bitów w lewo

Operator przesunięcia bitowego w lewo << ma następującą składnię:

wyrażenie1 << wyrażenie2

Działanie jest następujące:

Komputer wylicza wartości wyrażenia1 i wyrażenia2. Następnie bity wyrażenia1 są przesuwane w lewo o tyle pozycji, ile wynosi wyrażenie2. Na pozycje najmłodszych bitów wstawiane są bity o wartości 0.

Na przykład:

00000101 << 0 = 00000101
00000101 << 1 = 00001010
00000101 << 2 = 00010100
00000101 << 3 = 00101000
...

Uruchom program:

/*
 Bity
 (C)2016 mgr Jerzy Wałaszek
 Data utworzenia: 19.10.2016
*/

#include <stdio.h>
#include <stdlib.h>
#include <locale.h>

// Wczytuje liczbę dwójkową
//-------------------------
unsigned rb()
{
  char *p, t[9];
  unsigned w = 0;

  printf("Liczba binarna (max 8 cyfr) = ");
  scanf("%8s",t);
  for (p = t; *p; p++)
  {
    w += w;
    w += *p - '0';
  }

  return w;
}

// Wyświetla v jako liczbę dwójkową
//--------------------------------
void pbn(unsigned v, unsigned b)
{
  unsigned maska;

  for(maska = 1 << b; maska; maska >>= 1)
    if(v & maska) putchar('1');
    else          putchar('0');
}

int main()
{
  unsigned a = rb();
  int i;

  setlocale(LC_ALL,"");

  printf("\n");
  for(i = 0; i <= 7; i++)
  {
    pbn(a,8);
    printf(" << %d = ",i);
    pbn(a << i, 16);
    printf("\n");
  }

  return 0;
}

Jeśli argument1 potraktujemy jako liczbę, to przesunięcie bitów o 1 pozycję w lewo odpowiada pomnożeniu przez 2, o 2 pozycje w lewo odpowiada pomnożeniu przez 4, a przesunięcie o k pozycji w lewo odpowiada pomnożeniu przez 2k. Operacja przesunięcia bitowego jest wykonywana bardzo szybko w przeciwieństwie do operacji mnożenia i warto o tym pamiętać, szczególnie przy programowaniu prostych mikrokontrolerów, które mogą nie posiadać sprzętowego mnożenia i trzeba je wykonywać programowo, co zajmuje cenny czas.

Przesunięcie bitowe, jak każdy operator dwuargumentowy, można stosować w operacji modyfikacji zmiennej:

a <<= b; odpowiada a = a << b; czyli jest to przesunięcie bitów o b pozycji w lewo w zmiennej a.

Przesunięcie bitowe w prawo

Operator przesunięcia bitowego w prawo posiada następującą składnię:

wyrażenie1 >> wyrażenie2

Działanie operatora jest następujące:

Obliczane są oba wyrażenia. Wynikiem jest wyrazenie1 z przesuniętymi w prawo bitami o tyle pozycji, ile wynosi wartość wyrażenia2. Bity, które wysuwają sie poza bit b0 są tracone. Na pozycję najstarszych bitów są wprowadzane:

Na przykład:

00011101 >> 0 = 00011101
00011101 >> 1 = 00001110
00011101 >> 2 = 00000111
00011101 >> 3 = 00000011
...

Przesunięcie bitów o 1 pozycję w prawo odpowiada podzieleniu całkowitoliczbowemu argumentu1 przez 2, przesunięcie o k bitów w prawo odpowiada podzieleniu przez 2k. Zwróć uwagę, że liczby int w kodzie U2 są przesuwane z kopiowaniem bitu znakowego.

Na przykład:

10011100 >> 1 = 11001110 :  -100 / 2 = -50
10011100 >> 2 = 11100111 :  -100 / 4 = -25
10011100 >> 3 = 11110011 :  -100 / 8 = -13

Wynik jest zaokrąglany do liczby całkowitej.

/*
 Bity
 (C)2016 mgr Jerzy Wałaszek
 Data utworzenia: 19.10.2016
*/

#include <stdio.h>
#include <stdlib.h>
#include <locale.h>

// Wczytuje liczbę dwójkową
//-------------------------
unsigned rb()
{
  char *p, t[17];
  unsigned w = 0;

  printf("Liczba binarna (max 16 cyfr) = ");
  scanf("%16s",t);
  for (p = t; *p; p++)
  {
    w += w;
    w += *p - '0';
  }

  return w;
}

// Wyświetla v jako liczbę dwójkową
//--------------------------------
void pb(unsigned v)
{
  unsigned maska;

  for(maska = 1 << 15; maska; maska >>= 1)
    if(v & maska) putchar('1');
    else          putchar('0');
}

int main()
{
  unsigned a = rb();
  int i;

  setlocale(LC_ALL,"");

  printf("\n");
  for(i = 0; i <= 7; i++)
  {
    pb(a);
    printf(" >> %d = ",i);
    pb(a >> i);
    printf("\n");
  }

  return 0;
}

Na początek:  podrozdziału   strony 

Operacje na bitach

Istnieją cztery podstawowe operacje na bitach:
  1. Odczyt stanu bitu.
  2. Negacja bitu.
  3. Ustawienie bitu.
  4. Zerowanie bitu.

Operacje te realizujemy za pomocą poznanych funkcji bitowych. Wprowadźmy pojęcie maski bitowej. Jest to wartość binarna, w której tylko jeden bit jest ustawiony na 1 lub tylko 1 bit ma wartość 0. Maska posiada zawsze tyle samo bitów, co dana, an której wykonujemy określoną operację bitową.

Przykład:

00010000 – 8-bitowa maska z ustawionym bitem b4.
11111011 – 8-bitowa maska z wyzerowanym bitem b2.

Maskę pierwszego typu tworzymy za pomocą operacji przesunięcia bitowego w lewo:

1 << nr_bitu

Maskę drugiego typu tworzymy podobnie, lecz dodatkowo negujemy jej bity:

~(1 << nr_bitu)

Odczyt stanu bitu

Aby odczytać stan bitu o numerze b (bity numerujemy od 0) w argumencie a, tworzymy maskę z ustawionym bitem o numerze b (pierwszy typ maski), a następnie wykonujemy bitową koniunkcję maski oraz argumentu a. Jeśli wynik jest różny od 0, to bit b argumentu a ma wartość 1, w przeciwnym razie bit b argumentu a ma wartość 0:

a & (1 << b)

Na przykład:

  11100010
& 00100000
  00100000
 
  11000010
& 00100000
  00000000

Uruchom program:

/*
 Bity
 (C)2016 mgr Jerzy Wałaszek
 Data utworzenia: 21.10.2016
*/

#include <stdio.h>
#include <stdlib.h>
#include <locale.h>

// Wczytuje liczbę dwójkową
//-------------------------
unsigned rb()
{
  char *p, t[9];
  unsigned w = 0;

  printf("Liczba binarna (max 8 cyfr) = ");
  scanf("%8s", t);
  for (p = t; *p; p++)
  {
    w += w;
    w += *p - '0';
  }

  return w;
}

// Wyświetla v jako liczbę dwójkową
//--------------------------------
void pb(unsigned v)
{
  unsigned maska;

  for(maska = 1 << 7; maska; maska >>= 1)
    if(v & maska) putchar('1');
    else          putchar('0');
}

int main()
{
  unsigned a = rb();
  int i;

  setlocale(LC_ALL,"");

  printf("\n");
  for(i = 0; i <= 7; i++)
  {
    pb(a);
    printf(" : bit nr %d = %d\n", i, a & (1 << i) ? 1 : 0);
  }

  return 0;
}

Negacja bitu

Operacja negacji bitu b argumentu a polega na wykonaniu nad argumentem a i maską z stawionym bitem na pozycji b (pierwszy typ maski) operacji bitowej różnicy symetrycznej:

a ^ (1 << b)

Na przykład:

  11100010
00100000
  11000010
 
  11000010
& 00100000
  11100010

Uruchom program:

/*
 Bity
 (C)2016 mgr Jerzy Wałaszek
 Data utworzenia: 21.10.2016
*/

#include <stdio.h>
#include <stdlib.h>
#include <locale.h>

// Wczytuje liczbę dwójkową
//-------------------------
unsigned rb()
{
  char *p, t[9];
  unsigned w = 0;

  printf("Liczba binarna (max 8 cyfr) = ");
  scanf("%8s", t);
  for (p = t; *p; p++)
  {
    w += w;
    w += *p - '0';
  }

  return w;
}

// Wyświetla v jako liczbę dwójkową
//--------------------------------
void pb(unsigned v)
{
  unsigned maska;

  for(maska = 1 << 7; maska; maska >>= 1)
    if(v & maska) putchar('1');
    else          putchar('0');
}

int main()
{
  unsigned a = rb();
  int i;

  setlocale(LC_ALL,"");

  printf("\n");
  for(i = 0; i <= 7; i++)
  {
    pb(a);
    printf(" : negacja bitu nr %d = ", i);
    pb(a ^ (1 << i));
    printf("\n");
  }

  return 0;
}

Zerowanie bitu

Wyzerowanie bitu b argumentu a uzyskamy wykonując bitową operację koniunkcji nad argumentem a i maską z wyzerowanym bitem na pozycji b (drugi typ maski):

a & ~(1 << b)

Na przykład:

  11100010
& 11011111
  11000010
 
  11000010
& 11011111
  11000010

Uruchom program:

/*
 Bity
 (C)2016 mgr Jerzy Wałaszek
 Data utworzenia: 21.10.2016
*/

#include <stdio.h>
#include <stdlib.h>
#include <locale.h>

// Wczytuje liczbę dwójkową
//-------------------------
unsigned rb()
{
  char *p, t[9];
  unsigned w = 0;

  printf("Liczba binarna (max 8 cyfr) = ");
  scanf("%8s", t);
  for (p = t; *p; p++)
  {
    w += w;
    w += *p - '0';
  }

  return w;
}

// Wyświetla v jako liczbę dwójkową
//--------------------------------
void pb(unsigned v)
{
  unsigned maska;

  for(maska = 1 << 7; maska; maska >>= 1)
    if(v & maska) putchar('1');
    else          putchar('0');
}

int main()
{
  unsigned a = rb();
  int i;

  setlocale(LC_ALL,"");

  printf("\n");
  for(i = 0; i <= 7; i++)
  {
    pb(a);
    printf(" : zerowanie bitu nr %d = ", i);
    pb(a & ~(1 << i));
    printf("\n");
  }

 return 0;
}

Ustawianie bitu

Ustawienie na 1 bitu b argumentu a uzyskamy wykonując bitową operację alternatywy nad argumentem a i maską z ustawionym bitem na pozycji b (pierwszy typ maski):

a | (1 << b)

Na przykład:

  11100010
| 00100000
  11100010
 
  11000010
| 00100000
  11100010

Uruchom program:

/*
 Bity
 (C)2016 mgr Jerzy Wałaszek
 Data utworzenia: 21.10.2016
*/

#include <stdio.h>
#include <stdlib.h>
#include <locale.h>

// Wczytuje liczbę dwójkową
//-------------------------
unsigned rb()
{
  char *p, t[9];
  unsigned w = 0;

  printf("Liczba binarna (max 8 cyfr) = ");
  scanf("%8s", t);
  for (p = t; *p; p++)
  {
    w += w;
    w += *p - '0';
  }

  return w;
}

// Wyświetla v jako liczbę dwójkową
//--------------------------------
void pb(unsigned v)
{
  unsigned maska;
  for(maska = 1 << 7; maska; maska >>= 1)
    if(v & maska) putchar('1');
    else          putchar('0');
}

int main()
{
  unsigned a = rb();
  int i;

  setlocale(LC_ALL,"");

  printf("\n");
  for(i = 0; i <= 7; i++)
  {
    pb(a);
    printf(" : ustawienie bitu nr %d = ", i);
    pb(a | (1 << i));
    printf("\n");
  }

  return 0;
}

Zwróć uwagę, że we wszystkich wyrażeniach argument a występuje na początku wyrażenia. Umożliwia to zastosowanie modyfikacji zmiennej:


Ćwiczenia

  1. Napisz program, który ustawia bit b0 i zeruje bit b7 zmiennej.
  2. Napisz program, który obraca bity zmiennej typu char w lewo. Obrót polega na przesunięciu bitów o jedna pozycję w lewo i umieszczeniu w b0 poprzedniej wartości b7.
  3. Napisz program, który ze zmiennej 8-bitowej usuwa najmłodsze bity o wartości 0 przez przesuwanie zawartości zmiennej w prawo.

Zapraszam do następnego rozdziału.


Na początek:  podrozdziału   strony 

Zespół Przedmiotowy
Chemii-Fizyki-Informatyki

w I Liceum Ogólnokształcącym
im. Kazimierza Brodzińskiego
w Tarnowie
ul. Piłsudskiego 4
©2024 mgr Jerzy Wałaszek

Materiały tylko do użytku dydaktycznego. Ich kopiowanie i powielanie jest dozwolone
pod warunkiem podania źródła oraz niepobierania za to pieniędzy.

Pytania proszę przesyłać na adres email: i-lo@eduinf.waw.pl

Serwis wykorzystuje pliki cookies. Jeśli nie chcesz ich otrzymywać, zablokuj je w swojej przeglądarce.

Informacje dodatkowe.