Copie profonde / copie superficielle ...
Un mot qui est souvent source de confusion.
Ici, je voudrais voir comment le comportement du programme diffère en fonction de chaque méthode de copie, en utilisant le plus petit exemple dans lequel la différence entre chaque méthode de copie est apparente.
C'est un avertissement. Si cela ne vous dérange pas, passez au chapitre "Que faites-vous?".
Dans cet article, nous utiliserons le «passage partagé» pour unifier les noms des méthodes appelées «partage de passage», «appel par partage», «passage partagé» et «passage de référence par valeur».
Veuillez noter que l'auteur ne recommande pas ce terme. Pour faciliter la comparaison des spécifications multilingues, nous n'avons donné que les noms utilisés dans cet article.
Dans cet article, les termes «passer par référence» et «passer par partage» sont également utilisés pour décrire les «méthodes d'attribution de variables».
Normalement, c'est un mot utilisé lors du passage d'un argument à une fonction, mais comme le même concept vaut pour l'affectation, nous utiliserons le mot tel quel.
(Ceci est mon opinion personnelle, mais je ne suis pas sûr de l'intérêt d'utiliser le mot "passer XX" uniquement lors du passage d'arguments à une fonction. Je pense que c'est correct de l'utiliser pour l'affectation, donc je le ferai dans cet article. Je vais.)
Lorsque je recherche, je reçois beaucoup d'articles disant "La copie superficielle est une méthode qui consiste à copier uniquement une référence ou un pointeur et non à créer une nouvelle entité".
Dans cet article, le cas où «une nouvelle entité est créée, mais quelque part dans le contenu fait référence à la même entité qu'avant la copie» est appelé «copie superficielle».
Version anglaise de Wikipedia et Documentation officielle Python Mais c'est le cas.
(Ceci est mon opinion personnelle, mais au moins ce cas de "création d'une nouvelle entité mais faisant référence à la source de la copie" devrait être appelé "copie superficielle", donc "copie superficielle est nouvelle". Je pense que l'explication selon laquelle "cela ne crée pas d'entité" est incorrecte. Si "un objet qui ne crée pas de nouvelle entité ** est aussi appelé une ** copie superficielle", il n'y a pas de contradiction, mais je pense que c'est également incorrect. )
Remplacez «une copie dans un certain sens» de la variable «a» par la variable «b», jouez avec le «b», puis vérifiez le contenu du «a».
À cette époque, pas seulement deux types de méthodes de copie
Comparons les quatre.
La signification de chaque terme sera expliquée ensemble lorsque le code sera expliqué ci-dessous.
assignment.dart
void main() {
var a = [
["did deep copy"]
];
//Ici, le processus de substitution d'une copie de a dans un certain sens pour b
b[0][0] = "did shallow copy";
b[0] = ["did pass-by-sharing"];
b = [
["did pass-by-reference"]
];
print(a[0][0]);
}
Je voulais juste présenter le contenu du traitement, et je ne voulais pas en parler en fonction d'une langue spécifique, alors je l'ai écrit dans Dart, qui semble être utilisé par peu de gens. (Récemment augmenté ...?)
Le contenu du traitement est
Voyons pourquoi cela fait une différence.
Si l'assignation «b = a» est passée par référence, «b» aura une «référence» qui pointe vers «a», et tout traitement ultérieur sur «b» affectera également «a». Je vais.
Il est plus facile de comprendre le mouvement si vous pensez à «a» comme étant donné l'alias «b».
Dans ce cas, le code fonctionne comme ceci.
assignment.dart
void main() {
var a = [
["did deep copy"]
];
//Passez maintenant a par référence à b
//Tous les traitements suivants sont équivalents à ceux effectués pour un
b[0][0] = "did shallow copy";
b[0] = ["did pass-by-sharing"];
b = [
["did pass-by-reference"]
];
print(a[0][0]); // did pass-by-reference
}
Dernier
b = [
["did pass-by-reference"]
];
Le contenu de ʻa` est une liste de listes avec cette chaîne.
Par conséquent, la sortie sera "did pass-by-reference".
À propos, cette chaîne de caractères signifie «passé par référence».
À proprement parler, la livraison partagée n'est pas seulement un terme qui fait référence à la façon de réussir.
Dans un langage partagé, le contenu d'une variable n'est pas la valeur elle-même, mais une référence à cette valeur. (Ceci est pour de nombreux langages tels que Java, Python, JavaScript.)
Ensuite, lorsque «b = a» est défini, la «référence» stockée dans «a» est copiée et stockée également dans «b».
C'est pourquoi le passage partagé est parfois appelé "passage par référence".
Dans ce cas, le code ci-dessus se comporte comme suit.
assignment.dart
void main() {
//a fait référence à ce double tableau
var a = [
["did deep copy"]
];
//Ici, partagez de a à b
//Ce processus affecte a car a et b ont des références qui pointent vers la même entité.
b[0][0] = "did shallow copy";
//Ce processus affecte également un
b[0] = ["did pass-by-sharing"];
//Ici, b stocke une nouvelle référence pointant vers la nouvelle entité, donc ce processus n'affecte pas a.
b = [
["did pass-by-reference"]
];
print(a[0][0]); // did pass-by-sharing
}
Dernier
b = [
["did pass-by-reference"]
];
A assigné "" [["did pass-by-reference"]] à
b, une référence qui pointe vers une entité différente de celle d'avant, donc ce processus affecte ʻa
. Non.
Par conséquent, la sortie sera "did pass-by-sharing".
À propos, cette chaîne de caractères signifie «partagé et transmis».
Nous continuons à parler de langages où le contenu d'une variable n'est pas la valeur elle-même, mais une référence à cette valeur. (Java, Python, JavaScript, etc.)
Il n'y a pas de copie superficielle dans les langages où le contenu de la variable est la valeur elle-même (comme C ++). (Je pense qu'il peut être reproduit à l'aide de pointeurs et en passant par référence)
Une copie superficielle, comme son nom l'indique, fait une copie.
Si le contenu est "référence", copiez-le tel quel.
Voyons ce que cela signifie dans le code.
assignment.dart
void main() {
//a fait référence à ce double tableau
var a = [
["did deep copy"]
];
//Maintenant, faites une copie superficielle de a et attribuez-la à b
//a et b ont des références qui pointent vers des entités différentes.
//Cependant, les deux entités ont la même «référence» dans le 0e élément, donc
//Ce processus affecte un
b[0][0] = "did shallow copy";
//Le 0e élément de b est réécrit comme une référence pointant vers quelque chose de différent.
//Ce processus n'affecte pas a.
b[0] = ["did pass-by-sharing"];
//Ici, b stocke une nouvelle référence pointant vers la nouvelle entité, donc ce processus n'affecte pas non plus a.
b = [
["did pass-by-reference"]
];
print(a[0][0]); // did shallow copy
}
«A» et sa copie superficielle sont en fait différents. Si vous affichez ʻid en Python et
hashcode dans Dart et que vous le vérifiez, vous pouvez voir que ʻa
et b
pointent vers des entités différentes.
Cependant, le même contenu est copié.
D'ailleurs, une "référence" pointant vers la même chose a été copiée.
Donc, si vous réécrivez le contenu de la "destination de référence du contenu", ʻaet
b` seront affectés.
C'est ça.
b[0][0] = "did shallow copy";
D'un autre côté, si vous réécrivez le "contenu de b
" lui-même, cela n'affectera pas ʻa`.
C'est
b[0] = ["did pass-by-sharing"];
est. Ce processus n'affecte pas «a». Le résultat est une "copie superficielle".
Comme une copie superficielle, une copie profonde fait une copie, mais
Examinez la valeur référencée dans le contenu et copiez-la également.
S'il s'agit également d'une "référence", vérifiez la valeur de la référence et copiez-la également.
Répétez ceci jusqu'à ce que vous ayez copié toutes les références.
ʻA et
b` ne partagent plus rien.
Les opérations effectuées sur l'un n'affectent pas du tout l'autre.
assignment.dart
void main() {
//a fait référence à ce double tableau
var a = [
["did deep copy"]
];
//Passez maintenant une copie complète de a à b. Après cela, l'opération de b n'affecte pas a.
b[0][0] = "did shallow copy";
b[0] = ["did pass-by-sharing"];
b = [
["did pass-by-reference"]
];
print(a[0][0]); // did deep copy
}
Puisque rien n'a changé dans ʻa, la valeur initiale
did deep copy` est sortie.
Voyons comment ils sont réellement attribués dans certaines langues! !! !!
JavaScript
JavaScript passera partagé lorsqu'il est attribué normalement.
assignment.js
a = [["did deep copy"]];
b = a;
b[0][0] = "did shallow copy";
b[0] = ["did pass-by-sharing"];
b = [["did pass-by-reference"]];
console.log(a[0][0]); // did pass-by-sharing
Si vous ne voulez pas partager l'entité, si c'est un tableau, vous pouvez utiliser slice
pour faire une copie et l'avoir.
assignment.js
a = [["did deep copy"]];
b = a.slice(0, a.length);
b[0][0] = "did shallow copy";
b[0] = ["did pass-by-sharing"];
b = [["did pass-by-reference"]];
console.log(a[0][0]); // did shallow copy
La copie dans ce cas est une copie superficielle. Si vous souhaitez faire une copie complète, vous devez concevoir.
Même s'il s'agit d'un objet au lieu d'un tableau, il est partagé si vous l'affectez normalement. Si vous souhaitez le copier, vous pouvez procéder comme suit.
assignment.js
a = { x: { y: "did deep copy" } };
b = Object.assign({}, a); //Ici b=Si a est défini, il sera partagé
b.x.y = "did shallow copy";
b.x = { y: "did pass-by-sharing" };
b = { x: { y: "did pass-by-reference" } };
console.log(a.x.y); // did shallow copy
Python
Vient ensuite Python.
assignment.py
import copy
a = [['did deep copy']]
b = a
b[0][0] = 'did shallow copy'
b[0] = ['did pass-by-sharing']
b = [['did pass-by-reference']]
print(a[0][0]) # did pass-by-sharing
Même en Python, si vous l'attribuez normalement, il sera partagé.
Python a un module appelé copy
qui vous permet de faire explicitement des copies superficielles ou profondes.
Cliquez ici pour la documentation copie --- opérations de copie superficielle et de copie profonde
Vous pouvez faire une copie superficielle avec copy.copy
.
assignment.py
import copy
a = [['did deep copy']]
b = copy.copy(a)
b[0][0] = 'did shallow copy'
b[0] = ['did pass-by-sharing']
b = [['did pass-by-reference']]
print(a[0][0]) # did shallow copy
Vous pouvez faire une copie complète avec copy.deepcopy
.
assignment.py
import copy
a = [['did deep copy']]
b = copy.deepcopy(a)
b[0][0] = 'did shallow copy'
b[0] = ['did pass-by-sharing']
b = [['did pass-by-reference']]
print(a[0][0]) # did deep copy
Les objets peuvent également être copiés dans ce module. C'est pratique.
Dart
assignment.dart
void main() {
var a = [
["did deep copy"]
];
var b = a;
b[0][0] = "did shallow copy";
b[0] = ["did pass-by-sharing"];
b = [
["did pass-by-reference"]
];
print(a[0][0]); // did pass-by-sharing
}
Si Dart est également attribué normalement, il sera partagé.
C++
C ++ se comporte très différemment des langages jusqu'à présent.
C ++ n'est pas partagé lorsqu'il est attribué normalement.
En C ++, le contenu d'une variable n'est pas une "référence" mais une "valeur elle-même".
Puisqu'il est copié tel quel, il n'y a aucune relation avec la source de la copie à ce stade.
Peu importe comment vous le copiez et le créez, cela n'affecte pas la source de la copie.
Il se comporte exactement comme ** copie profonde **.
assignment.cpp
#include <iostream>
#include <vector>
using namespace std;
int main()
{
vector<vector<string>> a{vector<string>{"did deep copy"}};
vector<vector<string>> b = a;
b[0][0] = "did shallow copy";
b[0] = vector<string>{"did pass-by-sharing"};
b = vector<vector<string>>{vector<string>{"did pass-by-reference"}};
cout << a[0][0] << endl; // did deep copy
}
Vous pouvez également passer par référence en C ++.
En cas de passage par référence, toutes les opérations effectuées par copie et création affecteront la source de la copie.
assignment.cpp
#include <iostream>
#include <vector>
using namespace std;
int main()
{
vector<vector<string>> a{vector<string>{"did deep copy"}};
vector<vector<string>> &b = a;
b[0][0] = "did shallow copy";
b[0] = vector<string>{"did pass-by-sharing"};
b = vector<vector<string>>{vector<string>{"did pass-by-reference"}};
cout << a[0][0] << endl; // did pass-by-reference
}
Comme nous l'avons vu jusqu'à présent, si vous écrivez ce processus, vous pouvez clarifier ce qui est attribué au moment de l'affectation.
Si vous savez cela, vous devriez être moins susceptible d'être dérangé par un comportement inattendu.
De plus, bien qu'il s'agisse d'un article similaire, il existe un article qui favorise la compréhension en comparant le comportement lors du passage d'un argument à une fonction et le comportement lors de l'assignation, veuillez donc le lire si vous le souhaitez.
Si vous faites une erreur dans cet article, je vous serais très reconnaissant de bien vouloir le signaler! Je vous remercie.
Recommended Posts