C++ Questions d'entretien

Questions d'entretien pour la programmation en langage C

Lorsqu'il s'agit d'interviewer des candidats à un poste de programmation C++, il est important de se rappeler qu'il s'agit d'un langage très populaire qui a une longue histoire et qui a fait ses preuves. La syntaxe du code simple et les algorithmes courants peuvent être facilement référencés en ligne, et ce serait une terrible erreur de ne pas répondre à un programmeur qui ne connaîtrait pas quelque chose de banal ou de trivial. Dans ce contexte, les questions spécifiques au code peuvent parfois être inefficaces.

Cela dit, il est toutefois essentiel que le candidat soit capable de reconnaître les situations où des erreurs sont probables ou les techniques spécifiques sont les mieux appliquées. Cela exige un certain degré de compétence et d'aisance dans les subtilités de la langue. Sans cela, le temps de développement du projet augmentera, tout comme les risques d'erreurs. Ces deux points de vue peuvent sembler contradictoires, mais un bon ensemble de questions d'entretien peut couvrir le sujet sous les deux angles.

Cet article comprend 14 questions d'entretien dont le type et la difficulté varient. Il est important de se rappeler que le score final peut parfois dépendre de la façon dont vous structurez les questions d'entretien. Par exemple, des bribes de code ou des définitions de termes d'un domaine peuvent fournir des indications pour une question qui sera posée plus tard. La première section contient 5 questions d'entretien pour débutants et leurs réponses. Celles-ci sont destinées à examiner les connaissances de base du candidat et peuvent être utilisées pour écarter immédiatement ceux qui sont totalement inexpérimentés dans la langue. Les autres questions sont plus difficiles et permettent d'approfondir différents aspects des connaissances et de l'expérience de la personne interrogée.

Outre le fait de s'assurer que le futur programmeur comprend les éléments essentiels, tels que les types de données de base, il est utile de poser des questions qui testent des constructions spécifiques de la programmation C++. Il n'est pas nécessaire que les questions de l'entretien de programmation comprennent un volet où le candidat doit écrire un programme, bien qu'il faille certainement évaluer sa connaissance du code. À cette fin, le fait de parcourir un programme mettra en évidence la capacité à repérer rapidement les erreurs. En fin de compte, cette force peut faire gagner un temps précieux lors de la phase de débogage.

Questions, réponses, à quoi s'attendre et pourquoi

Chacune des questions de l'entretien comporte plusieurs points connexes. En plus de l'explication des réponses, la raison d'être de chaque question est expliquée. Ceci est important car les questions posées lors d'un entretien doivent non seulement être pertinentes, mais aussi porter sur des sujets spécifiques. Si l'examinateur ne sait pas ce qu'il faut rechercher, il se peut que les connaissances du candidat en matière de programmation ne soient pas correctement évaluées.

Questions et réponses pour les entretiens de niveau débutant

1 - Envisagez le programme suivant :

1
2
3
4
5
6
7
8
9
10
11
/* No programming interview would be complete without performing a code tracing exercise.
The int is one of the basic data types that can be manipulated by using the unary ++ operator. */
int main()
{
int a, b, c;        // int data type for simple math
a = 9;
c = a + 1 + 1 * 0;
b = c++;
 
return 0;           // return main int as Exit Status 0 (success)
}

Quelles sont les valeurs finales des variables a, bet c?

Justification

Ce court test couvre à la fois la préséance de l'opérateur simple et les connaissances de base sur l'opérateur unaire "++". Les opérateurs unaires en C++ sont courants, et la confusion s'installe parfois en fonction du côté de la variable sur laquelle ils se trouvent (c'est-à-dire ++a par rapport à a++). Un malentendu à ce sujet peut entraîner des erreurs dans les mathématiques algorithmiques, mais surtout des résultats inattendus lors de l'itération de boucles ou de l'accès à des éléments d'un tableau.

Réponse correcte :

