前面两篇基本把指针给介绍完了,相信你们对指针已经不是那么陌生了。也不会由于指针和数组之间的关系而致使混淆了。你们可能也火烧眉毛想了解下后来的知识。今天咱们就介绍下结构体。数组
对于结构体,既然叫结构体,形象上咱们能够理解其就是一堆数据集合在一块儿造成一个结构。就好比一个学生的信息包括:学号、姓名、班级、年龄等等。这些信息都是属于这个学生的,所以咱们就能够将这些信息统一绑定在一块儿。造成一个学生实体,这里有点C++的味道。咱们学C也仍是有必要这样思考。在咱们周围几乎每同样东西都有它本身的信息或者组成。好比药品,它有什么功效,有什么成分等等都能统一绑定在一块儿造成一个实体,咱们在程序中就能方便的访问这些实体的每个信息或组成。所以,当咱们在设计一个程序的时候,咱们就能把一些具备共同特性或者组成元素集合到一块儿构成一个结构体。好比咱们的学生就能够写成:ide
struct SStudent模块化
{函数
char name[ 13 ]; // 姓名工具
char className[ 16 ]; // 班级名spa
char age; // 年龄设计
....指针
};调试
这样一来,学生这个活生生的实体就把全部关于他的信息集中在一块儿了。这样就能集中管理了,里面的每个信息就能经过结构体变量来访问。先看看怎么访问:对象
C语言:
struct SStudent student;
student.age = 22;
C++:
SStudent student;
student.age = 22;
从上面能够看出要访问一个结构体成员是很方便的,同时也体现了实体的概念。咱们将学生实体的年龄信息取出来赋值为22岁。就好像在使用某个东西的某个功能同样。这也是众多面向对象语言的一种思想。就是将程序数据封装话、结构化,咱们要操做一个数据就跟现实生活中的使用某个工具的某个功能同样。咱们看到上面C和C++版本访问惟一不一样的就是C++版本在声明结构体变量的时候不须要在前面加上struct关键字,我的以为后来C++以为struct没有必要再写了吧,麻烦!省略了不是更好!在语法和意义上两个版本是相同的。
结构体还能够不须要名字,好比:
struct
{
char age;
char name[ 16 ];
}student, stu[ 10 ];
这里这个结构体就省略了名字,后面的student并非名字,而是结构体变量。这种就是匿名结构体。跟普通的没有什么区别,后面的stu就是一个结构体数组,普通结构体定义也能够在声明结构体的时候紧跟着就声明变量的。只是这样你要定义其它变量就麻烦了,呵呵!这种通常用得比较固定或者就用这么一次就能够不要名字。
再来看看结构体别名。所谓别名就是可使用另一个名字。
typedef struct SStudent
{
char age;
char sex;
char class;
}STU, *PSTU;
这里的STU就是SStudent结构体的别名,就至关因而另一个名字,使用的时候就能够不用加可恶的struct标识符了。
STU student;
PSTU pStuednt; // 别名为指针类型
好了,结构体就这么简单,就是把不一样类型或者同类型的一些数据集中到一块儿管理,构成一个实体。这个实体也能够理解为结构体。一般这样设计是为了程序的模块化结构化,这样理解起来更容易更接近于现实,计算机原本就是服务于现实的。再好比咱们的链表(将一组数据串联成一个链,咱们能够经过指针访问到这个链中的每个结点,形象的叫着是一个链,本质其实就是一组数据经过指针连接在一块儿,一般存放在内存中是不连续的),举个简单的例子:
struct Node
{
Node* pNext;
char name[ 16 ];
};
这里也是一个结构体,里面包含一个指针和一个名字。假如咱们这个名字就是某个学生的名字,这个结构体咱们就形象当作是一个结点,什么是结点?结点你能够想象我有一条很漂亮的珍珠项链,项链上有不少颗珍珠串联在一块儿,那么每一颗珍珠就能够想象成是一个结点。项链就是由不少个结点串联在一块儿造成的。可能有的读者以为这样比喻却是很容易理解,可是联想到程序里面仍是感受有点抽象。其实也不能说是抽象,我们就想成它就是这么回事。就比如咱们要安装一个工具,注意到这句话里面出现了两个现实生活中的词:“安装”“工具”。在计算机里咱们使用的所谓工具其实都是虚拟化的,这些名字只是为了形象一点,再说安装,也是如此,在现实生活中咱们会在组装或者安装某个零件的时候才会使用这个词,在计算机里使用这个词也是为了你们可以更容易理解形象化罢了。因此咱们没必要太拘泥于叫法。
好,咱们这里定义了一个结构体做为结点,咱们的目的是想把全班全部学生的名字所有串联在一块儿,假如全班有50我的,那么就有50个结点。所以咱们必须的有50个结构体结点来保存这50个学生的名字,并且咱们这50个学生的名字还可以经过循环遍历可以找到其中任意一个。那么咱们就得这样作:
struct Node root; // 根结点(第一个学生结点)
struct Node secSt; // 第2个学生结点
上面咱们定义了2个学生结点,如今把这两个结点连接在一块儿。
strcpy( root.name, "masefee" );
strcpy( secSt.name, "Tim" );
root.pNext = &secSt;
secSt.pNext = NULL;
上面咱们已经把这两个结点连接起来了。root结点的next指针指向的就是secSt,secSt的next指针这里赋值为NULL,若是还想指向下一个学生结点同理。再看看层级关系:
root---|----name ("masefee")
|----pNext---|----name ("Tim")
|----pNext---|----name ... ...
|----pNext ... ...
上面的层级关系很清晰的描述了这些结点的关系,这样就可以成一个链,咱们能够经过遍历找到其中任何一个结点。咱们也称这种存储在内存中为链式存储。其本质就是经过指针将一个一个数据块连接在一块儿。这里我只列举了两个结点。
问题一:咱们怎么将50个结点连接在一块儿?(提示:每一个结点能够malloc申请内存空间)
经过上面的描述,咱们对结构体的用法和概念上有了初步的认识了。再来看看结构体指针(怎么老是离不开指针,呵呵,没办法指针在CC++里原本就是个永恒的主题)。
struct Node stuNode; // struct Node
struct Node* pNode = &stuNode;
strcpy( pNode->name, "masefee" );
上面,咱们定义了一个Node结构体指针,该指针指向了stuNode,最后咱们将stuNode结构体的name拷贝成了“masefee”。一样咱们可使用库函数给申请空间,大小为Node结构的大小:
struct Node* pNode = ( struct Node* )malloc( sizeof( struct Node ) );
这里咱们使用malloc函数给申请了Node结构大小的一块内存,而后让pNode指针指向这块空间。所以咱们就能够向这块内存中写入值了。
strcpy( pNode->name, "masefee" );
pNode->pNext = NULL;
这里的pNext也能够指向下一块申请的内存空间(能够用来回答问题一),这里就不写了,你们要本身摸索才行。
说到这里,不得不说说结构体的对齐问题,什么是结构体对齐,为何要对齐。咱们都知道计算机的内存单位换算都是以2的多少次方来计算的,这样计算是有目的性的。固然是为了计算机的执行效率,你们能够想象一下,假如咱们一个变量的类型占用3字节,一个5字节,一个1字节。计算机在寻址的时候对于这种良莠不齐的内存会下降它的效率。因此一般默认状况下,结构体采用4字节对齐,意思就是说一些不足4字节的变量会可能被扩充到4字节大小或者与其它结构体成员变量进行合并成4字节。这样浪费小小的一点内存效率上会提升不少。这里说到4字节,固然就有8字节,16字节,1字节,2字节对齐了。咱们这里就默认谈谈4字节对齐,其它都是同理的。先举个例子:
struct Align
{
char age;
int num;
};
sizeof( struct Align ) = ?
这里求sizeof的结果咱们获得的确是8,而不是咱们想要的5。这里是8的缘由是默认为4字节对齐,这里char占用1字节,int占用4字节,首先编译器编译的时候遇到char会去寻找周围有没有更多的能够合并的字节,一共合并成4字节,或者合并一部分而后扩充一部分构成4字节,可是这里没有找到,那么age将被扩充到4字节,加上int的4字节,一共被扩充到了8字节。
struct Align align;
align.age = 0xff;
align.num = 0xeeeeeeee;
咱们觉得在内存中分布为:
age num
ff ee ee ee ee
然而:
age num
ff cc cc cc ee ee ee ee
age多出来了3个字节,这里未初始化时填充的是0xcc。假如咱们定义成:
struct Align
{
char age;
char age1;
char age2;
int num;
};
那么age2将被扩充为2字节,age age1 age2合并成3字节再扩充一个字节就组成4字节了。这里sizeof仍是为8字节。再好比:
struct Align
{
char age;
int num;
char age1;
};
这样sizeof结果出来将是12字节,缘由也很简单,首先在编译age的时候,查找挨着没有能合并成4字节的成员,那么就会扩充成4字节,age1同理,假如age为0xff,num为0xeeeeeeee,age1为0xaa,内存分布就为:
ff cc cc cc ee ee ee ee aa cc cc cc
问题二:这里为何不将age和age1分别扩充为2字节而后再合并成4字节,结构体一共8字节?
再举个例子。
struct Align
{
char age;
double num;
char age1;
};
这里的sizeof将是24字节,缘由就是结构体对齐仍是有标准的,假如默认是4字节对齐,常理这里彻底能够将age和age1分别扩充成4字节,整个结构体16字节。可是编译器并无这么作,而是都扩充成了8字节,这是由于结构体在处理对齐问题的时候,都是以最大的基本类型数据成员为标准进行对齐(注意这里是基本数据类型)。假如:
struct SStudent
{
int a[ 2 ];
}
struct Align
{
char age1;
struct SStudent stu;
};
这个Align结构体一样仍是12字节,而不是16字节。
再好比:
struct SStudent
{
char a[ 13 ];
};
struct Align
{
char age1;
struct SStudent stu;
};
问题三:上面程序中Align结构体的大小是多少?为何?
一样再来看看结构体指针和任意指针强制类型转换。
typedef unsigned char byte;
struct SStudent
{
byte age;
byte sex;
byte class;
};
byte array[ 99 ];
struct SStudent* pStu;
array[ 0 ] = 0xaa;
array[ 1 ] = 0xbb;
array[ 2 ] = 0xcc;
pStu = ( struct SStudent* )array;
上面这段程序,咱们将array的前3个元素赋值为0xaa,0xbb,0xcc。这样作的目的是想看看咱们强制类型转换事后,pStu结构体指针的pStu[ 0 ]三个成员是否就是array数组的前3个成员。答案是确定的,你们能够本身调试监视看。这个array数组强制类型转换过去后,pStu[ 0 ], pStu[ 1 ], ... , pStu[ 31 ], pStu[ 32 ]。一共就有33个结构体数据块。一样pStu++相似的加减及累加都会跳跃SStudent结构体大小个字节。跟前面一篇提到的原理同样。
在C++中结构体发生了翻天覆地的变化,跟C的结构体很大差异,这里暂时不说了。等咱们说了函数的时候再谈C++的结构体。不过本文提到的结构体相关在C++中一样有效。
在结构体中不少时候会用到位域,这里暂时不说,先留个思路在这里。等咱们专门谈位运算的时候再来详细说明。
好了,本文就介绍到这里,仍是一些比较初级的问题。只为了你们加深理解。与更多的东西结合着用。才能使用除更灵活的方法。加油!
【C++语言入门篇】系列: