HTML

c++ interjú kérdések

Ezt a blogot azért írom mert mostanában alkalmam nyílt több senior c++ állást megpályázni (munkanélküli lettem :)) és az elbeszélgetés előtt a következőkben felsorolt kérdéssorokat tolták elém. Főleg azoknak a c++ fejlesztőknek akarok segíteni akik évek óta ugyanazon a projekten dolgoznak és viszonylag ritkán kell új osztály hierarchiát létrehozniuk, inkább hibajavítással foglalkoznak vagy a meglévő osztályokat használják mit ahogy én is tettem.

Friss topikok

Címkék

Archívum

Tesztfeladatok C++ álláskeresőknek avagy C++ interjú kérdések.

2011.10.13. 09:18 landroo

 

A C++ nyelv nagyon hatékony és jól programozható, sajnos az elkövetkező példák pont az ellenkezőjét bizonyítják, néhány kivétellel. Érthetetlen módon a tesztfeladatok pont ezekre kérdeznek rá, így egy alkalmazott fejlesztő szinte sohasem találkozik ilyenekkel.

Első fejezet, osztályöröklés.

Az első kedvenc téma a virtual függvény és osztályöröklés :). Szinte mindegyik felvételi feladatsorban előfordult.

Első példa a mit ír ki kategóriában:

(A kódok mind a VisualStudio-ból vannak kimásolva egy futtatás után)

/************************************************/

#include "stdafx.h"

#include <iostream>

class cls1

{

public:

   virtual void func(int i = 1) = 0;

};

class cls2: public cls1

{

public:

   virtual void func(int i = 2)

   {

      std::cout << i << std::endl;

   }

};

int _tmain(int argc, _TCHAR* argv[])

{

   cls1 *first = new cls2;

   first->func();         // 1

   cls2 *sec = new cls2;

   sec->func();         // 2

   getchar();

   return 0;

}

Mint látjuk a program második sora 1-et ír annak ellenére, hogy cls2-őt hozunk létre, hiszen absztrakt osztályt önmagában nem is példányosíthatnánk, tehát a

   cls1 *first = new cls1;      // Fordítási hiba

fordítási hibát eredményez.

Persze ha kasztolnánk cls2-re

   ((cls2*)first)->func();      // 2

akkor 2-t kapnánk eredmények.

A cls2 implementálta a cls1 absztrakt függvényét így azt meg lehet hívni a virtuális táblán keresztül, de miután a default értéket a cls1-ben adtuk meg és az osztálytípusunk cls1 így az ehhez tartozó érték fog megjelenni.

(Világos, nem? Csak arra lennék kíváncsi hol van értelme az ilyen kódoknak. Persze ez a többi feladatra is érvényes.)

Imádják, pedig a polimorfizmus egy kicsit ellentmond a C++ szigorú típusellenőrzésének, hiszen addig nem tudod, mire mutat az osztály pointered amíg meg nem hívod, hacsak nincs a fejedben az egész virtuális tábla.

Tehát nem elég, hogy átláthatatlan a kód, még lassú is mert minden függvényt a virtuális táblán keresztül kell meghívni. Persze, van ahol egyszerűsíti a kódot, de nem könnyíti meg a hibakeresést. Meg hát zárt kód esetén nincs más mód az ősosztály átformálására.

Második példa:

/************************************************/

#include "stdafx.h"

#include <iostream>

class cls1

{

public:

   cls1(){std::cout << "cls1 constructor" << std::endl;}

   ~cls1(){std::cout << "cls1 destructor" << std::endl;}

};

class cls2: public cls1

{

public:

   cls2(){std::cout << "cls2 constructor" << std::endl;}

   ~cls2(){std::cout << "cls2 destructor" << std::endl;}

};

int _tmain(int argc, _TCHAR* argv[])

{

   cls2* cls = new cls2();

   getchar();

   return 0;

}

Ez a kód sokkal egyszerűbb, mint az előző, hiszen egyből rávágja az öröklést ismerő programozó, hogy

cls1 constructor

cls2 constructor

cls2 destructor

cls1 destructor

A main függvényből kihagyták a delete cls sort, csakhogy kiszúrjanak a tisztelt delikvenssel. Ennek hiányában azonban csak a konstruktorok futnak le, a destruktorok nem. Ezért csak az első 2 sor jelenik meg a kimeneten.

cls1 constructor

cls2 constructor

Ha nem veszed észre a delete hiányát vagy azt feltételezed, hogy az oprendszer meghívja, akkor buktad.

Ez is jellemző a felvételi feladatokra, nem azt nézik, hogy mit tudsz, hanem azt, hogy mit nem, vagy mit nem veszel észre. Valahogy az az érzésem, hogy a rezidens fejlesztők ki akarnak szúrni az újonnan jelentkezőkkel, és olyan feladatkódokat írnak, amivel nekik is gondjuk volt. Szóval írnak egy példakódot, aztán kitörölnek belőle sorokat és neked meg kell fejtened, mik voltak azok.

Harmadik példa:

/************************************************/

#include "stdafx.h"

#include <iostream>

class cls1

{

public:

   cls1()

   {

      this->func();

   }

   virtual void func()

   {

      std::cout << "cls1 func" << std::endl;

   }

};

class cls2: public cls1

{

public:

   cls2()

   {

      this->func();

   }

   virtual void func()

   {

      std::cout << "cls2 func" << std::endl;

   }

};

int _tmain(int argc, _TCHAR* argv[])

{

   cls2 cls;

   getchar();

   return 0;

}

Ehhez nincs is mit hozzáfűzni, majdnem ugyanaz, mint a második példa csak megspékelték egy-egy virtuális függvénnyel. Hogy minek, arra nem sikerült rájönnöm, hiszen az osztálypéldányosítás sem dinamikus, hanem statikus, így nem használható a virtuális tábla. Szóval a program kimenete:

cls1 func

cls2 func

Negyedik példa:

/************************************************/

#include "stdafx.h"

#include <iostream>

class cls1

{

public:

