关注公众号,后台回复
找书+ C++Primer
获取C++相关电子书。
什么是指针?
指针是一个变量,其值为另一个变量的地址,即,内存位置的直接地址。就像其他变量或常量一样,您必须在使用指针存储其他变量地址之前,对其进行声明。指针变量声明的一般形式为:
type *var-name;
在这里,type 是指针的基类型,它必须是一个有效的 C++ 数据类型,var-name 是指针变量的名称。用来声明指针的星号 * 与乘法中使用的星号是相同的。但是,在这个语句中,星号是用来指定一个变量是指针。以下是有效的指针声明:
int *ip; /* 一个整型的指针 */double *dp; /* 一个 double 型的指针 */float *fp; /* 一个浮点型的指针 */char *ch; /* 一个字符型的指针 */
C++ 指针详解
在 C++ 中,有很多指针相关的概念,这些概念都很简单,但是都很重要。下面列出了 C++ 程序员必须清楚的一些与指针相关的重要概念:
|
|
---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
用法示例
#include<iostream>intmain(){// 1. 声明一个整型变量 num 并赋值int num = 10;// 2. 声明一个指向整型的指针 ptr// 指针 ptr 存储变量 num 的内存地址int* ptr = #// 3. 输出变量 num 的值std::cout << "变量 num 的值: " << num << std::endl;// 4. 输出变量 num 的内存地址std::cout << "变量 num 的内存地址: " << &num << std::endl;// 5. 输出指针 ptr 存储的地址std::cout << "指针 ptr 存储的地址: " << ptr << std::endl;// 6. 通过指针 ptr 访问变量 num 的值// *ptr 是解引用操作,表示访问 ptr 指向的内存地址中的值std::cout << "通过指针 ptr 访问变量 num 的值: " << *ptr << std::endl;// 7. 修改通过指针 ptr 访问的变量 num 的值 *ptr = 20;// 8. 输出修改后的变量 num 的值std::cout << "修改后的变量 num 的值: " << num << std::endl;// 9. 输出修改后的指针 ptr 存储的地址std::cout << "修改后的指针 ptr 存储的地址: " << ptr << std::endl;// 10. 通过指针 ptr 访问修改后的变量 num 的值std::cout << "通过指针 ptr 访问修改后的变量 num 的值: " << *ptr << std::endl;return0;}
注意,同类型同指针,即int变量也要int*指针来表示。
int* p 和 int *p
在C和C++中,int* p 和 int *p 这两种声明方式在语法上是完全等价的,没有任何区别。它们都声明了一个指向整型的指针 p。空格的位置不会影响指针的声明和使用。以下是详细的解释和示例:多个变量声明时的区别:当在一个声明语句中声明多个变量时,空格的位置会影响这些变量的类型。
int* p; // 声明一个指向整型的指针 pint *p; // 声明一个指向整型的指针 pint* p1, p2; // p1 是指向整型的指针,p2 是整型变量int *p1, p2; // p1 是指向整型的指针,p2 是整型变量int* p1, *p2; // p1 和 p2 都是指向整型的指针int *p1, *p2; // p1 和 p2 都是指向整型的指针
如果我直接让p=a会怎么样
在C和C++中,将一个整数值直接赋给一个指针是不合法的,并且会导致编译错误。指针存储的是内存地址,而不是整数值。如果你尝试将一个整数值赋给指针,编译器会报错,因为类型不匹配。
#include<iostream>intmain(){int* p;int a = 10;// 错误的赋值操作 p = a; // 这行代码会导致编译错误return0;}---- error: invalid conversion from 'int' to 'int*' [-fpermissive] p = a;---- ^error C2440: “=”: 无法从“int”转换为“int *”
正确的用法
-
将变量的地址赋给指针:
p = &a;
这里 &a 表示取变量 a 的内存地址,并将其赋值给指针 p。
-
将整数常量作为地址赋给指针(不推荐,除非你知道你在做什么): 如果你确实需要将一个整数常量作为地址赋给指针,可以使用类型转换,但这通常是不安全的,因为这个地址可能不是有效的内存地址。
p = reinterpret_cast<int*>(10);
这里 reinterpret_cast<int*>(10) 将整数 10 转换为指向整型的指针。但请注意,这个地址 10 通常不是有效的内存地址,访问它会导致未定义行为。
#include<iostream>intmain(){int* p;int a = 10;// 正确的赋值操作:将变量 a 的地址赋给指针 p p = &a;// 输出变量 a 的值std::cout << "变量 a 的值: " << a << std::endl;// 输出变量 a 的内存地址std::cout << "变量 a 的内存地址: " << &a << std::endl;// 输出指针 p 存储的地址std::cout << "指针 p 存储的地址: " << p << std::endl;// 通过指针 p 访问变量 a 的值std::cout << "通过指针 p 访问变量 a 的值: " << *p << std::endl;return0;}----变量 a 的值: 10变量 a 的内存地址: 000000000014FD44指针 p 存储的地址: 000000000014FD44通过指针 p 访问变量 a 的值: 10
不同指针所占的内存空间
在C和C++中,指针的大小(即指针所占的内存空间)通常是固定的,与指针所指向的变量类型无关。这意味着无论指针指向的是 int、char、double 还是其他类型,指针本身的大小都是相同的。
#include<iostream>intmain(){// 声明不同类型的指针int* pInt;char* pChar;double* pDouble;float* pFloat;long* pLong; short* pShort;bool* pBool;// 输出不同指针类型的大小std::cout << "int* 指针大小: " << sizeof(pInt) << " 字节" << std::endl;std::cout << "char* 指针大小: " << sizeof(pChar) << " 字节" << std::endl;std::cout << "double* 指针大小: " << sizeof(pDouble) << " 字节" << std::endl;std::cout << "float* 指针大小: " << sizeof(pFloat) << " 字节" << std::endl;std::cout << "long* 指针大小: " << sizeof(pLong) << " 字节" << std::endl;std::cout << "short* 指针大小: " << sizeof(pShort) << " 字节" << std::endl;std::cout << "bool* 指针大小: " << sizeof(pBool) << " 字节" << std::endl;return0;}----int* 指针大小: 8 字节char* 指针大小: 8 字节double* 指针大小: 8 字节float* 指针大小: 8 字节long* 指针大小: 8 字节short* 指针大小: 8 字节bool* 指针大小: 8 字节
空指针和野指针
空指针(Null Pointer)和野指针(Dangling Pointer)是C和C++编程中常见的概念。下面是一个示例代码,展示了这两种指针的定义和使用。
#include<iostream>intmain(){// 1. 空指针 (Null Pointer)// 空指针是一个不指向任何有效内存地址的指针。// 在C++中,可以使用 nullptr 来表示空指针。int* pNull = nullptr;// 检查指针是否为空if (pNull == nullptr) {std::cout << "pNull 是空指针n"; }// 2. 野指针 (Dangling Pointer)// 野指针是指向已经释放或删除的内存地址的指针。// 示例:动态分配内存并释放后,指针仍然指向该内存地址// 动态分配一个整型变量int* pDangling = newint; *pDangling = 10;// 输出动态分配的值std::cout << "动态分配的值: " << *pDangling << std::endl;// 释放动态分配的内存delete pDangling;// 现在 pDangling 成为野指针,因为它指向的内存已经被释放// 访问野指针会导致未定义行为// std::cout << "野指针的值: " << *pDangling << std::endl; // 不要这样做// 为了避免野指针,可以在释放内存后将指针设置为空指针 pDangling = nullptr;// 检查指针是否为空if (pDangling == nullptr) {std::cout << "pDangling 已经被设置为空指针n"; }// 3. 正确的指针使用// 动态分配内存int* pValid = newint; *pValid = 20;// 输出动态分配的值std::cout << "动态分配的值: " << *pValid << std::endl;// 释放动态分配的内存delete pValid;// 将指针设置为空指针 pValid = nullptr;// 检查指针是否为空if (pValid == nullptr) {std::cout << "pValid 已经被设置为空指针n"; }return0;}----pNull 是空指针动态分配的值: 10pDangling 已经被设置为空指针动态分配的值: 20pValid 已经被设置为空指针
在大多数现代操作系统中,内存地址 0-255 通常被系统保留,用于特定的用途,因此应用程序通常不能直接访问这些地址。这些地址通常被称为保留地址或系统保留地址。以下是一些关键点来解释为什么这些地址不能访问:
-
保留地址的原因 •历史原因:在早期的计算机系统中,地址 0 通常用于表示空指针(null pointer)。虽然现代C和C++使用 nullptr 来表示空指针,但地址 0 仍然被保留以确保向后兼容性。 •系统用途:地址 0-255 通常被操作系统用于存储一些关键的系统信息或作为特殊的内存区域。例如,地址 0 可能用于存储中断向量表(Interrupt Vector Table),地址 1-255 可能用于其他系统级数据结构。 •安全性:限制对这些地址的访问可以提高系统的安全性,防止应用程序意外或恶意地修改这些关键区域,从而导致系统崩溃或其他严重问题。
-
访问保留地址的后果 •段错误(Segmentation Fault):尝试访问这些保留地址通常会导致段错误,这是一种运行时错误,表示程序试图访问未分配或受保护的内存区域。 •系统崩溃:访问这些地址可能会导致系统不稳定或崩溃,因为这些区域包含系统关键数据和代码。
-
示例代码 以下是一个示例代码,展示了尝试访问地址 0 的后果:
#include<iostream>intmain(){// 尝试访问地址 0int* p = reinterpret_cast<int*>(0);// 尝试读取地址 0 的值try {std::cout << "地址 0 的值: " << *p << std::endl; } catch (...) {std::cerr << "捕获到异常: 尝试访问地址 0 导致段错误n"; }return0;}
const修饰指针
在C和C++中,const 关键字可以用于修饰指针的不同部分,从而实现不同的常量约束。具体来说,const 可以修饰指针本身、指针所指向的值,或者两者。以下是三种常见的情况及其代码示例:
-
指针指向的值是常量:指针可以改变指向的地址,但不能通过该指针修改指向的值。
const int*
-
指针本身是常量:指针不能改变指向的地址,但可以通过该指针修改指向的值。
int* const
-
指针和指针指向的值都是常量:指针不能改变指向的地址,也不能通过该指针修改指向的值。
const int* const
代码示例
#include<iostream>intmain(){int a = 10;int b = 20;// 1. 指针指向的值是常量// 指针可以改变指向的地址,但不能通过该指针修改指向的值constint* ptr1 = &a; // ptr1 指向的值是常量// 尝试通过 ptr1 修改 a 的值会导致编译错误// *ptr1 = 20; // 错误: 不能通过 const 指针修改值// 可以改变 ptr1 指向的地址 ptr1 = &b;std::cout << "ptr1 指向的值: " << *ptr1 << std::endl; // 输出 20// 2. 指针本身是常量// 指针不能改变指向的地址,但可以通过该指针修改指向的值int* const ptr2 = &a; // ptr2 本身是常量// 可以通过 ptr2 修改 a 的值 *ptr2 = 30;std::cout << "通过 ptr2 修改后的 a 的值: " << a << std::endl; // 输出 30// 尝试改变 ptr2 指向的地址会导致编译错误// ptr2 = &b; // 错误: 不能改变 ptr2 指向的地址// 3. 指针和指针指向的值都是常量// 指针不能改变指向的地址,也不能通过该指针修改指向的值constint* const ptr3 = &a; // ptr3 本身是常量,指向的值也是常量// 尝试通过 ptr3 修改 a 的值会导致编译错误// *ptr3 = 40; // 错误: 不能通过 const 指针修改值// 尝试改变 ptr3 指向的地址会导致编译错误// ptr3 = &b; // 错误: 不能改变 ptr3 指向的地址return0;}---ptr1 指向的值: 20通过 ptr2 修改后的 a 的值: 30
指针和数组
指针和数组在C和C++中有着密切的关系。数组名在大多数情况下可以被视为指向数组第一个元素的指针。这种关系使得使用指针来访问数组元素变得非常方便和灵活。下面是一个详细的示例代码,展示了指针和数组之间的关系。
#include<iostream>intmain(){// 声明一个包含5个整数的数组int arr[5] = { 10, 20, 30, 40, 50 };// 数组名 arr 可以被视为指向数组第一个元素的指针int* ptr = arr; // 等价于 int* ptr = &arr[0];// 使用指针访问数组元素// 1. 使用指针访问数组元素std::cout << "使用指针访问数组元素:" << std::endl;for (int i = 0; i < 5; ++i) {std::cout << "arr[" << i << "] = " << *(ptr + i) << std::endl; }// 2. 使用指针遍历数组元素std::cout << "n使用指针遍历数组元素:" << std::endl;for (int i = 0; i < 5; ++i) {std::cout << "*(ptr + " << i << ") = " << *(ptr + i) << std::endl; }// 3. 使用指针递增访问数组元素std::cout << "n使用指针递增访问数组元素:" << std::endl;for (int i = 0; i < 5; ++i) {std::cout << "*ptr = " << *ptr << std::endl; ptr++; // 指针递增,指向下一个元素 }// 重新将 ptr 指向数组的起始位置,否则第5部分不对 ptr = arr;// 4. 使用指针和数组名访问数组元素std::cout << "n使用指针和数组名访问数组元素:" << std::endl;for (int i = 0; i < 5; ++i) {std::cout << "*(arr + " << i << ") = " << *(arr + i) << std::endl; }// 5. 使用指针和数组下标访问数组元素std::cout << "n使用指针和数组下标访问数组元素:" << std::endl;for (int i = 0; i < 5; ++i) {std::cout << "ptr[" << i << "] = " << ptr[i] << std::endl; }// 6. 使用数组名和指针下标访问数组元素std::cout << "n使用数组名和指针下标访问数组元素:" << std::endl;for (int i = 0; i < 5; ++i) {std::cout << "arr[" << i << "] = " << arr[i] << std::endl; }return0;}---使用指针访问数组元素:arr[0] = 10arr[1] = 20arr[2] = 30arr[3] = 40arr[4] = 50使用指针遍历数组元素:*(ptr + 0) = 10*(ptr + 1) = 20*(ptr + 2) = 30*(ptr + 3) = 40*(ptr + 4) = 50使用指针递增访问数组元素:*ptr = 10*ptr = 20*ptr = 30*ptr = 40*ptr = 50使用指针和数组名访问数组元素:*(arr + 0) = 10*(arr + 1) = 20*(arr + 2) = 30*(arr + 3) = 40*(arr + 4) = 50使用指针和数组下标访问数组元素:ptr[0] = 10ptr[1] = 20ptr[2] = 30ptr[3] = 40ptr[4] = 50使用数组名和指针下标访问数组元素:arr[0] = 10arr[1] = 20arr[2] = 30arr[3] = 40arr[4] = 50
指针和函数
指针可以作为函数的参数,使得函数可以修改调用者提供的数据,或者传递大型数据结构而不需要复制整个数据。指针还可以用于动态内存分配和复杂数据结构的管理。下面是一个详细的示例代码,展示了指针在函数中的不同作用,并说明了值传递和地址传递的区别。
#include<iostream>// 函数声明voidmodifyByValue(int x);voidmodifyByPointer(int* ptr);voidprintArray(int arr[], int size);voidmodifyArray(int arr[], int size);voidprintArrayUsingPointer(int* ptr, int size);intmain(){int num = 10;int arr[5] = {1, 2, 3, 4, 5};// 1. 值传递std::cout << "值传递示例:" << std::endl;std::cout << "修改前 num 的值: " << num << std::endl; modifyByValue(num);std::cout << "修改后 num 的值: " << num << std::endl; // 值未改变// 2. 地址传递(使用指针)std::cout << "n地址传递示例:" << std::endl;std::cout << "修改前 num 的值: " << num << std::endl; modifyByPointer(&num);std::cout << "修改后 num 的值: " << num << std::endl; // 值已改变// 3. 使用指针访问数组std::cout << "n使用指针访问数组元素:" << std::endl; printArrayUsingPointer(arr, 5);// 4. 使用数组名作为指针传递数组std::cout << "n使用数组名作为指针传递数组:" << std::endl; printArray(arr, 5);// 5. 修改数组元素std::cout << "n修改数组元素:" << std::endl; modifyArray(arr, 5); printArray(arr, 5);return0;}// 1. 值传递函数voidmodifyByValue(int x){ x = 20; // 修改局部变量 x 的值std::cout << "modifyByValue 内部 x 的值: " << x << std::endl;}// 2. 地址传递函数(使用指针)voidmodifyByPointer(int* ptr){ *ptr = 20; // 通过指针修改调用者提供的变量的值std::cout << "modifyByPointer 内部 *ptr 的值: " << *ptr << std::endl;}// 3. 使用指针访问数组元素voidprintArrayUsingPointer(int* ptr, int size){for (int i = 0; i < size; ++i) {std::cout << "ptr[" << i << "] = " << ptr[i] << std::endl; }}// 4. 使用数组名作为指针传递数组voidprintArray(int arr[], int size){for (int i = 0; i < size; ++i) {std::cout << "arr[" << i << "] = " << arr[i] << std::endl; }}// 5. 修改数组元素voidmodifyArray(int arr[], int size){for (int i = 0; i < size; ++i) { arr[i] *= 2; // 通过指针修改数组元素 }}---值传递示例:修改前 num 的值: 10modifyByValue 内部 x 的值: 20修改后 num 的值: 10地址传递示例:修改前 num 的值: 10modifyByPointer 内部 *ptr 的值: 20修改后 num 的值: 20//注意值改变了使用指针访问数组元素:ptr[0] = 1ptr[1] = 2ptr[2] = 3ptr[3] = 4ptr[4] = 5使用数组名作为指针传递数组:arr[0] = 1arr[1] = 2arr[2] = 3arr[3] = 4arr[4] = 5修改数组元素: //值变了arr[0] = 2arr[1] = 4arr[2] = 6arr[3] = 8arr[4] = 10
总结 •值传递:函数参数是调用者提供的变量的副本。在函数内部对参数的修改不会影响调用者提供的变量。 •地址传递(使用指针):函数参数是指向调用者提供的变量的指针。通过指针可以修改调用者提供的变量的值。 •指针和数组:数组名在大多数情况下可以被视为指向数组第一个元素的指针。可以使用指针和数组下标访问和修改数组元素。
指针、数组和函数
封装一个函数,利用冒泡排序,实现对整形数组的升序排序。
#include<iostream>// 函数声明voidbubbleSort(int* arr, int size);voidprintArray(int* arr, int size);intmain(){int arr[5] = { 5, 3, 8, 4, 2 };std::cout << "排序前的数组:" << std::endl; printArray(arr, 5);// 调用冒泡排序函数 bubbleSort(arr, 5);std::cout << "n排序后的数组:" << std::endl; printArray(arr, 5);return0;}// 冒泡排序函数voidbubbleSort(int* arr, int size){for (int i = 0; i < size - 1; ++i) {for (int j = 0; j < size - i - 1; ++j) {if (arr[j] > arr[j + 1]) {// 交换 arr[j] 和 arr[j + 1]int temp = arr[j]; arr[j] = arr[j + 1]; arr[j + 1] = temp; } } }}// 打印数组函数voidprintArray(int* arr, int size){for (int i = 0; i < size; ++i) {std::cout << arr[i] << " "; }std::cout << std::endl;}---排序前的数组:53842排序后的数组:23458
对比之前第五章的纯数组排序
// 冒泡排序函数(使用指针)voidbubbleSort(int* arr, int size){for (int i = 0; i < size - 1; ++i) {for (int j = 0; j < size - i - 1; ++j) {if (arr[j] > arr[j + 1]) {// 交换 arr[j] 和 arr[j + 1]int temp = arr[j]; arr[j] = arr[j + 1]; arr[j + 1] = temp; } } }}// 冒泡排序函数(使用数组)voidbubbleSort(int arr[], int size){for (int i = 0; i < size - 1; ++i) {for (int j = 0; j < size - i - 1; ++j) {if (arr[j] > arr[j + 1]) {// 交换 arr[j] 和 arr[j + 1]int temp = arr[j]; arr[j] = arr[j + 1]; arr[j + 1] = temp; } } }}
使用指针的冒泡排序
优点
-
灵活性: •指针可以指向任何内存地址,使得函数可以处理动态分配的内存或不同类型的数组。 •可以更灵活地操作内存,适用于更复杂的场景。 -
动态内存管理: •可以使用指针来处理动态分配的数组,例如通过 new 和 delete 分配和释放内存。 •适用于需要动态调整大小的数组。 -
通用性: •使用指针的函数可以处理不同大小的数组,只需传递数组的大小即可。 •适用于需要处理不同长度数组的场景。
缺点
-
复杂性: •使用指针需要更复杂的代码,容易出错,特别是对于初学者来说。 •需要小心处理内存管理,避免内存泄漏和悬空指针。
可读性: •使用指针的代码可能不如使用数组的代码直观,尤其是在处理多维数组时。 •需要理解指针算术和解引用操作。
纯用数组的冒泡排序
优点
-
简单性: •使用数组的代码更简单直观,易于理解和维护。 •特别适合初学者学习和使用。 -
可读性: •使用数组的代码通常更易读,尤其是在处理一维数组时。 •数组下标操作直观,易于理解。 -
安全性: •使用数组时,编译器通常会进行边界检查(在某些编译器和优化级别下),减少越界访问的风险。
缺点
-
灵活性有限: •数组的大小在声明时必须确定,不能动态调整。 •不适用于需要动态调整大小的数组。 -
通用性有限: •使用数组的函数通常需要知道数组的具体大小。 •不适用于需要处理不同长度数组的场景。 -
动态内存管理: •不能直接处理动态分配的数组,需要额外的内存管理代码。 •适用于静态数组,不适合动态内存分配的场景
🔔 想要获取更多网络安全与编程技术干货?
关注 泷羽Sec-静安 公众号,与你一起探索前沿技术,分享实用的学习资源与工具。我们专注于深入分析,拒绝浮躁,只做最实用的技术分享!💻
扫描下方二维码,马上加入我们,共同成长!🌟
👉 长按或扫描二维码关注公众号
或者直接回复文章中的关键词,获取更多技术资料与书单推荐!📚
原文始发于微信公众号(泷羽Sec-静安):07x指针56-63
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论