一、结构体
1.建立结构体声明
例: struct book{
char arr[20];
char st[10];
float value;
}; //声明一个由两个字符数组和一个float类型变量组成的结构体
1) 该声明并未创建实际的数据对象,只描述了该对象由什么组成
2) 在结构体声明中,用一对花括号括起来的是结构成员列表
3) 右花括号后面的分号是声明所必需的,表示结构体布局定义结束
4) 可以把结构体的声明放在所有函数的外部,也可以放在一个函数定义的内部
2.创建结构体变量
例: struct book library; //创建结构体变量library
1) 编译器使用book模板为该变量分配空间:一个内含20个元素的char数组、一个内含10个元素的char数组和一个float类型变量
2) 从本质上看,book结构声明创建了一个名为struct book的新类型
例: struct book library;
等同于 struct book{
char arr[20];
char st[10];
float value;
} library; //声明的右花括号后跟变量名
3.初始化结构体变量
1) 使用一对花括号括起来的初始化列表进行初始化,各初始化项用逗号分隔
2) 如果初始化一个静态存储期的结构,初始化列表中的值必须是常量表达式
4.访问结构体成员
使用结构成员运算符(.)访问结构中的成员
例: 可以像使用float类型变量那样使用library.vaule
可以像使用字符数组那样使用library.arr
5.结构体的初始化器
1) 结构体的指定初始化器使用点运算符和成员名(而不是方括号和下标)标识特定的元素
2) 例如只初始化book结构的value成员: struct book library = {.value = 10.8};
3) 与数组类似,在指定初始化器后面的普通初始化器,为指定成员后面的成员提供初始值
//对特定成员的最后一次赋值才是它实际获得的值
6.声明结构体数组
例: struct book library[100]; //声明一个内含100个元素的数组,数组名为library,数组中的元素类型为book
7.标识结构体数组成员
在结构体名后面加一个点运算符,再在点运算符后面写上成员名
例: library[1].value; //第二个数组元素的value值
library[2].arr; //第三个数组元素的arr数组
library[3].arr[4]; //第四个数组元素的arr数组的第五个字符
8.嵌套结构
举例: 嵌套结构体的初始化和访问
#include<stdio.h>
#define SIZE 30
struct names{ //第一个结构
char first[SIZE];
char last[SIZE];
};
struct guy{ //第二个结构体
struct names handle; //嵌套结构
char favfood[SIZE];
char job[SIZE];
float income;
};
int main()
{
struct guy fellow = { //初始化一个结构体变量
{"hello","world"},
"salmon",
"personality coach",
68712.00
};
printf("%s\n",fellow.handle.first); //打印hello
printf("%s\n",fellow.favfood); //打印salmon
}
9.指向结构体的指针
举例: 使用指向结构体的指针
#include<stdio.h>
#include<string.h>
#define SIZE 20
struct names
{
char first[SIZE];
char last[SIZE];
};
struct guy
{
struct names handle;
char favfood[SIZE];
char job[SIZE];
float income;
};
int main()
{
struct guy fellow[2] =
{
{{"hello","world"},"salmon","personality coach",68712.00},
{{"Rodney","Swillbelly"},"tripe","tabloid editor",432400.00}
};
struct guy *him; //结构体指针
printf("&fellow[0] = %p,&fellow[1] = %p\n", &fellow[0], &fellow[1]);
him = &fellow[0];
printf("him = %p,him+1 = %p\n", him,him+1);
printf("him->income = %.2f,(*him).income = %.2f\n", him->income, (*him).income);
him++;
printf("him->favfood = %s,him->handle->first = %s\n",him->favfood,
him->handle.first);
return 0;
}
1) 声明结构体指针: struct guy *him;
2) 该声明并未创建一个新的结构,但是指针him现在可以指向任意现有的guy类型的结构
3) 使用结构体指针指向结构体: him = &fellow[0];
4) 用结构体指针访问结构体成员:
(1).如果him == &fellow[0],那么him->income即是fellow[0].income
(2).如果him == &fellow[0],那么(*him).income即是fellow[0].income(必须使用圆括号 .运算符比*运算符优先级高)
二、向函数传递结构体的信息
1.传递结构体成员
#include<stdio.h>
struct str
{
double num1;
double num2;
};
double sum(double a,double b);
int main()
{
struct str c = {4572.5,545.2};
printf("num1 + num2 = %.2f\n",double(c.num1,c.num2));
return 0;
}
double sum(double a,double b)
{
return a+b;
}
//如果需要需要在被调函数中修改主调函数成员的值,就要传递成员的地址
2.传递结构体地址(必须使用&运算符来获取结构的地址)
#include<stdio.h>
struct str
{
double num1;
double num2;
};
double sum(const struct str*);
int main()
{
struct str c = {4572.5,545.2};
printf("num1 + num2 = %.2f\n",sum(&c));
return 0;
}
double sum(const struct str* d)
{
return (d->num1+d->num2);
}
3.传递结构体
#include<stdio.h>
struct str
{
double num1;
double num2;
};
double sum(struct str d);
int main()
{
struct str c = {4572.5,545.2};
printf("num1 + num2 = %.2f\n",sum(c)); //调用sum
return 0;
}
double sum(struct str d)
{
return (d.num1+d.num2);
}
//调用sum()时,编译器根据str模板创建了一个名为d的自动结构体变量(该结构体成员被初始化为c结构体成员的值)
4.其他结构体特性
1) 把一个结构体赋值给另一个结构体(相同类型):
例: o_data = n_data; //把n_data的每个成员值都赋给o_data的相应成员(即是成员是数组也能完成赋值)
2) 把一个结构体初始化为相同类型的另一个结构体:
例: struct str a = {"hello","world"};
struct str b = a; //用结构体a初始化结构体b
3) 结构体作为函数参数可以把结构体的信息传送给函数;结构体作为返回值的函数能把结构体的信息从被调函数传回主调函数
5.结构体和结构体指针的选择
1) 把指针作为参数有两个优点:
无论是以前还是现在的C实现都能使用这种方法,而且执行起来很快,只需要传递一个地址
缺点是无法保护数据(被调函数中的某些操作可能会意外影响原来结构中的数据)
2) 把结构体作为参数的优点: 函数处理的是原始数据的副本,保护了原始数据
缺点是较老版本的实现可能无法处理这样的代码,而且传递结构浪费时间和存储空间
6.结构体中的字符数字和字符指针
例1: struct names{
char first[20];
char last[20];
};
struct names a = {"hello","world"}; //字符串储存在结构内部,结构总共要分配40字节存储姓名
例2: struct pnames{
char *first;
char *last;
};
struct pnames b = {"LAND","CRUISERSYH"};
1) 对于pnames结构体变量b,字符串储存在编译器储存常量的地方;而结构体体本身只储存了两个地址
2) struct pnames结构不用为字符串分配任何存储空间,它使用的是储存在别处的字符串
3) 如果将字符串放在未初始化变量所表示的地址上,地址可以是任何值
7.结构体、指针和malloc()
使用malloc()分配内存并使用指针存储该地址,可以为字符串分配合适的存储空间
#include<stdio.h>
#include<string.h> //提供strcpy()、strlen()的原型
#include<stdlib.h> //提供malloc()、free()的原型
#define SIZE 20
char *s_gets(char *s, int n);
void get_str(struct str* p); //分配内存
void make_str(struct str* p);
void put_str(const struct str* p);
void free_str(struct str* p); //释放内存
struct str
{ //存放两个指针和一个int型变量
char *firstname;
char *lastname;
int letters;
};
int main()
{
struct str person;
get_str(&person);
make_str(&person);
put_str(&person);
free_str(&person);
return p;
}
void get_str(struct str* p)
{
char temp[SIZE];
printf("请输入你的姓: ");
s_gets(temp,SIZE);
p->firstname = (char *)malloc(strlen(temp) + 1); //分配内存以存储firstname
strcpy(p->firstname, temp); //把firstname拷贝到动态分配的内存中
printf("请输入你的名: ");
s_gets(temp, SIZE);
p->lastname = (char *)malloc(strlen(temp) + 1);
strcpy(p->lastname, temp); //字符串都储存在malloc()分配的内存块中,结构体只存放地址
}
void make_str(struct str* p)
{
p->letters = strlen(p->firstname) + strlen(p->lastname);
}
void put_str(const struct str* p)
{
printf("%s %s: %d\n", p->firstname, p->lastname, p->letters);
}
void free_str(struct str* p)
{
free(p->firstname);
free(p->lastname);
}
8.伸缩型数组成员(C99)
1) 声明一个伸缩型数组成语有如下规则:
伸缩型数组成员必须是结构体中的最后一个成员
结构体中必须至少有一个成员
伸缩数组的声明类似于普通数组,只有它的方括号中时空的
声明一个具有伸缩型数组成员的结构体变量时,编译器没有给这个数组预留存储空间
例: struct flex
{
int count;
double average;
double scores[]; //伸缩型数组成员
};
2) 声明一个指向struct flex类型的指针,用malloc()分配足够的空间,以存储结构体的常规内容和伸缩数组成员的额外空间
例: struct flex *p; //声明一个指向结构体的指针
p = malloc(sizeof(struct flex)+5*sizeof(double)); //请求为一个结构体和一个数组分配存储空间
p->count = 5; //设置count成员
p->scores[2] = 16.8; //访问伸缩型数组的一个元素
3) 带伸缩型数组成员的结构体不能用结构体进行赋值或拷贝
例: struct flex *p1,*p2; //*p1,*p2都是struct flex类型结构体
...
*p2 = *p1; //只能拷贝除伸缩型数组成员以外的其他成员
4) 传递给函数时要传递结构体的地址
5) 不能使用带伸缩型数组成员的结构体作为数组成员或另一个结构体的成员
9.匿名结构体(C11)
例: 使用嵌套的匿名成员结构定义person
struct person //声明
{
int id;
struct {char first[20];char last[20];}; //匿名结构
};
struct person teddy = {8483,{"hello","world"}}; //创建并初始化结构体变量
puts(teddy.first); //访问结构体变量
10.使用结构数组的函数
1) 可以把数组名作为数组中的第一个结构的地址传递给函数
2) 可以使用数组表示法访问数组中的其他结构
三、把结构体内容保存到文件中
1) 数据库文件可以包含任意数量的此类数据对象,储存在一个结构中的整套信息被称为记录,单独的项被称为字段
2) 如果pbook标识一个文件流:
例: fwrite(&primer,sizeof(struct book),1,pbooks);
定位到primer结构变量开始的位置,并把结构中所有的字节都拷贝到与pbooks相关的文件中
sizeof(struct book)告诉函数待拷贝的一块数据的大小,1表明一次拷贝一块数据
带相同参数的fread()函数从文件中拷贝一块结构大小的数据到&primer指向的位置
四、链式结构
1) 通常每个结构都包含一两个数据项和一两个指向其他同类型结构的指针
2) 这些指针把一个结构和另一个结构链接起来,并提供一种路径能遍历整个彼此连接的结构
五、联合简介
1) 联合是一种数据类型,它能在同一个内存空间中存储不同的数据类型(不是同时存储)
2) 使用联合类型的数组,其中的联合都大小相等,每个联合可以储存各种数据类型
3) 例:
union hold{
int digit;
double bigfl;
char letter;
};
4) 根据以上形式声明的结构可以储存一个int类型、一个double类型和char类型的值
5) 声明的联合只能储存一个int类型的值或一个double类型的值或char类型的值
6) 例:
union hold fit; //hold类型的联合变量
union hold save[10]; //内含10个联合变量的数组
union hold *pu; //指向hold类型联合变量的指针
1.第一个声明创建了一个单独的联合变量fit,编译器分配足够的空间储存联合声明中占用最大字节的类型(double:8字节)
2.第二个声明创建了一个数组save,内含10个元素,每个元素都是8字节
3.第三个声明创建了一个指针,该指针变量储存hold类型联合变量的地址
7) 初始化联合的三种方法:
1.把一个联合初始化为另一个同类型的联合
union hold A;
A.letter = 'Z';
union hold B = A; //用另一个联合来初始化
2.union hold C = {88}; //初始化联合的digit成员
3.union hold D = {.bigfl}; //指定初始化器
1.使用联合
fit.digit = 23; //把23储存在fit,占4字节
fit.bigfl = 2.0; //清除23,储存2.0,占8字节
fit.letter = 'h'; //清除2.0,储存h,占1字节
在联合中,一次只储存一个值,即使有足够的空间
2.匿名联合(C11)
匿名联合和匿名结构的工作原理相同,即匿名联合是一个结构或联合的无名联合成员
六、枚举类型
1) 可以用枚举类型声明符号名称来表示整型常量
2) 使用enum关键字,可以创建一个新"类型"并指定它可具有的值
//实际上,enum常量是int类型(只要能使用int类型的地方就可以使用枚举类型)
3) 例:
enum spectrum{red,orange,yellow,green,blue,violet}; //花括号括起来的被称为枚举符
enum spectrum color;
1.第一个声明创建了spectrum作为标记名,允许把enum spectrum作为一个类型名使用
2.第二个声明时color作为该类型的变量(可能的值为: red,orange,yellow,green,blue,violet)
color = blue;
if(color == yellow)
...
for(color = red;color<=violet;color++)
4) 从技术层面看,枚举符是int类型的常量
例: printf("red = %d,orange = %d\n",red,orange);
输出为: red = 0,orange = 1;
//red成为一个有名称的常量,代表整数0;类似的,其他标识符都是有名称的常量
// 默认情况下,枚举列表中的常量都被赋予0、1、2等
//在枚举声明中,可以为枚举常量指定整数值
enum levels{low = 100,medium = 500,high = 2000};
//如果只给一个枚举常量赋值,没有对后面的枚举常量赋值,那么后面的常量会被赋予后续的值
七、typedef简介
1) typedef与#define的不同:
与#define不同,typedef创建的符号名只受限于类型,不能用于值
typedef由编译器解释,不是预处理器
在其受限范围内,typedef比#define更灵活
2) typedef的工作原理:
例: typedef char BYTE;
BYTE x,y[10],*z; //使用BYTE来定义变量
//该定义的作用域取决于tepedef定义所在的位置,如果定义在函数中,就具有局部作用域;如果在函数外,则具有文件作用域
3) 通常,typedef定义中用大写字母表示被定义的名称,以提醒用户这个类型名实际上是一个符号缩写(也可用小写)
4) 可以把typedef用于结构体:
typedef struct complex{ //标签complex可省略
float real;
float imag;
}COMPLEX; //然后便可使用COMPLEX类型代替complex结构来表示复数
5) 使用typedef时,typedef并没有创建任何新类型,只是为了某个已存在的类型增加了一个方便使用的标签
八、其他复杂的声明
例:
int borad[8][8]; //声明一个内含int数组的数组
int **ptr; //声明一个指向指针的指针,被指向的指针指向int
int *risks[10]; //声明一个内含10个元素的数组,每个元素都是一个指向int的指针
int (* rusks)[10]; //声明一个指向数组的指针,该数组内含10个int类型的值
int *oof[3][4]; //声明一个3*4的二维数组,每个元素都是指向int的指针
int (*uuf)[3][4]; //声明一个指向3*4二维数组的指针,该数组中内含int类型值
int (* uof[3])[4]; //声明一个内含3个指针元素的数组,其中每个指针都指向一个内含4个int类型元素的数组
1) 数组名后面的[]和函数名后面的()具有相同的优先级,它们比*的优先级高
2) []和()的优先级相同,都是从左往右结合
例:
typedef int arr[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类型值的数组
九、函数和指针
1) 通常,函数指针常用作另一个函数的参数,告诉该函数要使用哪一个函数
2) 指向函数的指针中储存着函数代码的起始处的地址
3) 声明一个数据指针时,必须声明指针所指向的数据类型;声明一个函数指针时,必须声明指针指向的函数类型
4) 为了指明函数类型,要指明函数签名,即函数的返回类型和形参类型
例: void func(char *); //形参为char *类型,返回类型为void
void (*p)(char *); //p是一个指向函数的指针,(*p)是一个参数列表为char *,返回类型为void的函数
5) 要声明一个指向特定类型函数的指针,可以先声明一个该类型的函数,然后把函数名替换成(*p)形式的表达式
例: void func1(char *);
void func2(char *);
int round(double);
void (*p)(char *);
char arr[] = "hello world";
p = func1; //正确,func1是该类型函数的地址
(*p)(arr); //正确
p = func2; //正确,func2是该类型函数的地址
p(arr); //正确
p = round; //错误,round与指针类型不匹配
p = func2(); //错误,func2()不是地址;func2()没有返回值,不能用于赋值语句
例: void show(void (*fp)(char *),char *str);
//fp形参是一个函数指针,str是一个数据指针
show(func1,arr); //show()使用func1()函数: fp = func1;
show(pf,arr); //show()使用pf指向的函数:fp = pf;
6) 把带返回值的函数作为参数传递给另一个函数有两种不同的方法:
func1(sqrt); //传递sqrt()函数的地址
func2(sqrt(2.0)); //传递sqrt()函数的返回值
函数原型中的函数名 | int func(int x,int y); |
函数调用中的函数名 | status = func(a,b); |
函数定义中的函数名 | int func(int x,int y){...} |
在赋值表达式语句中作为指针的函数名 | pfunct = func; |
作为指针参数的函数名 | slowsort(arr,n,func) |
7) 使用函数指针举例:
#include<stdio.h>
#include<string.h>
#include<ctype.h>
#define LEN 81
char *s_gets(char *st, int n);
char showmenu();
void eatline(); //读取至行末尾
void show(void(*fp)(char *), char *str);
void ToUpper(char *); //把字符串转换为大写
void ToLower(char *); //把字符串转换成小写
void Transpose(char *); //大小写转置
void Dummy(char *); //不更改字符串
int main()
{
char line[LEN];
char copy[LEN];
char choice;
void(*pfun)(char *) = Dummy;//声明一个函数指针,被指向的函数接受char*类型的参数,无返回值
puts("输入字符串(空行退出): ");
while (s_gets(line, LEN) != NULL && line[0] != '\0')
{
while ((choice = showmenu()) != '\n')
{
switch (choice)
{
case 'u':pfun = ToUpper; break;
case 'l':pfun = ToLower; break;
case 't':pfun = Transpose; break;
case'o':pfun = Dummy; break;
}
strcpy(copy, line); //为show()函数拷贝一份
show(pfun, line); //根据用户的选择,使用选定的函数
}
puts("输入字符串(空行退出): ");
}
puts("正确退出!");
return 0;
}
char *s_gets(char *st, int n)
{
char *p;
int i = 0;
p = fgets(st, n, stdin);
if (p)
{
while (st[i] != '\n'&&st[i] != '\0')
i++;
if (st[i] == '\n')
st[i] = '\0';
else
{
while (getchar() != '\n')
continue;
}
}
return p;
}
char showmenu()
{
char ans;
puts("请输入菜单选项: ");
puts("u) uppercase l) lowercase");
puts("t) transposed case o) original case");
puts("n) next string");
ans = getchar(); //获取用户输入
ans =(char)towlower(ans); //转换为小写
eatline(); //清理输入行
while (strchr("ulton", ans) == NULL)
{
puts("请输入正确选项: ");
ans =(char)towlower(getchar());
eatline();
}
return ans;
}
void eatline()
{
while (getchar() != '\n')
continue;
}
void ToUpper(char *str)
{
while (*str)
{
*str = toupper(*str);
str++;
}
}
void ToLower(char *str)
{
while (*str)
{
*str = tolower(*str);
str++;
}
}
void Transpose(char *str)
{
while (*str)
{
if (islower(*str))
*str = toupper(*str);
else if (isupper(*str))
*str = tolower(*str);
str++;
}
}
void Dummy(char *str)
{
}
void show(void(*fp)(char *), char *str)
{
(*fp)(str); //把用户选定的函数作用于str
puts(str); //显示结果
}