C的结构体和其他数据形式

结构体

声明结构体时其实并未创建真实的数据,也并未给数据分配空间,只是描述了它由哪些成员(member)组成

struct tag {

  ...

};

也忽略tag可以直接声明一个结构体变量,但这个结构体只能使用一次:

struct {

  ...

} var_name;


结构体的初始化,注意结构体中不同值之间用逗号(,)隔开,有两种方法:

1. 按照顺序初始化:

struct book b1 {
   "The Hours",
   "Michael Cunningham",
   3.99
};

2. 通过变量名初始化,可以只初始化某几个变量:

struct book b1 = {
   .value = 3.99,
   .author = "Michael Cunningham",
   .title = "The Hours"};

也可以一次初始化多个结构体,不同结构体之间用逗号隔开

如果是static storage duration,变量只能用常量初始化(其实所有的static storage duration变量都是这样)


结构体可以直接互相赋值,如struct book b2 = b1;

结构体可以作为函数的返回值

结构体可以嵌套,如:

struct name {
   char first[20];
   char last[20];
};
struct person {
   int id;
   struct name pn;
};
struct person tom = { 123132, {"Tom", "Hanks"}};


结构体指针:

struct book* p; p = &b1;

注意,结构体的名字并不是结构体的地址(不像数组)。(*p).title == p->title。这里因为title是一个array,所以其实p->title的结果其实是数组名,也就是这个数组的地址


结构体参与函数运算有两种方式:

1. 使用结构体指针,如:double sum(const struct* book); 这种方式在调用结构体时要使用结构体的地址,不能使用结构体的名字!如:sum(&b1);

2. 将结构体作为参数,如double sum(struct book);

两种方式的比较:

1. 结构体指针

优点:a) 能够在旧的环境下运行 b) 速度快(因为不用复制结构体本身,只需要传递地址)

缺点:对数据的保护不够,可能会改动数据。但是这个问题可以通过将函数形参定义成const来解决

2. 传送结构体作为参数

优点:a) 函数处理的是结构体的副本,可以保护数据 b) 程序style更清晰

缺点:a) 旧的环境可能不支持 b) 速度慢,占用更多的空间,特别是将一个大的结构体传给函数却只用到了其中的一两个member

所以,这种方法通常只用于比较小的结构体


字符数组和结构体中的字符指针

struct names{
   char first[20];
   char last[20];
};
struct pnames{
   char* first;
   char* last;
};

对于names来说,字符串被存储在结构体中;而对panmes来说,字符串被存放在compiler存放字符串常量的位置。pnames没有给字符串分配存储空间,所以它只能处理那些在别处已经被分配了空间的字符串(如字符串常量或字符串数组)。同时,如果给结构体中未初始化的字符指针赋值,如scanf("%s", pn1.last); 程序可能将输入的字符串存储在任何位置,而这可能会导致严重的问题。

如果使用malloc()来存储结构体中的字符串,则可以在结构体中使用char型指针。


复合文字用于结构体(compound literal)

C99开始支持的复合文字(compound literal)除了数组外还支持结构体,如果只需要一个临时结构体的值的话可以使用,如作为实参传入函数(如果函数的形参是一个地址,则需要将compound literal的地址传入,在前面加上"&"即可)或给另一个结构体赋值。语法是将具体的初始值用大括号({})括起,在前面放置用小括号括起的值的类型,例:(struct book) {"The Hours", "Michael Cunningham", 3.99}(参考"C的数组与指针")


Flexible Array Members(C99)

C99支持一个叫做flexible array member的特性,它允许声明一个结构体,这个结构体的最后一个member是一个数组,这个数组有以下特性:

1. 这个数组并不存在,至少不是立刻存在

2. 可以像这个数组存在一样使用它,并且它的元素个数是任意的

声明flexible array member要遵守以下规则:

1. flexible array member必须是结构体的最后一个member

2. 除它本身之外结构体必须要有其他的member

3. flexible array member的声明方式与普通数组相同,但是括号内必须是空的

