C语言程序设计笔记---014
- C语言结构体初阶
- 1、结构的基础与概念
- 1.1、结构体基础例程1 --- 描述一个学生属性
- 2、结构体成员的类型
- 2.1、结构体类型例程1 --- 描述一个学生属性
- 3、结构体的变量定义与初识化
- 3.1、结构体的变量定义与初识化例程1
- 3.2、结构体的变量定义与初识化例程2
- 4、结构体成员的访问
- 4.1、结构体成员的访问例程1
- 4.2、结构体成员的访问例程2 ----- 结构体传值/传址调用
- 5、函数调用时的参数压栈
- 5.1、结构体参数压栈例程1
- 2.2、参数压栈解释说明
- 6、函数栈帧的创建与销毁
C语言结构体初阶
前言:
为什么会有结构体呢?
首先,当前学习过的类型都属于内置类型(char、int、double…),单独使用
而我们想描述一个人时,一个单独的类型无法描述出来
这时就需要特殊的类型,来描述一些复杂类型 ---- struct结构体类型
如:描述一个学生
学生类型 ----> 学生变量 —>描述学生相关属性
/知识点汇总/
1、结构的基础与概念
a、结构是一些值的集合,这些值称为成员变量
b、结构的每个成员可以是不同类型的变量
c、结构体成员可以是变量,数组,指针,甚至是其他结构体
d、结构体成员中可以有另外一个结构体
结构体的声明格式:
struct tag//结构体变量名
{
member - list;//成员变量列表
}variable-list;//变量列表
1.1、结构体基础例程1 — 描述一个学生属性
/*例程1.1*/
#include <stdio.h>
//写法一:
struct Stu
{
char name[20];
int age;
char sex[5];
char tele[12];
}s3,s4;//是结构体类型的变量,但属于全局变量
//写法二:
typedef struct Stu
{
char name[20];
int age;
char sex[5];
char tele[12];
}Stu;//在借助typedef的情况下,这里的Stu就属于重命名产生的新类型
int main()
{
struct Stu s1;
struct Stu s2;
//s1,s2是结构体类型的变量,但属于局部变量
Stu s5;//写法二。就可直接使用了
return 0;
}
2、结构体成员的类型
结构体成员的类型可以是变量、数组、指针、甚至是其他结构体等多组类型成员。
2.1、结构体类型例程1 — 描述一个学生属性
#include <stdio.h>
struct B
{
char sb;
char arr[200];
};
struct s
{
char c;
int num;
int arr[10];
double* pd;
struct B s;
struct B time;
};
int main()
{
return 0;
}
3、结构体的变量定义与初识化
结构体初识化与数组类似,使用大括号
3.1、结构体的变量定义与初识化例程1
#include <stdio.h>
struct S
{
char c;
int num;
int arr[10];
double* pd;
}s1;//s1全局变量
struct S s2;//s2全局变量
int main()
{
struct S s3;//s3局部变量
return 0;
}
3.2、结构体的变量定义与初识化例程2
#include <stdio.h>
struct B
{
char a;
int i;
};
struct S
{
char c;
int num;
int arr[10];
double* pd;
struct B ch;
struct B* pb;
}s1;//s1全局变量
struct S s2;//s2全局变量
int main()
{
double d = 3.14159;
//按照顺序初识化
struct S s3 = { 'Q',100,{1,2,3},&d,{'B',99},NULL};//s3局部变量
//指定成员初始化: ' . ' 操作符
struct S s4 = { .num = 999, .arr = {1,2,3,4,5} };
//未初始化的成员,默认给NULL(0)
return 0;
}
4、结构体成员的访问
.(点操作符 ) -> (箭头操作符)
结构体变量 . 成员
结构体指针 -> 成员
注意:一个操作数是变量。一个是指针
结构体变量的成员是通过点操作符( . )访问的,点操作符接收两个操作数
4.1、结构体成员的访问例程1
/*例程1.3*/
#include <stdio.h>
struct B
{
char c;
short s;
double d;
char* f;//指针类型成员·
};
struct stu
{
struct B abc;
char name[20];
int age;
char id[20];
}s1, s2;
int main()
{
//结构体初始化
struct stu s = { {'w',20,3.14159,'F'},"小强",18,"2020D2780106" };//对象
//. ->
//printf("%c\n",s.abc.c);
//printf("%c\n", s.abc.f);
struct stu* ps = &s;//结构体指针变量
printf("%c\n",(*ps).abc.c);//*解引用+ .成员运算符,获取对应的结构体成员
printf("%c\n", ps->abc.c);//->根据对象的指针,直接访问成员,即取结构体内成员
return 0;
}
4.2、结构体成员的访问例程2 ----- 结构体传值/传址调用
传值调用:
#include <stdio.h>
#include <string.h>
struct S
{
char name[20];
int age;
};
void set_s(struct S t)
{
t.age = 18;
//t.name = "zhangsan";//err,因为name是数组名,数组名是常量的地址
strcpy(t.name,"zhangsan");//字符串拷贝
}
int main()
{
struct S s = { 0 };
//写一个函数给s中存放数据
set_s(s);//传值调用,不会改变值的数据
//写一个函数打印s中的数据
//print_s(s);
return 0;
}
传址调用:
#include <stdio.h>
#include <string.h>
struct S
{
char name[20];
int age;
};
//写法一:点操作符
//void set_s(struct S* ps)
//{
// (*ps).age = 18;
//
// strcpy((*ps).name, "zhangsan");//字符串拷贝
//}
//写法二: -> 操作符
void set_s(struct S* ps)
{
ps->age = 18;
strcpy(ps->name, "zhangsan");//字符串拷贝
}
void print_s(struct S t)
{
printf("%s %d\n",t.name,t.age);
}
int main()
{
struct S s = { 0 };
//写一个函数给s中存放数据
set_s(&s);//传址调用
//写一个函数打印s中的数据
print_s(s);
return 0;
}
小结:
结构体传参的考虑:
根据实际需求考虑传址或传值作为参数
传址调用性能更强:
a、传值/传址调用,传址更具优势:节约内存的空间,且速率更快
b、函数在传参时,参数是需要压栈的,如果传递一个结构体对象的时候,结构体过大,参数压栈的系统开销比较大,所以会导致性能的下降。
即:结构体传参时,常传结构体的地址
5、函数调用时的参数压栈
栈—是一种数据结构
压栈/出栈:
理解为,盒子自上向下装,直到装满压紧,取出是却是后进先出,自上往下取出
参数压栈:
每一个函数的调用都会在内存的栈区上开辟一块区域(栈区、堆区、静态区)
特点:先进后出,后进的先出
5.1、结构体参数压栈例程1
#include <stdio.h>
int Add(int x, int y)
{
int z = 0;
z = x + y;
return z;
}
int main()
{
int a = 3;
int b = 5;
int c = 0;
c = Add(a,b);
return 0;
}
2.2、参数压栈解释说明
理解:每一个函数的调用都会在总的内存的栈区上开辟一块区域,然后会暂时存储变量的内存,然后每次调用一次函数也会开辟一块内存区域,像叠盘子一样,越跌越高,形成了堆叠,也就是压栈。直到函数执行完,里面的局部变量使出作用域,相继内存也得以释放,就像把刚堆叠的盘子再从上向下,拿取出去,也就是释放了空间。
如图所示:
总结就是:先进后出,后进先出
6、函数栈帧的创建与销毁
可参考这位博主,写得很好:
链接: 函数栈帧的创建与销毁