C/C++中,空数组、空类、类中空数组的解析及其作用

我们经常会遇到这些问题:

(1)C++中定义一个空类,他们它的大小(sizeof) 为多少?

(2)只有一个char数据成员的类的大小?

(3)能否定义一个空数组?

(4)空数组名做标示的指针指向什么地方?

(5)空类有什么用?

(6)空数组有什么用?

等等

这些问题,笔者在这篇文章统统做一个比较详细的解析和认识。

 

1,sizeof是什么?

首先我们要理解sizeof是什么东西?准确来讲,对于C++这种强类型的语言,在某一时刻,对象的类型的大小是确定的,这个信息在编译的时候直接可以确定,所以我们要明白sizeof并不是一个函数,而是一个返回对象类型大小的宏,这个宏的参数可以是对象,也可以是类型。我们需要明白的是,在编译结束后,sizeof的那个位置上面是直接被替换成一个常数的,我们用一个简单而直观的实验来证实一下:

int main()
{
        int a=2;
        int b[sizeof(a)];
        cout<<sizeof(b)/sizeof(int)<<endl;
}


如果sizeof是作为的函数,那么这个是不可能编译成功的,因为栈上定义数组是一定需要常数(或常数表达式的)。这样我们就能知道:

sizeof(i++);这样一句话,i是不可能加1的,因为这句话在编译的时候就已经被转换成的对应的常数,而编译的时候是不会进行运行的,这是常常出现的陷阱之一。

 

2,空类的大小

很多人都知道,一个空类(后者是空类对象)使用sizeof的时候,结果是1。解释相信大家也知道,想想,如果真是空类。那么对于同一个空类的对象就不会占空间,不会占空间就意味着无法区分。(有人可能会说,我们只分配对象名,不分配空间,那样的话,你会让整个C/C++语言为空类,以及相关的空类对象设计一套特殊的规则,是这个语言变得非常不合理)。所以,C++的选择是,自动的给空类插入一个char类型(只一个特殊对待),只要这个类对象将来占空间,那么就可以通过地址来区分他们。

 

3,空类的作用

还有一个关于空类的疑问就是,C++语言有必要保留空类吗?空类实现空对象有什么用?

有用的,尤其是在“泛型编程”中,空类(结构)的用处非常广:

在其他的文章中提到,我们利用类型(通常是空类)来区别对待不同类对象的属性。(其实我们是可以通过使用常数来区分的,但是区别我们很容易就能知道)。

使用常数来区分需要使用if else的这种运行时来确定执行的线路的方法,而使用函数重载的方法,在参数中加入一个空类域作为区分不同的函数的方法,编译的时候直接选择,而不是在运行的时候选择,这是非常提高效率的。

要知道,不同的空类,是不同的。他们代表着不同的类型(虽然他们结构一样)。在STL中,使用空类区分不同类型的标志,从而在编译的时候来对不同的类进行有针对性的优化是非常常见的。

template <typename A>
void fun(A a)
{
   typedef typename trait<A>::type T;

   _fun(A a, *(new T()));
}  

template <typename A>
void _fun(A a, int)
{
   ……
}

template <typename A>
void _fun(A b, float)
{
   ……
}

当然,空类应该还有其他的用处。我们所有知道和理解的就是。空类是C++中一个有用的机制,不同名称的空类代表着不同的类型。空类在编译的时候会被编译器自动的加入一个char成员,不为别的,只是为了,让它被实例后的对象占有空间,从而可以区分。

 

4,空数组

C中我们可以定义空数组:

int a[0];

使用sizeof的时候你差是多少:

0

好吧,这里0,我们可以理解。但是问题就来了

既然前面对于空类的情况中,因为需要让对象唯一定位,所以插入char,那么空数组既然sizeof的大小为0,那应该就是不占空间,那么如何区分。

事实上,对于空数组,在C/C++有着特别的交代(可能是具体的实现不同,这里只是使用GCC,G++)。

空数组名是一个指针,(但是又不占空间)指向一个位置:

对于结构体中,空数组名这个指针指向了前面一个成员结束的第一个空间。

对于非结构题中,空数组名这个指针指向的内容,与前一个对象的指针的内容一样(虽然可能他们的类型不一样)。

虽然,我们不知道这两种安排有什么玄机,或者益处,但是无所谓(下一节会介绍空数组的作用),但是针对空数组的sizeof为什么可以为0,我们有了解释。

 

与空类,不同的是,空数组是一个对象,而不是一个类。既然我们这个对象定义出来了,而且它会指向一个空间(虽然这个空间可能会与其他的地方重叠)但是,我们总算能够区分开不同的空数组。

 

6,空数组的作用

其实在C里面,空数组的使用是非常多的。

问题:

 

加入你想要给一个结构体(代表一个功能)添加一个缓冲区。你会怎么做?

1)定义一个固定长度的buffer数组成员,这样的不好之处在于buffer会被定死。

2)定义一个buffer指针,在构造函数(虽然C没有,但可以使用initialize函数来代替)中动态的创建一个需要大小的buffer,给这个结构体使用。但是这样就需要我们特别管理这个空间(使用析构函数),否则会很容易出现内存泄露。并且,一个buffer指针还占有了一个空间。

 

