目录
一、位字段
位字段是C语言提供的操作位的一种便捷且代码可读性更好的方式,适用于只需少量的位表示数据的场景下。GNU C下,位字段的内存大小是unsigned int类型所占内存的整数倍,64位下unsigned int类型通常是4字节,32位,即位字段最小为4字节,编译器保证位字段的内存大小大于位字段中声明的内存大小。
#include <stdio.h>
#include <stdbool.h>
//声明位字段,数字表示该字段占的位数,未命名的字段用于填充的
//位字段在内存中以unsigned int的内存大小为基本布局单位,64位下通常是4字节,32位,
//即使位字段声明的总位数小于32位,也会占用4字节内存,如果大于32位则会自动扩充位字段的
//内存大小,但保证按照4字节对齐
struct box_props {
bool b1 : 1;
unsigned int b10 : 10;
// unsigned int : 4;
unsigned int : 24;
unsigned int b3 : 3;
unsigned int b2 : 2;
unsigned int : 2;
};
int main(void)
{
printf("unsigned int size:%d \n",sizeof(unsigned int));
printf("box_props size:%d \n",sizeof(struct box_props));
//位字段的使用跟正常的结构体一致
struct box_props a={1,345,7,3};
printf("a b1:%d\n",a.b1);
printf("a b10:%d\n",a.b10);
printf("a b3:%d\n",a.b3);
printf("a b2:%d\n",a.b2);
//对位字段赋值时,需要确保数值不能大于该字段对应位数允许的最大值,这里是1024,超过后编译器会自动截断掉高位
//1025的二进制是1000 0000 01,截掉高位1,就是000 0000 01
a.b10=1025;
printf("a b10:%d\n",a.b10);
return 0;
}
二、_Alignof和_Alignas关键字
_Alignof给出指定数据类型内存对齐的字节数,如double按8字节对齐,其内存地址都是8的整数倍。_Alignas关键字指定某个变量按照其他数据类型对齐,如char正常按1字节对齐,可指定按short类型的2字节对齐。
#include <stdio.h>
int main(void)
{
int dx;
char ca;
char cx;
//指定对齐的字节数必须大于本来的,这里必须大于4
// int _Alignas(short) dz;
int _Alignas(double) dz;
char cb;
//_Alignas要求某个变量的内存地址按指定的字节数对齐
char _Alignas(short) cz;
//_Alignof关键字给出该类型变量的内存对齐字节数,比如double按8字节对齐,即其存储地址必须是8的整数倍
printf("char alignment: %zd\n", _Alignof(char));
printf("short alignment: %zd\n", _Alignof(short));
printf("int alignment: %zd\n", _Alignof(int));
printf("double alignment: %zd\n", _Alignof(double));
printf("&dx: %p\n", &dx);
printf("&ca: %p\n", &ca);
printf("&cx: %p\n", &cx);
printf("&dz: %p\n", &dz);
printf("&cb: %p\n", &cb);
printf("&cz: %p\n", &cz);
return 0;
}
三、可变参数函数
C语言通过stdarg.h头文件提供了对可变参数函数的支持,即函数的参数的个数和类型是未知的,不过要求可变参数必须是最后一个参数,且可变参数前一个参数固定是int类型的,固定用于表示实际参数个数。
#include <stdio.h>
//必须引入这个头文件
#include <stdarg.h>
double sum(int, ...);
int main(void)
{
double s,t;
s = sum(5, 1.1, 2.5, 13.3,1,2,'c');
t = sum(6, 1.1, 2.1, 13.1, 4.1, 5.1, 6.1);
printf("return value for "
"sum(3, 1.1, 2.5, 13.3,1,2,'c'): %g\n", s);
printf("return value for "
"sum(6, 1.1, 2.1, 13.1, 4.1, 5.1, 6.1): %g\n", t);
return 0;
}
double sum(int lim,...)
{
//保存可变参数的列表
va_list ap;
double tot = 0;
int i;
//将实际参数放到列表中
va_start(ap, lim);
for (i = 0; i < lim; i++){
//返回指定类型的参数,实际参数类型不符则返回0
double a=va_arg(ap, double);
printf("va_arg i=%d,a=%f\n",i,a);
tot += a;
}
//清空参数列表
va_end(ap);
return tot;
}
四、高级数据结构
提供类型属性和相关操作的抽象描述被称为抽象数据类型(ADT),通俗的理解就是Java中的类,既有类属性也有关联的类方法,类方法通过接口的形式定义,使用方只关心接口定义不关心接口实现。
1、双向链表
C中typedef不允许重复定义,因此只能通过void指针加上指针类型强转这种方式实现泛型的效果,最终让链表能够适用于所有数据类型。
头文件即接口定义如下:
//避免头文件重复引入
#ifndef MYLIST_H_INCLUDED
#define MYLIST_H_INCLUDED
#include <stdio.h>
typedef struct myNode
{
//用void指针实现泛型效果
void * data;
struct myNode *next;
} MyNode;
typedef struct myList
{
MyNode * first;
MyNode * last;
int count; //链表总结点数
int (*equal)(void * a, void * b);
} MyList;
typedef struct myListIterator
{
MyNode * p;
int count; //当前遍历的链表节点索引
int allSize; //链表节点总数
} MyListIterator;
//创建链表
MyList * createMyList();
//创建链表,带有相等参数,用于查找
MyList * createMySearchList(int(*equal)(void * a, void * b));
//释放链表
void freeMyList(MyList * list);
//插入在尾部
void myListInsertDataAtLast(MyList* const list, void* const data);
//插入在首部
void myListInsertDataAtFirst(MyList * const list, void* const data);
//插入
void myListInsertDataAt(MyList * const list, void* const data, int index);
//删除在尾部
void* myListRemoveDataAtLast(MyList* const list);
//删除在首部
void* myListRemoveDataAtFirst(MyList * const list);
//删除
void* myListRemoveDataAt(MyList* const list, int index);
//删除对象,返回是否删除成功
int myListRemoveDataObject(MyList* const list,