Ist es möglich, Strukturen in C++ wie unten angegeben zu initialisieren
struct address {
int street_no;
char *street_name;
char *city;
char *prov;
char *postal_code;
};
address temp_address =
{ .city = "Hamilton", .prov = "Ontario" };
Die Links hier und hier erwähnen, dass es möglich ist, diesen Stil nur in C zu verwenden. Wenn ja, warum ist dies in C++ nicht möglich? Gibt es einen technischen Grund, warum es nicht in C++ implementiert ist, oder ist es schlecht, diesen Stil zu verwenden? Ich benutze diese Art der Initialisierung gerne, weil meine Struktur groß ist und dieser Stil mir eine klare Lesbarkeit dessen ermöglicht, welcher Wert welchem Mitglied zugeordnet wird.
Bitte teilen Sie mir mit, ob es andere Wege gibt, um dieselbe Lesbarkeit zu erreichen.
Ich habe die folgenden Links vor der Veröffentlichung dieser Frage verwiesen
Wenn Sie deutlich machen wollen, was der jeweilige Initialisierungswert ist, teilen Sie ihn einfach in mehrere Zeilen auf und geben Sie jeweils einen Kommentar dazu:
address temp_addres = {
0, // street_no
nullptr, // street_name
"Hamilton", // city
"Ontario", // prov
nullptr, // postal_code
};
Nachdem meine Frage zu keinem befriedigenden Ergebnis geführt hat (da C++ keine tagbasierte Init-Methode für Strukturen implementiert), habe ich den Trick genommen, den ich hier gefunden habe: Sind Mitglieder einer C++ - Struktur standardmäßig auf 0 initialisiert?
Für Sie wäre es das wert:
address temp_address = {}; // will zero all fields in C++
temp_address.city = "Hamilton";
temp_address.prov = "Ontario";
Dies ist sicherlich am nächsten an dem, was Sie ursprünglich wollten (nullen Sie alle Felder mit Ausnahme der Felder, die Sie initialisieren möchten).
Die Feldbezeichner sind in der Tat eine C-Initialisiersyntax. In C++ geben Sie die Werte in der richtigen Reihenfolge ohne die Feldnamen an. Leider bedeutet dies, dass Sie alle angeben müssen (tatsächlich können Sie nachstehende Felder mit Nullwert weglassen, und das Ergebnis ist dasselbe):
address temp_address = { 0, 0, "Hamilton", "Ontario", 0 };
Diese Funktion wird als bezeichnete Initialisierer bezeichnet. Es ist eine Ergänzung zum C99-Standard. Diese Funktion wurde jedoch in C++ 11 nicht berücksichtigt. Gemäß der C++ - Programmiersprache, 4. Ausgabe, Abschnitt 44.3.3.2 (C-Funktionen, die nicht von C++ übernommen werden):
Einige Ergänzungen zu C99 (verglichen mit C89) wurden in C++ absichtlich nicht übernommen:
[1] Arrays mit variabler Länge (VLAs); Verwenden Sie einen Vektor oder eine Form eines dynamischen Arrays
[2] designierte Initialisierer; Konstruktoren verwenden
Die C99-Grammatik hat die designierten Initialisierer [Siehe ISO/IEC 9899: 2011, N1570-Ausschussentwurf - 12. April 2011]
6.7.9 Initialisierung
initializer:
assignment-expression
{ initializer-list }
{ initializer-list , }
initializer-list:
designation_opt initializer
initializer-list , designationopt initializer
designation:
designator-list =
designator-list:
designator
designator-list designator
designator:
[ constant-expression ]
. identifier
Andererseits verfügt C++ 11 nicht über die designierten Initialisierer [Siehe ISO/IEC 14882: 2011, N3690-Ausschussentwurf - 15. Mai 2013].
8.5 Initialisierer
initializer:
brace-or-equal-initializer
( expression-list )
brace-or-equal-initializer:
= initializer-clause
braced-init-list
initializer-clause:
assignment-expression
braced-init-list
initializer-list:
initializer-clause ...opt
initializer-list , initializer-clause ...opt
braced-init-list:
{ initializer-list ,opt }
{ }
Um den gleichen Effekt zu erzielen, verwenden Sie Konstruktoren oder Initialisierungslisten:
Wie bereits erwähnt, wird dies als Initialisierer bezeichnet.
Diese Funktion ist Teil von C++ 20
Sie können einfach über ctor initialisieren:
struct address {
address() : city("Hamilton"), prov("Ontario") {}
int street_no;
char *street_name;
char *city;
char *prov;
char *postal_code;
};
Sie können sogar die Lösung von Gui13 in eine einzige Initialisierungsanweisung packen:
struct address {
int street_no;
char *street_name;
char *city;
char *prov;
char *postal_code;
};
address ta = (ta = address(), ta.city = "Hamilton", ta.prov = "Ontario", ta);
Haftungsausschluss: Ich empfehle diesen Stil nicht
Ich weiß, dass diese Frage ziemlich alt ist, aber ich habe einen anderen Weg gefunden, mit constexpr und currying zu initialisieren:
struct mp_struct_t {
public:
constexpr mp_struct_t(int member1) : mp_struct_t(member1, 0, 0) {}
constexpr mp_struct_t(int member1, int member2, int member3) : member1(member1), member2(member2), member3(member3) {}
constexpr mp_struct_t another_member(int member) { return {member1, member, member2}; }
constexpr mp_struct_t yet_another_one(int member) { return {member1, member2, member}; }
int member1, member2, member3;
};
static mp_struct_t a_struct = mp_struct_t{1}
.another_member(2)
.yet_another_one(3);
Diese Methode funktioniert auch für globale statische Variablen und sogar für constexpr-Variablen .. Der einzige Nachteil ist die schlechte Wartbarkeit: Jedes Mal, wenn ein anderes Member mit dieser Methode initialisiert werden muss, müssen alle Member-Initialisierungsmethoden geändert werden.
Es ist nicht in C++ implementiert. (auch char*
strings? Ich hoffe nicht).
Wenn Sie so viele Parameter haben, ist dies normalerweise ein ziemlich schwerer Codegeruch. Aber stattdessen, warum nicht einfach die Struktur wert-initialisieren und dann jedes Mitglied zuweisen?
Ich könnte hier etwas vermissen, warum nicht:
#include <cstdio>
struct Group {
int x;
int y;
const char* s;
};
int main()
{
Group group {
.x = 1,
.y = 2,
.s = "Hello it works"
};
printf("%d, %d, %s", group.x, group.y, group.s);
}
Ich habe diese Methode für globale Variablen gefunden, für die die ursprüngliche Strukturdefinition nicht geändert werden muss:
struct address {
int street_no;
char *street_name;
char *city;
char *prov;
char *postal_code;
};
dann deklarieren Sie die Variable eines neuen Typs, die vom ursprünglichen Strukturtyp übernommen wurde, und verwenden Sie den Konstruktor für die Feldinitialisierung:
struct temp_address : address { temp_address() {
city = "Hamilton";
prov = "Ontario";
} } temp_address;
Nicht ganz so elegant wie der C-Stil ...
Für lokale Variable sollte es auch möglich sein, muss aber geprüft werden, ob ein zusätzliches Memset (dieses, 0, sizeof (* this)) dann notwendig wäre ...
(Beachten Sie, dass 'temp_address' eine Variable vom Typ 'temp_address' ist. Dieser neue Typ erbt jedoch von 'address' und kann an allen Stellen verwendet werden, an denen 'address' erwartet wird, daher ist dies in Ordnung.)
Dies ist nur möglich, wenn die zu initialisierende Struktur eine POD (plain old data) -Struktur ist. Es darf keine Methoden, Konstruktoren oder auch Standardwerte enthalten.
In C++ wurden die Initialisierer im C-Stil durch Konstruktoren ersetzt, die durch Kompilierungszeit sicherstellen können, dass nur gültige Initialisierungen ausgeführt werden (d. H. Nach der Initialisierung sind die Objektelemente konsistent).
Dies ist eine gute Praxis, aber manchmal ist eine Vorinitialisierung praktisch, wie in Ihrem Beispiel. OOP löst dies durch abstrakte Klassen oder kreative Designmuster .
Meines Erachtens bedeutet die Verwendung dieses sicheren Weges die Vereinfachung, und manchmal ist der Kompromiss für die Sicherheit zu teuer, da einfacher Code kein ausgefeiltes Design erfordert, um wartbar zu bleiben.
Als alternative Lösung schlage ich vor, Makros mit Hilfe von Lambdas zu definieren, um die Initialisierung so zu gestalten, dass sie fast wie im C-Stil aussieht:
struct address {
int street_no;
const char *street_name;
const char *city;
const char *prov;
const char *postal_code;
};
#define ADDRESS_OPEN [] { address _={};
#define ADDRESS_CLOSE ; return _; }()
#define ADDRESS(x) ADDRESS_OPEN x ADDRESS_CLOSE
Das ADRESSE-Makro wird erweitert
[] { address _={}; /* definition... */ ; return _; }()
das schafft und nennt das Lambda. Makroparameter sind auch durch Kommas getrennt, daher müssen Sie den Initialisierer in Klammern setzen und wie aufrufen
address temp_address = ADDRESS(( _.city = "Hamilton", _.prov = "Ontario" ));
Sie können auch einen generalisierten Makroinitialisierer schreiben
#define INIT_OPEN(type) [] { type _={};
#define INIT_CLOSE ; return _; }()
#define INIT(type,x) INIT_OPEN(type) x INIT_CLOSE
aber dann ist der Anruf etwas weniger schön
address temp_address = INIT(address,( _.city = "Hamilton", _.prov = "Ontario" ));
sie können das ADDRESS-Makro jedoch einfach mit dem allgemeinen Makro INIT definieren
#define ADDRESS(x) INIT(address,x)
In GNUC++ (scheint seit 2.5 veraltet zu sein, vor langer Zeit :) Siehe die Antworten hier: C-Strukturinitialisierung mit Labels Es funktioniert, aber wie? ), ist es möglich, eine Struktur wie folgt zu initialisieren:
struct inventory_item {
int bananas;
int apples;
int pineapples;
};
inventory_item first_item = {
bananas: 2,
apples: 49,
pineapples: 4
};
Ich hatte heute ein ähnliches Problem, wo ich eine Struktur habe, die ich mit Testdaten füllen möchte, die als Argumente an eine Funktion übergeben werden, die ich teste. Ich wollte einen Vektor dieser Strukturen haben und suchte nach einer Einzeilenmethode, um jede Struktur zu initialisieren.
Ich habe am Ende eine Konstruktorfunktion in der struct gewählt, die meiner Meinung nach auch in ein paar Antworten auf Ihre Frage vorgeschlagen wurde.
Es ist wahrscheinlich eine schlechte Praxis, wenn die Argumente für den Konstruktor dieselben Namen haben wie die öffentlichen Member-Variablen, sodass der Zeiger this
verwendet werden muss. Jemand kann eine Bearbeitung vorschlagen, wenn es einen besseren Weg gibt.
typedef struct testdatum_s {
public:
std::string argument1;
std::string argument2;
std::string argument3;
std::string argument4;
int count;
testdatum_s (
std::string argument1,
std::string argument2,
std::string argument3,
std::string argument4,
int count)
{
this->rotation = argument1;
this->tstamp = argument2;
this->auth = argument3;
this->answer = argument4;
this->count = count;
}
} testdatum;
Was ich in meiner Testfunktion verwendet habe, um die zu testende Funktion mit verschiedenen Argumenten wie folgt aufzurufen:
std::vector<testdatum> testdata;
testdata.Push_back(testdatum("val11", "val12", "val13", "val14", 5));
testdata.Push_back(testdatum("val21", "val22", "val23", "val24", 1));
testdata.Push_back(testdatum("val31", "val32", "val33", "val34", 7));
for (std::vector<testdatum>::iterator i = testdata.begin(); i != testdata.end(); ++i) {
function_in_test(i->argument1, i->argument2, i->argument3, i->argument4m i->count);
}
Inspiriert von dieser wirklich netten Antwort: ( https://stackoverflow.com/a/49572324/4808079 )
Sie können Lamba-Verschlüsse machen:
// Nobody wants to remember the order of these things
struct SomeBigStruct {
int min = 1;
int mean = 3 ;
int mode = 5;
int max = 10;
string name;
string nickname;
... // the list goes on
}
.
class SomeClass {
static const inline SomeBigStruct voiceAmps = []{
ModulationTarget $ {};
$.min = 0;
$.nickname = "Bobby";
$.bloodtype = "O-";
return $;
}();
}
Oder wenn Sie sehr schick sein wollen
#define DesignatedInit(T, ...)\
[]{ T ${}; __VA_ARGS__; return $; }()
class SomeClass {
static const inline SomeBigStruct voiceAmps = DesignatedInit(
ModulationTarget,
$.min = 0,
$.nickname = "Bobby",
$.bloodtype = "O-",
);
}
Damit sind einige Nachteile verbunden, die meistens mit nicht initialisierten Mitgliedern zu tun haben. Nach den Kommentaren der verknüpften Antworten wird es effizient kompiliert, obwohl ich es nicht getestet habe.
Alles in allem denke ich, dass es ein ordentlicher Ansatz ist.