例:

struct flex {
   int count;
   double average;
   double scores[];   // flexible array member
};

尽管定义了结构体flex,但不能使用数组scores,因为并未给它分配空间。要想使用这个结构体必须声明一个指向它的指针并且使用malloc()为flex的普通内容和flexible array member分配空间。例:

struct flex* pf; 
pf = (struct flex*)malloc(sizeof(struct flex) + 5*sizeof(double));

有flexible array member的结构体一些特殊的处理要求:

1. 例:struct flex* pf1, pf2; 

            ... 

            *pf2 = * pf1; 

这样只能将除flexible array member外其他的普通member赋给*pf2,所以请使用memcpy()函数进行赋值(参考C预处理器和C函数库)

2. 不要将这种结构体用于将结构体作为传入值的函数,原因与1相同;请使用传入结构体地址的函数进行处理

3. 有flexible array member的结构体不能组成数组,也不能作为其他结构体的成员


Anonymous Structure(C11)

anonymous structure是未命名的结构体成员,常用于union嵌套(nested union)

例:

struct name {                                   struct person {

   char first[20];                                   int id;  

   char last[20];                                    struct {char first[20]; char last[20];};

};                                       ==          };

struct person {

   int id;

   struct name pn;

};



Union

union能够在相同的存储空间中存储不同的数据类型(但不是同时),通常的用途是存储许多不规律或预先不知道的数据类型,另一个用途是当结构体中的某个数据的类型是什么取决于其他member时。通过使用union数组可以创建一个单位大小相同的数组,每一个单位都可以存储不同类型的变量,并且每一个单位都能够以其他单位的形式进行访问和读取

union的创建很像结构体,有union模板和union变量,如:

union hold {
   int digit;
   double bigfl;
   char letter;
};

hold这个union可以存储一个int或一个double或一个char型变量(只能储存一个!),编译器会分配能够存储占空间最大的变量所需的空间给一个union变量。union的初始化有四种方法:a) 直接对union中的某个变量进行初始化 b) 使用一个union变量去初始化另一个同类型的union变量 c) 初始化union的第一个元素 d) 使用designated initializer(C99支持)

例:

union hold val_a;
val_a.letter = 'R';
union hold val_b = val_a;
union hold val_c = {88};
union hold val_d = {.bigfl = 118.2};       // designated initializer

以val_a为例,它使用了char类型,但依然可以通过val_a.digit来访问val_a存储的内容(即将以char的形式存储的内容用int型的方式进行访问和读取)


union的使用方式:

1. 使用点(.):union hold fit; fit.digit = 23;

2. 使用"->":union hold* pu; pu = & fit; x = pu->digit;


Anonymous Union(C11)

anonymous union和anonymous structure用法类似,就是说anonymous union是一个结构体或union的未命名member



枚举类型(Enumerated Types)

可以使用枚举类型声明象征性的名字来代表整型常量,通过使用关键词enum可以创建新“类型”并且指明他们可能有的值(实际上,enum型常量就是int型)。使用枚举值的目的是增强程序的可读性,它的语法和结构体相似:

enum spectrum {red, orange, yellow, green, blue, violet};

enum spectrum color;

"red"等这些象征性的常量叫做枚举成员(enumerator),他们本质上就是int常数,只是"red"变成了代表整数0的命名了的常数,可以在任何可以使用整数常量的地方使用枚举常量。要使用这些常量,如red,必须输入数字1而不是字符串"red"。枚举常量默认从0开始,也可以使用其他整数值来代替,如:

enum feline { cat, lynx = 10, puma, tiger};   // cat == 0, lynx == 10, puma == 11, tiger == 12

尽管枚举成员(如"red")是int型,但枚举变量并不严格地局限于int型,只要可以容纳枚举的常量即可(比如也可以是unsigned char型)


有些C下的枚举性质并不能移植到C++,比如C允许对枚举变量使用"++"而C++则不允许。所以如果想让程序兼容C++就尽量少使用枚举类型


