1、结构

结构是派生的数据类型,可以使用其他数据类型来构造它们。

1.1 定义结构和结构类型的变量

关键字struct引入了结构定义,用一个标识符作为结构标记,来命名一个结构类型。结构定义大括号内声明的变量是结构的成员。同一结构的成员必须具有具有独一无二的名称,但两个不同的结构可能包含相同名称的结构成员,而不会相互冲突。每个结构定义必须用分号结束。

结构标记与关键字struct一起用来定义结构类型的变量,也可以通过在结构定义的右大括号和结束结构定义的分号之间加入逗号分隔的变量名列表来定义结构变量。关键字typedef也提供了为前面定义的数据类型创建别名(或者同义词)的机制。因此,也可以用typedef为结构类型建立比较短的类型名称。typedef不会创建新类型只会为已经创建的类型定义了新的名称,可以用作已创建类型的别名。

结构不能包含本身的实例,但是可以包含指向结构自身的指针,这里称为自引用结构。也就是说,在定义结构A的时候,不能有A类型的结构成员变量,但是可以有struct A *类型的成员变量(也就是说可以包含指向A类型的指针变量,也就是成员变量引用A自身,这很好理解吧)。需要注意的是结构的定义并没有在内存中分配任何空间,而只是创建了用户需要的用于定义变量的新的类型。定义结构类型变量的时候才会导致内存空间的分配。可以在结构上执行的操作有:将结构变量赋给相同类型的结构变量,获得结构变量的地址(&),访问结构变量的成员和使用sizeof运算符来确定结构变量的大小。

下面是几种关于结构和结构变量的定义是等价的:

(1)
struct card
{char *face;char *suit;
};
struct card aCard,deck[52],*cardPtr;
(2)
struct card
{char *face;char *suit;
}aCard,deck[52],*cardPtr;
(3)
struct
{char *face;char *suit;
}aCard,deck[52],*cardPtr;
(4)
typedef struct
{char *face;char *suit;
}Card;
Card aCard,deck[52],*cardPtr;

可以看出结构标记是可选的,如果没有结构标记,那么就只能在结构定义的时候定义结构类型的变量。为了养成良好的编程习惯和风格,定义结构时提供结构标记还是很有必要的,能够方便代码中稍后定义的结构类型的变量。

下面是图的邻接表存储结构中的顶点结构的定义,下面的两种定义也是等价的:

(1)
struct EdgeNode //边表结点
{int adjvex; //邻接点域,存储该顶点对应的下标 EdgeType weight; //用于存储权值,对于非网图可以不需要 struct EdgeNode *next; //链域,指向下一个邻接点
};
typedef struct EdgeNode EdgeNode;
(2)
typedef struct EdgeNode/* 边表结点  */
{int adjvex;/* 邻接点域,存储该顶点对应的下标 */EdgeType weight;/* 用于存储权值,对于非网图可以不需要 */struct EdgeNode *next; /* 链域,指向下一个邻接点 */
} EdgeNode;

这里之所以用typedef给一个结构声明一个别名,实际上是为了在定义或声明该结构类型的变量的时候,能够直接用EdgeNode而不必struct EdgeNode,少写一个struct。由于该结构成员中有一个指向EdgeNode类型的指针,可是到这里,还没有声明结构标志的别名,因此,这个struct是没法省略直接用EdgeNode的。

例:从矩阵邻接表存储的结构定义看struct定义和别名

typedef struct EdgeNode //边表结点
{int adjvex; //邻接点域,存储该顶点对应的下标 EdgeType weight; //用于存储权值,对于非网图可以不需要 struct EdgeNode *next; //链域,指向下一个邻接点
} EdgeNode;typedef struct VertexNode //顶点表结点
{VertexType data; //顶点域,存储顶点信息EdgeNode *firstedge; //边表头指针
} VertexNode, AdjList[MAXVEX];typedef struct
{AdjList adjList;int numNodes, numEdges; //图中当前顶点数和边数
} GraphAdjList;
//上面的代码非来自本博,仅用来举例说明

