103 votes

Utilisation de {} dans une déclaration de cas. Pourquoi ?

Quel est l'intérêt d'utiliser { y } dans un case déclaration ? Normalement, quel que soit le nombre de lignes d'une instruction case toutes les lignes sont exécutées. S'agit-il simplement d'une règle concernant les compilateurs anciens/nouveaux ou y a-t-il quelque chose derrière cela ?

int a = 0;
switch (a) {
  case 0:{
    std::cout << "line1\n";
    std::cout << "line2\n";
    break;
  }
}

et

int a = 0;
switch (a) {
  case 0:
    std::cout << "line1\n";
    std::cout << "line2\n";
    break;
}

58 votes

Une utilisation peut consister à limiter la portée des variables déclarées dans l'instruction case.

3 votes

1 votes

Trop d'indentations, aussi. Les cas ne sont que des étiquettes à l'intérieur du bloc de l'instruction switch : ils n'introduisent pas d'imbrication supplémentaire, ils devraient donc s'aligner sur le bloc de l'instruction switch. switch et, dans le deuxième exemple, les déclarations incluses ne sont indentées qu'une seule fois. Notez que vous avez un désindentation maladroite de quatre espaces après le mot-clé break; .

200voto

Rotem Points 9668

El {} désigne un nouveau bloc de portée .

Prenons l'exemple suivant, très artificiel :

switch (a)
{
    case 42:
        int x = GetSomeValue();
        return a * x;
    case 1337:
        int x = GetSomeOtherValue(); //ERROR
        return a * x;
}

Vous obtiendrez une erreur de compilation car x est déjà défini dans la portée.

En séparant ces éléments dans leur propre sous-scope, il ne sera plus nécessaire de déclarer les éléments suivants x en dehors de la déclaration de l'interrupteur.

switch (a)
{
    case 42: {
        int x = GetSomeValue();
        return a * x; 
    }
    case 1337: {
        int x = GetSomeOtherValue(); //OK
        return a * x; 
    }
}

Attention :

Déclarer et initialiser une variable dans case sans {} entouré a tort :

#include <iostream>
using namespace std;
int main() {
    int b = 3;
    switch (b) {
    case 3:
        int a = 3; //compilation error: "initialization of 'a' skipped by 'case' label"
        return a * b;
    case 1:
        return a * b;
    }
}

12 votes

En fait, l'OMI vous enverra une erreur de compilation même si vous ignorez la deuxième déclaration de la variable x.

0 votes

@AbhishekBansal : Vous ne devriez pas avoir d'erreur, mais si vous avez un problème, vous pouvez le résoudre. case 1337: toujours return a * x; alors vous devriez recevoir un avertissement indiquant que x peut ne pas être initialisé.

1 votes

Bien que l'utilisation excessive de ce style et la mise en place de gros blocs à l'intérieur de l'instruction switch rende illisible le suivi des cas. Je préfère que les instructions restent minuscules.

23voto

Shafik Yaghmour Points 42198

TL;DR

La seule manière de déclarer une variable avec un initialisateur ou un objet non trivial à l'intérieur d'un fichier cas est d'introduire un portée du bloc en utilisant {} ou une autre structure de contrôle qui a sa propre portée, comme une boucle o si la déclaration .

Détails sanglants

On peut voir que cas sont juste déclarations étiquetées comme le étiquettes utilisé avec un goto déclaration( ce sujet est traité dans le Projet de norme C++ section 6.1 Déclaration étiquetée ) et on peut voir dans la section 6.7 paragraphe 3 que le fait de sauter une déclaration n'est pas autorisé dans de nombreux cas, y compris ceux avec une initialisation :

Il est possible de transférer dans un bloc, mais pas d'une manière qui contourne les déclarations avec initialisation. Un programme qui saute 87 d'un point où une variable avec une durée de stockage automatique n'est pas dans la portée à un point où elle est dans la portée est mal formée à moins que la variable ait un type scalaire, un type classe avec un constructeur par défaut trivial et un destructeur trivial, une version qualifiée cv d'un de ces types, ou un tableau d'un des types précédents et soit déclarée sans initialisateur (8.5).

et fournit cet exemple :

void f() {
 // ...
 goto lx; // ill-formed: jump into scope of a

 ly:
  X a = 1;
 // ...
 lx:
  goto ly; // OK, jump implies destructor
          // call for a followed by construction
          // again immediately following label ly
}

Notez qu'il y a quelques subtilités ici, vous êtes autorisé à sauter au-delà d'un déclaration scalaire qui n'a pas d'initialisation, par exemple :

switch( n ) 
{
    int x ;
    //int x  = 10 ; 
    case 0:
      x = 0 ;
      break;
    case 1:
      x = 1 ;
      break;
    default:
      x = 100 ;
      break ;
}

est parfaitement valable( exemple concret ). Bien sûr, si vous voulez déclarer la même variable dans chaque cas alors ils auront chacun besoin de leur propre champ d'application, mais cela fonctionne de la même manière en dehors de l'application commutateur également, ce qui ne devrait pas être une grande surprise.

Quant à la raison pour laquelle on ne permet pas le saut après l'initialisation, rapport de défaut 467 bien que couvrant une question légèrement différente, fournit un cas raisonnable pour variables automatiques :

[...] les variables automatiques, si elles ne sont pas explicitement initialisées, peuvent avoir des valeurs indéterminées ("garbage"), y compris les représentations de pièges, [...]

Il est probablement plus intéressant de se pencher sur le cas où vous étendez une portée à l'intérieur d'un fichier commutateur sur plusieurs cas l'exemple le plus célèbre est probablement Dispositif de Duff qui ressemblerait à quelque chose comme ça :

void send( int *to, const int *from, int  count)
{
        int n = (count + 7) / 8;
        switch(count % 8) 
        {
            case 0: do {    *to = *from++;   // <- Scope start
            case 7:         *to = *from++;
            case 6:         *to = *from++;
            case 5:         *to = *from++;
            case 4:         *to = *from++;
            case 3:         *to = *from++;
            case 2:         *to = *from++;
            case 1:         *to = *from++;
                        } while(--n > 0);    // <- Scope end
        }
}

6voto

Yakk Points 31636

C'est une habitude qui vous permet d'injecter des déclarations de variables avec le destructeur qui en résulte (ou des conflits de portée) dans case clauses. Une autre façon de voir les choses est qu'ils écrivent pour le langage qu'ils aimeraient avoir, où tout contrôle de flux consiste en des blocs et non en des séquences d'instructions.

4voto

p.j Points 307

Vérifiez cette restriction de base du compilateur et vous commencerez à vous demander ce qui se passe :

int c;
c=1;

switch(c)
{
    case 1:
    //{
        int *i = new int;
        *i =1;
        cout<<*i;
        break;
    //}

    default : cout<<"def";
}

Cela vous donnera une erreur :

error: jump to case label [-fpermissive]
error:   crosses initialization of ‘int* i’

Alors que celui-ci ne le fera pas :

int c;
c=1;

switch(c)
{
    case 1:
    {
        int *i = new int;
        *i =1;
        cout<<*i;
        break;
    }

    default : cout<<"def";
}

1voto

dyesdyes Points 362

L'utilisation de parenthèses dans le commutateur indique un nouveau bloc de portée comme le dit Rotem.

Mais il peut l'être aussi pour plus de clarté lors de la lecture. Pour savoir où s'arrête l'affaire, car il peut y avoir des ruptures conditionnelles.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X