ch05 pointer
指针
指针声明 [1]
指针/指针变量 (pointer) 是用于存储地址的变量
使用 &
运算符 来访问变量的地址。例如
#include <stdio.h>
void main()
{
int a = 100;
printf("%x", &a);
}
输出结果为 16进制的内存地址
61fe1c
使用地址运算符 *
可以从变量地址中获取变量的值,这个行为被称为间接引用/解引用(indirection/dereferencing)。例如:
#include <stdio.h>
void main()
{
int a = 100;
printf("%d", *(&a));
// 也可以写为,因为*与&优先级相同,从右到左的顺序,所以有没有()意思是相同的
printf("%d", *&a);
}
输出结果为 100
指针变量
指针变量是指存储一个变量的地址的变量,可以使用符号 *
来修饰变量,定义语法为:
dataType *pointerVariableName = &variableName;
例如,下面的两个输出结果是相同的地址
#include <stdio.h>
void main()
{
int a = 100;
int *pointer;
pointer = &a;
printf("address of a is: %x\n",&a);
printf("address of pointer is: %x\n",pointer);
}
address of a is: 61fe14
address of pointer is: 61fe14
修饰符说明
修饰符 | 说明 |
---|---|
* | 两个用途: 指针变量的声明 返回被引用变量的值 |
& | 返回变量地址 |
使用const修饰指针 [3]
使用关键字 const
修饰的指针变量是不能改变指针变量所指向的地址的变量,通俗来讲即不能被改变值的指针变量
声明语句
<type of pointer> *const <name of pointer>;
int *const ptr;
const位置:const修饰的部分(const所在位置)不可改变
-
例如
const int *p;
与int const *p;
这里修饰的都是*p
故-
*p
(变量地址)不能被改变 -
p
(变量值)可以被改变 -
#include<stdio.h> #include<stdlib.h> void main() { int a = 10; int b = 20; int const *p; *p = &b; // result assignment of read-only location '*p' p = 30; // result 30 printf("%d",p); }
-
-
int * const p;
const在*后p前,这里修饰的都是p
故- p不可以被修改
- *p 可以被修改
-
const int *const p;
这里 const 修饰 * 和 p,所以两个都不可修改
Notes:通常情况下常用只有第一种情况
使用场景:最为参数形参修饰该参数为只读参数,例如 printf
int printf (const char *__format, ...)
指针的类型 [2]
C语言中包含多种指针类型:
- 空指针(Null Pointer)
- 野指针(Wild Pointer)
- 悬空指针(Dangling pointer)
- 泛型指针(void Pointer)
- 一些早期Dos中的概念
- 近指针(Near):不能存储大小大于 16 位的地址
- 远指针(Far):32 位大小的指针
- 大指针(Huge):类似于远指针。
空指针
在声明期间将 NULL 分配给指针的指针称为 Null 指针,例如
#include <stdio.h>
void main()
{
int *var = NULL;
printf("address of var is: %p\n",var);
}
泛型指针
使用 void
关键字声明指针变量,可以接受任意一种类型的变量地址,如果需要使用泛型指针,需要强转为对应类型才可以使用。如下示例:
#include <stdio.h>
void main()
{
int a = 6666;
void *p = &a;
printf("address of p is: %p\n", (int*) p);
printf("value of p is: %d\n", *(int*) p);
}
address of p is: 000000000061FE14
value of p is: 6666
野指针
野指针是指,没有有效地址的空间的指针,例如声明了指针变量没有对其赋值,这种情况下会出现 Segmentation fault
异常。
#include <stdio.h>
void main()
{
int *p;
printf("address of p is: %p\n", *p);
}
再例如一个无效的地址空间也会发生 Segmentation fault
异常;例如下列赋值中,指针p赋值被视为一个内存地址,而不是变量的值,这个地址无效。
#include <stdio.h>
void main()
{
int *p;
*p = 10000;
printf("address of p is: %p\n", *p);
}
悬空指针
悬空指针是指”已经被释放的内存“的指针变量,此时这个地址空间是无效的。如下所示
#include<stdio.h>
#include<stdlib.h>
void main()
{
int *P=(int *)malloc(sizeof(int));
int a=5;
P=&a;
free(P);
printf("After deallocating its memory *p=%d", *P);
}
指针数组
在C语言中 数组是由两部分组成,数组名与数组本身。
#include<stdio.h>
void main()
{
int a[10] = {0};
a[1] = 20;
for (int i=0; i< sizeof(a) / sizeof(a[0]); i++){
printf("%d\n",a[i]);
}
printf("arr %p = &arr %p", a, &a[0]);
}
上面例子中,变量a是一个数组,而变量a代表的是一个指向该数组第一个元素的地址,a=&a[0]
,这是一个const修饰的指针是不可以被改变的。
可以看到变量a和 &a[0]
值是相同的,而一个const修饰的指针变量是不可改变的,故a不能被赋值,下列代码是不合法的。总结为:不允许将任何地址分配给数组变量
#include<stdio.h>
void main()
{
int a[10] = {0};
int b[1] = {0};
int c = 10;
a = &b;
a = &c;
for (int i=0; i< sizeof(a) / sizeof(a[0]); i++){
printf("%d\n",a[i]);
}
printf("arr %p = &arr %p", a, &a[0]);
}
使用指针访问数组
上面知道了,数组变量指向的是数组的起始元素(第一个元素)的地址指针,那么通过指针可以对数组进行访问。
因为 a = &a[0]
那么 a[0] = *a
,由此可推导出下列公式:
&a[1] = a+1
(取数组元素的地址) 那么a[1] = *(a+1)
(取数组元素的值)&a[2] = a+2
那么a[2] = *(a+2)
这种情况下数组的访问就有四种方法
Array VS Pointer [4]
- 数组名是常量,指针是变量
- sizeof(array) 得到的是数组实际占用内存空间的字节数,sizeof(pointer) 是4/8 取决于操作系统
指针运算
左值和右值 [5]
了解对于指针运算前,需要对左值(lvalue)和右值(rvalue)进行了解
- 左值:通常来说是在占有内存地址(即具有地址)的对象
- 具有存储数据的内力,例如变量
- 不能是函数,表达式,或常量
- 综合来说,左值可以是以下几种:
- 任何类型的变量:int, float, pointer, struct等
- 数组的下标表达式,如a[1]
- 括号内的表达式(指针)
- 指针的间接引用
- 常量(不可改变的左值)
- 通过指针访问对象属性或成员 (-> or .)
- 右值:在内存中没有占有内存地址的对象
- 返回不可改变的表达式或值,例如a+b是一个常量,函数运行结果是一个右值
左值的示例
#include<stdio.h>
void main()
{
// 声明变量a为int类型
int a;
// a是一个左值,引用对象为int
a = 1;
// 左值a出现在右边的场景
int b = a;
// 非法,a是左值
9 = a;
// 左值,*p是指针,p就是值,*p+4是后面一个int类型的地址,是左值
int *p;
int a = 10;
p = &a;
// 这里实际上是指针运算,p+0为自己,p存储指针 *p为值,那么a=10000,p=10000
*(p+0) = 10000;
printf("%p\n",a);
printf("%p\n",*p);
printf("%d\n",a);
printf("%d\n",*p);
}
右值的示例
// 声明变量a b
int a = 1, b;
// 非法,a+1为常量,不是左值
a + 1 = b;
// 声明指针变量 p q
int *p, *q; // *p, *q 为左值
*p = 1; // 合法,左值可以赋值
// 非法 - "p + 2" 是右值
p + 2 = 18;
q = p + 5; // "p + 5" 是右值,合法
// 解引用表达式是左值
*(p + 2) = 18;
p = &b;
int arr[20]; // 数组元素访问arr[12] = *(arr+12) 所以是左值有效
struct S { int m; };
struct S obj; // obj and obj.m are lvalues
// ptr-> 等于 (*ptr).m 是左值有效
一元表达式需要有左值,当a是左值&a才生效,12本身是一个右值,不能&12
int a, *p; // a 和 *p都是左值
p = &a; // 合法,&a是常量为右值,p是左值
&a = p; // 缺少左值,非法
三元表达式是一个右值(C++是左值)
( x < y ? y : x) = 10; // c无效,c++有效
左值 VS 右值
- 左值为内存中可识别的对象,右值为一个常量(广义上,不是const)
- 左值可以在左边和右边,右值必须在右边
- 右值必须有左值才生效
- 指针运算可左可右,变量运算是右值
arithmetic [6]
C语言中,指针支持四种算术运算符,吧地址当作数值进行算数运算
运算符 | 说明 |
---|---|
= | 可以将值赋值给指针 |
+ | 从指针加整数值以指向不同的内存位置。 |
- | 从指针中减去整数值以指向不同的内存位置 |
比较运算(==, !=, <, >, <= , >=) | 仅比较两个指针地址,例如 pointer == NULL |
++ | 指针使用递增运算符将向前位移一位 |
– | 指针使用递减运算符将向后位移一位 |
- 当对指针变量进行递增和递减操作时,会改变指针变量本身所在地址空间
- 当对指针变量进行±运算时,不会改变指针变量本身
指针数组
指针数组是指数组存储的内容是指针,即数组内所有的元素都是指针
#include<stdio.h>
void main()
{
int a = 10;
int b = 20;
int c = 30;
int *arr[] = {&a, &b, &c};
}
指针数组本质也是一个多级指针,例如一个2D数组每行(rows) 存储的值是一列(colums)的地址
#include<stdio.h>
void main()
{
int a[] = { 10 };
int b[] = { 20 };
int c[] = { 30 };
int *arr[] = {a, b, c};
}
Pointer VS Array
- sizeof
sizeof(array)
返回数组中所有元素占用内存的大小sizeof(pointer)
只返回指针变量本身用内存的大小
- &运算符
- 数组名是
&array[0]
的别名,返回数组中第一个元素的地址 &pointer
返回指针的地址
- 数组名是
- 指针变量可以赋值,而数组变量不可以
- 数组是收集了相同类型元素的集合,而指针是一个存储地址的变量
Reference
[1] c pointer
[2] pointer type
[3] const pointer
[4] pointer VS array
[5] lvalue VS rvlaue
[6] pointer arithmetic
[7] [pointer arithmetic](