处理由不同类型成员构成的构造类型,要采用结构体的方式
1. 结构体类型定义
struct是构造新类型的关键字,可以构造任意的构造类型
1.1. 无名构造类型
不带来多余的变量名。若不在定义类型时定义变量,则会带来代码冗余
一般而言用于定义类型的同时定义变量
struct
{
char name[30];
char sex;
int age;
float high;
};
1.2. 有名构造类型
一处定义,可以多处使用
struct student
{
char name[30];
char sex;
int age;
float high;
}stu;
struct student stu2;
1.3. 别名构造体类型
typedef是一个常用于对结构体取别名的关键字,更好用的结构体
typedef对现有类型取别名,不能创造新的类型
typedef struct student
{
char name[30];
char sex;
int age;
float high;
}STUDENT;
STUDENT stu, stu2;
1.3.1. typedef作用
用自定义名字为已有数据类型命名:typedef 现在类型名 新类型名;
1.3.2. 定义新类型
使用方法:
- 先用原类型定义变量
- 在定义前加typedef
- 将原变量的名字,改成需要的类型名
typedef char int8;
typedef short int16;
typedef int int32;
typedef long long int64;
typedef int ARRAY[10];
int main2()
{
int8 i8;
int16 i16;
int32 i32;
int64 i64;
int arr[10];
printf("sizeof(arr) = %d sizeof(int[10]) = %d\n", sizeof(arr), sizeof(int[10]));
ARRAY arr2;
printf("sizeof(ARRAY) = %d sizeof(arr2) = %d\n", sizeof(ARRAY), sizeof(arr2));
return 0;
}
1.3.3. typedef和#define的区别
typedef是以;号结尾的c语言语句。而#define则是预处理阶段的文本替换,有时他们是可以互换的,但有时不可以
int main()
{
#define POINTED char *
typedef char *POINTERT;
POINTERT pta, ptb;
printf("sizeof(POINTERT) = %d\n", sizeof(POINTERT)); // 4
printf("sizeof(pa) = %d sizeof(pb) = %d\n", sizeof(pta), sizeof(ptb)); // 4 4
POINTED pda, pdb;
printf("sizeof(POINTED) = %d\n", sizeof(POINTED)); // 4
printf("sizeof(pda) = %d sizeof(pdb) = %d\n", sizeof(pda), sizeof(pdb)); // 4 1
return 0;
}
1.3.4. 小结
- 新类型名一般用大写表示,以便于区别
- 用typedef只能声明新的类型名,不能创造新的类型,只是为已经存在的类型起一个别名,也不能用来定义变量,即只能用其声明的类型来定义变量
- 有时也可用宏定义来代替typedef的功能,但是宏定义是由预处理完成的,而typedef是在编译时完成的,更为灵活方便
- typedef可以让类型更见名知意,更便于移植
1.4. 小结
- 我们定义的新类型,他的地位等同于int类型。还只是个模子,如果没有生成变量,是不会占用空间的
- 结构定义放置在程序的开始部分,位于头文件声明之后
- 注意{}不表示复合语句,其后有分号
- 结构体类型名称是struct+结构体名,注意struct关键字不能省略
2. 结构体变量初始化及成员访问
凡是基本类型,既可以在定义时初始化,也可以先定义再赋值
凡是构造类型,要么在定义的时候初始化,不可以先定义再初始化的方式赋值
2.1. 初始化及访问
2.1.1. 点成员运算符
typedef struct
{
char name[30];
char sex;
int age;
float high;
} STUDENT;
int main(void)
{
STUDENT stu = {"zhangsan", 'x', 45, 170};
STUDENT *ps = &stu;
printf("name = %s, sex = %c, age = %d, high = %f\n", ps->name, ps->sex, ps->age, ps->high);
strcpy(ps->name, "lisi");
ps->sex = 'x';
ps->age = 19;
ps->high = 165;
printf("name = %s, sex = %c, age = %d, high = %f\n", ps->name, ps->sex, ps->age, ps->high);
printf("请输入姓名:");
scanf("%s", ps->name);
getchar();
printf("请输入性别:");
scanf("%c", &ps->sex);
printf("请输入年龄:");
scanf("%d", &ps->age);
printf("请输入身高:");
scanf("%f", &ps->high);
printf("name = %s, sex = %c, age = %d, high = %f\n", ps->name, ps->sex, ps->age, ps->high);
ps = (STUDENT *)malloc(sizeof(STUDENT)); // 堆内存的指针表示法
printf("请输入身高:");
scanf("%f", &ps->high);
printf("name = %s, sex = %c, age = %d, high = %f\n", ps->name, ps->sex, ps->age, ps->high);
printf("name = %s, sex = %c, age = %d, high = %f\n", (*ps).name, (*ps).sex, (*ps).age, (*ps).high);
free(ps);
return 0;
}
2.2.2. 指向成员运算符(->(*).)
常见有指向栈内存变量或指向堆内存变量的表示方法
2.2. 成员运算符本质
成员运算符的本质,依然是通过计算偏移来实现的,此处不考虑内存对齐等原因
2.3. 赋值
同类变量间,可以赋值,常用于传参和返回。
int main()
{
Stu s;
Stu s2 = {"wyb", 1002, 'x', 90};
s = s2;
printf("%s %d %c %f\n", s.name, s.num, s.sex, s.score);
return 0;
}
常见的数组赋值
struct ARRAY
{
int a[10];
}
int main(void)
{
struct ARRAY array = {{1,2,3,4,5,6,7}}; // 内部括号可省
struct ARRAY array2 = array; // 结构体的等位拷贝
for(int i=0; i<10; i++)
{
printf("%d\n", array2.a[i]);
}
return 0;
}
3. 结构体类型作参数和返回值
3.1. 结构体变量作参数和返回值
结构体作参数或返回值,会发生同类型复制(本质是赋值)。同类型结构体间,是可以相互赋值的。
struct complex
{
float real;
float imag;
};
struct complex addComplex(struct complex com1, struct complex com2)
{
struct complex res;
res.real = com1.real + com2.real;
res.imag = com1.imag + com2.imag;
return res;
}
int main()
{
struct complex com1 = {1.2f, 12.3f};
struct complex com2 = {2.3f, 2.4f};
struct complex res = addComplex(com1, com2);
printf("res real: %f imag: %f\n", res.real, res.imag); // res real: 3.500000 imag: 14.700001
return 0;
}
3.2. 结构体指针作参数
传结构体的成本是很高的,用指针作参数有一个好处,就是避免了同类型复制,无论结构体多大,只传4个字节的指针。
struct complex
{
float real;
float imag;
};
struct complex addComplex(struct complex *pcom1, struct complex *pcpm2)
{
struct complex res;
res.real = pcom1->real + pcpm2->real;
res.imag = pcom1->imag + pcpm2->imag;
return res;
}
int main()
{
struct complex com1 = {1.2f, 12.3f};
struct complex com2 = {2.3f, 2.4f};
struct complex res = addComplex(&com1, &com2);
printf("res real: %f imag: %f\n", res.real, res.imag); // res real: 3.500000 imag: 14.700001
return 0;
}
3.3. 获取当前时间函数的使用
可以用系统提供的函数localtime,来求得当前日期的精确值。函数的具体声明可以查阅手册
int main()
{
time_t rawTime;
time(&rawTime); // 指针作入参
struct tm *st = localtime(&rawTime); // 返回了一个静态变量的地址
printf("current year: %d\n", st->tm_year + 1900);
printf("current month: %d\n", st->tm_mon + 1);
printf("current day: %d\n", st->tm_mday);
printf("current hour: %d\n", st->tm_hour);
printf("current minute: %d\n", st->tm_min);
printf("current second: %d\n", st->tm_sec);
return 0;
}
栈上的空间,不能通过函数返回,因为函数一结束,栈空间已经消失,或已被分配。再对其空间进行访问,是危险且无意义的。
localtime之所以可以返回一个指针所指向的空间,是因为localtime内部有一个静态的struct tm结构变量。
4. 结构体数组
结构体数组本质:一维数组,只不过是一维数组中的每个成员又是结构体
4.1. 定义及初始化
typedef struct _stud
{
int num;
char name[30];
char sex;
float score;
} Stud;
int main()
{
Stud s[] = {
{1001, "zhangsan", 'x', 100},
{1002, "lisi", 'm', 89},
{1003, "wangwu", 'x', 76},
{1004, "zhaoliu", 'm', 65}};
for (int i = 0; i < sizeof(s) / sizeof(*s); i++)
{
printf("num: %d\n", s[i].num);
printf("name: %s\n", s[i].name);
printf("score: %f\n", s[i].score);
printf("sex: %c\n", s[i].sex);
}
printf("******\n");
s[0].num = 1008;
strcpy(s[0].name, "xz");
s[0].score = 200;
s[0].sex = 'm';
for (int i = 0; i < sizeof(s) / sizeof(s[0]); i++)
{
printf("学号:%d,姓名:%s,性别:%c,成绩:%f\n", s[i].num, s[i].name, s[i].sex, s[i].score);
}
return 0;
}
4.2. 内存存储形式
4.3. 实战:选举
现有三位候选人员,候选人员包含名字和选票数两项,现在10人对其投票,每个人限投票一次,投票完毕,打印投票结果
typedef struct _candidate
{
char name[30];
int voteCount;
} Candidate;
void disCandidate(Candidate *c, int n, int count)
{
for (int i = 0; i < n; i++)
{
printf("Name: %-10s VoteCount: %-2d\n", c[i].name, c[i].voteCount);
}
printf("弃权的人数:%d\n", count);
int idx = 0;
for (int i = 0; i < n; i++)
{
if (c[i].voteCount > c[idx].voteCount)
idx = i;
}
printf("恭喜%s获得了本次选举\n", c[idx].name);
}
int main()
{
Candidate can[3] = {
{"zhangsan", 0},
{"lisi", 0},
{"wangwu", 0}};
char buf[1024];
// 弃投票
int count = 0;
// 10个人,投10次票,外层循环决定投票的总次数
for (int i = 0; i < 10; i++)
{
printf("pls input your like: \n");
scanf("%s", buf);
int flag = 1;
// 内层循环匹配人
for (int i = 0; i < 3; i++)
{
if (!strcmp(buf, can[i].name))
{
can[i].voteCount++;
flag = 0;
}
}
if (flag != 0)
{
count++;
}
}
disCandidate(can, 3, count);
return 0;
}
5. 结构体嵌套
5.1. 结构体中可以嵌套结构体
结构体中,既可以嵌套结构体类型变量,也可以嵌套结构体类型,但是不推荐
struct student
{
char name[30];
char sex;
int age;
float high;
struct birthday
{
int year;
int month;
int day;
} birth; // 如果未声明birth,则类似只有int
};
5.2. 嵌套结构体变量定义和初始化
int main()
{
struct student stu = {"wang", 's', 23, 175.0, {1998, 8, 18}};
printf("name %s birthday year = %d\n", stu.name, stu.birth.year);
return 0;
}
6. 结构体类型的大小
结构体类型,本身不占有内存空间,只有它生成的变量才占有内存空间的
6.1. 结构体成员内存分布
首成员在低地址,尾成员在高地址
类型本身是不占用空间的,类型产生的变量才占用空间
结构体中的每个成员的地址均是可以获得的
6.2. 内存对齐
类型本身是不占用空间的,类型产生的变量才占空间
结构体中的每个成员的地址均是可以获得的
typedef struct _staff
{
char sex;
// short num;
int age;
short num;
} Staff;
int main()
{
Staff s;
printf("sizeof(Staff) = %d\n", sizeof(Staff)); // 8
printf("sizeof(s) = %d\n", sizeof(s)); // 8
printf("&s = %p\n", &s); // &s = 0x16afe6fd4
printf("&s.sex = %p\n", &s.sex); // &s.sex = 0x16afe6fd4
printf("&s.age = %p\n", &s.age); // &s.age = 0x16fb32fd8
printf("&s.num = %p\n", &s.num); // &s.num = 0x16d4a2fd6
return 0;
}
问题:
- 为什么sex后面空了3个字节,然后再去填age
- 如果num放在age后,num后面空了两个字节
- sex与age间空了3个字节,num应该填在哪个位置
typedef struct _staff
{
char sex;
short num;
int age;
} Staff;
6.2.1. 什么是不对齐
不对齐:一个成员变量需要多个机器去读
对齐:本质是牺牲空间,换取时间
6.2.2. 对齐规则
x86(linux默认#pragma pack(4),windows默认#pragma pack(8)),linux最大支持4字节对齐
方法:
- 取pack(n)的值(n=1 2 4 8…),取结构体中类型最大值m,两者取小,即为外对齐,大小Y=(m<n?m:n)
- 将每一个结构体的成员大小与Y比较取小者为X,作为内对齐的大小
- 所谓按X对齐,即为地址(设起始地址为0)能被X整除的地方开始存放数据
- 外部对齐原则是根据Y的值(Y的最小整数倍),进行补空操作
7. 结构体使用注意事项
7.1. 向结构体内未初始化的指针拷贝
结构体中,包含指针,注意指针的赋值,切不可向未知的区域拷贝
int main()
{
struct student *ps;
strcpy(ps->name, "wyb"); // 直接拷贝会报错,name指针并没有一个合法的地址,此时内部存储的是乱码,调用strcpy时,会往乱码的内存上拷贝,导致出错
ps = (struct student*)malloc(sizeof(struct strudent)); // 为指针变量ps分配了内存,但是同样没给name指针分配内存
ps->name = (char *)malloc(100); // 解决
ps->score = 100;
printf("name = %s score = %d\n", ps->name, ps->score);
return 0;
}
7.2. 未释放结构体内指针所指向的空间
申请空间时,从外至内,释放空间时,从内至外
int main()
{
Stu *ps = (Stu *)malloc(sizeof(Stu)); // 给ps申请空间
ps->name = (char *)malloc(100); // 给name申请空间
strcpy(ps->name, "wyb");
ps->score = 100;
// free(ps->name); // 由内而外释放空间
free(ps);
return 0;
}
8. 栈的自实现
8.1. 栈的特点
先进后出,或先进先出,判空,判满,压栈,出栈
// 8
// top == 0 不能往外弹,已到栈底
// top == 8 不能往里压,已到栈顶
// top始终指向一个待插入的位置
// push操作:1.写入数据;2. top++;3. 此两步操作的前提是栈非满
// pop操作:1. top--;2. 弹出数据;3. 此两步操作的前提是栈非空
typedef struct _Stack
{
int mem[1024];
int top;
} Stack;
int isFull(Stack *ps)
{
return ps->top == 1024; // 判满
}
int isEmpty(Stack *ps)
{
return ps->top == 0; // 判空
}
void push(Stack *ps, char ch)
{
ps->mem[(ps->top)++] = ch; // 压栈
}
char pop(Stack *ps)
{
return ps->mem[--(ps->top)]; // 出栈
}
int main()
{
Stack s = {{0}, 0};
for (char ch = 'a'; ch < 'z'; ch++)
{
if (!isFull(&s))
push(&s, ch);
}
while (!isEmpty(&s))
{
putchar(pop(&s));
}
return 0;
}