1. 指针运算
1.1 算术运算
- 加减+、-
指针与整数相加:表示指针指向下个变量。
指针与整数相减:表示指针指向上个变量。
指针与指针相减:两个指针的元素间隔个数。
int arr[]={100,101,102,103,104,105};
int* p = arr;
int* q;
for(int i=0;i<5;++i){
q = p+i;
printf("%d\n",*q);
}
for(int i=0;i<5;++i){
p = q-i;
printf("%d\n",*p);
}
printf("q-p=%d\n",q-p);
- 自增自减++、- -
指针能够算术运算,必然能够自增自减。
int arr[]={100,101,102,103,104,105};
int* p = arr;
for(int i=0;i<5;++i){
printf("%d\n",*p++);
}
for(int i=0;i<5;++i){
printf("%d\n",*p--);
}
操作说明
1.操作数是指针
2.自增自减++、- -优先级高于解引用*
计算过程
1.运算++/- -,返回的是p的值(地址),然后p自加/自减。
2.运算*
,获取p指向的值。
等价于
*p;
p=p+1;
自增自减 | 相当于 |
---|---|
*q++ | *(q++) |
*q- - | *(q- -) |
操作说明
操作数是指针
1.前缀自增自减++、- -和解引用*
的结合律是自右向左。
2.前缀自增自减++、- -在解引用*
的右边,优先计算。
计算过程
1.运算++/–,p自加/自减,返回的是p自加/自减后的值(地址)。
2.运算*,获取p指向的值。
自增自减 | 相当于 |
---|---|
*++q | *(++q) |
*- -q | *(- -q) |
操作说明
*
操作数是指针,前缀自增自减++、- -操作数是指针指向的值。
前缀自增自减++、- -和解引用*
的结合律是自右向左。
解引用*
在前缀自增自减++、- -的右边,优先计算。
计算过程
运算*
,获取p指向的值。
运算++/- -,p指向的值自加/自减。
自增自减 | 相当于 |
---|---|
++*q | ++(*q) |
- -*q | - -(*q) |
如果一个表达式里有多个运算符,则先进行优先级比较,先执行优先级高的运算符;如果优先级相同,那就看结合性,根据结合方向来做运算。
1.2 单位长度
int iarr[] = {1,2,3,4,5,6};
int* p = iarr;
for(int i=0;i<5;++i){
printf("%p\n",p++);
}
char carr[] = {1,2,3,4,5,6};
char* q=carr;
for(int i=0;i<5;++i){
printf("%p\n",p++);
}
- 应用范围
指针的算术运算表示在一片连续空间上的移动。
指针的比较运算也是用于一片连续空间的地址比较。
常用于数组等连续内存。
2. 指针类型
1.无论指向什么类型,所有指针的大小都是一样的,都是地址的大小。
char* str;
short* ps;
int* pn;
long* pl;
long long* pll;
float* pf;
double* pd;
long double* pld;
2.指针类型转换
指向不同类型的指针不能直接相互赋值(特例void*),需要强制类型转换。
char* str = "abcd";
int* p = str;
指针类型转换没有改变指针内的地址,也没有改变指针指向的值,只是改变了移动的单位长度。
#include <stdio.h>
int main(){
char* str = "abcdef";
int* p=(int*)str;
p++;
char* q = (char*)p;
printf("%c\n",*q);
}
3.void
类型的指针
void*
是一种很特别的指针,表示指向未知类型的指针,并不指定它是指向哪一种类型的数据,而是根据需要转换为所需数据类型。
int n = 0;
int* p = &n;
void* q = p;
int* k = (int*) q;
指针作用小结
1.较大数据结构体传入时做参数。
2.传入数组后,对数组做操作。
3.函数需要多个返回值时,作为返回值参数。
4.动态申请内存。
5.避免使用未初始化指针、空指针和野指针。
3. 数组指针 vs 指针数组
3.1 数组指针
指向一个数组指针称为数组指针。
int n = 0;
int* p = &n;
int arr[] = {1,2,3,4,5,6};
int* q = arr;
3.2 指针数组
指针是一个类型,也可以组成一个数组,这样的数组称为指针数组。
#include <stdio.h>
int main(){
int a = 1;
int b = 2;
int c = 3;
int* p[] = {&a,&b,&c};
for(int i=0;i<3;++i){
printf("%d\n",*p[i]);
}
for(int i=0;i<3;++i){
printf("%d\n",**(p+i));
}
}
[]
的优先级高于*
,那么p
先和[]
结合,说明这是一个数组。再和int*
结合,说明这个数组里的每个元素都是一个指针,每个元素都能保存一个地址。
4. 常量指针 vs 指针常量
4.1 常量指针const int* p
可以写作int const *p
,p
是int*
类型,const
修饰的是*p
,所以*p
是常量,表示p
指向的地址里的值不可修改,也就是说,p
里的值不能再重新赋值了,但是可以修改p
指向的地址。
int a = 10;
int b = 20;
const int *p = &a;
p = &b; // 可以
*p = 100; // 错误
4.2 指针常量int* const p
p
是int*
类型,那么const
修饰的是p
,所以p
是常量,表示p
指向的地址不可修改,即p
不能再指向别的地方了,但是可以修改p
指向的这个地址里的值。
int a = 10;
int b = 20;
int * const p = &a;
p = &b; // 错误
*p = 100; // 允许
4.3 常量指针常量const int* const p
p是int类型,两个const分别修饰了p和p, 所以p和*p都是常量,表示p指向的地址不可修改,同时p指向的地址里的值也不可修改。
int a = 10;
int b = 20;
const int *const p = &a;
p = &b; // 错误
*p = 100; // 错误
自由的代价,是永远的警惕。-- C Primer Plus
你定义了一个指针,那就一定要知道这个指针指向的什么地方,而且你要保证这个指针是真实有效的,否则我就用程序崩溃来惩罚你。
NO. | 例子 | 名称 | 指向的值 | 地址 |
---|---|---|---|---|
1 | const int* p /int const* p | 常量指针 | 不可改变 | 可改变 |
2 | int* const p | 指针常量 | 可改变 | 不可改变 |
3 | const int* const p | 常量指针常量 | 不可改变 | 不可改变 |
*
之前的const
修饰指向的变量,*
之后的const
修饰指针。
0地址
#include <stdio.h>
int main(){
int *p = 0;
printf("%d\n",*p);
}
0
地址是内存中不能访问的地址。在C语言中,标准库定义NULL
表示0
地址。
通常用来表示如下:
1.指针没有初始化
2.返回指针无效