C++学习——指针篇

本篇文章记录我学习C++的指针内容,希望我的分享能给你带来不一样的收获!

目录

一、指针有什么好处

二、什么是指针

三、C++指针内容详解

(一)、空指针(Null)

(二)、指针的算数运算

1、递增一个指针

2、递减一个指针

3、指针的比较

(三)、指针与数组的比较 

1. 数组和指针的关系

2. 数组名和指针

3. 初始化和赋值

4. 指针与数组的访问

5. 数组的性质

6. 指针的灵活性

7. 指针与数组的区别

(四)、指针数组 

1、声明指针数组

2、初始化指针数组

3、访问指针数组元素

 4、动态分配内存给指针数组

5、释放内存

6、示例

(五)、指向指针的指针

(六)、C++传递指针给函数

(七)、C++从函数返回指针


一、指针有什么好处

通过指针,可以简化一些 C++ 编程任务的执行,还有一些任务,如动态内存分配,没有指针是无法执行的。每一个变量都有一个内存位置,每一个内存位置都定义了可使用连字号(&)运算符访问的地址,它表示了在内存中的一个地址。

#include <iostream>

 

using namespace std;

 

int main ()

{

   int var1;

   char var2[10];

 

   cout << "var1 变量的地址: ";

   cout << &var1 << endl;

 

   cout << "var2 变量的地址: ";

   cout << &var2 << endl;

 

   return 0;

}

执行结果如下:

var1 变量的地址: 0xbfebd5c0

var2 变量的地址: 0xbfebd5b6

二、什么是指针

指针是一个变量,其值为另一个变量的地址,即,内存位置的直接地址。就像其他变量或常量一样,必须在使用指针存储其他变量地址之前,对其进行声明。指针变量声明的一般形式为:

type *var-name;

在这里,type 是指针的基类型,它必须是一个有效的 C++ 数据类型,var-name 是指针变量的名称。用来声明指针的星号 * 与乘法中使用的星号是相同的。但是,在这个语句中,星号是用来指定一个变量是指针。以下是有效的指针声明:

int *ip; /* 一个整型的指针 */

double *dp; /* 一个 double 型的指针 */

float *fp; /* 一个浮点型的指针 */

char *ch; /* 一个字符型的指针 */

所有指针的值的实际数据类型,不管是整型、浮点型、字符型,还是其他的数据类型,都是一样的,都是一个代表内存地址的长的十六进制数。不同数据类型的指针之间唯一的不同是,指针所指向的变量或常量的数据类型不同。

三、C++指针内容详解

(一)、空指针(Null)

在变量声明的时候,如果没有确切的地址可以赋值,为指针变量赋一个 NULL 值是一个良好的编程习惯。赋为 NULL 值的指针被称为空指针。

NULL 指针是一个定义在标准库中的值为零的常量。请看下面的程序:

实例

#include <iostream>

using namespace std;

int main ()

{

   int *ptr = NULL;

   cout << "ptr 的值是 " << ptr ;

 

   return 0;

}

当上面的代码被编译和执行时,它会产生下列结果:

ptr 的值是 0

在大多数的操作系统上,程序不允许访问地址为 0 的内存,因为该内存是操作系统保留的。然而,内存地址 0 有特别重要的意义,它表明该指针不指向一个可访问的内存位置。但按照惯例,如果指针包含空值(零值),则假定它不指向任何东西。

如需检查一个空指针,可以使用 if 语句,如下所示:

if(ptr) /* 如果 ptr 非空,则完成 */

if(!ptr) /* 如果 ptr 为空,则完成 */

因此,如果所有未使用的指针都被赋予空值,同时避免使用空指针,就可以防止误用一个未初始化的指针。很多时候,未初始化的变量存有一些垃圾值,导致程序难以调试。

(二)、指针的算数运算

指针是一个用数值表示的地址。因此,可以对指针执行算术运算。可以对指针进行四种算术运算:++、--、+、-。

假设 ptr 是一个指向地址 1000 的整型指针,是一个 32 位的整数,让我们对该指针执行下列的算术运算:

ptr++

执行 ptr++ 后,指针 ptr 会向前移动 4 个字节,指向下一个整型元素的地址。这是由于指针算术运算会根据指针的类型和大小来决定移动的距离。在这种情况下,由于是一个 32 位整数指针,每个整数占据 4 个字节,因此 ptr++ 会将指针 ptr 向前移动 4 个字节,指向下一个整型元素的地址。

如果 ptr 指向一个地址为 1000 的字符,执行 ptr++ 指针 ptr 的值会增加,指向下一个字符元素的地址,由于 ptr 是一个字符指针,每个字符占据 1 个字节,因此 ptr++ 会将 ptr 的值增加 1,执行后 ptr 指向地址 1001。

