一、指针的重要性
C/C++语言之所以强大,以及其自由性,很大部分体现在其灵活的指针运用上。因此,说指针是C/C++语言的灵魂一点都不为过。通过指针,可以简化一些 C++ 编程任务的执行,还有一些任务,如动态内存分配,没有指针是无法执行的。所以,想要成为一名优秀的 C++ 程序员,学习指针是很有必要的。
二、什么是指针
指针是一个变量,其值为另一个变量的地址,即内存位置的直接地址。就像其他变量或常量一样,您必须在使用指针存储其他变量地址之前,对其进行声明。指针变量声明的一般形式为:
type *var-name;
- type 是指针的基类型,它必须是一个有效的 C++ 数据类型
- var-name 是指针变量的名称。
指针是c++中的一个重要概念,可以使我们通过指针间接地访问内存的一种工具,而这样的工具就是一把双刃剑,一面可以实现一些非常优化的程序,另一方面也会导致一些难以调试的错误。
三、指针的基操
对于指针来说,取地址(&) 和解引用(*) 是最基本也是最重要的两个操作符。
1.取地址(&):
每一个变量都有一个内存位置,每一个内存位置都定义了可使用连字号(&)运算符访问的地址,它表示了在内存中的一个地址:
#include <bits/stdc++.h>//万能头文件
using namespace std;
int main()
{
int a = 10;
cout << "变量a在内存中的地址: ";
cout << &a<< endl;
system("pause");
return 0;
}
执行结果:
变量a在内存中的地址: 0x62fe1c
可以看到,它输出了定义的变量的地址,也就是内容为10
的int变量a
在内存中的地址为0x62fe1c
2.解引用(*)
接下来我们看看解引用的使用:
#include <bits/stdc++.h>//万能头文件
using namespace std;
int main()
{
int a= 10;
int *p; // 指针变量的声明
p = &a; // 在指针变量中存储 a 的地址
cout << "变量a在内存中的地址: ";
cout << &a<< endl;
cout << "指针p的值: ";
cout << p << endl;
cout << "对p解引用获得的值: ";
cout << *p << endl;
system("pause");
return 0;
}
执行结果:
变量a在内存中的地址: 0x62fe14
指针p的值: 0x62fe14
对p解引用获得的值: 10
可以看到指针p就是将对应变量在内存中的地址存起来,
对指针解引用就是将指针存的(指向的)地址中的值再给取出来。
那到这的话,可能有人会说那我们为什么要用指针啊,直接通过变量名去访问不就行了?
是可以通过变量名去访问的,因为这里只是演示下指针的基本的操作,在实际中,指针也不会只是对变量进行访问这么朴实,它的存在还是有其他作用的。
四、为什么要使用指针
1.函数传参:
如果一个函数要使用参数,则必须声明接受参数值的变量。这些变量称为函数的形式参数。形式参数就像函数内的其他局部变量,在进入函数时被创建,退出函数时被销毁。
接下来我们直接看一个例子:
#include <bits/stdc++.h>//万能头文件
using namespace std;
int main(){
int a;
a = 10;
test(a);
cout << "test后a的值为:";
cout << a << endl;
system("pause");
return 0;
}
void test(int a){//形参a
a = a + 1;
}
执行结果:
test后a的值为:10
在该程序中,我们希望通过test()函数
将变量a加1
,输出的值应该为11
才对,可是实际输出的却是10
。这是因为这种向函数传递参数的方式叫传值调用。 该方法把参数的实际值赋值给函数的形式参数。在这种情况下,修改函数内的形式参数对实际参数没有影响。
就像上面说的,形参a只有在进入test()函数时才被创建出来,它就是一个值与实参相同的另一个变量罢了,对它的操作并不会影响到实参,而当函数结束时,它就会被销毁了,实参是不会有变化的。
那么如何在函数中对实参进行操作呢,这时我们的指针就能派上用场了。我们可以采用指针调用。 该方法把参数的地址赋值给形式参数。在函数内,该地址用于访问调用中要用到的实际参数。这意味着,修改形式参数会影响实际参数。来看例子:
#include <bits/stdc++.h>//万能头文件
using namespace std;
int main(){
int a;
a = 10;
test(&a);//将变量a的地址传进函数
cout << "test后a的值为:";
cout << a << endl;
system("pause");
return 0;
}
void test(int *a){
*a = *a + 1;
}
执行结果:
test后a的值为:11
可以看到,当我们通过指针调用进行传参时,在函数内就能对实参进行操作了。这是因为,我们传参时,不像传值调用一样只将值传给形参,而是将实参在内存中的地址信息传给了函数中的指针变量,那我们不仅可以通过对这个指针进行解引用得到实参的值
,我们还能通过指针对实参进行修改
。
不是所有函数都必须要使用指针调用,需根据函数的用途来决定传参的方式
2.数组的遍历
在c++中指针和数组是密切相关的。
原因之一是数组与指针有一个很重要的关系:数组名就是指向数组首元素的指针。(数组的首元素的地址,也被称为数组的基地址):
#include <bits/stdc++.h>//万能头文件
using namespace std;
int main(){
int arr[] = {1,2,3};
cout << arr << endl;
cout << *arr << endl;
system("pause");
return 0;
}
执行结果:
0x61fe14
1
另一个原因是指针算数运算自身的一个特点:
指针可以像整形那样进行一部分算数操作,但操作后的指针不一定会指向具有有效数据的地址,所以在进行指针算数操作的时候需要格外小心。
指针可以加上或减去一个整数。 指针的这种运算的意义和通常的数值的加减运算的意义是不一样的,指针的运算是有单位的:
#include <bits/stdc++.h>//万能头文件
using namespace std;
int main(){
int a = 1;
int* p = &a;
cout << "p的地址:";
cout << p << endl;
p++;
cout << "p++后的地址:";
cout << p << endl;
system("pause");
return 0;
}
执行结果:
p的地址:0x61fe14
p++后的地址:0x61fe18
可以看到进行p++后,p的地址不是单纯的加1,而是增加了(0x61fe18 - 0x61fe14) = 4,这是因为指针p的类型是int*,它指向的类型是int,对p++,编译之后乘上了它的单位sizeof(int)。
有了这两个原因,我们对于数组的遍历就显得很舒服了:
#include <bits/stdc++.h>//万能头文件
using namespace std;
int main(){
int arr[5] = {1,2,3,4,5};
int* p = arr;
for(int i = 0; i < 5; i++){
cout << *p << " ";
p++;
}
system("pause");
return 0;
}
执行结果:
1 2 3 4 5
这里还有一个需要注意的地方:数组名不能直接自加,即不可对arr++,因为数组被创建出来的时候,它在内存的地址就已经确定下来了,我们不能人为的更改它的位置。但是可以将其赋值给一个指针,指针可以自加:p++
3.其他
关于为什么要使用指针,其实还有许多原因:链表、二叉树等的构建
、动态分配内存
、操作申请的堆内存
、共享内存数据
、指针和数组的效率问题
等等,大家可以自行了解。
其实是我也不太会,嘿嘿,等我学会了再写
以上内容谨为个人学习过程的记录,欢迎大家一起学习和指正