07.指针
07.指针
目录介绍
- 7.1 指针基本概念
- 7.1.1 什么是指针
- 7.1.2 指针声明
- 7.1.3 指针初始化
- 7.1.4 指针占用空间
- 7.2 指针基本操作
- 7.2.1 取地址运算符
- 7.2.2 解引用运算符
- 7.3 指针和数组
- 7.4 指针和函数
- 7.4.1 指针作为函数参数
- 7.4.2 指针作为函数返回值
- 7.5 指针与常量
- 7.5.1 指向常量的指针
- 7.5.2 常量指针
- 7.5.3 指向常量的常量指针
- 7.5.4 综合案例
- 7.6 指针高级用法
- 7.6.1 指针的指针
- 7.6.2 函数指针
- 7.7 指针注意实现
- 7.7.1 空指针
- 7.7.2 野指针
- 7.8 综合案例练习
- 7.8.1 数组的升序
- 7.8.2 遍历数组案例
- 7.8.3 指针vs普通对象
7.1 指针基本概念
7.1.1 什么是指针
指针的作用: 可以通过指针间接访问内存
- 内存编号是从0开始记录的,一般用十六进制数字表示
- 可以利用指针变量保存地址
指针,它存储了一个内存地址。指针可以指向其他变量或对象的内存地址,通过指针,可以直接访问和操作内存中的数据。
7.1.2 指针声明
指针变量定义语法:
数据类型 *指针变量名;
数据类型:指针指向的变量的类型(如int、double等)。*:表示这是一个指针变量。指针变量名:指针的名称。
7.1.3 指针初始化
指针在使用前必须初始化,否则会指向一个未知的内存地址,可能导致程序崩溃。
int *ptr = nullptr; // 初始化为空指针
看如下案例所示:
int main() {int a = 10; //定义整型变量a//1、指针的定义//指针定义语法: 数据类型 * 变量名 ;int * p;//初始化,指针变量赋值p = &a; //指针指向变量a的地址cout << &a << endl; //打印数据a的地址cout << p << endl; //打印指针变量p//2、指针的使用//通过*操作指针变量指向的内存cout << "*p = " << *p << endl;return 0;
}
指针变量和普通变量的区别
- 普通变量存放的是数据,指针变量存放的是地址
- 指针变量可以通过" * "操作符,操作指针变量指向的内存空间,这个过程称为解引用
- 总结1:我们可以通过 & 符号 获取变量的地址
- 总结2:利用指针可以记录地址
- 总结3:对指针变量解引用,可以操作指针指向的内存
7.1.3 指针占用空间
提问:指针也是种数据类型,那么这种数据类型占用多少内存空间?示例:
int main() {int a = 10;int * p;p = &a; //指针指向数据a的地址cout << *p << endl; //* 解引用cout << sizeof(p) << endl;cout << sizeof(char *) << endl;cout << sizeof(float *) << endl;cout << sizeof(double *) << endl;\return 0;
}
//10
//8
//8
//8
//8
在 C++ 中,指针的大小取决于编译器和操作系统的位数。通常情况下,指针在 C++ 中也会占用一定的内存空间,这个空间大小与系统的位数相关。
在大多数现代计算机系统中,指针的大小通常如下:
32 位系统:在 32 位系统中,指针通常占用 4 个字节(32 位)的内存空间。
64 位系统:在 64 位系统中,指针通常占用 8 个字节(64 位)的内存空间。
7.2 指针基本操作
7.2.1 取地址运算符
& 用于获取变量的内存地址。
int num = 10;
int *ptr = # // ptr 指向 num 的地址
7.2.2 解引用运算符
* 用于访问指针指向的内存地址中的值。
int num = 10;
int *ptr = #
cout << *ptr; // 输出 10
7.3 指针使用场景
7.3.1 指针和数组
数组名本身就是一个指针,指向数组的第一个元素。示例
#include <iostream>
using namespace std;int main() {int arr[3] = {10, 20, 30};int *ptr = arr; // ptr 指向数组的第一个元素for (int i = 0; i < 3; i++) {cout << "Element " << i << ": " << *(ptr + i) << endl;}return 0;
}
输出:
Element 0: 10
Element 1: 20
Element 2: 30
7.4 指针和函数
指针可以作为函数的参数或返回值,用于传递或返回内存地址。
7.4.1 指针作为函数参数
#include <iostream>
using namespace std;void increment(int *ptr) {(*ptr)++; // 修改指针指向的值
}int main() {int num = 10;increment(&num); // 传递 num 的地址cout << "Incremented value: " << num << endl;return 0;
}
输出:
Incremented value: 11
7.4.2 指针作为函数返回值
#include <iostream>
using namespace std;int* getMax(int *a, int *b) {return (*a > *b) ? a : b;
}int main() {int x = 10, y = 20;int *maxPtr = getMax(&x, &y);cout << "Max value: " << *maxPtr << endl;return 0;
}
输出:
Max value: 20
7.5 指针与常量
指针可以与 const 关键字结合,表示指针指向的值或指针本身不可修改。
const修饰指针有三种情况
- const修饰指针 — 常量指针。常量指针(Constant Pointer):在这种情况下,const 修饰指针本身,表示指针本身是常量,不能通过该指针修改指向的地址。
- const修饰常量 — 指针常量。指向常量的指针(Pointer to Constant):在这种情况下,const 修饰指针所指向的值,表示指针指向的值是常量,不能通过该指针修改所指向的值。
- const即修饰指针,又修饰常量。指向常量的常量指针(Constant Pointer to Constant):结合上述两种情况,指针本身和指针所指向的值都是常量,既不能通过指针修改所指向的值,也不能修改指针本身指向的地址。
7.5.1 指向常量的指针
const int *ptr; // ptr 指向的值不可修改
7.5.2 常量指针
int *const ptr = # // ptr 本身不可修改
7.5.3 指向常量的常量指针
const int *const ptr = # // ptr 和 ptr 指向的值都不可修改
7.5.4 综合案例
示例:
int main() {int a = 10;int b = 10;//const修饰的是指针,指针指向可以改,指针指向的值不可以更改const int * p1 = &a;p1 = &b; //正确//*p1 = 100; 报错//const修饰的是常量,指针指向不可以改,指针指向的值可以更改int * const p2 = &a;//p2 = &b; //错误*p2 = 100; //正确//const既修饰指针又修饰常量const int * const p3 = &a;//p3 = &b; //错误//*p3 = 100; //错误return 0;
}
技巧:看const右侧紧跟着的是指针还是常量, 是指针就是常量指针,是常量就是指针常量
7.6 指针高级用法
7.6.1 指针的指针
指针可以指向另一个指针。
int num = 10;
int *ptr = #
int **ptr2 = &ptr; // ptr2 指向 ptr
7.6.2 函数指针
函数指针是指向函数的指针变量,可以用于动态调用函数。
#include <iostream>
using namespace std;// 函数
int add(int a, int b) {return a + b;
}int main() {// 声明函数指针int (*funcPtr)(int, int) = add;// 使用函数指针调用函数int result = funcPtr(3, 5);cout << "Sum: " << result << endl;return 0;
}
输出:
Sum: 8
7.7 指针注意实现
7.7.1 空指针
空指针:空指针是指不指向任何有效内存地址的指针。空指针通常用来表示指针没有被初始化或者指向了无效的内存地址。在 C++ 中,空指针的值通常是 0 或者使用 nullptr 关键字表示。
用途:初始化指针变量
注意:空指针指向的内存是不可以访问的
示例1:空指针
int main() {//指针变量p指向内存地址编号为0的空间int * p = NULL;//访问空指针报错 //内存编号0 ~255为系统占用内存,不允许用户访问cout << *p << endl;return 0;
}
7.7.2 野指针
野指针:指针变量指向非法的内存空间。
野指针是指指向未知内存地址或已释放的内存地址的指针。野指针通常是由于指针未正确初始化、指向已释放的内存或者超出作用域而导致的。
int main() {//指针变量p指向内存地址编号为0x1100的空间int * p = (int *)0x1100;//访问野指针报错 cout << *p << endl;return 0;
}
总结:空指针和野指针都不是我们申请的空间,因此不要访问。
使用野指针可能会导致程序崩溃、内存泄漏或产生不可预测的行为。如何避免野指针:避免野指针的最佳方法是谨慎管理指针的生命周期,确保正确初始化、释放内存后置空指针,并避免超出指针的作用域。
7.8 综合案例练习
7.8.1 数组的升序
案例描述: 封装一个函数,利用冒泡排序,实现对整型数组的升序排序
例如数组:int arr[10] = { 4,3,6,9,1,2,10,8,7,5 };
示例:
//冒泡排序函数
void bubbleSort(int * arr, int len) //int * arr 也可以写为int arr[]
{for (int i = 0; i < len - 1; i++){for (int j = 0; j < len - 1 - i; j++){if (arr[j] > arr[j + 1]){int temp = arr[j];arr[j] = arr[j + 1];arr[j + 1] = temp;}}}
}//打印数组函数
void printArray(int arr[], int len)
{for (int i = 0; i < len; i++){cout << arr[i] << endl;}
}int main() {int arr[10] = { 4,3,6,9,1,2,10,8,7,5 };int len = sizeof(arr) / sizeof(int);bubbleSort(arr, len);printArray(arr, len);return 0;
}
总结:当数组名传入到函数作为参数时,被退化为指向首元素的指针
7.8.2 遍历数组案例
作用:利用指针访问数组中元素。数组名本身是一个指针,指向数组的第一个元素。
示例:
int main() {int arr[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};int *p = arr; //指向数组的指针。看解释1cout << "第一个元素: " << arr[0] << endl;cout << "指针访问第一个元素: " << *p << endl; //指向数组第一个元素的指针。看解释2for (int i = 0; i < 10; i++) {//利用指针遍历数组。看解释3cout << *p << endl;p++;}return 0;
}
- 解释1,数组名是指针:在 C++ 中,数组名可以被隐式转换为指向数组第一个元素的指针。这意味着p可以将数组名视为指向数组的第一个元素的指针。
- 解释2,指针和数组的关系:指针可以用来访问数组中的元素。通过指针算术运算,可以遍历数组中的元素。
- 解释3,指针和数组的传递:当传递数组给函数时,实际上传递的是数组的地址,即数组名被隐式转换为指向数组第一个元素的指针。
7.8.3 指针vs普通对象
值语义 vs 指针语义,核心概念理解:
// 值语义:操作副本
Account copy = original; // 创建副本
copy.deposit(100); // 修改副本,原对象不变// 指针语义:操作原对象
Account* ptr = &original; // 指向原对象
ptr->deposit(100); // 修改原对象
性能影响分析,内存使用对比
// Account对象大小估算
class Account {std::string accountNumber; // ~24字节(小字符串优化)std::string name; // ~24字节double balance; // 8字节// 总计:约56字节
};// 使用指针:8字节(64位系统)
Account* ptr;// 使用对象:56字节 + 复制开销
Account obj;
时间复杂度对比:
- 指针方式:O(1) 赋值操作
- 对象方式:O(n) 复制操作(n为对象大小)