指针算术运算的详细解析:

加法运算:可以对指针进行加法运算。当一个指针p加上一个整数n时,结果是指针p向前移动n个元素的大小。例如,如果p是一个int类型的指针,每个int占4个字节,那么p + 1将指向p所指向的下一个int元素。

减法运算:可以对指针进行减法运算。当一个指针p减去一个整数n时,结果是指针p向后移动n个元素的大小。例如,如果p是一个int类型的指针,每个int占4个字节,那么p - 1将指向p所指向的前一个int元素。

指针与指针之间的减法运算:可以计算两个指针之间的距离。当从一个指针p减去另一个指针q时,结果是两个指针之间的元素个数。例如,如果p和q是两个int类型的指针,每个int占4个字节,那么p - q将得到两个指针之间的元素个数。

指针与整数之间的比较运算:可以将指针与整数进行比较运算。可以使用关系运算符(如<、>、<=、>=)对指针和整数进行比较。这种比较通常用于判断指针是否指向某个有效的内存位置。

1、递增一个指针

我们喜欢在程序中使用指针代替数组,因为变量指针可以递增,而数组不能递增,因为数组是一个常量指针。下面的程序递增变量指针,以便顺序访问数组中的每一个元素:

实例:

#include <iostream>



using namespace std;

const int MAX = 3;



int main ()

{

   int var[MAX] = {10, 100, 200};

   int *ptr;



   // 指针中的数组地址

   ptr = var;

   for (int i = 0; i < MAX; i++)

   {

      cout << "Address of var[" << i << "] = ";

      cout << ptr << endl;



      cout << "Value of var[" << i << "] = ";

      cout << *ptr << endl;



      // 移动到下一个位置

      ptr++;

   }

   return 0;

}

当上面的代码被编译和执行时,它会产生下列结果:



Address of var[0] = 0xbfa088b0

Value of var[0] = 10

Address of var[1] = 0xbfa088b4

Value of var[1] = 100

Address of var[2] = 0xbfa088b8

Value of var[2] = 200

2、递减一个指针

同样地,对指针进行递减运算,即把值减去其数据类型的字节数,如下所示:

实例:

#include <iostream>



using namespace std;

const int MAX = 3;



int main ()

{

   int var[MAX] = {10, 100, 200};

   int *ptr;



   // 指针中最后一个元素的地址

   ptr = &var[MAX-1];

   for (int i = MAX; i > 0; i--)

   {

      cout << "Address of var[" << i << "] = ";

      cout << ptr << endl;



      cout << "Value of var[" << i << "] = ";

      cout << *ptr << endl;



      // 移动到下一个位置

      ptr--;

   }

   return 0;

}



当上面的代码被编译和执行时,它会产生下列结果:



Address of var[3] = 0xbfdb70f8

Value of var[3] = 200

Address of var[2] = 0xbfdb70f4

Value of var[2] = 100

Address of var[1] = 0xbfdb70f0

Value of var[1] = 10

3、指针的比较

指针可以用关系运算符进行比较,如 ==、< 和 >。如果 p1 和 p2 指向两个相关的变量,比如同一个数组中的不同元素,则可对 p1 和 p2 进行大小比较。

下面的程序修改了上面的实例,只要变量指针所指向的地址小于或等于数组的最后一个元素的地址 &var[MAX - 1],则把变量指针进行递增:

实例:

#include <iostream>



using namespace std;

const int MAX = 3;



int main ()

{

   int var[MAX] = {10, 100, 200};

   int *ptr;



   // 指针中第一个元素的地址

   ptr = var;

   int i = 0;

   while ( ptr <= &var[MAX - 1] )

   {

      cout << "Address of var[" << i << "] = ";

      cout << ptr << endl;



      cout << "Value of var[" << i << "] = ";

      cout << *ptr << endl;



      // 指向上一个位置

      ptr++;

      i++;

   }

   return 0;

}

当上面的代码被编译和执行时,它会产生下列结果:



Address of var[0] = 0xbfce42d0

Value of var[0] = 10

Address of var[1] = 0xbfce42d4

Value of var[1] = 100

Address of var[2] = 0xbfce42d8

Value of var[2] = 200

(三)、指针与数组的比较 

从某种意义上来讲,指针和数组在一些情况下是可以相互互换的。一个指向数组开头的指针,可以通过使用指针的算术运算或数组索引来访问数组。

在C++中,指针和数组是相关但不完全相同的概念。下面是它们之间的一些比较:

1. 数组和指针的关系
  • 数组是一系列相同类型的元素的集合,它们在内存中是连续存储的。
  • 指针是一个变量,其值为另一个变量的地址。指针可以指向数组的首元素,也可以指向任意其他内存位置。
