C语言基础之关键词
1、const
const修饰全局变量
此时
全局变量
只能读取,不能修改;无论是直接修改,还是指针修改。
const修饰普通局部变量
此时
局部变量
,不能直接修改;但可以通过指针修改。// const int a;a值不可以直接更改,但可以通过指针地址修改。 // 这里a地址里面的值已经变更为了2,但在不同编辑器里打印的结果,可能不同。如在xcode中打印的仍是1,这涉及到编译器优化问题。 const int a = 1; int *p = &a; *p = 2;
const修饰指针变量
1、若const修饰
指针变量的类型
,无法通过指针变量修改地址里面的值;// const int *p;不能修改直接修改 *p 的值,但可以通过 p 修改。 int a = 1; const int *p = &a; int b = 2; p = &b;// 或a = 2;此时*p的值已经变更。
2、若const修饰
指针变量
,无法修改指针变量保存的地址;// int *const p;不能直接修改 指针p 的值,但可以通过*p 修改。 int a = 1; int * const p = &a; *p = 2;此时*p的值已经变更。
3、若const修饰
指针变量
,同时也修饰指针变量的类型
,则只能初始值确定变量值,其余情况下不得修改。eg:const int * const p;
const修饰指针的三种效果
2、struct
结构体是一种数据构造类型
格式1
struct 结构体类型名 { 成员列表 };
格式2
// 该方式定义的变量为
全局变量
struct 结构体类型名 { 成员列表 } 结构体变量名1,变量名2;
格式3
//
无名结构体
由于没有结构体类型名,所以定义后是无法再定义结构体变量的,只能在定义类型的同时定义结构体变量
struct { 成员列表 } 变量名1,变量名2;
定义结构体变量
// 这种定义的变量内存分配位置,要看定义的位置
struct 结构体类型名 变量3,变量4;
定义结构体别名
// 使用:NAME_A 变量1,变量2;不需要关键字struct
typedef struct { 成员列表 } 重新定义的结构体类型名NAME_A;
注意:
typedef
主要用于给一个类型取别名,此时相当于给当前结构体重新取类型名为NAME_A,相当于struct结构体名。
结构体数组格式
struct 结构体类型名 数组名[ 元素个数 ];
结构体指针格式
// 指针变量占4个字节,用来保存结构体的地址编号
struct 结构体类型名 * 结构体指针变量名;
结构体指针的引用方式
1、(*结构体指针变量).成员
2、结构体指针变量名->成员
结构体内存分配问题
规则1:以结构体中占最大的
成员类型字节长度
为单位开辟内存
规则2:字节对齐。即存放类型变量的起始位置,必须是规则1: 最大成员类型字节数
的倍数。开辟内存的时候,从上往下依次按成员在结构体中的位置顺序开辟空间。
为什么要有字节对齐?用空间换时间,提高cpu读取数据的效率。
位域
struct 位域结构名 {
位域类型 [位域名] : 位域宽度 ;
// 位域类型:决定了如何解释位域的值。
// 位域宽度:位域中位的数量。宽度必须小于或等于指定类型的位宽度。
};
示例:struct packed_data { unsigned int a:2; unsigned int b:4; unsigned int c:6; unsigned int d;// 不设置位域,位域默认为数据类型的字节长度 }data;
注意:
1、不能对位域成员取地址。
2、赋值时,不要超出位域定义的范围。
3、位域成员的类型必须指定为整型或字符型。
4、一个位段必须存放在一个存储单元中,不能跨两个单元;第一个单元空间不能容纳下一个位段,则该空间不用,而从下一个单元起放该位段。
5、若位域成员列表所占位数在数据类型的字节长度内,通过sizeof()函数打印出的是位域成员数据类型的字节长度。
3、union
共用体:由几种不同类型的变量共同占用一段内存的结构。也称为“联合体”
描述:
1、是和结构体类似的一种构造类型的数据结构,在进行某些算法的时候,需要使用几种不同类型的变量存到同一内存单元中,这些变量所使用的内存空间相互重叠。
2、共用体所有成员占有同一段内存空间。
3、共用体的大小使是其占内存长度最大的成员的大小。特点:
1、同一内存段可以用来存放几种不同类型的成员,但每一瞬时只有一种起作用。
2、共用体变量中起作用的成员是最后一次存放的成员,在存入一个新的成员后原有的成员的值会被覆盖。
3、共用体变量的地址和他的各成员变量的地址都是同一地址。
4、共用体的格式:union 共用体类型名称 { 成员列表 };
4、enum
枚举类型也是一种构造类型。
格式:enum 枚举类型名 { 枚举值列表;};
注意:在定义枚举类型的时候枚举元素可以用等号给它赋值,用来代表元素从几开始;在程序中不能再次对枚举元素赋值,因为枚举元素是常量。
5、strlen
strlen库函数:size_t strlen(const char *s),这个函数接收一个字符串的指针,返回这个字符串的长度(
以字节为单位
);strlen返回的字符串长度是不包含字符串结尾的'\0
'的。
6、sizeof
sizeof运算符:用来返回一个类型或者是变量所占用的
内存字节数
。
7、typeof
typeof宏:使用typeof()可由
结构体成员
得到该成员的数据类型
。
8、offsetof
offsetof宏:用来获取结构体中某个元素
相对于结构体首地址
的偏移量
。
/**
* @type: 成员所嵌入的容器结构体类型
* @member: 结构体中的成员名
* 计算的结构体中某成员的偏移量
* 1. ( (TYPE *)0 ) 将零转型为TYPE类型指针;
* 2. ((TYPE *)0)->MEMBER 访问结构中的数据成员;
* 3. &( ( (TYPE *)0 )->MEMBER )取出数据成员的地址,即相对于0的偏移量,要的就这个;
* 4. (size_t)(&(((TYPE*)0)->MEMBER))结果转换类型,size_t应该最终为unsigned int类型。
此宏的巧妙之处在于将 0 转换成(TYPE*),这样结构体中成员的地址即为在此结构体中的偏移量。
*/
#define offsetof(type, member) (size_t)&(((type*)0)->member)
9、container_of
container_of宏:根据结构体变量中某个成员的指针,反推出结构体变量的指针,继而得到结构体变量中其他的成员。实现上实际就是根据当前这个成员的地址
减去 这个成员相对整个结构体的的偏移量
,就是结构体的地址,然后强制转换指定数据类型
即可。
/**
* container_of - 通过结构体的一个成员获取容器结构体的指针
* @ptr: 指向成员的指针
* @type: 成员所嵌入的容器结构体类型
* @member: 结构体中的成员名
* 根据结构体(type)某成员变量(member)的指针(ptr),来求出该结构体(type)的首指针。
* 1. typeof( ( (type *)0)->member )为取出member成员的变量类型。
* 2. 定义__mptr指针ptr为指向该成员变量的指针
* 3. mptr为member数据类型的常量指针,其指向ptr所指向的变量处
* 4. (char *)__mptr转换为字节型指针。(char *)__mptr - offsetof(type,member))用来求出结构体起始地址(为char *型指针),然后(type *)( (char *)__mptr -offsetof(type,member) )在(type *)作用下进行将字节型的结构体起始指针转换为type *型的结构体起始指针。
* 5. ({ })这个扩展返回程序块中最后一个表达式的值。
*/
#define container_of(ptr, type, member) ({ \
const typeof( ((type *)0)->member ) *__mptr = (ptr); \
(type *)( (char *)__mptr - offsetof(type,member) );})
写在最后
iOS中位域(位段)的简单使用
理解大小端字节序 - 范兵 - 博客园
字节序(大端和小端) - flxx - 博客园
字节序探析:大端与小端的比较 - 阮一峰的网络日志
iOS中位域(位段)的简单使用
Linux内核 container_of 宏和 offsetof 宏分析