p.s. 从十四章写起,前面的一些章节不想补(二周目再说)
因为是基于书上的东西,我会做一些笔记,还有一些自己的理解(毕竟不是看原著只是看中文译本),略过的部分读者自行看书,有些是比较简单读者看书就懂,我就不再赘述。绝对不是懒[微笑]。
14.2 建立结构声明
14.3定义结构变量
14.3.1初始化结构
14.3.2访问结构体成员
14.3.3结构体初始化器
14.4结构体数组
14.5嵌套结构
14.6指向结构的指针
14.6.1声明和初始化结构指针
14.6.2用指针访问成员
14.7.1传递结构成员
14.7.2传递结构的地址
14.7.3传递结构
14.7.4其他结构特性
14.7.5 结构和结构指针的选择
14.7.6结构中的字符数组和字符指针
14.8把结构内容保存到文件中
14.10联合介绍(union)
14.2(定义)
结构的定义语法,struct可以不跟名字(不推荐),然后是变量(成员)的定义,变量的定义和平常我们的定义变量没有区别。
struct name{
data_type value_name;
};
struct { //不推荐
data_type value_name;
};
14.3(结构体变量)
这是两种定义的方法,在实际情况下我们我们会把struct放在.h里定义变量并不是一个好习惯,我们应该在使用的时候再定义,并注意其它的生存周期。
struct name{ //可以没有name 对照上面
data_type value_name;
}joe;
struct name joe,*Moon,stu[10];
14.3.1(初始化)
数值,字符或字符串之间,只需要用逗号隔开即可,如果是更复杂的结构我们通过换行来提高阅读性。(ANSI之前,不能用自动变量初始化结构:ANSI之后可以同任意存储类型),初始化与初始化数组类似。
struct rectangle{
float length;
float width;
};
struct rectangle r1={4.0,2.0};
注意 初始化结构体和类型存储期
第12章中提到,如果初始化一个具有静态存储时期的变量,只能使用常量。这条规则同样使用与结构体。如果存储时期是自动的,列表的值就不必是常量了。
14.3.2(访问结构成员)
结构本身类似于本质上是一个超级数组,我们可以这样访问成员(变量)
r1.length=2;
printf("%f\n",r1.length);
scanf("%f",&r1.width);
这里由于 . 的运算优先级比 & 更高所以&r1.width
等同于&(r1.width)
14.3.3(结构体初始化器)
#include "stdio.h"
struct man{
int length;
int width;
int weight;
};
int main(void){
struct man p1={.weight=10,
.length=21,
.width=2,
3
};
printf("%d%2d%2d\n",p1.length,p1.width,p1.weight);
}
最后一次赋值才是它真正的值,但赋值其实是按顺序的赋值是根据前一个指向的成员的位置确认下一个成员即便没有成员名。
struct man p1={
.length=21,
.width=2,
.weight=10,
3
};
这样的一个写法是3只会警告为无效,而不会出错,由此可见编译器是按照指向去初始化的,如果只是简单的struct man p1={21,2,10}
初始化编译器会顺序初始化,而我们使用骚操作也是可行的
struct man p1={.length=21,
.width=2,
.weight=10,
.width=3,
3,
.length=10,
9
};
//为骚而骚了,实际上并不会这么使用
14.4结构体数组
struct man people[100]
这不是一个合适的做法,因为是自动存储类别变量,其中的信息会存到栈中,如果数量过大可能会导致溢出,这时可以设置编译器中栈的大小,又或者可以创建静态或者外部数组。(我们并不建议过大的数组)。
代码14.2(部分代码)
//只是大概情况,绝对没偷懒[微笑]
#define len 40
struct book{
char title[len];
char author[len];
float value;
};
int main(){
......
scanf("%f",&library[count++].value);
......
return 0;
}
14.5嵌套结构
结构嵌结构其实是十分常见的,不过既然是结构体,那自然可以套用结构体声明的规则了。同时我们用 . 或者->来访问结构体变量的结构体成员的成员(该用哪个我们视情况而定)。
#define len 30
struct name{
char first_name[len];
char last_name[len];
};
struct book{
char title[len];
struct name author;
float value;
};
int main(){
struct book mybook={"c语言从入门到放弃",
{"虾说","吴"},
54.32
};
printf("hello %s%s",mybook.author.last_name,mybook.author.first_name);
return 0;
}
14.6(指向结构的指针)
喜欢使用指针的人如果得知能够最使用指向结构的指针会十分高兴。原因有四
第一: 就像指向数组的指针比数组本身更容易操作(排序中会很明显),指向结构的指针通常比结构本身更容易操作。
第二: 在早期的c实现中,结构不能作为参数被传递给函数。但指向结构的指针可以。
第三:即便可以传递结构,传递指针也更有效率。
第四:许多的数据表示,都使用了包含指向其他结构的指针的结构。
14.6.1(声明和初始化结构指针)
struct book mybook;
struct book papers[10];
struct book *p=NULL; //置空是个好习惯
p=&mybook;
p=&papers[0];
book结构的大小的94(假设float是4)字节,但是在一些系统中,一个结构的大小可能比各成员大小的总和还大一点,这是因为系统对数据进行校准过程中产生了“间隙”,例如有些系统必须把每个成员放在偶数地址上,或是4的倍数的地址上。
14.6.2(用指针访问成员)
常见的访问方式.
和->
,这两者区别,我们可以通俗的理解,一个实际的变量如 papers[0],*p我们用.
访问成员,而一些地址如p, papers, (&mybook)我们使用->
来访问成员。
p=&mybook;
mybook.value == p->value ==(*p).value ==(&mybook).value;
//*的优先级低于.,必须先用括号,同理&也要加括号
14.7.1-4(传递成员、地址、结构和其他特性)
char* sum1(char *,char *); //传递成员
struct book *sum2(struct book *); //传递地址
struct book sum3(struct book); //传递结构
struct book mybook={"c语言从入门到放弃",
{"虾说","吴"},
54.32
};
full_name1(mybook.names.first_name,mybook.names,last_name);
full_name2(&mybook);
full_name3(mybook);
struct book youbook=mybook;
现在的c(第六版的出版年)包括ANIS C,允许结构作为参数传递,还能作为参数传递。这里还是讲究数据类型和函数的内容
14.7.5(结构和结构指针的选择)
把指针作为参数有两个优点:任何C都可以实现,执行速度快,只要地址。缺点是无法保护数据在调用函数的某些操作可能使数据发生改变,ANSI C用const解决了这个问题。可以说是克服了缺点但是实际应用难免修改某个成员的值。
而传递结构的优点是,只是在修改副本,保护了原始数据,当然也更易阅读。缺点是增加了内存空间和传递时间。当他只使用少数的参数时会显得特别不讨好。
这样说来,指针样样都好,但是程序的可阅读性和维护性是你应该想的(可能你在写一个简单的算法和demo时想不到这些因素)。
14.7.6结构的字符数组和指针
如果编译器比较宽容的话,代码在运行时不会出错。我用的是gcc 版本 6.4.1 2017 07 27 (Red Hat 6.4.1-1) (GCC)
#define len 30
struct name1{
char first_name[len];
char last_name[len];
};
struct name2 {
char *first;
char *last;
};
struct name1 programer;
name2 coder;
scanf("%s",programer.first_name);
scanf("%s",coder.first);
printf("pro first %s in %p\n",programer.first_name,&programer.first_name);
printf("cod first %s in %p\n",coder.first,coder.first);
如果你知道字符常量,我们简单的测了一下就出了问题
int main() {
char *str="abc";
struct name1 programer;
struct name2 coder;
scanf("%s", programer.first_name);
scanf("%s", coder.first);
printf("str %s in %p\n",str,str);
printf("pro first %s in %p\n",programer.first_name,&programer.first_name);
printf("cod first %s in %p\n",coder.first,coder.first);
return 0;
}
指针太骚还是要慎用又或是我们用
coder.first=(char *)malloc(···);
给他一块专属内存,不需要的时候free掉free(coder.first)
。当然指针管理数据还是十分收到青睐。
14.7.8(复合型字面量 c99 )
这相当于即用即定义,和寻常的变量一样,但是它没有名字并不一个好事。如果它是全局变量则是静态存储期,在块中则是自动存储期。复合字面量和普通初始化列表采取一样的规则,骚操作指定初始化器一样可以使用。
14.8(把结构内容保存到文件中)
以二进制表示法储存数据的缺点是,不同的系统可能使不同的二进制表示法,所以数据文件可能不具有移值性。甚至在同一个系统,不同编译器设置也可能导致不同的二进制布局。
14.10(联合简介)
#include "stdio.h"
union all {
int a;
_Bool b;
char c;
};
int main() {
union all test;
scanf("%d", &test.a);
printf("%d,%d,%c\n", test.a, test.b, test.c);
scanf("%d", &test.b);
printf("%d,%d,%c\n", test.a, test.b, test.c);
scanf("%d", &test.c);
printf("%d,%d,%c\n", test.a, test.b, test.c);
}
即
&test==&test.a==&test.b==&test.c
使用初始化结构器的情况,有区别于结构体的初始化(没骚起来…)
int main() {
union all test = {.b=3,89,.a=77,0};
printf("%d",test);
return 0;
}