C语言结构体整理

C语言结构体小结

**关于结构体 **

把一些相关的变量组合起来,以一个整体形式对对象进行描述.

**一些相关知识 **

  • 只有结构体变量才分配地址,而结构体的定义是不分配空间的
  • 结构体中各成员的定义和之前的变量定义一样,但在定义时也不分配空间
  • 结构体变量的声明需要在主函数之上或者主函数中声明,如果在主函数之下则会报错
  • c语言中的结构体不能直接进行强制转换,只有结构体指针才能进行强制转换
  • 相同类型的成员是可以定义在同一类型下的
1
2
3
4
5
6
7
struct Student
{ 
    int number,age//int型学号和年龄
    char name[20],sex;//char类型姓名和性别
    float score;
}

关于结构体变量的定义和引用

在编译时,结构体的定义不分配存储空间,对结构体变量才按其数据结构分配相应的存储空间

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
struct Book
 { 
    char title[20];//一个字符串表示的titile 题目
    char author[20];//一个字符串表示的author作者
    float value;//价格表示 
 };//这里只是声明 结构体的定义 
struct Book book1,book2;//结构体变量的定义 分配空间

book1.value;//引用结构体变量


定义结构体变量以后,系统就会为其分配内存单元,比如book1和book2在内存中占44个字节(20+20+4)具体的长度你可以在你的编译器中使用sizeof关键字分别求出来。

用sizeof关键字求结构体长度时,返回的最大基本类型所占字节的整数倍 比方说我们上面求得的为44 为 float(4个字节)的整数倍,但是我们把title修改为title[22];这时正常长度为46 ,但是你会发现实际求得的为48,(4的整数倍)

这涉及到结构体的存储:

  1. 结构体整体空间是占用空间最大的成员(的类型)所占字节数的整数倍。
  2. 结构体的每个成员相对结构体首地址的偏移量(offset)都是最大基本类型成员字节大小的整数倍,如果不是编译器会自动补齐.

偏移量----偏移量指的是结构体变量中成员的地址和结构体变量首地址的差。即偏移字节数,结构体大小等于最后一个成员的偏移量加上他的大小,第一个成员的偏移量为0,

1
2
3
4
5
6
7
8
struct S1
{
    char a;

    int b;

    double c;
};

这里char a 偏移量为1 之后为int b 因为偏移量1不为int(4)的整数倍,所以会自动补齐,而在 double c 时,偏移量为8 是double(8)的整数倍,所以不用自动补齐 最后求得结构体得大小为 16

结构体变量的初始化

结构体的初始化有很多注意的地方:

在对结构体变量初始化时,要对结构体成员一一赋值,不能跳过前面成员变量,而直接给后面成员赋初值,但是可以只赋值前面几个,对与后面未赋值的变量,如果是数值型,则会自动赋值为0,对于字符型,会自动赋初值为NULL,即‘\0’

定义时直接赋值

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
struct Student
{ 
    char name[20];
    char sex;
    int number;
}stu1={"zhaozixuan",'M',12345};
//或者
struct Student
{ 
    char name[20];
    char sex;
    int number;
}
struct Student stu1={"zhaozixuan",'M',12345};

注意字符为' ',而字符串为" ";

定义结构体之后逐个赋值

1
2
3
4
5
stu1.name="王伟"
stu1.sex='M';
stu1.number=12305;
//也可用strcpy函数进行赋值
strcpy(stu1.name,"王伟");

定义之后任意赋值

1
2
3
4
5
 struct Student stu1={
  .name="Wang",
  .number=12345,
  .sex='W', 
 };//可以对任意变量赋值

这样写的好处时不用按照顺序来进行初始化,而且可以对你想要赋值的变量直接进行赋值,而不想赋值的变量可以不用赋值

需要注意的是 如果在定义结构体变量的时候没有初始化,那么后面就不能全部一起初始化了;

利用typedef说明结构体类型:

这里的BOOK就相当于struct book的一个别名一样,用它来定义结构体变量非常简便

结构体变量的引用(输出和输入)

  • .是运算符,在所有运算符优先级中最高
  • 如果结构体的成员本身是一个结构体,则需要继续用.运算符,直到最低一级的成员。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
struct Student
{   char name[20];
    char sex;
    int number;
    struct Date
    {
        int year;
        int month;
        int day;
    }birthday;

}stu1;
printf("%d",stu1.birthday);//这样子是错误的,因为birthday也是一个结构体变量
scanf("%d",&stu1.birthday.month);//正确
  • 可以引用接头体变量成员的地址,也可以引用结构体变量的地址和

结构体数组及其初始化

具有相同类型的结构体变量组成数组就是结构体数组

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
struct Student
{ 
    char name[20];
    char sex;
    int number;
}
stu1[5]={
     {"zhaozixuan",'M',12345},
     {"houxiaohong",'M',12306},
     {"qxiaoxin",'W',12546},
     {"wangwei",'M',14679},
     {"yulongjiao",'W',17857}
};
stu1[3].name[3]//表示stu1的第三个结构变量中姓名的第五个字符
//若初始化时已经是结构体数组全部元素[]中的数可以不写如stu1[]=