上面的例子中比较好的一点就是在定义结构体VertexNode的时候,不但为struct VertexNode定义了别名VertexNode,而且将AdjList声明为了一个VertexNode数组类型的别名(注意,这里不是定义了一个VertexNode类型的数组,而是为VertexNode数组类型声明了一个别名。)。这样,再想定义这种类型的数组,就没必要VertexNode adjList[MAXVEX];了,而只需要AdjList adjList;即可。Clean and Tidy.

1.2 结构的内存对齐问题

由于结构成员不一定是存储在内存中的连续字节中,所以不能用==和!=来比较结构。有时候,因为计算机可能仅在某些内存边界上存储特定的数据类型,如半个字、字或者双字(字是标准内存单元,它用于在计算机中存储数据,通常是两个字节或者四个字节)边界,所以在存储结构的内存区域中可能会有“洞”。这就是传说中的内存对齐问题。

struct E
{char c;int i;
}e1,e2;

上面的例子中,使用两个字节作为字的计算机可能需要在字边界上对齐struct E的每个成员,也就是在字的开头处(这是和机器相关的)进行对齐。下图中,说明了结构体成员变量在内存中的对齐情况,其中变量已经被赋值为字符‘a’和整数97。

如果成员存储在字边界的开头处,则在类型struct E变量的存储空间中有一个字节的空洞,如上图中所示。空洞中的值是没有定义的。如果e1和e2成员变量的值实际上相等,但可能因为在空洞中包含不同的值,所以结构比较并不一定相等。

1.3 结构的初始化

可以像数组那样使用初始值列表来初始化结构。要初始化结构,需要在定义的结构变量名后面加入的等号,以及使用逗号分隔的初始化列表,并在外面加大括号。例如语句struct card aCard={"Three","Hearts"};在定义变量aCard是struct card类型的同时也将其成员face初始化为“Three”,suit初始化为"Hearts"。如果列表中初始值的个数小于结构成员的个数,则将把剩余的成员自动初始化为0,如果成员变量是指针,则初始化为NULL。如果在函数外部定义的结构变量没有进行显示地初始化,那么其结构变量将初始化为0或者NULL。我们也可以在赋值语句中初始化结构变量:相同类型的结构变量之间赋值,或者对结构的单个成员进行赋值。

1.4 访问结构成员

有两个运算符可以用于访问结构成员:结构成员运算符(.)和结构指针运算符(->)。结构成员运算符通过结构变量名来访问结构成员,如printf("%s\n",aCard.suit);便可以访问到aCard结构变量的成员suit;结构指针运算符通过结构指针来访问结构成员,它由负号和大于号构成,两个符号之间没有空格,假设指针cardPtr是指向struct card的指针,那么语句printf("%s\n",cardPtr->suit);便可以访问到cardPtr所指向对象的suit成员。cardPtr->suit等价于(*cardPtr).suit。(由于结构成员运算符比解参考运算符的优先级要高,所以要加括号。)

1.5 在函数中使用结构

可以把单个结构成员、整个结构或者结构指针传递给函数。当把结构或者单个结构成员传递给函数时,它们采用的是值调用传递。所以,被调函数不能修改主调函数中结构的成员。要使用引用调用来传递结构,则需要传递结构变量的地址。结构数组和所有其它数组一样,都是自动使用引用传递。应该知道的是,使用引用调用来传递结构要比使用值调用来传递结构效率要高,因为引用调用不用复制整个结构。

1.6 一个结构的例子

