本篇文章记录我学习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 变量的地址: 0xbfebd5c0var2 变量的地址: 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] = 0xbfa088b0Value of var[0] = 10Address of var[1] = 0xbfa088b4Value of var[1] = 100Address of var[2] = 0xbfa088b8Value 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] = 0xbfdb70f8Value of var[3] = 200Address of var[2] = 0xbfdb70f4Value of var[2] = 100Address of var[1] = 0xbfdb70f0Value 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] = 0xbfce42d0Value of var[0] = 10Address of var[1] = 0xbfce42d4Value of var[1] = 100Address of var[2] = 0xbfce42d8Value 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 **
表示指向指针的指针,因此它用于指向指针数组的指针。所以,整体来说,这行代码的意思是:动态分配一个可以存储
size
个int
类型指针的内存空间,并将其地址赋值给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、返回指向动态分配内存的指针
#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)问题。
- 在使用返回指针的函数时,要注意对返回的指针进行空指针检查,以确保指针的有效性。