一、概念

  • 在实际问题中,一组数据往往有很多种不同的数据类型。例如,登记学生的信息,可能需要用到 char型的姓名,int型或 char型的学号,int型的年龄,char型的性别,float型的成绩。又例如,对于记录一本书,需要 char型的书名,char型的作者名,float型的价格。在这些情况下,使用简单的基本数据类型甚至是数组都是很困难的。而结构体(类似Pascal中的“记录”),则可以有效的解决这个问题。
  • 结构体本质上还是一种数据类型,但它可以包括若干个“成员”,每个成员的类型可以相同也可以不同,也可以是基本数据类型或者又是一个构造类型。
  • 结构体的优点:结构体不仅可以记录不同类型的数据,而且使得数据结构是“高内聚,低耦合”的,更利于程序的阅读理解和移植,而且结构体的存储方式可以提高CPU对内存的访问速度。

二、程序实现

2.1.结构体的基本使用

2.1.1创建结构体

  • strcut标识符,用户自定义的名字
  • 结构体不一定只能放外面,main函数里面也可以放。(作用域不同)
  • 定义一个结构体类型,struct Student合起来才算是类型名
  • 成员变量不能直接赋值
struct Student
{
	char name[50];
	int age;
	int score;
};//有分号

2.1.2命名结构体变量

  • 类型 +变量
  • struct Student合起来才是类型名
  • s是变量
struct Student s;

2.1.3访问成员变量

  • 如果是普通变量(非指针),使用 .运算符
stpcpy(s.name,"黑马");
s.age = 18;
s.score = 100;
  • 如果是指针变量,使用 ->运算符
printf("%s %d %d\n",(&s)->name,(&s)->age,(&s)->score);//打印结构体中的值

2.2.结构体的变量

  • 普通结构体变量
//普通结构体变量初始化(此方式需要按照结构体定义顺序赋值)
struct Student s = {"abc",10,30};
//打印初始化的值
printf("%s, %d, %d",s.name,s.age,s.score);
  • 结构体数组变量
//普通结构体变量初始化
struct Student a[] =
{
	{"heibi", 18, 100},
	{"testw", 25, 300},
	{"eicis", 49, 200},
};
//打印结构体数组个数
int n = sizeof(a)/sizeof(a[0]);
printf("n:%d",n);
//访问结构体数组内容(4种方式)
for(int i = 0; i<n ; ++i)
{
  printf("%s, %d, %d\n",a[i].name,a[i].age,a[i].score);
  //printf("%s, %d, %d\n",(*(a+i)).name,(*(a+i)).age,(*(a+i)).score);
  //printf("%s, %d, %d\n",(&a[i])->name,(&a[i])->age,(&a[i])->score);
  //printf("%s, %d, %d\n",(a+i)->name,(a+i)->age,(a+i)->score);
}

2.3.结构体的嵌套

  • 引例

    这里可以看到两个结构体得内容相差不大,有三个重复的变量,这种情况即可以使用结构体的嵌套

struct Info
{
	int age;
	char sex;
	char name[50];
};

struct Student
{
	int age;
	char sex;
	char name[50];

	int score;
};
  • 嵌套例子
struct Info
{
	int age;
	char sex;
	char name[50];
};

struct Student
{
	struct Info info;//嵌套另外一个结构体变量

	int score;
};
  • 初始化及访问
//结构体嵌套初始化(和不嵌套的结构体一致)
struct Student s = {18, 'm', "xiaoming", 18};
//打印嵌套结构体的内容
printf("%d, %c, %s, %d\n",s.info.age, s.info.sex, s.info.name, s.score);
  • 注意事项

    • 嵌套结构体变量,不能是本结构体类型。
    • 但是可以嵌套任何类型的结构体指针变量。
    struct Student
    {
    	struct Info info;
      	int score;
    //嵌套本结构体类型,会报错。因为`struct Student`类型不确定,内存大小无法确定
    //	struct Student temp;
    
    //嵌套任何类型的结构体指针变量。因为指针大小是确定的。32位系统4字节大小,64位系统8字节大小
      	struct Student *next;
    };
    
  • 补充:typedef的使用:将 struct Student结构体重新命名代替为为 Student

    • 传统用法
    struct Student
    {
    	int age;
    	char sex;
    	char name[50];
    };
    
    typedef struct Student Student;//有分号
    
    • 常见用法
    typedef struct Student
    {
    	int age;
    	char sex;
    	char name[50];
    }Student;
    

2.4.同类型结构体相互赋值

  • 直接赋值
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

//typedef改一个类型名,把复杂名字改简单点,不能创造新的类型

