Język C

Witam, witam.

Jako student pierwszego roku elektroniki muszę przyswoić sobie język C w możliwie krótkim czasie. Mam już pewne doświadczenie w programowaniu (lua, dawno temu visual basic), tym niemniej wykładowca przedstawiający kilkanaście różnych konstrukcji i operatorów w ciągu dwugodzinnego wykładu za pomocą piętnastoletnich bazgrołów nie ułatwia przyswojenia materiału. Dlatego proszę was, doświadczonych w bojach programistów VCMI, o rady w doborze odpowiedniej książki, w której wszystko odnośnie tego języka byłoby opisane. Nie potrzebuję podstaw, tylko wyczerpującego opisu z przykładami.
Niestety prowadzący nie polecił żadnej pozycji :-/

Niestety nie znam porządnie praktycznie jakichkolwiek pozycji do nauki czystego C i chyba jest ich w ogóle względnie niewiele. Ja sam C mam o tyle opanowany, iż jest on w dużym stopniu podzbiorem C++. Ale w twojej sytuacji, przynajmniej na razie, raczej nie widzę sensu nauki C++.
Język C jest prosty, tylko że dość niskopoziomowy i wymaga nieco innego podejścia niż lua czy vbasic. Żeby dobrze opanować język trzeba przede wszystkim samemu jak najwięcej z nim pracować, kompilować, oglądać i modyfikować przykłady, samemu sprawdzać działanie różnych mechanizmów itd. Samo czytanie książki nic nie da.
W internecie jest sporo materiałów do nauki C, także w języku polskim. O tyle są dobre, że można łatwo przekopiować sobie przykłady i na nich operować.

Nawet nie najgorszy tutorial jest na wikibooks: pl.wikibooks.org/wiki/C
Dość skrótowy, ale może lepszy do szybkiego ogarnięcia składni C: physics.drexel.edu/students/ … /C_basics/ (ang)
Nieco bardziej wyczerpujący: www2.its.strath.ac.uk/courses/c/ (ang)
Poza tym sprawdź PW :->

A jakby co - także i tutaj mogę służyć jakąś drobną pomocą lub wyjaśnieniami.

Na co uważać? Jak zaczynałem programowanie to głównie gubiłem średniki, ale akurat tego typu błędy kompilator wyłapuje bez problemu. Pamiętać należy, że operator ‘=’ służy do przypisywania (i wartością wyrażenia a=b jest b) i nigdy nie należy nim porównywać (od stwierdzenia czy dwa elementy sa równe jest ==). Łatwo się sypnąć, a taki błąd nie spowoduje błędu kompilacji.
Poza tym - inaczej niż w lui - numeracja elementów w tablica zaczyna się od 0. Trzeba uważać, żeby nie wyjść przypadkiem poza zakres (ostatni element n-elementowej tablicy ma indeks n-1).
Bolączką C są niewygodne operacje na stringach (nie funkcjonują one w ogóle jako osobny typ). Obsługa wejścia/wyjścia także bywa niewygodna.

No i są jeszcze ciekawostki, typu przemienność operatora ] (zapis tab[5] jest równoważny 5[tab]) - ale raczej na razie nie masz co sobie nimi zaprzątać głowy, chyba że prowadzący naprawdę lubi się znęcać nad wami :wink:

Ja jeszcze od siebie dodam, że kiedyś korzystałem z całkiem dobrego opisu biblioteki standardowej C. Obecnie jest niestety tylko w takiej formie: sciaga.pl/prace/getattach.html?aid=5559 , ale bardzo ładnie (i po polsku) opisuje różne przydatne funkcje.

Przy okazji potwierdzam, że należy bardzo uważać na wyjścia poza zakres. Szczególnie w przypadku np. takim:

struct jakas_tam
{
   int a;
   int tablica[64];
   int b;  
};

W takim przypadku pisząc do 64. elementu tablicy tablica nie wysypiemy programu, tylko najprawdopodobniej (nigdy nie zagłębiałem się w tajniki wyrównywania pól w klasach i strukturach) nadpiszemy pole b.

