C筑基——指针
指针
我们在声明变量时,有两个要素即“对应地址+对应值”,变量的指针就是变量的地址
优点:提高效率,使用灵活,可实现动态存储分配
缺点:过于灵活,易出现危险的野指针
指针变量
访问变量的方式
1 | int arr[3] = {10, 20, 30}; |
直接访问(通过变量名找到其所在的存储空间)
1
cout<<arr[1]<<endl;
间接访问(将a的地址放入p,通过p去访问变量a)
1
cout<<*(p + 1)<< endl; //&:取地址符 *:指针运算符,所声明的即指针变量
[!TIP]
直接访问(变量名 / 数组下标)是最基础、最直观的内存操作方式,胜在可读性高、出错风险低,适合简单变量操作、非性能敏感的普通场景;而间接访问(指针)的核心价值在于突破直接访问的局限 —— 能直接操作底层固定内存地址适配硬件开发、支持运行时动态创建可变长度数据、传递大对象时无需拷贝提升效率,是实现通用函数、高性能程序、底层 / 动态逻辑的核心方式;实际开发中,两者并非对立,而是互补:简单逻辑用直接访问保证易读,复杂场景(底层、动态内存、高性能传参)用间接访问实现核心功能。
定义指针变量
1 | int a = 10; |
在使用时,指针变量前加 ‘*’ 表示指针变量所指向的目标
1 | int i; |
双指针交换
交换两个指针变量的值
1 |
|
交换指向变量的值
1 | /*--------------------在上一步的基础上--------------------*/ |
用指针做函数参数
将一个变量的地址传送给被调用函数的形参
1 |
|
若想通过函数调用得到n个想要改变的值:
- 在主调函数里设n个变量,用n个指针指向他们;
- 设置函数(包含n个指针形参,在函数中去该百年这n个形参的值)
- 在调用时,在主调函数里将这n个指针变量作为实参,将地址传给函数的形参;
- 在执行函数时通过形参指针变量,改变所指的n个变量的值;
[!NOTE]
不能试图通过改变形参指针变量的值而使实参指针变量的值改变;
函数的调用可以(而且只可以)得到一个返回值,
但是用指针变量作参数,可以得到多个变化了的值。
例:对输入的两个函数按大小顺序输出
1 |
|
数组与指针
数组元素的指针就是数组元素的地址;
在C和C++中数组名代表数组在内存中的起始地址,因此对于
int a[10],*p,则p=&a[0],p=a;这两句是一样的意思;p = p +1;即表示指向数组的下一个元素
引用一个数组元素的方法:(以输出数组中的全部元素为例)
下标法:a[i] 【直观】
1
2
3
4
5
6
7
8
9
10
using namespace std;
int main(){
int a[10];
int i;
for(i=0;i<10;i++)cin>>a[i];
cout<<endl;
for(i=0;i<10;i++)cout<<a[i]<<" "<<endl;
return 0;
}指针法:p=p+i; 【改变指针p的指向,适用于对数组进行遍历 速度快】
1
2
3
4
5
6
7
8
9
10
using namespace std;
int main(){
int a[10];
int i,*p=a;
for(i=0;i<10;i++)cin>>*(p+i);
cout<<endl;
for(p=a;p<(a+10);p++)cout<<*p<<" "<<endl; //主要从这就能看出来
return 0;
}地址法:* (a+i)或* (p+i)【通过计算偏移量直接找到目标位置,不改变当前p的值,适用于随机访问】
1
2
3
4
5
6
7
8
9
10
using namespace std;
int main(){
int a[10];
int i,*p=a;
for(i=0;i<10;i++)cin>>*(a+i); //通过数组名计算地址,也可以像上一种那样用*(p+i)
cout<<endl;
for(p=a;p<(a+10);p++)cout<<*(a+i)<<" "<<endl; //不移动指针p
return 0;
}
指针运算
指针运算符与自增/自减运算符优先级相同,按照从右向左结合
- p++ 指针指向下一个元素
- *p++ 等价于 *(p++) 即先将p所指的元素取出,再使p自增指向下一个元素
- *(++p) 先将p自增1指向下一个元素,然后再取值
- (*p)++ 取出p所指的数组元素,将其值增加1(p自身的值不变,就是地址没变呀)
号外:i +=2;的意思是先算i+2,然后把新值赋给i
字符串与指针
【例】将字符串str1复制为字符串str2
1 | char str1[] = "I Love China",str[20],*p1;*p2; |
函数与指针
函数的入口地址就称为函数的指针;
当一个指针变量指向函数入口地址,那么可以通过这个指针变量调用函数。
定义形式: 函数类型 (*指针变量名)(函数参数) 声明的时候省略
在对函数指针赋值时应写成
p=max;即只将函数名赋给p,因为函数名代表函数入口地址,不能写成后面带括号的,那样就变成调用了
在应用时,一个函数指针变量可以调用不同函数
1 | int a,b,c,n; |
也可用指向函数的指针作为函数的参数
1 | /*这里直接用例子去理解*/ |
void指针类型
它不指向任何具体类型的数据,只表示一个地址;
可以用强制类型转换将其转换成其它类型的指针,从而实现定义一个指针访问多种类型的数据;
它必须转换为指定一个确定类型的数据,才能访问其中的数据;
需注意void 指针*:不是指向对象的类型,即这种指针不能指向一个实体。【可以把非void型的指针赋给void型指针变量,但不能把void指针直接赋给非void型指针变量,必须先强制进行类型转换】
动态分配内存
1 | /*先写一个C++的例子*/ |
小结
- 区分数组指针与指针数组
区分函数&指针
int (*p)(参数): p为指向函数的指针,该函数带回一个整型值
int *p(参数): p为返回值为指针的函数,该指针指向整型值
int **p: p是一个指向指针的指针
const int *p: p是int型的指针常量,其指向的目标不能改变【等价于int const *p】
int * const p: p是int型的常量指针
p2 = p1 = &a; //将变量a的地址赋给p1,再将 p1的值赋给p2指针变量可以有空值,即该指针变量不指向任何变量【int *p = nullptr;】
若NULL指针不指向具体对象,则它仍是空指针,此时,解引用 *q会触发未定义行为,程序必然崩溃;
1
2
3
4
5
6
7
8
9
10
11
12int main()
{
int y=2;
int *q=NULL;
q = &y; //不在这里指明具体对象就会触发未定义行为
*q = 6; //如果没有上面一步,就会提示在给未知空间进行赋值
cout << *q << endl;
return 0;
}
/*
因此对于空指针的使用流程即:初始化为空指针→指向具体对象(通过取地址符获取相应的内存地址并赋值给指针)→安全解引用(就是用*这个操作符,这样就可以修改内存中的数据了)
*/
两个指针相减常用于数组中,这样结果即为两个指针间的元素个数,也可以用于比较(比如前一个元素小于后一个元素这样),但若两个指针指向不同的数组比较就没有意义。


