结构体中冒号的含义


C99规定int、unsigned int和bool可以作为位域类型,但编译器几乎都对此作了扩展,允许其它类型类型的存在。
使用位域的主要目的是压缩存储,其大致规则为:
1)如果相邻位域字段的类型相同,且其位宽之和小于类型的sizeof大小,则后面的字
段将紧邻前一个字段存储,直到不能容纳为止;
2)如果相邻位域字段的类型相同,但其位宽之和大于类型的sizeof大小,则后面的字
段将从新的存储单元开始,其偏移量为其类型大小的整数倍;
3)如果相邻的位域字段的类型不同,则各编译器的具体实现有差异,VC6采取不压缩方
式,Dev-C++采取压缩方式;
4) 如果位域字段之间穿插着非位域字段,则不进行压缩;
5) 整个结构体的总大小为最宽基本类型成员大小的整数倍。
还是让我们来看看例子。
示例1
struct BF1
{
char f1 : 3;
char f2 : 4;
char f3 : 5;
};
其内存布局为:
|__f1___|____f2___|__|____f3______|______|
|__|__|__|__|__|__|__|__|__|__|__|__|__|__|__|__|

位域类型为char,第1个字节仅能容纳下f1f2,所以f2被压缩到第1个字节中,而f3
能从下一个字节开始。因此sizeof(BF1)的结果为2
示例2
struct BF2
{
char f1 : 3;
short f2 : 4;
char f3 : 5;
};
由于相邻位域类型不同,在VC6中其sizeof6,在Dev-C++中为2
示例3
struct BF3
{
char f1 : 3;
char f2;
char f3 : 5;
};
非位域字段穿插在其中,不会产生压缩,在VC6Dev-C++中得到的大小均为3




写出下列程序在X86上的运行结果。

structmybitfields
{
unsigned short a : 4;
unsigned short b : 5;
unsigned short c : 7;
}test;

void main(void)
{
int i;
test.a=2;
test.b=3;
test.c=0;
i=*((short *)&test);
printf("%d ",i);
}

这个题的为难之处呢,就在于前面定义结构体里面用到的冒号,如果你能理解这个符号的含义,那么问题就很好解决了。这里的冒号相当于分配几位空间,也即在定义结构体的时候,分配的成员a4位的空间, b 5位,c 7位,一共是16位,正好两个字节。下面画一个简单的示意:
变量名 位数
test 15 14 13 12 11 10 9 |8 7 6 5 4 |3 2 1 0
test.a | |0 0 1 0
test.b |0 0 0 1 1 |
test.c 0 0 0 0 0 0 0 | |
在执行i=*((short *)&test);时,取从地址&test开始两个字节(short占两个字节)的内容转化为short型数据,即为0x0032,再转为int型为0x00000032,即50。输出的结果就是50。当然,这里还涉及到字节及位的存储顺序问题,后面再说。

前面定义的结构体被称为位结构体。所谓位结构体,是一种特殊的结构体,在需要按位访问字节或字的一个或多个位时,位结构体比按位操作要更方便一些。
位结构体的定义方式如下:
struct [位结构体名]{
数据类型 变量名:整数常数;
...
}位结构变量;
说明:
1)这里的数据类型只能为int型(包括signed和unsigned);
2)整数常数必须为0~15之间的整数,当该常数为1时,数据类型为unsigned(显然嘛,只有一位,咋表示signed?光一符号?没意义呀);
3)按数据类型变量名:整数常数;方式定义的结构成员称为位结构成员,好像也叫位域,在一个位结构体中,可以同时包含位结构成员及普通的结构成员
4)位结构成员不能是指针或数据,但结构变量可以是指针或数据;
5)位结构体所占用的位数由各个位结构成员的位数总各决定。如在前面定义的结构体中,一共占用4+5+7=16位,两个字节。另外我们看到,在定义位结构成员时,必须指定数据类型,这个数据类型在位结构体占用多少内存时也起到不少的作用。举个例子:
struct mybitfieldA{
char a:4;
char b:3;
}testA;

struct mybitfieldB{
short a:4;
short b:3;
}testB;
这里,testA占用一个字节,而testB占用两个字节。知道原因了吧。在testA中,是以char来定义位域的,char是一个字节的,因此,位域占用的单位也按字节做单位,也即,如果不满一个字节的话按一个字节算(未定义的位按零处理)。而在testB中,short为两个字节,所以了,不满两个字节的都按两个字节算(未定义位按零处理)

