telecharger - programmation c++ exercices corrigés pdf




L'analyse la plus vexante: pourquoi A a(()); travail? (4)

Les déclarateurs de fonction C

Tout d'abord, il y a C. En C, A a() est une déclaration de fonction. Par exemple, putchar a la déclaration suivante. Normalement, ces déclarations sont stockées dans des fichiers d'en-tête, mais rien ne vous empêche de les écrire manuellement, si vous savez à quoi ressemble la déclaration de fonction. Les noms d'arguments sont facultatifs dans les déclarations, donc je l'ai omis dans cet exemple.

int putchar(int);

Cela vous permet d'écrire le code comme ceci.

int puts(const char *);
int main() {
    puts("Hello, world!");
}

C vous permet également de définir des fonctions qui prennent des fonctions comme des arguments, avec une syntaxe lisible qui ressemble à un appel de fonction (bien, c'est lisible, tant que vous ne retournerez pas un pointeur pour fonctionner).

#include <stdio.h>

int eighty_four() {
    return 84;
}

int output_result(int callback()) {
    printf("Returned: %d\n", callback());
    return 0;
}

int main() {
    return output_result(eighty_four);
}

Comme je l'ai mentionné, C permet d'omettre les noms d'arguments dans les fichiers d'en-tête, donc le résultat de output_result ressemblerait à ceci dans le fichier d'en-tête.

int output_result(int());

Un argument dans le constructeur

Ne reconnaissez-vous pas celui-là? Eh bien, laissez-moi vous rappeler.

A a(B());

Oui, c'est exactement la même déclaration de fonction. A est int , a est output_result , et B est int .

Vous pouvez facilement remarquer un conflit de C avec de nouvelles fonctionnalités de C ++. Pour être exact, les constructeurs sont un nom de classe et une parenthèse, et une syntaxe de déclaration alternative avec () au lieu de = . De par sa conception, C ++ essaie d'être compatible avec le code C, et donc il doit faire face à ce cas - même si pratiquement personne ne s'en soucie. Par conséquent, les anciennes fonctionnalités C ont priorité sur les nouvelles fonctionnalités C ++. La grammaire des déclarations essaie de faire correspondre le nom comme fonction, avant de revenir à la nouvelle syntaxe avec () si elle échoue.

Si l'une de ces fonctionnalités n'existait pas, ou avait une syntaxe différente (comme {} dans C ++ 11), ce problème ne se serait jamais produit pour la syntaxe avec un seul argument.

Maintenant, vous pouvez demander pourquoi A a((B())) fonctionne. Eh bien, déclarons output_result avec des parenthèses inutiles.

int output_result((int()));

Ça ne marchera pas. La grammaire exige que la variable ne soit pas entre parenthèses.

<stdin>:1:19: error: expected declaration specifiers or ‘...’ before ‘(’ token

Cependant, C ++ attend une expression standard ici. En C ++, vous pouvez écrire le code suivant.

int value = int();

Et le code suivant.

int value = ((((int()))));

C ++ s'attend à ce que l'expression à l'intérieur des parenthèses entre soit ... bien ... l'expression, contrairement au type C attend. Les parenthèses ne veulent rien dire ici. Cependant, en insérant des parenthèses inutiles, la déclaration de la fonction C ne correspond pas, et la nouvelle syntaxe peut être correctement mise en correspondance (qui attend simplement une expression, telle que 2 + 2 ).

Plus d'arguments dans le constructeur

Sûrement un argument est gentil, mais qu'en est-il de deux? Ce n'est pas que les constructeurs puissent avoir un seul argument. Une des classes intégrées qui prend deux arguments est std::string

std::string hundred_dots(100, '.');

Tout cela est bien et bien (techniquement, il serait plus vexant d'analyser si cela serait écrit comme std::string wat(int(), char()) , mais soyons honnêtes - qui écrirait cela? le code a un problème épineux: vous supposez que vous devez tout mettre entre parenthèses.

std::string hundred_dots((100, '.'));

Pas tout à fait.

<stdin>:2:36: error: invalid conversion from ‘char’ to ‘const char*’ [-fpermissive]
In file included from /usr/include/c++/4.8/string:53:0,
                 from <stdin>:1:
/usr/include/c++/4.8/bits/basic_string.tcc:212:5: error:   initializing argument 1 of ‘std::basic_string<_CharT, _Traits, _Alloc>::basic_string(const _CharT*, const _Alloc&) [with _CharT = char; _Traits = std::char_traits<char>; _Alloc = std::allocator<char>]’ [-fpermissive]
     basic_string<_CharT, _Traits, _Alloc>::
     ^

Je ne sais pas pourquoi g ++ essaie de convertir char en const char * . De toute façon, le constructeur a été appelé avec juste une valeur de type char . Il n'y a pas de surcharge qui a un argument de type char , donc le compilateur est confus. Vous pouvez demander - pourquoi l'argument est de type char?

(100, '.')

Oui, voici un opérateur virgule. L'opérateur virgule prend deux arguments et donne l'argument de droite. Ce n'est pas vraiment utile, mais c'est quelque chose d'être connu pour mon explication.

Au lieu de cela, pour résoudre l'analyse la plus vexante, le code suivant est nécessaire.

std::string hundred_dots((100), ('.'));

Les arguments sont entre parenthèses, pas l'expression entière. En fait, une seule des expressions doit être entre parenthèses, car il suffit de rompre légèrement avec la grammaire C pour utiliser la fonction C ++. Les choses nous amène au point de zéro arguments.

Zéro argument dans le constructeur

Vous avez peut-être remarqué la fonction eighty_four dans mon explication.

int eighty_four();

Oui, ceci est également affecté par l'analyse la plus vexante. C'est une définition valide, et celle que vous avez probablement vu si vous avez créé des fichiers d'en-tête (et vous devriez le faire). L'ajout de parenthèses ne le résout pas.

int eighty_four(());

Pourquoi est-ce si? Eh bien, () n'est pas une expression. En C ++, vous devez mettre une expression entre parenthèses. Vous ne pouvez pas écrire auto value = () en C ++, car () ne veut rien dire (et même si, comme le tuple vide (voir Python), ce serait un argument, pas zéro). Pratiquement cela signifie que vous ne pouvez pas utiliser la syntaxe abrégée sans utiliser la syntaxe {} C ++ 11, car il n'y a pas d'expressions à mettre entre parenthèses, et la grammaire C pour les déclarations de fonction s'appliquera toujours.

Parmi les nombreuses choses que Stack Overflow m'a enseignées, il y a ce que l'on appelle «l'analyse la plus vexante», qui est classiquement démontrée avec une ligne telle que

A a(B()); //declares a function

Bien que ceci, pour la plupart, semble intuitivement être la déclaration d'un objet de type A , prenant un objet B temporaire comme paramètre constructeur, il s'agit en fait d'une déclaration d'une fonction renvoyant un A , prenant un pointeur vers une fonction qui retourne B et lui-même ne prend aucun paramètre. De même la ligne

A a(); //declares a function

relève également de la même catégorie, puisqu'à la place d'un objet, il déclare une fonction. Maintenant, dans le premier cas, la solution de contournement habituelle pour ce problème consiste à ajouter un ensemble supplémentaire de parenthèses / parenthèses autour du B() , car le compilateur l'interprétera alors comme la déclaration d'un objet

A a((B())); //declares an object

Cependant, dans le second cas, faire la même chose conduit à une erreur de compilation

A a(()); //compile error

Ma question est, pourquoi? Oui, je suis très conscient que la bonne solution est de le changer en A a; , mais je suis curieux de savoir ce que fait le extra () pour le compilateur dans le premier exemple qui ne fonctionne pas lors de la réapplication dans le second exemple. Est-ce que A a((B())); contourner une exception spécifique écrite dans la norme?


Il n'y a pas de réponse éclairée, c'est juste parce qu'elle n'est pas définie comme syntaxe valide par le langage C ++ ... Il en est ainsi, par définition du langage.

Si vous avez une expression à l'intérieur, elle est valide. Par exemple:

 ((0));//compiles

Pour en savoir plus sur la définition des langues et le fonctionnement des compilateurs, vous devez vous familiariser avec la théorie des langages formels ou plus spécifiquement avec les grammaires contextuelles (CFG) et les matériaux associés tels que les machines à états finis. Si cela vous intéresse, même si les pages wikipedia ne vous suffiront pas, vous devrez vous procurer un livre.


Les parens les plus profonds de votre exemple seraient une expression, et en C ++ la grammaire définit une expression comme une expression d' assignment-expression ou une autre expression suivie d'une virgule et d'une autre assignment-expression (Annexe A.4 - Grammaire récapitulative / Expressions).

La grammaire définit en outre une assignment-expression comme l'un de plusieurs autres types d'expression, dont aucun ne peut être rien (ou seulement un espace).

Donc, la raison pour laquelle vous ne pouvez pas avoir A a(()) est simplement parce que la grammaire ne le permet pas. Cependant, je ne peux pas expliquer pourquoi les gens qui ont créé C ++ n'ont pas permis que cette utilisation particulière des parens vides soit une sorte de cas spécial - je suppose qu'ils préféreraient ne pas mettre dans un cas si spécial s'il y avait une alternative raisonnable.


Vous pourriez plutôt

A a(());

utilisation

A a=A();




c++