Samenvatting c++
- Algemeen
- Basis
- Enkele nieuwe mogelijkheden in c++
- Declaratie en definitie
- Externe declaratie
- Expressie en statement
- Handige mogelijkheden
- Repetitie statements
- Control flow statements
- Macro’s
- Namespaces
- Run-Time Type information RTTI
- Variabelen
- Pointers
- Diverse functies
- Arrays
- Structures
- Unions
- Bitvelden / bit fields
- Bitmanipulatie met bitvelden en unions
- Bitmanipulatie
- Conditionele compilatie
- variable qualifier
- Cast
- Headers
- console applicaties user interface
- Objecten toevoegen en verwijderen
- References
- Templates en Traits
- Special classes
- Basis
Om het programmeren in c++ eenvoudig te houden kun je het op een procedurele manier gebruiken met alleen de basis functies. Maar als je alles uit te kast trekt dan is c++ zeer ingewikkeld. Ik merk dat het moeilijk is om alle mogelijkheden van c++ paraat te hebben, terwijl dit voor het programmeren toch wel nodig is. Als je een tijdje niet meer met c++ gewerkt hebt dan raak je al snel veel kennis kwijt. Daarom heb ik een overzicht gemaakt van álle mogelijkheden van c++. Het is bedoeld om snel je c++ kennis op te frissen. Het is niet geschikt voor nieuwelingen die de programmeertaal c++ willen leren.
Er wordt speciaal aandacht gegeven aan de gecompliceerde aspecten van c++. In het hoofdstuk tips staan oplossingen voor lastige compiler en linker foutmeldingen.
Dit document was aanvankelijk alleen mijzelf bedoeld. Maar ik vond het zonde om het niet te publiceren. Ik heb geen tijd besteed om het overzicht aan te passen voor het web, het is dus zoals het is. Sommige voorbeelden hebben betrekking op Borland c++ Builder. Een goed Nederlands c++ leerboek is C++ van Leen Ammeraal, hierin worden de onderstaande c++ mogelijkheden uitgebreid uitgelegd.
Reacties zijn welkom, als er iets belangrijks ontbreekt dan hoor ik het graag.
Algemeen
Basis
Enkele nieuwe mogelijkheden in c++
· Referentie argumenten, pass by reference
· Commentaar op twee manieren:
/* blabla */
// blabla
· Case sensitive. Gebruik hoofdletters in variabelen: GetVideoRect;
· Iostreams: cout << “hello’;
· Eenvoudigere struct, enum en union notatie: zonder typedef.
struct S {int a, int b};
S StructVar;
Declaratie en definitie
· Declaratie
Kenbaar maken van een variabele, functie of klasse (= interface)
int i;
void fun(int);
· Initialisatie / definitie / implementatie
Waarde toekennen aan object / variabele
i = 123;
of body / code toekennen
Externe declaratie
File1
int g = 1; // declaration and initialisation
File2
extern int g; // to use g in file2
extern classBlabla g; // classes
Expressie en statement
· Expressie
i=1234
i+5
· Statement is een expressie met ; ofwel een programma element zoals for.
i=1234;
Handige mogelijkheden
· Komma operator if(read(i), i==0) // de tweede expressie wordt afgeleverd
· Increment operator i++ (i wordt afgeleverd) ++i (i+1 wordt afgeleverd)
· if(i=a+b) // if(expressie) combinatie van test en expressie
· c = (a>b) ? a : b; Conditional operator ?
Repetitie statements
· while(variabele) { };
· do{ }while(variabele);
· for(int i =0; i <= 10; i++){ }
· continue hervat de do, for, while loop (én doe de test)
· break verlaat een do, for, while loop
Control flow statements
· if(x==0) {}
else {}
· switch(x)
{ case 1: blabla;
case 2: { blabla; }
default:
}
Macro’s
Zorgt voor eenvoudige schrijfwijze voor zeer ingewikkelde notaties:
\ doorgaan volgende regel
· Token-pasting (merging) operator (##) #define a(x) a##x // a(b) -> ab
· Stringizing Operator(#) #define c(d) #d // c(a) -> “a”
· Charizing Operator (#@) #define a(x) #@x // a(b) -> ‘b‘
· Variadic macros #define d(args...) int i[]={args} // d(1,2,3) -> int i={1,2,3}
Voorbeelden
#define fun(x,y) ((x) * (y))
#define x(n) printf_s("bla" #n "=%d", bla##n) // x(9) -> printf_s("bla9=%d", bla9);
#define progmemPrintf(x) _progmemPrintf(PSTR(x)) PSTR is heel lang!
#define _HEX(a) Struct_BASED(a, HEX) levert een object af
#define FLASH_STRING(name, value) \
static const char name##_flash[] PROGMEM = value; \ (meerdere regels, name##
_FLASH_STRING name(name##_flash); creeer object
#define debug(format, args...) fprintf (stderr, format, args)
Namespaces
· Verdeling in subsystemen die dezelfde namen van globale variabelen gebruiken, bijvoorbeeld bij gebruik van bibliotheken met conflicterende namen. Zie programma Namespace.
· Er zijn drie methodes om de namespace te gebruiken:
namespace A
{ char c = 'a';
}
using namespace A; char x = c; // Methode 1
using A::c; char x = c; // Methode 2
char x = A::c; // Methode 3
Run-Time Type information RTTI
· typeid
Type identificatie:
int i;
String s = typeid(i).name(); // s = ”int”
String s = typeid(*p).name() // Bij pointers
const char* myObjectTypeName = typeid(*this).name(); // Let op: klassenaam, moet met const char*
Type vergelijking met typeid:
if(typeid(i) == typeid(int))
· dynamic_cast
Zie cast
Variabelen
· Constant, int, float, enz.
Zie uitvoerig overzicht Borland c++ Builder Help “Constants and internal representation”
int -32768 … 32767
insighed int = unsigned 0 ... 65535
float 1.18 10^-38 < |X| < 3.40 10^38 Scientific (7-digit) precision) max 9.999.999
double 64 2.23 10^-308 < |X| < 1.79 10^308 Scientific (15-digit precision)
long double 80 3.37 10^-4932 < |X| < 1.18 10^4932 Financial (18-digit precision)
· Variabelen kunnen overal gedeclareerd worden, bv. in for statement: for (int i=0; i < 10; i++)
int x;
for(int x; x<10; x++) // Local scope voor variabelen
{int x;} // Andere variable x dan in regel 1
· Initialisatie kan op twee manieren:
int x(1); // Methode 1
int x = 1; // Methode 2
· bool
bool b = false; b = true;
· Nieuwe integer types: Shortint, Smallint, Longint, Int64, Byte, Word, and Longword.
· Enum
Doe enumeratie in hoofdletters
Enum {ZONDAG, MAANDAG}; unnamed enum type, ZONDAG=0, MAANDAG=1
enum MyEnumType {ZONDAG, MAANDAG} MyEnumTypeName;
enum {ZONDAG, MAANDAG} MyEnumTypeName; // mag ook
Gebruik: MyEnumTypeName = ZONDAG; int i = ZONDAG;
· Typedef
Een nieuw type definieren.
typedef char *ptr; // Of typedef char* ptr;
ptr p1; // ptr is nieuw type, n.l. char*
Pointers
· Plaats van het sterretje:
int *i, j; // Eén pointer, sterretje moet bij i
int* i, j; // Twee pointers, sterretje moet bij int
· Indirect member operator
p->x = (*p).x
· Void pointer
Is een pointer naar ieder willekeurig type
· Functiepointers
Zie Functie pointers
Diverse functies
· assert(int test) Tests een conditie en evt. aborts program, foutmelding met programmaregelnummer
· min(10, 20) // = 10 max(20, 10) // = 20
· Sleep(1000); // Pause 1000 ms
Arrays
Een dimensionale array:
int a[10] = {1, 2, 3}; // a[9] = 0
of int b[] = {1, 2, 3};
Twee dimensionale array:
int c[][3] = {{60, 30, 50}, {20, 80, 40}}; // 3 is verplicht
c[1][2] = 40
Array van standaardstrings
String s[3] = {"aap", "noot"};
s[1] = "noot"
sizeof(a) = 10 * sizeof(int)
Structures
struct structTypeBlaBla
{ int i;
char c[10];
} s, t = {12, "bla"};
initialiseren: s.i = 25;
structTypeBlaBla a, b, c;
struct // zonder het type een naam te geven (niet handig)
{ int i;
char c[10];
} u, v;
Unions
Meerdere variabelen die dezelfde geheugenplaats in beslag nemen, de grootste bepaalt de geheugenruimte.
union {int i; byte j;} u;
Bitvelden / bit fields
Moet in struct
struct bv
{ unsigned blaRight:1, b:1, blaLeft:1;
};
Bitmanipulatie met bitvelden en unions
union
{ bv s;
int i;
} u;
u.i=0;
u.s.blaLeft = 1;
Resultaat: u.i = 4
Bitmanipulatie
& | ^(xor) ~(not) << >>
int b = ~a
int b = a << 3
Conditionele compilatie
#ifdef x (of #if)
...
#else
...
#endif
variable qualifier
const
· const int i = 25; // i is niet meer te wijzigen i.p.v. #define
char* const array[] = {"a", "f"}; // Een pointer naar een constant character string
· fun(const int &x) // x kan niet worden gemodificeerd
· fun(K &k) const // bij lidfunctie kan er geen data gemodificeerd worden in de de klasse
static
Variable blijft na het verlaten van de functie behouden en wordt niet destroyd.
static int i=0; let op: mag in een lus want wordt maar één keer 0 gemaakt.
volatile
Voor een variabele die kan worden gewijzigd buiten het programma om, bijvoorbeeld in een andere thread of een interrupt routine. Anders kan de compiler de variabele soms weg-optimaliseren.
Cast
static_cast
· Veranderen van een type, kan op twee manieren:
int v;
(long) v; gebruik liever static_cast<long>
long (v);
reinterpret_cast
Cast voor converteren van pointers die verwijzen naar verschillende objecten, pas op!
const cast
Manipulatie van const pointers.
dynamic_cast
· Doel: om een pointer naar een basisklasse om te zetten naar een pointer naar een afgeleide klasse. Als de basisklasse geen virtuele lidfuncties bevat voor de lidfuncties van de afgeleide klasse (zoals soms bij Borland VCL) dan moet dynamic_cast worden gebruikt om de lidfuncties van de afgeleide klassen aan te roepen. Bij een goed ontwerp is dynamic_cast niet nodig.
afgeleide_klasse* = dynamic_cast<afgeleide klassetype*>(basisklasse*)
· Voorbeeld Borland c++:
TMDIChild* ChildPtr=dynamic_cast<TMDIChild*>(MainForm->ActiveMDIChild);
· Testen op een bepaalde klasse:
if(afgeleide_klasse*)!= NULL // Bij geen resultaat = NULL
· Dubbele cast
Variant v =(int)(bool)var; // Soms nodig, zie ConfigVariant in dll AVD
Headers
Slechts één keer uitvoeren van een header
#ifndef Headerx
#define Headerx
declaraties ..
#endif
Waarom headers
Verhogen compilatie snelheid. Als alleen de code verandert maar niet de header hoeven de modules die de header aanroepen niet te worden gerecompileerd. Zonder gebruik van headers, dus met .cpp includements moet dit wel.
Tips
· Geen includes nesten. Headers mogen geen andere include’s bevatten behalve bij overerving en externe hpp files (en zelf gedefinieerde types). Oplossen met forward declaration, zie verder.
· Gebruik zo weinig mogelijk #include "Main.h" en "ChildWin.h"
pre-compiled header
· Voor snellere compilatie, zie Borland documentatie voor project options enz.
· Gebruik een enkele precompiledheaders.h file met de lijst van alle pre-compiled headers:
#include <vcl.h> // Include common (system) header files
#include “Main.h” // My project headers
#include <myDLLfiles.h>
#include andere headers
In de cpp file:
#include < precompiledheader.h>
#pragma hdrstop // Terminate list here
andere headers…
Let op:
· Alleen in cpp files, werkt niet in headers zelf
· Er mag geen code in de headers.
· Er mag geen global variable definition in de headers, dus ook geen static variabelen
Code in header
· Nadeel: geen precompiled headers mogelijk
· De code wordt uitgevoerd in de header i.p.v. in de cpp file
· Bij templates is het handiger omdat anders de .cpp file included moet worden
Forward declaration
Voordelen:
1. Voorkomt het nesten van includes
2. Voorkom circular includes door #includes in header te vermijden
Gebruik
1. Bij declaratie van een klasse of een struct, gebruik pointers naar objecten
In header file:
class K; // Dit is forward declaration. Dus niet #include “K.h”
K *k;
In cpp file:
#include "K.h"
k = new K; // creëer met new
2. Bij argumenten
In header file:
struct K; // #include “K.h” is niet nodig!
void fun(K k);
vector<K> v;
3. Forward declaration bij templates:
template <class T>
class xxx;
console applicaties user interface
· cout << Str << VarInt << VarFloat << endl;
· cin >> Var1 >> Var2;
· cout << setw(3) veldbreedte tot nader order
· width(3) zie boven, alleen voor volgende regel
· cout << setprecision(1) aantal decimalen tot nader order
· precision(1) zie boven alleen voor volgende regel
· cin.clear(); wissen teveel gelezen char
· cin.get(ch); lees char.
· cin.ignore(100,’\n’); lezen ten hoogste 100 char t/m \n
· cin >> hex >> i; hexadecimale invoer
· cout << hex hexadecimale uitvoer
· cout << setfill(‘0’) extra posities 0 i.p.v. spatie
Objecten toevoegen en verwijderen
struct structnaam {int a; int b;};
int lengte = 3;
1. Niet dynamisch
structnaam vb2[3]; // constante 3 vereist, variabele "lengte" is niet mogelijk
vb2[0].a = 25;
2. Dynamisch geheugenallocatie met new
structnaam* vb3 = new structnaam[lengte]; // array, lengte is variabele!
vb3[0].a = 25;
delete[] vb3; // let op: gebruik [] bij arrays
3. Pointers en new
Toepassing array van pointers (naar objecten). Voordeel: het array heeft weinig geheugen nodig terwijl de objecten zelf groot zijn.
structnaam* vb1[3]; // array van pointers, constante 3 vereist
vb1[0] = new structnaam;
vb1[0]->a = 25; // -> nodig want vb1 is een pointer
Met reference (zie references)
structnaam& ref = *vb1[0];
ref.a = 25; // punt . i.p.v. ->
Altijd doen:
· Pointer declaratie:
int *p = NULL;// Veilig, een pointer mag niet naar een willekeurige geheugenplaats wijzen
· Pointer verwijderen
delete ptr; ptr = NULL; // Meerdere keren deleten mag bij NULL pointer. Of doe:
if(ptr) delete ptr; // Test op NULL
Bijzonderheden met new en delete
· Beginadres speciferen
int* p = new(pointerA) int[5] // Beginadres = pointerA. Geheugen wordt zo over elkaar heen gebruikt. Dit kan ook met unions.
· Wisselbestand
Bij exit komt het geheugen weer vrij. Bij overschrijding RAM wordt een op de harde schijf gebruikt. Zie programma “new and delete”. Bekijk het geheugen met taakbeheer of met systeembeheer > prestaties / beschikbare geheugen mb.
References
· Gebruik als pointers maar geen adres van (&) en inhoud van (*) nodig. Zie programma Reference
· Notatie:
int& myref; //of int &myref Let op: & betekent niet adres van!
· Nadeel: initialisatie moet gelijk met declaratie
1. Simple references
De referentie kan worden doorgegeven
char c = 'a';
char& cr1 = c; // cr1 is een alias (schuilnaam) voor c. Let op: & betekent niet adres van!
char& cr2 = cr1;
cr2 = 'x'; // resultaat: c = 'x' Let op: dit is natuurlijk een linke toepassing
2. Reference / dereference
Als voorbeeld 1 maar dan met pointers i.p.v. reference. & = adres van. * = inhoud van. int* pointer declaratie
char c = 'a';
char* cp1 = &c; // Let op: hier is & wel adres van!
char* cp2 = cp1;
*cp2 = 'x'; // resultaat: c = 'x'
Voorbeeld in c, argumenten wijzigen kan alleen met adressen
fun(int *x)
{ *x = 25;
}
int i;
fun(&i);
3. Referentie argumenten
· Pass by reference:
fun(int& x) of fun(int &x)
(Pass by value: fun(int x))
Toepassingen
1. Wijzigen van variabelen via parameters:
fun(int &x){x++};
swap(int& Var1, int& Var2);
2. Doorgeven grote objecten:
fun(const string &x)
Er hoeft geen copie gemaakt te worden en is dus sneller, toepassing bij Variants, Strings enz..
· Let op: aanroep moet met variable als parameter:
fun(10) kan niet, wel bij fun(const int &x).
· fun(const int &x) x is niet modificeerbaar door de functie
4. Referentie als functiewaarde
Toepassingen
· Wijzigen returnvariabele:
int &kleinste(int &x, int &y)
{ return (x < y ? x : y);
}
int x=1, y=2;
kleinste(x, y) = 0; // x=0 De returnvariabele wordt dus gewijzigd!
· Setter, getter oplossing / combinatie:
class Reftst
{ public:
int& access() { return i;} // Getter, setter combinatie
int i;
};
Reftst reftst;
reftst.access() = 123; // Gebruik als setter. Reftst::i wordt 123
int i = reftst.access(); // Gebruik als getter. i = 123
5. Reference lidvariabelen
· Initialisatie via constructor is verplicht bij reference members (lidvariabelen)
int& r; // lidvariabele
Fun(int& i): // Constructor
r(i) // Verplicht
· r wijst naar i zolang fun bestaat, zie programma Reference.
6. Polymorfisme
· void uitvoer(const K *k) { k->print();} // Verdient de voorkeur
void uitvoer(const K &k) { k.print();} // Reference. Punt i.p.v. ->
// Aanroep:
uitvoer(r) // bij reference
uitvoer(*p) // bij pointers, nadeel: extra * nodig!
uitvoer(p) // bij gewoon object
· Nadeel van reference: Bij dynamic_cast is een pointer vereist
7. Reference naar inhoud van pointer
· TRegistry& regKey = *new TRegistry; // of *(new TRegistry)
regKey.OpenKey(); // direct member operator (.) i.p.v indirect member operator (->)
delete ®Key; // let op: &
* nodig bij operators: *var = 500; ipv var = 500;
· Nadelen:
1. Reference kan niet NULL worden gemaakt, dus oppassen met 2 x delete
2. Initialisatie moet gelijk met declaratie
8. Reference naar pointer
Bijvoorbeeld bij functies die een object crëeeren, de waarde van de pointer verandert hier.
createObject(T* &t) {t = new T);
Templates en Traits
Functietemplate voorbeeld
template <class T>
void incr (T &x) { x++;}
int i = 0; // Voor int
incr(i);
float f=0; // Voor float
incr (f);
Klassetemplate voorbeeld
template <class elem>
class vector
{ elem test(elem x);
void ikGeefGeenFoutmelding(){bla bla bla}; // Fout maar geeft geen foutmelding!
};
template <class elem>
elem vector<elem>::test(elem x)
{ return x*x;
}
vector<int> vi; // Type vermelden bij klassetemplates
int x = vi.test(5);
Templates
Toepassing:
1. Voor het ondersteunen van variabele typen argumenten
2. Voor het uitvoeren van variabele functies als argument, zie functie objecten
· Ook wel static polyformisme genoemd. Beter is dynamische polyformisme d.m.v. polymorfe of abstracte klassen.
· Let op: De code moet ook geïnclude worden, niet alleen de header. Schrijf de code daarom in de header. Gebruik ook het #ifndef en #define mechanisme, ook voor de code. Zie “separate file template compilation”. Zie programma Template.
· Let op bij templates in DLL: wordt altijd gedebugd ook bij ontbreken van de DLL TDS file.
· Let op: wordt alleen op gecompileerd en fouten gecontroleerd wanneer de functie wordt aangeroepen, dus altijd alle template functies testen!
· Bij klassetemplates moet het type worden vermeld b.v. K<int>, bij functietemplates niet.
· Om bij klassen het type niet te hoeven vermelden gebruik de templates alleen bij de lidfuncties
· Bij Linker Error Unresolved external .. xxx.obj. Plaats in xxx.cpp de .cpp file met de template lidfuncties #include .cpp
· Afgeleide klasse van een template klasse: class TAfgeleide: public TBasis<int>
· Overerven van templates: afgeleide klasse van een template klasse met 2 templates, als 2e template wordt weer een template: class TAfgeleide: public TBasis <int, class float>
Traits
· Uibreiding op templates, zie Borland Standard c++ library > Users Guide > Part V: Special techniques
Special classes
· Zie Standard Borland Standard c++ library > Users Guide > Part VI: Special classes
auto_ptr
· Voordeel: wordt altijd gedelete in destructor, voorkomt memory leaks. Strikt ownership: een object wordt altijd door maximaal één auto_ptr aangewezen, bij copieëeren wordt één pointer NULL.
· Nadelen: o.a. werkt niet goed bij containers en arrays. Debuggen is lastig want de inspector geeft de auto_ptr aan i.p.v. het gewenste object.
complex
· Voor werken met complexe getallen
numeric_limits
· Speciale klasse om limits te bepalen
Voorbeeld:
#include <limits>
using namespace std;
numeric_limits<double> doubleInfo;
int dig = doubleInfo.digits10; // Equals 15
double e = doubleInfo.epsilon; // Equals 2.22044604925031E-16
Functies
Begrippen
· Parameter: i in fun(int i){}
· Argument: 123 in a = fun(123);
· Call by reference fun(int& x) of fun(int &x), zie references
Default parameters
Moet in header.
TestFunc(int i = 5)
i kan weggelaten worden want default = 5, let op: bij meerdere parameters default parameters toevoegen vanaf het einde van de parameter list.
Function overloading
· Functies (en lidfuncties) met dezelfde naam maar met verschillende parameters
fun(int i){};
fun(char c){};
· Tip: gebruik functie overloading als default parameters niet mogelijk zijn
· Gebruik van const klasse object
Een functie met const en een functie zonder const:
int func(int i) const // Wordt aangeroepen met k
int func(int i) // Wordt niet aangeroepen met k
Const klasse object:
const K k;
k.func(123); // Roep de 1e functie aan
Functie pointers
· void fun(int i) {};
void (*p)(int); // p is functiepointer naar alle functies zoals void functienaam(int);
p = fun;
p(123); // Roep de functie fun aan
· In c++ kan dit beter met het Strategy pattern
· Bij klassen alleen voor static lidfuncties.
· De functiepointer kan alleen wijzen naar functies met identieke argumenten
Static function pointer
With typedef:
typedef void (*switchCallback_t)(void*); // typedef in header
static switchCallback_t _beepAllCallback = nullptr; // declaration and initialisation in header
switchCallback_t Switch::_beepAllCallback; // definition outside class is mandatory
Without typedef:
void (*Switch::_beepAllCallback)(void*); // definition outside class is mandatory
static void(*_beepAllCallback)(void*) = nullptr; // declaration and initialisation in header
Inline
· Een lidfunctie gedefinieerd in de Klasse is inline by default
Variadic function
Een functie met een variabel aantal parameters, doen mbv va_list en …
Operator overloading
· Toepassing technische functies, maak eigen operatoren
· Bij pointers kan niet -> gebruikt worden, b.v. p->+ bij + operator. Oplossing: (*p)+
· Ook mogelijk: operator new en operator delete
Dyadische operator (2 operanden)
Er zijn 2 mogelijkheden:
1. Lidfunctie met operator, assymmetrisch:
breuk breuk::operator+(const breuk &a){ }
ostream &operator<<(ostream &os, const lang &u){}
2. Gewone functie (of friend functie) met operator, symmetrisch:
breuk operator/(const breuk &a, const breuk &b){ }
Dit heeft de voorkeur. Er kan voor beide argumenten gebruik worden gemaakt van impliciete conversie, zie “impliciete conversie”. Bij de assymmetrische vorm moet één argument altijd een klasse object zijn.
monadische operator (1 operand)
· Prefix operator
K operator ++() {} // Prefix. Geen dummy argument nodig
· Postfix operator:
K operator ++(int) {} // int is een dummy argument, nodig voor de Postfix operator
Conversion operator
· Converteert een object naar een (ander) type
Toepassingen:
1. Getter zonder gebruik van een functienaam
Normale getter functie:
double T::get(){return v;} // Lidfunctie
T t;
Aanroep:
double y = t.get(); // Met functienaam
Getter met conversion operator:
T::operator double () {return v;} // Let op notatie
Aanroep:
double y = t; // Object wordt geconverteerd naar double, geen functienaam nodig
2. Converteren naar een ander type
Als boven maar dan voor int:
T::operator int () {return v;} // Conversie naar int
Klassen
Begrippen
· Ancester = klasse voorouder
· Parent = ouder
· Descandant = afstammeling
· Derive = afgeleide
· Access specifiers: private, public
· Scope override ::
· Klasse object (of instantie van een klasse) is een variable van het type van de klasse.
· Klasseleden = functieleden en dataleden
· Functieleden = lidfuncties
· Dataleden = lidvariabelen
· Instantie = object
· Specialisatie: een subclass is een specialisatie van een superclass.
· Generalisatie: een superclass is een generalisatie van de subclasses
Algemeen
· Het voordeel van klassen is de sterke koppeling van data en functies
· Korte en pure virtuele functies kunnen in de header:
int GetXMin(){return XMin;}
Nadeel: bij code in de headers is geen pre-compiled header mogelijk
· This
Is de pointer naar het actuele object.
this->lidvariabele is gelijk aan lidvariabele
· Bij meerdere files per klasse neem de filenaam: ClassName_section1.cpp
· Standaard klasse-interfaces:
1. constructor, initialiseer hier de variabelen
2. destructor, verwijder hier de variabelen weer (bij gebruik van new)
3. copy functie: copy constructor (niet altijd nodig)
4. copy functie: operator = functie (niet altijd nodig)
Structures
· Gebruik als klasse maar alleen public mogelijk en geen private en protected
Relaties
· Client class gebruikt diensten van een andere class
· Server class verleent diensten aan andere class
· Agent is een client en een server tegelijk
· Klasse interface = de public methods die clients kunnen gebruiken
· Contract is een gebruiksaanwijzing tussen de client en de server (server interface)
· Contractbreuk (bij een fout) geeft een exception door de server
· Aggregaat relatie: een klasse bevat weer een andere klasse
Soorten klassen
Zie ook Design Patterns
1. Abstracte klasse
Een klasse die een of meer pure virtuele lidfuncties bevat:
K::virtual fun()=0;
Hiervan zijn geen objecten te creëren (wel pointers). fun() moet in een afgeleide klasse worden gedefieerd
2. Concrete klasse
Een niet abstracte klasse.
3. Polymorfe klasse
Een klasse die virtuele lidfuncties bevat, zie “polymorfisme”:
K::virtual fun();
4. Lege klasse
class noemerNul {}; // lege klasse
throw noemerNul(); // noemerNul is een anonym object van het type noemerNul
5. Anoniem object
Dit is een object zonder objectnaam.
Voorbeeld:
K(); // i.p.v. K k().
K().fun(); // Aanroepen lidfunctie van een anoniem object.
int returnvalue = K().fun(); // ook returnvalue mogelijk
Toepassingen:
· Functie objecten
· TestAll() // Run de constructor met testfuncties zonder een apart object te gebruiken
Let op: de destructor wordt aangeroepen na de constructor
Let op: bij een constructor met één argument dat een pointer is en waarbij gebruik wordt gemaakt van een anonym object gaat het fout:
K::K(int* i){} // Constructor
Anonym object creëren:
int* i;
K(i); // Compiler error: Multiple declaration for i;
Oplossing:
int* i;
K((int*)i); // Cast verplicht
Ander voorbeeld:
int i = int(); // i = 0
Werken met klassen
Classe voorbeeld
Header:
class K: public Base
{
public:
K(int x); // Constructor
~K(); // Destructor
int fun(); // Lidfunctie
protected:
private:
int i;
};
cpp file:
K::K(int x):
i(0) // Initialiser
{ i=x;
}
int K::fun()
{ return i;
}
Noodzaak van pointers naar objecten
1. Vermijdt het nesten van includes door toepassing van forward declaration
2. Bij het opslaan van objecten in containers is het opslaan van pointers naar objecten efficiënter dan het opslaan van het object zelf omdat er minder geheugen nodig is.
Constructors
· Kunnen niet virtueel of private zijn, alleen public.
· Function overloading is ook mogelijk bij constructors
Constructors zonder argument
· Geen haakjes gebruiken bij declaratie: K k; fout: K k(); want dit is een functie declaratie
· Declaratie in een header kan alleen met constructors zonder parameters. K k; Dus niet: K k(5); Hier moeten we dynamisch creëren.
Constructors met één argument
· Zie ook implicite conversie
· Bij aanroepen constructor met = voor een bestaand object b.v. k=5; wordt de constructor opnieuw aangeroepen!
· Let op: bij een constructor met één argument dat een pointer is en waarbij gebruik wordt gemaakt van een anonym object gaat het fout. Zie voorbeeld anoniem object.
Destructors
· Delete objecten in de destructor.
· Pure virtual destructors zijn niet toegestaan, gebruik een lege body ~fun(){};
· Destructors kunnen apart worden aangeroepen met ~fun();
· Maak voor de veiligheid álle destructors virtueel omdat het vergeten hiervan niet opgemerkt wordt en geheugenlek veroorzaakt doordat delete instructies in de destructor niet worden uitgevoerd. Kan niet in Arduino.
Impliciete conversie
· Kan alleen bij constructors met één argument
· Expliciete conversie K k(5)
· Impliciete conversie K k=5
· Toepassing:
fun(K k);
fun(1234); // aanroep
De constructor van K wordt aangeroepen met 1234 als argument. Dit is implicite conversie
· Implicite conversie voorkomen met explicit.
Lidvariabelen
· Problemen bij identieke functieparameternaam en lidvariabelenaam “x”:
1. Constructor
Class(int x):
x(x) { } // Initialisatie van lidvariabele
2. Doorgeven van constantes
const int x;
Dit kan alleen zo:
Class(int x):
x(x) { }
3. Lidfunctie
fun(int x)
{ this->x = x;} // Moet met this, maar dit is onhandig, gebruik fun(int _x)
· Reference lidvariabelen int& r; zie references
Lidfuncties
· Soorten lidfuncties: constructors, destructors, selectors (er verandert niets) , modifiers
· lidFunctie() const;// De functie kan de lidvariabelen niet wijzigen. Niet noodzakelijk maar wel aan te bevelen.
· Een lidfunctie gedefinieerd in de Klasse is inline by default
· Lidfunctie aanroep bij object creatie
Singleton* s = new Singleton->Instance(); // Returnvalue moet pointer zijn.
Static lidfunctie
· Static lidfunctie is buiten de klasse te gebruiken zonder object X::fun();
· Heeft alleen toegang tot static lidvariabelen
· Kunnen niet overgeërfd worden
Static lidvariabelen
· Behoort tot de klasse en niet tot de instanties. Is voor alle objecten gelijk.
· Verplichte definitie buiten de klasse
class K
{ static int i; };
int K::i; of int K::i = 25; // niet in de header
· Bij gebruik van templates zijn meerdere regels nodig: int K<double>::i = 25; K<float>::i = 25; enz..
· Initialiseren van static lidvariabelen kan niet in de initializers van de constructor
Lidfunctie pointer
· Zonder closure pointer
Bij gebruik van een lidfunctiepointer moeten de lidfuncties static zijn want de functiepointer heeft alleen een functieadres en geen apart objectadres. Static lidfunctie zijn voor alle objecten identiek dus is geen verwijzing naar een object nodig. void (*ptr)(int);
ptr = K::staticMemberFunc; // K is een Klasse, geen object
ptr(2000);
· Met closure pointer
Closure pointer is geen c++ maar een Borland c++ keyword extension. Een closure maakt een functiepointer naar een niet static lidfunctie mogelijk. Een closure pointer heeft een adres naar een object én een adres naar een functie.
voorbeeld:
void (__closure* closurePtr)(int);
closurePtr = k1.nonStaticMemberFunc;
closurePtr(9999);
Friends
· Een friend is een gewone (globale) functie of klasse. Beter vermijden. Het enige voordeel is dat een friend gedeclareerd kan worden in een klasseheader.
1. Friend functie
· friend void fun();
· Toepassing: maak dezelfde syntax als ingebouwde typen: cos(x) i.p.v. object.cos()
2. Friend class
· Beter vermijden, friend class K;
3. Friend lidfunctie
· Beter vermijden, friend void K::fun();
4. Friend operatoren
· Bij eigen operatoren verdienen gewone functies de voorkeur boven lidfuncties. Declaratie in de klasse met friend: friend K operator+(const K& x, const K& y) // Gewone functie in klasseheader
Virtuele lidfuncties
· Basisklasse:
virtual fun() {}; // leeg
virtual fun() {code};
Toepassing 1: gebruik de lidfunctie uit de basisklasse of de lidfunctie van de afgeleide klasse als deze bestaat. Lege virtuele lidfuncties gebruiken als er geen code nodig is.
Toepassing 2: lidfuncties van afgeleide klassen aanroepen vanuit de basisklasse.
· virtual fun() = 0;
Pure virtuele functie. Om een abstracte basisklasse te definieren. Lidfunctie in afgeleide klasse vereist.
Functie objecten
· Een functie-object is een klasse met een operator () lidfunctie.
· Doel is functies doorgeven (Strategy pattern) aan een functie “voeruit”:
template <class Functie>
void voeruit(Functie f)
{ f(1234);
}
Deze functie accepteert gewone functies én functieobjecten (dit is een klasse!)
· Functieobject
Het voordeel van een functie-object is dat een (extra) parameter via de constructor kan worden doorgegeven. Er wordt een lidfunctie van het object doorgegeven. Dit geeft meer mogelijkheden dan bij het doorgeven van gewone functies:
class K
void operator()(int x) // Lidfunctie met operator ()
{}
Werking: Omdat er een operator () lidfunctie wordt gebruikt is geen functienaam nodig en is de interface voor alle functies gelijk, n.l. ().
voeruit(K());
Er wordt een anoniem object K() als argument doorgegeven aan voeruit, de operatorfunctie () wordt uitgevoerd: f();
Met getal 100 doorgeven aan constructor van K: voeruit(K(100));
· Gewone functies
Kunnen ook worden doorgegeven als argument i.p.v. functie-objecten omdat gebruik wordt gemaakt van een template
void GewoneFun(int x)
{}
voeruit(gewonefunctie);
· unary_function: Een basis klasse voor unary function objects in Borland c++.
Overerving
Tips
· Vermijd diepe overervingshiërarchieën, dit is moeilijk te begrijpen
· Meervoudige overerving is in 95% van de gevallen onnodig en geeft botsingen tussen gelijknamige lidfuncties
Public, private of protected overerving
· Private klasseleden zijn alleen zichtbaar binnen de klasse
· Protected zijn ook zichtbaar in afgeleide klassen
· Public klasseleden zijn altijd zichtbaar
· class K: public B
Public klasseleden van B zijn public in K
· class K: protected B
Public klasseleden van B zijn protected in K
· class K: private B
Public klasseleden van B zijn private in K
Constructors en destructors
· Basisklasse constructor wordt eerst aangeroepen, daarna die van de afgeleide klasse.
· Met destructors is het omgekeerd.
· Doorgeven waarde aan constructor basisklasse B
cpp file:
K::K(int x):
i(0), B(x)
Scoperesolutie parameter
· Toepassing 1. Aanroep forceren van een andere lidfunctie i.p.v. de lidfunctie die normaal zou worden aangeroepen zoals de lidfunctie van de afgeleide klasse.
B::f() //roep altijd lidfunctie f aan van de klasse B
· Toepassing 2. Public maken van een (public) functie B::fb (of variabele) van een basisklasse B, die anders private zou zijn:
ClassD : private B
{ public:
B::fb; // fb was private lidfunctie van B, maar is nu public!
}
Polymorfisme
· Een klasse is polymorf als hij minimaal één virtuele (of pure virtuele) lidfunctie heeft (of erft)
class B
{
public:
virtual int& value()=0; // pure virtuele lidfuncties
};
class A: public B
{
public:
int& value() // reference als functiewaaarde (setter / getter combinatie)
{ return i;
}
private:
int i;
};
B* b = new A; // b is een pointer die geschikt is om naar andersoortige (afgeleide) objecten te wijzen
b->value()=1234; // Setter / getter combinatie
int i = b->value();
Copiëren van klasse objecten
· Bij echt Kopieëren van klassen is een diepe copie gewenst. Kopiëren kan op 2 manieren:
K b(a); // copy constructor nodig
b=a; // assignment operator = nodig
· Voorbeeld
class K
{
public:
K(int i):
i(i){}; // constructor
K(const K& k) // copy constructor, moet met & volgens c++
{ i = k.i;
}
K &operator=(const K& k) copy assigenment operator
{ if(this != &k) // Bij k1=k1 mag er vaak niets gebeuren zoals deleten van pointers
{ i = k.i;
return *this; // i.v.m. kopiëren k1=k2=k3
}
}
private:
int i;
};
K k1(1234), k3(1);
K k2(k1); // gebruik de copy constructor
k1=k2=k3; // gebruik de assigenment operator
· Copy constructor bij overerving
Kafg:: Kafg (const Kafg & k) : KBasis(k) // Roep de KBasis copy constructor aan
{ code }
· De toekenningsoperator wordt niet geërft
Roep in de toekenningsoperatorfunctie de toekenningsoperator uit de basisklasse aan met KBasis::operator = (k);
Strings
1. Null-terminated strings
Let op: dit zijn arrays of adressen
· Ook wel genoemd c stijl string
· char* str = "tekst"; // "tekst" levert een adres af, \0 wordt toegevoegd
· *"tekst" = t / "tekst"[0] = t
· char str[10] = "tekst"; // string variable (array) met vaste lengte *str = str[0] = t
· Routines in Borland c++ :
1. Visual Component Library Ref. > Categorical Routines Listing > String handling routines (null-terminated)
Deze beginnen met hoofdletters: AnsiStrComp, StrCat
2. C runtime library reference > Categerical routines and Types Listing >
c++ Prototyped Routines strstr
Classification Routines isalnum
Conversion Routines itoa
Input / output Routines scanf
Manipulation Routines strcat
2. Standaardtype string (basic_string)
· Let op: functies werken ook voor c stijl strings
· string str;#include <string>, using namespace std;
· Standard C++ Library > Users guide > Part III: containers > string
· Gebruik liever String
3. AnsiStrings
· String = AnsiString (dit gaat automatisch)
· Gebruik altijd String voor strings, variabele lengte enz.
· Let op: er zijn veel meer functies dan Ansistring methods! Zoek in help op “String handling routines”
· Routines in Borland c++ :
Visual Component Library Ref. > Categorical Routines Listing > String handling routines
int AnsiCompareStr(const AnsiString S1, const AnsiString S2);
· AnsiCompareText, AnsiSameText, CompareText: without case sensitivity.
· “Menu support routines”
AnsiSameCaption, ShortCutToText enz.
· Omzetten van string naar getalwaarden StrToInt
· Voorbeelden:
String s = 5; // s = “5” Conversie integer en floats enz. naar String
String v=“Deze tekst mag niet worden” // Splitsen van tekst
“gesplitst.”;;
s=v[2] // s = ”b”
Mystring.c_str(); //Conversie AnsiString naar c string (char array)
StrToInt(Edit3->Text); // Conversie string naar int, nadeel throws een EConvertError exception
String S2 = S1.SubString(1,pos);
String Y = “XX” + S2; // Let op: één van beide moet van het type String zijn
Containers
Soorten containers
Containers selecteren
· Zie Borland c++ : Standard c++ library > Part III: containers > Container classes > Selecting a container
· Let op snelheid, R/W bij vector het snelst
Sequence containers
De volgorde blijft behouden
Map Random access x[key], 2 Dimensionaal, altijd gesorteerd, unieke elementen. Read, write: 220 nsec
Multimap Random access x[key], 2 Dimensionaal, altijd gesorteerd, niet unieke elementen,
Vector Random access x[i], efficiënt insert/delete op eind. Toevoegen van data (push_back) 120 nsec.
R/W 12 ns. Toepassen bij als de lengte niet van te voren bekend is en vaak wordt gewijzigd.
Deque Random access x[i], als vector, efficiënt insert/delete op begin of eind
List Snel insert, alleen sequentieel access
Eenvoudig elementen verwijderen: remove(const T& element); // verwijdert alle elementen gelijk aan element, hoeft niet in de list voor te komen
erase(iterator position)
return 1e element chartDatas.front();
Stack LIFO
Queues FIFO
priority_queue FIFO, als Queues
Gesorteerde associatieve containers
Gesorteerd opslaan, de volgorde blijft niet behouden
Set Verzameling unieke elementen.
Eenvoudig element verwijderen: erase(iterator position) of erase(const key_type& x)
Multiset Als set, niet unieke elementen
Pair Ook te gebruiken voor het retourneren van meer dan 1 variabele door een functie
DynamicArray Dynamisch van grootte veranderen. Resize is traag >4us. Toepassen als de lengte niet van tevoren
bekend is en niet vaak wordt gewijzigd. R/W 17 ns, trager dan vector!
DynamicArray<int> a;
a.Lenght=10;
a.Lenght=11; // resize
a.high // = Length-1.
Valarray Voor efficiënte numeric programming
Container functies
Voor alle containers
begin() Returns an iterator or a const_iterator pointing to the first element in the collection
end() Returns an iterator or a const_iterator pointing just beyond the last element in the collection
swap( Swaps elements between this container and the swap’s argument
clear() Deletes all the elements in the container
size() Returns the number of elements in the collection as a size_type
max_size() Returns the largest possible number of elements for this type of container as a size_type
empty() Returns true if the container is empty, false otherwise
get_allocator() Returns the allocator used by this container
Container algoritmes
Er is een hele verzameling van algoritmes voor containers, zie Standard C++ Library: User’s Guide / Agorithms
for_each
· Voert een functie f uit (met de iterator als argument) voor de elementen.
· Als f een waarde aflevert wordt deze genegeerd.
· void for_each(InputIterator first, InputIterator last, Function f);
· Aanroep: for_each(v.begin(), v.end(), f);
· Let op: Functie f of functie-object f moet een functieparameter hebben van het type van de container:
· vector<int> Functie voorbeeld f(int x)
· map<String, int> Functie voorbeeld f(pair<int, int> Pair) of met value_type.
· Bij een lidfunctie moet f static zijn.
· Een functieobject fo is nodig voor het eenmalig doorgeven van een variabele (5).
· Aanroep: for_each(v.begin(), v.end(), fo(5)); // Geef 5 door aan de constructor
find(k)
Returns an iterator pointing to an element with key equal to k or end(), if such an element is not found
count(k)
Returns the number of elements with key equal to k
find_if
· De (test) functie moet een bool variabele (true / false) afleveren.
value_type
· The type held by the container, a key/value pair. (typedef pair<const Key, T> value_type;)
· typedef map<string, int> MyMapType;
· typedef MyMapType::value_type My_PairType;
· My_PairType MyEntry
· cout << MyEntry.first << MyEntry.second << endl;
Alle elementen doorlopen bij map
typedef map<String, ConfigVariant*> mapType;
mapType myMap;
mapType::iterator i;
for(i = myMap.begin(); i != myMap.end(); i++) delete (*i).second; // b.v. delete
Verwijderen van een element in een vector
chartDatas.erase(find(chartDatas.begin(), chartDatas.end(), element));
Testen of de container een element bevat
if(find(chartDatas.begin(), chartDatas.end(), element) == chartDatas.end()) return;
Tips
Aanbevelingen
· Gebruik forward declarations, altijd pointers naar objecten (kan niet bij microcontrollers)
· Variabelen altijd initialiseren, b.v. in de constructor
· Gebruik altijd zo veel mogelijk const als het kan
· Vermijd globale variabelen en anders houd globale variabelen en functies lokaal binnen de file (of beter binnen de functie) met static
· Bij switch gebruik geen overbodige default case
· Gebruik safeDelete() {delete ptr; ptr = NULL;}
· Maak voor de veiligheid alle destructors virtueel
· Gebruik referentie argumenten bij doorgeven van grote objecten. fun(const string &x). Er hoeft geen copie gemaakt te worden
· Afronden geeft soms problemen met floats. Oplossing: Decimal class, heeft grondtal 10.
· Controleer af en toe de folders van de files in Borland c++ > View > project manager:!
Opmaak
Zie ook Google C++ Style Guide
Klasse header:
public:
methods
data members
private:
methods
data members
· else if op dezelfde regel
· While op dezelfde regel als de closing brace: } while( x < y );
Let op
· Use delete[] at arrays.
· B is executed twice in max(A, time-consuming operation B).
· Don't call virtual functions in a constructor or destructor.
· Don't store anonymous objects into a container.
· Use {} at nested if / else statements.
· Delete ook alle objecten waarvan de objectpointers in een container zijn opgeslagen bij het deleten van de container
while(!cont.empty())
{ safeDelete(cont.back());
cont.pop_back();
}
· Void vóór functies niet vergeten
· Kijk uit bij scanf, %hu gebruikten voor type Word
· Gebruik niet variabelenamen Function, Update(), delete enz.
Let op bij Borland c++
· Aansturen VCL componenten vanuit lidfunctie: met ActiveMDIChild en dynamic_cast.
· Keyword __published niet aankomen.
· In console applicatie: toevoegen #include <conio.h> en getch() op het eind;
· #include <vector> is niet altijd #include <vector.h>
Lastige compiler errors
· In header foutieve naam achter #ifndef. Oorzaak is fout bij kopiëren
· Bij een constructor met één argument dat een pointer is en waarbij gebruik wordt gemaakt van een anonym object gaat het fout. Zie voorbeeld anoniem object.
· Voorkomen warning “parameter … is never used”: vermeld het type zonder de variabele: fun(int, int, double d)
Lastige linker foutmeldingen
· Sluit klasse declaratie af met ;
· Fun(int x)
{ x = x; // gaat fout, doe x = _x of this.x = x;
}
· Bij static datamembers verplichte definitie buiten de klasse
· Gebruik forward declaration bij circular includement in header
· “Unresolved external .. xxx.obj” Plaats in de cpp file de template lidfuncties #include .cpp
Lastige runtime foutmeldingen
· Let op overschrijding van arrays char str[100];
· Fouten bij geheugen toewijzing
fun(char* c)
{ sprintf(c, “bla”); // c wordt gevuld
}
Aanroep:
char* ch; fun(ch); // Gaat fout want ch heeft nog geen geheugen toegewezen gekregen
· Scope van een variabele is verlopen:
fun(char* &ref)
{ char str[6] = "E-21";
ref = str; // Tot hier gaat het goed
}
Aanroep:
char* arg;
fun(arg); // Fout: na beïndigen van fun wijst arg naar een stuk vrijgegeven geheugen
Voorbeeld2:
fun(char* prefix)
{ char* p = “bla”;
prefix = p;
}
Aanroep:
char* ch;
fun(ch); // Fout: de geugeninhoud van prefix wordt vrijgegeven
· char* str = "E-21"; // Pointer naar een vaste geheugenplaats, is niet hetzelfde als:
char str[6] = "E-21"; // Array met initialisatie
· Form ontbreekt in: project options > forms > auto create forms
· Aanroepen van een lidfunctie van een object dat nog niet gecreëerd is.
· In de constructor van een basis klasse wordt een lidfunctie van een afgeleide klasse aanroepen. Foutmelding “Pure virtual function called”. De afgeleide klasse is nog niet volledig gecreëerd. Oplossing: maak een functie init() in de basisklasse die de functies van de afgeleide klasse oproept. Roep init() aan in de constructor van de afgeleide klasse.