侧边栏壁纸
博主头像
秋码记录

一个游离于山间之上的Java爱好者 | A Java lover living in the mountains

  • 累计撰写 139 篇文章
  • 累计创建 308 个标签
  • 累计创建 46 个分类

不说是彻底搞懂,至少让你不再惧怕c/c++指针,以及各种奇葩指针变种

经历过C/C++Coder,说起指针,无不是摇头晃脑的。

然而,没弄懂指针,可以说是无法在C/C++世界里徜徉,毕竟指针可是C/C++的灵魂所在。

由于C/C++Coder可以通过指针自由玩转内存,从而让外界给C/C++戴上了一顶内存不安全的语言。

相较于现代语言,诸如,JavagolangpythonPHP等这些具有垃圾回收机制的语言来说,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;
}

image-20241125195242456

数组名就是指针

在初步了解了什么是指针的前提下,我们将继续探讨下一个话题,数组与指针到底有着怎样的联系呢?

而为什么说数组名称就是指针呢?我们知道,当我们以数组作为实参传入给函数形参时,它是以数组第一个元素的地址传入的,也就是以指针方式传入的。

#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;
}

image-20241125204453457

从上面输出的结果中,我们可以得出,数组名称就是数组首个元素的地址。例子中的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;
}

image-20241125205604020

数组指针和指针数组

当你看到数组指针指针数组,脑瓜子是不是就开始生疼了。的确,我也是被这个回行文给整晕了。

数组指针

数组指针它从本质上来讲,还是一个指针,它是指向数组的指针(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;
}

image-20241126203614684

数组指针有时也称为行指针,其意是当指针 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;
}

image-20241126210116013

指针数组

指针数组(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;
}

image-20241126212157334

数组指针和指针数组的区别

那么,现在你对数组指针指针数组的区分,是不是有了大致的了解呢。

  • 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;
}

image-20241127210529294

函数指针和指针函数

讲完了数组指针指针数组后,将进入另一个话题,函数指针指针函数,或许你还处于数组指针指针数组概念中,一时半会儿还绕不出哪个圈来。

函数指针

函数指针其本质是一个指针,该指针指向了函数。

简而言之,函数指针就是指向函数指针

以下是函数指针的声明格式:

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 = &sub;
	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;
}

image-20241128212836647

其实,函数名(函数标识符)就表示了它的地址,故而取地址运算符&不是必需的,这一点,与数组标识符就是数组的地址,倒是相同的。

指针函数

指针函数其本质上是一个函数,而该函数的返回值是一个指针

其声明格式如下:

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;
}

image-20241129212250444

指针函数与函数指针的区别

我们也同指针数组数组指针的区别一样,同样是从以下几点来区别两者的不同点。

  • 1、从概念上来讲:

    指针函数其本质是函数,而该函数的返回值是指针

    函数指针其本质是指针,他指向了函数

  • 2、语法不同(语法不同):

    指针函数:return_type* func(parameter_type,[…])

    函数指针:return_type (* func) (parameter_type,[…])

其实,C/C++中的关于指针的奇葩变种远不止这些,限于篇幅有限(一整长篇大论,总是让人感觉怎么还没到文末呢,而C/C++又是个枯燥的东西),故而,至于c/c++指针的篇目还未终结……

image-20241205230032072

image-20241208215801343

解决windows下php8.x及以上版本,在Apache2.4中无法加载CURL扩展的问题
« 上一篇 2024-11-27
一套由 Hugo 驱动的博客主题 hugo-theme-kiwi 开源啦
下一篇 » 2023-12-24

相关推荐