   virtual void func()

   {

      std::cout << "cls1 func" << std::endl;

   }

};

class cls2: public cls1

{

public:

   virtual void func()

   {

      std::cout << "cls2 func" << std::endl;

   }

};

void glFunc(cls1 cls)

{

   cls.func();

}

int _tmain(int argc, _TCHAR* argv[])

{

   cls1 a;

   a.func();   // cls1 func

   cls2 b;

   b.func();   // cls2 func

   glFunc(b);   // cls1 func

   getchar();

   return 0;

}

A negyedik példában is ugyanazokat látjuk, mint a harmadikban, csak itt van egy globális függvény is. Ez a kapott örökölt példányt ős osztállyá kasztolja és így annak a member függvényét hívja meg.

Ötödik példa:

/************************************************/

#include "stdafx.h"

#include <iostream>

class cls1

{

public:

   virtual void func1() = 0;

   virtual void func2() = 0;

};

class cls2:public virtual cls1

{

public:

   virtual void func1();

};

void cls2::func1()

{

   std::cout << "cls2 func1" << std::endl;

   func2();

}

class cls3:public virtual cls1

{

public:

   virtual void func2();

};

void cls3::func2()

{

   std::cout << "cls3 func2" << std::endl;

}

class cls4:public cls2, public cls3

{

public:

};

int _tmain(int argc, _TCHAR* argv[])

{

   cls4* a = new cls4();

   cls2* b = a;

   cls1* c = a;

   a->func1();

   b->func1();

   c->func1();

   getchar();

   return 0;

}

Na, itt aztán megkeverték a kódot rendesen. Szerintem ennek a kibogozásába még magának Stroustrupnak is beletörne a bicskája. A program kimenete háromszor ugyanaz, nevezetesen:

cls2 func1

cls3 func2

cls2 func1

cls3 func2

cls2 func1

cls3 func2

Hiszen az absztrakt ősosztály teljes implementálása csak cls4-ben sikeredett, és bármit is csinálunk a virtuális tábla csak az implementált függvényekre tud mutatni. A cls2::func1() függvényben lévő func2(); hívás felelős a "cls3 func2" kiírásokért. Azért érdekes lehet az osztályok hívás early binbdig esetén :)

Hatodik példa:

/************************************************/

class cls1

{

public:

   virtual void func()

   {

      std::cout << "cls1 func" << std::endl;

   }

};

class cls2:public cls1

{

};

class cls3:public cls1, public cls2

{

public:

   virtual void func()

   {

      std::cout << "cls3 func" << std::endl;

   }

};

int _tmain(int argc, _TCHAR* argv[])

{

   cls3 cls;

   cls.func();   // cls3 func

   getchar();

   return 0;

}

Ez a példa az előbbinek egy némileg egyszerűsített változata, bár ez is a „találd ki mit töröltem ki a kódból” mezőnyben indult. Eredetileg ugyanis a cls3 is üres osztály volt. A kimenete egyértelmű cls3 func

Hetedik példa:

/************************************************/

#include "stdafx.h"

#include <iostream>

class cls

{

public:

   cls(int i =0)

      :m_i(i)

   {

   }

   cls(const cls a)

   //cls(cls &a)

   {

      this->m_i = a.m_i;

   }

   const cls& operator = (const cls a)

   {

      this->m_i = a.m_i;

      return *this;

   }

private:

   int m_i;

};

int _tmain(int argc, _TCHAR* argv[])

{

   cls a;

   cls b(a);

   getchar();

   return 0;

}

Ez a kód nem fordul le, mert a copy konstrutorban nem konstans osztály példányt kell átadni, hanem referenciát, ahogy a kiemelt sor mutatja. Gondolom erre gondolt a teszt írója, mert a kérdés az volt, hogy mi a hiba a következő kódban.

Nyolcadik példa:

/************************************************/

#include "stdafx.h"

#include <iostream>

class clsError/*{}*/;

class cls

{

public:

   cls(int iNum)

   {

      this->m_iNum = iNum;

   };

   ~cls()/*{};*/

   int getNum()

   {

      return this->m_iNum;

   }

private:

   int m_iNum;

};

void func(int iNum)

{

   cls* a = new cls(iNum);

   if(a->getNum() == 1)

      std::cout << a->getNum() << std::endl;

   else

      throw(new clsError);

   delete a;

}

int _tmain(int argc, _TCHAR* argv[])

{

   int i = 11;

//   try{

      func(i);

//   }catch(...){}

   getchar();

   return 0;

}

Ez a kód szintén nem fordul le, ez is a „találd ki mit töröltem ki a kódból” kategóriában indult. A kiemelt kódrészletek hiányoztak a kódból. Gondolom, arra lehetett kíváncsi a feladat kiötlője, hogy észreveszi-e a delikvens, hogy a throw-hoz tartoznia kell egy catch-nek is.

Kilencedik példa:

/************************************************/

#include "stdafx.h"

class cls1

{

public:

   cls1()

   {

      fnc(11);

   }

   virtual void fnc(int i = 1)

   {

      printf("class : %d\n", i);

   }

};

class cls2: public cls1

{

public:

   cls2()

   {

      fnc(22);

   }

   virtual void fnc(int i = 2)

   {

      printf("class : %d\n", i);

   }

};

 

int _tmain(int argc, _TCHAR* argv[])

{

   cls1 c1;            // class: 1

   cls1 *pc1 = new cls1();   // class: 1

   cls2 c2;            // class: 1 class 2

   cls2 *pc2 = new cls2();   // class: 1 class 2 

   printf("\n");

   c1.fnc();            // class: 1

   pc1->fnc();            // class: 1

   

//   ((cls2)c1).fnc();      // ambiguous

   ((cls2*)pc1)->fnc();   // class: 2

   getchar();

   return 0;

}

Itt az early és a late bindig-re kell odafoigyelni.

A következő nagyobb fejezet a template osztályok. Ezzel aránylag ritkán találkoztam, igaz az életben sem fordult elő, hogy írnom kellett volna akár egyet is, hiszen szinte minden szükséges template osztály megtalálható az STL-ben illetve a WTL-ben.

