C语言结构体

处理由不同类型成员构成的构造类型,要采用结构体的方式

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. 定义新类型

使用方法:

  1. 先用原类型定义变量
  2. 在定义前加typedef
  3. 将原变量的名字,改成需要的类型名
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;
}

问题:

  1. 为什么sex后面空了3个字节,然后再去填age
  2. 如果num放在age后,num后面空了两个字节
  3. 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字节对齐
方法:

  1. 取pack(n)的值(n=1 2 4 8…),取结构体中类型最大值m,两者取小,即为外对齐,大小Y=(m<n?m:n)
  2. 将每一个结构体的成员大小与Y比较取小者为X,作为内对齐的大小
  3. 所谓按X对齐,即为地址(设起始地址为0)能被X整除的地方开始存放数据
  4. 外部对齐原则是根据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;
}
  • 22
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Qi妙代码

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值