渣渣c的c语言学习之路
1.关于c语言的结构体:
首先我们为什么要用到结构体,我们都已经学了很多int char …等类型还学到了同类型元素构成的数组,以及取上述类型的指针,在一些小应用可以灵活使用,然而,在我们实际应用中,每一种变量进行一次声明,再结合起来显然是不太实际的,类如一位学生的信息管理,他可能有,姓名(char),学号(int)成绩(float)等多种数据。如果把这些数据分别单独定义,就会特别松散、复杂,难以规划,因此我们需要把一些相关的变量组合起来,以一个整体形式对对象进行描述,这就是结构体的好处。
2首先我们要了解一些小知识
2.1只有结构体变量才分配地址,而结构体的定义是不分配空间的。
2.2结构体中各成员的定义和之前的变量定义一样,但在定义时也不分配空间。
2.3结构体变量的声明需要在主函数之上或者主函数中声明,如果在主函数之下则会报错
2.4c语言中的结构体不能直接进行强制转换,只有结构体指针才能进行强制转换
2.5相同类型的成员是可以定义在同一类型下的
列如
struct Student {
int number,age;//int型学号和年龄 char name[20],sex;//char类型姓名和性别 float score; };
最后的分号不要忘了 有的编译器会自动加上,因此有的同学就会不注意。
3关于结构体变量的定义和引用
在编译时,结构体的定义并不分配存储空间,对结构体变量才按其数据结构分配相应的存储空间
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)都是最大基本类型成员字节大小的整数倍,如果不是编译器会自动补齐,
关于这个我们简单介绍下:
1.偏移量—-偏移量指的是结构体变量中成员的地址和结构体变量首地址的差。即偏移字节数,结构体大小等于最后一个成员的偏移量加上他的大小,第一个成员的偏移量为0,
struct S1 {
char a; int b; double c; };
这里char a 偏移量为1 之后为int b 因为偏移量1不为int(4)的整数倍,所以会自动补齐,而在 double c 时,偏移量为8 是double(8)的整数倍,所以不用自动补齐 最后求得结构体得大小为 16
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};
注意字符为‘ ’ 字符串为””
4.2定义结构体之后逐个赋值
stu1.name="王伟"; stu1.sex='M'; stu1.number=12305; //也可用strcpy函数进行赋值 strcpy(stu1.name,"王伟");
4.3定义之后任意赋值
struct Student stu1={
.name="Wang", .number=12345, .sex='W', };//可以对任意变量赋值
这样写的好处时不用按照顺序来进行初始化,而且可以对你想要赋值的变量直接进行赋值,而不想赋值的变量可以不用赋值
需要注意的是如果在定义结构体变量的时候没有初始化,那么后面就不能全部一起初始化了;
等下结构体数组初始化时我们还会有一个讲解
这里我们顺带提一下typedef说明结构体类型

这里的BOOK就相当于struct book的一个别名一样,用它来定义结构体变量非常简便
主要也是考二级要用到,所以我们简单介绍下
5结构体变量的引用(输出和输入)
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);//正确
(3)可以引用接头体变量成员的地址,也可以引用结构体变量的地址:
这里我们简单说下,具有相同类型的结构体变量组成数组就是结构体数组
结构体数组与结构体变量区别只是将结构体变量替换为数组
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[]=
struct Student stu1; stu1[3]={
{
"zhaozixuan",'M',12345}, {
"houxiaohong",'M',12306}, {
"qxiaoxin",'W',12546} };
这样子是错误的,
char str[20]; str="I love you";/* 这样会修改数组的地址,但是数组的地址分配之后是不允许改变的 */
在第一条语句中 str就已经被定义成数组而在C99标准中不允许将字符串(实际上是一个指针变量) 赋值给数组,所以如果我们直接赋值是错误的
1.定义数组时直接定义
char str[20]=“I love you”;
2.用strcpy或者memset函数进行复制
char str[20];
strcpy(str,“I love you”);
再用到memset函数时,出现了一些问题
对于memcset函数简单介绍下
memset
void *memset(void *s,int c,size_t n)
作用:将已开辟内存空间s的首n个字节的值设为值c。
char str[20]; memset(str,'a',20);
如果是字符类型数组的话,memset可以随便用,但是对于其他类型的数组,一般只用来清0或者填-1,如果是填充其他数据就会出错
int str[10]; memset(str,1,sizeof(str));//这样是错误的
这里我们说下这个错误,
首先我们要知道memset在进行赋值时,是按字节为单位来进行赋值的,每次填充的数据长度为一个字节,而对于其他类型的变量,比如int,占4个字节 所以sizeof(str)=40; 而用memset赋值时,将会对指向str地址的前40个字节进行赋值0x01(00000001) 的操作,把0x00000000赋值4次0x01操作变为0x0(000000000000)
3用指针(注意内存分配)
char *str;
str=“I love you”;
这两句话的本质是,在内存中开辟一段内存空间,把”I love you”放进这段内存空间,然后把这段内存空间的地址交给str,由于str是变量,所以给它赋值是合法的。
请注意,在我们进行数组初始化的时候如果定义的数组过长,而我们只初始化了一部分数据,对于未初始化的数据如果是数值型,则会自动赋值为0,对于字符型,会自动赋初值为NULL,即‘\0’ 即不足的元素补以默认值
这里我们在4小节中也提到了
比如
int str[10]={
1};//这里只是把str的第一个元素赋值为1,其他元素默认为0

7结构体与指针
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 (*p).cName student1.cName p->cName //可以进行正常的运算
7.2指向结构体数组的指针
struct Student stu1[5]; struct Student*p; p=stu[n]; (++p).number//是指向了结构体数组下一个元素的地址
struct Student {
char* Name;//这样防止名字长短不一造成空间的浪费 int number; char csex; }student1;
在使用时可以很好地防止内存被浪费,但是注意在引用时一定要给指针变量分配地址,如果你不分配地址,结果可能是对的,但是Name会被分配到任意的一的地址,结构体不为字符串分配任何内存存储空间具有不确定性,这样就存在潜在的危险,
struct Student {
char* Name; int number; char csex; }stu,*stu; stu.name=(char*)malloc(sizeof(char));//内存初始化
struct Student {
char* Name; int number; char csex; }stu,*stu; stu = (struct student*)malloc(sizeof(struct student));./*结构体指针初始化*/ stu->name = (char*)malloc(sizeof(char));/*结构体指针的成员指针同样需要初始化*/
首先我们要注意的一点,使用结构体变量作为函数参数的时候,采取的是值传递的方式,将结构体所占内存单元的内容全部传递给形参,并且形参必须也要是同类型的结构体变量,在使用时,会自动创建一个结构体变量作为原变量的副本,并且也需要占内存,并且在调用期间如果修改(形参)结构体中成员的值,修改值是无效的,

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

在这里我们再提供几种互换两个结构体的方法
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函数互换 3用memcpy()函数进行互换 4比较笨的方法: 用for循环互换
最后提下memset清空结构体
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);
整理不易,点个赞再走呗!
发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/198659.html原文链接:https://javaforall.net