Warmongerze, ze swojej strony polecam ci stronę 4programmers.net - Programowanie. W sekcji katalog masz opcje: C/C+ , ewentualnie C#. Mam nadzieję, że dobrze podpowiedziałem?

Wielkie dzięki, wszystko co tutaj zapodaliście bardzo mi się przyda w nauce. Pewnie jest tego więcej, niż kiedykolwiek będę miał okazję użyć.

Tym niemniej jednak muszę mieć jakąś książkę, żeby się nią posiłkować w czasie wykładu / ćwiczeń :wink:

Myślę że “Stephen Prata - Język C. Szkoła programowania” się nada jeśli chodzi o książkę.

No dobra, oto mój pierwszy program badający, czy wprowadzona liczba jest pierwsza.

#include <math.h>

void  main (void)
  {
  long i=2, x;
  double pierw;
  printf ("Wprowadz liczbe, ktora chcesz sprawdzic ");
  scanf ("%d",x);
  if (x == 2)
    printf ("Liczba 2 jest oczywscie pierwsza, wysil sie bardziej");
  else
  pierw = sqrt(x);
  while (i<=pierw);
    {
    if ((x%i) == 0)
      {
      printf ("Liczba nie jest pierwsza");
      break;
      }
    else
    i++;
    }
  printf ("Liczba jest pierwsza");
  getchar ();
  }

Niestety, przy kompilacji zwraca dwa błędy, których nie udało mi się rozkminić. Poprzednie 8 już wyłapałem :wink:

pierwsza.c:19: error: break statement not within loop or switch
pierwsza.c:5: warning: return type of ‘main’ is not `int’

Błąd jest tu: while (i<=pierw);
Trzeba zlikwidować średnik. Bo obecną wersję kompilator uzna za pętlę z pustym ciałem (średnik oznacza pustą instrukcję). Tak więc dopóki i<=pierw wykonywałoby się to nic. Czyli potencjalnie pętla nieskończona. Break powoduje błąd kompilacji, bo blok pod whilem kompilator uznał za niezwiązany z żadną pętlą (bo pobliski while się zadowolił pustą instrukcją).

Kolejny błąd jest we wczytywaniu - scanf ("%d",x); - to w najlepszym wypadku nie zadziała.
Trzeba zamienić x na &x (& służy do pobrania adresu zmiennej). Funkcji scanf należy wysłać adres zmiennej x (żeby scanf wiedział gdzie zapisać wczytaną liczbę), a nie jej wartość. W C argumenty funkcjom są przekazywane tylko przez wartość (czyli nie wysyłasz zmiennej, a jedynie to, co jest w niej zapisane - jej wartość).

Poza tym inne problemy. Na samym początku wyodrębniasz przypadek dla liczby 2. Wtedy wypisujesz, że jest ona oczywiście pierwsza, ale i tak wykonujesz dla niej prawie całą następną procedurę. To co jest po pierwszym else aż do prawie końca programu powinna być ujęte w klamry, bo wszystko to jest dla liczb różnych od 2 (a bez klamer else łapie tylko najbliższą instrukcję).

Dalej, instrukcja break oznacza wyjście z bloku pętli. Więc dla liczb nie-pierwszych wypisany zostanie w końcu odpowiedni komunikat, a pętla się przerwie… po czym w pierwszej instrukcji za pętlą program wypisze, że liczba jest pierwsza. Ta instrukcja generalnie wykona się absolutnie zawsze, program nie ma jak jej obejść przed dojściem do końca.

Co do ostrzeżenia, że “warning: return type of ‘main’ is not `int’”, to w sumie wszystko masz na tacy.
Funkcja main powinna mieć postać int main (void). [z int a nie void na początku]
I program powinien się kończyć instrukcją return 0; która sygnalizuje, że program się poprawnie wykonał.
Niemniej jest to ostrzeżenie, więc kompilator przeżyje, jeśli tego nie poprawisz (choć wypadałoby).