注意结构体数组要在定义时就直接初始化,如果先定义再赋初值是错误的

1
2
3
4
5
6
7
struct Student stu1
stu1[3]={
  {"zhaozixuan",'M',12345},
  {"houxiaohong",'M',12306},
  {"qxiaoxin",'W',12546}
  };
  //错误

正确的方法有3种:

  1. 定义数组时直接定义 char str[20]="I love you" ;

  2. 使用strcpy或者memset函数进行复制

1
2
3
char str[20];
strcpy(str,"I love you");
再用到memset函数时,出现了问题

memset void *memset(void *s,int c,size_t n) 作用:将已开辟内存空间s的首n个字节的值设为值c。

1
2
char str[20]
memset(str,'a',20);

如果是字符类型数组的话,memset可以随便使用,对于其他类型的数组,一般只用来清零或者填-1.

int str[10];
memset(str,1,sizeof(str));
  1. 用指针
1
2
char *str;
str = "I love you";

这两句话的本质是,在内存中开辟一段内存空间,把"I love you"放进这段内存空间,然后把这段内存空间的地址交给str,由于str是变量,所以给它赋值是合法的。

结构体与指针

指针指向的是变量所占内存的首地址,在结构体中,指针指向的是结构体变量的起始地址,当然也可指向结构体变量的元素

指向结构体变量的指针

定义形式一般为: struct 结构体名* 指针名; struct Student *p

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
struct Student
{   
    char cName[20];
    int number;
    char csex;  
}student1;
struct Student*p;
p=&student1;
//若为结构体数组则
struct Student stu1[5];
struct Student*p;
p=stu1;//因为stu1为结构体数组而p=stu1直接是指向stu1的首地址,就不用再加&符

用结构体指针变量访问结构体变量成员有以下两种方式: (*p).cName //这里的括号不能少,在5.1中有提到 p->cName

1
2
3
4
5
6
p->cName //1

(*p).cName //2

student1.cName//3
p->cName //可以进行正常的运算

三种方式是等价的

指向结构体数组的指针

想要用指针访问结构体数组的第n个数据时可以用

1
2
3
4
struct Student stu1[5];
struct Student*p;
p=stu[n];
(++p).number//是指向了结构体数组下一个元素的地址

结构体成员是指针类型变量

1
2
3
4
5
6
7
struct Student
{
    char* Name;//这样防止名字长短不一造成空间的浪费
    int number;
    char csex;  
}student1;

在使用时可以很好地防止内存被浪费,但是注意在引用时一定要给指针变量分配地址,如果你不分配地址,结果可能是对的,但是Name会被分配到任意的一的地址,结构体不为字符串分配任何内存存储空间具有不确定性,这样就存在潜在的危险,

1
2
3
4
5
6
7
8
struct Student
{
    char* Name;
    int number;
    char csex;  
}stu*stu

stu.name=(char*)malloc(sizeof(char));//内存初始化

如果我们定义了结构体指针变量,他没有指向一个结构体,那么这个结构体指针也是要分配内存初始化的,他所对应的指针类型结构体成员也要相应初始化分配内存

1
2
3
4
5
6
7
8
9
struct Student
{
    char* Name;
    int number;
    char csex;  
}stu,*stu;
stu = (struct student*)malloc(sizeof(struct student));./*结构体指针初始化*/
  stu->name = (char*)malloc(sizeof(char));/*结构体指针的成员指针同样需要初始化*/ 

结构体作为函数参数

使用结构体变量作为函数参数的时候,采取的是值传递的方式,将结构体所占内存单元的内容全部传递给形参,并且形参必须也要是同类型的结构体变量,在使用时,会自动创建一个结构体变量作为原变量的副本,并且也需要占内存,并且在调用期间如果修改(形参)结构体中成员的值,修改值是无效的

而如果用指针作为实参,传递给函数的形参,这时候传递的是结构体的地址,形参所指向的地址就是结构体变量的地址,这时候进行修改的话是可以修改的,这正是指针的精华所在

几种互换两个结构体的方法

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
struct Student
{
 char cName[20];
 int number;
 char csex;  
}student1,student2;
struct Student student1={"Wang",12345,'W'};
struct Student student2={"Zhao",54321,'M'}; 
struct Student*stu1=&student1;
struct Student*stu2=&student2;

struct Student *student3;
student3=stu1;
stu1=stu2;
stu2=student3;//互换地址

2对于同类型结构体直接互换值就行
struct stu student3;
student3=student1;
student1=student2;
student2=student3;
//这里也可以写成应strcmp函数互换

3memcpy()函数进行互换


4比较笨的方法: for循环互换

最后提一下memset清空结构体

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
struct Student
{
 char cName[20];
 int number;
 char csex;  
}stu1;

一般情况下,清空str的方法
  str.cName[0]='\0';
  str.csex='0';
  str.number=0;
  但是我们用memset就非常方便
  memset(&str,0,sizeof(struct Student));
  如果是数组:
  struct Student stu[10];
  就是
  memset(stu,0,sizeof(struct Student)*10);


updatedupdated2020-04-152020-04-15