2. 数组名和指针
  • 在大多数情况下,数组名会转换为指向数组首元素的指针。例如,对于数组 int arr[5];arr 可以被视为指向 arr[0] 的指针。
3. 初始化和赋值
  • 数组的初始化int arr[5] = {1, 2, 3, 4, 5};
  • 指针的初始化int* ptr = arr; 这里 ptr 指向 arr 的首元素。
4. 指针与数组的访问
  • 数组元素访问:使用下标访问 arr[i]
  • 指针访问数组元素:可以使用指针进行数组元素的访问,比如 *(arr + i) 或者 ptr[i](等效于 *(ptr + i))。
5. 数组的性质
  • 数组是常量指针:数组名本身在大多数情况下是一个常量指针,不能被重新赋值。
6. 指针的灵活性
  • 指针的灵活性:指针可以指向数组的任意位置,也可以通过指针进行遍历和修改数组元素。
7. 指针与数组的区别
  • 大小和维度:数组具有固定的大小和维度,而指针没有固定大小,可以指向不同大小的内存块。
  • 内存分配:数组在声明时需要分配固定大小的内存空间,而指针可以动态分配或指向已分配的内存块。

下面举一个实例: 

#include <iostream>

int main() {
    int arr[5] = {1, 2, 3, 4, 5};
    int* ptr = arr; // ptr指向arr的首元素

    // 使用数组下标访问元素
    for (int i = 0; i < 5; ++i) {
        std::cout << arr[i] << " ";
    }
    std::cout << std::endl;

    // 使用指针访问元素
    for (int i = 0; i < 5; ++i) {
        std::cout << *(ptr + i) << " ";
    }
    std::cout << std::endl;

    return 0;
}

(四)、指针数组 

指针数组是一个数组,其中的每一个元素都是指针。每个指针可以指向数组、字符串、函数或者其他数据类型的内存地址。这种数据结构在C和C++等编程语言中经常被使用。

1、声明指针数组
int *ptrArray[5]; // 声明一个包含5个指向int类型数据的指针数组
2、初始化指针数组

 指针数组可以通过循环或者手动赋值的方式进行初始化

int a = 10, b = 20, c = 30, d = 40, e = 50;
int *ptrArray[5] = {&a, &b, &c, &d, &e}; // 初始化指针数组
3访问指针数组元素

可以使用数组索引来访问指针数组的元素,每个元素是一个指针,可以通过解引用操作符 * 来获取指向的值。

printf("%d\n", *ptrArray[0]); // 输出指针数组第一个元素指向的值
 4动态分配内存给指针数组

也可以动态分配内存给指针数组,这在需要根据程序运行时情况动态调整数组大小时很有用。

int **ptrArray;
int size = 5;
ptrArray = (int **)malloc(size * sizeof(int *)); // 分配存储指针的内存空间

ptrArray = (int **)malloc(size * sizeof(int *));

  • malloc(size * sizeof(int *)): malloc 是 C 标准库中的函数,用于动态分配内存。sizeof(int *) 是指针类型 int * 的大小(在大多数系统上通常是4字节或8字节,取决于系统的位数),size 是要分配的指针的数量。因此,size * sizeof(int *) 表示要分配的总字节数,即存储 size 个指针所需的总内存空间。

  • (int **): 这是一种类型转换,将 malloc 返回的通用指针 void * 转换为 int ** 类型的指针。int ** 表示指向指针的指针,因此它用于指向指针数组的指针。

所以,整体来说,这行代码的意思是:动态分配一个可以存储 sizeint 类型指针的内存空间,并将其地址赋值给 ptrArray,这样 ptrArray 就成为了一个指向 int 类型指针数组的指针。

5释放内存

如果使用了动态分配内存的指针数组,在使用完毕后需要进行释放内存,以避免内存泄漏。

free(ptrArray); // 释放内存
6、示例
#include <stdio.h>

int main() {
    int a = 10, b = 20, c = 30, d = 40, e = 50;
    int *ptrArray[5] = {&a, &b, &c, &d, &e}; // 初始化指针数组

    // 访问指针数组元素并输出
    for (int i = 0; i < 5; i++) {
        printf("%d ", *ptrArray[i]);
    }
    printf("\n");

    return 0;
}

(五)、指向指针的指针

指向指针的指针是指一个指针变量,它存储的是另一个指针变量的地址。在C和C++中,指针本身也是一种变量,它存储的是内存地址,而指向指针的指针则是存储指针变量的地址的变量。

下面举一个简单的例子来说明一下:

#include <stdio.h>

int main() {
    int x = 10;
    int *ptr1 = &x;  // ptr1 指向 x 的地址
    int **ptr2 = &ptr1;  // ptr2 指向 ptr1 的地址

    printf("x = %d\n", x);  // 输出 x 的值
    printf("*ptr1 = %d\n", *ptr1);  // 输出 ptr1 指向的值,即 x 的值
    printf("**ptr2 = %d\n", **ptr2);  // 输出 ptr2 指向的值,即 ptr1 指向的值,即 x 的值

    return 0;
}

在这个例子中:

  • ptr1 是一个指向 x 的指针,它存储了 x 的地址。
  • ptr2 是一个指向 ptr1 的指针,它存储了 ptr1 的地址。
  • **ptr2 指的是通过 ptr2 找到 ptr1,再通过 ptr1 找到 x,因此输出的是 x 的值。

指向指针的指针在某些情况下很有用,特别是在函数中传递指针的地址以便能够修改指针指向的内容时。

(六)、C++传递指针给函数

通过传递指针给函数来实现对函数外部变量的引用或者修改。这种方式在函数调用时不会对原始数据进行拷贝,而是直接操作原始数据的地址。以下是传递指针给函数的基本方法。

1、函数声明和定义

// 函数声明
void modifyValue(int* ptr);

// 函数定义
void modifyValue(int* ptr) {
    *ptr = 100; // 修改指针所指向的变量的值为100
}

2、调用函数并且传递指针

int main() {
    int value = 10;
    int* ptr = &value; // 指针ptr指向value的地址

    // 调用函数并传递指针
    modifyValue(ptr);

    // 打印修改后的值
    std::cout << "Modified value: " << value << std::endl;

    return 0;
}

3、解析

  • 在 main 函数中创建了一个整型变量 value,并将其地址赋给指针 ptr
  • modifyValue 函数接受一个指针作为参数,通过该指针可以修改原始数据的值。
  • 在调用 modifyValue 函数时,将指针 ptr 作为参数传递给函数,函数内部通过解引用指针来修改变量 value 的值。
  • 打印修改后的值,可以看到 value 的值已经被修改为100。

注意事项

  • 在使用指针传递参数时,要确保指针不为NULL,否则可能会导致未定义的行为或错误。
  • 使用指针传递参数时,要注意指针所指向的内存区域的生命周期,确保在函数使用期间该内存区域是有效的。
  • 当需要在函数内部修改函数外部变量的值时,传递指针是一种有效的方法,但需要注意函数的副作用。

(七)、C++从函数返回指针

在C++中,函数可以返回指针以提供对动态分配内存或函数外部变量的访问。返回指针的函数通常用于以下情况:

  1. 动态内存分配:函数可以在堆上动态分配内存,并返回指向该内存的指针,允许调用者在函数外部访问分配的内存。

  2. 函数外部变量的访问:函数可以返回指向函数外部变量的指针,以便在函数外部修改该变量的值。

1、返回指向动态分配内存的指针

#include <iostream>

int* createArray(int size) {
    int* arr = new int[size]; // 在堆上动态分配内存
    for (int i = 0; i < size; ++i) {
        arr[i] = i * 2; // 初始化数组元素
    }
    return arr; // 返回指针指向分配的内存
}

int main() {
    int* ptr = createArray(5); // 调用函数并接收返回的指针

    // 使用返回的指针访问动态分配的内存
    for (int i = 0; i < 5; ++i) {
        std::cout << ptr[i] << " ";
    }
    std::cout << std::endl;

    // 释放动态分配的内存
    delete[] ptr;

    return 0;
}

2、返回指向函数外部变量的指针

#include <iostream>

int* findLarger(int a, int b) {
    if (a > b) {
        return &a; // 返回指向a的指针
    } else {
        return &b; // 返回指向b的指针
    }
}

int main() {
    int x = 5, y = 10;
    int* larger = findLarger(x, y); // 调用函数并接收返回的指针

    // 使用返回的指针修改函数外部变量的值
    *larger = 100;

    std::cout << "x: " << x << std::endl; // 输出修改后的x的值
    std::cout << "y: " << y << std::endl; // 输出修改后的y的值

    return 0;
}

注意事项

  • 返回指针的函数必须确保返回的指针在函数外部仍然有效。在第一个例子中,返回的指针指向动态分配的内存,因此在使用完指针后需要负责释放内存。
  • 当函数返回指向函数外部变量的指针时,要确保函数外部变量的生命周期足够长,以免在指针使用期间变量被销毁而导致悬挂指针(dangling pointer)问题。
  • 在使用返回指针的函数时,要注意对返回的指针进行空指针检查,以确保指针的有效性。

 

  • 20
    点赞
  • 30
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小小_扫地僧

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值