1.1 结构体的基础知识
结构是一些值的集合,这些值称为成员变量,结构的每一个成员可以是不同类型的变量。
数组:一组相同元素的集合
结构体:其实是一组不一定相同类型元素的集合
1.2结构的声明
struct tag
{
member-list;
}variable-list;
struct Student {
char name[50];
int age ;
float score;
}
这个例子中就是声明了结构体的类型
在这里,struct student 定义了一个新的数据类型,名为 student。在这个数据类型中,有三个成员:name,age和score 。这个声明包含了结构体的名称(student )和包含的数据类型(字符串 name[50],整数 age和浮点数 score)。然后,你可以使用这个已经定义的结构体类型 student 去定义变量。
声明类型和声明变量
类型不占内存,只有用类型创建变量后才占用内存。
1.3 结构成员的类型
例如描述一个学生:
typedef struct Stu
{
char name[20];//名字
int age;//年龄
char sex[5];//性别
char id[20];//学号
}Stu;//分号不能丢
结构的成员可以是标量、数组、指针,甚至是其他结构体。
1.4 结构体变量的定义和初始
C语言允许在声明结构体类型的同时创建结构体变量。这用到了结构体的完整定义形式,即将变量名放在定义的最后,如下所示:
structStudent{
char name[50];
int age;
float score;
} student1, student2;
以上代码将student1和student2定义为struct Student类型的变量。因此,你可以直接使用student1和student2。
例如,student1.age = 20; 就是合法的。
同时,这个定义也包含了结构体类型struct Student,所以你仍然可以使用它来定义更多的变量:struct Student student3;。
最前面我们对比了一下数组和结构体记得吗
他们是很类似的区别是成员的类型多种多样
那么初始化也是类似的。数组初始化是等号后面一个花括号然后放元素
不过要按照顺序来初始化
如果成员里面有数组也可以这样初始化
就是花括号里面再放一个花括号
这里是创建了一个变量s并且给它初始化了一些值
如果要初始化的结构体里面的成员还有结构体呢,有如何初始化呢,以这里的structB为例子
这里就是创建了B这个结构体,他的成员有结构体,初始化的方式的在加一个花括号{}
这里还展示了如何访问结构体用到了.这个结构成员访问操作符。
也可以不按照顺序了,想象一下一个结构体里面的成员多了,按照顺序初始化是不是会容易出错
在C99标准之后,允许了指定初始化器(designatedinitializer),它可以让你按任意顺序初始化结构体变量的成员。这在大的结构体中非常有用,因为你不需要记住所有成员的顺序:
通过点操作符来实现:
struct S s3 = {.arr = "ABC", .p = NULL, .a =1};
struct Student student1={.name = "John Doe",.id=1,.age = 18};
2. 结构体成员的访问
两种访问的方式
结构体变量 . 结构体成员变量
结构体指针 -> 结构体成员变量
点操作符
箭头操作符
结构体变量 + 点操作符
struct S s; strcpy(s.name, "zhangsan");//使用.访问name成员 s.age = 20;//使用.访问age成员
来看一个错误:
把常量字符串“张三”拷贝到一个数组中
struct Stu
{
char name [20];
int age ;
};
void set_stu(struct Stu t)
{
t.age = 20;
t.name = "张三";
}
int main ()
{
struct Stu s = {0};
set_stu(s);
return 0;
}
有问题的写法,why?
因为name是一个数组,数组名是地址,怎么能把张三放在一个地址呢
应该把张三放在地址指向的空间里面
void set_stu(struct Stu t)
{
t.age =20;
strcpy(t.name, "张三")
}
还是错误的,为什么呢
因为这里函数是传值调用,也就是说函数里面t这个结构体的变化和主函数里面的s结构体没有关系,修改t并不会改变s。t只是s的一份临时拷贝
所以应该转址调用
int main ()
{
struct Stu s = {0};
set_stu(&s);
return 0;
}
void set_stu(struct Stu *ps)
{
(*ps).age = 20;
//等价于 ps->age =20;
strcpy((*ps).name, "张三")
//等价于 (ps->name,"张三")
}
结构体指针 + 箭头操作符
struct Stu { char name[20]; int age; }; void print(struct Stu* ps) { printf("name = %s age = %d\n", (*ps).name, (*ps).age); //使用结构体指针访问指向对象的成员 printf("name = %s age = %d\n", ps->name, ps->age); } int main() { struct Stu s = {"zhangsan", 20}; print(&s);//结构体地址传参 return 0; }
3. 结构体传参
struct S
{
int data[1000];
int num;
};
struct S s = {{1,2,3,4}, 1000};
//结构体传参
void print1(struct S s)
{
printf("%d\n", s.num);
}
//结构体地址传参
void print2(struct S* ps)
{
printf("%d\n", ps->num);
}
int main()
{
print1(s); //传结构体
print2(&s); //传地址
return 0;
}
上面的 print1 和 print2 函数哪个好些?
答案是:首选print2函数。
原因:
函数传参的时候,参数是需要压栈的。 如果传递一个结构体对象的时候,结构体过大,参数压栈的的系统开销比较大,所以会导致性能的 下降。