#include <stdio.h>
#include <stdlib.h>
#include <time.h>struct card
{const char *face;const char *suit;
};
typedef struct card Card;void fillDeck(Card * const wDeck,const char *wFace[],const char *wSuit[]);
void shuffle(Card *const wDeck);
void deal(const Card * const wDeck);int main()
{//定义数组存储纸牌Card deck[52];//牌面数组const char *face[]={"Ace","Deuce","Three","Four","Five","Six","Seven","Eight","Nine","Ten","Jack","Queen","King"};//花色const char *suit[]={"Hearts","Diamonds","Clubs","Spades"};//设置随机数发生器的种子srand(time(NULL));//load the card to deckfillDeck(deck,face,suit);//shuffle the cardshuffle(deck);//distributedeal(deck);return 0;
}
void fillDeck(Card * const wDeck,const char *wFace[],const char *wSuit[])
{int i;for (i=0;i<=51;i++){wDeck[i].face=wFace[i%13];wDeck[i].suit=wSuit[i%4];}
}
void shuffle(Card *const wDeck)
{int i;int j;Card temp;//随机选出一张放在第i个位置for (i=0;i<=51;i++){j=rand()%52;temp=wDeck[i];wDeck[i]=wDeck[j];wDeck[j]=temp;}
}
void deal(const Card *const wDeck)
{int i;for (i=0;i<=51;i++)printf("%5s of %-8s%c",wDeck[i].face,wDeck[i].suit,(i+1)%3?'\t':'\n');
}

运行结果:


这是一个关于发牌的例子,还是比较好的。首先定义纸牌结构的时候,成员变量的类型是const char *,这样,便不能通过对该指针进行解参考来修改指针指向的变量的值,却可以修改指针本身的指向,很明显这样是安全(符合一般程序设计中的最低权限原则)而且合理的。void fillDeck(Card * const wDeck,const char *wFace[],const char *wSuit[]);、void shuffle(Card *const wDeck);和void deal(const Card * const wDeck);等函数中const的运用十分巧妙的控制了函数对参数的访问权限,十分漂亮。

2、联合(union)

联合是一种派生的数据类型,它与结构类似,区别在于联合的成员共享相同的存储空间。在程序中有这样的情况,某些变量之间是相关的。这样就可以使用联合来对这些变量的聂存空间进行共享,来避免不必要的内存空间浪费。联合的成员可以是任意类型,存储联合所需要的字节数必须至少能够存储联合中占空间最大的成员。多数情况下,联合包含两种或两种以上的数据类型,每次只能引用一个成员,也就是只能引用一种数据类型。使用时,应该注意用正确的类型来引用联合中的数据。

在联合上可以执行的操作有:将联合赋值给相同类型的另一个联合、获取联合的地址(&)以及使用结构成员运算符和结构指针运算符来访问联合成员。类似地,也不能用==和!=运算符来来比较联合。

2.1 定义和初始化

联合的定义和结构类似,下面是一个例子:

union number
{int x;double y;
};

和struct一样,上面仅仅是创建了一个类型,还没有用该类型来定义变量。在联合变量的定义中,仅能用于第1个联合类型相同的值来初始化联合,因为上面的例子中联合成员的第一个类型是int,所以语句union number value={10};正确地初始化了变量value。但语句union number value={3.1415}将截断浮点数值的小数部分,并通常会产生编译警告。

2.2 联合的例子

#include <stdio.h>
union number
{char c;unsigned int i;
};
int main()
{union number value;value.c='A';printf("%s:\nchar:%c\nunsigned int:%d\n","Put a value int the char member",value.c,value.i);value.i=97;printf("%s:\nchar:%c\nunsigned int:%d\n","Put a value int the unsigned int member",value.c,value.i);value.c='A';printf("%s:\nchar:%c\nunsigned int:%d\n","Put a value int the char member",value.c,value.i);return 0;
}

运行结果:


需要说明的是联合这种数据结构可能不能轻易地移植到其它计算机系统上。联合能否被移植通常依赖于给定系统上存储联合成员数据类型时所使用的对齐方式。

2.3 联合的使用

从上面看,仿佛联合这个东西是一个怪物,谁会自找麻烦去用这个啊。我也是这么想的,结果在网上找到一篇文章专门写联合的使用的。摘了一写东西,贴在这里。

2.3.1 增加代码的可读性

struct  Matrix
{union{struct{float  _f11, _f12, _f21, _f22;};float  f[2][2];}_matrix;
};
struct  Matrix m;

