C语言指针
1 运算
-
加减法
指针加减1:实际是加减指针所指变量的数据类型长度int a, *p_a = &a; char b, *p_b = &b; printf("%#X, %#X\n", p_a, p_b); \\ 0XEFF9D8, 0XEFF9C3 p_a++, p_b++; printf("%#X, %#X", p_a, p_b); \\ 0XEFF9DC, 0XEFF9C4 \\ p_a 是int,+1对应加4字节;p_b是char,+1对应加1字节
在指针指向数组中的元素时有实际意义,可通过加减1指向上一个或下一个元素;其他情况下,变量间并不一定无缝存储,就无法通过加减1指向相邻变量
-
比较
地址相等意味着指向同一份数据 -
其他运算均不成立
2 指针访问数组元素
数组中元素数据类型相同且连续排列,非常适合用指针指向数组内元素并访问
int a[4], *p = a; // 数组名a可退化为指针常量,等价于&a[0],指向数组首地址
越界问题:指针的权限范围和其指向的数组在内存中的范围是一致的,越界修改可能会有未知问题出现(如Stack around the variable ‘s’ was corrupted)
两种元素访问方式:
- *(p+i) 或 *p++ 访问数组
*p++和*++p结果不同:*p++是先取p指向的值再自增;*++p是p先自增再取指向的值int a[3] = { 1,2,3 }; int i, *p = a, len = sizeof(a) / sizeof(a[0]); for (i = 0; i < len; i++) { printf("%d ", *p++); } // 输出1 2 3
- p[i] 访问数组
p[i] 和 a[i] 等价int a[] = { 1,2,3 }, *p = a; printf("%d, %d", p[0], a[0]); // 1, 1
数组名和指向数组首地址的指针都可以用以上两种方法访问数组,但数组名为常量,只能指向数组首地址,无法指向其他元素
3 指针类型
3.1 字符指针:指向字符串首字符的指针
用字符指针间接储存字符串
char *str = "hello";
字符指针储存的字符串不可变!
char *str = "hello";
str = "ok"; // 不报错:指向新的字符串首地址
str[0] = 'c'; // 报错:修改字符串
-
输出字符串
指向字符串中某字符的字符指针可表示从此字符开始到\0(结束标志)的字符串char *str = "hello"; printf("%s, %s", str, str+1); \\ hello, ello
-
字符访问方式:使用*(str+n)或str[n]
规则和 2 指针访问数组元素 相同:printf("%c", *(str+1), str[1]); \\ e, e
char * const str1 = "hello";
printf("%s, %c", str1, *str1); // hello, h
char *str2 = str1; //指针str1本身为常量,本来无法升权限赋值给变量str1,但是字符指针在给其他字符指针赋值时,表示的是字符串,所以这条语句不报错
3.2 数组指针:指向一维数组的指针(而非指向数组首元素)
datatype (*p)[len] // p指向一个数组,数组的元素类型为datatype,长度为len,p的值为数组首地址
二维数组名可退化为数组指针常量:
二维数组名退化
int a[2][3] = { 1,2,3,4,5,6 }, (*p)[3] = a;
// p+1指向下一行数组,*p+1指向下一列元素
3.3 结构体指针:指向结构体变量的指针
struct 结构体名 *p = value;
struct stu{
int age;
float score;
char name[10];
} stu1 = {18, 90,"Tom"}, *p = &stu1;
获取结构体成员:
// 方法一
(*p).memberName // *p的括号不能少,因为 , 的优先级高于()
// 方法二
p -> memberName // -> 在C语言中唯一用途
结构体指针作为函数参数比copy整结构体高效
3.4 函数指针:指向函数的指针
一个函数占用一段连续内存空间,定义函数指针p指向内存首地址,即可通过*p或p调用函数
returntype(*p)(param list) = value
returntype:函数返回值类型;param list:参数列表(可省略参数名,只给类型);第一个()不能省略,因为()优先级高于*;用函数作为value给函数指针赋值,函数会退化为相应类型的函数指针
int max(int a, int b) {
return a > b ? a : b;
}
int main() {
int(*p)(int, int) = max;
printf("%d", (*p)(1, 2)); \\ 使用*p调用函数时注意加括号!
}
3.5 二级指针:指向指针的指针
datatype **p = value;
指针变量也会占用存储空间,可以用&获取地址赋给二级指针
int a = 100, *p1 = &a, **p2 = &p1;
printf("%d", **p2); // 100
C语言不限制指针级数,增一级就在定义时多一个*,但超过二级的指针很少使用
3.6 空指针和void指针
空指针不指向任何对象,值为NULL,NULL是纯粹的0,地址0不运行程序访问,空指针作为函数调用失败的返回值;
void指针指向某个对象,但该对象不属于任何类型,可以和其他类型指针自动或强制转换
4 指针数组:指针作为元素的数组
datatype *p[len] \\ p为数组,包含len个类型为 datatype * 的指针元素
int a = 1, b = 2, c = 3;
int *p[3] = {&a, &b, &c};
printf("%d", *p[0]); \\ 1
5 指针变量作为函数参数
参数传递的本质是内存的copy!
两种情况下使用指针变量作为函数参数
- 需要通过形参在函数内部的改变实时修改函数外部对应变量的值
// main{} 内a, b换值 void swap(int *p_a, int *p_b){ int temp; temp = *a; *a = *b; *b = temp; }
- 传递数组、字符串等复合数据类型
数组形参退化为指针
copy复合数据类型内所有值的效率低,所以C语言选择copy指针
6 指针作为函数返回值-指针函数
注意:函数在运行结束后会销毁内部定义的所有局部数据,所以返回的指针尽量不要指向这些数据,不一定能获取到!
// 函数返回的指针指向函数内部定义的n,但在main()中指针正常获取了n值
int *func() {
int n = 100;
return &n;
}
int main() {
int *p = func(), n;
n = *p;
printf("%d", n); // 100
return 0;
}
“销毁”局部数据实际并没有清空它所占内存空间,而是程序清除了局部数据对这块内存的所有权,之后的代码可以随意使用这块内存。在func()运行后立刻使用指针,其实是可以获取n值的,但如果有其它函数被调用就会覆盖这块内存,得到的数据就失去了意义
// 指针无法正常获取n值
int *func() {
int n = 100;
return &n;
}
int main() {
int *p = func(), n;
printf("i love coding\n"); // 导致p指向的内存被占用
n = *p;
printf("%d", n); // 14
return 0;
}
7 数组名可 自动 退化成指针常量,但不等价
所谓退化即抛弃数组长度信息
一维数组名退化为指向数组首元素的指针:
int a[3] = {1, 2, 3};
printf("%d", *a); \\ 1
int a[2][3] = { 1,2,3,4,5,6 };
a的值是二维数组首行首元素的地址;*a是整个第一行数据(一维数组),(*a)[0]是首行首元素;在表达式中*a会退化成指针,指向首行首元素,类型为int *,所以**a也为首行首元素
printf("%d, %d, %d, %d, %d, %d\n", a, &a[0][0], sizeof(*a), (*a)[0], *a, (**a)); //输出15465044, 15465044, 12, 1, 15465044, 1
\\ *a[1]和(*a)[1]不同,前者实际为*(a[1])是第二行退化为指向首元素的指针后获取值,即a[1][0],后者是a[0][1]
-
换行:a+1指向二维数组第二行
printf("%d, %d, %d", a + 1, &a[1][0], sizeof(*(a + 1))); // 15465056, 15465056, 12
-
换列:*a+1指向第一行第二个元素,*(*a+1) 和 (*a)[1] 都表示第一行第二个元素
printf("%d, %d, %d, %d\n", *a + 1, &a[0][1], *(*a + 1), (*a)[1]); // 15465048, 15465048, 2, 2
退化规则:
- 对数组下标的引用总是可以写成“一个指向数组起始地址的指针加上偏移量”
str[i]等价于*(str+i)
- 函数形参声明中,数组退化为指针(包括字符数组)
C语言中只会以值拷贝的方式传递参数,copy整个数组效率低下,所以数组名自动退化为指针常量,从而copy指针
一维数组 形参 退化为指针:void fun(int a[4]) {}
二维数组 形参 退化数组指针:void fun(int *a) {}
void fun(int a[4][5]) {}
void fun(int (*a)[5]) {}
三种情况下不会退化:
-
使用sizeof()函数时,数组名代表数组
int a[3] = { 1, 2, 3 }, *p = a; printf("%d, %d", sizeof(a), sizeof(p)); \\ 12, 4
-
对数组名取址操作时,数组名代表数组
int a[3] = { 1, 2, 3 }; printf("%d, %d", &a, &a[0]); \\ 输出18085708, 18085708:&a 是对数组取址,获得数组首地址,而不是对指向数组数组首元素的指针取址
-
使用字符串初始化数组时,数组名代表字符串
char a[] = "abcde"; printf("%d, %s", sizeof(a), a); // 6, abcde