我的第一篇文章,所以请放轻松我!
我知道C ++中的结构和类之间没有真正的区别,但包括我在内的很多人使用结构或类来显示意图 - 用于对“普通旧数据”进行分组的结构和用于具有有意义操作的封装数据的类
现在,这很好但是你在什么时候开始认为某些东西不仅仅是一个结构而且应该成为一个类?
我认为结构具有合理性:
我不太确定的事情,但可能会这样做:
我不认为结构应该有:
界限在哪里?
此外,将类实例作为结构的成员是否合理? e.g。
class C {private: int hiddenData; public: void DoSomething();};
struct S {int a; float b; C c; };
S s; s.c.DoSomething();
请记住,我不知道你可以用C ++做些什么,我对你在设计好的软件时应该做些什么感兴趣。
思想?
答案 0 :(得分:13)
我认为有三个主要的,连贯的思想流派:
struct
和class
的人。struct
的人只代表小POD。struct
作为记录的人。我不能对这些策略中的任何一个做出决定性的论据。我倾向于遵循路径2,但是当我看到它适合时,我也使用非POD类型的结构,特别是对于功能对象(即使这些可能不符合POD要求)。
(顺便提一下,C ++ FAQ lite对POD)的定义非常好。
编辑我没有触及模板元编程技术,例如使用struct
作为占位符(类型标记)或实现元函数。我想在这些情况下绝对没有争议:因为它们从不包含方法(甚至数据),所以总是使用struct
)。
答案 1 :(得分:10)
我个人的偏好是只使用结构,如果根本没有方法的话。如果我因任何原因需要添加方法,那就是一个类。
答案 2 :(得分:9)
课程与结构
使用class或struct关键字与读者产生的'感觉'是一种品味问题。从技术上讲它们是等价的,但如果结构用于POD和C结构类型以及其他任何类的类,则可读性更好。
应该放在C ++结构中的基本内容:初始化数据的构造函数(我不喜欢使用memset,如果POD演变成不同的东西,它可以稍后回来)或者来自其他类型但不是复制构造函数的构造。
如果您需要定义复制构造函数或赋值运算符,因为生成的编译器不够好,请将其设为类。
对于将传递给STL算法和模板元编程的仿函数,通常使用结构,如
struct square_int {
int operator()( int value )
{
return value*value;
}
};
std::transform( v.begin(), v.end(), v.begin(), square_int() );
或
// off the top of my head
template <typename T>
struct is_pointer { enum { value = false } };
template <typename T>
struct is_pointer<T*> { enum { value = true } };
会员方法与免费功能
除了我之前所说的,这不会增加其他人已经回答的内容,我想把重点放在你在帖子中评论的其他类型的函数,作为比较运算符等。
通常,对称(比较,算术),插入和删除运算符以及转换的运算符更好地实现为自由函数,无论是将其声明为类还是结构。
如果将对称运算符(关于数据类型)实现为成员函数,则它们不对称。查找规则不会强制左侧调用成员函数,但它将应用相同的强制转换以匹配自由函数。
// Example of symmetry with free functions where method would be asymmetric
int main()
{
std::string( "Hello " ) + "world"; // compiles as free / member function
"Hello " + std::string( "world" ); // compiles with free function, fails with member function definition of +
}
在上面的代码中,如果operator +是std :: string的成员方法,编译器将无法编译,因为它无法将const char * literal强制转换为std :: string以使用成员方法。
从流中插入和提取必须始终作为自由函数实现,因为流始终是操作的左侧。
将转换保持为自由函数可以解耦两种不同的类型。如果A和A'可以相互转换并且您决定将变换实现为A的成员,则A必须知道A',并且A的所有使用将取决于A'是否使用它。如果将转换定义为自由函数,则A在没有A'的情况下完成,并且两个类/结构之间的耦合将更小。转换到/来自网络,序列化和反序列化也是如此。当您在类/结构中实现它们时,您将强制所有用户了解这些转换。
答案 3 :(得分:3)
通过阅读Visual Studio附带的一些STL源代码,似乎正在使用的一个标准是“大部分公开”(以 struct 开头)或“大多数私有”(开始)使用类)?
以类似的方式,如果您在类的顶部(可能因为它很重要)写的是 public ,那么请使用 struct 。另一方面,如果列出成员数据,请先使用 class 。
答案 4 :(得分:3)
当我需要维护不变量和数据完整性时,我使用类和封装。如果你没有不变量,并且数据真的只是一堆项目,那么即使你添加了花哨的辅助构造函数或函数,我们的工作室也总是可以使用struct。然而,你做的装饰越多,那就应该让你停下来想想也许它应该是一个阶级。
如果你必须与POD兼容(比如说与C代码接口),你仍然需要使用struct。但是,您可以将此结构包装在类中,并使用get()函数公开它以与C API进行交互。或者创建一个辅助函数来从类中返回一个合适的POD结构。
答案 5 :(得分:3)
您可以查看标准库的功能。每个人最喜欢的struct std :: pair 只有构造函数。
我发现使用带有结构的构造函数如此方便和自然,我无法想象没有它们。我从不给结构任何其他方法,但当然可能有自由函数或其他类的成员将它们作为参数。
答案 6 :(得分:2)
为了方便起见,我会在结构中添加一些方法,只有在我实际使用它们的时候。当然所有这些都是公开的。如果我需要更多,它会立即转换为一个类。
答案 7 :(得分:2)
我可能属于少数,但我使用struct
来表示一件事,“比特的顺序很重要”。任何必须序列化到磁盘或网络,或必须与某些第三方库兼容并且需要按正确顺序排列的东西,或者如果它正在进行某种位域魔术作为特定于处理器的优化,那么它总会进入struct
。因此,重新排列struct
中的字段总会产生某种后果,应该仔细考虑。
class
具有仅在语义上有意义的字段。如果由于某种原因,我或其他人想要重新安排或修改class
的数据成员,这可以非常自由地发生。
答案 8 :(得分:1)
一致性是最重要的。惯例的目的是为将来阅读代码的所有人提供一个共同的参考点。
就个人而言,如果我觉得自己需要课程的功能,我会避免使用结构 AKA:“简单的旧数据”方法。
查看您的项目并设置标准,或坚持已经存在的标准。
尽管如此,我建议你尽量避免继承,特别是如果所有结构都在持有POD。没有什么比跟踪一堆超类的整数或字符更糟糕了。
答案 9 :(得分:1)
包括我在内的很多人都使用了 结构或类来显示意图 - 用于分组“普通旧数据”的结构 和封装数据的类 有意义的操作。恕我直言,这种区别是一种误解,但很好理解为什么会这样使用。它基于传统的约定和关于类与结构的内置感觉。我会遵循Marshall Cline的建议:
7.8 What's the difference between the keywords struct and class?
因为这是最有意义的 人们已经拥有了,你应该 如果你可能使用struct关键字 有一个方法很少的类 并有公共数据(这样的事情 存在于设计良好的系统中!),但是 否则你应该使用 class keyword。
就个人而言,我(过度)使用struct
metafunctions
关键字
答案 10 :(得分:0)
结构的另一个常见用途是模板内容和本地RAII或仿函数类。这些是一次性的代码,它们比整个类更接近函数,并且需要额外的public:
声明才是愚蠢的。如果你看一下boost,你会看到很多像这样的东西:
template<
bool C
, typename T1
, typename T2
>
struct if_c
{
typedef T1 type;
};
显然不是POD(事实上它根本没有任何数据)。在其他情况下,您可以使用仅包含构造函数/析构函数的RAII类:
struct LogOnLeavingScope : public NonCopyable
{
string Message;
LogOnLeavingScope(const string &message) : Message(message) {}
~LogOnLeavingScope() { Log(Message); }
];
Functor类提供相同的参数:
struct Logger
{
void operator()(const string &message) { Log(message); }
}
我会说C ++没有一个特性暗示你应该使用类而不是结构(除了虚函数)。这都是关于你想要呈现什么界面的 - 如果你对一切都是公开的,你可以使用结构。如果您发现要添加private:
个部分,那么这是一个好兆头,您真的想要一个类而不是一个结构。
答案 11 :(得分:0)
如果有数据成员的accesser方法,那么它就是一个类。
如果您可以直接访问数据并可以随意修改它,那么它就是一个结构。
结构不应该有构造函数,比较运算符等等。
答案 12 :(得分:0)
我喜欢在结构上添加的唯一方法是属性类型方法,简单转换(以及适用于类型的运算符)和非默认构造函数。
例如,我可以定义一个RECT
结构如下:
typedef struct tagRECT{
int left;
int top;
int right;
int bottom;
int get_width(){return right - left;}
int get_height(){return bottom - top;}
int set_width(int width){right = left + width; return width;}
int set_height(int height){bottom = top + height; return height;}
} RECT, *PRECT;
在编译器支持属性作为扩展名的情况下,“set”方法返回新值以支持赋值链。
对于存储复杂数据的POD类型,我可能包含对该数据执行简单转换的方法。一个明显的例子可能是在TransformationMatrix结构上包括Rotate,Scale,Shear和Translate方法。
真的只是上面的扩展,如果运算符对类型有意义,我会将它们添加到结构中。这对于维持对象的原子性是适当且必要的。一个明显的例子是Complex结构上的标准算术运算符。
我更喜欢不在我的结构上有一个默认构造函数。我不想分配一个POD数组,并且需要调用一千个(或其他)默认初始化。如果memset
初始化不够,我将提供初始化方法。
答案 13 :(得分:0)
我总是将结构用于'数据块',即使它们被构造函数和有时比较运算符修饰,它们也永远不会添加方法(甚至不是get / set方法)。
我认为这显示了该类型的意图 - 结构只是其他东西要操作的数据。
答案 14 :(得分:0)
每当我想使用数据成员作为对象的接口时,我都使用struct。每当需要添加私有部分时,我都会将结构更改为类。
答案 15 :(得分:0)
通常,每当我需要使用访问说明符时,我都会使用class
。这样做的结果是我的大多数顶级东西仍然是类,而罕见的POD集合和我不那么罕见的内部类pimpl通常是结构。
答案 16 :(得分:0)
结构和类的简单经验法则:
if (data_requires_strict_alignment == TRUE) {
use(struct);
} else {
use(class);
}
也就是说,如果您所表示的数据对应于某个具有严格成员顺序和对齐要求的数据对象(例如,在驱动程序级别与硬件交换的数据结构),请使用结构。对于所有其他情况,请使用课程。类有很多特性和功能,结构没有,根据我的经验,尽可能使用类是有益的,即使你现在没有使用任何这些附加功能(如果没有别的,只有数据类是就像一个具有更安全的默认访问级别的结构)。当您需要结构的唯一属性时,为这些情况保留结构;即,能够以精确的顺序指定结构成员并具有精确的对齐/填充(通常具有低级通信,例如驱动程序),以便您可以将其转换为字节数组,在其上使用memcpy()
等如果您遵循此模型,则不会将类用作结构的成员(因为类定义未指定sizeof(class)
的对齐或可预测值)。同样,如果您正在考虑构造函数或运算符,请使用类。
这个经验法则也有助于更容易地与C代码接口,因为结构以与C结构一致的方式使用。
答案 17 :(得分:0)
当我在思考这个问题时,我没有区分成员和自由函数,但我现在看到这是一个错误。在我看来,结构应该很少有成员函数。很明显,结构中的所有内容都应该是公开的。
因此,结构成员函数通常没有意义,因为任何函数都可以更改结构中的数据。结构上的成员函数是一种方便而不是必需。
唯一的例外是为某些其他目的而成为成员函数所需的东西 - 用于初始化数组的构造函数;与地图一起使用的比较;模板使用的东西;等
也许类和结构应该被视为对立面 - 类暴露函数并隐藏数据,而结构体暴露数据并允许您隐藏函数。
回到字节交换示例:
struct S
{
int data;
S() {data = 1234;}
void ByteSwap() {network::ByteSwap( &data );}
};
S s;
s.ByteSwap();
会变成:
struct S
{
S() {data = 1234;}
int data;
};
namespace network
{
void ByteSwap( int* i ) {/*byte swap *i*/}
void ByteSwap( S* s ) {ByteSwap( &s->data );}
S s;
ByteSwap(&s);
}
当数据和某些功能并不总是强烈地属于一起时,这是有意义的。字节交换只对网络系统有意义,但更高级别的函数仍然可以使用结构,甚至不知道字节交换等低级别的东西。
这种情况的另一个好处是相关的字节交换操作都保存在同一个地方。
答案 18 :(得分:-1)
这纯粹是一种风格问题,你的商店会决定是对还是错。有时候决定不做决定,但是,用Rush的话来说,他们仍然会做出选择。
根据经验,我通常会使用结构体来处理从POD到具有成员简单函数的对象的简单数据类型。但是没有明确的界限,一旦我定义了一个结构,我通常不会返回并将其更改为类,因为我添加了更多功能。我认为没有价值。