关于位结构体在内存中的存储问题
Kevin's Theory #2: In a C structure that contains bit fields, iffield A is defined in front of field B, then field A alwaysoccupies a lower bit address than field B.(来自http://www.linuxforum.net/forum/showflat.php?Cat=&Board=linuxk&Number=638637&page=0&view=collapsed&sb=5&o=all&fpart=all)
说的是,在C结构体中,如果一个位域A在另一个位域B之前定义,那么位域A将存储在比B小的位地址中。
如果一个位域有多个位时,各个位的排列顺序通常是按CPU的端模式(Endianess)来进行的,即在大端模式(bigendian)下,高有效位在低位地址,小端模式则相反。
补充说明一个关于位域与普通结构成员一起使用的问题
先看一个例子
struct mybitfield{
char a:4;
char b:3;
char aa;
char c:1;}test;
这种情况下,test应该占几个字节呢?2个(4+3+1=8占一个字节,aa占一个)还是3个(4+3不足补一位,占一个字节,aa占一个字节,c占一个字节)?
写个小程序验证一下:

int main(int argc, char* argv[])
{
int i;
test.a = 1;
test.b = 1;
test.aa = 1;
test.c = 1;

i=*((short *)&test);
printf("%d /n",i);

return 0;
}

输出结果是273,化为十六进制数0x111,可见是按三个字节来处理了(如果按两个字节处理的话,cba组成一个字节,是10010001(十六进制0x91)再加上aa,那就应该是0x191了)

举这个例子是为了说明一下,定义位域的话,最好是把所以有位域放在一起,这样可以节省空间(如果把c和aa换一下位置,那test就只占两个字节了)。另外也是为了强调一下位结构体的内存分配方式,按定义的先后顺序来分配,而位域(或成员)内的字节顺序则按照CPU的位顺序来进行(一般与CPU的端模式对应)。




struct mybitfields
{
unsigned short a : 4;
unsigned short b : 5;
unsigned short c : 7;
} test;
=> sizeof(test) == 2;

struct mybitfields
{
unsigned char a : 4;
unsigned char b : 5;
unsigned char c : 7;
} test;
=> sizeof(test) == 3;

struct mybitfields
{
unsigned char a : 4;
unsigned short b : 5;
unsigned char c : 7;
} test;
=> sizeof(test) == 6;

struct mybitfields
{
unsigned short a : 4;
unsigned char b : 5;
unsigned char c : 7;
} test;
=> sizeof(test) == 4;

struct mybitfields
{
unsigned char a : 4;
unsigned char b : 5;
unsigned short c : 7;
} test;
=> sizeof(test) == 4;

struct mybitfields
{
unsigned char a : 4;
unsigned int b : 5;
unsigned short c : 7;
} test;
=> sizeof(test) == 12;

### C++ 编程语言中冒号语法的用法和含义 C++ 中的冒号语法主要用于多种场景,包括继承、成员变量初始化、位域定义以及范围遍历等。以下是具体解释及其示例代码。 #### 一、继承中的冒号语法 在类定义中,冒号用于指定基类或接口。这表示当前类是从某个基类派生出来的。 ```cpp // 继承示例 class Base { public: virtual void show() { std::cout << "Base Class" << std::endl; } }; class Derived : public Base { // 冒号用于指定继承方式 public: void show() override { std::cout << "Derived Class" << std::endl; } }; ``` 此部分描述了如何通过冒号来实现单继承或多继承的功能[^1]。 #### 二、构造函数中的初始化列表 冒号还广泛应用于构造函数的初始化列表中,尤其对于常量成员变量或引用类型的成员变量来说至关重要。 ##### 基本概念 当一个类拥有 `const` 或者引用类型的成员时,在构造过程中必须使用初始化列表对其进行赋值,因为这些类型无法被重新分配新值。 ##### 示例代码 ```cpp #include <iostream> using namespace std; class Example { private: const int value; double& refValue; public: Example(double& d) : value(42), refValue(d) {} // 初始化列表 void printValues() const { cout << "Constant Value: " << value << ", Reference Value: " << refValue << endl; } }; int main() { double externalVar = 3.14; Example e(externalVar); e.printValues(); } ``` 上述例子展示了如何利用冒号语法完成复杂的初始化过程[^3]。 #### 三、位域(Bit Field) 的应用 另外一种不太常见但是非常有用的用途就是定义位域结构体成员之间的界限划分。 ```cpp struct BitFields { unsigned int flag1 : 1; // 占用一位存储空间 unsigned int field2 : 3; // 只占用三位的空间大小 }; BitFields bf; bf.flag1 = true; bf.field2 = 7; ``` 这里可以看到我们是如何精确控制内存布局从而节省资源消耗的方法之一。 #### 四、增强型for循环 (Range-based For Loop) 自C++11标准以来新增加的一种简化写法即为增强版foreach循环语句形式,其中也包含了类似的冒号符号作为分隔标志。 ```cpp std::vector<int> nums = {1, 2, 3, 4, 5}; for(auto num : nums){ // Range-Based For Loop std::cout<<num<<" "; } ``` 这段简单的片段体现了现代C++风格下更加直观易懂的操作模式[^4]。 --- ### 总结 综上所述,C++里的冒号不仅仅局限于单一功能表现形式而是涵盖了众多方面如继承关系确立、对象属性设定乃至高级特性体现等多个层面都发挥着不可替代的作用。
评论 2
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值