文章目录
位段
#include <studio.h>
struct S
{
//一个字节8个比特位,4个字节4个字节开辟,不足的直接浪费掉
int a:2;//2个比特位,本来int得占4个字节,也就是32个比特位
int b:5;//5个比特位
int c:10;//10个比特位
int d:30;//30个比特位
}
int main()
{
struct S s;//结构体变量的说明
printf("%d\n",sizeof(s))//输出8
s.a=10;
s.b=20
s.c=3
s.d=4
return 0;
}
- 位段的成员必须是int unsigned int或signed int或者是char类型
- 位段的成员名后边有一个冒号和一个数字
- 位段的空间上是按照以4个字节(int)或者1革命字节(char)的方式来开辟的
- 位段是为了节省空间
枚举enum
//枚举类型定义
enum 变量名
{
枚举值列表
};
//例如:
enum sex//定义枚举
{
MAlE;//枚举的可能选项列表
FEMALE;
SECRET;
};
int main()
{
enum sex s=MALE;//枚举变量的定义并使用
};
//枚举变量说明
enum sex
{
MAlE;
FEMALE;
SECRET;
}a,b,c;//这里的abc是枚举变量,MAlE;FEMALE;SECRET;是枚举常量
//枚举变量的使用
int main()
{
a=MALE;
};
- 枚举类型是一种基本数据类型,也就是一一列举的意思
- 枚举值是常量,不能在对其进行赋值,例如:下方MAlE;FEMALE;SECRET;已经是常量,不能在下方写成MALE=2,等
- 枚举值默认从0开始,以后逐渐增1
- 只能把枚举常量赋予给枚举变量
- 不能把元素的数值直接赋予枚举常量,但可以在枚举中先行更改枚举常量的初始值
- 如果一定要在下方使用数值,必须使用强制类型转化MALE=(enum sex)2;
- 枚举标识符不能再用来定义变量,也就是MALE等不能再使用来定义变量,例如:int MALE =3;是错误的
- 注意:枚举元素使用时是,枚举常量,不要加单,双引号
枚举的优点
- 增加代码的可读性和可维护性
- 和# define定义的标识符比较有类型检查,更加严谨
- 防止了命名污染(封装)
- 便于调试
- 使用方便,一次可以定义多个常量
共用体(联合)union
//共用体的定义
union 共用体名
{
成员列表
};
//例如:
union date
{
char c;
int i;
};
int main()
{
union date u;//拿共用体创建变量
printf("%d\n",sizeof(u));//输出4
printf("%p\n",&u);//地址相同,三者共用同一块空间
printf("%p\n",&(u.c));
printf("%p\n",&(u.i));
return 0;
};
//共用体的定义的同时定义变量
union date
{
char c;
int i;
}a,b,c;
int main()
{
return 0;
};
- 与结构体类似,但结构体各个成员都会分配相应的内存空间,而共用体的所用成员 共用一段内存
- 这样一个联合变量空间的大小,至少是最大成员空间的大小
- 共用体使用联合覆盖技术,几个成员变量相互覆盖,从而共用一段内存,并且在同一时刻只能使用其中的一个成员变量,
- 如果对新的成员变量赋值,则原来的成员变量将被覆盖
- 共用体也是一种特殊的自定义类型
知识扩展
int main()
{
//高字节---->低字节
//int a=0x11223344;
//低地址----------->高地址
//....[][11][22][33][44][][]....大端存储模式->高字节放在低地址
//....[][44][33][22][11][][]....小端存储模式->低字节放在高地址
}
动态内存分配
- 整个程序包含了申请内存空间,使用内存空间,释放内存空间三个步骤,实现了空间的动态分配
目前内存的使用方式,
-
1,创建一个变量
int a=12//局部变量,存放栈区
int b=12//全局变量,存放静态区
-
2,创建一个数组
int arr[10];
局部放在栈区 ,全局放在静态区 -
上述地开辟空间的方式,空间开辟大小式固定的,数组在申明时,必须指定数组的长度,它所需要的内存在编译时分配
-
数组的大小必须是常量,如果想用变量表示长度,想对数组的大小做动态说明是不允许的
-
所需的内存空间取决于实际输入的数据,而这个无法事先确定,为了解决这个问题,C语言提供一些内存管理函数,可以根据需要动态地分配空间
-
如果size为0,这个标准未定义,有错
分配内存空间函数malloc
- 为确保内存分配精确,常常与sizeof一起使用
- 分配一块长度为size字节的连续区域
- 返回值为该区域的首地址
- (类型说明符*)用于强制类型转换
#include <stdlib.h>//malloc函数所需要的头文件
//调用形式:
(类型说明符*)malloc(size)//向内存申请size个字节,然后(类型说明符*)用来强制转换
int main()
{
//向内存申请10个整型的空间
int* p=(int*)malloc(10*sizeof(int))
//申请10个整型的空间赋给整型指针p,但因为malloc返回类型默认为volid所以用(int*)强制转换
if(p==NUll)//如果开辟空间失败,尽量检查是否失败
{
printf("%s\n",strerror(errno))
//strerror函数可以把错误码所对应的错误信息打印出来
}
else//开辟成功
{
int =0;
for(i=0;i<10;i++)
{
*(p+i)=i;//p+i是下标为i的元素地址,*(p+i)找下标为i的元素,把i放在开辟空间中有10个在使用空间
}
for(i=0;i<10;i++)
{
printf("%d\n",*(p+i))
}
}
free(p);//释放空间,但存放首地址的p没变
p=NUll;//主动设置为空指针,防止p被”怀仁“找到
return 0;
}
释放内存空间函数free
- 当空间申请的空间不再使用的时候,就应该还给操作系统
- free函数专门来做动态内存的释放和回收
- 当程序生命周期到了,也会自动释放
- 该函数只释放 由malloc 或calloc函数所分配的动态内存空间区域
//释放ptr所指向的一块内存空间
free(void *ptr)
free(p)//谁开辟,谁释放,不回收,有可能内存泄漏
分配内存空间函数calloc
(类型说明符*)calloc(n.size)
//按stu的长度分配两块连续区域,强制转换为指向stu的结构体指针类型。并把首地址赋予指针变量ps
ps=(struct stu*)calloc(2,sizeof(struct stu));
int *ps=(int*)calloc(10,sizeof(int));
- 在内存动态存储区中分配n块长度为size字节的连续区域
- 函数的返回值为该区域的首地址
- calloc会在返回地址之前把申请的空间的每个字节初始化内容默认都为0
- (类型说明符*)用于强制类型转换
- 为确保内存分配精确,常常与sizeof一起使用
内存调整函数realloc
- realloc函数的出现让动态内存管理更加灵活
- 它可以做到对动态开辟内存大小的调整
- 这个函数调整原空间大小的基础上,还会将原来内存中的数据移动到新的空间,然后释放旧空间
- 如果p指向的空间之后有足够的内存空间可以追加,则直接追加,后返回p
- 如果p指向的空间之后没有足够的内存空间可以追加,则重新找一个新的内存区域,并且把原来内存中数据拷贝回来,释放旧的内存空间 ,最后返回新开辟的内存空间地址
void* realloc(void*ptr,size_t size);
//ptr是要调整的内存大小
//size是调整后的大小
//返回值为调整之后的内存起始位置
int main()
{
int *p=(int*)malloc(20);
if(p==NULL)
{
printf("%s\n",strerror(errno))}
}
else//开辟成功
{
int =0;
for(i=0;i<10;i++)
{
*(p+i)=i;
}
}//截至到这里,只是在使用malloc函数开辟的空间
//假设开辟的空间不够,希望多20个,这就要用到realloc函数
int *p2=realloc(p,40);
if(p2==NULL)
{
printf("开辟失败"}
}
else if(p2!=NULL)
{
p=ptr;//目的是保持从始至终始终用p维护新的内存
printf("开辟成功"}
for(i=5;i<10;i++)
{
*(p+i)=i//p+5就是下标是5的元素地址。把5赋给下标是5的元素
}
for(i=0;i<10;i++)
{
printf("%d",*(p+i))
}
}
//释放旧空间
free(p);
//使指针为空
p=NULL'
return 0;
}
常见的动态内存错误
- 对NULL指针的解引用操作
- 对动态开辟内存的越界访问
- 对非动态开辟内存使用free释放
- 使用free释放一块动态开辟内存的一部分
- 对同一块动态内存多次释放
- 动态开辟内存忘记释放(内存泄漏)
用指针处理链表
- 使用动态分配时,每个结点(每次分配的一块连续空间)之间可以是不连续的(节点内是连续的),节点之间的联系可用指针实现
- 在节点结构体中定义一个成员项,用来存放下一结点的首地址,这个存放地址的成员,常被称作“指针域”,存放数据的各个成员笼统的称为“数据域”
- 第一个节点的指针域存放第二个节点的首地址…如此串联下去,最后一个结点没有后续结点,其指针域可赋为0,这样的连接方式,在数据结构中称为链表
- 链表能更好的利用内存,按需分配和释放存储空间
- 链表中插入或删除一个节点,只需改变某节点的指针域即可
- 第0个节点称为头节点或链表点,存放首地址,无数据,只是指针变量
- 每个节点两个域,一个是数据域,存放各种实际数据,一个是指针域,存放下一节点首地址
- 链表中每一个节点都是同一种结构体类型
#include <studio.h>
#include <math.h>//引用库函数
#define N 3
struct student
{
long num;
float score;
struct student *next;
//前两个成员num,score组成数据域,
//后一个成员next构成指针域,它是一个指向stu类型结构体的指针变量
};
void main()
{
struct student *head,*create(void)
//建立链表函数 struct student* create(void)
void plink(struct student *head);
//输出链表函数 void plink(struct student *head);
float averf(struct student *head);
//求平均值函数 float averf(struct student *head);
int i,len;
float aver;
head==NULL;
head==create();//返回链表头指针
plink(head);
aver=averf(head);//返回平均值
printf("Average=%5.2f",aver);//输出平均值
}
struct student* create()
{
struct student *head,*p1,*p2;
int i,len;
len=sizeof(struct student);
for(i=1;i<=N;i++)
{
p1 =(struct ,student*)malloc(len);
printf ("Enter num, score:");
scanf(”&ld,%f", &p1->num, &p1->score)
if(i==1)head=p2=pl;//头节点
else { p2->next=p1; p2=p1;}//中间节点
if(i==N) p2->next=NULL;//尾节点
}
return (head) ;//返回链表头指针
}
void plink (struct student, *head) //输出各节点
{
struct student *p;inti;
for(i=1; i<=N; i++)
{
if(i==1)
p = head;
else
p = p->next;
printf("%d: num=%ld, score=%5.2f\n", p->num, p->score);
}
return;
}
fLoet averf(struct student *head)
{
struct student *p;
float sum= 0;
int c =0;
P = head;//p指向首节点
while(p !- NULL)
{
c++;//c用于统计节点数
sum = sum+p->score;
p=p->next;
}
return(sum/c);//返回平均值
}
花神博客生涯之C语言(结构体下篇)结束了哦~
接下来会持续更新(⊙o⊙)!