当前位置:小鱼儿玄机二站 > 编程应用 > 第二片段第六课,初阶深入分析C语言编制程序中

第二片段第六课,初阶深入分析C语言编制程序中

文章作者:编程应用 上传时间:2019-09-03

初步剖析C语言编程中的结构体,剖析c语言结构

C语言结构体,可谓是C强大功能之一,也是C++语言之所以能衍生的有利条件,事实上,当结构体中成员中有函数指针了后,那么,结构体也即C++中的类了。

C语言中,结构体的声明、定义是用到关键字struct,就像联合体用到关键字union、枚举类型用到enum关键字一样,事实上,联合体、枚举类型的用法几乎是参照结构体来的。结构体的声明格式如下:

struct tag-name{

{

member 1;

…

member N;

};

因此,定义结构体变量的语句为:struct tag-name varible-name,如struct point pt;其中,point 为tag-name,pt是结构体struct point变量。当然,也可以一次性声明结构体类型和变量,即如下:struct tag-name {…} x,y,z;就类似于int x,y,z;语句一样。也可以在定义结构体变量时即赋初值,即变量初始化,struct point pt={320,200};

当然,也就可以有结构体指针、结构体数组了。访问结构体变量中的member的方法有:如果是由结构体变量名来访问,则是structure-variable-name.member;如果是由结构体变量指针来访问,则是structure-variable-pointer->member;

好了,上面的不是重点,也不难掌握,只是细节问题。结构体具有重要的应用,如下的:

如自引用的结构体,常用来作为二叉树等重要数据结构的实现:假设我们要实现一个普遍的问题的解决算法——统计某些输入的各单词出现的频数。由于输入的单词数是未知,内容未知,长度未知,我们不能对输入进行排序并采用二分查找。……那么,一种解决办法是:将已知的单词排序——通过将每个到达的单词排序到适当位置。当然,实现此功能不能通过线性排序,因为那样有可能很长,相应地,我们将使用二叉树来实现。该二叉树每一个单词为一个二叉树结点,每个结点包括:

  • a pointer to the text of the word
  • a count of the number of occurences
  • a pointer to the left child node
  • a pointer to the right child node

其写在程序中,即:

struct tnode{/*the tree node:*/

char *word;/*points to the next*/

int count;/*number of occurences*/

struct tnode *left;/*left child*/

struct tnode *right;/*right child*/

}

完成上述功能的完整程序如下:

#include<stdio.h> 
#include<ctype.h> 
#include<string.h> 
#include"tNode.h" 

#define MAXWORD 100 
struct tnode *addtree(struct tnode *,char *); 
void treeprint(struct tnode *); 
int getword(char *,int); 


struct tnode *talloc(void); 
char *strdup2(char *); 


/*word frequency count*/ 
main() 
{ 
  struct tnode *root; 
  char word[MAXWORD]; 

  root=NULL; 
  while(getword(word,MAXWORD)!=EOF) 
    if(isalpha(word[0])) 
      root=addtree(root,word); 
  treeprint(root); 
  return 0; 
} 

#define BUFSIZE 100 
char buf[BUFSIZE];/*buffer for ungetch*/ 
int bufp=0;/*next free position in buf*/ 

int getch(void)/*get a (possibly pushed back) character*/ 
{ 
  return (bufp>0)? buf[--bufp]:getchar(); 
} 

void ungetch(int c)/*push back character on input*/ 
{ 
  if(bufp>=BUFSIZE) 
    printf("ungetch:too many charactersn"); 
  else 
    buf[bufp++]=c; 
} 

/*getword:get next word or character from input*/ 
int getword(char *word,int lim) 
{ 
  int c,getch(void); 
  void ungetch(int); 
  char *w=word; 

  while(isspace(c=getch() )); 

  if(c!=EOF) 
    *w++=c; 
  if(!isalpha(c)){ 
    *w=''; 
    return c; 
  } 
  for(;--lim>0;w++) 
    if(!isalnum(*w=getch())){ 
      ungetch(*w); 
      break; 
    } 
  *w=''; 
  return word[0]; 
} 


/*addtree:add a node with w,at or below p*/ 
struct tnode *addtree(struct tnode *p,char *w) 
{ 
  int cond; 

