《c primer plus》c语言学习笔记整理(十四)-结构与其他数据形式

第十四章 结构与其他数据形式

1.结构声明的建立
(1)结构声明描述了一个结构的组织布局
(2)右花括号后面的分号是必须的,表明结构布局定义结束。
(3)结构声明可以放在所有函数的外部,也可以放在一个函数定义的内部,但是如果把声明放在函数内部,它的标记只局限于该函数内部使用,
(4)结构的标记名是可选的,但是在一处定义结构布局,在另一处定义实际的结构变量,必须使用标记。
2.定义结构变量
(1)初始化结构
1)声明结构的过程和定义结构变量的过程可以组合为一个步凑,组合后的结构声明和结构变量定义不需要使用结构标记。
(2)访问结构成员
1)使用结构成员运算符-点(.)访问结构中的成员
2)点运算符优先级比&高
(3)结构的初始化器
1)C99和C11为结构提供了指定初始化器,结构的指定初始化器使用点运算符和成员名(而不是方括号和下标)标识特定的元素

struct book gift ={
	.value =25.99,
	.author = "James Broadfool",
	.title = "Rue for the Toad"
	};

2)对特定成员的最后一次赋值才是它实际获得的值
3)在指定初始化器后面的普通初始化器,为指定成员后面的成员提供初值。

struct book gift= {
	.value = 18.90,
	.author = "Philionna Pestle",
	0.25
	};

例如本例中赋给value的值为0.25,因为它在结构声明中紧跟在author成员之后
3.结构数组
(1)声明结构数组
与声明其他类型的数组类似

struct book library[MAXBKS];

(2)标识结构数组的成员
可以采用访问单独结构的规则:在结构名后面加一个点运算符,再在点运算符后面写上成员名。
4.嵌套结构
如何访问嵌套结构的成员,需要使用两次点运算符:

printf ("Hello, s!\n", fellow.handle.first);

5.指向结构的指针
(1)声明和初始化结构指针

struct guy * him;

1)与数组不同的是,结构名并不是结构的地址,因此要在结构名前加上&运算符
(2)用指针访问成员
1)方法1:用->运算符
如果him==&barney,那么him->income即是barney.income
如果him==&fellow[0],那么him->income即是fellow [0].income
2)方法2:用运算符(必须使用圆括号,因为.运算符比运算符的优先级高)

barney.income == (*him) . income == him->income //假设him== &barney
fellow [0].income == (*him) .income

在有些系统中,一个结构的大小可能大于他的各个成员大小之和,这是因为系统对数据进行校准的过程中产生了一些“缝隙”。(例如,有些系统必须把每个成员放在偶数地址上,或4的倍数的地址上)
6.向函数传递结构的信息
(1)传递结构成员
只要结构成员是一个具有单个值的数据类型(int及其相关类型,char,float,double或指针,便可以把它作为参数传递给接受该特定类型的函数)
(2)传递结构的地址
必须使用&运算符来获取结构的地址,结构名只是其地址的别名;
(3)传递结构
(4)其他结构特性
1)现在的C允许把一个结构赋值给另一个结构,但是数组不可以这么做。即使成员是数组,也能完成赋值,另外,还可以把一个结构初始化为相同类型的另一个结构。

struct names right field = ("Ruthie", "George"});
struct names captain = right-field; //把一个结构初始化为另一个结构

2)现在的c,函数不仅能把结构本身作为参数传递,还能把结构作为返回值返回,把结构作为函数参数可以把结构的信息传递给函数,把结构作为返回值的函数能把结构的信息从被调函数传回给主调函数
3)两种方法的区别:传递地址为直接处理传递的值,传递结构时候,函数处理的是传递数据的副本。
(5)结构与结构指针的选择
1)指针作为参数的优点:无论是以前的方式现在的C实现都能使用这种方法,而且执行起来更快,只需要传递一个地址。缺点是无法保护数据,ANSI C新增搞得const限定符可以解决这个问题
2)把结构作为参数传递的优点:函数处理的是原始数据的副本,保护了原始数据,代码风格也更加清楚。
3)传递结构的两个缺点:较老的版本可能无法处理这样的代码,而且传递结构浪费时间和存储空间。
(6)结构中的字符数组和字符指针
如果要用结构储存字符串,用字符数组作为成员比较简单。用指向char的指针也行,但是误用会导致严重的问题。(指针最好只用来在程序中管理那些已分配和在别处分配的字符串)
不要使用未初始化的指针,使用指针必须进行相应的初始化。
(7)结构、指针、malloc()
如果使用malloc()分配内存并使用指针储存该地址,那么在结构中使用指针处理字符串就比较合理。
(8)复合字面量和结构(C99)
1)C99的复合字面量特性可用于结构和数组,如果只需要一个临时值,复合字面量很好用。可以使用复合字面量创建一个数组作为函数的参数或赋给另一个结构。
2)可以把复合字面量作为函数的参数,如果函数接收一个结构,可以把复合字面量作为实际参数传递。
3)复合字面量和普通初始化列表的语法规则相同。这意味着,可以在复合字面量中使用指定初始化器。
4)使用方法:

(struct book) {"The Idiot", "Fyodor Dostoyevsky", 6.99}