该例子中,struct和float f[][]共享内存空间,没有造成内存空间的浪费。这样,用矩阵的时候可以用m._matrix.f(比如说传参,或者是整体赋值等);需要用其中的几个元素的时候可以用m._matrix._f11,可以避免用m.f[0][0](不直观,且容易出错)。

2.3.2 union和强制类型转换

这里需要说明的是union里面的成员都是从低地址开始对齐的。拿上面定义的number联合作为例子,其中的char成员c和unsigned int成员i,它们在内存中的分布应该如下图:


下面是一个利用判断大小端的例子,分别采用union和非union的实现:

#define TRUE 1
#define FALSE 0
#define BOOL int
//不用union
BOOL  isBigEndian()
{unsigned int  i = 1;   /* i = 0x00000001*/char  c = *(char  *)&i; /* 注意不能写成 char c = (char)i; */return  (int )c != i;
}
//用union
BOOL  isBigEndian()
{union{unsigned int  i;char  c;}test;test.i = 2;return  test.c != 2;
}

3、位运算

在计算机内部是使用位序列来表示所有数据的。每一位的值可以是0或者是1。因此,利用位运算比较方便,而且通常比较高效。

3.1 C语言提供了的位运算符

&|^ 对两个操作数按位进行与、或、异或操作
<<左移 将第一个操作数的各位向左移动第二个操作数所指定的位数;在右边用0来填充空位,向左移动到边界之外的1将丢失。
>>右移 将第一个操作数的各位向右移动第二个操作数所指定的位数;填充左边的方法依赖于计算机。对于unsigned整数执行右移将使得左边的空位用0代替,移动到右边界之外的1将丢失。如果右边的操作数是负值,或者右边的操作数大于存储左边操作数的位数,则移位的结果是不确定的。
~取反 将操作数按位取反

3.2 一个例子:以二进制的形式输出无符号整数