Jeszcze jeden drobiazg - getchar() na końcu może nie być zbyt szczęśliwym rozwiązaniem. Funkcja ta ma swoje niuanse i program będzie Ci uciekać (bo złapie twój “enter” po podaniu liczby). Żeby osiągnąć efekt postaw drugie getchar(); pod spodem, albo użyj jakiejś innej funkcji.
Jeżeli programujesz pod windows i nie zależy Ci na przenośności, to bardzo wygodnym rozwiązaniem jest tu system(“pause”); choć ładne nie jest.

Mam nadzieję, że nie pogmatwałem przesadnie :wink:

Coraz bardziej utwierdzam się w przekonaniu, że C nie jest fajny.

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

int main (void)
  {
  long i=2, x;
  double pierw;
  printf ("Wprowadz liczbe, ktora chcesz sprawdzic ");
  scanf ("%d\n",&x);
  if (x == 2)
    printf ("Liczba 2 jest oczywscie pierwsza, wysil sie bardziej");
  else
   {
  pierw = sqrt(y);
  char jest='t';
  while (i<=pierw)
    {
    if (x%i == 0)
      {
      jest = 'n';
      break;
      }
    i++;
    }
  if (jest ='t')
    printf ("Liczba nie jest pierwsza");
  else
    printf ("Liczba jest pierwsza");
   }
  return 0;
  }

Zauważyłem, że kod wypluwa odwrotny komunikat ;), ale to mały problem.

Tego komunikatu ni w ząb nie rozumiem.

E tam, jest fajny, tylko że trzeba się z nim najpierw zaprzyjaźnić :wink:

Faktycznie cudaczny. Czym to kompilujesz? Bo mi zarówno MSVS jak i G CC dawały dość sensowne pretensje o niezdefiniowanie zmiennej y.

Odnośnie programu:

  1. pierw = sqrt(y);
    Winno być sqrt(x), zmiennej y w ogóle nie ma.

  2. scanf ("%d\n",&x);
    Wywal to \n ze scanfa. Nie wiem do czego konkretnie chcesz tym zmusić scanfa, ale jest to zbędne. W obecnym kształcie nie zadziała jak powinno (biały znak w scanfie IIRC powoduje, że funkcja będzie wisieć i żreć wszystko, dopóki nie napotka nie-białego znaku).

  3. (jest =‘t’)
    Przestrzegałem przed tym w pierwszym poście - tu jest przypisanie zamiast porównania. A że takie przypisanie ma zawsze wartość logiczną prawda (bo ‘t’ != 0), to będzie wchodzić do tego if-a niezależnie wartości od zmiennej jest.

  4. No i komunikat - jak wspomniałeś - jest na odwrót :wink:

Oczywiscie, C ma swoje wady, jednak w pewnym obszarze zastosowań jest najlepszym dostępnym (projekty informatyczne nastawione zdecydowanie na szybkość działania, jednak wykluczające pisanie wszystkiego w assemblerze). Poza tym naprawdę ciężko o osobę lubiącą język programowania, którego nie zna…

Hmm, pierwszy raz w życiu widzę, aby dopiero linker pluł się o niezadeklarowanie zmiennej. Co ty zrobiłeś kompilatorowi, że to przepuścił?

Gdybym miał powiedzieć, co tu jest grane, to bym strzelał, że gdzieś pojawiła się linijka

extern int y;
  • albo, że kompilator sam ją sobie wygenerował, ayb utrudnić szukanie błędu ;].

Używam gcc, bo to jedyny znany mi kompilator na serwerze wydziałowym. Szczerze mówiąc, jestem na niego skazany :confused:

Błędy wskazane przez Towa poprawiłem, ale nie zmienia to rezultatu.

No tak, jestem wyjątkowy. Pierwszy raz piszę w C i już zagiąłem starych wyjadaczy :stuck_out_tongue:

Tak sobie myślę… czy jest szansa, że mam w kodzie jakiś niewidzialny znak specjalny, który psuje to i owo? Ogólnie rzecz biorąc, obsługę edytora tekstu wciąż nie do końca rozumiem :wink: , więc mogłem coś popsuć.

