一、指针
1、指针概念:
地址:内存中每个字节单位都有一个编号(门牌号)
指针:指针就是地址
指针变量:用于存放地址的变量就叫做指针变量
2、格式
存储类型 数据类型 *指针变量名
int *p //定义了一个指针变量名
例子:
int a_p = 10;
int *p = &a_p;
char b_p = 'h';
char *q = &b_p;
3、指针操作符
* : 取内容,取地址里面存放的内容
& : 取地址符,取变量的地址
*&a == a :* &互为互逆运算
&*a a是变量 错误使用
a是指针 则可取到内容的地址
4、初始化
指针变量在使用前不仅要定义还要初始化,未初始化的指针变量不能随便使用,会产生野指针
1、将普通变量地址赋值给指针变量
int p_p = 5;
(1) int *p = &p_p;
(2) int *P = NULL;
p = &p_p;
printf("%d %d\n",p_p,*p);// 5 5
printf("%p %p",&p_p,p);//打印的都是p_p这个变量的地址
2、将数组的首地址赋值给指针变量
char str[10] = "hello";
(1) char *p = str;//指针指向的数组的首地址,即指向了字符'h'
3、将指针变量里面保存的地址赋值给另一个指针变量
int num = 2;
int *p = #
int *q = p;
5、指针运算
5.1算数运算:
char str[32] = "world";
char *p = str;
p++;// 指针向高地址方向移动一个数据单位,指针指向发生变化
p--; // 指针向低地址方向移动一个数据单位,指针指向发生变化
int *p; p++;//移动了四个字节
double *p; p++;//移动了八个字节
p+n: 访问高地址方向第n个数据的地址,指针指向不发生变化
p-n: 访问低地址方向第n个数据的地址,指针指向不发生变化
问:p 到 p+4 之间相隔了多少个地址// n: 数据的个数,体现不出来字节的大小
偏移了多少地址:n * sizeof(数据类型)
俩个地址之间的差 = 两个地址之间相隔元素的个数
int num = 100;
double sum = 200;
int *p1 = NULL, *p2 = NULL;
double *q1 = NULL, *q2 = NULL;
p1 = #
p2 = p1 + 3;
q1 = ∑
q2 = q1 + 4;
printf("p1 = %p, p2 = %p\n", p1, p2);
printf("p2-p1 = %d\n", p2 - p1);
printf("q1 = %p, q2 = %p\n", q1, q2);
printf("q2-q1 = %d\n", q2 - q1);
// 验证了两指针相减的结果值不是地址量而是一个整数值
5.2关系运算
> < == !=
指针之间的关系运算比较的是它指向地址的高低
指向高地址的指针大于指向低地址的指针
char str[10]="hello"
char *p = &str[1];
cahr *q = &str[2];
q > p; 为真
注意:指向不同类型的数组指针关系运算没有意义,指向不同区域的指针关系运算也没有意义(同一个数组进行比较)
- 指针的大小
32位操作系统 指针4字节
64位操作系统 指针8字节
7、指针修饰
1. const常量化
1) const int num = 10;
int const num = 10;
num = 20; // 错误的,进行了const修饰,因为num不能改变
//这样可以,因为const修饰num,并没有修饰*p
int *p=#
*p=20;//可以改变
2) const int *p; // 修饰 *p, 指针指向的内容不能修改,指针的指向可以修改的
int const *p;//修饰的也是*p
int a = 10;
const int *p = &a;
1.1) *p = 20; // 错误,因为 *p 被修饰
1.2) int b = 20; p = &b // 可以
3)int *const p//修饰的是p,可以修改内容 不能修改指向
int a = 10;
int b = 20;
int *const p = &a;
2.1) *p = 20; // 可以
2.2) p = &b; // 不可以, p 被 const 修饰,所以不能修改
2.void
void a;//不允许修饰变量
void *p;//p是一个任意类型的指针
使用场景:函数参数或函数返回值
注意:通过void类型指针进行取内容时,需要对地址进行强转,在哪里使用
在哪里强制转换
8、二级指针
一级指针:存放变量的地址
二级指针:存放一级指针的地址
格式:存储类型 数据类型 **指针变量名
int a=10;
int *p = &a;
int **q = &p;
访问a的值:
a *p *(*q)
二、指针与数组
直接访问:按变量的地址存取变量的值(通过数组名访问)
间接访问:通过存放变量地址的变量去访问变量(通过指针访问)
- 指针与一维数组
int a[5]={1,2,3,4,5};
int *p=a;//a为数组首地址
直接访问:通过数组名访问
间接访问:通过指针来访问
地址元素对应
地址 | 内容 | 元素 | |||||
p | a | &a[0] | 3 | a[0] | *a | p[0] | *p |
p+1 | a+1 | &a[1] | 4 | a[1] | *a+1 | p[1] | *p+1 |
p+2 | a+2 | &a[2] | 5 | a[2] | *a+2 | p[2] | *p+2 |
p+3 | a+3 | &a[3] | 6 | a[3] | *a+3 | p[3] | *p+3 |
p+4 | a+4 | &a[4] | 7 | a[4] | *a+4 | p[4] | *p+4 |
a和p本质上不同,a是地址常量,p是变量,a不能执行++操作,但是p可以
访问数组元素a[i]的值:
直接访问:a[i] *(a+i)
间接访问:p[i] *(p+i)
访问数组元素a[i]的地址:
直接访问:&a[i] a+i
间接访问:&p[i] p+i
例子
int a[3]={1, 2, 3};
int *p = a;
printf("%d\n", *p++); // 1 再打印一次就是 2
printf("%d\n", *a++); // 错误,a是地址常量
运算方法:
1)++和*都是单目运算符,优先级相同
2)单目运算符从右向左运算
*(p++); // 1
(*p)++; // 打印出来1,实际上第一个数变成2;
++*p; // 打印出来是2,自加完之后的值
++(*p)// 同上
*++p; // 先移动再取值,2
*(++p)// 同上
- 指针和二维数组
直接访问
int a[2][3] = {
{1, 2, 3},
{4, 5, 6}
}; // a:第一行的首地址,a+1:第二行的首地址
在 a 前面加 *,表示将行地址降级成为列地址
*a;表示把行地址降级为列地址。
*a:第一行第一个地址
*a+1:第一行第二例地址
*(a+1):第二行第一列地址
*(a+1)+1:第二行第二列地址
访问数组元素地址(a[i][j]的地址)
printf("%p\n",&a[i][j]);
printf("%p\n",*(a+i)+j);
printf("%p\n",a[i]+j);
访问数组元素值:
printf("%d\n", a[i][j]);
printf("%d\n", *(*(a+i)+j));
printf("%d\n", *(a[i]+j));
直接访问元素对应
地址 | 数组 | 元素 | ||||
a[0] | &a[0][0] | *a | 1 | a[0][0] | *(*a) | *a[0] |
a[0]+1 | &a[0][1] | *a+1 | 2 | a[0][1] | *(*a+1) | *(a[0]+1) |
a[1] | &a[1][0] | *(a+1) | 3 | a[1][0] | *(*(a+1)) | *a[1] |
a[1]+1 | &a[1][1] | *(a+1)+1 | 4 | a[1][1] | *(*(a+1)+1) | *(a[1]+1) |
a[2] | &a[2][0] | *(a+2) | 5 | a[2][0] | *(*(a+2)) | *a[2] |
a[2]+1 | &a[2][1] | *(a+2)+1 | 6 | a[2][1] | *(*(a+2)+1) | *(a[2]+1) |
间接访问
int a[2][3]={1, 2, 3, 4, 5, 6};
int *p = *a;
for (size_t i = 0; i < 2; i++)
{
for (size_t j = 0; j < 3; j++)
{
printf("%p\n", &a[i][j]);
}
}
printf("%p %p\n", a, a+1); // 加了一行
printf("%p %p %d\n", p, p+1, *(p+5)); // 加了一个
- 数组指针
定义:本质是指针,指向的是数组(又叫行指针)
格式:存储类型 数据类型 (*指针变量名)[列数]
int a[2][3] = {1,2,3,4,5,6};
int (*p)[3] = a;
for (size_t i = 0; i < 2; i++)
{
for (size_t j = 0; j < 3; j++)
{
printf("%p\n", *(p+i)+j);
printf("%d\n", *(*(p+i)+j));
printf("%p\n", *(a+i)+j);
printf("%d\n", *(*(a+i)+j));
}
}
p: int (*)[n]; 运算n个n个运算
p可以代替a进行元素访问,但是本质不同
访问数组元素地址(a[i][j]的地址)
*(p+i)+j)
p[i]+j
访问数组元素值:
*(*(p+i)+j)
*(p[i]+j)
大小:
sizeof(p) // 4
因为本质上还是指针,所以大小都是4字节
四、指针数组(本质还是数组)
定义:定义:本质是数组,里面存放的是指针
格式:存储类型 数据类型 *数组名[元素个数]
int *arr[2];
应用:
1) 用于存放普通变量的地址
int a = 10, b = 20, c = 30;
int *arr[3] = {&a, &b, &c};
访问b的地址;
arr[1] *(arr+1)
访问b的值:
*arr[1] *(*(arr+1))
2) 用于存放二维数组的每行第一个元素的地址(列地址)
int a[2][3] = {1, 2, 3, 4, 5, 6};
int *p[2] = { a[0], a[1] };
访问a[1][2]的值:
*(p[1]+2) *(*(p+1)+2)
访问a[1][2]的地址:
p[1]+2 *(p+1)+2
sizeof(p) = 8; // 因为 p 中包含了两个指针