提示:这是今天的学习笔记,主要学习共用体,枚举,位域,位操作等。
目录
一 内存池
二 typedef关键字
四 枚举类型
五 位域
六 位操作
七 位移和位操作的应用
我们平时通过malloc函数和free函数分配和释放内存,但是频繁的调用他们会产生内存碎片,并且由于调用malloc函数向操作系统申请堆内存应用程序会经历应用层到内核层的切入,分陪完成后还要在切回应用层来完成整个堆空间的申请过程。这也就会产出时间上的消耗,为了解决效率问题和减少碎片的产生我们可以为程序创建一个内存池,就是让程序额外维护的缓存区域。
内存池原理:简单的原理就是把内存碎片放入内存池中,使用的时候在从内存池里经行调用。释放的内存空间可以放入内存池中,下次使用的时候在调用空间。如果内存池中空间不够可以在用malloc函数在进行调用内存。内存池使用一个单链表来维护他,只需要将没有用的内存空间依次用一个单链表记录下来,当再次使用的时候从单链表中获取即可。例如:内存池可以用预处理宏来建立一个空间,在进行使用。
typedef是C语言重要的关键字
在变成使用中typedef目的一般有两个:一个是给变量起一个容易记住且有意义明确名的别名;另一个是简化一些比较复杂的內型声明。
typedef可以对已有的数据类型指定新的类型名。用typedef只是对已经存在的类型指定一个新的类型名,而没有创造新的类型。
1.对基本类型指定新类型名:
a. 对基本类型指定新类型名
typedef int Integer;
typedef float Real;
则可以书写如下代码:
int i,j;
float a, b;
//可以这样定义
Integer i, j;
Real a, b;
这样可以使熟悉FORTRAN语言的人能用Integer和Real定义变量,以适应他们的习惯。
b. 又如在一个程序中,用一个整型变量来计数,则可以命名Count为新的类型名,代表int类型:
typedef int Count;
Count i,j;
可以使人更一目了然地知道它们是用于计数的。
#include
typedef int INTEGER;//也可写typedef int INTEGER,*PTRINT;
typedef int *PTRINT;
int main(void)
{
INTEGER a=520;
PTRINT b,c;
b=&a;
c=b;
printf("addr of a=%p\n",*c);
return 0;
}
typedf关键字和宏的区别:相比宏定义的直接替换,tydpedf是对类型的封装。
#include
typedef int INTEGER;
#define PTRINT int*
int main(void)
{
INTEGER a=520;
int *b,c;//事实上为 int *b,c;输出地址不一样有警告
b=&a;
c=b;
printf("addr of a=%p\n",c);
return 0;
}
和结构体一起使用:
#include
#include
struct Dete
{
int year;
int month;
int day;
};
int main(void)
{
struct Date *date;
date=(struct Date*)malloc(sizeof(struct Date));
if(date==NULL)
{
printf("内存分配失败!\n");
exit(1);
}
date->year=2017;
date->month=5;
date->day=15;
printf("%d-%d-%d",date->year,date->month,date->day);
return 0;
}
#include
#include
typedef struct Dete
{
int year;
int month;
int day;
}DATE;//同时定义指针}DATE,*PDATE;
int main(void)
{
struct Date *date;
date=(DATE *)malloc(sizeof(DATE);//date=(PDATE)malloc(sizeof(DATE);
if(date==NULL)
{
printf("内存分配失败!\n");
exit(1);
}
date->year=2017;
date->month=5;
date->day=15;
printf("%d-%d-%d",date->year,date->month,date->day);
return 0;
}
2.对复杂数据类型名指定简单的类型名
a. 命名一个新的类型名代表结构体类型:
typedef struct
{
int month;
int day;
int year;
} Date;
以上声明了一个新类型名Date,代表上面的一个结构体类型。然后可以用新的类型名Date去定义变量,如
Date birthday;
Date * p;
b. 命名一个新的类型名代表数组类型
typedef int Num[100]; //声明 Num 为整型数组类型名
Num a; //定义 a 为整型数组名, 它有 100 个元素
c. 命名一个新的类型名代表指针类型
typedef char* String; //声明 String 为字符指针类型
String p, s[10]; //定义p为字符指针变量,s为字符指针数组
d. 命名一个新的类型名代表指向函数的指针类型
typedef int (*Pointer)(); //声明 Pointer为指向函数的指针类型, 该函数返回整型值
Pointer p1,p2; //pl,p2 为 Pointer类型的指针变量
习惯上,常把用typedef声明的类型名的第1个字母用大写表示,以便与系统提供的标准类型标识符相区别。
举例说明:
例一:
int (*ptr)[3];数组指针用括号把*括起来所以说先是一个指针,在是一个数组,是一个指针。
别名:typedef int (*PRT_TO_ARRAY)[3];PRT_TO_ARRAY指向数组的意思。
#include
typedef int (*PTR_TO_ARRAY)[3];
int main(void)
{
int array[3]={1,2,3};
PTR_TO_ARRAY ptr_to_array=&array;
int i;
for(i=0;i<3;i++)
{
printf("%d",(*ptr_to_array)[i]);
}
return 0;
}
123
例二:
int (*fun)(void);fun后面跟的是一个函数(小括号是函数,方括号是数组),是一个函数指针。指向一个参数为void,返回值为int的函数。
别名:typedef int (*PTR_TO_FUN)(void);指向函数的指针
#include
typedef int (*PTR_TO_FUN)(void);
int fun(void)
{
return 520;
}
int main(void)
{
PTR_TO_FUN prt_to_fun=&fun;
printf("%d\n",(*prt_to_fun)());
return 0;
}
520
例三:
int *(*array[3])(int);因为没有小括号括这方括号的优先级比*高,说以先是一个数组然后是一个指针,(*array[3])也就是一个指针数组,指针数组也就是说数组中每个元素都是指针的意思。把(*array[3])=A,式子就是int *A (int);成了一个指针函数(y因为小括号的优先级比*高,所以先是一个函数,在是一个指针),返回值是一个指向整型变量的指针,参量也是一个整型变量。
别名:typeder int*(*PRT_TO_FUN)(int); PRT_TO_FUN array[3];
#include
typedef int *(*PTR_TO_FUN)(int);
int *funA(int num)
{
printf("%d\n",num);
return #
}
int *funB(int num)
{
printf("%d\n",num);
return #
}
int *funC(int num)
{
printf("%d\n",num);
return #
}
int main(void)
{
PTR_TO_FUN array[3] ={&funA,&funB,&funC};
int i;
for(i=0;i<3;i++)
{
printf("addr of num:%p\n",(*array[i])(i));
}
return 0;
}
0
addr of num:0000000000000000
1
addr of num:0000000000000000
2
addr of num:0000000000000000
例四:
void(*fun(int,void(*funB)(int)))(int);从左向右因为*的优先级比小括号的低,所以说是一个函数再是一个指针,就是指针函数fun:void(*funA(参数)(int);。fun里有两个参数一个整型变量,另一个void(*funB)(int)从左到右先是一个指针,再是一个函数所以说是一个函数指针。void(*funA(参数)(int);和第二个参数void(*funB)(int))一样,利用相同点,所以可以写成如下所示:
typeder void(*PRT_TO_FUN)(void); PRT_TO_FUN funA(int,PRT_TO_FUN);
四 共用体
共用体类型:用同一段内存单元存放不同类型的变量。共用体占用的内存能够存储成员列表里最大的内存尺寸,单不一定是等于那个尺寸。
同一个内存段可以用来存放几种不同类型的成员,但在每一瞬时只能存
放其中一个成员,而不是同时存放几个。
union data
{
int i;
char ch;
float f;
};
union data a;
a.i = 97;
printf("%d", a.i); //输出97
printf("%c", a.ch); //输出‘a’
printf("%f", a.f); //输出0.000000
1.声明共用体类型
union 共用体名
{
成员表列
} 变量表列;
2.定义共用体类型变量
1. 声明的同时定义变量
union Data
{
int i;
char ch;
float f;
}a, b, c;
2. 先声明,再定义变量
union Data
{
int i;
char ch;
float f;
};
union Data a, b, c;
3. 直接定义共用体变量
union Data
{
int i;
char ch;
float f;
};
union Data a, b, c;
3.初始化共用体变量
a. 可以对共用体变量初始化,但初始化表中只能有一个常量。
union Data
{
int i;
char ch;
float f;
};
union Data a = { 1,'a',1.5 }; //不能初始化 3 个成员, 它们占用同一段存储单元
union Data a = { 16 }; //正确, 对第 1 个成员初始化
union Data a = {ch= 'j' }; //C99允许对指定的一个成员初始化
b. 共用体变量中起作用的成员是最后一次被赋值的成员。
a.ch = 'a';
a.f = 1.5;
a.i = 40;
完成以上3个赋值运算以后,变量中存放的是最后存入的40,原来的‘a’和1.5都被覆盖了。
c. 共用体变量可以互相赋值。如下:
union Data
{
int i;
char ch;
float f;
};
union Data a, b;
b=a;
4.使用共用体变量
a.只能引用共用体变量中的成员。例如
a.i;
a.ch;
a.f;
b.不能只引用共用体变量,例如右面的引用是错误的。printf("%d", a);
c.因为a的存储区可以按不同的类型存放数据,有不同的长度,仅写共用体变量名a,系统无法知道究竟应输出哪一个成员的值。正确引用方式如下:
printf("%d", a.i);
printf("%c", a.ch);
printf("%f", a.f);
1.枚举类型:如果一个变量只有几种可能的值,则可以定义为枚举(enumeration)类型 。
2.声明枚举类型:
a.声明枚举类型的一般形式为
enum [枚举名]{枚举元素列表};
举例:enum Weekday { sun, mon, tue, wed, thu, fri, sat };
b.花括号中的sun,mon,…,sat称为枚举元素或枚举常量。
3.定义枚举类型变量:例如:enum Weekday workday, weekend;
4.初始化及引用枚举类型变量:枚举变量和其他数值型量不同,它们的值只限于花括号中指定的值之一。例如枚举变量workday和weekend的值只能是sun到sat之一。
5.枚举类型应用说明
a.C编译对枚举类型的枚举元素按常量处理,故称枚举常量。不要因为它们是标识符(有名字)而把它们看作变量,不能对它们赋值。例如:
sun = 0;
mon = 1;
以上代码有误,不能对枚举元素赋值。
b. 每一个枚举元素都代表一个整数(int类型),C语言编译按定义时的顺序默认它们的值为0,1,2,3,4,5…。在上面的声明中,sun的值为0,mon的值为l,…sat的值为6。如果有赋值语句:
workday = mon;
相当于
workday = 1;
c. 也可以人为地指定枚举元素的数值,在声明枚举类型时显式地指定,例如:
enum Weekday{ sun = 7,mon = 1,tue,wed,thu,fri,sat }workday,week_end;
指定枚举常量sun的值为7,mon为1,以后顺序加1,sat为6。
d. 枚举元素可以用来作判断比较。例如:
if (workday == mon)…
if (workday〉sun)…
枚举元素的比较规则是按其在初始化时指定的整数来进行比较的。如果定义时未人为指定,则按上面的默认规则处理,即第1个枚举元素的值为0,故mon>Sun,sat>fri。
我们用C语言除了计算机编程外,还有单片机开发领域。
单片机( Microcontrollers )是一种集成电路芯片,是采用超大规模集成电路技术把具有数据处理能力的中央处理器 CPU 、随机存储器 RAM 、只读存储器 ROM 、多种 I /0口和中断条统、定时器/计数器等功能(可能还包括显示驱动电路、脉宽调制电路、模拟多路转换器、 A / D 转换器等电路)集成到一块硅片上构成的一个小而完善的微型计算机系统,在工业控制领域广泛应用。但是单片机内存非常小所以C语言应用了位域或位段,为字段。
位域:使用位域的做法是在结构体定义时,在结构体成员后面使用冒号(:)和数字来表示该成员所占的位数。
#include
int main()
{
struct Test
{
unsigned int a:1;
unsigned int b:1;
unsigned int c:2;
};
struct Test test;
test.a=0;
test.b=1;
test.c=2;
printf("a=%d,b=%d,c=%d",test.a,test.b,test.c);
printf("size of test=%d\n",sizeof(test));
return 0;
}
a=0,b=1,c=2size of test=4
上程序中十进制的二需要用两个位域来存放,位域的宽度不能超过它说依附的数据类型的长度,
无名位域:位域成员可以没有名称,只要给出数据类型和位宽即可 。
struct Test
{
unsigned int x :100;
unsigned int y :200;
unsigned int z :300;
unsigned int :424;//无名位域
};
逻辑位运算符:逻辑运算符和逻辑位运算符不同,逻辑运算符只是对整个操作数起作用,逻辑位运算符只作用与整型数据的,并且对数据中每一个单独的二进制位经行运算,C语言提供的逻辑位运算符有四个如下所示:
按位取反~:
按位与&:
按位异或^:
按位|:
和赋值号结合:这四个运算符,除了按位取反只有一个操作数之外,其它三个都可以跟赋值号(=)结合到一块,使得代码更加简洁!
另外注明:C 语言并没有规定一个字节的尺寸“可寻址的数据存储单位,其尺寸必须可以容纳运行环境的基本字符集的任何成员”,C语言没有规定一个字节就是八位,而是编译器默认的是八位。
C语言处理提供地位逻辑运算符之外,还提供了可以将某个变量中所有的二进制位经行左移或者右移的运算符——移位运算符
左移运算符:<<
例如:11001010左移两位补零就是00101000
右移运算符:>>
例如:11001010右移两位补零就是00110010
和赋值号结合:左移右移也可以和赋值号结合。
#include
int main(void)
{
int value=1;
while(value<1024)
{
value<<=1;
printf("value=%d\n",value);
}
printf("\n-----------------");
value=1024;
while(value>0)
{
value>>=2;
printf("value=%d\n",value);
}
return 0;
}
value=2
value=4
value=8
value=16
value=32
value=64
value=128
value=256
value=512
value=1024
-----------------value=256
value=64
value=16
value=4
value=1
value=0
将一个无符号整数按位左移n位表示乘以2的n次幂,右移n位表示除以以2的n次幂。也就是十进制和二进制的转化。
一些未定义行为:左移、右移运算符右边的操作数如果是为负数,或者右边的操作数大于左边操作数支持的最大宽度,那么表达式的结果均是属于“未定义行为”。左边的操作数是有符号还是无符号数其实也对移位运算符有着不同的影响。无站号数肯定没问题,因为这时候变量里边所有的位都用于表示该数值的大小。但如果是有符号数,那就要区别对待了,因为有符号数的左边第一位是符号位,所以如果恰好这个操作数是个负数,那么移动之后是否覆盖符号位的决定权还是落到了编译器上 。
应用:
掩码:在计算机学科和数字逻辑中指的是一串二进制数字,通过与目标数字按位操作来达到屏蔽指定为的需求操作的。
打开位:有时候需要确保某个特定为的值必须是打开状态,其他的位不变可以使用按位或。
关闭位:将掩码取反并于目标值经行按位与的操作。
转置位:将二进制位取反。