Mógłbyś wkleić tu poprawione źródło? Może ciągle jest gdzieś błąd.

Ja dość niewiele pracowałem pod g cc, głównie MSVC. Nie wiem, czy “stary wyjadacz” to dobre określenie :stuck_out_tongue:

Na ile znam standard języka C oraz kompilator g cc jest to absolutnie wykluczone. Prędzej jakieś dziwne opcje kompilatora. Najlepiej oprócz kodu twojego programu daj nam też komendę, którą go kompilujesz.

Ja stawiam, że faktycznie coś źle ten kompilator obsługujesz. Wklejony przez Ciebie cudaczny błąd jest błędem linkera. Zabawa polega na tym, że nie powinno do niego dojść - powinno się wysypać przed linkowaniem na etapie kompilacji.

Sam błąd linkera, to niezdefiniowanie funkcji sqrt. G CC wymaga widać, by jawnie podsunąć mu do zlinkowania odpowiedni kawałek biblioteki standardowej C. Google w takiej sytuacji dodać do polecenia, którym budujesz program parametr

-lm

co jest widać wymagane przy korzystaniu z funkcji matematycznych.
(Sam na tym polu się słabo orientuję, moja znajomość g cc jest powierzchowna)

Młodzi wyjadacze? :stuck_out_tongue:

Bo ja wiem… C++ przy umiejętnym używaniu powinien być przynajmniej równie szybki.

-lm pomogło, wielkie dzięki :wink:

Co nie zmienia faktu, że program w obecnej postaci

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

int main (void)
  {
  long i=2, x;
  double pierw;
  printf ("Wprowadz liczbe, ktora chcesz sprawdzic\n");
  scanf ("%d",&x);
  if (x == 2)
    printf ("\n Liczba 2 jest oczywscie pierwsza, wysil sie bardziej");
  else
   {
  pierw = sqrt(x);
  char jest='t';
  while (i<=pierw)
    {
    if (x%i == 0)
      {
      jest = 'n';
      break;
      }
    i++;
    }
  if (jest =='n')
    printf ("\n Liczba nie jest pierwsza");
  else
    printf ("\n Liczba jest pierwsza");
   }
  getchar ();
  return 0;
  }

Po wpisaniu liczby produkuje tylko puste linie :confused: Bez \n i/lub getchar() na końcu też.

Dziwne, przy kompilacji kodu w C++ prawie na pewno tego nie ma.

Już lepiej brzmi ;]

Jeśli będziesz korzystał głównie z tego, co już masz w C… generalnie stosując obiektowe sztuczki z C++'a dostaje się jednak troszkę wolniejszy kod (zobacz choćby tu: shootout.alioth.debian.org/gp4/b … &lang2=gpp ). Oczywiście, straty na szybkości są bardzo niewielkie (gorzej z pamięcią, choć jej jest pod dostatkiem obecnie i można optymalizować pod tym kątem tylko to co trzeba), a możliwość pisania ładniejszego kodu jest znaczącym zyskiem.

Mi tam ten program działa, jak kompiluję pod Dev-Cpp (korzysta z g cc). Jedynym pomysłem, jaki mi przychodzi do głowy, jest to, abyś na końcu wypisał jeszcze jedną pustą linię (tuż przed zakończeniem działania programu). Być może bufor wyjścia nie jest opróżniany i zostaje w nim komunikat o tym, czy liczba jest pierwsza.

Niemożliwe. Program nie ma jak wyjść inaczej, niż wypisując po drodze komunikat o tym, czy liczba jest pierwsza. Sprawdziłem i pod Linuksem i pod Windowsem - twój kod działa.

Musisz albo źle kompilować, albo źle uruchamiać, albo źle wpisywać liczbę.

Ja to zrobiłem tak (środowisko unikspodobne):

Wkleiłem twój kod do nowo utworzonego pliku pliku warm.c. Zbudowałem program do pliku warm poleceniem:

