结构:
什么是结构:
是一种由程序员设计的复合数据类型,它由若干个其它类型的成员组成,用于统一描述事物的各项属性。
使用各类型的变量也可以描述事物的各项属性(如:通讯录项目),但使用麻烦且容易出错,没有使用结构方便,安全性高、统一性高,同时结构也是面向对象编程的基础。
基础C语言编程思想:面过过程
如何设计结构:
struct 结构名 { // 结构名一般首字母大写
成员类型 成员名;
...
};
// 设计联系人结构
struct Contact {
int id;
char name[20];
char sex;
char tel[12];
};
// 注意:一般结构的设计放置在函数外,这样所有函数都可以使用该结构类型
// 注意:结构设计完成后,仅仅只是设计出一种数据类型而已,必须通过该类型定义结构变量、结构数组、或者分配堆内存才能正常使用
如何使用结构:
// 在C语言中,使用结构类型时 struct 关键字不能省略
struct 结构名 结构变量名;
// 定义结构数组
struct 结构名 结构数组名[数量];
// 定义在堆内存
// 一般结构变量字节数较大,建议定义在堆内存中
struct 结构名* 结构指针变量 = malloc(sizeof(struct 结构名)*数量);
// 结构指针传参 提高传参效率
// 不希望被修改 所以const保护
void func(const struct 结构名* con) {
}
初始化结构变量:
// 顺序初始化,数据要与成员的顺序一一对应。
struct 结构名 结构变量名 = {v1,v2,v3,...};
struct 结构名 结构数组[n] = {
{v1,v2,v3,...},
{v1,v2,v3,...},
...
};
// 指定成员初始化
// 初始化顺序无所谓 没有初始化的成员会默认为0
struct 结构名 结构变量名 = {
.成员名1 = 初始化数据1,
.成员名2 = 初始化数据2,
...
};
struct 结构名 结构数组[n] = {
{.成员名1 = 初始化数据1,.成员名2 = 初始化数据2,...},
{.成员名1 = 初始化数据1,.成员名2 = 初始化数据2,...},
{.成员名1 = 初始化数据1,.成员名2 = 初始化数据2,...},
...
};
访问成员:
// 结构变量.成员名;
// 结构指针->成员名;
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
// 设计联系人结构
struct Contact {
int id;
char name[20];
char sex;
char tel[12];
};
// 结构指针传参 提高传参效率
// 不希望被修改 所以const保护
void show_con(const struct Contact* con) {
printf("%d %s %c %s\n",
con->id,con->name,con->sex,con->tel);
}
int main() {
char str[] = "hehe";
// 定义结构变量
struct Contact con = {.sex = 'm', .name = "uuu"};
show_con(&con);
// 访问结构变量的成员
con.id = 1001;
strcpy(con.name,"hehe");
con.sex = 'w';
strcpy(con.tel,"1234123123");
//printf("%d %s %c %s\n",con.id,con.name,con.sex,con.tel);
show_con(&con);
// 定义结构数组
struct Contact arr[10];
// 定义在堆内存
struct Contact* p = malloc(sizeof(struct Contact)*10);
if(NULL == p) {
perror("malloc");
return 0 ;
}
}
使用typedef重定义简短的类型名:
在C语言中,struct 结构名 才是完整的数据类型名,但使用时比较麻烦,可以使用typedef给结构重定义简短的类型名。
// 结构设计完成后重定义
typedef struct 结构名 结构类型名;
typedef struct Contacts Contacts;
// 设计结构时重定义
typedef struct 结构名 {
成员类型 成员名;
...
}结构名;
typedef struct Contact {
...
}Contact;
Contact con;
设计一个教师结构体:
设计一个教师结构体(姓名,工龄,工号,科目,...),定义教师结构变量,使用scanf从终端输入各成员的值,然后使用printf显示结构变量。
typedef struct Teacher {
char name[20];
char age;
char sex;
char id[8];
char subject[10];
} Teacher;
int main() {
/*
char str[10] = {};
scanf("%s",str);
printf("str:%s\n",str);
*/
Teacher tch = {};
printf("请输入教师信息:");
scanf("%s %hhd %c %s %s",
tch.name,&tch.age,&tch.sex,tch.id,tch.subject);
printf("%s %hhd %c %s %s\n",
tch.name,tch.age,tch.sex,tch.id,tch.subject);
}
如何计算结构的总字节数:
1、结构变量的总字节数 >= 所有成员的字节数之和
2、结构成员的顺序会影响结构的总字节数
3、了解结构总字节数的计算规则,可以通过合理安排结构的成员顺序,从而达到节约内存的目的
4、计算机为了提高结构成员的访问速度,会在成员之间以及结构内存的末尾填充一些空闲内存,称为内存对齐、内存补齐行为
5、在笔试题中内存对齐、补齐考量较大。
内存对齐:
假定从0字节排列结构的第一个成员,之后所有成员的起始字节数,必须是成员本身字节数的整数倍,如果不是则填充一些空闲字节,直到是为止。
struct Data {
char c; // 0
// 1 2 3 空闲字节
int i; // 4 5 6 7
double d; // 8 9 10 11 12 13 14 15
} Data;
内存补齐:
结构的总字节数必须是它最大成员字节数的整数倍,如果不是则在结构的末尾填充一些空闲字节。
struct Data {
char c; // 0
// 1 2 3 空闲字节
int i; // 4 5 6 7
double d; // 8 9 10 11 12 13 14 15
char c1; // 16
// 总字节为17,不能被4整除,所以会在末尾填充三个空闲字节
// 17 18 19 因此Date的总字节数是20
} Data;
注意:
在32位系统下,内存对齐、内存补齐字节数是有上限的,超过上限按4字节计算。
// 在Linux32位系统下,超过4字节按4字节计算。
#include <stdio.h>
// 在Windows32位系统下,超过8字节按4字节计算。
typedef struct Data {
char ch; // 0
// 1 2 3
long double num; // 4 ~ 15
short sh; // 16 17
// 18 19
} Data;
int main() {
printf("%d\n",sizeof(Data)); // 结果是20字节
}
#include <stdio.h>
// 在Windows32位系统下,未超过8字节,按成员的实际字节对齐补齐
typedef struct Data {
char ch; // 0
// 1 2 3 4 5 6 7
double num; // 8 ~ 15
short sh; // 16 17
// 18 19 20 21 22 23
} Data;
int main() {
printf("%d\n",sizeof(Data)); // 结果是24字节
}
#include <stdio.h>
// Windows64位系统和Linux64位系统,都按成员的字节数计算内存对齐、内存补齐
typedef struct Data {
char ch; // 0
// 1 2 3 4 5 6 7 8 9 11 12 13 14 15
long double num; // 16 ~ 31
short sh; // 32 33
// 34 35 36 37 38 39 40 41 42 42 44 45 46 47
} Data;
int main() {
printf("%d\n",sizeof(Data));
}
注意:如果结构成员是数组类型,那么计算对齐补齐时,应当选择该数组的成员类型字节数计算
struct Data {
char c; // 0
char str[20]; // 1~20
};
// Data的总字节数是21 而不是24
long类型的字节数:
Linux32系统 4字节
Linux32系统 8字节
Windows32系统 4字节
Windows64系统 4字节
练习:使用结构知识,重写通讯录。进阶:把结构数组存储在堆内存中
注意:一般结构变量、结构数组所占用的连续内存可能较大,所以建议存储在堆内存
结构成员的位域:
早期由于计算机内存资源比较匮乏,一种节约内存的方式。
// 设计结构时重定义
typedef struct 结构名 {
成员类型 成员名:n; // 设置该成员只使用n个二进制位
...
} 结构名;
联合:union
也是一种由程序员设计的复合数据类型,使用语法与结构一模一样,与结构不同的是,结构中的成员各自拥有独立的内存,而联合中的所有成员共用一块内存(也叫共用体),所以只要有一个成员的值发生变化,其它成员的也会跟着一起变化。
union 联合名 {
成员类型 成员名;
...
};
联合的总字节数:(考点)
由于联合的所有成员共用一块内存,所有成员是天然对齐的,不需要考虑内存对齐,但要考虑内存补齐。
情况1:所有联合的成员都是基本类型,则联合的总字节数就是最大成员的字节数。
union D {
char c;
int i;
double d;
};
情况2:如果联合的成员有数组类型,则联合的总字节数应该是最大成员的整数倍。
union D {
char ch[5];
int n;
};//总字节是8,在末尾内存补齐了3个空白字节
使用联合的意义:
1、使用少量的内存对应若干个标识符,只要不同时使用联合的成员,就不会冲突,能大大节约内存,在早期计算机内存比较小时,该技术使用较多,现在随着计算机内存越来越大已经基本不再使用。
2、联合可以对一块内存进行不同格式的解释,所以在网络通信时还存在着少量的使用(使用网络协议中已经设计好的联合体)。UDP\TCP了解
大端系统和小端系统:
假定有一个int类型变量,它的4字节的内存地址分别是:
int num;
0xbf9f3828 // 低位地址
0xbf9f3829
0xbf9f382a
0xbf9f382b // 高位地址
假定有一个十六进制的整数:0xa1b2c3d4
0xa1 //高位数据
0xb2
0xc3
0xd4 //低位数据
大端系统:
低位数据存储高位地址,或者说是高位数据存储在低位地址,一般大型的服务器、网络设备采用的是大端系统,所以大端格式也叫网络字节序。
int num = 0xa1b2c3d4;
0xbf9f3828 存储的是0xa1
0xbf9f3829 存储的是0xb2
0xbf9f382a 存储的是0xc3
0xbf9f382b 存储的是0xd4
小端系统:
低位数据存储在低位地址,或者说高位数据存储在高位地址,一般的个人计算机采用的是小端系统。
int num = 0xa1b2c3d4;
0xbf9f3828 存储的是0xd4
0xbf9f3829 存储的是0xc3
0xbf9f382a 存储的是0xb2
0xbf9f382b 存储的是0xa1
注意:
数据存储的是大端格式还是小端格式是由计算机的CPU决定的。
常考笔试题
实现判断当前系统是大端还是小端功能。
1、可以使用联合解决
2、直接使用指针解决
int main() {
union Data d;
d.i = 0xa1b2c314;
if (0x14 == d.ch) {
printf("小端\n");
} else {
printf("大端\n");
}
int num = 0x10203040;
char* p = (char*)#
if (0x40 == *p) {
printf("小\n");
} else {
printf("大\n");
}
}
枚举:
是一种值受限的整数类型,由程序员设置它的值的范围,并且还可以给这些值取一个名字。
设计枚举:
typedef enum 枚举名 {
标识符名=枚举值1,
枚举值2,
枚举值3,
...
} 枚举名;
注意:使用标识符作为枚举值
// enum与struct、union一样,使用typedef重定义省略enum关键字
定义枚举变量:
enum 枚举名 枚举变量;
1、理论上枚举变量只能使用枚举值赋值,这样可以提高代码的可读性和安全性。
2、C语言编译器为了提高编译速度,不会检查枚举变量的赋值,全靠程序员的自觉(枚举变量就是int类型变量)。
3、C++编译器类型检查比较严格,所以使用C++编译器编译C代码时,枚举变量只能由枚举值赋值、比较。
枚举值:
1、第一个枚举值的默认值是0,之后的枚举值逐渐递增+1。
#include <stdio.h>
enum DirectionKey {
Up,
Down,
Right,
Left
};
int main() {
enum DirectionKey key;
key = Up;
printf("%d\n",key); // 0
key = Down;
printf("%d\n",key); // 1
key = Right;
printf("%d\n",key); // 2
key = Left;
printf("%d\n",key); // 3
}
2、可以使用=设置枚举值,没有进行设置的枚举值是上一个枚举值递增+1。
#include <stdio.h>
enum DirectionKey {
Up = 123,
Down,
Right = 456,
Left
};
int main(int argc,const char* argv[]) {
enum DirectionKey key;
key = Up;
printf("%d\n",key); // 123
key = Down;
printf("%d\n",key); // 124
key = Right;
printf("%d\n",key); // 456
key = Left;
printf("%d\n",key); // 457
}
3、枚举值可以单独使用,这种用法可以给没有意义的字面值数据取一个有意义的名字,这样可以提高代码的可读取性,也可以定义匿名的枚举,只使用枚举值。
#include <stdio.h>
enum {
Up = 123,
Down,
Right = 456,
Left
};
int main(int argc,const char* argv[]) {
printf("%d %d %d %d\n",Up,Down,Right,Left);
}
4、枚举值是常量,所以可以与switch语句配合使用,枚举值可以写在case的后面。
#include <stdio.h>
#include <getch.h>
enum {
Up = 183,
Down,
Right,
Left
};
int main() {
while (true) {
switch(getch()) {
case Up: puts("上"); break;
case Down: puts("下"); break;
case Right: puts("右"); break;
case Left: puts("左"); break;
}
}
}
5、枚举值的作用域是全局的(尽量名字取的复杂一些),所以它不能与全局变量、函数、结构、联合重名。
#include <stdio.h>
#include <getch.h>
int num;
enum {
Up = 183,
Down,
Right,
Left,
num,
main
};
int main() {
}
注意:
枚举是一种锦上添花的技术,使用它能让代码的可读性、安全性更高,但直接使用字面值数据也不影响代码的编写和运行。
项目-某个系统的用户管理模块:
用户信息:
ID(唯一)(长度8)、昵称、密码(长度20)、手机号
最多存储100个用户
登录前的一级界面功能:
1、用户注册
ID自动生成、唯一、随机
输入昵称、密码、密码要二次确认、手机号
密码输入时要保密(1、什么都不显示 2、显示 * 号)
2、用户登录
ID、密码(要求二分查找ID)
3、重置密码
ID、昵称、手机号全部符合才能重置密码 000000
4、遍历所有用户
5、退出系统
登录成功后的二级菜单功能:
1、查看信息
2、修改信息
修改昵称、手机号
3、修改密码
先输入旧密码、正确:再输入新密码、二次确认,修改成功
4、退出登录,退回一级界面
具体实现:
#include <stdio.h>
#include <getch.h>
#include <string.h>
#include <stdbool.h>
#include <stdlib.h>
#include <time.h>
#include <unistd.h>
typedef struct User {
int id;
char name[20], password[21], phone[12];
} User;
User users[100];
size_t cnt;
time_t msg_show_sec = 20.2; // 提示信息显示时间
int lg_index; // 登录成功的下标
void clear_stdin() {
stdin->_IO_read_ptr = stdin->_IO_read_end;
}
char get_op(const char start, const char end) {
clear_stdin();
printf("请输入指定选项[%c,%c]\n", start, end);
while (true) {
char op = getch();
if (start <= op && op <= end) {
printf("%c\n", op);
return op;
}
}
}
char* get_str(char* str, size_t size) {
clear_stdin();
size_t len = strlen(fgets(str, size, stdin));
if (str[len - 1] == '\n') str[len - 1] = '\0';
return str;
}
char* get_passwd(char* pwd, size_t size, bool is_show) {
clear_stdin();
size_t index = 0;
while (index < size - 1) {
pwd[index] = getch();
if (pwd[index] == '\n') break;
if (pwd[index] == 127) {
if(index > 0) {
index--;
if(is_show) {
printf("\b \b");//抹除一个显示字符
}
}
continue;
}
if (is_show) {
printf("*");
}
index++;
}
pwd[index] = '\0';
printf("\n");
return pwd;
}
int get_id(int len) {
char* str = "0123456789";
int id = str[rand() % 9 + 1] - '0';
for (int i = 1; i < len; ++i) {
id = id * 10 + str[rand() % 10] - '0';
}
return id;
}
void put_str(const char* msg, float sec) {
printf("%s\n", msg);
usleep(1000000 * sec);
}
void anykey_continue() {
clear_stdin();
printf("请按任意键继续...");
getch();
}
int binary_search(int x) {
int l = 0, r = cnt - 1;
while (l < r) {
int mid = l + r >> 1;
if (users[mid].id < x) {
l = mid + 1;
} else {
r = mid;
}
}
return users[r].id == x ? r : -1;
}
void swap(User *x, User *y) {
User temp = *x;
*x = *y;
*y = temp;
}
void quick_sort(User *q, int l, int r) {
if (l >= r) return ;
int i = l - 1, j = r + 1;
int x = q[l + r >> 1].id;
while (i < j) {
do i++; while (q[i].id < x);
do j--; while (q[j].id > x);
if (i < j) swap(&q[i], &q[j]);
}
quick_sort(q, 1, j);
quick_sort(q, j + 1, r);
}
char menu() {
system("clear");
puts("============用户登录=============");
puts("1.用户注册 2.用户登录 ");
puts("3.重置密码 4.遍历所有用户");
puts("5.退出系统 ");
puts("=================================");
return get_op('1', '5');
}
void sign_user() {
if (cnt >= 100) {
put_str("系统正在升级,暂停注册!", msg_show_sec);
return;
}
while (true) {
users[cnt].id = get_id(8);
if (binary_search(users[cnt].id) == -1) {
break;
}
}
printf("请输入昵称:");
get_str(users[cnt].name,sizeof users[cnt].name);
printf("请输入手机号:");
get_str(users[cnt].phone,sizeof users[cnt].phone);
while (true) {
printf("请输入密码(6~19位):");
get_passwd(users[cnt].password, sizeof users[cnt].password, true);
size_t len = strlen(users[cnt].password);
if(6 > len) {
printf("密码长度不足6位,请重新输入\n");
} else break;
}
char pass_2[20] = {};
printf("请确认密码:");
get_passwd(pass_2, sizeof pass_2, true);
if (strcmp(users[cnt].password,pass_2) != 0) {
put_str("两次密码不同,注册失败\n", msg_show_sec);
return;
}
quick_sort(&users, 0, cnt);
cnt += 1;
anykey_continue();
}
char menu_login() {
system("clear");
printf("===========欢迎%s登录系统===========\n", users[lg_index].name);
puts("1.查看信息 2.修改信息");
puts("3.修改密码 4.退出登录");
return get_op('1', '4');
}
void show_user_info() {
printf("%d %s %s %s\n",
users[lg_index].id,
users[lg_index].name,
users[lg_index].password,
users[lg_index].phone);
anykey_continue();
}
void modify_user() {
puts("=====请输入要修改的信息=====");
puts("1.修改昵称 2.修改手机号");
if (get_op('1', '2') - '0' == 1) {
printf("请输入新的昵称:");
get_str(users[lg_index].name, sizeof users[lg_index].name);
} else {
printf("请输入新的手机号:");
get_str(users[lg_index].phone, sizeof users[lg_index].phone);
}
printf("修改后个人信息:\n");
show_user_info();
}
void modify_password() {
char pass[20];
printf("请输入旧密码:");
get_passwd(pass, sizeof pass, true);
if (strcmp(users[lg_index].password, pass)) {
put_str("密码验证失败,修改退出\n", msg_show_sec);
return;
}
printf("请输入新密码:");
get_passwd(pass, sizeof pass, true);
char repass[20];
printf("请确认密码:");
get_passwd(repass, sizeof repass, true);
if (0 == strcmp(pass, repass)) {
strcpy(users[lg_index].password, pass);
put_str("修改密码成功\n", msg_show_sec);
return;
}
put_str("两次密码不同,修改失败\n", msg_show_sec);
}
void login_suc() {
while (true) {
int op = menu_login() - '0';
if (op == 1) {
show_user_info();
} else if (op == 2) {
modify_user();
} else if (op == 3) {
modify_password();
} else break;
}
}
void login_user() {
int id = 0;
printf("请输入用户ID:");
clear_stdin();
scanf("%d", &id);
char pass[20];
printf("请输入用户密码;");
get_passwd(pass, sizeof pass, true);
int index = binary_search(id);
if (index >= 0 && strcmp(pass, users[index].password)) {
put_str("登录成功\n", msg_show_sec);
lg_index = index;
login_suc();
return;
}
put_str("用户ID或者密码错误,请检查\n", msg_show_sec);
}
void reset_user() {
clear_stdin();
int id;
printf("请输入用户ID:");
scanf("%d",&id);
char name[20];
printf("请输入用户昵称:");
get_str(name, sizeof name);
char phone[12];
printf("请输入用户手机号:");
get_str(phone,sizeof phone);
int index = binary_search(id);
if (index >= 0 && 0 == strcmp(users[index].name,name)
&& 0 == strcmp(users[index].phone,phone)) {
strcpy(users[index].password, "000000");
put_str("重置密码成功\n", msg_show_sec);
return;
}
put_str("数据信息有误,重置失败\n", msg_show_sec);
}
void show_user() {
if (cnt <= 0) {
puts("暂无普通用户!");
anykey_continue();
return ;
}
for (int i = 0; i < cnt; ++i) {
printf("ID:%d 昵称:%s \n", users[i].id, users[i].name);
}
anykey_continue();
}
int main() {
srand(time(NULL));
while (true) {
int op = menu() - '0';
if (op == 1) {
sign_user();
} else if (op == 2) {
login_user();
} else if (op == 3) {
reset_user();
} else if (op == 4) {
show_user();
} else break;
}
}
标签:struct,int,高级,C语言,char,枚举,printf,结构
From: https://www.cnblogs.com/sleeeeeping/p/18175411