那么C里面,就有一个巧用空数组来达到这个问题的方法。

sttuct  T
{
   int a;
   int b;
   ……
   char buffer[];
};

我们知道,由于buffer并不占空间,所以,T的对象的总大小是不会把buffer算上的。

struct T * p=(T*)malloc(sizeof(T) +buffer_len);

看到没有,在声请空间的时候,加上buffer_len(所需缓冲区长度),这样结构体和缓冲区一起被声请了,(在结束的时候,也可以直接使用free一起释放,可以避免独立的管理结构体和缓冲区)。

并且我们知道,buffer这个“指针”指向的就是结构体后面的那个buffer_len的空间。

 

这样做还有一个好处,如果我们分开管理结构体和缓冲区(通常这个时候结构体是一个小碎片)。在申请和释放的时候,很容易在内存中制造出碎片。而如果向上面的这样管理,结构体,依附这缓冲区(大块)一起被管理。那将是一个很美好的事情。

这就是空数组的用处。

 

7,类中空数组

class T
{
  int a[0];
};

您看这个类的sizeof为多少:

0

你会觉得这像个玩笑。但是其实仔细分析一下,也是可以所通的。我前面说给一个空类插入一个char成员,是因为想让程序能够区分该类的不同对象。而这里,我们知道由于空数组是不占内存的,它就像一个指针指向某个地方,但是又不占内存。但是的确我们的类对象能够借助空数组这个东西来区分开来。所以既然目的达到了,那么为什么还要加入什么一些什么信息呢?

<think>好的,我现在需要回答用户关于结构体设计模式及使用场景的问题。首先,我得回顾用户提供的引用内容,看看里面有没有相关的信息。引用1提到C++模板结构体在设计模式中的应用,比如策略模式。引用2列举了Android中的设计模式分,包括结构型和行为型模式。引用3展示了C语言中通过结构体和函数指针模拟多态行为,这似于策略模式或模板方法模式。引用4讨论结构体的内存占用,可能和某些特定设计模式相关,比如标记接口。 接下来,我需要将这些信息整理成结构体的设计模式和使用场景。可能需要分点说明,比如策略模式、组合模式、工厂模式等。同时要结合具体的例子,比如引用3中的Shape结构体,说明如何用结构体实现多态。还要注意使用中文回答,并按照用户的格式要求,行内公式用$...$,独立公式单独成段。 然后,生成相关问题,确保问题与内容相关,帮助用户进一步学习。比如结构体如何实现多态,或者结构体的应用场景。 最后检查是否符合系统指令,确保LaTeX语法正确,引用标识正确添加,例如引用3中的例子后面加上[^3]。同时要避免使用Markdown格式,保持自然的中文表达。</think>### 结构体设计模式及使用场景 #### 一、常见结构体设计模式 1. **策略模式(Strategy Pattern)** 通过结构体封装算法族,使算法可以独立于客户端变化。例如,引用[1]中提到的C++模板结构体可定义不同策略实现: ```c typedef struct { void (*execute)(void* context); // 函数指针定义策略行为 } Strategy; ``` 客户端通过切换不同的结构体实例实现算法替换[^1]。 2. **组合模式(Composite Pattern)** 用结构体表示树形结构的节点和叶子,统一处理整体与部分。例如文件系统设计中: ```c typedef struct FileComponent { void (*display)(struct FileComponent*); struct FileComponent* children[10]; // 子节点数组 } FileComponent; ``` 3. **工厂模式(Factory Pattern)** 通过结构体封装对象创建逻辑,例如几何图形工厂: ```c typedef struct ShapeFactory { Shape* (*createCircle)(float radius); // 创建圆形结构体的函数指针 Shape* (*createRect)(float w, float h); } ShapeFactory; ``` 4. **多态模拟(Polymorphism)** 引用[3]中C语言示例:通过结构体嵌套和函数指针实现多态: ```c typedef struct Shape { void (*draw)(struct Shape*); // 函数指针定义绘制行为 } Shape; typedef struct Circle { Shape base; // 基结构体 float radius; } Circle; ``` 调用时通过基指针`Shape*`统一操作不同子对象[^3]。 --- #### 二、结构体使用场景与最佳实践 1. **数据封装** 将相关数据字段与操作函数绑定,例如: ```c typedef struct Vector { float x, y; void (*normalize)(struct Vector*); // 成员函数指针 } Vector; ``` 2. **内存优化** 使用结构体(C++中常见)作为标记或型标识,例如: ```c struct Empty {}; // 引用[4]中空结构体宽度为0,适用于型占位[^4] ``` 3. **协议通信** 结构体直接映射二进制协议格式,如网络报文解析: ```c #pragma pack(1) typedef struct PacketHeader { uint16_t id; // 报文ID uint32_t length; // 数据长度 } PacketHeader; ``` 4. **性能关键场景** 结构体内存连续访问的特性适合数值计算,例如矩阵运算: $$ \mathbf{M} = \begin{bmatrix} a & b \\ c & d \end{bmatrix} $$ 可定义为: ```c typedef struct Matrix2x2 { float data[2][2]; } Matrix2x2; ``` ---
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值