#include <stdio.h>
void displayBits(unsigned value);
int main()
{unsigned x;printf("Enter an unsigned integer:\n");scanf("%u",&x);displayBits(x);return 0;
}
void displayBits(unsigned value)
{unsigned c;unsigned displayMask=1<<31;printf("%10u= ",value);for (c=1;c<=32;c++){putchar(value&displayMask?'1':'0');value<<=1;//左移一位if(c%4==0){putchar(' ');if(c%8==0)putchar(' ');}}putchar('\n');
}

运行结果:


4、位域

在C语言中,可以指定结构或者联合中unsigned或者int成员的位数,这些位称为位域。位域在所需要的最小位数内存存储数据,因此,可以更好地利用内存。位域成员必须声明为int或者unsigned。

4.1 位域的声明

struct bitCard
{unsigned face : 4;unsigned suit : 2;unsigned color : 1;
};

该定义包含3个unsigned位域,即face、suit和color,用于表示52张纸牌中的一张纸牌。在unsigned或者int成员名称的后面加入冒号和表示位宽度的整数常量(也就是存储成员所需的位数),就可以声明位域。表示宽度的常量必须是0和系统上存储int的总位数之间的一个整数。

struct example
{unsigned a : 13;unsigned   : 19;unsigned b : 4;
};

如上面的例子,我们也可以指定没有命名的位域,其中的字段可以用来做填充内容。上面例子中没有命名的19位作为填充内容,在这19位中不能存储任何东西,只是用来保证成员b存储在另一个存储单元(这里讨论的是4字节的机器)。

struct example
{unsigned a : 13;unsigned   : 0;unsigned b : 4;
};

宽度为0的无名位域可以用于在新的存储单元边界上对齐下一个位域。上面的例子中使用没有命名且宽度为0的位域来跳过存储a的存储单元中的剩余位(无论有多少),这使得b和下一个存储单元边界对齐。

4.2 使用位域的好处和缺点

因为位域没有地址,所以不能用&来获得位域的地址,另外,也不能像数组元素那样访问位域内的单个位。尽管位域可以节省空间,但是它们会使编译器产生执行速度较慢的机器语言代码。因为机器语言代码与需要额外的步骤来访问可寻址存储单元中的部分。这是一种典型的时间换空间的实现。

4.3 例子

#include<stdio.h>
struct bitCard
{unsigned face : 4;unsigned suit : 2;unsigned color: 1;//1 bit
};
typedef struct bitCard Card;
void fillDeck(Card *const wDeck);
void deal(const Card * const wDeck);int main()
{Card deck[52];fillDeck(deck);deal(deck);return 0;
}
void fillDeck(Card *const wDeck)
{int i;for (i=0;i<=51;i++){wDeck[i].face=i%13;wDeck[i].suit=i/13;wDeck[i].color=i/16;}
}
void deal(const Card * const wDeck)
{int i;for (i=0;i<=51;i++)printf("Card:%2d Suit:%2d Color:%2d%s",wDeck[i].face,wDeck[i].suit,wDeck[i].color,(i+1)%3?" | ":"\n");printf("\n");
}

运行结果:


5、枚举

C语言提供的最后一个用户自定义类型称为枚举。由关键字enum声明的枚举是用标识符表示的一组整数常量。实际上,这些枚举常量是可以自动设置值的符号常量。枚举中的值从0开始,每次增加1(除非特别指定)。

5.1 枚举声明

如声明enum months{JAN,FEB,MAR,APR,MAY,JUN,JUL,AUG,SEP,OCT,NOV,DEC};创建了一个新类型enum months,其中标识符设置为整数0到11。如果要记录月份1-12,可以采用enum months{JAN=1,FEB,MAR,APR,MAY,JUN,JUL,AUG,SEP,OCT,NOV,DEC};,第一个值被显示地设置为1,后续的其余值从1开始累加,从而产生1到12的值。枚举中的标识符必须是唯一的。通过标识符赋值,可以明确地在定义中设置枚举的每个常量值。枚举的多个成员可以具有相同的常量值。

5.2 例子

#include <stdio.h>
//声明一个枚举类型
enum months {JAN=1,FEB,MAR,APR,MAY,JUN,JUL,AUG,SEP,OCT,NOV,DEC};
int main()
{//定义一个枚举变量enum months month;const char *monthName[]={"*Error*","January","February","March","April","May","June","July","August","September","October","November","December"};for (month=JAN;month<=DEC;month=(enum months)(1+(int)month)){printf("%2d %10s%c",month,monthName[month],month%2?'\t':'\n');}return 0;
}

运行结果:


好了,又over了一篇。尽管充其量只能算是笔记,我还是像耐着性子把它们写完,就算是锻炼心态了。

参考:http://blog.csdn.net/jiangnanyouzi/article/details/3158702和<C语言程序设计经典教程>

C语言结构、联合、位操作、位域和枚举相关推荐

  1. C语言结构联合位字段知识体系总结大学霸IT达人

    C语言结构联合位字段知识体系总结大学霸IT达人 C语言的基础类型中只能去定义单一类型的变量用于指代数据,但在现实生活中我们常常要处理的数据却会包含多种类型的数据. 例如,公司员工的信息管理,每个员工的 ...

  2. 自定义类型:结构体、位段、枚举、联合 ------- C语言

    C语言中,7可以存放再整型变量中,'c' 可以存放在字符型变量中,3.14可以存放在浮点型变量中,一个学生有:姓名.年龄.性别.学号等.C语言中是否有这样一个可以存放学生的类型呢?答案是肯定的,这一种 ...

  3. 详解C语言自定义类型(结构体,位段,枚举,联合)

    C语言中有许多类型,比如整形int,字符型char,双精度浮点型double等等.这些类型可以存放一些值或者字符.但是如果我想要一种类型存放一本书,显然是没有的,那么这时候就需要自定义类型了,也就是结 ...

  4. C语言结构体的大小 — — 内存对齐和位域

    C语言结构体对齐 C语言结构体对齐也是老生常谈的话题了.基本上是面试题的必考题.内容虽然很基础,但一不小心就会弄错.写出一个struct,然后sizeof,你会不会经常对结果感到奇怪?sizeof的结 ...

  5. c语言结构体加联合,C语言:结构体和联合体(共用体)

    结构体:struct 1.结构体变量的首地址能够被其最宽基本类型成员的大小所整除. 2.结构体每个成员相对于结构体首地址的偏移量(offset)都是成员的整数倍. 3.结构体的总大小为结构体最宽基本类 ...

  6. C语言基础12——自定义类型。结构体、位段、枚举、联合体。通讯录

    目录 结构体 结构体的声明 结构体自引用 结构体变量的定义.初始化以及访问 结构体内存对齐 结构体传参 位段 枚举 枚举是什么? 枚举的声明 枚举的优点 枚举类型的大小 联合体 联合体类型的定义 联合 ...

  7. c语言枚举和结构体的区别,全面了解结构体、联合体和枚举类型

    一. 结构体: 1. 定义结构体类型: struct 结构体 { 任意类型 任意变量: 任意类型 任意变量: -- }: 注意:这不是定义变量,而是自定义一种类型而已. 如 struct studen ...

  8. C语言 结构体 联合体 | 嵌套使用

    一.简单的实例分析 题目:获取0x12345678各个字节. 答案: //方法一: #include <stdio.h>typedef unsigned int uint32_t; typ ...

  9. c++ new一个结构体_C语言结构体,又一个纸老虎,纯干货讲解(附代码)

    来源:网络,排版整理:晓宇 微信公众号:芯片之家(ID:chiphome-dy)结构体的定义结构体(struct)是由一系列具有相同类型或不同类型的数据构成的数据集合,也叫结构.结构体和其他类型基础数 ...

  10. C语言-结构体内存对齐

    C语言结构体对齐也是老生常谈的话题了.基本上是面试题的必考题.内容虽然很基础,但一不小心就会弄错.写出一个struct,然后sizeof,你会不会经常对结果感到奇怪?sizeof的结果往往都比你声明的 ...

最新文章

  1. 好看的html导航栏作品,精选10款超酷的HTML5/CSS3菜单
  2. 进阶丨如何让你的数据分析更加简洁专业
  3. python dataframe删除某一列_怎样用Python进行数据分析
  4. 算法题目——被围绕的区域(dfs,bfs)
  5. Oracle连接字符串记录
  6. ami编码设计流程图_专用设备转向系统电控单元设计
  7. JAVA设计模式Design Pattern→单例模式Singleton Pattern、工厂模式Factory Pattern、代理模式Proxy Pattern
  8. iOS9新系统下App Store应用上传新指南
  9. Android入门之login设计
  10. 【 Codeforces Round #301 (Div. 2) D】Bad Luck Island【概率DP】
  11. android中屏幕保护的实现的,Android 屏幕保护程序制做及源码
  12. Avalondock 第四步 边缘停靠
  13. 开源引导框架升级发布、懒人神器灰度源码下载
  14. java 给图片加水印整理:2种方法
  15. Backstepping反步法控制四旋翼无人机(2)
  16. Python入门笔记3
  17. vue路由守卫死循环及next原理解释
  18. 人工智能大作业——五子棋
  19. 「Python条件结构」使用if结构实现密码验证
  20. python读取txt文档判断某一个值是否为nul_Python如何读取以NUL分隔的行的文件?

热门文章

  1. 提高订单成交率的九大技巧,你还不知道吗?
  2. 移动电影院迎来2.0版本,5部影片在移动电影院上举办“首映礼”
  3. Security and Communication Networks 论文投稿
  4. 网线制作方法-RJ45
  5. 《你可以不平凡》-- 周杰伦在北京大学百年讲堂的演讲
  6. ChatGPT指令大全(建议收藏)
  7. 数字货币钱包 HD Wallet的助记词和种子的生成原理(BIP39)
  8. 有什么好的学编程的网站或者是软件?『编程入门』?
  9. Useing rvm
  10. c语言大学生自学网教学视频,6个质量最高的自学网站,悄悄的提成能力!