  if(p==NULL){/*a new word has arrived*/ 
    p=talloc();/*make a new node*/ 
    p->word=strdup(w); 
    p->count=1; 
    p->left=p->right=NULL; 
  }else if((cond=strcmp(w,p->word))==0) 
    p->count++;/*repeated word*/ 
  else if(cond<0)/*less than into left subtree*/ 
    p->left=addtree(p->left,w); 
  else  /*greater than into right subtree*/ 
    p->right=addtree(p->right,w); 
  return p; 
} 
/*treeprint:in-order print of tree p*/ 
void treeprint(struct tnode *p) 
{ 
  if(p!=NULL){ 
    treeprint(p->left); 
    printf("%4d %sn",p->count,p->word); 
    treeprint(p->right); 
  } 
} 

#include<stdlib.h> 
/*talloc:make a tnode*/ 
struct tnode *talloc(void) 
{ 
  return (struct tnode *)malloc(sizeof(struct tnode)); 
} 


char *strdup2(char *s)/*make a duplicate of s*/ 
{ 
  char *p; 

  p=(char *)malloc(strlen(s)+1);/*+1 for ''*/ 
  if(p!=NULL) 
    strcpy(p,s); 
  return p; 
} 

其中,其它的关于union、enum这里就不多说了,再说一个关于结构体的非常重要的应用——位操作:

当然,我们知道,对于位操作,我们可通过#define tables(即用宏和C中的位操作来实现)

如:

#define KEYWORD 01 /*0001*/

#define EXTERNAL 02 /*0010*/

#define STATIC 04   /*0100*/

enum{KEYWORD =01,EXTERNAL =02,STATIC =04};

那么,flags|=EXTERNAL|STATIC;将打开flags的EXTERNAL和STATIC位,而

flags&=~(EXTERNAL|STATIC);将关闭flags的EXTERNAL和STATIC位.

然而,上述定义的位模式可以用结构体如下写:

struct{

unsigned int is_keyword:1;

unsigned int is_extern:1;

unsigned int is_static:1;

}flags;/*This defines a variable called flags that contains three 1-bit fields*/

那么,上述打开相应位的操作为:

flags.is_extern=flags.is_static=1;

上述关闭相应位的操作为:

flags.is_extern=flags.is_static=0;

图片 1

您可能感兴趣的文章:

  • C语言中的结构体的入门学习教程
  • 浅谈C语言中结构体的初始化
  • 深入分析C语言中结构体指针的定义与引用详解

C语言结构体,可谓是C强大功能之一,也是C++语言之所以能衍生的有利条件,事实上,当结...

-- 简书作者 谢恩铭 转载请注明出处

上一课C语言探索之旅 | 第二部分第五课:预处理之后,我们进入令人激动也非常有意思的一课。

众所周知,C语言是面向过程的编程语言,与Java,C#等面向对象的编程语言有所不同。

在面向对象的编程语言中,有类的概念。

C语言是没有类这种“类型”的,但是C语言就不能“模拟”面向对象编程了吗?

不,只要你设计得好,C语言也可以模拟面向对象编程。

这一课我们学到的关于struct的知识就可以使你有能力用C语言实现"面向对象"。

前面我们学习了指针,数组,字符串和预处理,掌握这些知识你的C语言水平已经还不错啦,但是我们岂能就此止步。必须Bigger than bigger~

C语言还可以让我们做一些更厉害的事情:创建你自己的变量类型

我们可以将其称为“自定义的变量类型”,我们来看三种:struct,union和enum。

因为当你需要编写的程序比较复杂的时候,你会发现创建自定义的变量类型是很重要的。

幸好,这学起来其实也不是特别难。但是大家需要专心学习这一课,因为从下一课开始,我们会一直用到struct了。

什么是struct呢?

首先它是英语structure的简写, 所以struct的专业术语是“结构体”。

定义:struct就是一系列变量的集合,但是这些变量可以是不同类型的

这个定义是不是唤起了大家对我们的老朋友 -- 数组的怀念啊。数组里面的每个成员都必须是同一个类型的,相比之下struct更灵活。

一般来说,我们习惯把struct定义在.h头文件中,也就是和预处理命令以及函数原型那群"家伙"在一起。

下面就给出一个struct的例子:

struct 你的struct的名字{ char variable1; short variable2; int otherVariable; double numberDecimal;};

可以看到:struct的定义以关键字struct开始,后面接你自定义的struct的名称(比如 Dog,Cat,Person等)。

一般来说,在我的代码里,我的struct的命名也是遵照变量的命名规则,唯有一点不一样,就是struct的名称我会将首字母大写,例如:SchoolName。

但是我的普通变量一般都是首字母小写,例如:studentNumber。

这样做只是个人习惯,便于在代码里区分普通的变量和自定义的变量,之后会学到的enum和union,我也是习惯将其名称的首字母大写。

在struct的名字之后,我们需要写上一对大括号,在这对大括号里面放你的struct要包含的各种类型的变量。

通常说来,struct的大括号内至少得定义两个变量吧,如果只有一个变量,那定义这个结构体也没什么意义。

注意:特别不要忘了,在大括号后面还要加上一个分号;因为毕竟这个struct是一个变量,变量的定义最后都要加分号的。

如你所见,创建一个自定义的变量也不复杂么。其实结构体就是各种基本类型变量的集合,一个"大杂烩"。

当然以后的课程中我们还会看到:结构体的嵌套定义(结构体里包含另一个结构体)。

假设我们需要自定义一个结构体,它储存屏幕上的一个点的坐标。在之后第三部分“用C语言编写游戏”里面,会用到类似的结构。

下面就给出2D(D是英语dimension的首字母,表示维度)世界的坐标系的大致印象:

图片 2

当我们在2D世界中做研究时,我们有两个轴:

  • 横坐标轴(从左到右,一般也称为x轴)
  • 纵坐标轴(从下到上,一般也称为y轴)

只要数学还没有还给小学体育老师,应该都知道x和y轴的。

现在,你自己可以写出一个名叫Coordinate(英语“坐标”的意思)的struct的定义了吗?我看好你!

可以自己先写,然后对一下我们给出的参考答案:

struct Coordinate{ int x; // 横坐标 int y; // 纵坐标};

我们的Coordinate这个struct包含了两个变量:x和y,都是int类型,分别表示横坐标值和纵坐标值。

当然了,如果我们愿意,也可以创建一个表示3D空间的点的struct,只需要在刚才的Coordinate这个结构体的基础上加上z轴。

结构体里面也可以存放数组

可以构造一个名叫Person的结构体,如下所示:

struct Person{ char firstName[100]; // 名 char lastName[100]; // 姓 char address[1000]; // 地址 int age; // 年龄 int boy; // 布尔值 : 1 = boy, 0 = girl};

可以看到,这个结构体变量包含五个基本的变量。

前三个分别表示 名,姓和地址,是字符数组。

第四个是年龄,第五个是一个“布尔值”(当然,C语言本身没有定义布尔类型(true或false),但是可以用数值来“表示”布尔值的真或假),boy这个int变量的值如果为1,那就是表示男孩;如果为0,那就是女孩。

这个结构体可以用于构建一个通讯录程序。当然,你大可以在这个结构体里再添加其他变量,使其更完善。

只要内存够,一般来说在一个结构体里没有变量的数目限制。

现在,我们的结构体已经定义在.h头文件里了,那么我们就可以在include此头文件的文件中使用这些结构体了。

以下展示如何创建一个类型为Coordinate(此结构体我们上面定义了,表示二维空间的坐标)的变量:

#include "main.h" // 包含结构体定义的头文件int main(int argc, char *argv[]){ struct Coordinate point; // 创建一个Coordinate类型的变量,名字是point return 0;}

如上,我们创建了一个Coordinate类型的变量,名字是point。

这个变量自动拥有两个子变量:x和y,都是int类型,分别表示此二维坐标的横坐标值和纵坐标值。

你也许要问:“创建结构体变量开头的那个关键字struct是必须的吗?”

是的,必须的。“爱我,非你莫属...” 不好意思又跑题了...

struct关键字使电脑能够区分基础变量类型和自定义变量类型(例如Coordinate)。

然而,每次加struct关键字也有点麻烦,所以聪伶的C语言开发者们设计了typedef关键字。

当然了,人类的大多数发明都是为了“懒惰”的缘故,能提高效率谁不想啊。就是这个feel,倍爽儿。

重新回到刚才定义Coordinate这个结构体的.h头文件中。我们来加一条由typedef开头的命令,目的是为Coordinate结构体创建一个别名。

什么是别名呢?

就是比如有一个人,真实姓名叫王小明,别名就可以是小明,明明,等,但都代表那个人。

有点类似C++的引用的机制。

所以对别名的操作就是对原先对象的操作。

比如小时候你上课不乖,老师点名的时候,点到你的小名或者你的真实名字,都是叫的你,你就得去罚站。

我们就在Coordinate结构体的定义之前加这句命令吧,一般习惯加在后面的,但是加在前面也可以:

typedef struct Coordinate Coordinate;struct Coordinate{ int x; int y;};

可以看到,我们新加了一行命令:

typedef struct Coordinate Coordinate;

为了更好地理解这句命令的作用,我们把它拆为三部分来看:

  1. typedef:说明我们将要创建一个别名
  2. struct Coordinate:这是我们要为其创建别名的结构体
  3. Coordinate:这就是别名

所以,上面这句命令的含义就是“从今以后,Coordinate就相当于struct Coordinate”。

这样做以后,我们就可以不用每次在创建一个新的Coordinate结构体的变量时都加上struct关键字了。

所以,我们的.c文件中就可以改写为:

int main(int argc, char *argv[]){ Coordonnees point; // 因为有了typedef,电脑就清楚地知道此处的Coordinate其实就是"struct Coordinate" return 0;}

当然,不一定要都叫Coordinate,别名也可以叫:Coor。例如:

typedef struct Coordinate Coor;struct Coordinate{ int x; int y;};Coor coor; // 创建一个结构体变量

建议大家在平时定义了struct类型后,也加一句typdedef命令,这样在代码里就不用每次新建一个此类型的变量时都要在开头写struct关键字了。

很多程序员都会这么做。因为一个好的程序员是懂得如何偷懒的程序员,这和一个懒惰的程序员是有区别的。我们要使代码“write less,do more”(用尽量少的代码做更多的事)。

当然,上面的代码块可以简写为:

typedef struct 名字{ // struct的内容} 别名;

所以上面Coordinate的代码块可以简写为:

typedef struct Coordinate{ int x; int y;} Coordinate;

注意:之后我们的示例代码,有时会出现例如

Person player1;

这样的形式,那就是假定我们之前已经用了typedef了:

typedef struct Person Person;

就可以省略开头的struct关键字,不需要再写成:

struct Person player1;

既然我们的point变量(是Coordinate类型的,希望大家还没晕)已经创建好了,那我们就可以修改它的成员的值了。

我们如何访问point的两个成员x和y呢?如下所示:

int main(int argc, char *argv[]){ Coordinate point; point.x = 10; point.y = 20; return 0;}

这样,我们就顺利地修改了point的两个成员的值,使其x坐标为10,y坐标为20。

因此我们的点就位于坐标系的处了。

所以,为了能访问到结构体的某个成员,我们可以这样做:

结构体实例名称.成员名

中间的点表示“从属”关系。

如果有面向对象编程基础的朋友,就会觉得:这与“类和对象”也太像了吧。

是的,其实我们可以用struct来“模拟”类。

如果我们用之前创建的Person这个结构体来举例子的话:

int main(int argc, char *argv[]){ Person user; // user是英语“用户”的意思 printf("您姓什么 ? "); scanf("%s", user.lastName); printf("您名叫什么 ? "); scanf("%s", user.firstName); printf("原来你的名字是 %s%s,失敬失敬n", user.lastName, user.firstName); return 0;}

运行输出:

您姓什么?王您名叫什么?小明原来您的名字是 王小明,失敬失敬。

我们把user.lastName传给scanf,使得用户输入的值直接修改user的lastName成员;我们对user.firstName也是如此。

当然我们也可以再添加对address,age,boy的赋值。

当然了,你也许会说:“我不知道结构体的使用,我用两个单独的字符串变量lastName和firstName不是也可以做到和上述程序相同的事么?”

是的,但是用结构体的好处就是我们可以创建此结构体的变量,将很多相关联的数据封装在一起,成为一个整体,而不是零散地定义。

比如定义了Person这个结构体之后,凡是用Person来创建的变量,里面都自动包含了 lastName,firstName,address,age和boy这五个变量,非常方便。

比如我们可以这样创建:

Person player1, player2; //之前肯定有用typedef( typedef struct Person Person;)

在player1和player2中都包含lastName,firstName,address,age和boy这五个变量。

我们也可以更“偷懒”一些:创建结构体数组。例如:

Person player[2];

这样,我们就可以很方便的访问player[1]当中的变量了,例如:

player[1].lastName = "xiaoming";

用结构体数组的好处是可以方便地使用循环,等等。

创建一个名叫ProgrammerLeague的结构体,在定义里放入你想创建的变量。然后创建此结构体的一个数组,用循环的方式给变量赋值,再用循环的方式打印出其中变量的信息。

之前的课程里,我们建议对于基本变量,数组和指针,最好在创建的时候对其初始化。结构体也不例外。

初始化有一个很大的好处,就是避免此变量里存放“任意数据”。

事实上,一个变量在创建时,如果没有初始化,它会取当时在内存那个位置所存的值,所以这个值的随机性是很大的。

我们来回忆一下,不同变量的初始化应该怎么做:

  • 基础变量(int,double,char等):初始化为0

  • 指针:初始化为NULL。事实上,NULL位于stdlib.h标准库头文件中,是用#define预处理命令定义的一个常量。它的值通常是0。虽然是0,但是有多种定义形式,例如:

#define NULL 0#define NULL 0L#define NULL  0)

但是我们只要每次用NULL就好了,为了清楚表明这是指针变量,而不是一般变量。

  • 数组:将每一个成员变量初始化为0

那么对于我们的"朋友" -- 结构体,我们怎么初始化呢?

其实结构体的初始化也很简单,与数组的初始化很类似。我们可以像下面这样定义:

Coordinate point = {0, 0};

这样,我们就依照顺序将point.x和point.y都初始化为0了。

对于像Person这样的结构体,里面的变量类型有 char型数组和int,那么我们可以将char型数组初始化为""。

我们可以像这样初始化一个字符串,在C语言探索之旅 | 第二部分第四课:字符串那一课忘记提了。

不过,我想现在提还不算晚吧。

所以我们就可以这样来初始化我们的Person结构体变量:

Person player = {"", "", "", 0, 0};

然而,我们也可以这样来初始化一个结构体变量:创建一个函数,比如叫initializeStruct,可以为每一个传递给它的结构体做初始化,这样就方便很多,特别是当结构体中的变量很多时。

之前指针那一章我们也已经学了,如果我们对函数传递普通变量,那么因为C语言的函数参数传递方式是值传递,所以它会对传给它的函数参数做一份拷贝,这样函数里面修改的其实是那一份拷贝,真正的实参并没有被改变。

为了让实参实实在在被修改,我们需要用到指针,也就是传递此变量的地址。

对于结构体,也需要这样。因此,接下来我们就来学习如何使用结构体指针。开始要有点难咯。准备好了吗?

结构体指针的创建其实和普通的指针变量创建没什么区别。例如:

Coordinate *point = NULL;

上面的代码就创建了一个叫做point的Coordinate结构体指针变量(Coordinate是我们上面定义的表示坐标的一个结构体)。

我们再来提醒一次:

一般推荐写成:

Coordinate *point = NULL; // 星号挨着指针变量名字

而不推荐写成:

Coordinate* point = NULL; // 星号挨着结构体名,这种写法不好!

在指针的创建中,我们推荐第一种写法。

因为用第二种写法,如果你在一行上创建好几个指针变量时,会容易忘记在第二个之后的变量前加*号。例如,容易写成这样:

Coordinate* point1 = NULL, point2 = NULL; // 编译会出错

但这样编译会出错,因为point2其实是Coordinate结构体变量,而不是Coordinate结构体指针变量!

所以我们建议这样写:

Coordinate *point1 = NULL, *point2 = NULL;

在以前的课程中,对于基础类型的指针变量,我们也是这样建议:

int *number1 = NULL, *number2 = NULL;

特别是int型的指针,还很不容易察觉到错误,如果写成:

int* number1 = NULL, number2 = NULL;

编译器是不会报错的。因为NULL的值就是0,可以赋给number2这个int型变量(注意:上面的number2不是int指针)。

好吧,回顾总是很好的(伤心总是难免的...)。

这里,我们主要来学习如何将一个结构体指针(为什么是传结构体指针而不是传结构体,可以看之前的解释)传给一个函数,使得函数内部可以真正修改此结构体。

我们来看一个实例:

#include <stdio.h>typedef struct Coordinate // Coordinate是英语“坐标”的意思{ int x; // 横坐标值 int y; // 纵坐标值} Coordinate;void initializeStruct(Coordinate *point); // 函数原型int main(int argc, char *argv[]){ Coordinate myPoint; initializeCoordinate(&myPoint); // 函数的参数是myPoint变量的地址 return 0;}// 用于初始化结构体变量void initializeCoordinate(Coordinate *point){ // 放置结构体初始化的代码}

上面的initializeCoordinate函数体内,我们将放置初始化结构体的成员变量的代码。

我们按顺序来看一下这段代码:

  • 首先,我们定义了一个结构体,叫做Coordinate,里面包含两个变量,x和y。

  • 我们在main函数中创建了Coordinate结构体的变量,名字叫myPoint。

  • 我们将myPoint的地址传递给initializeCoordinate这个函数。

  • 接下来,我们就在initializeCoordinate函数中添加初始化x和y变量的代码吧:

void initializeCoordinate(Coordinate *point){ *point.x = 0; *point.y = 0;}

point前面的*号是不可以少的噢,因为,传进函数的参数是一个结构体指针,我们要取到此结构体,就需要用到“解引用”符号:星号。

但是,认真的读者看出上面这个函数中的错误了吗?

我们的初衷是想要:先用*号解引用point这个结构体指针,取到结构体,然后再用.号取到其中的变量x和y。但是如果按上面的写法,其实效果相当于如下:

* = 0;* = 0;

因为.号的优先级是高于*号的。

有兴趣可以看一下C语言运算符的优先级,不过之前的课我们也说过了,记不清怎么办呢?加括号就解决啦。

上面的代码编译是通不过的,因为结构体指针point并没有成员叫x和y,而且,对于结构体指针我们也不能用.号来取到什么值。

因此,我们需要修改一下。改为如下就可以了:

void initializeCoordinate(Coordinate *point) { .x = 0; .y = 0;}

这样就对了。用括号去掉了运算符优先级的影响。

但是,之前也说过:程序员是懂得偷懒的一群人。

如果每次要取结构体的成员变量都要这么麻烦,要用*号,还要加括号,再用.号。

想想都要让Denis Ritchie老爷子醉了。他是决不允许这种事发生的,因此,他就定义了一个新的符号:

-> (一个箭头。是的,就是这么霸气侧漏)

用法如下:

point->x = 0;

就相当于:

.x = 0;

是不是简便了很多?

记住:这个符号,只能用在指针上面

因此,我们的函数可以改写为:

void initializeCoordinate(Coordinate *point) { point->x = 0; point->y = 0;}

我们在main函数里也可以这样写:

int main(int argc, char *argv[]){ Coordinate myPoint; Coordinate *myPointPointer = &myPoint; myPoint.x = 10; // 用结构体的方式,修改myPoint中的x值 myPointPointer->y = 15; // 用结构体指针的方式,修改myPoint中的y值return 0;}

结构体是C语言中一个非常好用,很重要的概念,希望大家好好掌握。

当然还有不少知识细节就要大家自己去看C语言的经典教材了,例如《C程序设计语言》(不是谭浩强那本《C语言程序设计》!而是C语言作者写的经典之作),《C和指针》,《C专家编程》,《C语言深度解剖》,《C陷阱和缺陷》,等等。

union是术语“联合”的意思,是C语言的关键字,也有的书上翻译为“共用体”。

我们可以来写一个union的例子。

union ProgrammerLeague{ char character; int memberNumber; double rate;};

乍看之下,简直和struct没什么区别么。但是真的没有区别吗?

假如我们用sizeof关键字来测试此union的大小(大小指的是其在内存中所占的字节数,一个字节相当于8个bit:

#include <stdio.h>typedef union ProgrammerLeague{ char character; // 大小是1 int memberNumber; // 大小是4 double rate; // 大小是8} ProgrammerLeague;int main(int argc, char *argv[]){ ProgrammerLeague programmerLeague; printf("此联合的大小是 %lun", sizeof(programmerLeague)); return 0;}

运行程序,输出:

此Union的大小是 8

假如我们对结构体也做一次测试,对比一下:

#include <stdio.h>typedef struct ProgrammerLeague{ char character; // 大小是1 int memberNumber; // 大小是4 double rate; // 大小是8} ProgrammerLeague;int main(int argc, char *argv[]){ ProgrammerLeague programmerLeague; printf("此结构体的大小是 %lun", sizeof(programmerLeague)); return 0;}

运行程序,输出:

此Structure的大小是 16

为什么我们自定义的union的大小是8,而struct是16呢?

这就涉及到union和struct的区别了。

struct的大小是其中所有变量大小的总和,但是你会说:“不对啊, 1+4+8 = 13,为什么sizeof(programmerLeague)的值为16呢?”

好问题!这个有点复杂,涉及到内存对齐的问题,我们以后再说。如果你一定要知道,那时因为内存对齐使得第一个char变量对齐了第二个int变量的空间,也变成了4,如此一来:

4+4+8 = 16

有兴趣的读者可以去参考《C语言深度解剖》的解释。

在嵌入式编程等内存有限的环境下,需要考虑内存对齐,以节省空间。

union的大小等于其中最大得到的值最大)的那个变量的大小。所以我们就知道了,其实union的储存是这样的:

其中的每个变量在内存中的起始地址是一样的,所以union同一时刻只能存放其中一个变量,union的大小等于其中最大的那个变量,以保证可以容纳任意一个成员。

union适合用在很多相同类型的变量集,但是某一时刻只需用到其中一个的情况,比较节省空间。

看完了struct和union,我们最后来学习很常用的一个自定义变量类型:enum。

enum是英语enumeration的缩写,也是一个C语言关键字。

枚举是一个比较特别的自定义变量类型。当初我学C语言时,一开始还真有点不理解。但用得好,却非常实用。

我们之前学了:结构体里面包含了多个可以是不同类型的成员变量(一说“成员”就有点面向对象的感觉 :P。)

咬字要清晰,不是“淳元”皇后那个郑淳元,是成员...

但是enum里面是一系列可选择的值。也就是说每次只能取其中一个值,听着和union有点类似啊。但是和union还是有区别的。

我们来举一个例子就知道区别了:

typedef enum Shape Shape;enum Shape // shape是英语“身材、体型”的意思{ THIN, // thin是英语“瘦”的意思 MEDIUM, // medium是英语“中等”的意思 FAT // fat是英语“胖”的意思};

所以,我们定义了一个名叫 Shape的enum变量。其中有三个值,分别是THIN, MEDIUM和FAT(身材有瘦,中等和胖之分)。不一定要大写,只是习惯。

那我们怎么来创建enum变量呢?如下:

Shape shape = MEDIUM;

shape这个变量,我们在程序里,也可以再将其修改为 THIN或者FAT。

大家看到enum和union以及struct的区别了吗?是的,enum的定义里,每个成员没有变量类型(int,char,double之类)!

很奇怪吧。想起来为什么enum的成员习惯用大写了吗?

对,就是因为enum的每个成员都不是变量,而是常量。但是enum的机制和常量定义以及#define还是有些区别:

像上面的代码:

typedef enum Shape{ THIN, MEDIUM, FAT} Shape;

编译器会自动为其中的每一个成员绑定一个常量值,我们写程序测试一下:

#include <stdio.h>typedef enum Shape Shape;enum Shape{ THIN, MEDIUM, FAT};int main(int argc, char *argv[]){ Shape shape = THIN; printf("THIN = %dn", shape); shape = MEDIUM; printf("MEDIUM = %dn", shape); shape = FAT; printf("FAT = %dn", shape); return 0;}

运行程序,输出:

THIN = 0MEDIUM = 1FAT = 2

看到了吗?编译器自动给这三个成员赋了 0,1和2。如果没有指定enum成员的值,那么它们的值是从0开始,依次加1。

我们也可以自己来定义enum成员的值,不一定要每次让编译器给我们自动分配。

我们可以这样写:

typedef enum Shape{ THIN = 40, MEDIUM = 60, FAT = 90} Shape;

这样,我们就自己给每个成员定义了值。

我们也可以让编译器为我们自动分配几个值,再自己定义几个值,例如:

typedef enum Shape{ THIN, MEDIUM, FAT = 90} Shape;

上面,我们没有为THIN和MEDIUM赋值,那么编译器会给他们赋值为0和1。

而FAT,因为我们已经指定了其值为90,所以FAT就等于90。

是不是觉得enum和用#define来定义的常量是有些类似呢?

其实,还是有些不同的:

  • "#define" 宏常量是在预处理阶段进行简单替换,枚举常量则是在编译的时候确定其值。

  • 一般在编译器里,可以调试枚举常量,但是不能调试宏常量。

  • 枚举可以一次定义大量相关的常量,而#define 宏一次只能定义一个。

  1. 结构体是一种自定义的变量类型,完全由我们自由发挥,自己定制(走的是"高级定制"的路线啊)。与int,double等基础变量类型有所区别。结构体的使用可以使我们的C语言程序更加灵活,可以做更多事。

  2. 结构体里包含成员变量,通常是基础变量类型的变量,如int,double等变量,但也可以有指针变量,数组,甚至其他的结构体变量。

  3. 为了访问到结构体的成员变量,我们可以用普通的结构体方式访问:结构体变量名称.成员变量名(中间用一个"点"连接)。

  4. 我们也可以用特别简便的结构体指针的方式来访问结构体的成员变量:结构体指针变量名->成员变量名(中间用一个"箭头"连接)。

  5. union(“共用体”,或“联合”)和struct的最大不同就是:union的大小是其中容量最大的那个成员变量的大小,而结构体的大小是每一个成员变量的总和。union一次只能取其中一个变量。

  6. enum一次只能取其中的一个成员的值,这一点和union有些类似。但是enum的成员都是常量,而不是变量。而且enum的成员如果没有指定数值,编译器会按照递增顺序为每一个变量赋值,从0开始。

今天的课就到这里,一起加油咯。

下一次我们学习:C语言探索之旅 | 第二部分第七课:文件读写

本文由小鱼儿玄机二站发布于编程应用,转载请注明出处:第二片段第六课,初阶深入分析C语言编制程序中

关键词: