1、结构声明
结构声明并没有创建一个实际的数据对象,而是描述了组成这类对象的元素,类似于C++的模板。没有让计算机为数据分配空间。
例如:
struct book{
char title[MAXTITL];
char author[MAXAUTL];
float value;
};
但是,如果
struct book library;
编译器会创建一个library,并使用book模板为该变量分配空间:一个具有MAXTITL个元素的char数组,一个具有MAXAUTL个元素
的char数组和一个float变量。
在结构变量的生命中,struct book所起的作用就像int和float在较简单的声明中的作用一样。
如果初始化一个具有静态存储时期(比如静态外部链接、静态内部链接或静态空链接)的变量,只能使用常量值。这条规则同样
适用于结构,如果初始化一个具有静态存储时期的结构,初始化项目列表中的值必须是常量表达式。如果存储时期是自动的,
列表中的值就不必是常量了。
2、比如:
struct{
char title[MAXTIL];
char author[MAXAUTL];
float value;
}library;
正像数组一样,跟在一个指定初始化项目之后的常规初始化项目为跟在指定成员后的成员提供了初始值。另外,对特定成员的
最后一次赋值是它实际获得的值。如:
struct book gift={ .value=18.90,
.author="James Broadfool",
0.25};
这将把值0.25赋给成员value,因为它在结构声明中紧跟在author成员之后,新的值0.25代替了早先的赋值18.90.
3、输入字符串,如果这个字符串为空,退出
while(gets(str)!=NULL && str[0]!='\0')
因为gets读到换行符时,删除换行符,然后把前面的内容赋给str,并在末尾添加'\0',所以如果没有输入任何字符然后回车的
话就会退出循环。
while(getchar()!='\n')
coutinue;
scanf()函数忽略掉空格和换行符,这段代码弥补了这个不足。当您输入例如:
12.50[enter]
这个语句传送了下面的字符序列:
12.50\n
scanf()函数读入了1、2、.、5和0,但把\n留在输入流中,等待下一个读入语句处理。如果没有前面的预处理的代码,下一个读
入语句gets就会把留下来的换行符当做空行读入,程序会以为用户发出了一个停止信号。
4、声明和初始化结构指针
和数组不同,一个结构的名字不是该结构的地址,必须使用&运算符。如:
struct book{
int ID;
}barney;
struct book *p;
p=&barney;
5、使用结构地址
#include<stdio.h>
#define FUNDLEN 50
struct funds{
char bank[FUNDLEN];
double bandfund;
char save[FUNDLEN];
double savefund;
};
double sum(const struct funds*);
int main()
{
struct funds stan={
"Garlic-Melon Bank",
3024.72,
"Lucky's Savings and Loan",
9237.11
};
printf("Stan has a total of $%.2f\n",sum(&stan));
return 0;
}
double sum(const struct funds *money)
{
return (money->bandfund+money->savefund);
}
显示结果为:
Stan has a total of $12261.83
6、把结构作为参数传递,如:
#include<stdio.h>
#define FUNDLEN 50
struct funds{
char bank[FUNDLEN];
double bandfund;
char save[FUNDLEN];
double savefund;
};
double sum(const struct funds);
int main()
{
struct funds stan={
"Garlic-Melon Bank",
3024.72,
"Lucky's Savings and Loan",
9237.11
};
printf("Stan has a total of $%.2f\n",sum(stan));
return 0;
}
double sum(const struct funds money)
{
return (money.bandfund+money.savefund);
}
显示结果为:
Stan has a total of $12261.83
6中创建自动变量money,这个结构的成员被初始化为stan结构的相应成员取值的副本。但是5那个使用指针的是原有结构本身。
7、其他结构特性
允许吧一个结构赋值给另一个结构,不能对数组这样做。也就说,如果n_data和o_data是同一类型的机构,可以像下面这样做:
o_data=a_data;
这就使o_data的每个成员都被赋成a_data相应成员的值,即使有一个成员是数组也照样完成完成赋值。
比如:
代码一:
#include<stdio.h>
#include<string.h>
struct namect{
char fname[20];
char lname[20];
int letters;
};
void getinfo(struct namect *);
void makeinfo(struct namect *);
void showinfo(const struct namect *);
int main()
{
struct namect person;
getinfo(&person);
makeinfo(&person);
showinfo(&person);
return 0;
}
void getinfo(struct namect *pst)
{
printf("Please enter your first name.\n");
gets(pst->fname);
printf("Please enter your last name.\n");
gets(pst->lname);
}
void makeinfo(struct namect *pst)
{
pst->letters=strlen(pst->fname)+strlen(pst->lname);
}
void showinfo(const struct namect *pst)
{
printf("%s %s,your name contains %d letters.\n",pst->fname,pst->lname,pst->letters);
}
显示结果为:
Please enter your first name.
Viola(手动输入)
Please enter your last name.
Plunderfest(手动输入)
Viola Plunderfest,your name contains 16 letters.
代码2:
#include<stdio.h>
#include<string.h>
struct namect{
char fname[20];
char lname[20];
int letters;
};
struct namect getinfo();
struct namect makeinfo(struct namect);
void showinfo(const struct namect);
int main()
{
struct namect person;
person=getinfo();
person=makeinfo(person);
showinfo(person);
return 0;
}
struct namect getinfo()
{
struct namect pst;
printf("Please enter your first name.\n");
gets(pst.fname);
printf("Please enter your last name.\n");
gets(pst.lname);
return pst;
}
struct namect makeinfo(struct namect pst)
{
pst.letters=strlen(pst.fname)+strlen(pst.lname);
return pst;
}
void showinfo(const struct namect pst)
{
printf("%s %s,your name contains %d letters.\n",pst.fname,pst.lname,pst.letters);
}
在代码2中,3个函数中的每一个都创建了自己的person副本,因此该程序不是只使用1个结构,而是使用了4个不同的结构。
两者的区别:
用指针作为参数的方法的缺点是缺少对数据的保护,可以用const限定词保护;
把结构作为参数传递的一个优点是函数处理的是原始数据的副本,比直接处理原始数据安全。
8、在结构中使用字符数组还是字符指针
代码1:
#define LEN 20
struct names{
char first[LEN];
char last[LEN];
};
代码2:
struct pnames{
char *first;
char *last;
};
代码3:
struct names veep={"Talia","Summers"};
struct pnames treas={"Brad","Fallingjaw"};
printf("%s and %s\n",veep.first,treas.first);
代码1和代码2放到代码3中都能正常运行,但是字符串存储在哪里。对于structnames变量veep来说,字符串存储在结构内部;这
个结构总共分配了40字节来存放两个字符串。然而,对于structpnames变量treas来说,字符串存储在编译器存储字符常量的任
何地方。这个结构中存放的只是两个地址而已,在我们系统中总共占用8个字节。struct pnames结构不为字符串分配任何存储
空间。只适用于再另外的地方已经为字符串分配了空间。简单地说,pnames结构中的指针应该只用来管理那些已创建的而且在
程序其他地方已经分配过空间的字符串。
如果结构指针在处理字符串的时候使用malloc()分配内存,那么指针可以存放地址,如:
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
struct namect{
char *fname;
char *lname;
int letters;
};
void getinfo(struct namect *);
void makeinfo(struct namect *);
void showinfo(const struct namect *);
void cleanup(struct namect *);
int main()
{
struct namect person;
getinfo(&person);
makeinfo(&person);
showinfo(&person);
cleanup(&person);
return 0;
}
void getinfo(struct namect *pst)
{
char temp[81];
printf("Please enter your first name.\n");
gets(temp);
pst->fname=(char*)malloc(strlen(temp)+1);
strcpy(pst->fname,temp);
printf("Please enter your last name.\n");
gets(temp);
pst->lname=(char*)malloc(strlen(temp)+1);
strcpy(pst->lname,temp);
}
void makeinfo(struct namect *pst)
{
pst->letters=strlen(pst->fname)+strlen(pst->lname);
}
void showinfo(const struct namect *pst)
{
printf("%s %s,your name contains %d letters.\n",pst->fname,pst->lname,pst->letters);
}
void cleanup(struct namect *pst)
{
free(pst->fname);
free(pst->lname);
}
8、复合文字和结构
(struct book){"The Idiot","Fyodor Dostoyevsky",6.99};
例如:
#include<stdio.h>
#define MAXTITL 41
#define MAXAUTL 31
struct book{
char title[MAXTITL];
char author[MAXTITL];
float value;
};
int main()
{
struct book readfirst;
int score;
printf("Enter test score:");
scanf("%d",&score);
if(score>=84)
readfirst=(struct book){"Crime and Punishment",
"Fyodor Dostoyevsky",
9.99};
else
readfirst=(struct book){"Mr. Bouncy's Nice Hat",
"Fred Winsome",
5.99};
printf("Your assigned reading:\n");
printf("%s by %s $%.2f \n",readfirst.title,readfirst.author,readfirst.value);
return 0;
}
9、伸缩型数组成员
a、伸缩型数组成员必须最后一个数组成员;
b、结构中必须至少有一个其他成员;
c、伸缩型数组就像普通数组一样声明,除了它的括号内是空的。
例如:
struct flex
{
int count;
double average;
double scores[];
};
如果声明了一个struct flex类型的变量,您不能使用scores做任何事情,因为没有为它分配任何内存空间。怎么用呢?声明
一个指向struct flex类型的指针,然后使用malloc()来分配足够的空间,以存放struct flex结构的常规内容和伸缩型数组
成员需要的任何额外空间。如,scores表示含有5个double数组,可以这样:
struct flex *pf;
pf=malloc(sizeof(struct flex)+5*sizeof(double));
现在有一个足够大的内存,以存储count、average和含有5个double数值的数组。
如:
#include<stdio.h>
#include<stdlib.h>
struct flex{
int count;
double average;
double scores[];
};
void showFlex(const struct flex *);
int main()
{
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<n;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(const struct flex *pf)
{
int i=0;
for(;i<pf->count;i++)
printf("%g\t",pf->scores[i]);
printf("\naverage=%g\n",pf->average);
}
10、使用结构数组的函数
#include<stdio.h>
#define FUNDLEN 50
#define N 2
struct funds{
char bank[FUNDLEN];
double bankfund;
char save[FUNDLEN];
double savefund;
};
double sum(const struct funds pf[],int n);
int main()
{
struct funds jones[N]={
{
"Garlic-Melon Bank",
3023.72,
"Lucky's Savings and Loan",
9237.11
},
{
"Honest Jack's Bank",
3534.28,
"Party Time Savings",
3203.89
}
};
printf("The Joneses have a total of $%.2f.\n",sum(jones,N));
return 0;
}
double sum(const struct funds pf[],int n)
{
double total;
int i;
for(i=0,total=0;i<N;i++)
total+=pf[i].bankfund+pf[i].savefund;
return total;
}
显示结果为:
The Joneses have a total of $18999.00.
11、共享的名字空间
意思就是说,名字相同不同作用域的两个变量不会冲突,而名字相同并在相同作用域中的两个变量就会冲突。例如:
struct rect {double x;double y; };
int rect;
12、typedef简介
typedef与#define的区别:
a、typedef给出的符号名称仅限于对类型,而不是对值;
b、typedef的解释由编译器,而不是预处理器执行;
c、虽然范围有限,但在受限范围内,typedef比#define更灵活。
如:
typedef char * STRING;
则
STRING name,sign;
意思是:
char *name,*sign;
但是假设这样:
#define STRING char *
那么
STRING name,sign;
被翻译成:
char *name,sign;
typedef的原因之一是为经常出现的类型创建一个方便的、可识别的名称。
typedef struct { double x;double y; } rect;
假设像下面这样使用typedef定义的类型名:
rect r1={3.0,6.0};
rect r2;
r2=r1;
翻译成:
struct { double x; double y; } r1={ 3.0; 6.0 };
struct { double x; double y; } r2;
r2=r1;
typedef unsigned char BYTE;
BYTE x,y[10],*z;
typedef的作用域取决于typedef语句所在的位置。如果定义是在一个函数内部,它的作用域就是局部的,限定在该函数内,如果
定义是在函数外部,它将具有全局作用域。
当使用typedef时,要记住它并不创建新的类型;它只是创建了便于使用的标签。
13、奇特的声明
声明时可以使用的修饰符
修饰符 含义
* 表示一个指针
() 表示一个函数
[] 表示一个数组
下面类型:
int board[8][8]; //int数组的数组
int **ptr; //指向int的指针的指针
int *risks[10]; //具有10个元素的数组,每个元素是一个指向int的指针
int (*risks)[10]; //一个指针,指向具有10个元素的数组
int *cof[3][4]; //一个3*4数组,每个元素是一个指向int的指针
int (*uuf)[3][4]; //一个指针,指向3*4的int数组
int (*uof[3])[4]; //一个具有3个元素的数组,每个元素是一个指向具有4个元素的int数组的指针
1、表示一个数组的[]和表示一个函数的()具有同样的优先级,这个优先级高于间接运算符*的优先级,所以
int *risks[10];表示有10个元素的数组,每个元素是指针。
2、[]和()都是从左到右进行结合的,所以:
int goods[12][50];表示goods是一个由12个具有50个int值的数组构成的数组。
3、[]和()具有相同优先级,但由于它们是从左到右结合的,所以:
int (*rusks)[10];是一个指针,这个指针指向了具有10个元素的数组。
4、同理:int *oof[3][4];[3]具有比*更高优先级,并且根据从左到右,它的优先级比[4]高,所以它是一个3*4的指向int
的指针数组,需要为12个指针留出存储空间;
5、int (*uuf)[3][4];圆括号使修饰符*具有更高优先级,所以uuf就是一个指向3*4的int数组的是真。需要为一个单个指针
留出存储空间。
同理:
char *fump(); //返回指向char的指针的数组
char (*frump)(); //指向返回类型为char的函数的指针
char (*flump[3])(); //由3个指针组成的数组,每个指针指向返回类型为char的函数
14、函数和指针
一个函数指针可以作为另一个函数的参数,告诉第二个函数使用哪一个函数。
同样,函数也有地址,这是因为函数的机器语言实现是由载入到内存的代码组成。指向函数的指针中保存着函数代码起始处
的地址。
例如:
void ToUpper(char *);
函数ToUpper()的类型是“具有char*类型的参量,返回类型为void的函数”,要声明指向这种类型的函数的指针pf,可以这样
void(*pf)(char*);
第一队圆括号将运算符*和pf结合,意味着pf是一个指向函数的指针,使得(*pf)是一个函数并使(char*)作为该函数的参量
列表,void作为返回类型。
有了函数指针之后,可以把适当类型的函数的地址赋给它。
void ToUpper(char *);
void ToLower(char *);
int round(double);
void (*pf)(char*);
pf=ToUpper; //合法,ToUpper是函数ToUpper()的地址
pf=ToLower; //合法
pf=round; //无效,类型不对
pf=ToLower(); //无效,ToLower()不是地址
char mis[]="Hello!";
pf=ToUpper;
(*pf)(mis) //合法,把ToUpper作用于mis