C语言之指针
1.指针
内容RAM支持随机寻址,对内存空间的访问通过地址进行
- 变量名的本质就是地址的别名,编译器根据符号表进行绑定解析。
- 指针的初衷用途就是通过间接访问的方式支持内存空间的匿名访问
指针的类型与所指向的数据类型相关联,但本质是储存地址,其大小与系统有关,与类型无关
- 指针类型可以支持编译器类型检查
- 指针运算与类型相关联
声明
-
指针的类型
- 函数指针:
void (*f)(int, int);
- 对象指针:
char*
,int*
- 通用指针:
void*
, 一般作为函数的参数使用,转为其他类型需要强制类型转换,不不会丢失数据
- 函数指针:
-
指针相关的运算符,优先级依次递增
- 类型声明:
int *
- 取址:
&
- 间接访问:
*
- 自增自减:
--
,++
- 成员选择:
->
- 其他:
()
,[]
- 类型声明:
-
易混淆的指针表达式,基于运算符的优先级与结合性
*p++; // 间接访问(*p),然后自增p++ &p++; // 指针的地址自增(&p)++ &a.name; // &(a.name) int *a[]; // 指针数组,元素类型 int* int (*a)[]; // 数组指针,指向数组 int [] int *f(); // 指针函数,返回值 int* int (*f)(); // 函数指针,指向函数 int f(); int *(*f)[];// 数组指针,指向 int * a[], 元素类型 int*
运算
所有指针运算都会自动考虑所指向对象的长度
- 有效的初始化:NULL或者表示地址的表达式
- 有效的运算
- 相同类型指针之间的赋值
- 指针与整数之间的加减法
- 数组或链表中相同类型的两个指针之间的减法和比较
- 允许赋值但不可访问最后一个元素的下一个元素的位置
- 减法的单位是数据类型的长度而不是字节
- 将指针赋值为0或者与0进行比较
2.指针与函数
指针作为函数参数
- C语言基于传值方式将参数传递给函数,将指针作为函数参数可以实现在函数内修改实参
指针函数
- 函数的返回值类型是指针
函数指针
-
指向函数的指针
int func(void); // 声明函数 int (*fp)(void); // 声明函数指针 fp = func; // 等价于 f = &func; fp(); // 调用函数,等价于 (*f)();
-
类似于数组名,函数名就是指向函数的指针常量,即函数的入口地址。
-
主要用途
- 回调函数
- 转换表
3.指针与数组
-
数组下标操作
E1[E2]
等价于指针操作*(E1+E2)
- 一般后者效率稍好,但前者更具可读性
- 具体可参考 《C和指针》8.1.3和8.1.4中关于二者的效率对比
-
数组名与数组指针:
-
数组名表示数组首个元素的地址
int a[5]; // 声明数组,分配5个整型空间 int *p; // 声明指针变量,并未初始化 // 情景1 赋值给字符指针 p = a; // 等价于 p = &a[0]; // 情景2 将数组名作为参数,实际传递的是指针 int f(int *a); // 等价于 int f(int a[]); 但更推荐指针类型的写法
-
数组名表示整个数组
// 情景1 声明一个数组 int a[3] = {1, 2, 3}; // 情景2 使用sizeof计算整个数组的大小 #define NKEYS (sizeof(a) / sizeof(a[0])) // 情景2 取址运算 int (*pa)[3]; pa = &a; // 将数组的地址赋值给数组指针
-
-
注意指针的合法运算
指针与字符串
字符指针
字符串本质是一个字符数组常量,一般通过字符指针进行访问。
// 只是字符指针的赋值,不涉及字符串的复制
char p1 = "hello";
char p2 = p1;
4.指针与结构体
-
将指向结构体的指针传递给函数优化效率,使用
->
间接访问成员 -
利用指针实现引用声明
// 自引用结构 struct tnode { struct *tnode left; struct *tnode right; } // 所有指针大小一致,编译期间即可确定长度 // 互引用结构 struct B; // imcomplete declaration struct A { struct B *ref; } struct B { struct A *ref; }
-
结构体地址与其第一个成员的地址相同,但是类型不同
类似于数组与其首元素
typedef struct A { int a; } T_A, *PT_A; T_A ta = {100}; PT_A pa = &ta; // pa是指向结构体的指针,*pa表示结构体ta int *p = &pa->a; // p是指向整型的指针,*p表示ta.a // pa和p的值相等,因为ta和ta.a的地址相同,但是二者的类型并不相同
-
经典应用:链表
5.二级指针
-
修改指针变量的值
-
指针数组传参
// 指针数组和二维指针作为函数参数是等价的 int main(int argc, char *argv[]); int main(int argc, char **argv);
-
操作二维数组
void array_print(int a[][5]); void array_print(int (*a)[5]);
6.辨析
指针与常量
int const *p = &a; // 指向常量的指针,const修饰(*p), 改向不改值
int * const p = &a; // 指针类型的常量,const修饰p, 改值不改向
字符指针与字符数组
char a[] = "hello"; // 字符数组,字符元素可以修改,数组位置不可变
// 等价于 char a[] = {'h', 'e', 'l', 'l', 'o', '\0'};
char *p = "world"; // 字符指针,指向字符串常量,字符不可修改,指向的位置可以改变
数组名
int a[] = "hello";
printf("%p\n", a+1); // 首元素的下一个元素的地址
printf("%p\n", &a+1); // 下一个数组的地址 // TODO 等于a[5]的地址吗?
指针数组与数组指针
// 指针数组用于传递命令行参数,数组保存不等长字符串
// 数组指针用于参数传递二维数组,更方面体现其行数组等长的结构
char a[2][3] = {"abc", "def"};
char (*p)[3] = a;
char **pp = &a[0][0];
int f1(char **p1); // pointer to pointer to char
int f2(char *p2[]); // array of pointer to char
int f3(char (*p3)[3]); // pointer to array[3] of char
int f4(char p4[][3]); // array of array[3] of char
指针数组与二维数组
int a[5][10]; // 二维数组,分配50个int类型长度空间
int *p[10]; // 指针数组,仅分配了10个指针,且没有初始化,但是指针所指向的长度无需相同
// 后续都可以通过 a[i][j]的形式访问,本质上还是指针运算
int (*p1)[10] = a; // 数组指针,指向int array[10]的数组 a[0]
// 数组指针作为参数传递二维数组需要显式说明长度 int f(int (*p)[10]);
7.参考
- 《C程序设计语言》-2th
- 5.指针和数组
- 6.4 指向结构的指针
- 6.2 结构与函数
- 6.3 结构数组
- 6.5 自引用结构
- 《C和指针》
- 6.指针
- 8.数组
- 9.字符串、字符和字节
- 10.结构体和联合
- 13.高级指针话题
- 《嵌入式C语言的自我修养》
- 7.8从变量到指针
- 7.9指针与数组的暧昧关系
- 7.10指针与结构体
- 7.11二级指针
- 7.12函数指针