经历过C/C++
的Coder
,说起指针,无不是摇头晃脑的。
然而,没弄懂指针,可以说是无法在C/C++
世界里徜徉,毕竟指针可是C/C++
的灵魂所在。
由于C/C++
的Coder
可以通过指针
自由玩转内存
,从而让外界给C/C++
戴上了一顶内存不安全
的语言。
相较于现代语言
,诸如,Java
、golang
、python
、PHP
等这些具有垃圾回收机制
的语言来说,C/C++
把操控内存
的主动权交给了Coder
,之所以会被誉为内存不安全
的语言,其最大责任便是Coder
使用指针
不当而引发内存
崩溃。
什么是指针呢?
对于初学者而言,到底什么是指针
(pointer)呢?我们光从字面上就大抵能猜出个一二,指针
,指针
,无非就是一个事物指向
另一个事物。
说起指针
,便无法抛开内存
而不谈。
我们知道数据(Data)+ 算法(algorithm)= 程序(program)
,那么程序
的本质就是在操控
数据,而内存
便是存放数据
(这里存放数据,不要与数据库
存放的数据混淆了。)
由于变量
的类型
和作用域
所限制,内存
可分为:堆(heap)
、栈(stack)
、全局(global)
以及静态存储(static)
。
无论是C/C++
,抑或是Java
等其他高级语言
,最终都是要编译成汇编语言
,而这种语言它可不管你为变量起了个多么好听的名字,在它那里统统都转变成了内存寻址
。
而指针
便是存放着某一块的内存地址
,可以是某个基础类型变量的地址,也可以是复合类型
变量的地址,甚至还可以是指针
的地址。
#include <iostream>
using namespace std;
int main()
{
int a = 10;
int* ptr = &a; //声明一个int类型的指针,并指向变量a的地址
cout << "变量 a 的地址=" << &a << endl;
cout << "指针变量 ptr 存放着变量 a 的地址=" << ptr << endl;
cout << "通过 * 解引用操作,间接获得变量 a 的值=" << *ptr << endl;
*ptr = 15; //通过 * 解引用操作,来修改指针指向的内容。
cout << "通过 * 解引用操作,间接修改变量 a 的值=" << a << endl;
return 0;
}
数组名就是指针
在初步了解了什么是指针
的前提下,我们将继续探讨下一个话题,数组与指针
到底有着怎样的联系呢?
而为什么说数组名称
就是指针
呢?我们知道,当我们以数组作为实参
传入给函数形参
时,它是以数组第一个元素的地址传入的,也就是以指针
方式传入的。
#include <iostream>
using namespace std;
int main()
{
//定义一个 int 类型数组
int myArr[] = { 10,20,30,40,50,60 };
//再定义一个 int 类型指针,并指向 myArr 数组
int* ptr = myArr;
//试着通过 *ptr 来修改数组里的元素的值。
*ptr = 15;
//随后,输出 myArr 的地址,以及指针变量存放的地址和它指向数组元素的值
cout << "数组 myArr 的地址:" << myArr << endl;
cout << "数组 myArr 第一个元素的地址:" << &myArr[0] << endl;
cout << "指针变量 ptr 存放的地址:" << ptr << endl;
cout << "指针变量 &ptr[0] 存放的地址:" <<&ptr[0] << endl;
//通过遍历数组,输出所有元素
for (int arr : myArr)
{
cout << '\t' << arr;
}
cout << endl;
return 0;
}
从上面输出的结果中,我们可以得出,数组名称
就是数组首个元素的地址。例子中的myArr
(数组名)与&myArr[0]
都是指向第一个元素的地址,而若将它的地址存放在一个指针变量
,那么,ptr
和&ptr[0]
也是同样的指向数组首个元素的地址。
当然,我们是可以通过myArr[0]
和ptr[0]
来获取数组的首个元素,而其他元素则可以通过数组下标获取,例如:myArr[1]
、*(myArr+1)
以及*(ptr+1)
都是获取数组的第二个元素。
#include <iostream>
using namespace std;
int main()
{
//定义一个 int 类型数组
int myArr[] = { 10,20,30,40,50,60 };
//再定义一个 int 类型指针,并指向 myArr 数组
int* ptr = myArr;
//试着通过 *ptr 来修改数组里的元素的值。
*ptr = 15;
cout << "数组首个元素值是:" << myArr[0] << endl;
cout << "通过指针形式获取数组第一个元素内容:" << ptr[0] << endl;
cout << "获取第二个元素的内容:" << myArr[1] << " | 或者另一种方式:" << *(myArr + 1) << endl;
cout << "还是由ptr来获取数组第二个元素:" << *(ptr + 1) << endl;
return 0;
}
数组指针和指针数组
当你看到数组指针
或指针数组
,脑瓜子是不是就开始生疼了。的确,我也是被这个回行文
给整晕了。
数组指针
数组指针
它从本质上来讲,还是一个指针
,它是指向数组的指针(Pointer to an array)
,指向数组的指针
也称为数组指针(array pointer)
。
其定义语法:
data_type (*variable_name)[size of array]
#include <iostream>
using namespace std;
int main()
{
// Pointer to an array of five numbers
//声明一个 指向包含5个int类型元素数组的指针
int(*a)[5];
int b[5] = { 1, 2, 3, 4, 5 };
int i = 0;
// Points to the whole array b
//指向整个数组 b
a = &b;
for (int j = 0;j < 5;j++) {
cout << "直接使用数组名得到的地址:" << &b[j] << " | 直接通过数组下标获取元素:" << b[j] << endl;
}
for (i = 0; i < 5; i++)
cout <<"数组地址:"<< (*a + i)<<" | 获取数组元素:" << *(*a + i) << endl;
return 0;
}
数组指针
有时也称为行指针
,其意是当指针 ptr
执行ptr+1
运算时,指针
是会指向数组
的下一行。
#include <iostream>
using namespace std;
int main()
{
//定义一个 3 行 4 列 的二维数组
int arr[3][4]{ {1,2,3,4},{5,6,7,8},{9,10,11,12} };
//声明一个 数组指针,指向了一个包含 4 个 int 类型元素数组的指针
int (*ptr)[4];
ptr = arr; //将数组首元素的地址赋值给 数组指针 变量ptr,即 arr[0] 或 arr[0][0]
cout << "通过数组指针获取数组arr的元素:" << *(*ptr) << endl;
ptr++;
cout << "通过数组指针获取数组arr的下一行第一个元素:" << *(*ptr) << endl;
return 0;
}
指针数组
指针数组(Array of pointers)
,本质上是一个数组,数组中的所有元素都是一个指针。它是指针变量的数组
,其声明语法:
data_type *variable_name[array_size];
#include <iostream>
using namespace std;
const int SIZE = 5;
int main()
{
//定义一个数组
int arr[]{ 10,20,30,40,50 };
//声明一个 int 变量,以及一个 指针数组
int i, * ptr[SIZE];
for (i = 0; i < SIZE; i++) {
// 将数组元素的地址分配给 指针数组 ptr 变量
ptr[i] = &arr[i];
}
// 遍历输出数组元素,通过 指针数组 的方式
for (i = 0; i < SIZE; i++) {
cout << "Value of arr[" << i << "] = " << *ptr[i] << endl;
}
return 0;
}
数组指针和指针数组的区别
那么,现在你对数组指针
与指针数组
的区分,是不是有了大致的了解呢。
1、声明不同:
数组指针
:本质是一个指针,指向了一个数组指针数组
:本质是一个数组,该数组中的每个元素都是一个指针2、语法不同:
数组指针
:data_type (*variable_name)[n];指针数组
:data_type *variable_name[n];区分方法:
数组名带括号
的就是数组指针
,不带括号
的就是指针数组
。(这个类似于函数指针
和指针函数
的区别)3、可以通过从右到左结合来区分: ①对于
数组指针
data_type (*variable_name)[n],因为括号优先级较高
,因此*号
与数组名variable_name
先结合,也就是说variable_name
首先是一个指针
,然后与[n]
结合,表示指针variable_name
指向了一个大小为n的数组
,数组的类型为data_type
。 ②对于指针数组
data_type *variable_name[n],variable_name
和[]
先结合,因此variable_name
首先是一个大小为n的数组
,剩下的部分是数组的类型
,即data_type*
类型,也就是数组的每个元素都是一个data_type指针
。
访问数组
#include <iostream>
using namespace std;
const int SIZE = 5;
int main()
{
//定义一个二维数组
int arr[3][4]{ {1,2,3,4},{5,6,7,8},{9,10,11,12} };
int* ptr[3];
for (int i = 0;i < 3;i++)
{
ptr[i] = arr[i]; // 指针数组 ptr 有 3 个指针,指向了二维数组 arr 的每一行
}
//可以通过以下几种方式来访问
cout << "使用传统的数组下标访问 arr[1][2] =" << arr[1][2] << endl;
cout << "使用指针的第 1 种方式【*(ptr[1]+2)】 =" << *(ptr[1] + 2) << endl;
cout << "使用指针的第 2 种方式【 *(*(ptr+1) + 2)】 =" << *(*(ptr+1) + 2) << endl;
cout << "使用指针的第 3 种方式【(*(ptr+1))[2]】 =" << (*(ptr+1))[2] << endl;
return 0;
}
函数指针和指针函数
讲完了数组指针
和指针数组
后,将进入另一个话题,函数指针
与指针函数
,或许你还处于数组指针
和指针数组
概念中,一时半会儿还绕不出哪个圈来。
函数指针
函数指针
,其本质是一个指针
,该指针
指向了函数。
简而言之,函数指针
就是指向函数
的指针
。
以下是函数指针
的声明格式:
data_type (*fun)(parameter_type,...) //parameter_type 是可选的
#include <iostream>
using namespace std;
//声明加法函数
int add(int, int);
//声明一个减法函数
int sub(int, int);
//声明一个 函数指针
int (*fun)(int, int);
int main()
{
//第一种写法
fun = add; //将 add 函数地址赋值给 函数指针 fun
cout << "fun(3,4)的结果是:" << fun(3, 4) << endl;
//第二种写法
fun = ⊂
cout << "(*fun)(3,4)的结果是:" << (*fun)(3, 4) << endl;
return 0;
}
int add(int x, int y)
{
return x + y;
}
int sub(int x, int y)
{
return x - y;
}
其实,函数名(函数标识符)就表示了它的地址,故而取地址运算符&
不是必需的,这一点,与数组标识符就是数组的地址,倒是相同的。
指针函数
指针函数
,其本质上是一个函数
,而该函数
的返回值是一个指针
。
其声明格式如下:
return_type* func(parameter_type,[...]) //parameter_type是可选的
其实指针函数
声明与普通函数
声明,就是了多了*
。
#include <iostream>
using namespace std;
//声明 指针函数
int* fun();
int main()
{
int* ptr = fun();
cout << "输出 ptr 的值:" << *ptr << endl;
return 0;
}
int* fun()
{
static int a = 10;
return &a;
}
指针函数与函数指针的区别
我们也同指针数组
与数组指针
的区别一样,同样是从以下几点来区别两者的不同点。
1、从概念上来讲:
指针函数
,其本质是函数
,而该函数
的返回值是指针
。函数指针
,其本质是指针
,他指向了函数
。2、语法不同(语法不同):
指针函数
:return_type* func(parameter_type,[…])函数指针
:return_type (* func) (parameter_type,[…])
其实,C/C++
中的关于指针
的奇葩变种远不止这些,限于篇幅有限(一整长篇大论,总是让人感觉怎么还没到文末呢,而C/C++
又是个枯燥
的东西),故而,至于c/c++
指针的篇目还未终结……