a = 9 (n'a pas été modifié)
b = 10
c = 11

Erreurs courantes

c = 0 ou c = 1

Cela se produira s'il y a une erreur dans l'évaluation de la préséance. Comme l'opérateur de multiplication (*) a une priorité plus élevée que l'addition (+), et que l'ordre des opérations est autrement de gauche à droite, cette équation se traduit comme suit :
c = ((a + 1) + (1 * 0))
؞c = ((9 + 1) + 0)
؞c = 10 [Correct à ce stade].

Si l'ordre de préséance correct n'est pas respecté, une possibilité existe :

c = a + 1 + 1 * 0
؞c = 9 + 1 + 1 * 0
؞c = 10 + 1 * 0
؞c = 11 * 0
؞c = 0 [Faux]

Pour comprendre comment quelqu'un peut calculer incorrectement que c = 1, examinez ce qui se passe après la valeur de b est déterminé. Pour commencer, b s'est vu attribuer la valeur : c++

Que le candidat sache que c = 10 à ce stade, ou qu'il croie à tort que c = 0, il est courant d'appliquer l'opération d'affectation (=) après avoir effectué l'opération d'addition unitaire (++). La procédure correcte pour b = c++ est la suivante :

  1. b se voit attribuer la valeur de cen laissant b = 10 [Correct].
  2. Ensuite, la valeur de c est incrémenté, ce qui donne c = 11 [Correct]

Si le candidat augmente par erreur la valeur de c en premier, alors :

  1. c est incrémenté, ce qui donne c = 11 [Correct]
  2. b se voit attribuer la valeur de c, ce qui fait également 11 [Faux]

D'autres erreurs, telles que le raisonnement erroné selon lequel l'opérateur "++" est analogue à "+1", sont également courantes et seront exclues par cette question.

Drapeaux rouges :

Si le candidat n'a jamais utilisé un opérateur unary auparavant, ou s'il ne peut, pour une raison quelconque, retracer les quelques lignes de code, il est peu probable qu'il ait une expérience du C++.

2 - Quelles sont les différences entre une classe et une structure ? Par exemple, les données sont-elles stockées différemment, et une structure soutient-elle le fonctionnement des membres ?

Justification

Les structures en C++ ne sont pas des types de base, bien qu'elles soient très couramment utilisées. En réalité, de nombreux programmeurs ne comprennent pas la différence entre ces deux types. Il est intéressant de noter que le choix d'utiliser l'un plutôt que l'autre aura rarement des conséquences. Cependant, comprendre qu'il y a une différence, et ce qu'elle est, peut être important pour le débogage.

Un autre langage de programmation, tel que Java, présentera plus de différences lorsque l'on comparera ces deux entités. Des différences existeront également pour d'autres types de classe de stockage ou de type de données. Les réponses données à cette question peuvent indiquer les langages avec lesquels le programmeur est le plus à l'aise. Cela sera précisé, par exemple, si les différences entre les structures et les classes Java sont énumérées.

Réponse correcte :

En C++, il y a très peu de différence entre les classes et les structures. Plus précisément, l'accessibilité par défaut des variables et méthodes membres est publique dans une structure, et privée dans une classe. Rien de plus. En pratique, de nombreux programmeurs utilisent le type struct comme classe de stockage, uniquement. C'est peut-être un retour au C, où les structures ne supportaient pas les fonctions (ou les méthodes). Un candidat qui décrit des structures comme incapables de supporter des fonctions membres peut être nouveau en C++, ou bien être amené à développer principalement en utilisant le paradigme de la classe.

Drapeaux rouges :

Ne pas connaître au moins un peu les structures et les classes indiquerait une très mauvaise compréhension du C++. Même le fait de fournir des réponses incorrectes en ce qui concerne les différences, mais qui comprennent en fait des détails sur les deux types, est positif.

3 - Définition des structures ; considérez les deux définitions suivantes pour la classe, "pomme" :

1
2
3
4
5
6
7
8
9
10
11
enum colour = {Red, Green, Yellow}; // Normally in header file
struct apple
{
    float weight;
    colour c;
};
struct
{
    float weight;
    colour c;
} apple;

  1. Quelle est la différence entre ces deux définitions ? Sont-elles toutes deux légales ?
  2. Qui fonctionnera correctement dans la fonction suivante ?

1
2
3
4
5
6
void createApple()
{
apple myApple;
myApple.weight = 75.5;
myApple.c = Red;
}

Justification

Les bons programmeurs C++ connaissent bien les structures, qu'ils les utilisent régulièrement ou non. Il existe des variations dans la façon dont elles sont définies, et une bonne maîtrise de ces subtilités contribuera à garantir l'efficacité du développement.

Réponse correcte :

  1. La première définition ne définit que le type. Elle n'instancie pas la structure. La seconde ne crée pas du tout de type. Elle instancie plutôt "pomme" pour l'utilisation dans le champ d'application actuel.
  2. L'exemple de code nécessite un type "pomme", ce qui signifie que la première définition est la bonne.

Drapeaux rouges :

Ne pas connaître la différence entre ces deux bribes de code n'est pas critique, car la question est triviale et sera prise en compte lors de la compilation. Cependant, cela tend à suggérer que le candidat n'a pas beaucoup d'expérience avec le langage.

4 - Définissez les termes suivants qui sont couramment utilisés dans la langue :

  1. Classe d'amis
  2. Opérateur surchargé
  3. Variable statique
  4. Variable protégée

Justification

Le C++ partage la terminologie avec d'autres langues, il est donc important que les questions d'entretien sondent les connaissances du candidat en ce qui concerne les domaines pour lesquels il peut y avoir des ambiguïtés ou des différences. Une situation similaire se produit lorsque les développeurs de bases de données migrent vers d'autres plateformes. Oracle et MySQL supportent tous deux le langage de requête structuré (SQL), mais il existe des différences qui peuvent entraîner des retards dans la transition. Cette question indiquera, au minimum, la familiarité du prospect avec la terminologie.

Réponse correcte :

  1. A classe d'amis est un qui est autorisé à accéder aux membres privés et protégés de toute classe qui l'a déclaré comme ami. La propriété n'est pas héritée (les sous-classes d'amis ne deviennent pas automatiquement amies), et n'est pas transitoire (les amis des amis ne sont pas des amis).

    La compréhension des classes d'amis est précieuse pour le développement et le débogage, et ce serait un drapeau rouge si le candidat n'en avait jamais entendu parler dans le contexte de la conception OO. Cela dit, la syntaxe et l'utilisation sont simples, et peuvent être facilement référencées tant que le programmeur sait que la technique existe.

  2. Les opérateurs surchargés sont ceux pour lesquels la fonctionnalité par défaut a été modifiée. Cette fonctionnalité permet de personnaliser les opérateurs en langage de programmation, comme le signe de la fiche (+).

    Un exemple trivial est l'utilisation d'un signe plus dans la concaténation des chaînes de caractères :
    int : 1 + 1 = 2
    chaîne de caractères : "Amazon" + "interview de programmation" = "interview de programmation Amazon".

    Il est certain que tout programmeur C++ connaît bien les opérateurs surchargés. Cette approche est couramment utilisée dans les classes de stockage personnalisées pour accéder aux données et les modifier. La syntaxe de la surcharge d'un opérateur spécifique pour une classe est simple, de sorte qu'une démonstration du codage d'un opérateur sans référence n'est pas importante. Cependant, il est essentiel de savoir que l'on peut utiliser "+" au lieu d'écrire une fonction "plus(...)" peu esthétique.

  3. Les variables statiques sont stockées dans une mémoire statique, parfois appelée tasplutôt que sur le stack. Il est important de savoir que ces variables ne sont initialisées qu'une seule fois et que la portée persiste pendant toute l'exécution.

    Une variable statique à l'intérieur d'une fonction gardera une trace de sa valeur après la fin de la fonction, et restera inchangée lors des appels ultérieurs à la même fonction. Elle est similaire à une variable globale, mais avec une accessibilité limitée à la fonction.

    Tous les programmeurs C++ devraient savoir ce que signifie le mot-clé "static", et comment déclarer cette classe de stockage. Les variables statiques ne sont pas utilisées dans toutes les applications, mais le fait de n'en avoir jamais entendu parler doit être considéré comme un signal d'alarme.   

  4. Lorsqu'une fonction variable ou membre est définie comme protégé à l'intérieur de sa classe, elle implique un type d'accès restreint qui est similaire à privé. La différence est qu'il est accessible par ses sous-classes, ou classes dérivées.

    Le mot-clé est assez courant, et les programmeurs C++ devraient en être bien conscients. Dans les hiérarchies de classes très simples sans classes dérivées, il est clair que la fonctionnalité n'est pas nécessaire. En même temps, il s'agit d'un élément important à prendre en compte lors de la conception d'une hiérarchie de classes.

5 - Dans une hiérarchie de classes orientée objet, qu'est-ce que l'héritage multiple ? Dans quelles circonstances le problème du diamant se pose-t-il ?

Justification

Cette question teste la profondeur des connaissances du candidat par rapport aux principes orientés objet qui sont couramment utilisés en C++. Bien que la question ne porte pas spécifiquement sur le langage, cet entretien de programmation couvre de multiples perspectives. Il est donc utile d'évaluer la familiarité avec le paradigme.  

Réponse correcte :

L'héritage multiple n'est pas pris en charge par toutes les langues de la POO. Il est cependant pris en charge par le C++. Il décrit une situation dans laquelle un objet ou une classe hérite de caractéristiques, telles que des fonctions membres ou des variables, de plus d'une classe parente. Comparez cela à l'héritage unique, où un objet ou une classe est limité à l'héritage d'un seul parent.

Bien que cette fonctionnalité soit puissante, elle peut conduire au problème des diamants.

Pensez à la classe supérieure : "A", qui comporte deux sous-classes "B1" et "B2". Chacune des classes enfantines hérite séparément de la classe mère, "A". C'est lorsqu'un troisième niveau, "C", est ajouté que le problème peut se poser. Il ne peut se produire que si la sous-classe "C" hérite à la fois de "B1" et de "B2".

En outre, elle n'aura lieu que s'il existe une méthode définie dans "A" qui est remplacée à la fois dans "B1" et "B2", mais qui n'est pas remplacée dans "C". Si cette méthode est invoquée par "C", il existe alors une ambiguïté quant à la méthode à utiliser.

Il n'est pas important de savoir comment ce problème est résolu en C++. Il faut plutôt reconnaître que le problème existe et doit être pris en compte lors de la conception d'une hiérarchie de classes.

Drapeaux rouges :

Si le candidat ne sait pas ce qu'est l'héritage multiple, cela suggère un manque d'expérience en matière de conception orientée objet. Bien qu'elle ne soit pas critique pour toutes les applications, la POO fait partie intégrante du C++.

6 - La récursion est une technique courante utilisée dans la programmation C++. Quels sont les avantages et les inconvénients de l'utilisation de la récursion ? Donnez un exemple pour lequel la récursion est bien adaptée.

Justification

La récursion est une technique souvent utilisée par les programmeurs qui permet de simplifier le codage. Qu'il s'agisse de programmer à partir d'une ardoise vierge, d'ajouter des fonctionnalités ou de maintenir une base de code existante, un développeur est pratiquement assuré de voir ou d'utiliser la récursion. Il y a des éléments spécifiques à prendre en compte lors du développement ou du débogage d'une fonction récursive, c'est pourquoi il est important de bien connaître cette technique.

Réponse correcte :

La définition la plus simple est qu'une fonction récursive est une fonction qui s'appelle elle-même.

Le principal avantage est qu'il permet d'obtenir un code plus simple et plus élégant. Il est donc plus facile à développer et, du moins théoriquement, il y a moins d'erreurs.

Le principal inconvénient est que la récursion est souvent plus lente en pratique que son homologue itérative. Cette performance n'est pas attribuable à la complexité, mais plutôt au fait qu'une fonction récursive utilise plus de mémoire. Chaque appel de fonction récursive réserve de l'espace supplémentaire sur la pile, ce qui la fait croître. Si la récursivité est trop "profonde", c'est-à-dire qu'elle va trop longtemps avant de revenir, elle peut entraîner une erreur de "débordement de pile".

Exemples de situations bien adaptées

Les structures arborescentes, telles qu'un arbre de recherche binaire, sont très bien adaptées à la récursion. De même, lorsqu'il existe un nombre variable (ou très important) de boucles imbriquées, la version récursive sera beaucoup plus élégante. Les algorithmes de tri tels que le tri par fusion, le tri rapide et le tri par bulles sont tous plus faciles à mettre en œuvre de manière récursive.

Drapeaux rouges :

La récursivité est une technique qui est abordée dès le début de la formation d'un programmeur. Le candidat doit certainement être capable de la décrire, et savoir aussi qu'elle consomme plus de mémoire (espace de pile) que la version itérative du même algorithme. De plus, savoir qu'elle est bien adaptée aux structures arborescentes est trivial.

Bonus

Le moyen d'éviter une consommation excessive de mémoire et l'erreur de débordement de pile est d'utiliser la récursion de queue (également appelée optimisation de la récursion de queue), tant qu'elle est prise en charge par le compilateur. Le fait de savoir que cela existe, sans décrire son fonctionnement ou la façon de le coder, doit quand même être considéré comme un plus. Il est évident qu'une connaissance approfondie de la récursion de la queue de l'avion résume l'objectif de la question initiale.

7 - Des pointeurs intelligents ont été introduits en C++11 pour aider à la gestion de la mémoire et prévenir les fuites de mémoire. Les questions suivantes concernent l'allocation de la mémoire pour les données utilisées dans le programme, quel que soit leur type.

  1. Décrivez brièvement la différence entre :

    unique_ptr
    shared_ptr
    weak_ptr

     

  2. Dans le code suivant, quel est le meilleur choix de pointeur intelligent (remplacer ? ?? par le bon choix), et pourquoi ? Qu'arrive-t-il à la mémoire réservée au "p" lorsque la fonction est abandonnée ?