共享命名空间(shared namespace)

C用命名空间(namespace)来指明程序中名字可以被识别的部分。在一个scope中的structure tag,union tag,enumeration tag共享同样的命名空间,这个命名空间和普通变量的不同。也就是说,一个变量名在一个scope中可以同时用于变量和tag而不会产生问题。但是强烈不建议这么做,因为会产生混淆,并且C++也不允许这样,因为它把所有的tag和变量名放在同一个namespace中


typedef

typedef是一个能让你为一个类型(type)创建新名字的高级数据特性,它类似#define,但是有三点不同(注意typedef最后要接分号):

1. 不像#define,typedef只能给类型(type)命名而不能给值命名

2. typedef的实现是由compiler完成的,不是预处理器

3. 在这些限制内时,typedef比#define更灵活

#define UCHAR unsigned char == typedef unsigned char UCHAR;

但是typedef char* STRING;,不能用#define STRING char*实现。如果使用前者,STRING name, sign; == char* name, *sign;,name和sign都是指针;而如果使用后者,STRING name, sign; == char* name, sign;,只有name是指针,而sign是char型


例:typedef unsigned char UCHAR;

这个定义的作用域取决于typedef语句的位置


Fancy Declarations

"[]"代表数组,"()"代表函数,"*"代表指针

一些符号的优先级:

1. "[]"和"()"有同样的优先级,比"*"的优先级高,所以int* risk[10]先是一个数组,然后它的类型是int*

2. "[]"和"()"的顺序是从左到右,因此int good[12][50]先是一个长度为12的数组,然后每个元素又是长度为50的int型

3. 因为"[]"和"()"的优先级相同且是从左到右,所以int (* rusk)[10]先是一个指针,然后这个指针指向了一个长度为10的int型数组

根据上面的理论可得:

char* fump(int):是一个函数;函数名是fump,参数是int型,返回值是char*

char (* frump)(int):是一个指针,指针名是frump,指向一个函数;函数的参数是int型,返回值是char型

char (* flump[3])(int):是一个数组,数组名是flump,长度为3;数组的元素是指针,指向一个函数;函数的参数是int型,返回值是char型

typedef可以用来简化上面的定义,例:

typedef int arr5[5];
typedef arr5 * p_arr5;
typedef p_arr5 arrp10[10];
arr5 togs;   // togs是一个长度为5的int型数组
p_arr5 p2;   // p2是一个指向长度为5的int型数组的指针
arrp10 ap;   // ap是一个长度为10的数列,数列的元素是指针,指针指向长度为5的int型数组



函数指针

指针可以指向函数,函数指针通常用作另一个函数的参数,告诉这个“另一个函数”应该调用什么函数。函数指针也是存放一个地址,这个地址标志了一段函数的开始代码。当声明函数指针时,必须要指明所指向函数的类型。函数的类型由函数签名(signature)决定,即函数的参数类型和返回值,例如【void (*pf)(char *)】就声明了一个函数指针,这个函数的参数是char*,返回值为空。确定一个函数指针格式的快速方法是将函数的prototype中的函数名用指针(*pf)代替即可,注意要有括号,如果没有括号【void *pf(char*)】是一个函数,函数名是pf,参数是char*,返回值是pointer-to-void(参考前面fancy declaration部分)

声明了函数指针后,就要将同类型函数的地址赋给它。在这种情况下,函数名就可以代表函数的地址

例:

void ToUpper(char *);
void (*pf)(char *);
pf = ToUpper;
char mis[] == "Nina"
(*pf)(mis);       // 合法,因为pf指向ToUpper函数,所以*pf是ToUpper函数
pf(mis);           // 合法,因为函数名是一个指针,所以指针名和函数名可以互换


函数指针的一个常见用法是作为另一个函数的参数,例:

void show(void (* fp)(char *), char* str); 
... 
show(ToUpper, mis);
show(pf, mis);


函数不能定义成数组,但是函数指针可以定义成数组


  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值