g cc warm.c -o warm -lm

(wywal spację spomiędzy g cc, głupi bug w forum nie pozwala mi tego wpisać razem)

Kompilacja przeszła poprawnie, otrzymałem jedynie komunikat z ostrzeżeniem:
warm.c: In function ‘main’:
warm.c:9: warning: format ‘%d’ expects type ‘int *’, but argument 2 has type ‘long int *’

Następnie uruchomiłem program:

./warm

Zgodnie z oczekiwaniem otrzymałem zapytanie:

Wprowadz liczbe, ktora chcesz sprawdzic

Wpisałem 113, nacisnąłem enter. Otrzymałem:

Liczba jest pierwsza

Spróbuj powielić moje kroki i sprawdź, gdzie pojawia się problem (jeśli w ogóle). Kod jest w porządku (przynajmniej do tego stopnia).

I tak i nie…
C++ nie jest wolniejszym językiem. Wszystko to, co możesz zrobić w bardzo wydajny sposób w C, możesz zrobić identycznie (albo z drobnymi poprawkami) w C++. Wiele z tych rzeczy można zrobić dodatkowo ładniej.
Nowinki składni C++ to w znacznej mierze lukier na to, co można zrobić naokoło w C, tak więc używanie jej nie może być wolniejsze. Przykładowo, wywołanie metody klasy w C++ jest de facto tym samym, co wywołanie funkcji w C, gdzie pierwszym argumentem jest wskaźnik na przetwarzany obiekt struktury.
Faktycznie narzut pojawia się przy tych możliwościach C++, które albo dają większe możliwości od swoich odpowiedników w C, albo w ogóle nie mają odpowiedników. Ot, choćby strumienie wejścia/wyjścia w C++ są wolniejsze, ale dają też większe bezpieczeństwo. Jeżeli nam zależy na szybkości, korzystamy z biblioteki C i prędkość zachowujemy. Funkcje wirtualne są nowinką mającą swoje narzuty, ale implementacja czegoś podobnego w C darmowa też by nie była (a ile roboty).

Testy które podałeś nie są wiarygodne, pisane ewidentnie pod C. Choćby Hello World na którym C ma taką przewagę - porównajmy źródła:

Kod C:

/* The Computer Language Benchmarks Game
 * http://shootout.alioth.debian.org/
 * contributed by Joe Tucek 2008-03-31
 *
 * Tell G CC that we don't want atexit, we don't want to use the heap,
 * and we really don't want anything.  Can't even call write the "normal"
 * way, because write() isn't linked in....
 *
 * Compile flags are picky for this.  I used:
 * g cc -pipe -Wall -O3 -fomit-frame-pointer -march=pentium4 -ffreestanding -nostartfiles -s -static -o start3 start3.c
 */

#include <sys/syscall.h>
#include <unistd.h>

int _start() {
  syscall(__NR_write, 1, "hello world\n", 12);
  syscall(__NR_exit, 0);
  return(0);
}

Kod C++:

// -*- mode: c++ -*-
// $Id: hello.1.gpp.code,v 1.1 2008-08-06 18:34:19 igouy-guest Exp $
// http://shootout.alioth.debian.org/

#include <iostream>

using namespace std;

int main() {
    cout << "hello world" << endl;
    return(0);
}

To ma być niby równoważne?
Jakbym tak przekleił kod z C do C++, a w C zrobił metodą standardową, to nie zdziwiłbym się, gdyby wyniki się odwróciły.

Ja sam nie bardzo widzę powody, dla których ktoś miałby używać C, jeżeli ma możliwość użycia C++. C++ pozwala zrobić to samo i wiele więcej,

Wielkie dzięki, wszystko już działa - wystarczyło dodać pustą linijkę.

Teraz już czuję się pewnie, zacząłem optymalizować skrypty napisane przez innych studentów :wink:

A ja mam pytanie od siebie.
Właśnie zamierzam się uczyć programowania. I co mi radzicie . Pascala czy C