void printModifiedArray(int *nums, int length)
{
	// Allocate memory for a new array used only in this function
	std::???_ptr p(new int[length]);

	// Copy the int data with the proper size of data type
	std::memcpy(p.get(), nums, sizeof(int)*length); 

	// Iterate through the elements in the array
	for (int a = 0; a < length; a++)
	{
		// Call the destructive shiftInt() function
		shiftInt(p[a]);
		// Output the modified numbers from the array
		std::cout << p[a] << " ";
	}

	// Conclude with a newline escape sequence
	std::cout << endl;
}

Justification

L'allocation de mémoire et les erreurs de pointage sont traditionnellement parmi les problèmes les plus difficiles à détecter et à réparer en C++. Un grand nombre de bibliothèques de gestion de la mémoire développées indépendamment ont aidé les développeurs pendant de nombreuses années. Avec la publication de la norme C++11, l'introduction des pointeurs intelligents a permis de résoudre ces problèmes difficiles et de longue date. Les développeurs à tous les niveaux doivent être compétents dans leur utilisation.

Réponse correcte :

  1. Un pointeur unique (unique_ptr) est un pointeur intelligent qui va automatiquement désallouer la mémoire réservée dès qu'elle est hors de portée. Un seul pointeur unique (unique_ptr) peut pointer vers une ressource, il est donc illégal (au moment de la compilation) de faire une copie d'un pointeur unique (unique_ptr).
  2. Un pointeur partagé (shared_ptr) est également un pointeur intelligent qui désocculte automatiquement la mémoire réservée une fois qu'elle est hors de portée. Cependant, une seule ressource peut être référencée simultanément par plusieurs pointeurs partagés. Un compteur de référence interne est utilisé pour garder une trace du nombre de shared_ptr pour une ressource donnée. S'il n'y a pas de références, la mémoire est libérée. Ceci est susceptible de poser le problème de la dépendance circulaire dans les cas où deux ou plusieurs pointeurs partagés se référencent mutuellement.
  3. Un pointeur faible (weak_ptr) fait en effet référence à la mémoire, mais ne peut pas l'utiliser avant d'être converti en pointeur partagé. Cela se fait en utilisant la fonction membre lock(). L'utilisation de pointeurs faibles est une façon courante de traiter le problème des dépendances circulaires. Il n'y a pas de comptage de références, mais en tant que tel, la ressource est d'abord vérifiée dès que lock() est appelé. Si la mémoire est encore disponible, elle est alors utilisable en tant que pointeur partagé. Cependant, si elle avait été désallouée auparavant, le lock() échouerait.

Dans l'extrait de code, le meilleur choix est l'unique_ptr. Certes, un shared_ptr fonctionnera, mais il est inefficace de devoir conserver des références lorsque la fonction ne fait pas appel à une ressource partagée. Les développeurs C++ sont connus pour utiliser exclusivement des pointeurs partagés, bien que cela soit mal vu en raison de la dégradation des performances qui en découle. Une suggestion d'utiliser le ptr_faible indiquerait que le candidat n'a aucune expérience des pointeurs intelligents.

Lorsque la fonction revient, si la mémoire réservée à p est détenu par un unique_ptr ou un shared_ptr alors il sera automatiquement désaffecté.

Drapeaux rouges :

Si le candidat n'a aucune connaissance des pointeurs intelligents ou de la façon de les utiliser, cela indique un manque d'expérience dans une partie importante du langage de programmation. Les pointeurs intelligents ont été introduits en C++11, et bien qu'un développeur C/C++ chevronné puisse s'en passer, il ne faut pas négliger l'avantage qui découle de leur utilisation. 

8 - La conversion de type explicite d'une variable, également appelée "casting", est une opération souvent utilisée en C++. Il existe plusieurs fonctions, chacune ayant ses propres propriétés.

  1. Dans quelles circonstances faut-il utiliser le dynamic_cast ?
  2. Pourquoi reinterpret_cast est-il considéré comme dangereux ?
  3. Dans le code suivant, quel type de casting choisiriez-vous, le cas échéant, pour rendre la variable mot de passe inscriptible ?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
int main()
{
// Password to access file data
const char password[16] = { “Dennis ritchie”};      // Data annot be changed
char *changePass;
 
// Cast to make it modifiable
??????
// Change the password to decrypt the sequential access file
changePass[7] = ‘R’;
void *address = static_cast<void*>(changePass);
 
// Open access file using the password and display the contents
return openAccessFile(password);
 
</void*>

  1. Est-il possible que le contenu de password et changePass soit maintenant : "Dennis Ritchie" ?
  2. La définition et l'attribution de la variable d'adresse sont-elles légales ? Quel emplacement mémoire contiendra-t-elle ?

  1. Lequel des moulages, le cas échéant, effectue la vérification du type d'exécution ?

Justification

La conversion explicite des types entre variables et objets a toujours fait partie du C++, et elle continuera à l'avenir. Il est essentiel de bien comprendre le typage et, ce qui est tout aussi important, d'utiliser les nouveaux styles de moulage plutôt que la syntaxe de moulage originale en C, entre parenthèses.

Réponse correcte :

  1. Un dynamic_cast est utilisé uniquement dans le contexte d'une hiérarchie de classes. Il peut être utilisé pour lancer le pointeur d'une classe dans l'une de ses sous-classes. Il est également capable de lancer des références de classe de la même manière.
  2. Il est considéré comme dangereux d'utiliser reinterpret_cast car le compilateur part du principe que le programmeur sait exactement à quoi s'attendre. Aucun contrôle de validité n'est effectué à la compilation ou à l'exécution. Cette méthode est similaire à celle du casting original de style C.
  3. Ce code est une question piège, car techniquement, le comportement est indéfini.

    Pour être clair, si la réponse est :
    changePass = const_cast(password) ;

    Ensuite, certains compilateurs le permettront, et l'exécution réussira. Ainsi, le candidat devrait être crédité d'une syntaxe correcte, ainsi que du choix du "const_cast". Cependant, si le candidat ne reconnaît pas que la modification d'une variable définie à l'origine comme "const" est un comportement indéfini, il doit être pénalisé.

  4. Un dynamic_cast effectue un contrôle de sécurité de type au moment de l'exécution pour valider l'opération. Si les pointeurs ou les références ne sont pas du même type (comme le permet ce cast), le cast échoue.

Drapeaux rouges :

Si le futur programmeur n'est pas familier avec le casting, alors la quantité d'expérience réelle en programmation en langage C++ est remise en question. Le cas est moins grave lorsque la personne interrogée ne connaît que le casting de style C. Dans ce cas, il ne lui faudra pas longtemps pour se familiariser avec l'utilisation et la syntaxe des fonctions de casting modernes.

9 - En plus de la fonctionnalité de tableau intégrée dans le langage, la STL prévoit un certain nombre de classes de conteneurs. Deux d'entre elles sont List (std::list) et Vector (std::vector). Quelles sont les différences entre ces deux types de conteneurs, et dans quelles circonstances choisiriez-vous l'un plutôt que l'autre ?

Justification

Lors d'un entretien de programmation, il est utile d'interroger le candidat sur l'utilisation des fonctions de la bibliothèque, ainsi que sur les constructions de niveau supérieur telles que les classes de conteneurs pré-construites. Lorsqu'il s'agit d'une structure de données dynamique, il est important de reconnaître que différents choix sont mieux adaptés à certaines applications. L'expérience acquise avec ces types de données aidera à choisir la meilleure approche et permettra de gagner du temps lors de la phase de développement.

Réponse correcte :

Les classes de vecteurs et de listes sont toutes deux des conteneurs stockés de manière séquentielle, mais leur implémentation interne est sensiblement différente. Le vecteur est stocké dans des emplacements mémoire contigus comme un tableau. Une liste, en revanche, est stockée en interne sous la forme d'une liste doublement liée. Ces deux structures ont leurs propres mises en garde.

Vecteur

  1. L'insertion et la suppression sont coûteuses car :
  2. Si l'un ou l'autre se trouve au milieu de la liste, tous les éléments doivent être déplacés, soit pour faire de la place, soit pour libérer l'espace non utilisé.
  3. Si un élément est ajouté à la fin, il peut nécessiter une nouvelle allocation de la mémoire et la copie de tous les éléments.
  4. Les itérateurs C++ peuvent être invalidés.
  5. L'accès aléatoire est à la fois possible et efficace, grâce à l'opérateur de tableau (ex : tasks [12]).
  6. D'autres algorithmes STL qui nécessitent des itérateurs à accès aléatoire peuvent être utilisés sans risque avec la classe de vecteur. Un exemple est std::sort(...), qui sur un vecteur peut être appelé : std::sort(monVecteur.début(), monVecteur.fin()) ;

Liste

  1. L'insertion ou la suppression à un point quelconque (début, milieu, fin) ne nécessite que la modification de quelques pointeurs. Lors de l'insertion, il faudra bien sûr allouer une nouvelle mémoire à l'élément entrant.
  2. L'accès aléatoire n'est pas possible dans une liste. Pour accéder aux tâches de l'exemple précédent, les tâches [12], il faut commencer au début de la liste et parcourir les 11 premiers éléments un par un.
  3. Les itérateurs ne sont pas invalidés par l'insertion ou la suppression de listes car aucun élément n'est déplacé de sa position.
  4. Les itérateurs d'accès aléatoire sont par défaut non valables pour les listes. Par conséquent, des fonctions personnalisées utilisant des itérateurs d'accès non aléatoires doivent être mises en œuvre. En raison de la fréquence de tri des listes, cette fonctionnalité fait partie de la STL :
    std::sort(..) ne fonctionne pas pour les listes ; cependant, std::list::sort() fait partie de la STL

Drapeaux rouges :

Si le candidat n'a jamais entendu parler de std::list ou std::vector, il peut ignorer l'existence de la bibliothèque de modèles standard. Bien que de nombreuses candidatures soient rédigées sans elle, il s'agit certainement d'une bibliothèque utile et éprouvée qui aide dans une variété d'applications. Le débogage ou la maintenance d'une base de code existante exigera presque certainement une connaissance plus large du langage et des bibliothèques de normes.

10 - Avant le C++11, le mot-clé "auto" était un spécificateur de classe de stockage utilisé pour déclarer qu'une variable devait avoir une durée automatique. À quoi sert-il dans le langage de programmation moderne C++ ? Fournir un exemple simple pour un type de données de base.

Justification

L'utilisation de "auto" pour déclarer un type de variable simplifie le codage, accélère le temps de développement et entraîne moins de bogues. Son utilisation devrait être naturelle pour les programmeurs C++ modernes.

Réponse correcte :

En termes simples, le mot-clé "auto" est utilisé pour que le compilateur détermine le type de variable.

Ce spécificateur de classe de stockage a été si rarement utilisé que le mot-clé a été doté de cette nouvelle fonctionnalité à partir du C++11.

Exemples

auto simple = 5,5 ; // Simple sera un flottant
auto count = 2 ; // Count sera un entier

Bonus

Sachant qu'à partir de C++14, le mot-clé auto peut être utilisé pour déclarer un type de déclaration.

1
2
3
4
auto testFunction()    
{
return 5;           // The return value will be an integer
}

Raconter (et peut-être très mal)

Ne pas savoir que le mot-clé "auto" a été remplacé est généralement une mauvaise chose. Le candidat peut avoir une connaissance approfondie du C++, mais ne pas être totalement à jour en ce qui concerne le C++11/14. Cela doit être considéré comme un inconvénient, car le langage a été considérablement étendu depuis C++98 et C++03.

11 - Une fonction lambda est un programme miniature qui est défini dans une fonction standard. Comme écrit, la fonction lambda à l'intérieur de ce programme acceptera deux paramètres int et produira un autre int comme réponse.

1
2
3
4
5
6
7
8
9
10
11
int getSum(int a, int b)
{
int bias = 3;
int sum;
auto lf = [](int x, int y) { return x + y; };
 
// Calculate the sum using the lambda function
sum = lf(a, b);
 
return(sum);
}

  1. Quelles modifications peuvent être apportées à la fonction lambda pour qu'elle ait accès à la variable locale, le biais, et qu'elle puisse retourner "x + y + biais" au lieu de "x + y" ?
  2. Entre [crochets], quelle est la différence entre le signe égal [=] et l'esperluette [&] ?

Réponse correcte :

  1. auto lf = [=](int x, int y) { return x + y; };
  2. auto lf = [&](int x, int y) { return x + y; };
  3. auto lf = [bias](int x, int y) { return x + y; };
  4. auto lf = [&bias](int x, int y) { return x + y; };
  5. La spécification du signe égal permet de saisir toutes les variables par valeur, tandis que l'esperluette permet de saisir les valeurs par référence.

Drapeaux rouges :

Les fonctions lambda sont de plus en plus courantes, et les développeurs C++ doivent être conscients de la façon dont elles sont utilisées. Le fait de ne pas avoir d'expérience avec les fonctionnalités suggère une connaissance limitée du langage.

 

12 - Dans le cadre des paramètres des appels de fonction, comparer l'appel par référence à l'appel par valeur. Laquelle représente un pointeur vers les données ?

  1. Quelle est la différence entre un appel par référence et un appel par valeur ? Laquelle présente des effets secondaires potentiels ?
  2. Quelle est la syntaxe utilisée pour les distinguer ?

Justification

Les fonctions sont l'épine dorsale du C++, et elles sont instruites par leurs paramètres. Connaître la différence entre le passage de paramètres par valeur ou par référence est une partie essentielle du langage. Tant pour le développement que pour le débogage, c'est absolument essentiel pour comprendre.

Réponse correcte :

  1. L'appel par valeur fonctionne en faisant passer une copie de la valeur dans la fonction. Les modifications apportées à l'intérieur de la fonction n'affectent pas la valeur qui a été utilisée pour spécifier le paramètre. C'est la méthode par défaut utilisée par le langage dans les paramètres pour les appels de fonction.

    void myValueFunction(int value_1, float value_2) ;

  2. Appelez les ouvrages de référence en passant la référence dans la fonction. La référence se comporte de la même manière qu'un pointeur déréférencé et, de ce fait, les modifications apportées à l'intérieur de la fonction se reflètent dans la fonction d'appel (ou scope). Cela laisse la possibilité d'effets secondaires, il faut donc être prudent lorsque l'on passe des paramètres par référence.

    void myReferenceFunction(int &reference_1, float &reference_2) ;

Drapeaux rouges :

Un candidat qui ne connaît pas la différence entre ces deux concepts n'a que très peu, voire pas du tout, d'expérience en développement C++.

13 - Qu'est-ce qu'un modèle de classe ? Commenter la façon dont l'utilisation d'un modèle favorise la réutilisation du code.

Justification

Les modèles de classe sont une fonctionnalité importante utilisée pour la généralisation, l'abstraction et la réutilisation du code en C++. Une bonne compréhension des modèles de classe est bénéfique à tous les stades du développement, y compris l'utilisation de bibliothèques tierces.

Réponse correcte :

Un modèle de classe permet aux classes d'être abstraites en ce sens qu'elles ne savent pas quel type de données sera transmis et traité par ses opérations. Un modèle ne dépend pas des caractéristiques d'un type de données particulier ; l'accent est plutôt mis sur la logique.

La classe de modèle std:vector est un exemple fourni par la STL. Elle est générique car un vecteur de n'importe quel type de données peut être instancié. Cette capacité permet une réutilisation efficace du code et, par conséquent, l'ensemble du système est plus facile à mettre en œuvre et à maintenir.

Drapeaux rouges :

Si le candidat n'a aucune idée de ce qu'est un modèle de classe, cela indique un manque d'expérience en C++. Il existe de nombreuses applications qui n'utilisent pas de modèles. Cependant, leur utilisation n'est pas rare. Dans certains cas, les candidats auront utilisé des modèles tels que std::vector et n'auront pas su qu'ils en utilisaient un.

14 - C++ contient la fonctionnalité permettant de retourner une valeur R à partir d'une fonction appelée. Par exemple, il peut contenir un pointeur vers une chaîne de caractères qui a été lue dans un fichier. Dans ce contexte, quels sont les avantages de renvoyer une valeur R ?

Justification

Des références à la "bonne valeur" ont été ajoutées au langage en C++11, et elles constituent un outil utile pour améliorer les performances. Malheureusement, il y a souvent un malentendu sur leur utilité et leur utilisation. Par conséquent, ils sont sous-utilisés dans certains projets.

Réponse correcte :

Une valeur r résout deux problèmes : la sémantique du mouvement et l'avancement parfait. Le problème abordé dans cette question est la sémantique du déplacement, où les données doivent être "déplacées" entre les fonctions, plutôt que copiées.

En sémantique des mouvements, la fonction appelée a une mémoire réservée à une ressource qui sera renvoyée à la fonction appelante. Le retour d'une valeur r permet à la fonction appelante de "permuter" les données entre les fonctions, en guise de référence. Dans l'alternative, la chaîne mentionnée ci-dessus sera copiée du champ de la fonction appelée vers le champ de la fonction appelante. Cela nécessite l'allocation de mémoire par la fonction appelante, une procédure de copie et la désallocation ultérieure de la mémoire dans la fonction appelée. Il est clair que ce processus est moins efficace. Par conséquent, l'utilisation d'une valeur r est plus rapide et plus efficace.

Drapeaux rouges :

Les candidats qui n'ont pas entendu parler des rvalues créeront un code moins efficace dans certains cas. Cela peut indiquer qu'ils ne sont pas entièrement à jour avec les fonctionnalités supplémentaires fournies par le C++11.

Conclusion

Les questions fournies ici sont destinées à évaluer les compétences d'un futur programmeur C++. Il est important de se rappeler que les réponses ne doivent pas nécessairement être parfaites. Au contraire, lorsqu'un candidat démontre une compréhension du sujet, mais manque peut-être de connaissances sur des détails spécifiques, cela doit être considéré comme un crédit.

Dans le même temps, les candidats doivent être qualifiés et leurs compétences doivent être évaluées. Les compétences dont ils ont besoin dépendent du poste à pourvoir et, à ce titre, les variations fournies par ces questions d'entretien contribueront à cette évaluation. N'oubliez pas que l'une des meilleures façons d'évaluer les compétences du candidat pour votre projet est de procéder à une évaluation pratique de la programmation.