结构体属于用户自定义的数据类型,允许用户存储不同的数据类型。
1. 结构体的声明
1.1 结构的基础知识
结构是一些值的集合,这些值称为成员变量。结构的每个成员可以是不同类型的变量。
1.2 结构的声明
struct tag {
member-list;//成员列表
} variable-list;//变量列表(全局的结构体变量)
例如描述一个学生:
typedef struct Stu {
char name[20];//名字
int age;//年龄
char sex[5];//性别
char id[20];//学号
} Stu;//分号不能丢
注:一个汉字算两个字符。
1.3 结构成员的类型
结构的成员可以是标量、数组、指针,甚至是其他结构体。
1.4 结构体变量的定义和初始化
在 C 语言中,变量和对象是一个概念。
有了结构体类型,如何定义变量?
struct Point {
int x;
int y;
}p1; //声明类型时定义变量p1
struct Point p2;//定义结构体变量p2
初始化:定义变量的同时附初值/定义变量后逐个赋值。
struct Point p3 = {x, y};
struct Point_3D {
Point pt;
int depth;
char name[10];
}pt1 = {{5, 6}, 10, "pt_1"};
struct Point_3D pt2 = {{7, 8}, 9, "pt_2"};
struct Point_3D pt3;
pt3.pt = {1, 2};
pt3.depth = 4;
pt3.name = "pt_3";
结构体的嵌套:
作用:结构体中的成员可以是另一个结构体。
初始化:
struct Node {
int data;
struct Point p;
struct Node* next;
} n1 = {10, {4, 5}, NULL};
struct Node n2 = {20, {7, 8}, NULL};
struct Node n3;
n3.data = 30;
n3.p.x = 3;
n3.p.y = 4;
n3.next = NULL;
printf("%d %d", n3.p.x, n3.p.y);
2. 结构体成员的访问
-
结构体变量访问成员
结构体变量的成员是通过点操作符(.)访问的。点操作符接受两个操作数。
例如:
struct Stu { char name[20]; int age; }; struct Stu s1;
我们可以看到 s1 有成员 name 和 age;那我们如何访问 s1 的成员?
strcpy(s.name, "zhangsan"); s.age = 20;
-
结构体指针访问指向结构体变量的成员
有时我们得到的是一个结构体变量的指针,那如何访问成员?结构体变量的指针可以通过 -> 操作符 或者 *操作符+.操作符 来访问指向结构体变量的成员(属性)。
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 Stu {
char name[20];
int age;
};
struct Stu s = {"zhangsan", 20};
void print1(struct Stu s) {//结构体传参
printf("name = %s age = %d\n", s.name, s.age);
}
void print2(const struct Stu* ps) {//结构体地址传参
printf("name = %s age = %d\n", ps->name, ps->age);
}
int main() {
print1(s);//传结构体
print2(&s);//传地址
return 0;
}
上面的两个函数哪个好些?
答案是:首选 print2() 函数。
原因:
函数传参的时候,参数是需要压栈的。
如果传递一个结构体对象的时候,结构体过大,参数压栈的系统开销比较大,所以会导致性能的下降。
结论:结构体传参的时候,要传结构体的地址。
补充:加入 const 使得指针指向的结构体变量无法被修改,防止函数体中的误操作。
4. 练习
代码 1:
假设 p 的值是 0x100000。如下表达式的值分别为多少?
已知,结构体 Test 类型的变量大小是 20 个字节。
#include <stdio.h>
struct Test {
int Num;
char* pcName;
short sDate;
char cha[2];
short sBa[4];
}*p = (struct Test*)0x100000;
int main() {
printf("%p\n", p + 0x1);//00100014
printf("%p\n", (unsigned long)p + 0x1);//00100001
printf("%p\n", (unsigned int*)p + 0x1);//00100004
return 0;
}
- 0x100000 为整型,赋给指针时最好进行强制类型转换。
- 地址需要以 十六进制的形式 进行计算,因此 结构体地址 + 20 <===> 结构体地址 + 0x00000014。
- 整数按地址形式输出需要转换为十六进制。
- 打印地址需要打印 8 位 十六进制数。
代码 2:
#include <stdio.h>
int main() {
int a[4] = { 1, 2, 3, 4 };
int* ptr1 = (int*)(&a + 1);//4
int* ptr2 = (int*)((int)a + 1);//2000000
printf("%x,%x", ptr1[-1], *ptr2);
return 0;
}
%x:按 16 进制形式打印。
代码 3:
#include <stdio.h>
int main() {
int a[3][2] = { (0,1),(2,3),(4,5) };//{ 1, 3, 5, 0, 0, 0 };
int* p;
p = a[0];
printf("%d", p[0]);//1
return 0;
}
a[0] 是第一行的数组名,表示首元素的地址,即 a[0][0] 的地址(&a[0][0])。
注意 逗号表达式 和 数组初始化 的区别。
代码 4:
#include <stdio.h>
int main() {
int a[4][5] = { 0 };
int(*p)[4];
p = a;
printf("%p,%d\n", &p[4][2] - &a[4][2], &p[4][2] - &a[4][2]);//FFFFFFFC,-4
return 0;
}
- 地址 - 地址 是地址之间元素的个数,有正负之分。
- %p 打印内存中的值,可以认为是打印无符号数。
代码 5:
#include <stdio.h>
int main() {
char* a[] = { "work", "at", "alibaba" };
char** pa = a;
pa++;
printf("%s\n", *pa);//at
return 0;
}
%s 打印地址向后的内容,传入的参数是一个地址。
代码 6:
#include <stdio.h>
int main() {
char* c[] = { "ENTER", "NEW", "POINT", "FIRST" };
char** cp[] = { c + 3,c + 2,c + 1,c };
char*** cpp = cp;
printf("%s\n", **++cpp);//POINT
printf("%s\n", *-- * ++cpp + 3);//ER
printf("%s\n", *cpp[-2] + 3);//ST
printf("%s\n", cpp[-1][-1] + 1);//EW
return 0;
}
- ++cpp 会改变 cpp 的值(或者叫 cpp 指向的地址/位置)。
- cpp[-2] 不会改变 cpp 的值。
- –*cpp 会改变 cp 中相对应的指针的值,也需要注意(⚠)。