深入理解C语言结构体(数据结构基础)
引言:首先我们怎样认识结构体?结构体在c语言基础以及数据结构中的地位是什么?可以说,结构体作为了一种中间比较重要的桥梁基础,是从基础的C语法过渡到C数据结构必不可少的一种重要数据结构。以及配合指针,成就C语言的灵魂所在。
深入理解C语言结构体
一:结构体定义与初始化引用
1:结构体是什么?它的特点?
<1>相对于数组存储结构的区别?
数组是一种存储结构,一种可以存放相同类型的存储结构。比如int类型的存储结构就只能存放Int类型的数据,但是你若是想要描述清除一个学生的身份信息,一个数组绝对是不行的,比如名字,学号等这些,必须要使用多个数组来说明学生的信息。我们还需要构造关联的索引形成一一的对应,如果大型项目这样做,那么就会无比臃肿了。
结构体是这样一种数据结构,一种可以存放多种数据类型的数据结构。也就是既可以有int类型,也可以有char,float等,以此类推。是一种非常方便的数据结构,对于描述一个事物的特征具有非常方便的意义。
<2>结构体定义(常规)
我们定义结构体的方式有多种
1:第一种比较规范化的定义如下
//定义结构体,包含完整的结构体名和结构体变量
struct name//结构体名/标签
{
/* data */
int a;//结构体成员
float b;
char c;
double d;
}var;//结构体变量
2:无结构体名
//定义结构体
struct
{
/* data */
int a;
float b;
char c;
double d;
}var;//结构体变量
3:无结构变量
//定义结构体
struct name
{
/* data */
int a;
float b;
char c;
double d;
};
当然你就算定义一个空的结构体也是可以的,我这边只得空就是没有结构体成员,没有成员就是空的啦!
那么结构体在定义的时候可以简化到什么程度呢?
可以简化到只有结构体变量或者结构体名。但是如果连结构体变量或者结构体名都没有的化就会报错,没有办法识别。也当然没有什么意义。
//定义结构体
struct name//只含有结构体名
{
};
//定义结构体
struct
{
}var;//只含有结构体变量
4:有typedef 关键字情况下对结构体初始化
<3>结构体初始化(常规)
我们下面对结构体进行初始化,初始化的结构体才真正具有了一定的意义。
我们来举例几种初始化结构体的方法
1:在定义变量的时候初始化
//定义结构体
struct name
{
/* data */
int a;
float b;
char c;
double d;
}var = {1,2.0,'c',3.00};//对结构体变量进行了初始化
顺便说明一下结构体初始化对结构体变量的意义的说明
结构体名和结构体变量是不一样的,结构体名可以作为结构体的一种表标识,但是它不具有a,b,c,d这四个成员的属性,而我们的一个结构变量就具有了a,b,c,d四个成员属性。所以我们初始化需要对var赋值,而不是name。
2:在main函数中通过 struct name + var 定义变量并进行初始化
#include<stdio.h>
#include<windows.h>
//定义结构体
struct name
{
/* data */
int a;
float b;
char c;
double d;
}var = {1,2.0,'c',3.00};//对结构体变量进行了初始化
int main()
{
struct name var1 = {1,2.0,'c',4.0};//在main函数中声明结构体变量并进行初始化
system("pause");
}
注意:如果在main函数中,我们不能直接用var={};来进行赋值。
<3>typedef关键字对结构体的改变
先说明一下typedef关键字的作用。
我们常规的定义结构体的方式便是struct 这样的形式,但是我们也会常常见到typedef struct的形式。
那么typedef关键字的作用是什么呢?
明白了说就是给紧挨着后i面的类型起一个别名。举例如下:
// An highlighted block
//定义结构体
typedef struct name
{
/* data */
int a;
float b;
char c;
double d;
}var_dif_name;
这里定义的struct name是我们的结构体,然后下面是var_dif_name,此时的var_dif_name相当于struct name,现在的var_dif_name并不是结构体变量,而是struct name这个整体类型的别称。所以你此时这样定义了不能再对var_dif_name进行赋值。如下是错误的
我们可以更加简化一些,这样来做
//定义结构体
typedef struct
{
/* data */
int a;
float b;
char c;
double d;
}var_dif_name ;
int main()
{
var_dif_name var = {1,2,'c',8};
system("pause");
}
可以看到,若如上所言,那么这个意思就是var_dif_name就相当于struct了吗?如果这样去理解,顺着逻辑理解下去的化,那么var_dif_name就相当于struct var了,如果这样的话,这样做不是错误的话吗?其实这里的名字是隐含掉了,tag成为一种匿名的方式,我们这里省略了struc后面的名字tag。所以我们这样定义也是可以的。编译器会认为合法的,
举例了结构体使用typedef,那么我们为什么要使用它,使用它的意义何在?你想啊,如果没有typedef,那么如果我们再主函数里面声明多个结构体变量就需要struct name +var,当有了typedef,我们就可以用一个替代的var_dif_name + var来代替struct name +var。
2:引用结构体变量的成员属性
<1>在主函数中使用结构体变量
那么我们如何在主函数中使用结构体变量呢?
直接printf()可以吗?当然不可以。你想啊,我们输出一个结构体变量,它可能具有不同类型的成员属性,那么你怎么可以用一种形式输出呢?当然不可以。所以我们需要这样做。
#include<stdio.h>
#include<windows.h>
//定义结构体
typedef struct
{
/* data */
int a;
float b;
char c;
double d;
}var_dif_name ;
int main()
{
var_dif_name var = {1,2,'c',8};
printf("%d-%f,%c,%d",var.a,var.b,var.c,var.d);
system("pause");
}
引用结构体的成员属性,需要我们通过结构体变量来进行引用。结构体变量.结构体成员。
<2>通过指针进行引用
如何通过指针对结构体进行操作也是一件比较巧妙地事情。指针与结构体地结合是过渡数据结构的重要一步。
这边需要考虑的是,结构体变量内含有多个属性。int,char,类型这些,我们可以定义一个指针指向一个结构体变量吗?当然不可以,为了使类型匹配,所以需要也定义一个结构体指针来进行操作。
#include<stdio.h>
#include<windows.h>
//定义结构体
typedef struct
{
/* data */
int a;
float b;
char c;
double d;
}var_dif_name ;
int main()
{
var_dif_name var = {1,2,'c',8.00};
var_dif_name * var_v = NULL;
var_v= &var;
printf("%d",var_v->a);
//printf("%d",(*var_v).a);
system("pause");
}
释疑:通过定义的结构体指针来引用结构体成员属性,先让我们的指针指向变量地址,然后一种是通过->符号取成员属性的值,一种是进行一层进行解引用,然后取到结构体变量层次,然后进行.来进行取属性,也就是说这两种方法是等效的。但是要注意.的运算级别高于号,所以为了符合逻辑上的需要我们需要加上()。
二:结构体嵌套
1:结构体嵌套使用
结构体嵌套的话,也是一样的道理,如果你的结构体嵌套了另一个结构体,如果你要取到成员属性的值,那么你需要进行两层解引用。来举例。
#include<stdio.h>
#include<windows.h>
struct Date
{
int year;
int month;
int day;
};
struct Book
{
char title[128];
char author[40];
float price;
struct Date date;//结构体的嵌套
char publisher[40];
} book = {
"<<带你学c带你飞>>",
"小甲鱼",
42,
{2017,11,11},
"清华大学出版社"
};
int main()
{
printf("title::%s\n",book.title);
printf("author:%s\n",book.author);
printf("food: %.2f\n",book.price);
printf("date:%d-%d-%d\n",book.date.year,book.date.month,book.date.day);
printf("publisher:%s\n",book.publisher);
system("pause");
}
三:结构体变量做参数传递
1:在函数中传入结构体变量
结构体作为参数传递的话,我们只要在函数内部将形式参数的类型定义为结构体类型。具体代码示例如下。
#include<stdio.h>
#include<windows.h>
struct Date
{
int year;
int month;
int day;
};
struct Book
{
char title[128];
char author[40];
float price;
struct Date date;
char publisher[40];
};
struct Book getInput(struct Book book)
{
printf("Please enter the title::\n");
scanf("%s",book.title);
printf("Please enter the author:\n");
scanf("%s",book.author);
printf("please enter the price\n");
scanf("%f",&book.price);
printf("please enter the date\n");
scanf("%d-%d-%d",&book.date.year,&book.date.month,&book.date.day);
printf("please enter the publisher:\n");
scanf("%s",book.publisher);
}
void printBook(struct Book book)
{
printf("title:%s\n",book.title);
printf("author:%s\n",book.author);
printf("price:%.2f\n",book.price);
printf("date:%d-%d-%d\n",book.date.year,book.date.month,book.date.day);
printf("publisher:",book.publisher);
}
int main(void)
{
struct Book b1,b2;
printf("please enter the message of the first book:\n");
b1 = getInput(b1);
putchar('\n');
printf("please enter the message of the second book:\n");
b2 = getInput(b2);
printf("\n\nover!");
printf("first book:\n");
printBook(b1);
putchar('\n');
printf("second book:\n");
printBook(b2);
system("pause");
}
说明,如果你的结构体变量传入到函数不是传入的地址,那么如果想要成功的更新结构体包括赋值这些,那么你就需要返回一个接受的变量,否则结构体无法更新。
2:在函数中传入结构体变量的地址
当我们给函数中传入结构体变量的地址的时候,那么在主函数就无需再用变量接收传递改变,当把地址传入去的时候,那么就成为一种实质上的改变。具体看示例代码
#include<stdio.h>
#include<windows.h>
struct Date
{
int year;
int month;
int day;
};
struct Book
{
char title[128];
char author[40];
float price;
struct Date date;
char publisher[40];
};
void *getInput(struct Book *book)
{
printf("Please enter the title::\n");
scanf("%s",book->title);
printf("Please enter the author:\n");
scanf("%s",book->author);
printf("please enter the price\n");
scanf("%f",&book->price);
printf("please enter the date\n");
scanf("%d-%d-%d",&book->date.year,&book->date.month,&book->date.day);
printf("please enter the publisher:\n");
scanf("%s",book->publisher);
}
void *printBook(struct Book *book)
{
printf("title:%s\n",book->title);
printf("author:%s\n",book->author);
printf("price:%.2f\n",book->price);
printf("date:%d-%d-%d\n",book->date.year,book->date.month,book->date.day);
printf("publisher:",book->publisher);
}
int main(void)
{
struct Book b1,b2;
printf("please enter the message of the first book:\n");
getInput(&b1);
putchar('\n');
printf("please enter the message of the second book:\n");
getInput(&b2);
printf("\n\nover!");
printf("first book:\n");
printBook(&b1);
putchar('\n');
printf("second book:\n");
printBook(&b2);
system("pause");
}
当我们传入地址的时候,那么形式参数就需要为指针类型,指针才可以存放地址。
四:计算结构体变量占用的字节?(结构体成员的内存对齐?)
struct A
{
char a;
int b;
char c;
} a={'x',5,'z'};//结构体存在内存对齐
请问结构体A占用多少字节?是1+4+1=6吗?
非也,答案是12个字节,你看啊!虽然char一个字节,但是int类型占用4个字节,两个char类型会和int类型对齐,int类型是四个字节,那么两个char也会给4个,这样就一共十二个字节。
那么再来看下面的占用多少?编译器不是傻子。
struct B
{
char a;
char c;
int b;
} c={'x','y',6};
什么?你不会认为三个字节吧?hh
来看小甲鱼的图。非常详细了,所以我就拿来用了。
所以这样一共八个字节。