【《C Primer Plus》读书笔记】第14章:结构和其他数据形式
- 14.1 C 结构体
- 定义结构
- 结构体变量的初始化
- 访问结构成员
- 结构作为函数参数
- 指向结构的指针
- 复合字面量和结构(C99)
- 伸缩型数组成员(C99)
- 匿名结构(C11)
- 14.2 C 结构体
- 定义共用体
- 访问共用体成员
- 匿名共用体(C11)
- 14.3 C 枚举
- 枚举变量的定义
- 将整数转换为枚举
- 14.4 C typedef
- typedef vs #define
14.1 C 结构体
C 数组允许定义可存储相同类型数据项的变量,结构是 C 编程中另一种用户自定义的可用的数据类型,它允许您存储不同类型的数据项。
结构体中的数据成员可以是基本数据类型(如 int、float、char 等),也可以是其他结构体类型、指针类型等。
结构用于表示一条记录,假设您想要跟踪图书馆中书本的动态,您可能需要跟踪每本书的下列属性:
- Title
- Author
- Subject
- Book ID
定义结构
结构体定义由关键字 struct 和结构体名组成,结构体名可以根据需要自行定义。
struct 语句定义了一个包含多个成员的新的数据类型,struct 语句的格式如下:
struct tag { member-listmember-list member-list ...
} variable-list ;
tag 是结构体标签。
member-list 是标准的变量定义,比如 int i; 或者 float f;,或者其他有效的变量定义。
variable-list 结构变量,定义在结构的末尾,最后一个分号之前,您可以指定一个或多个结构变量。下面是声明 Book 结构的方式:
struct Books
{char title[50];char author[50];char subject[100];int book_id;
} book;
在一般情况下,tag、member-list、variable-list 这 3 部分至少要出现 2 个。
以下为实例:
//此声明声明了拥有3个成员的结构体,分别为整型的a,字符型的b和双精度的c
//同时又声明了结构体变量s1
//这个结构体并没有标明其标签
struct
{int a;char b;double c;
} s1;//此声明声明了拥有3个成员的结构体,分别为整型的a,字符型的b和双精度的c
//结构体的标签被命名为SIMPLE,没有声明变量
struct SIMPLE
{int a;char b;double c;
};
//用SIMPLE标签的结构体,另外声明了变量t1、t2、t3
struct SIMPLE t1, t2[20], *t3;//也可以用typedef创建新类型
typedef struct
{int a;char b;double c;
} Simple2;
//现在可以用Simple2作为类型声明新的结构体变量
Simple2 u1, u2[20], *u3;
在上面的声明中,第一个和第二声明被编译器当作两个完全不同的类型,即使他们的成员列表是一样的,如果令 t3=&s1,则是非法的。
结构体的成员可以包含其他结构体,也可以包含指向自己结构体类型的指针,而通常这种指针的应用是为了实现一些更高级的数据结构如链表和树等。
//此结构体的声明包含了其他的结构体
struct COMPLEX
{char string[100];struct SIMPLE a;
};//此结构体的声明包含了指向自己类型的指针
struct NODE
{char string[100];struct NODE *next_node;
};
如果两个结构体互相包含,则需要对其中一个结构体进行不完整声明,如下所示:
struct B; //对结构体B进行不完整声明//结构体A中包含指向结构体B的指针
struct A
{struct B *partner;//other members;
};//结构体B中包含指向结构体A的指针,在A声明完后,B也随之进行声明
struct B
{struct A *partner;//other members;
};
结构体变量的初始化
和其它类型变量一样,对结构体变量可以在定义时指定初始值。
实例:
#include <stdio.h>struct Books
{char title[50];char author[50];char subject[100];int book_id;
} book = {"C 语言", "RUNOOB", "编程语言", 123456};int main()
{printf("title : %s\nauthor: %s\nsubject: %s\nbook_id: %d\n", book.title, book.author, book.subject, book.book_id);
}
执行输出结果为:
title : C 语言
author: RUNOOB
subject: 编程语言
book_id: 123456
访问结构成员
为了访问结构的成员,我们使用成员访问运算符(.)。成员访问运算符是结构变量名称和我们要访问的结构成员之间的一个句号。您可以使用 struct 关键字来定义结构类型的变量。下面的实例演示了结构的用法:
#include <stdio.h>
#include <string.h>struct Books
{char title[50];char author[50];char subject[100];int book_id;
};int main( )
{struct Books Book1; /* 声明 Book1,类型为 Books */struct Books Book2; /* 声明 Book2,类型为 Books *//* Book1 详述 */strcpy( Book1.title, "C Programming");strcpy( Book1.author, "Nuha Ali"); strcpy( Book1.subject, "C Programming Tutorial");Book1.book_id = 6495407;/* Book2 详述 */strcpy( Book2.title, "Telecom Billing");strcpy( Book2.author, "Zara Ali");strcpy( Book2.subject, "Telecom Billing Tutorial");Book2.book_id = 6495700;/* 输出 Book1 信息 */printf( "Book 1 title : %s\n", Book1.title);printf( "Book 1 author : %s\n", Book1.author);printf( "Book 1 subject : %s\n", Book1.subject);printf( "Book 1 book_id : %d\n", Book1.book_id);/* 输出 Book2 信息 */printf( "Book 2 title : %s\n", Book2.title);printf( "Book 2 author : %s\n", Book2.author);printf( "Book 2 subject : %s\n", Book2.subject);printf( "Book 2 book_id : %d\n", Book2.book_id);return 0;
}
当上面的代码被编译和执行时,它会产生下列结果:
Book 1 title : C Programming
Book 1 author : Nuha Ali
Book 1 subject : C Programming Tutorial
Book 1 book_id : 6495407
Book 2 title : Telecom Billing
Book 2 author : Zara Ali
Book 2 subject : Telecom Billing Tutorial
Book 2 book_id : 6495700
结构作为函数参数
您可以把结构作为函数参数,传参方式与其他类型的变量或指针类似。您可以使用上面实例中的方式来访问结构变量:
#include <stdio.h>
#include <string.h>struct Books
{char title[50];char author[50];char subject[100];int book_id;
};/* 函数声明 */
void printBook( struct Books book );
int main( )
{struct Books Book1; /* 声明 Book1,类型为 Books */struct Books Book2; /* 声明 Book2,类型为 Books *//* Book1 详述 */strcpy( Book1.title, "C Programming");strcpy( Book1.author, "Nuha Ali"); strcpy( Book1.subject, "C Programming Tutorial");Book1.book_id = 6495407;/* Book2 详述 */strcpy( Book2.title, "Telecom Billing");strcpy( Book2.author, "Zara Ali");strcpy( Book2.subject, "Telecom Billing Tutorial");Book2.book_id = 6495700;/* 输出 Book1 信息 */printBook( Book1 );/* 输出 Book2 信息 */printBook( Book2 );return 0;
}
void printBook( struct Books book )
{printf( "Book title : %s\n", book.title);printf( "Book author : %s\n", book.author);printf( "Book subject : %s\n", book.subject);printf( "Book book_id : %d\n", book.book_id);
}
当上面的代码被编译和执行时,它会产生下列结果:
Book title : C Programming
Book author : Nuha Ali
Book subject : C Programming Tutorial
Book book_id : 6495407
Book title : Telecom Billing
Book author : Zara Ali
Book subject : Telecom Billing Tutorial
Book book_id : 6495700
指向结构的指针
您可以定义指向结构的指针,方式与定义指向其他类型变量的指针相似,如下所示:
struct Books *struct_pointer;
现在,您可以在上述定义的指针变量中存储结构变量的地址。为了查找结构变量的地址,请把 & 运算符放在结构名称的前面,如下所示:
struct_pointer = &Book1;
为了使用指向该结构的指针访问结构的成员,您必须使用 -> 运算符,如下所示:
struct_pointer->title;
让我们使用结构指针来重写上面的实例,这将有助于您理解结构指针的概念:
#include <stdio.h>
#include <string.h>struct Books
{char title[50];char author[50];char subject[100];int book_id;
};/* 函数声明 */
void printBook( struct Books *book );
int main( )
{struct Books Book1; /* 声明 Book1,类型为 Books */struct Books Book2; /* 声明 Book2,类型为 Books *//* Book1 详述 */strcpy( Book1.title, "C Programming");strcpy( Book1.author, "Nuha Ali"); strcpy( Book1.subject, "C Programming Tutorial");Book1.book_id = 6495407;/* Book2 详述 */strcpy( Book2.title, "Telecom Billing");strcpy( Book2.author, "Zara Ali");strcpy( Book2.subject, "Telecom Billing Tutorial");Book2.book_id = 6495700;/* 通过传 Book1 的地址来输出 Book1 信息 */printBook( &Book1 );/* 通过传 Book2 的地址来输出 Book2 信息 */printBook( &Book2 );return 0;
}
void printBook( struct Books *book )
{printf( "Book title : %s\n", book->title);printf( "Book author : %s\n", book->author);printf( "Book subject : %s\n", book->subject);printf( "Book book_id : %d\n", book->book_id);
}
当上面的代码被编译和执行时,它会产生下列结果:
Book title : C Programming
Book author : Nuha Ali
Book subject : C Programming Tutorial
Book book_id : 6495407
Book title : Telecom Billing
Book author : Zara Ali
Book subject : Telecom Billing Tutorial
Book book_id : 6495700
复合字面量和结构(C99)
复合字面量也可用于结构,在只需要临时结构时很好用,也可以传递复合字面量本身和地址。
语法是把类型名放在圆括号中,后面是花括号括起来的初始化列表。
可以在复合字面量中使用指定初始化器。
复合字面量定义在所有函数外则有静态存储期,定义在块中有自动存储期。
实例:
#define MAXTITL 41
#define MAXAUTL 31
struct book {char title[MAXTITL];char author[MAXAUTL];float value;
};
下面是struct book类型的复合字面量:
(struct book) {"The Idiot", "Fyodor Dostoyevsky", 6.99};
伸缩型数组成员(C99)
C99新增了伸缩型数组成员,利用这项声明的结构,最后一个数组成员不会立即存在。使用这个数组成员可以编写合适的代码。
声明一个伸缩型数组成员有以下规则:伸缩型数组成员必须是结构的最后一个成员,结构中至少有一个成员,伸缩数组的声明类似于普通数组,不过方括号是空的。
C99的意图是希望使用一个指向此结构的指针,然后用malloc()动态内存分配足够的空间来存储数据。
下面是使用伸缩型数组成员的一个例子。
#include<stdio.h>
#include<stdlib.h>
struct flex
{size_t count;double average;double scores[];
};
void showFlex(struct flex* p);int main(void)
{struct flex* pf1, * pf2;int n = 5;int i;int tot = 0;pf1 = malloc(sizeof(struct flex) + n * sizeof(double));pf1->count = n;for (i = 0; i < 5; i++) {pf1->scores[i] = 20.0 - i;tot += pf1->scores[i];}pf1->average = tot / n;showFlex(pf1);n = 9;tot = 0;pf2 = malloc(sizeof(struct flex) + n * sizeof(double));pf2->count = n;for (i = 0; i < n; i++) {pf2->scores[i] = 20.0 - i / 2.0;tot += pf2->scores[i];}pf2->average = tot / n;showFlex(pf2);free(pf1);free(pf2);return 0;
}
void showFlex(struct flex* p)
{int i;printf("Score : ");for (i = 0; i < p->count; i++)printf("%g ", p->scores[i]);printf("\nAverage:%g\n", p->average);
}
有伸缩型数组成员的结构有一些特殊处理要求:不能用结构进行赋值或拷贝,不能按值传递给函数,不要作为一个数组的成员或另一个结构的成员。
匿名结构(C11)
匿名结构是一个没有名称的结构成员,在一个结构体声明中用一个复合字面量声明另一种结构。
这样初始化的方式相同,但是访问被嵌套的成员时简化了步骤(少用一次)。
实例:
#include <stdio.h>
#include <string.h>
/*
时间:2022-05-13 16:02
作者:sgbl888
功能:匿名结构
知识点:1、2、3、
*/
/* struct conact{char email[30]; //邮箱long long phone; //电话号码
}; */
struct Student{char name[20]; //姓名short gender; //性别//struct conact; //匿名结构作为Student成员struct{ //匿名结构char email[30]; //邮箱long long phone; //电话号码 };
};
int main(){struct Student st1;strcpy(st1.name, "李小花");st1.gender = 2;strcpy(st1.email, "lixiaohua@163.com");st1.phone = 13001300123;printf("姓名:%s\t性别:%hd\t%邮箱:%s\t电话:%lld\n", st1.name, st1.gender, st1.email, st1.phone);return 0;
}
14.2 C 结构体
共用体是一种特殊的数据类型,允许您在相同的内存位置存储不同的数据类型。您可以定义一个带有多成员的共用体,但是任何时候只能有一个成员带有值。共用体提供了一种使用相同的内存位置的有效方式。
定义共用体
为了定义共用体,您必须使用 union 语句,方式与定义结构类似。union 语句定义了一个新的数据类型,带有多个成员。union 语句的格式如下:
union [union tag]
{member definition;member definition;...member definition;
} [one or more union variables];
union tag 是可选的,每个 member definition 是标准的变量定义,比如 int i; 或者 float f; 或者其他有效的变量定义。在共用体定义的末尾,最后一个分号之前,您可以指定一个或多个共用体变量,这是可选的。下面定义一个名为 Data 的共用体类型,有三个成员 i、f 和 str:
union Data
{int i;float f;char str[20];
} data;
现在,Data 类型的变量可以存储一个整数、一个浮点数,或者一个字符串。这意味着一个变量(相同的内存位置)可以存储多个多种类型的数据。您可以根据需要在一个共用体内使用任何内置的或者用户自定义的数据类型。
共用体占用的内存应足够存储共用体中最大的成员。例如,在上面的实例中,Data 将占用 20 个字节的内存空间,因为在各个成员中,字符串所占用的空间是最大的。下面的实例将显示上面的共用体占用的总内存大小:
实例:
#include <stdio.h>
#include <string.h>union Data
{int i;float f;char str[20];
};int main( )
{union Data data; printf( "Memory size occupied by data : %d\n", sizeof(data));return 0;
}
当上面的代码被编译和执行时,它会产生下列结果:
Memory size occupied by data : 20
访问共用体成员
为了访问共用体的成员,我们使用成员访问运算符(.)。成员访问运算符是共用体变量名称和我们要访问的共用体成员之间的一个句号。您可以使用 union 关键字来定义共用体类型的变量。下面的实例演示了共用体的用法:
实例:
#include <stdio.h>
#include <string.h>union Data
{int i;float f;char str[20];
};int main( )
{union Data data; data.i = 10;data.f = 220.5;strcpy( data.str, "C Programming");printf( "data.i : %d\n", data.i);printf( "data.f : %f\n", data.f);printf( "data.str : %s\n", data.str);return 0;
}
当上面的代码被编译和执行时,它会产生下列结果:
data.i : 1917853763
data.f : 4122360580327794860452759994368.000000
data.str : C Programming
在这里,我们可以看到共用体的 i 和 f 成员的值有损坏,因为最后赋给变量的值占用了内存位置,这也是 str 成员能够完好输出的原因。现在让我们再来看一个相同的实例,这次我们在同一时间只使用一个变量,这也演示了使用共用体的主要目的:
实例:
#include <stdio.h>
#include <string.h>union Data
{int i;float f;char str[20];
};int main( )
{union Data data; data.i = 10;printf( "data.i : %d\n", data.i);data.f = 220.5;printf( "data.f : %f\n", data.f);strcpy( data.str, "C Programming");printf( "data.str : %s\n", data.str);return 0;
}
当上面的代码被编译和执行时,它会产生下列结果:
data.i : 10
data.f : 220.500000
data.str : C Programming
在这里,所有的成员都能完好输出,因为同一时间只用到一个成员。
匿名共用体(C11)
匿名共用体(anonymous union)没有名称,其成员将成为位于相同地址处的变量。
如果我们将一个 union 包括在一个结构(structure)的定义中,并且不赋予它对象(object)名称 (就是跟在
花括号{}后面的名字),这个union 就是匿名的。这种情况下我们可以直接使用 union 中元素的名字来访问该元素,而不需要再在前面加 union 对象的名称。
在下面的例子中,我们可以看到这两种表达方式在使用上的区别:
以上两种定义的唯一区别在于左边的定义中我们给了 union 一个名字 price ,而在右边的定义中我们没给。
在使用时的区别是当我们想访问一个对象(object)的元素 dollars 和 yen 时,在前一种定义的情况下,需要使用:
book.price.dollars;
book.price.yen;
而在后面一种定义下,我们直接使用:
book.dollars;
book.yen;
再一次提醒,因为这是一个联合(union),域 dollars 和 yen 占据的是同一块内存空间,所以它们不能被用来存储两个不同的值。也就是你可以使用
一个 dollars 或 yen 的价格,但不能同时使用两者。
14.3 C 枚举
枚举是 C 语言中的一种基本数据类型,用于定义一组具有离散值的常量。,它可以让数据更简洁,更易读。
枚举类型通常用于为程序中的一组相关的常量取名字,以便于程序的可读性和维护性。
定义一个枚举类型,需要使用 enum 关键字,后面跟着枚举类型的名称,以及用大括号 {} 括起来的一组枚举常量。每个枚举常量可以用一个标识符来表示,也可以为它们指定一个整数值,如果没有指定,那么默认从 0 开始递增。
枚举语法定义格式为:
enum 枚举名 {枚举元素1,枚举元素2,……};
接下来我们举个例子,比如:一星期有 7 天,如果不用枚举,我们需要使用 #define 来为每个整数定义一个别名:
#define MON 1
#define TUE 2
#define WED 3
#define THU 4
#define FRI 5
#define SAT 6
#define SUN 7
这个看起来代码量就比较多,接下来我们看看使用枚举的方式:
enum DAY
{MON=1, TUE, WED, THU, FRI, SAT, SUN
};
这样看起来是不是更简洁了。
注意:第一个枚举成员的默认值为整型的 0,后续枚举成员的值在前一个成员上加 1。我们在这个实例中把第一个枚举成员的值定义为 1,第二个就为 2,以此类推。
可以在定义枚举类型时改变枚举元素的值:
enum season {spring, summer=3, autumn, winter};
没有指定值的枚举元素,其值为前一元素加 1。也就说 spring 的值为 0,summer 的值为 3,autumn 的值为 4,winter 的值为 5
枚举变量的定义
前面我们只是声明了枚举类型,接下来我们看看如何定义枚举变量。
我们可以通过以下三种方式来定义枚举变量:
一、先定义枚举类型,再定义枚举变量
enum DAY
{MON=1, TUE, WED, THU, FRI, SAT, SUN
};
enum DAY day;
二、定义枚举类型的同时定义枚举变量
enum DAY
{MON=1, TUE, WED, THU, FRI, SAT, SUN
} day;
三、省略枚举名称,直接定义枚举变量
enum
{MON=1, TUE, WED, THU, FRI, SAT, SUN
} day;
实例:
#include <stdio.h>enum DAY
{MON=1, TUE, WED, THU, FRI, SAT, SUN
};int main()
{enum DAY day;day = WED;printf("%d",day);return 0;
}
以上实例输出结果为:
3
在C 语言中,枚举类型是被当做 int 或者 unsigned int 类型来处理的,所以按照 C 语言规范是没有办法遍历枚举类型的。
不过在一些特殊的情况下,枚举类型必须连续是可以实现有条件的遍历。
以下实例使用 for 来遍历枚举的元素:
实例:
#include <stdio.h>enum DAY
{MON=1, TUE, WED, THU, FRI, SAT, SUN
} day;
int main()
{// 遍历枚举元素for (day = MON; day <= SUN; day++) {printf("枚举元素:%d \n", day);}
}
以上实例输出结果为:
枚举元素:1
枚举元素:2
枚举元素:3
枚举元素:4
枚举元素:5
枚举元素:6
枚举元素:7
以下枚举类型不连续,这种枚举无法遍历。
enum
{ENUM_0,ENUM_10 = 10,ENUM_11
};
枚举在 switch 中的使用:
实例:
#include <stdio.h>
#include <stdlib.h>
int main()
{enum color { red=1, green, blue };enum color favorite_color;/* 用户输入数字来选择颜色 */printf("请输入你喜欢的颜色: (1. red, 2. green, 3. blue): ");scanf("%u", &favorite_color);/* 输出结果 */switch (favorite_color){case red:printf("你喜欢的颜色是红色");break;case green:printf("你喜欢的颜色是绿色");break;case blue:printf("你喜欢的颜色是蓝色");break;default:printf("你没有选择你喜欢的颜色");}return 0;
}
以上实例输出结果为:
请输入你喜欢的颜色: (1. red, 2. green, 3. blue): 1
你喜欢的颜色是红色
将整数转换为枚举
以下实例将整数转换为枚举:
实例:
#include <stdio.h>
#include <stdlib.h>int main()
{enum day{saturday,sunday,monday,tuesday,wednesday,thursday,friday} workday;int a = 1;enum day weekend;weekend = ( enum day ) a; //类型转换//weekend = a; //错误printf("weekend:%d",weekend);return 0;
}
以上实例输出结果为:
weekend:1
14.4 C typedef
C 语言提供了 typedef 关键字,您可以使用它来为类型取一个新的名字。下面的实例为单字节数字定义了一个术语 BYTE:
typedef unsigned char BYTE;
在这个类型定义之后,标识符 BYTE 可作为类型 unsigned char 的缩写,例如:
BYTE b1, b2;
按照惯例,定义时会大写字母,以便提醒用户类型名称是一个象征性的缩写,但您也可以使用小写字母,如下:
typedef unsigned char byte;
您也可以使用 typedef 来为用户自定义的数据类型取一个新的名字。例如,您可以对结构体使用 typedef 来定义一个新的数据类型名字,然后使用这个新的数据类型来直接定义结构变量,如下:
实例:
#include <stdio.h>
#include <string.h>typedef struct Books
{char title[50];char author[50];char subject[100];int book_id;
} Book;int main( )
{Book book;strcpy( book.title, "C 教程");strcpy( book.author, "Runoob"); strcpy( book.subject, "编程语言");book.book_id = 12345;printf( "书标题 : %s\n", book.title);printf( "书作者 : %s\n", book.author);printf( "书类目 : %s\n", book.subject);printf( "书 ID : %d\n", book.book_id);return 0;
}
当上面的代码被编译和执行时,它会产生下列结果:
书标题 : C 教程
书作者 : Runoob
书类目 : 编程语言
书 ID : 12345
typedef vs #define
#define 是 C 指令,用于为各种数据类型定义别名,与 typedef 类似,但是它们有以下几点不同:
- typedef 仅限于为类型定义符号名称,#define 不仅可以为类型定义别名,也能为数值定义别名,比如您可以定义 1 为 ONE。
- typedef 是由编译器执行解释的,#define 语句是由预编译器进行处理的。
下面是 #define 的最简单的用法:
实例:
#include <stdio.h>#define TRUE 1
#define FALSE 0int main( )
{printf( "TRUE 的值: %d\n", TRUE);printf( "FALSE 的值: %d\n", FALSE);return 0;
}
当上面的代码被编译和执行时,它会产生下列结果:
TRUE 的值: 1
FALSE 的值: 0