C_指针

C_指针
BarbecueC_指针
指针基础
指针变量
现代计算机都将内存分割为字节,每个字节都有唯一的地址。程序中的每一个变量占一个或多个字节的内存,第一个字节的地址称为是变量的地址。
指针就是地址,而指针变量就是存储地址的变量。(有时候也把指针变量称作为指针)
当指针变量 p
存储变量 i
的地址时, p
指向了 i
。
指针变量的声明
指针变量的声明与普通变量的声明基本一样,唯一不同的就是必须在指针变量名的前面加上星号:
1 | int *p; |
注意:
指针变量名为
p
而非* p
指针变量的类型为
int*
,而非int
类型。
指针变量可以和其它变量一起出现在声明中:
1 | int i, j, a[10], b[20], *p, *q; |
&
和 *
获取变量的地址,取地址运算符 &
。访问指针变量所指向的对象,解引用运算符 *
。 p
是指针, *p
就表示 p
当前指向的对象。
取地址运算符 &
&
运算符可以获取变量的地址,并可以把变量的地址赋值给指针变量:
1 | int i, *p; |
解引用运算符 *
一旦指针变量指向了对象,就可以使用 * 运算符访问被指向的对象。
p
指向了 i
,下面的语句将显示 i
的值:
1 | printf("%d\n", *p); |
p
指向了 i
, *p
就是 i
的别名。*p
不仅拥有和 i
相同的值,而且对 *p
的改变也会改变 i
的值:
1 | i: 直接访问,读取内存一次 |
可以把 &
和 *
看作是一对逆运算。
优先级顺序:自增自减运算符 > 解引用 *
*和取地址 &
> 算术运算
警惕野指针问题
野指针:指针变量 p
未被初始化或者指向了一个未知的区域。对野指针使用解引用运算符会导致未定义的行为:
1 | int *p; |
指针赋值
- 把变量的地址赋值给指针变量:
1 | int i, j, *p, *q; |
- 通过另一个指针变量进行赋值:
1 | q = p; |
q = p
和*q = *p
的区别
q = p
是将指针的值进行复制,两个指针指向相同的内存位置。*q = *p
是将指针所指向的内存位置的值进行复制,两个指针可以指向不同的内存位置。
指针作为参数
C 语言函数调用时,是进行值传递的,在函数调用中,无法改变实参的值。
解决方法:不传递 a 和 b 作为函数的实际参数,而是传递 &a
和 &b
。形式参数声明为对应的指针类型。函数参数列表中*p
和 *q
分别为变量 a 和 b 的别名,因此可以通过 *p
和 *q
改变变量 a 和 b 的值。
1 | void swap(int* p, int* q) |
scanf
函数格式串之后的参数必须是指针,但并不是必须使用 &
运算符。
1 | int i, *p; |
指针作为返回值
返回指针的函数
1 | int* find_middle(int a[], int n) |
注意:永远不要返回指向当前栈帧区域的指针: 一旦当前函数执行完毕,其所在的栈帧会被释放。
1 | /*** 错误 ***/ |
指针与数组
指针的算术运算
指针可以指向数组的元素
1 | int a[10], *p; |
通过对指针 p
执行指针算术运算,可以访问数组 a 的其它所有元素。
C 语言支持 3 种格式的指针算术运算:
指针加上一个整数,结果是指针
指针减去一个整数,结果是指针
两个指针相减,结果是整数
指针加上整数
p
指向数组元素 a[i]
, p + j
将指向 a[i+j]
。
1 | int a[10], *p, *q; |
指针减去整数
p
指向数组元素 a[i]
, p - j
指向 a[i-j]
。
1 | int a[10], *p, *q; |
两个指针相减
当两个指针相减时,结果为指针之间的距离。
1 | p = &a[5]; |
- 对一个不指向数组元素的指针执行算术运算会导致未定义的行为。
- 只有在两个指针指向同一个数组时,相减才有意义。
指针比较
可以用关系运算符 (<
, <=
, >
和 >=
) 和判等运算符 (==
和 !=
) 对指针进行比较。
1 | p < q 等价于 p - q < 0 |
只有当两个指针指向同一数组时,比较才有意义。如:
1 | p = &a[5]; |
指针用于处理数组
通过对指针变量进行重复的自增来遍历数组中的元素。
1 |
|
尽管
a[N]
这个元素不存在,但是对它使用取地址运算符是合法的。因为&a[N]
只会计算地址,而不会尝试访问a[N]
的值。
*
与 ++
的组合
对某个元素进行赋值,然后前进到下一个元素:
1 | a[i++] = j; |
当 p
指向 a[i]
,对某个元素进行赋值,然后前进到下一个元素:
1 | *p++ = j; |
*
与 ++
的其它组合方式(常见的还是 *p++
):
优先级顺序:自增自减运算符 > 解引用 *
*和取地址 &
> 算术运算
表达式 | 含义 |
---|---|
*p++ 或 *(p++) |
返回指针当前指向的值,指针指向下一个元素 |
(*p)++ |
返回指针当前指向的值,指针指向的值自增 |
*++p 或 *(++p) |
返回指针下一个元素的值,指针指向下一个元素 |
++*p 或 ++(*p) |
返回指针当前指向的值自增后的新值,指针指向的值自增 |
数组元素求和时
1 | for (p = &a[0]; p < &a[N]; p++) |
改写成
1 | p = &a[0]; |
用数组名作为指针
数组名可以被隐式转换为指向数组第一个元素的指针:
1 | int a[10]; |
a+i
等同于 &a[i]
,*(a+i)=a[i]
。
数组名作为指针的场景:
遍历数组元素:通过递增指针来访问数组的每个元素。
以下遍历数组元素:
1
2for (p = &a[0]; p < &a[N]; p++)
sum += *p;可以改写成:
1
2
3/* idiom */
for (p = a; p < a + N; p++)
sum += *p;虽然可以把数组名当作指针,但是不能给数组名赋新的值,也就是说数组名是一个
指针常量
。1
2>while (*a != 0)
a++; /*** 错误 ***/传递数组给函数:函数参数可以接受数组类型或指向数组的指针。数组名在传递给函数时,会
退化成指针
。1
2
3
4
5
6
7
8int find_largest(int a[], int n) {
int max = a[0];
for (int i = 1; i < n; i++)
if (a[i] > max)
max = a[i];
return max;
}
largest = find_largest(b, N);在调用
find_largest
函数时,会把指向数组b
第一个元素的指针赋值给a
,数组本身并没有复制。传递数组时,只是传递一个指针,可以把数组类型的形式参数声明为指针类型。
1
int find_largest(int* a, int n);
- 对于形式参数而言,声明数组和声明指针是一样的。
- 对于变量而言,声明数组和声明指针是不同的。(声明
int a[10]
; 会导致编译器预留 10 个整数的空间。但声明int *a
; ,编译器只会为一个整数的空间。 )
形式参数为数组,可以给函数传递一个数组片段。(数组片段:数组的某个连续部分)
1
largest = find_largest(b + 5, 10);
用指针作为数组名
1 |
|
编译器会把 p[i]
看作是 *(p+i)
。
用指针作为数组名的情况:
- 动态分配内存:使用指针来管理动态分配的数组,比使用固定大小的静态数组更灵活。
- 处理多维数组:在多维数组的情况下,指针的灵活性比数组名更有用。