Błąd "error C2065: 'A' : undeclared identifier" oprócz oczywistych przypadków niezadeklarowania zmiennej, może pojawić się w sytuacji takiej, jak opisana w przypadku błędu C2143: syntax error : missing ';' before 'type' .

Programując w C musimy pamiętać o zasadzie, że zmienne możemy deklarować tylko na początku bloków kodu (czyli bezpośrednio po otwarciu nawiasu klamrowego "{").

Jeżeli nie trzymamy się tej zasady, otrzymujemy błąd, którego przyczynę trudno wywnioskować z opisu (bo przecież wszystkie średniki są na miejscu):

error C2143: syntax error : missing ';' before 'type'

Przykład programu, który może spowodować taki błąd:

#include <stdio.h>

int main(void) {
    printf("Podaj wartosc zmiennej A: ");
    double A;
    scanf("%lf", &A);

    return 0;
}

Aby go naprawić, należy przenieść deklarację zmiennej A na sam początek funkcji main():

int main(void) {
    double A;
    printf("Podaj wartosc zmiennej A: ");
    scanf("%lf", &A);

    return 0;
}

Aby korzystać w Visual Studio ze stałych matematycznych, trzeba dołączyć do programu bibliotekę math.h:

#include <math.h>

To jednak nie wystarczy, ponieważ w tej bibliotece stałe matematyczne są definiowane pod warunkiem wcześniejszego zdefiniowania _USE_MATH_DEFINES, co widać gdy zajrzymy do pliku math.h:

#if defined(_USE_MATH_DEFINES) && !defined(_MATH_DEFINES_DEFINED)
#define _MATH_DEFINES_DEFINED

/* Define _USE_MATH_DEFINES before including math.h to expose these macro
 * definitions for common math constants.  These are placed under an #ifdef
 * since these commonly-defined names are not part of the C/C++ standards.
 */

/* Definitions of useful mathematical constants
 * M_E        - e
 * M_LOG2E    - log2(e)
 * M_LOG10E   - log10(e)
 * M_LN2      - ln(2)
 * M_LN10     - ln(10)
 * M_PI       - pi
 * M_PI_2     - pi/2
 * M_PI_4     - pi/4
 * M_1_PI     - 1/pi
 * M_2_PI     - 2/pi
 * M_2_SQRTPI - 2/sqrt(pi)
 * M_SQRT2    - sqrt(2)
 * M_SQRT1_2  - 1/sqrt(2)
 */

#define M_E        2.71828182845904523536
#define M_LOG2E    1.44269504088896340736
#define M_LOG10E   0.434294481903251827651
#define M_LN2      0.693147180559945309417
#define M_LN10     2.30258509299404568402
#define M_PI       3.14159265358979323846
#define M_PI_2     1.57079632679489661923
#define M_PI_4     0.785398163397448309616
#define M_1_PI     0.318309886183790671538
#define M_2_PI     0.636619772367581343076
#define M_2_SQRTPI 1.12837916709551257390
#define M_SQRT2    1.41421356237309504880
#define M_SQRT1_2  0.707106781186547524401

#endif  /* _USE_MATH_DEFINES */