Első példa:

/************************************************/

#include "stdafx.h"

#include <iostream>

template <typename X>

class cls

{

public:

   cls(int i):

      data(0),

      m_iSize(i)

   {

      data = new X(m_iSize);

   }

   ~cls()

   {

      if(this->data)

         delete [] data;

   }

   void setData(int iDx, const X& val)

   {

      if(iDx < this->m_iSize)

         this->data[iDx] = val;

   }

   X getData(int iDx) const

   {

      if(iDx < this->m_iSize)

         return this->data[iDx];

      else

         return X();

   }

private:

   X*   data;

   int   m_iSize;

};

int _tmain(int argc, _TCHAR* argv[])

{

   cls <int> a(10);

   a.setData(5, 1);

   std::cout << a.getData(5) << std::endl;

   std::cout << a.getData(11) << std::endl;

   getchar();

   return 0;

}

Itt a kérdés az volt, hogy mi a hiba a következő osztálysablon definícióban és mi a javítás rá. Hogy megmondjam az őszintét én semmi kivetni valót nem találtam benne, lefordult, futott, hacsak azt nem, hogy a member tömb tagjai nincsenek inicializálva, így memória szemét van bennük. Persze írogathatunk még hibákat, hogy miért nem hibát dob az osztály, ha hibás az index, de, hogy valójában milyen hibára gondolt a tisztelt tesztíró, azt csakis ő tudhatja. Amúgy az egész abszolút értelmetlen, hiszen egy int *i = new int [10]; sorral helyettesíthető.

Második példa:

/************************************************/

#include "stdafx.h"

#include <iostream>

template <int i>

struct stru

{

   enum

   {

      val = i * stru<i - 1>::val

   };

};

template <>

struct stru<0>

{

   enum

   {

      val = 1

   };

};

int _tmain(int argc, _TCHAR* argv[])

{

   std::cout << stru<5>::val << std::endl;

   std::cout << stru<0>::val << std::endl;

   

   getchar();

   return 0;

}

Ez egy tanulságos kis kód, tulajdonképpen frakcionálist számol, így sablon struktúrákat hoz létre rekurzívan. Wow, ilyet is lehet C++-ban, jól látszik mivel is ütik el az időt a ráérő C++ fejlesztők. Ami még érdekes az egészben, hogy egy overloading sablon is szükséges a 0-ra, ami 1-et ad vissza. Ha ezt a részt kiemeljük összeomlik a fordító :), hiszen a sablon csak akkor kerül befordításra ha használni próbáljuk, ekkor generál kódot belőle a fordító.

Második fejezet, Const és társai, értem ezalatt a static és referencia változókat.

Első példa:

/************************************************/

#include "stdafx.h"

class cls1

{

public:

   virtual const int* func(const int* i) const = 0;

};

class cls2:public cls1

{

public:

   virtual const int* func(const int* i) const

   {

      int j = *i;

      this->m_i = i;

      return this->m_i;

   }

private:

   const int* m_i;

};

int _tmain(int argc, _TCHAR* argv[])

{

   int i = 1;

   int* pi = &i; 

   cls2 cls;

   cls.func(pi);

   getchar();

   return 0;

}

Ilyen kódokkal is gyakran találkozni a feladatsorban, aztán felteszik a kérdést, hogy melyik const mire vonatkozik. Ez a kód nem fordul le, miután a csl2 osztályban implementált függvényben megpróbálunk értéket adni egy const membernek. Amúgy ez az a kulcsszó, aminek vajmi kevés értelme van a C++-ban. Alig találkoztam velük a netes példákban, csak azokban amelyek pont a const használatát magyarázták :). Hiszen ha olyan figyelmetlen vagy, hogy az általad konstansként használt változót át akarod írni, akkor magadra vess.

De ha már itt tartunk, simán felül lehet írni egy const változó tartalmát egy egyszerű kis kód segítségével. Imígyen:

#include "stdafx.h"

int _tmain(int argc, _TCHAR* argv[])

{

   const int a = 100;

   int b = 333;

   const int *const pa = &a;

   int *pb = &b;

   printf("%d %d\n", *pa, *pb);   // 100 333

   //pa = &b;         // error you cannot assign to a variable that is const

   //*pa = 5;         // error you cannot assign to a variable that is const

   _asm

   {

      mov eax, pb

      mov pa, eax

   }

   printf("%d %d\n", *pa, *pb);   // 333 333

   getchar();

   return 0;

}

Szóval ennyit a const-ról és ha én kódból meg tudom változtatni egy const változó értéket, akkor egy programhiba is meg fogja tudni.

A második példában összegyűjtöttem néhány tipikus kérdést a referenciákra és konstansokra.

/************************************************/

#include "stdafx.h"

#include <iostream>

void func1(int &i)

{

   ++i;

}

void func2(int i)

{

   ++i;

}

int g = 0;

int& func3()

{

   return g;

}

int& func4()

{

   int i = 5;

   return i;   // nem ajánlott csak ha a változó static vagy globális

}

const int& func5()

{

   static int i = 1;

   return i; 

}

int _tmain(int argc, _TCHAR* argv[])

{

   int i = 0;

   func1(i);   // 1

   func2(i);   // 1

   std::cout << i << std::endl;

   func3() = 2;   // 2

   ++func3();      // 3

   std::cout << g << std::endl;

   i = func4();   // ??

   std::cout << i << std::endl;

   int j = 1;

   const int& rj1 = j;      // kérdés mi a különbség

   int& const rj2 = j;      // értelmetlen

   std::cout << j << std::endl;

   i = func5();

   std::cout << i << std::endl;

   //++func5();         // constans felülírás

   getchar();

   return 0;

}

Ehhez nincs is mit hozzáfűzni, csak jó, ha az ember felkészül, mire kérdezhetnek rá.

Meg hát az elmaradhatatlan mi a különbség típusú kérdések, mint például:

