C语言基础之关键词

2023-03-27  本文已影响0人  Eugene_iOS

1、const

const修饰全局变量

此时全局变量只能读取,不能修改;无论是直接修改,还是指针修改。

const修饰普通局部变量

此时局部变量,不能直接修改;但可以通过指针修改。

// const int a;a值不可以直接更改,但可以通过指针地址修改。
// 这里a地址里面的值已经变更为了2,但在不同编辑器里打印的结果,可能不同。如在xcode中打印的仍是1,这涉及到编译器优化问题。
const int a = 1;  
int *p = &a; 
*p = 2; 

参考:
const变量通过指针修改
通过const指针修改变量名

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 宏分析

上一篇下一篇

猜你喜欢

热点阅读