You have this labelled with C#, C++ and "language-agnostic". The fact is though, the difference between structs and classes in C# and C++ are completely different, so this is not a language-agnostic question.
In C++ class
is syntactic sugar for struct
with different default visibility. The reason is that C had a struct
and only had "public" visibility (indeed, it's not even a fully meaningful statement in C, there's no such thing as OO-style information hiding in C).
C++ wanted to be compatible with C so it had to keep struct
default to everything visible. C++ also wanted to follow good OO rules, in which things are private by default, so it introduced class
to do exactly the same, but with different default visibility.
Generally you use struct
when you are closer to C-style use; no or relatively simple member functions (methods), simple construction, ability to change all fields from the outside ("Plain Old Data"). class
is generally used for anything else, and hence more common.
In C# a struct
has value semantics while a class
has reference semantics (in C++ both classes and structs have value semantics, but you can also use types that access them with reference semantics). A struct, as a value-type is self-contained (the variable contains the actual value(s) directly) while a class, as a reference type refers to another value.
Some other differences are entailed by this. The fact that we can alias reference types directly (which has both good and bad effects) comes from this. So too do differences in what equality means:
A value type has a concept of equality based on the value contained, which can optionally be redefined (there are logical restrictions on how this redefinition can happen*). A reference type has a concept of identity that is meaningless with value types (as they cannot be directly aliased, so two such values cannot be identical) that can not be redefined, which is also gives the default for its concept of equality. By default, == deals with this value-based equality when it comes to value types†, but with identity when it comes to reference types. Also, even when a reference type is given a value-based concept of equality, and has it used for == it never loses the ability to be compared to another reference for identity.
Another difference entailed by this is that reference types can be null - a value that refers to another value allows for a value that doesn't refer to any value, which is what a null reference is.
Also, some of the advantages of keeping value-types small relate to this, since being based on value, they are copied by value when passed to functions.
Some other differences are implied but not entailed by this. That it's often a good idea to make value types immutable is implied but not entailed by the core difference because while there are advantages to be found without considering implementation matters, there are also advantages in doing so with reference types (indeed some relating to safety with aliases apply more immediately to reference types) and reasons why one may break this guideline - so it's not a hard and fast rule (with nested value types the risks involved are so heavily reduced that I would have few qualms in making a nested value type mutable, even though my style leans heavily to making even reference types immutable when at all practical).
Some further differences between value types and reference types are arguably implementation details. That a value type in a local variable has the value stored on the stack has been argued as an implementation detail; probably a pretty obvious one if your implementation has a stack, and certainly an important one in some cases, but not core to the definition. It's also often overstated (for a start, a reference type in a local variable also has the reference itself in the stack, for another there are plenty of times when a value type value is stored in the heap).
Some further advantages in value types being small relate to this.
Therefore in C# a struct when you are solely concerned with value-semantics and will not want to alias (string
is an example of a case where value-semantics are very important, but you would want to alias, so it is a class to make it a reference-type). It's also a very good idea for such types to be immutable and an extremely good idea for such types to have fields that total to less than 16bytes - for a larger struct or a struct that needs to be mutable it may well be wise to use a class instead, even if the value-semantics make struct your first choice.