typedef struct Student
{
	int age;
	char sex;
	char name[50];
}Student;


int main(void)
{
	Student s1 = {18, 'm', "xiaoming"};
	Student s2;

//	s2 = s1;	//同类型的两个变量相互赋值
	strcpy(s2.name,s1.name);
//	成员变量逐一拷贝
	s2.age = s1.age;
	s2.sex = s1.sex;

	printf("%d, %c, %s\n",s2.age, s2.sex, s2.name);
	return 0;
}
  • 通过函数实现

    • 关于内存栈区与堆区操作(这里是实现用指针形式访问结构体成员)

      • 如果结构体只定义了一个空指针,便不能操作其内容,要让其指针指向有效地址(栈区或者堆区)
      • 在进行指向栈区空间时,不需要释放,该空间由系统自行维护。
      • 在进行指向堆区空间时,需要自己申请空间,并在使用完成后手动释放。
      #include <stdio.h>
      #include <stdlib.h>
      #include <string.h>
      typedef struct Student
      {
      	int age;
      	char sex;
      	char name[50];
      }Student;
      
      int main(void)
      {
      	//定义一个结构体指针变量
      	Student *p = NULL;
        	//让指针有个合法指向(指向栈区)
      //	Student temp;//普通结构体变量,栈区
      //	p = &temp;
      
      	//让指针有个合法指向(指向堆区)
      	p = (Student *) malloc(sizeof(Student));//申请一个`Student`大小的地址,并用p指向
      	if(p == NULL)
      	{
      		printf("分配内存失败\n");
      		return -1;
      	}
      
      	strcpy(p->name, "小明");
      	p->sex = 'm';
      	p->age = 19;
      
      	printf("%d, %c, %s\n",p->age, p->sex, p->name);
      
      	//手动释放堆区空间
      	free(p);
      	p = NULL;
      
      	return 0;
      }
      
    • 函数代码实现赋值

      • 这里使用了 const,使s1传入进来的参数不可改变。可防止误改传入参数s1
      #include <stdio.h>
      #include <stdlib.h>
      #include <string.h>
      typedef struct Student
      {
      	int age;
      	char sex;
      	char name[50];
      }Student;
      
      //从左往右看,const修饰最近的字符,这修饰的是指针变量
      //这里使用了const,使s1所指向的内存(成员变量)不可改变,指针变量可以改变
      void fun(const Student *s1, Student *s2)
      {
      	//两个指针的内容实现赋值
      	*s2 = *s1;
      }
      
      int main(void)
      {
      	Student s1;
      	Student s2 = {0,'0',"0"};
      	printf("%d, %c, %s\n",s2.age, s2.sex, s2.name);
      
      	strcpy(s1.name, "小明");
      	s1.sex = 'm';
      	s1.age = 19;
      	//通过一个函数将s1的值赋值给s2
      	fun(&s1, &s2);
      
      	printf("%d, %c, %s\n",s2.age, s2.sex, s2.name);
      	return 0;
      }
      

三、内存分析

  • 通过一个函数,动态分配内存
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

typedef struct Student
{
	int age;
	char sex;
	char name[50];
}Student;
//需要注意的是:所有子函数运行完后的变量都会自动进行释放(不能再直接调用)
Student* fun()
{
	Student *a;
	a = (Student *)malloc(sizeof(Student));
	return a;
}

int main(void)
{
	Student *p = NULL;
	p = fun();//返回并用`p`指向所开辟出来的地址
	//只有p指向相应地址时,才能进行赋值
	strcpy(p->name, "小明");
	p->sex = 'm';
	p->age = 19;

	printf("%d, %c, %s\n", p->age, p->sex, p->name);
	//释放子函数中`a`开辟出来的空间(`p`和`a`均会指向,只不过`a`在函数调用完后被释放了)
	free(p);
	p = NULL;
	return 0;
}

四、出现过的问题

  1. 不能将 x*类型的值分配到 x*类型的实体(以下代码会出现该问题)

    #include <stdlib.h>	//malloc();
    #include <stdio.h>
    
    typedef struct /*Student*/	//出错原因:在定义结构体时没有定义变量
    {
    	//数据域(位置)
    	int x, y;
    	//指针域
    	struct Point *next;
    }Student;
    
    void main()
    {
    	Student *pnew = (Student *)malloc(sizeof(Student));
    	Student *s = NULL;
    	pnew->next = s; //此处会报错:`s为x*类型的值` , pnew->next为`x*类型的实体`
    }
    

    解决方法:在定义结构体时加上结构体变量 Student即可