5)复合字面量在所有函数的外部具有静态存储期,如果在块中,则具有自动存储期
(9)伸缩性数组成员(C99)
(10)匿名结构(C11)
1)匿名结构是一个没有名称的结构成员

struct person
{
	int id;
	struct {char first [20]; char last [20];}; //匿名结构
}

2)匿名的特性在嵌套联合中更加有用。
3)只需要把匿名结构中的成分看作是成员就可以像是person的成员那样使用它。
struct person ted = {8483, {“red”, “Grass”}};
但是,在访问ted时简化了步骤,只需把first看作是person的成员那样使用它:
puts (ted.first);

(11)使用结构数组的函数
如果一个函数要处理一个结构数组,由于数组名就是该数组的地址,所以可以把它传递给函数。另外该函数还需要访问结构模板。
1)可以把数组名作为数组中的第1个结构的地址传递给函数
2)然后可以用数组表示法访问数组中的其他结构,下面的函数调用和使用数组名效果相同:

sum (&jones [0], N)

因为jones与&jones[0]的地址相同,使用数组名是一种间接的方法。
7.把结构内容保存到文件中
8.联合简介
(1)使用联合
1)一种数据类型,能在同一块内存空间中储存不同的数据类型(不是同时储存),但是声明的联合只能储存一个值
2)初始化方法:把一个联合初始化为另一个同类型的联合,初始化联合的第一个元素,或者根据C99标准,使用指定初始化器。

union hold valA;
valA.letter = 'R';
union hold valB = valA;  //用另一个联合来初始化
union hold valc = (88};  1/初始化联合的digit成员union hold valD = (.bigfl = 118.2); //指定初始化器

3)点运算符表示正在使用哪种数据类型,在联合中,一次只储存一个值。
4)和利用指针访问结构使用->运算符一样,用指针访问联合也要使用->运算符

pu = &fit;
x=pu->digit; //相当于x =fit.digit

下面的语句序列是错误的,fit中为char类型,但是下一行内容却是double类型

fit.letter = 'A';
flnum =3.02*fit.bigfl; //错误

5)联合的作用:一个成员把值储存在一个联合中,然后用另一个成员查看内容,这种做法有时候很有用。联合的另一个作用是在结构中储存与其成员有从属关系的信息。
(2)匿名联合
同匿名结构的工作原理相同
9.枚举类型
(1)enum常量:int类型的常量,有名称的常量

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

1)枚举符:red等符号常量
2)枚举符是int类型,但是枚举变量可以是任意整数类型,前提是该整数类型可以储存枚举常量。
3)c枚举的一些特性并不适用于C++。如果将来编写的代码会并入C++程序,那么必须把color声明为int类型。
(2)默认值:枚举列表中的常量都被赋予0、1、2等
(3)赋值
1)枚举声明中,可以为枚举常量指定整数值。

enum levels {low = 100, medium = 500, high - 2000};

2)如果只给一个枚举常量赋值,没有对后面的枚举常量赋值,那么后面的常量会被赋予后续的值。

enum feline {cat, lynx =10, puma, tiger};

(4)共享名称空间
1)c语言使用名称空间标识程序的各部分,即通过名称来识别,作用域是名称空间概念的一部分:两个不同作用域的同名变量不冲突,两个相同作用域的同名变量冲突。
2)名称空间分类别
3)在特定作用域中的结构标记、联合标记和枚举标记都共享相同的名称空间,该名称空间与普通变量使用的空间不同。
4)C++不允许这么做,因为它把标记名和变量名放在相同的名称空间中。
10.typedef简介
(1)利用type可以为某一类型自定义名称
(2)与#define的区别:
1)创建的符号名只受限于类型,不能用于值
2)typedef由编译器解释,不是预处理器
3)在受限范围内,typedef比#define更灵活
(3)typedef的作用
1)提高程序的可移植性(比如size_t类型)
2)其一些特性和#define功能重合,但是也有#define没有的功能。
(4)使用原因
1)为经常出现的类型创建一个方便、易识别的类型名
2)常用于给复杂的类型命名

typedef char (* FRPTC ()) [5];

3)typedef并没有创建任何新的类型,它只是为某个已存在的类型增加了一个方便实用的标签。
11.其他复杂的声明
12.函数和指针
1)通常函数指针常作为另一个函数的参数,告诉函数使用哪一个函数。
2)指向函数的指针储存着函数代码的起始处的地址。函数名可以表示函数的地址
3)声明一个函数指针时,必须声明指针指向的函数类型,为了指明函数类型,要指明函数签名,即函数的返回类型和形参类型。(但是函数地址必须和声明的指针类型匹配)

void (*pf) (char *);
//pf是一个指向函数的指针

4)可以用函数指针用来访问函数
5)使用方法,使用传入的函数指针

void show (void (*fp) (char *), char * str)
{
	(*fp) (str); /*把所选函数作用于str*/
	puts (str);  /*显示结果*/
}

6)把带返回值的函数作为参数传递给另一个函数有两种不同的方法

functionl (sqrt);  /*传递sqrt(1)函数的地址*/
function2 (sqrt (4.0)); /*传递sqrt ()函数的返回值*/

猜你喜欢

转载自blog.csdn.net/weixin_45096408/article/details/104758951