int *const pi = &i;      // int-re mutató const int pointer

int const *pi = &i;      // const int-re mutató int pointer

const int *const pi = &i;   // const int-re mutató const int pointer

stb.

Pointer indirektció minden mennyiségben.

A harmadik nagyobb fejezet a pointerek kezelése. Minden C++ programozó attól érzi magát különbnek, hogy olyan bonyolult pointer műveleteket tud csinálni, amibe maga is belezavarodik. Vegyünk is mindjárt egy egyszerű példát.

Első példa:

/************************************************/

#include "stdafx.h"

int _tmain(int argc, _TCHAR* argv[])

{

   char *s[] = {"first", "secound", "third", "fourth"};

   char **ps[] ={s + 3, s + 2, s + 1, s};

   char ***p3 = ps;

   printf("%s\n", *s);      // first 

   printf("%s\n", **ps);   // fourth

   printf("%s\n", **p3);   // fourth

   **++p3;

   printf("%s\n", **p3);   // third

   printf("%s\n", *--*++p3 + 3);   // st

   

   getchar();

   return 0;

}

Én ezt csak úgy tudtam lekövetni, hogy felrajzoltam melyik pointer hova mutat és így is elvétettem az utolsó +3mat. Azért kíváncsi lennék, ki az, aki ezt fejből elsőre megmondja. Persze tudom, az igazi programozó első ránézésre megmondja egy 2 megás dumb-fájból, hogy hol a hiba :). Meg, ha mondjuk megváltozik a bemeneti sting tartalma valamely okból, pl. más nyelvűt kell csinálni, mennyi ideig fog küzdeni a tisztelt C++ fejlesztő, mire átírja ezt a kifejezést *--*++p3 + 3 egy megfelelőre.

Második példa:

/************************************************/

#include "stdafx.h"

int _tmain(int argc, _TCHAR* argv[])

{

   float f = 2.5;

   float *pf = &f;

   char *cf = (char*)&f;

   printf("%f\n", *cf);      // 0.000000

   printf("%f\n", *(float*)cf);   // 2.500000

   printf("%f\n", f);      // 2.500000

   printf("%f\n", *pf);      // 2.500000

   printf("%f\n", *(float*)&f);   // 2.500000

   

   getchar();

   return 0;

}

Itt, ha nem cast-ols akkor az alapértelmezett cast fut le és nem az, amit várnál és persze ne feledkezzünk meg az indirektcióról sem.

A többi példába mindenféle beletartozik: ciklusok, operátorok, bitvadászat, paraméterek, és értelmetlenségek. Egy átlagos C++ programozó viszonylag ritkán találkozik velük, hiszen a saját jól megszokott rutinjait és eljárásait használja. Ez mindenkinél más lehet, hiszen a C++ nagyon engedékeny nyelv, nem hiszem, hogy lenne olyan programozó aki minden projekt kapcsán a C++ minden lehetőségét kihasználná a frend függvénytől az operátor overloading-on keresztül a makrókig. Hiszen én nyugodtan megtehetem, hogy felül definiálom a - > operátort és innentől kezdve lesz értelme a:

MyClass cls;

cls->func();

hívásoknak. Én például bit-műveleteket Assembly-ben használtam utoljára. C-ben csak a flag-ek beállítására illetve kiolvasására használtam az and és az or műveleteket.

DWORD flags = FILE_ATTRIBUTE_NORMAL | FILE_ATTRIBUTE_HIDDEN;

if(flags & FILE_ATTRIBUTE_NORMAL)

{

   ...

Első példa:

Ciklusváltozó

/************************************************/

#include "stdafx.h"

int _tmain(int argc, _TCHAR* argv[])

{

   char *c = "text";

   for(int i = 0; c[i++];i)

   {

      printf("%c", c[i++]);

   }

   getchar();

   return 0;

}

Kimenet: 

et 

mert kétszer is növeljük a ciklusváltozót és a string lezáró nulla a ciklusfeltétel.

A következő példa is ide tartozik, bár nem kifejezetten a ciklus változóról szól:

/************************************************/

#include "stdafx.h"

int _tmain(int argc, _TCHAR* argv[])

{

int i = -1;

int *ia = new int [5];

while(i < 4)

{

ia[i] = ++i;

printf("%d\n", ia[i]); // 0 1 2 3 4

}

delete [] ia;

getchar();

return 0;

}

Első ránézésre azt gondolná az ember, hogy a tömb -1-edik elemébe akarjuk beleírni a változó értékét, de nem így van mert az inkrementáció már az értékadás előtt végrehajtódik, így az első elem értékébe kerül a már az egyel megnövelt szám, az-az a nulla. Nem tudom, nekem eszembe sem jutna ilyen kódot írni, mert ugye nem akarjuk megszivatni az utánunk jövő kollégát, vagy igen?

Nekem a suliba még azt tanították annó, hogy tiszta, érthető, világos, jól kommentezett kódot kell írni. 

Hahh, hiába, változnak az idők.

Amikor annak idején tőlem kértek ilyen tesztfeladatokat, én is hasonlóban gondolkodtam de a főnököm rám szólt, hogy nem megszivatni akarjuk a jelentkezőt hanem megtudni, hogy mit is tud. Minden tiszteletem az övé, de úgy látszik mára már kihaltak az ilyen szakemberek.

Az inkrementációra visszatérve, én azt tapasztaltam, hogy érdemes külön sorba ki tenni, mert egy módosítás során valamelyik függvényhívásban benne felejtett inkrementáció vagy dekrementáció súlyos galibákat tud okozni.

Második példa:

operátorok

/************************************************/

#include "stdafx.h"

int _tmain(int argc, _TCHAR* argv[])

{

   char c = 'A';

   printf("%d %d %d\n", sizeof(c), sizeof('A'), sizeof(2.5));   // 1 1 8

   

   getchar();

   return 0;

}

 

Harmadik példa:

bitvadászat

/************************************************/

#include "stdafx.h"

int _tmain(int argc, _TCHAR* argv[])

{

   a = 3;

   b = 4;

   printf("%d %d %d %d %d\n", a & b, a | b, a ^ b, a >> b, a << b);   // 0 7 7 0 48

   printf("%d %d %d %d\n", a & a | b & b, a | a & b | b, a & b | b & a, a | b & b | a);   // 7 7 0 7

   

   getchar();

   return 0;

}

Bitvadászatból aztán mindenféle/fajta kombináció előfordulhat AND (&), OR (|), NOT(~), XOR(^), SHIFT (<<, >>), szóval, jó ha tudja az ember ezeket fejből (de minek is?).

Negyedik példa:

makró és inline függvény

/************************************************/

#include "stdafx.h"

#define ABS_macro(i) (i < 0 ? -i: i)

inline int ABS_func(int i)

{

   return i < 0 ? -i: i;

}

int _tmain(int argc, _TCHAR* argv[])

{

   int i = -100;

   printf("%d\n", ABS_func(++i));      // 99

   int j = -100;

   printf("%d\n", ABS_macro(++j));      // 98

   getchar();

   return 0;

}

A makro még fordítás előtt behelyettesítődik, így ott kétszer fog szerepelni a kódban a ++j, míg az inline funkció csak a fordítás során helyettesítődik be ezért ott a megfelelő helyen értékelődik ki az átadott változó.

Ötödik példa:

if helyett

/************************************************/

#include "stdafx.h"

int _tmain(int argc, _TCHAR* argv[])

{

   int a = 100;

   int b = (a > 10 ? (a < 50 ? 300: 400): 200);

   printf("%d\n", b);   // 400

   if(a > 10)

   {

      if(a < 50)

      {

         b = 300;

      }

      else

      {

         b = 400;

      }

   }

   else

   {

      b = 200;

   }

   printf("%d\n", b);   // 400

   getchar();

   return 0;

}

Mindkét kódrészlet ugyanazt csinálja, csak a második kicsit olvashatóbb. De ezt csak az első kódrevízió után értik meg a tisztelt C++ programozók. Meg magukkal csesznek ki, ha a fizetés a kódsorok számával arányos :).

Hatodik példa:

paraméterek

/************************************************/

#include "stdafx.h"

// first secound third fourth

int main(int argc, char* argv[])

{

   printf("argc = %d\n", argc); 

   for(int i = 0; i < argc; i++) 

   {

      printf("argv[%d] = %s\n", i ,argv[i]);

   }

   printf("%s\n", ++*argv);

   printf("%s\n", *++argv);

   printf("%s\n", ++argv[1]);

   getchar();

   return 0;

}

Kimenet:

C:\...\Debug>Console.exe first secound third fourth

argc = 5

argv[0] = Console.exe

argv[1] = first

argv[2] = secound

argv[3] = third

argv[4] = fourth

onsole.exe

first

ecound

Nem tudom minek erőltetik annyira, hiszen a Windows alatt ritkán van lehetőségünk commandline paramétert megadni. (próbáld meg elmagyarázni egy felhasználónak hogyan csinálja :))

 

Hetedik példa:

értelmetlenségek

/************************************************/

#include "stdafx.h"

int func(int a, int b)

{

   int aa = a * a;

   int bb = b + b;

   return (aa, bb);

}

int _tmain(int argc, _TCHAR* argv[])

{

   int a = (0, 1, 2, 3, 4);

   int b = (5, 6, 7, 8, 9);

   int c = func(a, b);

   printf("%d\n", c);   // 18

   getchar();

   return 0;

}

Csak azt nem értem ki és miért ír ilyen kódot? Kit akar ezzel megszívatni? Saját magát a kódrevíziónál, vagy esetleg az utána jövő kollégát? Hol van az a kereskedelmi program, amiben ilyen kód van benne?

Nyolcadik példa:

rekurzió/ciklus

/************************************************/

#include "stdafx.h"

int fract(int i)

{

   if(i == 0)

   {

      return 1;

   }

   else

   {

      return i * fract(i - 1);

   }

}

int fib(int i) 

{

   if(i <= 1)

   {

      return i;

   }

   else

   {

      return fib(i - 1) + fib(i - 2);

   }

}

int _tmain(int argc, _TCHAR* argv[])

{

   printf("%d\n", fract(4));   // 24

   printf("%d\n", fract(0));   // 1

   int a = 1;

   for(int i = 1; i < 5; i++)

   {

      a *= i;

   }

   printf("%d\n", a);   //   24

   printf("%d\n", fib(20));   // 6765

   getchar();

   return 0;

}

Ha valami számsoros példát kapsz, mint például a fibonacci, vagy frakcionális, vagy pi számítás, eszedbe ne jusson ciklussal számolni, mert az nem elég C-s. Az nem számít, hogy a rekurzió telenyomja a stack-et, éppen ezért lassabb, mint a ciklus. Persze a rekurzió nagyon is hasznos eljárás, nincs is annál jobb megoldás például fában való keresésnél, vagy rendezéseknél. De ezt is ésszel kell használni.

Kilencedik példa:

mit csinál/mi a hiba/hogyan lehetne egyszerűbben

/************************************************/

#include "stdafx.h"

int *func()

{

   int x = 100;

   ++x;

   return &x;

}

int _tmain(int argc, _TCHAR* argv[])

{

   int *ip = func();

   clrscr();

   printf("%d", *ip);   // mit ír ki?

   getchar();

   return 0;

}

Itt megint nem lehet tudni, mit szeretne hallani a teszt megírója. Ha szigorúan vesszük, akkor a program 100-at ír ki, hiszen hiába mutat a pointer a már érvénytelen memória területre, nem valószínű, hogy ennyi idő alatt bármi is felülírná a memóriát. Ha arra kíváncsi, hogy a kód lefordul-e egyáltalán, akkor lefordulhat, ha a clrscr() függvény definiálva van az include állományok egyikében.

Tizedik példa:

írj programot, ami...

/************************************************/

#include "stdafx.h"

// írj kódot ami megszámolja egy char-ban hány bit 1

int _tmain(int argc, _TCHAR* argv[])

{

   char c = 37;

   char tmp = 1;

   int num = 0;

   for(int i = 0; i < 8; i++)

   {

      if(c & tmp)

      {

         num++;

      }

      tmp <<= 1;

   }

   printf("%d\n", num);

   int cnt = 0;

   cnt += !(c & 1);

   cnt += !(c & 2);

   cnt += !(c & 4);

   cnt += !(c & 8);

   cnt += !(c & 16);

   cnt += !(c & 32);

   cnt += !(c & 64);

   cnt += !(c & 128);

   cnt = 8 - cnt;

   printf("%d\n", cnt);

   getchar();

   return 0;

}

Én mondjuk nem találkoztam evvel a feladattal (netről származik), de a ciklusos megoldást adtam volna rá. Bár a második megoldás, ami egy hozzászólótól származik hatékonyabb, hiszen a bitműveletek a processzor egy órajel alatt (vagy még gyorsabban :)) hajtja végre.

Tizenegyedik példa:

Mit ír ki...

/************************************************/

#include "stdafx.h"

#include <vector>

#include <algorithm>

int _tmain(int argc, _TCHAR* argv[])

{

   char *s = ")ZzB CéQND GkHéTeI!XdJAAvKNErReLsU:YnMcN OkPGFdSeV";

   std::vector<short> V;

   V.resize(25);

   memcpy(&V[0], s, 50);

   sort(V.begin(), V.end());

   for(int i = 0; i < V.size(); i++)

   {

      printf("%c", V[i] & 0x00FF);

   }

   getchar();

   return 0;

}

Kicsit macerás, míg az ember a második karakterek alapján sorba rendezi a stringet, majd összeolvassa az első karaktereket. Főleg mert időre kell megcsinálni a feladatokat.

Tizenkettedik példa:

Optimalizáld a következő...

/************************************************/

#include "stdafx.h"

#include <vector>

#include <algorithm>

 

class cls

{

public:

   cls()

   {

      m_iMin = 0x0FFFFFFF;

      m_iMax = 0;

   }

   void addNum(int iNum)

   {

      double dAver = 0;

      this->V.push_back(iNum);

      for(int i = 0; i < this->V.size(); i++)

      {

         if(this->m_iMax < this->V[i])

            this->m_iMax =  this->V[i];

         if(this->m_iMin > this->V[i])

            this->m_iMin =  this->V[i];

         dAver += this->V[i];

      }

      this->m_dAver = dAver / this->V.size();

   }

   int getMin()

   {

      return m_iMin;

   }

   int getMax()

   {

      return m_iMax;

   }

   double getAver()

   {

      return m_dAver;

   }

private:

   std::vector<int> V;

   int m_iMin;

   int m_iMax;

   double m_dAver;

};

 

int _tmain(int argc, _TCHAR* argv[])

{

   cls c;

   c.addNum(10);

   c.addNum(15);

   c.addNum(8);

   c.addNum(21);

   c.addNum(32);

   c.addNum(5);

   printf("%d %d, %f\n", c.getMax(), c.getMin(), c.getAver());

   getchar();

   return 0;

}

A feladat az volt, hogy optimalizáljuk a következő osztályt, bár ahogy így visszagondolok, ott nem volt benne a három int member tag, az iMax, és az iMin. Lehet, hogy pont ez szerették volna látni? Mindegy én az adott feladatra ilyen kódot írtam volna, és csak a memberekkel hasonlítottam volna össze az új elemeket, ha viszont nem akarunk member iMax-ot és iMin-t, akkor olyan hozzáadó függvényt írtam volna, ami megőrzi a vektor rendezettségét és csak az első, illetve az utolsó elemét adja vissza. Ugyanez a helyzet az átlaggal is. Ott is elég egyszer kiszámolni a member tagba az átlagot, és utána a már meglévő átlagból, a vektor méretéből, és az új számból ki lehet számolni az új átlagot.

Logikai feladatok

Mondjuk nem értem mi a köze a programozáshoz, pl. egy ügyviteli program megírásához, hogy hogyan kel át a folyón egy katona, egy kannibál és egy szamaritánus, vagy másfél tyúk hány tojást tojik, de jobb ha az ember kigyűjti a net-ről ezeket a „logikai” feladványokat, hogy ebben is legyen gyakorlata.

Legyen itt néhány példa ezekről is csak példa kedvéért:

Első példa:

Merre megy a busz? (Az ábrát mellékelni szokták). Ez egy régi ismert feladat, a busz ajtaját kell figyelni. Persze azt nem mondják meg, hogy itt, vagy teszem-azt Londonban.

Második példa:

Van 26 dobozunk az angol abc egy-egy betűjével ellátva. A dobozokba betűket lehet rakni, kivéve az adott dobozon szereplőt. A kérdés az, hogy minimum hány dobozváltás szükséges, hogy az összes betűt behelyezzük a dobozokba egy adott karaktersorozatra. 

- Egy dobozba akármennyi betűt belerakhatunk egymás után is és egy dobozt akárhányszor használhatunk újra. 

- Dobozváltás alatt azt értjük, hogy ha az adott ponton egy másik dobozba kezdjük rakosgatni a betűket. 

- A sorozat bármilyen hosszú lehet, egy karakter természetesen többször is előfordulhat.

Milyen algoritmussal lehetne az optimális (tehát minimális dobozváltásos) megoldást megtalálni?

Milyen adatszerkezettel lehetne ezt hatékonyan?

Harmadik példa:

Van két gyújtózsinórod, amelyek egyenetlenül égnek, de mindkét zsinór egy óra alatt ég le és van gyufád. Hogyan határozol meg 15 percet?

Negyedik példa:

Hűtlenség

Egy faluba a férfiak megelégelik asszonyaik hűtlenségét, és úgy határoznak, hogy véget vetnek ennek. A következő szabályokat beszélik meg:

Ha valaki rájön, hogy megcsalja a felesége, azonnal kiteszi.

Minden nap egyszer találkoznak és közlik, ha a feleségük megcsalja őket.

Nem szólnak egymásnak a feleségeik viselt dolgairól.

Minden férfi tudja a másik feleségéről, hogy megcsalja-e a férjét.

A 11. napon minden hűtlen feleséget kitettek.

Hány hűtlen feleség volt?

Ötödik feladat:

Négyen érkeznek a hídhoz, amin egyszerre csak ketten kelhetnek át egy zseblámpával. Mindenki különböző sebességgel érhet át pl. 1, 2, 5, 10 perc. Milyen sorrendben kell átkelniük, hogy a leggyorsabban érjenek át?

Hatodik feladat:

Lóverseny

Egy lóversenyre 27 lovat neveztek be. Minden lónak más a sebessége. Egy futamban egyszerre 3 ló versenyezhet.

Hány verseny kell a leggyorsabb ló kiválasztásához?

Hány verseny kell a második leggyorsabb ló kiválasztásához?

Hetedik feladat:

Van két strucctojásod, legkevesebb hány ejtésből állapíthatod meg, hogy hány emeletnyi zuhanást bír ki?

Nyolcadik feladat:

Egy nyolcfős társaság szeretne átkelni a folyón.

Férj, feleség, két kislány, két kisfiú, egy rendőr és egy tolvaj.

A csónakban egyszerre 2en lehetnek.

A feleség nem lehet együtt a fiúkkal, ha nincs ott a férj.

A férj nem lehet együtt a lányokkal, ha nincs ott a feleség.

A tolvaj nem lehet együtt a családtagokkal, ha nincs ott a rendőr.

Csak a férj, a feleség és a rendőr tudja kezelni a tutajt.

Hogyan jutnak át a folyón?

Kilencedik feladat:

Van három falu, hamis, fél igaz, igaz falva.

Hamis falván mindig hazudnak, fél igaz falván minden második mondásuk igaz, igaz falván mindig igazat mondanak.

Valaki felhívja a tűzoltót:

Ég a házam!

Honnan hív? Kérdezi a tűzoltó.

Fél igaz falváról.

Mit tesz a tűzoltó?

Tizedik feladat:

Van 100 lámpa kapcsolókkal.

Összes lámpa le van kapcsolva.

Minden lámpát felkapcsolunk.

Minden második lámpa kapcsolóját átváltjuk.

Minden harmadik lámpa kapcsolóját átváltjuk.

Minden negyedik lámpa kapcsolóját átváltjuk.

Minden századik lámpa kapcsolóját átváltjuk.

Mely lámpák fognak világítani?

Tizenegyedik feladat:

Egy hordót minden segédeszköz nélkül pontosan félig kell megtölteni. Hogyan csinálnád?

Tizenkettedik feladat:

Egy L alakú ábrát 4 egyforma részre kell osztani (Az ábrát mellékelni szokták). Az ábra négy kis L alakra osztható.

A végére egy kis csemege. Aki kipróbálás nélkül megmondja mit ír ki a következő kis ATL-es alkalmazás, az valóban egy igazi tapasztalt C++ programozó. Jó próbálkozást :)

#include "stdafx.h"

int _tmain(int argc, _TCHAR* argv[])

{

   CComBSTR a = "hello";

   CW2A c(a);

   printf("%s\n", c);

   printf("%s %s\n", c, c);

   getchar();

   return 0;

}

 

14 komment

Címkék: teszt interjú c

A bejegyzés trackback címe:

https://c-interju.blog.hu/api/trackback/id/tr113299250

Kommentek:

A hozzászólások a vonatkozó jogszabályok  értelmében felhasználói tartalomnak minősülnek, értük a szolgáltatás technikai  üzemeltetője semmilyen felelősséget nem vállal, azokat nem ellenőrzi. Kifogás esetén forduljon a blog szerkesztőjéhez. Részletek a  Felhasználási feltételekben és az adatvédelmi tájékoztatóban.

kalisz 2011.10.25. 08:50:08

Szia.

Esetleg a logikai feladatok megoldásai is megvannak?
Akár mehet privátba is.
Mail: kalisza [kukac] gmail [pont] com

Köszi

Regisz 2011.10.28. 10:55:32

oda | vissza
férj kisfiú | férj
férj kisfiú | férj
férj feleség | feleség
rendőr tolvaj | férj
férj feleség | feleség
feleség kislány | rendőr tolvaj
rendőr kislány | rendőr
rendőr tolvaj | KÉSZ

Regisz 2011.10.28. 10:57:13

@Regisz: bocs, az "Egy nyolcfős társaság szeretne átkelni a folyón." megoldása akart lenni az előző hozzászólásom :)

Regisz 2011.10.28. 12:36:33

@Regisz: opsz, mégsem jó :(, az elejére valamiért nem figyeltem
a helyes megoldás:
oda | vissza
1. rendőr tolvaj | rendőr
2. rendőr kisfiú | rendőr tolvaj
3. férj kisfiú | férj
4. férj feleség | feleség
5. rendőr tolvaj | férj
6. férj feleség | feleség
7. feleség kislány | rendőr tolvaj
8. rendőr kislány | rendőr
9. rendőr tolvaj | KÉSZ

gabor2 2012.09.23. 15:29:31

Par hiba:

A templates feladatban a tomb deklaracio a hiba.

"data = new X(m_iSize);" helyett
"data = new X[m_iSize]"

A "Második fejezet, Const és társai, értem ezalatt a static és referencia változókat." feladtnal azert nem fordul le, mert egy const tagfuggvenyben akarsz valtoztatni erteket. Egyebbkent lefordulna, mert a 'const int *m' lehet valtoztatni.

Csaba++ 2013.09.11. 22:38:54

Harmadik példa: a példa jól mutatja, hogy a konstruktor futásának pillanatában a virtuális tábla még nem "használható".
Mivel cls2-ben a virtuális func függvént felülírták, a kezdők gondolhatnák, hogy cls2 példány létrehozásánál a következő lesz az eredmény:
cls2 func
cls2 func

Példányosításkor először lefut a cls1 konstruktora, de ekkor a virtuális táblában még cls1::func szerepel.
Ha valaki számít arra, hogy az objektum létrehozásakor egyből felépül a végleges virtuális tábla, hibázhat.
Számomra ez azt jelenti, hogy óvakodjak virtuális függvények hívásától a konstruktor futása alatt. A kérdés feltevője erre lehetett kíváncsi szerintem.

Csaba++ 2013.09.11. 23:01:03

Negyedik példa: szerintem a paraméter átadásakor létrejön egy ideiglenes cls1 objektum, ami paraméterül a cls2 objektum cls1 részét kapja értékül (slicing). Nem csupán típuskonverzióról van tehát szó, hanem teljesen új objektum jön létre.

A folyamat sokkal érthetőbbé válik, ha a cls1 osztály másoló konstruktorába elhelyezünk egy kis output-ot. Az eredmény így a következő lenne:

cls1 func
cls2 func
cls1 copy constructor
cls1 func

Csaba++ 2013.09.11. 23:35:31

Kilencedik példa:
A kimenet:
class : 11
class : 11
class : 11
class : 22
class : 11
class : 22

class : 1
class : 1
class : 2

Sajtkukac 2013.10.09. 04:27:18

A 26 dobozos-ra (2. példa) a megoldás mi volna?
Programozási oldalról nézve egy buffer (plusz doboz) beiktatása, de valszeg nem ez az elméleti megoldás...

XEP 2013.11.24. 15:46:41

"Második fejezet, Const és társai, értem ezalatt a static és referencia változókat."
-----------------
"Ez a kód nem fordul le, miután a csl2 osztályban implementált függvényben megpróbálunk értéket adni egy const membernek."

Szerintem az a baj, hogy a const-tal jelölt tagfüggvényben próbálunk változtatni egy adattagot, legyen az const vagy nem, igazából mindegy is. Itt ugye a pointert írjuk át, amit lehetne, nem pedig a memóriacímet ahová mutat.

Szerintem helyesen:

A const-al jelölt függvények nem változtathatnak meg adattagokat, ugyanis azt jelzik, hogyha az osztályból konstans példányt hoznak létre akkor ez a metódus meghívható maradjon, viszont ha meghívható, akkor az objektum állapotán nem változtathat.

Sealka 2015.07.29. 05:11:34

Csupa pure virtual fgv-eket tartalmazó classt interface-nek hívják, ahol kidolgozott fgv-ek és/vagy memberek is vannak az már abstract.
Patternek 90% interfészről szól, meg polimorfizmusról, ezek nélkül ma már semmit se érdemes írni.

Const-ot kinyírni még annál is egyszerűbb, mint ahogy leírtad:
const int a = 100;
const_cast<int &>(a) = 101;

Ettől még persze hasznos a dolog a const, egyféle nyelvi megkötés.

A bitvadászat, meg a pointer pointerének a ponterének a referenciája és a többi meg sima kiszúrás, próbálják felmérni tudod e mi a pointer kb.

Egyébként csomó kérdés tényleg arra megy, hogy írtál e már a 0-ról rendszer az utolsó string osztályig, kezdve attól, hogy melyik konténer milyen adatszerkezetet használ operator, template stb. Manapság a senior szint itt kezdődik, más rendszerének a bütyköláse már inkább pályakezdő munka, senior-t ilyenre nem pazarolnak, túl drága hozzá. :)

Én egyébként szintén teszteket irkálok, nálam a C# is játszik párhuzamosan 10 éve a kettőt űzöm. Nekem inkább a fizetés igény után szoktak hátast dobni. Ja, hogy megfizetni a tudást azt nem szokás? Ez van. :)

Sealka 2015.07.29. 05:19:11

Ha már itt tartunk mennyiben mérnek manapság egy senior C++ fejlesztőt, avagy mennyit merjen mondani az interjún? (nettó persze)
250-re azonnal felvennének tüzijátékkal, pezsgőbontással
300 felett már erős homlok ráncolás több óra gondolkodás
460-nál már szivinfarktus gyanu és hosszas elemzés arról, hogy az iroda bérlése és a kv az nekik burrtó 300, szóval 1 milliót ők honnét szedjenek elő havonta!?
500 felett golyóálló mellény viselése kötelező a banki biztonsági őr előtt

Sealka 2015.07.29. 05:37:25

Kedvenc értelmetlen kérdéseim a vessző operátoros feladatok.
Mit ír ki és miért?
int x = (1, 2, 3, 4, 5, 6);
int y = (4, 5, 6, 7, 8, 9);
cout << x + y << endl;

Ezt még szokták fokozni, hátha sikerül összezavarni. Ezt kapd ki ne a náthát!
◾Mi a következő kód outputja és miért?
◾(senioroknak): a bar() visszatérési értékében mi a 10, ha a compilerben nincs beállítva semmilyen optimalizáció?

void foo()
{
std::cout << "- Get another sweet little 80-year-old lady to yell *BINGO*!" << std::endl;
}

void bar()
{
std::cout << "- How do you get a sweet 80-year-old lady to say the F word?" <<std::endl;
return 10, foo(), void();
}

int main()
{
//Ezt az álláshirdetést nem HR-es írta. :)
bar();
return 0;
}

Én zárójelben odaírtam a teszt végére, hogy amúgy értelmetlennek találtam az ilyen kérdéseket, remélem senki nem ír a cégnél ilyen kódot. :D

Sealka 2015.07.29. 05:43:16

Egy másik interjú, ahol hírtelen előkerült a matematika és az algel is valami oknál fogva az SQL és a C++ után :D
Írj egy 3*3-as Rubik csomó megoldó algoritmust.
Hány kombinációja létezik a játéknak?

Ja és két C++ kérdés volt:
1. mit jelent a final osztály
2. van e többszörös öröklés C++ ban, mit jelent ez, hátrány/előny?
süti beállítások módosítása