Synlighet Synlighet
Namespace
Scope-operatorn Klasser
Vänner
Synlighet
• Ett problem med moduler i C är att alla
variabel- och funktionsnamn ligger globalt synliga.
• C++ botar detta genom att införa det mycket användbara nyckelordet namespace
• I namnrymder kan man kapsla in klasser, funktioner och data
• Med synlighetsoperatorn (scope operator) ::
kan man komma åt en namnrymd och medlemmar i klasser
• Med using exponerar man innehåll i en namnrymd
• Med odöpta namnrymder (unnamed
namespaces) kan man deklarera data som endast är synliga i filen där de är definierade
• ObsoIet metod: I C (och därför även C++) skapar man filstatiska funktioner genom nyckelordet static
• Genom att ha fillokala data minskar risken för namnkonflikter
Synlighet
Synlighet
namespace Spc // håll namnen på namnrymder korta {
class A; // framåt(forward)deklaration int foo();
int j;
enum {arms = 2, legs = 1, hair = 517};
}
class Spc::A // A är inte synlig utan Spc::
{
public :
int foo();
int j;
};
inline int Spc::A::foo() {return j;} // använder Spc::A::j inline int Spc::foo() {return j;} // ok: Spc::j synlig
Synlighet
int main() {
if(Spc::j > Spc::legs) // Använd Spc::j och { /*...*/ } // Spc::legs explicit
using Spc::j; // j synlig i hela main()
j = Spc::foo(); // foo() ej synlig, använd Spc::
Spc::A a;
j = a.foo(); // Spc::A::foo() synlig genom a
using Spc::A; // hela klassen A synlig using namespace Spc; // allt data i Spc synligt
return 0;
}
Några överkursexempel på namnrymder och using
na m es pa c e XY Z _v e rs io n 1 {
i nt i ;
v oi d f o o( ) ; // d e kl ar a ti on }
na m es pa c e XY Z _v e rs io n 2 {
i nt i ; i nt j ;
v oi d f o o( ) ; // d e kl ar a ti on }
na m es pa c e XY Z = XY Z_ v er si o n2 ; // a l ia s f ör n a mn r ym d
vo i d XY Z :: fo o () {} // d e fi ni t io n a v XY Z _v er 2 :: f oo in t i ; // g l ob al va ri a be l i
in t j ; // g l ob al va ri a be l j
// ex em p el p å u s in gd e kl ar a ti on (e ns t ak a v ar ia b el ) {
u si n g XY Z :: i ; // l o ka l i , gö m me r g lo b al i i = 3; // o k : lo k al i
}
// ex em p el p å u s in gd i re kt i v (h e l na m nr ym d ) {
u si n g na m es p ac e X YZ ; // i n tr od u ce ra r i , j o c h fo o j = 4; // f e l! : : j el l er X Y Z: : j ? }
Man kan byta synlighet på medlemmar med using
Synlighet
struct A { protected:
int i;
};
struct B : public A { using A::i;
};
A a;
B b;
a.i = 1; // fel: i är protected b.i = 2; // ok
Synlighet och klasser
• För att komma åt data i klasser använder man synlighetsoperatorn ::
• Ett bra exempel på hur synlighetsoperatorn används är STLs iteratorer
• Iteratorerna är inkapslade i behållarklasserna och döpta till samma sak: T::iterator
resp. T::const_iterator
Synlighet och klasser
// här kan vi byta till lämpligare behållare vid behov // t.ex. std::vector eller std::deque
typedef std::list<std::string> T;
T t;
...
// oavsett klass, hämta dess const_iterator T::const_iterator i = t.begin();
for(; i != t.end(); ++i)
std::cout << *i << std::endl;
Nedanstående exempel visar hur man kan använda klassens synlighetoperator för att abstrahera sin kod
const
Deklarationer med const ger skrivskydd.
class A {
const int ci; // ci är skrivskyddad (ssk) const int *p_ci; // pekare till ssk int
int *const cp_i; // ssk pekare till int
const int *const cp_ci; // ssk pekare till ssk int
int get() const; // objektet är ssk i get int i;
};
// källkodsfil
int A::get() const { return i; } // ok: ändrar inte int A::get() const { return i = 2; } // fel: ändrar obj
const och mutable
Funktioner deklarerade med const gör objektet read-only. Med mutable inför man ett undantag.
class A {
int get() const; // objektet är read-only i get int i;
mutable int access_count;
};
inline
int A::get() const {
access_count++; // ok: får ändras trots const return i;
}
Vänner
• Ibland måste en klass ha tillgång till data i en annan
• Man vill dock inte använda åtkomsttypen public eftersom man får oönskad insyn
• Man specificerar sina vänner (klass eller funktion) med friend
• De får då åtkomstnivå public på data och funktioner
• friend-förhållandet ärvs inte
• friend bör undvikas för att inte äventyra inkapslingen
cl a ss M y In t {
i nt i; // i är p r iv a te pu b li c:
M yI n t( in t j ) : i ( j) { }
f ri e nd c o ns t M yI n t op e ra to r *( in t , co n st M y In t & );
f ri e nd s t d: : os tr e am &
o pe r at or < <( s td :: o st re a m &, co ns t M yI n t &) ; };
in l in e c on st My I nt o p er at o r* (i n t i, co ns t M yI n t & j)
{ r et ur n M yI n t( i * j . i) ; } // k o mm er åt My In t :: i
in l in e s td :: o st r ea m &
op e ra to r << (s t d: : os tr e am & s , co n st M y In t & j)
{ s < < " {M yI n t = " < < j .i << " } "; } // k o mm er åt My In t :: i
Vänner
Överlagring av operatorer Överlagring av operatorer
Konstanta referenser Returtyper
Argumenttyper
Överlagring av operatorer
• Man kan omdefiniera operatorerna i C++
• Detta gäller dock endast för egna typer
såsom klasser, enums etc.
class MyInt { public:
const MyInt &operator=(const MyInt &); // tilldeln.
const MyInt &operator+=(const MyInt &); // tilldeln.
const MyInt operator+(int) const; // addition const MyInt operator+(const MyInt &) const;
...
};
MyInt i, j;
i = j + 7; // använd oper+() och oper=() j += i; // använd oper+=()
i.operator+=(j); // operatorer kan användas explicit
Överlagring av operatorer
Exempel på operatoröverlagring
Överlagring av operatorer
Det är viktigt att tänka på returtyperna för operatorerna
• Ta addition som exempel: inget av de ingående objekten innehåller resultatet och därför måste en temporär skapas
• Tar man däremot prefix ++-operatorn så håller objektet själv rätt värde och kan returneras som referens
• Om returnerade referenser ska vara const är olika från fall till fall och beroende på klassens
användningsområden (ta exempel heltalsklass):
– helst const i fallet (a = b) = c
– inte nödvändigtvis const om (rect = r).normalize()
• Är man osäker härmar man beteendet hos int
class B;
class A {
int operator()(int, double); // anropsoper B &operator[](int) const; // indexering B &operator[](const char *) const; // (i t.ex. map) const A operator++(int); // postfix inkr.
const A &operator--(); // prefix dekr.
const A operator*(const A &) const; // multiplikation A &operator*(); // avreferering };
const A operator*(int, const A &); // extern mult std::ostream &operator<<(std::ostream &, const A &);
Överlagring av operatorer
Ytterligare exempel på operatoröverlagring
Polymorfi Polymorfi
Dynamisk bindning Virtuella funktioner Virtuella destruktorer
Strikt virtuella funktioner
Abstrakta basklasser
Virtuella funktioner
• Normalt sett: kompilatorn vet vilken funktion som skall köras vid funktionsanrop - statisk bindning
• Virtuella funktioner: vilken funktion som körs bestäms av vilket objekt funktionen anropats genom - dynamisk bindning.
• Detta kallas polymorfi – beteendet bestäms vid körningstillfället
• Polymorfi är endast applicerbart på pekare och referenser eftersom typen på ett objekt är känt vid kompileringstillfället.
s t r u c t A {
v o i d p r i n t ( ) { s t d : : c o u t < < " A " ; } v i r t u a l v o i d v p r i n t ( ) { s t d : : c o u t < < " A " ; } } ;
s t r u c t B : p u b l i c A {
v o i d p r i n t ( ) { s t d : : c o u t < < " B " ; } v i r t u a l v o i d v p r i n t ( ) { s t d : : c o u t < < " B " ; } } ;
A a ; B b ;
A * a p = & b ; / / o k : B ä r s u b k l a s s t i l l A A & a r = b ; / / o k : a r r e f e r e r a r a
a p - > v p r i n t ( ) ; / / p o l y m o r f i : s k r i v e r u t " B "
a r . v p r i n t ( ) ; / / p o l y m o r f i : s k r i v e r u t " B "
a p - > p r i n t ( ) ; / / e j p o l y m o r f i : s k r i v e r u t " A "
a r . p r i n t ( ) ; / / e j p o l y m o r f i : s k r i v e r u t " A "
b . v p r i n t ( ) ; / / o b j e k t : s k r i v e r u t " B "
B * b p = & a ; / / f e l : A ä r i n t e n e d ä r v d f r å n B
Virtuella funktioner
Virtuella destruktorer
class Av { public: virtual ~Av(); };
class Bv : public Av { public: ~Bv(); };
class A { public: ~A(); };
class B : public A { public: ~B(); };
B *bp = new B;
A *ap = bp;
delete ap; // fel?: B's destruktor anropas inte
Bv *bvp = new Bv;
Av *avp = bvp;
delete avp; // ok: B's destruktor anropas
För att alla objekt i en arvskedja skall få sina
destruktorer anropade krävs att basklassen har en virtuell destruktor.
Abstrakta basklasser och strikt virtuella funktioner
• En strikt virtuell (pure virtual) funktion tvingar subklasser att ge en definition
• Basklassen kan då inte instantieras och kallas därför abstrakt basklass
• En abstrakt basklass definierar ett
interface som alla nedärvda klasser
måste följa
cl a ss A {
pu b li c:
v ir t ua l v oi d p ri n t( ) = 0 ; // s t ri kt pg a ' =0 ' };
cl a ss B : pu b li c A {
pu b li c:
v oi d p ri n t( ) ; / / d ef in i ti on må st e g e s om }; / / B s ka l l in s ta nt i er a s
vo i d B: : pr in t () {
s td : :c ou t < < " B" << s t d: :e n dl ; }
.. .
A a ; / / fe l : A ä r en ab st r ak t b as kl a ss
B b ; / / ok : B h a r al l a fu n kt io n er d e fi n ie ra d e b. p ri nt ( ); / / kö r B :: p ri nt ( );