Source: https://www.cs.uky.edu/~raphael/programming.html
Od uczniów młodszych i starszych klas CS oczekuje się znajomości podstawowych informacji na temat języków C , Unix i narzędzi do tworzenia oprogramowania . Na tej stronie szczegółowo opisano niektóre z tych zagadnień i zasugerowano kilka ćwiczeń .
Table of contents
C
- C pozwala deklarować zmienne poza jakąkolwiek procedurą. Zmienne te nazywane są zmiennymi globalnymi .
- Zmienna globalna jest przydzielana jednorazowo podczas uruchamiania programu i pozostaje w pamięci aż do zakończenia programu.
- Zmienna globalna jest widoczna dla wszystkich procedur w tym samym pliku.
- Możesz sprawić, że zmienna globalna zadeklarowana w pliku Ac będzie widoczna dla wszystkich procedur w niektórych innych plikach Bc, Cc, Dc, ... deklarując ją z modyfikatorem extern w plikach Bc, Cc, Dc, ... jak w tym przykładzie:
extern in theVariable
Jeśli masz wiele plików współdzielących tę zmienną, powinieneś zadeklarować ją jako extern w pliku nagłówkowym foo.h (opisanym poniżej ) i użyć #include foo.h w plikach Bc, Cc, Dc, ... . Musisz zadeklarować zmienną w dokładnie jednym pliku bez modyfikatora extern , w przeciwnym razie w ogóle nie zostanie ona przydzielona. - Nie jest dobrym pomysłem używanie zbyt wielu zmiennych globalnych, ponieważ nie można zlokalizować miejsc, w których są one dostępne. Są jednak sytuacje, w których zmienne globalne pozwalają uniknąć przekazywania wielu parametrów do funkcji.
- Ciągi znaków są wskaźnikami do tablic znaków zakończonych znakiem null.
- Deklarujesz ciąg jako char *variableName .
- Jeśli Twój ciąg znaków jest stały, możesz po prostu przypisać do niego literał ciągu:
char *myString = "To jest przykładowy ciąg znaków"; - Pusty ciąg ma tylko terminator zerowy:
mójCiąg = ""; // pusty ciąg znaków o długości 0, zawierający wartość null
Wskaźnik zerowy nie jest prawidłową wartością ciągu:
mójCiąg = NULL; // nieprawidłowy ciąg
Możesz użyć takiej wartości, aby wskazać koniec tablicy ciągów:
argv[0] = "nazwa programu";
argv[1] = "pierwszyparametr";
argv[2] = "drugiparametr";
- argv[3] = NULL; //terminator
Jeśli ciąg znaków jest obliczany w czasie wykonywania, musisz zarezerwować wystarczająco dużo miejsca, aby go przechowywać. Pokój musi wystarczyć, aby pomieścić wartość null na końcu:
char *mójString;
mójString = (char *) malloc(strlen(jakiśCiąg) + 1); // przydziel miejsce
- strcpy(myString, jakiśString); // skopiuj SomeString do myString
- Aby uniknąć wycieków pamięci, powinieneś w końcu zwrócić miejsce przydzielone za pomocą malloc , używając free . Jego parametrem musi być początek spacji zwróconej przez malloc :
free((void *) mójString);
Aby zachować czystość i czytelność kodu, powinieneś wywołać free() w tej samej procedurze, w której wywołujesz malloc() ; możesz wywołać inne procedury między tymi dwoma punktami, aby manipulować ciągiem. - Jeśli kopiujesz ciągi znaków, powinieneś bardzo uważać, aby nigdy nie kopiować więcej bajtów, niż może pomieścić docelowa struktura danych. Przepełnienia bufora są najczęstszą przyczyną luk w zabezpieczeniach programów. W szczególności rozważ użycie strncpy() i strncat() zamiast strcpy() i strcat() .
Jeśli używasz C++, musisz przekonwertować obiekty łańcuchowe na ciągi w stylu C przed przekazaniem ich w wywołaniu systemowym.
string myString // Ta deklaracja działa tylko w C++
...
- SomeCall(myString.c_str())
Niestety, c_str() zwraca niezmienny ciąg znaków. Jeśli potrzebujesz modyfikowalnego ciągu, możesz skopiować dane za pomocą strcpy() (jak powyżej) lub możesz rzutować typ:
SomeCall(const_cast(myString.c_str()))
Rzutowanie nie jest tak bezpieczne jak kopiowanie, ponieważ funkcja SomeCall() może w rzeczywistości zmodyfikować ciąg znaków, co mogłoby zmylić każdą część programu, która zakłada, że myString jest stała, co jest typowym zachowaniem ciągów C++.
- Bufor to obszar pamięci pełniący rolę pojemnika na dane . Mimo że dane mogą mieć interpretację (taką jak tablica struktur z wieloma polami), programy, które buforują odczyt i zapis, często traktują je jako tablice bajtów. Tablica bajtów to nie to samo, co ciąg znaków, mimo że oba są zadeklarowane jako char * lub char [] .
- Mogą nie zawierać znaków ASCII i nie mogą być zakończone znakiem null.
- Nie można użyć funkcji strlen() do znalezienia długości danych w buforze (ponieważ bufor może zawierać bajty zerowe). Zamiast tego musisz obliczyć długość danych na podstawie wartości zwracanej przez wywołanie systemowe (zwykle read ), które wygenerowało dane.
- Nie możesz używać strcpy() , strcat() ani podobnych procedur w buforach bajtowych; zamiast tego musisz użyć memcpy() lub bcopy() .
Zapisujesz bufor o wielkości 123 bajtów do pliku, używając kodu takiego jak ten:
char *nazwa pliku = "/tmp/foo"
#zdefiniuj ROZMIAR BUF 4096
char buf[BUFROZMIAR]; // bufor zawierający maksymalnie bajtów BUFSIZE
...
int plik wyjściowy; // deskryptor pliku, mała liczba całkowita
int bajtówToWrite; // liczba bajtów pozostałych do zapisania
char *outPtr = buf;
...
if ((outFile = creat(fileName, 0660)) < 0) [ // błąd
// zobaczuprawnienia do plików , aby zrozumieć 0660
perror(nazwa pliku); // wypisz przyczynę
wyjście(1); // i wyjdź
[
bajtówToWrite = 123; // inicjalizacja; 123 to tylko przykład
while ((bytesWritten = write(outFile, outPtr, bytesToWrite)) < bytesToWrite) [
// nie wszystkie bajty zostały jeszcze zapisane
if (bytesWritten < 0) [ // błąd
perror("zapisz");
wyjście(1);
[
outPtr += bajtyZapisane;
bytesToWrite -= bajty zapisane;
- [
Aby kompilator przydzielił miejsce na bufory, musisz zadeklarować bufor o rozmiarze, który kompilator może obliczyć, jak w
#zdefiniuj ROZMIAR BUF 1024
- char buf[BUFROZMIAR];
Jeśli po prostu zadeklarujesz bufor bez rozmiaru:
char buf[];
wtedy ma nieznany rozmiar i C nie przydziela żadnego miejsca. Jest to dopuszczalne, jeśli buf jest parametrem formalnym (to znaczy pojawia się w nagłówku procedury); rzeczywisty parametr (dostarczony przez osobę wywołującą) ma rozmiar. Ale jest to niedopuszczalne, jeśli buf jest zmienną. Jeśli nie znasz rozmiaru bufora w czasie kompilacji, powinieneś użyć takiego kodu:
char *buf = (char *) malloc(rozmiarbufora);
gdzie buforSize jest wynikiem niektórych obliczeń w czasie wykonywania.
- Można dynamicznie przydzielać i zwalniać pamięć.
Poszczególne instancje dowolnego typu:
typedef ... mójTyp;
mójTyp *mojaZmienna = (mójTyp *) malloc(rozmiar(myTyp));
// możesz teraz uzyskać dostęp do *myVariable.
...
- free((void *) mojaZmienna);
Ponownie, dobrą praktyką programistyczną jest wywoływanie free() w tej samej procedurze, w której wywołujesz malloc() .
Tablice jednowymiarowe dowolnego typu:
mójTyp *myArray = (myTyp *) malloc(Długość tablicy * rozmiar(myTyp));
// myArray[0] .. myArray[arrayLength - 1] są teraz przydzielone.
...
- free((void *) mojaArray);
Tablice dwuwymiarowe są reprezentowane przez tablicę wskaźników, z których każdy wskazuje na tablicę:
myType **myArray = (myType **) malloc(numRows * sizeof(myType *));
int indeks wiersza;
for (rowIndex = 0; rowIndex < numRows; rowIndex += 1) [
myArray[rowIndex] = (myType *) malloc(numColumns * sizeof(myType));
[
// myArray[0][0] .. myArray[0][numColumns-1] .. myArray[numRows-1][numColumns-1]
// są teraz przydzielone. Być może zechcesz je zainicjować.
...
for (rowIndex = 0; rowIndex < numRows; rowIndex += 1) [
free((void *) mojaArray[rowIndex]);
[
free((void *) mojaArray);
- Jeśli używasz C++, nie mieszaj new/delete z malloc/free dla tej samej struktury danych. Zaletą new/delete dla instancji klas jest to, że automatycznie wywołują konstruktory, które mogą inicjować dane, oraz destruktory, które mogą finalizować dane. Kiedy używasz malloc/free , musisz jawnie zainicjować i sfinalizować.
- Liczby całkowite
- C zwykle reprezentuje liczby całkowite w 4 bajtach. Na przykład liczba 254235 jest reprezentowana jako liczba binarna 00000000,00000011,11100001,00011011.
- Z drugiej strony tekst ASCII reprezentuje liczby jak każdy inny znak, z jednym bajtem na cyfrę przy standardowym kodowaniu. W kodzie ASCII liczba 254235 jest reprezentowana jako 00110010, 00110101, 00110110, 00110010, 00110011, 00110101.
- Jeśli chcesz napisać plik liczb całkowitych, ogólnie rzecz biorąc, zarówno pod względem przestrzennym, jak i czasowym, bardziej efektywne jest zapisanie wersji 4-bajtowych niż konwersja ich na ciągi ASCII i zapisanie ich. Oto jak zapisać pojedynczą liczbę całkowitą do otwartego pliku:
write(outFile, &myInteger, sizeof(moja liczba całkowita))
Możesz przyjrzeć się poszczególnym bajtom liczby całkowitej, rzucając ją jako strukturę czterech bajtów:
int adres IP; // przechowywana jako liczba całkowita, rozumiana jako 4 bajty
struktura typedef [
char bajt1, bajt2, bajt3, bajt4;
[ IPSzczegóły_t;
IPDetails_t *details = (IPDetails_t *) (&Adres IP);
printf("bajt 1 to %o, bajt 2 to %o, bajt 3 to %o, bajt 4 to %o\n",
- szczegóły->bajt1, szczegóły->bajt2, szczegóły->bajt3, szczegóły->bajt4);
- Wielobajtowe liczby całkowite mogą być reprezentowane w różny sposób na różnych maszynach. Niektóre (jak Sun SparcStation) umieszczają najbardziej znaczący bajt na pierwszym miejscu; inne (takie jak Intel i80x86 i jego potomkowie) umieszczają najmniej znaczący bajt na pierwszym miejscu. Jeśli piszesz dane w postaci liczb całkowitych, które mogą być odczytywane na innych komputerach, przekonwertuj dane na „sieciową” kolejność bajtów za pomocą funkcji htons() lub htonl() . Jeśli czytasz dane w postaci liczb całkowitych, które mogły zostać zapisane na innych komputerach, przekonwertuj dane z kolejności „sieciowej” na lokalną kolejność bajtów za pomocą ntohs() lub ntohl() .
Możesz przewidzieć układ pamięci struktur i wartość zwróconą przez sizeof() . Na przykład,
struktura foo [
znak a; // wykorzystuje 1 bajt
// C wstawia tutaj 3-bajtową dokładkę, więc b może zaczynać się od 4-bajtowej granicy
int b; // wykorzystuje 4 bajty
bez znaku krótkie c; // wykorzystuje 2 bajty
bez znaku znak d[2]; // wykorzystuje 2 bajty
- [;
Dlatego sizeof(struct foo ) zwraca 12. Ta przewidywalność (dla danej architektury) jest powodem, dla którego niektórzy nazywają C „przenośnym językiem asemblera”. Należy przewidzieć układ struktury podczas generowania danych, które muszą mieć określony format, taki jak nagłówek pakietu sieciowego. - Możesz zadeklarować wskaźniki w C do dowolnego typu i przypisać im wartości wskazujące na obiekty tego typu.
W szczególności C pozwala budować wskaźniki do liczb całkowitych:
int pewna liczba całkowita;
int *intPtr = &pewna liczba całkowita; // deklaruje zmienną o wartości wskaźnika i przypisuje odpowiednią wartość wskaźnika
jakieśWywołanie(intPtr); // przekazuje wskaźnik jako rzeczywisty parametr
- SomeCall(&someInteger); // ma taki sam efekt jak powyżej
- Procedura biblioteki AC, która pobiera wskaźnik do wartości, najprawdopodobniej modyfikuje tę wartość (staje się parametrem „out” lub „in out”). W powyższym przykładzie jest bardzo prawdopodobne, że SomeCall modyfikuje wartość liczby całkowitej SomeInteger .
Można zbudować wskaźnik do tablicy liczb całkowitych i użyć go do przechodzenia przez tę tablicę.
#zdefiniuj ARRAY_LENGTH 100
int intArray[ARRAY_LENGTH];
int *intArrayPtr;
...
suma całkowita = 0;
for (intArrayPtr = intArray; intArrayPtr < intArray+ARRAY_LENGTH; intArrayPtr += 1) [
suma += *intArrayPtr;
- [
Można zbudować wskaźnik do tablicy struktur i użyć go do przechodzenia przez tę tablicę.
#zdefiniuj ARRAY_LENGTH 100
typedef struct [int foo, bar;[ pair_t; // pair_t to nowy typ
para_t strukturaArray[ARRAY_LENGTH]; // structArray jest tablicą elementów ARRAY_LENGTH pair_t
para_t *structArrayPtr; // structArrayPtr wskazuje na element pair_t
...
suma całkowita = 0;
for (structArrayPtr = structArray; structArrayPtr < structArray+ARRAY_LENGTH; structArrayPtr += 1) [
suma += structArrayPtr->foo + structArrayPtr->bar;
- [
- Po dodaniu liczby całkowitej do wskaźnika wskaźnik zostanie przesunięty o określoną liczbę elementów, niezależnie od tego, jak duże są te elementy. Kompilator zna rozmiar i postępuje właściwie.
- Wyjście
- Formatujesz dane wyjściowe za pomocą printf lub jego wariantu, fprintf .
- Ciąg formatujący używa %d , %s , %f do wskazania, że na wyjściu ma zostać umieszczona liczba całkowita, ciąg znaków lub wartość rzeczywista.
- Ciąg formatujący używa \t i \n do wskazania tabulacji i nowej linii.
- Przykład:
printf("Myślę, że liczba %d to %s\n", 13, "szczęśliwa"); - Mieszanie printf() , fprintf() i cout może nie spowodować wydrukowania elementów w oczekiwanej kolejności. Używają niezależnych obszarów przejściowych („buforów”), które drukują, gdy są pełne.
- Procedura main() pobiera parametry funkcji, które reprezentują parametry wiersza poleceń .
- Jednym z powszechnych sposobów pisania głównej procedury jest:
int main(int argc; char *argv[]);
Tutaj argc to liczba parametrów, a argv to tablica ciągów znaków, czyli tablica wskaźników do tablic znaków zakończonych znakiem null.
Zgodnie z konwencją pierwszym elementem argv jest nazwa samego programu.
int main(int argc; char *argv[]);
printf("Mam %d parametrów; nazywam się %s, a mój pierwszy parametr to %s\n",
- argc, argv[0], argv[1]);
- Przydatne funkcje językowe
- Możesz zwiększyć liczbę całkowitą lub ustawić wskaźnik na następny obiekt, używając operatora ++ . Zwykle najlepiej jest umieścić ten operator po zmiennej: myInt++ . Jeśli umieścisz ++ przed zmienną, zmienna będzie zwiększana przed jej oceną, a rzadko jest to, czego chcesz.
Możesz zbudować przypisanie, w którym zmienna po lewej stronie będzie pierwszą częścią wyrażenia po prawej stronie:
mojaInt -= 3; // odpowiednik myInt = myInt - 3
mojaInt *= 42; // odpowiednik myInt = myInt * 42
- mojaInt += 1; // odpowiednik i być może lepszy niż myInt++
- Liczby można wyrażać w systemie dziesiętnym, ósemkowym (poprzedzając cyfrą 0 , jak w przypadku 0453 ) lub szesnastkowo (poprzedzając cyfrą 0x , jak w przypadku 0xffaa ).
Możesz traktować liczbę całkowitą jako zbiór bitów i wykonywać operacje bitowe:
mojaInt = mojaInt | 0444; // bitowy LUB; 0444 jest w formacie ósemkowym
mojaInt &= 0444; // bitowe AND ze skrótem przypisania
- myInt = coś ^ cokolwiek; //bitowy XOR
C i C++ mają wyrażenia warunkowe. Zamiast pisać
jeśli (a < 7)
a = pewna wartość
w przeciwnym razie
- a = inna wartość;
Możesz pisać
a = a < 7? jakaśWartość: jakaśInnaWartość; - Przypisania zwracają wartość po lewej stronie, dzięki czemu można uwzględnić przypisanie w większych wyrażeniach, takich jak wyrażenia warunkowe. Powinieneś jednak przestrzegać konwencji, zgodnie z którą takie przypisania są zawsze otoczone nawiasami, aby wskazać zarówno osobie czytającej Twój kod, jak i kompilatorowi, że tak naprawdę masz na myśli przypisanie, a nie test równości. Na przykład napisz
if ((s = gniazdo(...)) == -1)
nie
if (s = gniazdo(...) == -1)
Druga wersja jest zarówno trudniejsza do odczytania, jak i w tym przypadku niepoprawna, ponieważ operator równości == ma wyższy priorytet niż operator przypisania = .
- Programy, które nie są trywialnie krótkie, powinny być zwykle rozłożone na wiele plików źródłowych , każdy o nazwie kończącej się na .c (w przypadku programów w języku C) lub .cpp (w przypadku programów w języku C++).
- Spróbuj zgrupować funkcje, które manipulują tymi samymi strukturami danych lub mają powiązane cele, w tym samym pliku.
- Wszystkie typy, funkcje, zmienne globalne i stałe manifestu, które są potrzebne więcej niż jednemu plikowi źródłowemu, należy również zadeklarować w pliku nagłówkowym , którego nazwa kończy się na .h .
- Z wyjątkiem funkcji wbudowanych, nie deklaruj treści funkcji (ani czegokolwiek, co powoduje, że kompilator generuje kod lub przydziela miejsce) w pliku nagłówkowym.
- Każdy plik źródłowy powinien odnosić się do potrzebnych plików nagłówkowych za pomocą linii #include .
- Nigdy #nie dołączaj pliku .c .
- Jeśli masz wiele plików źródłowych, musisz połączyć wszystkie skompilowane pliki obiektowe wraz z bibliotekami, których potrzebuje Twój program.
- Najłatwiejszą metodą jest użycie kompilatora C, który zna biblioteki C:
gcc *.o -o mójProgram
To polecenie prosi kompilator o połączenie wszystkich plików obiektowych z biblioteką C (która jest domyślnie dołączona) i umieszczenie wyniku w pliku myProgram , który staje się wykonywalny. - Jeśli Twój program potrzebuje innych bibliotek, powinieneś podać je po plikach obiektowych, ponieważ linker zbiera tylko procedury z bibliotek, o których już wie, że są potrzebne, i łączy pliki w określonej przez Ciebie kolejności. Jeśli więc potrzebujesz biblioteki takiej jak libxml2 , polecenie łączenia powinno wyglądać mniej więcej tak:
gcc *.o -lxml2 -o mójProgram
Kompilator wie, jak przeszukiwać różne standardowe katalogi w poszukiwaniu bieżącej wersji libxml2 .
- Debugowanie programów w języku C
- Jeśli wystąpi błąd segmentacji, najprawdopodobniej masz indeks poza zakresem, niezainicjowany wskaźnik lub wskaźnik zerowy.
- Możesz umieścić instrukcje print w swoim programie, aby pomóc Ci zlokalizować błąd.
- Debugowanie będzie prawdopodobnie najskuteczniejsze, jeśli użyjesz gdb (opisanego poniżej ), aby dowiedzieć się, gdzie leży błąd.
- Programy działające przez długi czas muszą uważać, aby zwolnić całą przydzieloną pamięć, w przeciwnym razie w przeciwnym razie zabraknie im pamięci. Aby debugować wycieki pamięci, możesz zapoznać się z tymi artykułami na temat debugowania wycieków pamięci w C i C++ .
Uniksa
pliki standardowe , polecenia , wywołania systemowe , uprawnienia do plików
- Zgodnie z konwencją każdy proces rozpoczyna się od otwarcia trzech standardowych plików : standardowego wejścia, standardowego wyjścia i standardowego błędu, powiązanych z deskryptorami plików 0, 1 i 2.
- Standardowe wejście jest zwykle podłączone do klawiatury. Cokolwiek wpiszesz, trafia do programu.
- Standardowe wyjście jest zwykle podłączone do ekranu. Niezależnie od tego, co wyjdzie z programu, stanie się widoczne.
- Błąd standardowy jest również zwykle podłączony do ekranu.
- Możesz użyć powłoki do wywoływania programów, tak aby standardowe wyjście jednego programu było bezpośrednio połączone („potokowane”) ze standardowym wejściem innego programu:
jest | toaleta
Możesz użyć powłoki do wywoływania programów, aby standardowe wejście i/lub wyjście zostało połączone z plikiem:
ls > lsOutFile
wc < lsOutFile
- sort -u
posortowanyPlik - Ogólnie rzecz biorąc, programy nie wiedzą lub nie przejmują się tym, czy powłoka zmieniła znaczenie ich standardowych plików.
- Polecenia Uniksa
- Polecenia to po prostu nazwy plików wykonywalnych. Zmienna środowiskowa PATH mówi powłoce , gdzie ich szukać. Zazwyczaj ta zmienna ma wartość taką jak /bin:/usr/bin:/usr/local/bin:. .
- Aby zobaczyć, gdzie powłoka znajduje konkretny program, na przykład vim , powiedz, gdzie vim .
- Wywołania systemowe i wywołania bibliotek podlegają pewnym ważnym konwencjom.
- Wartość zwracana przez wywołanie zwykle wskazuje, czy wywołanie powiodło się (zwykle wartość wynosi 0 lub jest dodatnia), czy też nie powiodło się (zwykle wartość wynosi -1).
Zawsze sprawdzaj wartość zwracaną przez wywołania biblioteki. Gdy wywołanie systemowe nie powiedzie się, funkcja perror() może wyświetlić informację o błędzie (do standardowego błędu):
int fd;
char *nazwa pliku = "mójplik";
if ((fd = open(nazwa pliku, O_RDONLY)) < 0) [
perror(nazwa pliku); // może wyświetlić „mójplik: Nie ma takiego pliku ani katalogu”
- [
- Strona podręcznika dotycząca wywołania systemowego lub procedury bibliotecznej może zawierać listę typów danych, których nie definiuje, na przykład size_t lub time_t lub O_RDONLY . Typy te są zazwyczaj zdefiniowane w plikach nagłówkowych wymienionych na stronie podręcznika; musisz dołączyć wszystkie te pliki nagłówkowe do swojego programu C.
- Uprawnienia do plików w systemie Unix są zwykle wyrażane liczbami ósemkowymi.
- W powyższym przykładzie funkcji creat() 0660 jest liczbą ósemkową (to właśnie oznacza początkowe 0), reprezentującą binarną liczbę 110 110 000. Ta liczba ósemkowa przyznaje uprawnienia do odczytu i zapisu, ale nie uprawnienia do wykonywania, właścicielowi pliku i grupie pliku, ale nie ma uprawnień innym użytkownikom.
- Uprawnienia ustawiasz podczas tworzenia pliku za pomocą parametru wywołania creat() .
- Polecenie ls -l pokazuje uprawnienia do plików.
- Możesz zmienić uprawnienia do posiadanego pliku za pomocą programu chmod .
- Wszystkie Twoje procesy mają cechę zwaną umask, zwykle przedstawianą jako liczba ósemkowa. Kiedy proces tworzy plik, bity umaski są usuwane z uprawnień określonych w wywołaniu creat() . Jeśli więc Twój umask ma wartość 066, inni nie będą mogli czytać ani zapisywać utworzonych przez Ciebie plików, ponieważ 066 reprezentuje uprawnienia do odczytu i zapisu dla Twojej grupy i innych osób. Możesz sprawdzić i zmodyfikować swój umask za pomocą programu umask , który zwykle wywołujesz w skrypcie startowym powłoki (w zależności od powłoki, ~/.login lub ~/.profile ).
Narzędzia do tworzenia oprogramowania
edytor tekstu , debugger , kompilator , strony podręcznika , tworzenie , wyszukiwanie ,
- Użyj edytora tekstu, aby tworzyć, modyfikować i sprawdzać swój program. Dostępnych jest kilka rozsądnych edytorów tekstu.
- Nauczenie się edytora vima i jego interfejsu graficznego gvim wymaga trochę wysiłku, ale zapewnia bardzo wysokiej jakości zestaw narzędzi do edycji plików programów, w tym podświetlanie składni, dopasowywanie nawiasów, uzupełnianie słów, automatyczne wcięcia, wyszukiwanie według znaczników (które przesuwają szybkie przejście z miejsca, w którym program wywołuje funkcję do miejsca, w którym funkcja jest zdefiniowana) oraz zintegrowane wyszukiwanie stron podręcznika. Vim jest przeznaczony do obsługi klawiatury; nie musisz nigdy używać myszy, jeśli nie chcesz. Jest swobodnie dostępny dla systemów operacyjnych Unix, Win32 i Microsoft. Jest to najbardziej rozwinięta wersja serii edytorów, która obejmuje ed , ex , vi iElvisa . Możesz przeczytać dokumentację online vima i uzyskać natychmiastową pomoc za pomocą polecenia : help vima .
- Edytor emacsa jest, jeśli w ogóle, bardziej funkcjonalny niż vim . Nauka wymaga również dużego wysiłku. Jest również swobodnie dostępny dla systemów operacyjnych Unix i Microsoft. Dokumentację znajdziesz tutaj .
- Dostępnych jest wiele innych edytorów tekstu, ale generalnie nie zapewniają one dwóch najbardziej przydatnych funkcji potrzebnych do tworzenia programów: automatycznego wcięcia i podświetlania składni. Jednak te edytory tekstu często mają tę zaletę, że są łatwiejsze do nauczenia, zgodnie z ich ograniczonymi możliwościami. Do tych edytorów tekstu o niższej jakości należą (dla Uniksa) pico , gedit i joe oraz (dla Microsoftu) notatnik i word .
- Być może znasz zintegrowane środowisko programistyczne (IDE), takie jak Eclipse, Code Warrior lub .NET. Środowiska te zazwyczaj mają edytory tekstu zintegrowane z debugerami i kompilatorami. Jeśli używasz takiego IDE, warto skorzystać z powiązanych edytorów tekstu.
- gdb to debuger , który rozumie zmienne i strukturę programu.
- Dokumentację znajdziesz tutaj .
- Aby efektywnie używać gdb , musisz przekazać flagę -g do kompilatora C lub C++.
- Jeśli Twój program myProgram nie pozostawił pliku o nazwie core , spróbuj gdb myProgram core .
- Możesz także uruchomić swój program od początku pod kontrolą gdb : gdb myProgram .
- Wszystkie polecenia gdb można skrócić do unikalnego przedrostka.
- Polecenie pomocy jest bardzo przydatne.
- Polecenie Where pokazuje stos wywołań, w tym numery linii pokazujące, gdzie znajduje się każda procedura. Jest to pierwsze polecenie, które powinieneś wypróbować podczas debugowania pliku podstawowego.
- Aby wydrukować wartość jakiegoś wyrażenia (możesz dołączyć swoje zmienne i zwykłe operatory C), wpisz print wyrażenie , jak w
drukuj (myInt + 59) & 0444; - Aby zobaczyć swój program, spróbuj wyświetlić listę myFunction lub listę myFile.c:38 .
- Aby ustawić inny rekord aktywacji jako bieżący, użyj polecenia w górę (dla nowszego) lub w dół (dla nowszego).
- Możesz ustawić punkt przerwania w dowolnej linii dowolnego pliku. Na przykład możesz powiedzieć break foo.p:38 , aby ustawić punkt przerwania w linii 38 w pliku foo.p. Za każdym razem, gdy program podczas wykonywania natrafi na tę linię, zatrzyma się, a gdb wyświetli monit o podanie poleceń. Możesz na przykład przyjrzeć się zmiennym lub przejść dalej przez program.
- Następne polecenie powoduje przejście o jedną instrukcję do przodu (wywołanie i powrót z dowolnej procedury, jeśli to konieczne) .
- Polecenie step powoduje przejście do przodu o jedną instrukcję, ale jeśli instrukcja zawiera wywołanie procedury, wchodzi do procedury i zatrzymuje się na pierwszej instrukcji.
- Jeśli wpiszesz polecenie set follow-fork-mode child , to kiedy program wykona wywołanie fork() , gdb będzie kontynuował debugowanie dziecka, a nie rodzica.
- Opuść gdb , wprowadzając polecenie Quit .
- Możesz preferować użycie graficznego interfejsu ddd do gdb .
- Zawsze dawaj programom kompilacyjnym gcc lub g++ flagę -Wall , aby włączyć wysoki poziom ostrzeżeń. Podobnie nadaj javacowi flagę -Xlint:all . Nie włączaj programu, który generuje ostrzeżenia w czasie kompilacji.
- Możesz przeczytać podręcznik , aby uzyskać szczegółowe informacje o programach, procedurach biblioteki C i wywołaniach systemowych Uniksa, używając programu man , na przykład man printf lub man gcc .
- Czasami żądana funkcja znajduje się w określonej sekcji podręcznika Uniksa i musisz wyraźnie o nią poprosić: man 2 open lub man 3 printf . Sekcja 1 dotyczy programów, sekcja 2 dotyczy wywołań systemowych, sekcja 3 dotyczy biblioteki C, a sekcja 8 dotyczy administracji systemem. Najprawdopodobniej nie potrzebujesz innych sekcji.
- Możesz sprawdzić, czy jakiś program, procedura biblioteki C lub wywołanie systemowe Uniksa jest istotne dla jakiegoś tematu, używając flagi -k , jak w przypadku man -k print .
- Użyj programu make , aby uporządkować przepisy dotyczące rekompilacji i ponownego łączenia programu po zmianie pliku źródłowego.
- Aby uzyskać szczegółowe informacje, zobacz ten samouczek lub niniejszą instrukcję .
Jeśli Twój program składa się z kilku plików, możesz skompilować je osobno, a następnie połączyć ze sobą. Kompilujesz z flagą -c i używasz flagi -o do wskazania pliku wyjściowego. Rozsądny plik makefile może wyglądać następująco:
ŹRÓDŁA = sterownik.c wejście.c wyjście.c
OBIEKTÓW = sterownik.o wejście.o wyjście.o
NAGŁÓWKI = wspólne.h
CFLAGS = -g -Ściana
program: $(OBIEKTÓW)
$(CC) $(CFLAGS) $(OBIEKTÓW) -o program
$(OBIEKTÓW): $(NAGŁÓWKI)
testRun: program
- program
Ten plik makefile wykorzystuje wbudowaną definicję CC i wbudowaną regułę do konwersji plików źródłowych C, takich jak sterownik.c, na ich plik obiektowy. Jeśli zmodyfikujesz tylko input.c , to make testRun spowoduje, że kompilator odbuduje input.o , następnie kompilator ponownie połączy obiekty, utworzy program , a następnie uruchomi program ze standardowym wejściem przekierowanym z pliku testData . - Jeśli masz wiele plików źródłowych i wiele plików nagłówkowych, możesz użyć programu makedependent do automatycznego zbudowania reguł Makefile , które określają, w jaki sposób pliki źródłowe zależą od plików nagłówkowych. W powyższym przykładzie założono, że wszystkie pliki źródłowe zależą od wszystkich plików nagłówkowych, co często nie ma miejsca.
- Program grep może szybko wyszukać definicję lub zmienną, szczególnie w plikach dołączanych:
grep "struct timeval [" /usr/include/*/*.h
Ćwiczenia
Wykonaj te ćwiczenia w języku C.
- Napisz program o nazwie atoi , który otwiera plik danych nazwany w wierszu poleceń i wczytuje z niego pojedynczy wiersz wejściowy, który powinien zawierać liczbę całkowitą przedstawioną w znakach ASCII. Program konwertuje ten ciąg na liczbę całkowitą, mnoży liczbę całkowitą przez 3 i wypisuje wynik w formacie standardowym. Program nie może używać funkcji atoi() . Powinieneś użyć programu make . Twój plik Makefile powinien mieć trzy reguły: atoi , run (który uruchamia program na standardowych danych testowych i przekierowuje dane wyjściowe do nowego pliku) i clean(który usuwa pliki tymczasowe). Upewnij się, że program działa poprawnie na złych danych i kończy działanie z pomocnym komunikatem, jeśli brakuje pliku danych lub jest on nieczytelny. Przejdź przez program, uruchamiając go za pomocą gdb , umieszczając punkt przerwania w funkcji main() i wielokrotnie używając polecenia step .
- Wyszukaj stronę podręcznika programu cat . Zakoduj własną wersję cat . Twoja wersja musi akceptować wiele parametrów nazw plików (lub nie akceptować ich wcale). Nie musi akceptować żadnych parametrów opcji.
- Napisz program usuwaniaSuffix , który przyjmuje pojedynczy parametr: nazwę pliku sufiksu. Plik przyrostków zawiera jedną linię na wpis. Wpis to niepusty ciąg znaków, który nazywamy przyrostkiem , po którym następuje znak >, po którym następuje inny (prawdopodobnie pusty) ciąg znaków, który nazywamy zamianą . Twój program powinien przechowywać wszystkie przyrostki i ich zamienniki w tablicy mieszającej. Użyj zewnętrznego łańcucha. Twój program powinien następnie czytać standardowe wejście. Dla każdego słowa w oddzielonego spacjami w danych wejściowych znajdź najdłuższy przyrostek s występujący w w i zmodyfikuj w , usuwając słowo s i wstawiajączamiana s , utworzenie w' . Wypisz jedną linię na każde zmodyfikowane słowo w postaci w>w' . Nie pisz żadnych słów, które nie zostały zmodyfikowane.
Let professional writers deal with your paper, quickly and efficiently.
Write My Paper