Dlatego trzeba jeszcze w programie zawrzeć definicję _USE_MATH_DEFINES (koniecznie przed math.h, aby dołączając bibliotekę dać znać preprocesorowi, że chcemy korzystać z tych stałych.

#define _USE_MATH_DEFINES

Przykład wyświetlający wartość stałej PI:

#include <stdio.h>
#include <stdlib.h>
#define _USE_MATH_DEFINES
#include <math.h>

int main(void) {
   printf("%lf\n",M_PI);

   return EXIT_SUCCESS;
}

54

(1 odpowiedzi, napisanych Programowanie w LabVIEW)

Getting Started with LabVIEW (PDF)

Getting Started with NI LabVIEW Student Training (tutorial video)

Można też szukać na stronie ni.com/manuals.

Utworzenie nowej klasy wiąże się z dodaniem do projektu dwóch plików - Klasa.h oraz Klasa.cpp.

W którym z tych plików najlepiej zawrzeć dyrektywy #include dołączające potrzebne biblioteki?

Plik nagłówkowy Klasa.h jest niejako wizytówką klasy. Powinien więc zawierać tylko to, co jest niezbędne do zrozumienia przeznaczenia i sposobu wykorzystania klasy.

Użytkownika klasy niekoniecznie powinny interesować szczegóły implementacji poszczególnych jej funkcjonalności.

Na przykład jeżeli w klasie dokonywane jest obliczanie wartości funkcji sinus to dla użytkownika klasy nie ma większego znaczenia czy jest to zrobione z wykorzystaniem takiej czy innej biblioteki, czy może poprzez rozwinięcie w szereg.

Dlatego wszelkie pliki nagłówkowe, które dotyczą wyłącznie implementacji, powinny być dołączone w pliku Klasa.cpp.

W pliku Klasa.h należy dołączać tylko te pliki nagłówkowe, które są niezbędne do zrozumienia przez kompilator kodu w nim zawartego.

Na przykład:

W klasie Wielokąt wyświetlamy na ekran jakieś informacje z wykorzystaniem strumieni i instrukcje to wykonujące znajdują się tylko w pliku Wielokat.cpp. Dołączenie biblioteki iostream znajdzie się więc w pliku Wielokat.cpp.

Jeżeli oprócz tego w skład klasy Wielokat wchodzą obiekty klasy Punkt i deklaracja takiego składnika klasy jest już umieszczona w pliku Wielokat.h, to dołączenie pliku Punkt.h musi zostać zawarte w pliku Wielokat.h.

Wielokat.h:

#pragma once
#include "Punkt.h"

class Wielokat {
   int ileMamWierzcholkow;
   Punkt* wierzcholki;

   // pozostałe składniki klasy...
};

Wielokat.cpp:

#include "Wielokat.h"
#include <iostream>

using namespace std;

void Wielokat::info(void) {
   cout << "Liczba wierzcholkow: " << ileMamWierzcholkow << endl;
}
//ciąg dalszy implementacji klasy...

Aby zminimalizować ryzyko wystąpienia błędu podczas pracy ze wskaźnikami proponuję stosowanie następujących zabiegów w programach:

Gwiazdkę oznaczającą wskaźnik piszemy bliżej nazwy typu, a nie nazwy zmiennej.

int* wsk;  //zamiast int *wsk lub int * wsk

Nie ma to znaczenia dla kompilatora, ale "wsk jest typu wskaźnik na int" wydaje się łatwiejsze w zrozumieniu niż "to na co wskazuje wsk jest typu int".

Taki zapis sprawia również, że łatwiejsze w odczycie stają się typy zwracane przez funkcje oraz ich argumenty wywołania.

int* generujTablice(int rozmiar, int wartoscMin, int wartoscMax);
int progowanie(struct Obraz* obr, int prog);

Każdy nowo zadeklarowany wskaźnik inicjujemy wartością NULL.
Uchroni nas to przed próbą wykorzystania wskaźnika, który nie został do niczego konkretnego przypisany.

Stosujemy to zarówno w przypadku pojedynczych wskaźników:

int* w = NULL;

Jak również dla wskaźników będących elementami większych struktur:

struct Wektor {
   int wielkosc;
   int* liczby;
};

void main(void) {
   struct Wektor w1;
   w1.liczby = NULL;
}

Przed wykonywaniem operacji na danych, na które pokazuje wskaźnik, sprawdzamy czy nie ma on wartości NULL.

Zwalniając zaalokowaną pamięć, oprócz wywołania funkcji free(), wpisujemy do zmiennej wskaźnikowej wartość NULL.
(funkcja free() powoduje jedynie zwolnienie pamięci, do której adres dostaje, nie jest w stanie zmienić tego adresu na NULL)

if (wsk != NULL) {
   free(wsk);
   wsk = NULL;
}
char c;

Niektórzy programiści zastanawiają się, czy słówko char nie powinno wymawiać się [kar], skoro pochodzi od character, które wymawia się [karakter].

Widziałem nawet na YouTubie zaciekłą dyskusję na ten temat pod tutorialami jakiegoś programisty, który upierał się przy takiej właśnie wymowie.

Bardzo dobrze, że programiści zadają sobie takie pytania, ponieważ to oznacza, że kierują się logiką.

Logika jednak nie obowiązuje w języku angielskim i char wymawiamy [czar] smile
(pisze o tym również na swojej stronie Bjarne Stroustrup, twórca języka C++)

Jeżeli korzystamy w Visual Studio z funkcji takich jak scanf, fscanf, fopen, możemy otrzymać takie ostrzeżenie:

warning C4996: 'scanf': This function or variable may be unsafe. Consider using scanf_s instead. To disable deprecation, use _CRT_SECURE_NO_WARNINGS. See online help for details.

Visual Studio ostrzega nas w ten sposób, że te funkcje mogą być niebezpieczne.

Na czym polega niebezpieczeństwo?

Jeżeli wczytując dane do tablicy znaków:

char napis[20];
scanf("%s", napis);

wpiszemy więcej znaków niż przewidzieliśmy deklarując tablicę, nastąpi przepełnienie bufora, czego efektem będzie błąd podczas uruchomienia programu.

Nawet jeżeli w programie nie mamy takiej sytuacji z wczytywaniem tablicy, Visual i tak doczepi się do każdej funkcji scanf.

Visual podpowiada nam, aby użyć w zamian funkcji scanf_s, która posiada dodatkowe zabezpieczenia przed takimi sytuacjami.

My jednak wolimy pozostać przy "zwykłych" funkcjach typu scanf, gdyż ich Microsoftowe odpowiedniki sprawiają, że kod staje się mniej przenośny (nie będzie działał na innych systemach operacyjnych i kompilatorach).

Poza tym testując prosty program:

#include <stdio.h>

void main(void) {
    char napis[10];
    scanf_s("%s", napis);
    printf("%s", napis);
}

zauważyłem, że po wpisaniu "abc" nic nie zostało wyświetlone. Być może Visual aż tak dalece posunął się w swojej ostrożności, że zablokował mi całkowicie możliwość wczytywania danych smile

Dlatego aby wyciszyć ostrzeżenia o tym "niebezpieczeństwie" korzystamy z drugiego rozwiązania, jakie podpowiada nam kompilator, czyli makrodefinicji _CRT_SECURE_NO_WARNINGS (deklarujemy ją koniecznie na samym początku pliku z kodem źródłowym).

Natomiast przed przepełnieniem bufora zabezpieczamy się sami, podając w funkcji maksymalną liczbę znaków, jaką dopuszczamy wczytać do tablicy:

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>

void main(void) {
    char napis[10];
    scanf("%9s", napis);
    printf("%s", napis);
}

(pamiętamy, że w C tablica znaków, którą chcemy traktować jak napis musi się kończyć znakiem '\0', więc do tablicy char tab[10] możemy wczytać przez %s 9 znaków)

59

(0 odpowiedzi, napisanych Programowanie obiektowe (C++))

Na zajęciach definiowaliśmy operatory wewnątrz klas. Pierwszym argumentem każdego z operatorów był wówczas obiekt naszej klasy. Drugi argument mógł być zdefiniowany (dla operatorów dwuargumentowych jak +=, =, +) lub nie (dla operatora ++).

Ale operatory możemy również definiować poza klasami, tak jak poza klasami możemy definiować zwykłe funkcje.

Na przykład dla ćwiczenia możemy zdefiniować operator ==, który porówna nam ze sobą dwa obiekty klasy Liczba. Do uznania je za równe wystarczy nam równość pól wartosc:

bool operator==(Liczba& liczba1, Liczba& liczba2) {
    return (liczba1.dajWartosc() == liczba2.dajWartosc());
}

Tak zdefiniowany operator umieszczamy na przykład w pliku z programem głównym i możemy go już użyć w funkcji main():

if (a==b) {
    cout << "Liczby rowne" << endl;
} else {
    cout << "Liczby rozne" << endl;
}

Do operatora warto dodać informacje o tym, że jego argumenty nie zostają zmienione, czyli słówko const:

bool operator==(const Liczba& liczba1, const Liczba& liczba2) {
    return (liczba1.dajWartosc() == liczba2.dajWartosc());
}

Oczywiście powyższy przypadek można równie dobrze zdefiniować wewnątrz klasy Liczba, więc jeszcze nie widzimy uzasadnienia dla stosowania tego sposobu.

Okaże się on jednak przydatny, jeżeli będziemy chcieli na przykład pomnożyć liczbę przez walutę.

Liczba a(10, 2);
Waluta w(10, 3, "PLN", 1.0);

Dla takiego przypadku:

w = w * a;

możemy zdefiniować operator * w klasie Waluta, ponieważ "wie" ona czym jest klasa Liczba.

Nie możemy zrobić tego jednak dla takiego przypadku:

w = a * w;

Ponieważ trzeba by to zrobić w klasie Liczba, a klasa Liczba nie wie, że ma potomka Waluta.

Pozostaje nam więc zdefiniowanie tego operatora poza klasami:

Waluta operator*(const Liczba& liczba, const Waluta& waluta) {
    return Waluta(liczba.dajWartosc()*waluta.dajWartosc(), 3, waluta.dajWalute(), waluta.dajPrzelicznik());
}

Zachęcam do przetestowania tego we własnych programach.

Poniżej zamieszczam wskazówki dotyczące stylu programowania w C++.

Nazwy klas zaczynamy od rzeczownika pisanego z wielkiej litery:

class Licznik {
   ...
};

class TaliaKart {
   ...
}

Nazwy zmiennych i obiektów zaczynamy od rzeczownika pisanego z małej litery:

int suma;

Waluta stanKonta;

Nazwy funkcji i metod zaczynamy od czasownika pisanego z małej litery (oczywiście wyjątkiem są konstruktory i destruktory):

void drukuj(void);

int pobierzStan(void);

Stosujemy jednolity język (polski lub angielski), nie mieszamy (zarówno jeżeli chodzi o program jako całość, jak i pojedyncze nazwy zmiennych i funkcji):

void aktualizujNapis(void);
int pobierzWartosc(void);

lub

void updateString(void);
int getValue(void);

W jednej linii piszemy maksymalnie 80 znaków (oprócz czytelności na ekranie ma to znaczenie jeżeli chcemy później wydrukować listing programu).

Jeżeli funkcja nic nie zwraca lub nie przyjmuje żadnych argumentów, piszemy jawnie void:

void drukuj(void);

Wskaźniki i referencje piszemy przy nazwie typu, a nie nazwie zmiennej (spację dajemy po znaczku * lub &):

Liczba& Liczba::dodaj(Liczba& liczbaDodawana);

Zamiast makra NULL stosujemy nowe słowo kluczowe nullptr:

wartoscNapis = nullptr;

Poszczególne elementy klasy grupujemy według sposobu dostępu, w kolejności:

public:
...

protected:
...

private:
....

Więcej na ten temat: C++ Programming Style Guidelines

61

(0 odpowiedzi, napisanych Programowanie obiektowe (C++))

Odniosę się jeszcze do pytania o słówko const w metodzie pobierającej napis z pola waluta.

Idąc po kolei...

W klasie Liczba mieliśmy taką sytuację, że napis, który zwracaliśmy zadeklarowany był jako wskaźnik:

char* wartoscNapis;

Pisząc metodę pobierzWartoscNapis, mogliśmy to zrobić w ten sposób:

inline char* pobierzWartoscNapis(void) {return wartoscNapis;};

Jeżeli chcieliśmy zaznaczyć, że metoda ta ma nie zmieniać zawartości obiektu, stosowaliśmy po jej nazwie słówko const:

inline char* pobierzWartoscNapis(void) const {return wartoscNapis;};

I rzeczywiście, zawartością obiektu jest w tym przypadku pojedynczy wskaźnik, więc pobierając go z tej metody nie będziemy w stanie zmienić go na zewnątrz - więc nie będziemy w stanie zmienić zawartości obiektu - wszystko się zgadza smile

Natomiast w klasie Waluta, pole przechowujące napis waluty mamy zdefiniowane inaczej, jako statyczną tablicę:

char waluta[4];

Więc w obiekcie tym razem "siedzi" cały napis, a nie wskaźnik na jakiś zewnętrzny napis, jak to było w przypadku klasy Liczba.

Pisząc metodę pobierzWalute, możemy zacząć tak:

inline char* pobierzWalute(void) {return waluta;};

I wszystko na tym etapie działa.

Jeżeli chcemy zasygnalizować, że ta metoda nie ma zmieniać zawartości obiektu, próbujemy to zrobić w ten sposób (analogicznie jak w klasie Liczba):

inline char* pobierzWalute(void) const {return waluta;};

Wówczas kompilator zgłasza błąd. Dlaczego?

Dzieje się tak ponieważ pomimo tego, że zadeklarowaliśmy metodę jako const, to mając wskaźnik na napis waluta, moglibyśmy zmienić ten napis, więc moglibyśmy zmienić zawartość obiektu (a chcemy poprzez const wymusić, że tego nie będzie dało się zrobić).

Dlatego w tym przypadku, konieczne jest jeszcze dodanie słówka const z przodu, aby zasygnalizować, że zwracamy wskaźnik na stały napis i uniemożliwić zmianę tego napisu (napis ten jest przecież elementem obiektu, a my zawartość obiektu chcemy ochronić przed zmianą):

inline const char* pobierzWalute(void) const {return waluta;};

Dopiero teraz kompilator przestaje sygnalizować błąd.



Wracając do klasy Liczba...

Jeżeli mamy zadeklarowaną metodę pobierającą napis w ten sposób:

inline char* pobierzWartoscNapis(void) const {return wartoscNapis;};

To pomimo tego, że taka deklaracja uniemożliwia zmianę pola obiektu (czyli wskaźnika), to sam napis, na który ten wskaźnik wskazuje moglibyśmy zmienić, na przykład tak:

liczba1.pobierzWartoscNapis()[1] = 'a';

Dlatego aby dodatkowo zabezpieczyć się przed taką operacją, możemy zastosować słówko const w deklaracji typu zwracanego przez metodę:

inline const char* pobierzWartoscNapis(void) const {return wartoscNapis;};

Zachęcam do przetestowania tego we własnych programach.

Jeżeli tworzymy tylko obiekty klas pochodnych, czy to statycznie czy dynamicznie:

Waluta w1;
Waluta* w2 = new Waluta("PLN", 1.0, 0.0, 3);

To w momencie ich usuwania z pamięci będzie uruchamiany zarówno destruktor klasy pochodnej, jak i destruktor klasy bazowej:

Wywołano destruktor klasy Waluta
Wywołano destruktor klasy Liczba

Więc nie zauważymy żadnego wpływu tego, czy destruktor zadeklarujemy jako wirtualny czy nie.

Możemy to zauważyć dopiero w sytuacji, gdy korzystamy ze wskaźników.

Ponieważ zmienna zadeklarowana jako wskaźnik na obiekt klasy bazowej może wskazywać również na obiekty klas pochodnych, możemy zrobić tak:

Liczba* wsk = new Waluta("PLN", 1.0, 0.0, 3);

Ale wówczas, jeżeli chcemy usunąć stworzony w ten sposób obiekt:

delete wsk;

Zostanie wywołany tylko destruktor z klasy Liczba:

Wywołano destruktor klasy Liczba

Aby wywołany został również destruktor z klasy Waluta, należy w klasie Liczba zadeklarować destruktor jako wirtualny:

virtual ~Liczba();

Teraz już destruktory zostaną wywołane poprawnie:

Wywołano destruktor klasy Waluta
Wywołano destruktor klasy Liczba

Zachęcam do przetestowania tego we własnym programie.

Inny przykład: Wirtualne destruktory