没看过我之前的文章,可以看看哦
1 C++数据类型
2 C++之程序流程结构
3 C++之数组
4 C++之排序算法
5 C++之初识函数
6 C++之字符串
持续更新ing

5 指针

5.1 指针的基本概念

指针的作用:可以通过指针间接访问内存

  • 内存编号是从0开始记录的,一般用十六进制数字表示
  • 可以利用指针变量保存地址

5.2 指针变量的定义与使用

语法:

数据类型 * 变量名

代码示例:

//1、定义一个指针
//指针定义的语法:数据类型*指针变量名
int a = 10;
int *p;
//让指针记录变量a的地址
p = &a;//或者直接int *p=&a;

cout << "a的地址为:" << p << endl;
//2、使用指针
//可以通过解引用的方式来找到指针指向的内存
//指针前加*代表解引用,找到指针指向的内存中的数据
*p = 1000;
cout << "a=" << *p << endl;

运行结果:

a的地址为:00F6FC7C
a=1000

注意:

    int* p中int* 是指向int型的指针。这表明,* p 的类型为int。由于 * 运算符被用于指针,因此p变量本身必须是指针。我们说p指向 int类型,我们还说p的类型是指向int 的指针,或int * 。可以这样说, p是指针(地址),而* p是 int,而不是指针。而int * p=&a中,被初始化的是指针,而不是它指向的值,也就是将p的值设置为&a。

5.3 指针所占内存空间

有两点需要说明:

  • 在32位操作系统下,指针是占4个字节空间大小(不管是什么数据类型)

  • 在64位操作系统下,指针是占8个字节大小(不管是什么数据类型)

代码示例:

//指针所占内存的空间大小
int a = 10;
int *p = &a;
//在32位操作系统下,指针是占4个字节空间大小(不管是什么数据类型)
//在64位操作系统下,指针是占8个字节大小
cout << "sizeof int *=" << sizeof(int *) << endl;
cout << "sizeof char *=" << sizeof(char *) << endl;
cout << "sizeof double *=" << sizeof(double *) << endl;
cout << "sizeof float *=" << sizeof(float *) << endl;

运行结果(默认32位):

sizeof int *=4
sizeof char *=4
sizeof double *=4
sizeof float *=4

在vs中,改成64位,如下图

运行结果:

sizeof int *=8
sizeof char *=8
sizeof double *=8
sizeof float *=8

5.4 空指针和野指针

空指针: 指针变量指向内存中编号为0的空间

用途: 初始化指针变量

注意: 空指针指向的内存是不可以访问的

代码示例:

//初始化指针,NULL为0
int *p = NULL;
//空指针不可访问
//0~255之间的内存编号是系统占用的,不允许用户访问
cout << *p << endl;

引发了异常: 读取访问权限冲突。
p 是 nullptr。

野指针: 指针变量指向非法的内存空间

代码示例:

//野指针
//在程序中,尽量避免出现野指针
int *p = (int *)0x1100;
cout << *p << endl;

引发了异常: 读取访问权限冲突。
p 是 0x1100。

总结:空指针和野指针都不是我们申请的空间,因此不要访问。

5.5 new运算符

    前面我们都将指针初始化为变量的地址;变量是在编译时分配的有名称的内存,而指针只是为可以通过名称直接访问的内存提供了一个别名。指针真正的用武之地在于,在运行阶段分配未命名的内存以存储值。在这种情况下,只能通过指针来访问内存。在C++可使用new运算符来分配内存在运行阶段为一个int值分配未命名的内存,并使用指针来访问这个值。这里的关键所在是C++的new运算符。程序员要告诉new,需要为哪种数据类型分配内存;new将找到一个长度正确的内存块,并返回该内存块的地址。程序员的责任是将该地址赋给一个指针。下面是一个这样的示例:

int *p = new int;

    new int告诉程序,需要适合存储int的内存。new运算符根据类型来确定需要多少字节的内存。然后,它找到这样的内存,并返回其地址。接下来,将地址赋给p,p是被声明为指向int 的指针。现在,p是地址,而*p是存储在那里的值。

int *p1 = new int;
*p1 = 100;
cout << "sizeof *p1=" << sizeof(*p1) << endl;
cout << "*p1=" << *p1 << endl;
cout << "p1=" << p1 << endl;

double *p2 = new double;
*p2 = 100.1;
cout << "sizeof  *p2=" << sizeof(*p2) << endl;
cout << "*p2=" << *p2 << endl;
cout << "p2=" << p2 << endl;

运行结果:

sizeof *p1=4
*p1=100
p1=009AA7A8
sizeof *p2=8
*p2=100.1
p2=009AE9A0

使用delete运算符释放内存

    delete运算符,它使得在使用完内存后,能够将其归还给内存池,这是通向最有效地使用内存的关键一步。归还或释放(free)的内存可供程序的其他部分使用。使用delete时,后面要加上指向内存块的指针(这些内存块最初是用new分配的):

int *p = new int;
...
delete p;

注意:这将释放p指向的内存,但不会删除指针p本身。例如,可以将p重新指向另一个新分配的内存块。一定要配对地使用new和 delete;否则将发生内存泄漏(memory leak),也就是说,被分配的内存再也无法使用了。如果内存泄漏严重,则程序将由于不断寻找更多内存而终止。

使用new和delete的注意事项:

  • 不要使用delete来释放不是new分配的内存。
  • 不要使用delete释放同一个内存块两次。
  • 如果使用new[]为数组分配内存,则应使用delete[]来释放。
  • 如果使用new[ ]为一个实体分配内存,则应使用delete(没有方括号)来释放。
  • 对空指针应用delete是安全的。

使用new创建动态数组

语法

int *p=new int[10];//指针p为数组第一个元素的地址,*p为第一个元素的值
...
delete[] p;//释放内存

new和delete要一一对应

代码示例:

int *p = new int[3];
//指针和数组名一样,因为C++内部都用指针来处理数组
p[0] = 1;
p[1] = 2;
p[2] = 3;
//p指针后移一位,现在p指针指向数组第二个元素地址
p++;
cout << p[0] << endl;
delete[] p;

但是上述情况会报错,vs编译会出现未加载wntdll.pdb的异常。为什么?

解答: 因为对指针 p 的使用时改变了指针指向的位置,直接delete时不是从开辟内存的开始位置开始释放内存,我们只要将p指向的位置指向最初就可以解决该问题。

修改代码如下:

int *p = new int[3];
//指针和数组名一样,因为C++内部都用指针来处理数组
p[0] = 1;
p[1] = 2;
p[2] = 3;
//p指针后移一位,现在p指针指向数组第二个元素地址
p++;
cout << p[0] << endl;
p--;
delete[] p;

运行结果:

2

思考:

如果我们想要遍历这个数组怎么弄?有如下两个方法:

  1. 普通遍历

    int *p = new int[3];
    //赋值
    p[0] = 1;
    p[1] = 2;
    p[2] = 3;
    //普通遍历
    //这里的*(p + i)作用在于变动指针的位置,从而实现遍历
    for (int i = 0; i < 3; i++) {
        cout << *(p + i) << endl;
    }
    //有new必有delete
    delete[] p;
    
  2. 指针遍历

    int *p = new int[3];
    //赋值
    p[0] = 1;
    p[1] = 2;
    p[2] = 3;
    //指针遍历
    //这里的*q,将p的地址赋给了q,q++就是指针移位
    for (int *q = p; q != p + 3; q++) {
        cout << *q << endl;
    }
    //有new必有delete
    delete[] p;
    

指针加法的具体图示:

注意: 将指针变量加1后,其增加的值等于指向的类型占用的字节数

5.6 const修饰指针

const修饰指针有三种情况:

  • const修饰指针 — 常量指针
  • const修饰常量 — 指针常量
  • const既修饰指针,又修饰常量

const修饰指针 — 常量指针

在这里插入图片描述

const修饰常量 — 指针常量

在这里插入图片描述

const既修饰指针,又修饰常量

在这里插入图片描述

代码示例:

int a = 10;
int b = 20;
//1、const修饰指针 
cout << "const修饰指针:" << endl;
const int * p1 = &a;
cout << "指针p1的原地址:" << p1 << endl;
cout << "指针p1指向的值为:" << *p1 << endl;
p1 = &b;
cout << "指针p1的新地址:" << p1 << endl;
cout << "指针p1指向的值为:" << *p1 << endl;

//2、const修饰常量
cout << "\nconst修饰常量:" << endl;
int * const p2 = &a;
cout << "指针p2的地址:" << p2 << endl;
cout << "指针p2指向的原值为:" << *p2 << endl;
*p2 = 100;
cout << "指针p2的地址:" << p2 << endl;
cout << "指针p2指向的新值为:" << *p2 << endl;

//3、const修饰常量和指针
cout << "\nconst修饰常量和指针:" << endl;
const int * const p3 = &a;
cout << "指针p3的地址:" << p3 << endl;
cout << "指针p3指向的值为:" << *p3 << endl;

运行结果:

const修饰指针:
指针p1的原地址:00BEFC68
指针p1指向的值为:10
指针p1的新地址:00BEFC5C
指针p1指向的新值为:20

const修饰常量:
指针p2的地址:00BEFC68
指针p2指向的原值为:10
指针p2的地址:00BEFC68
指针p2指向的新值为:100

const修饰常量和指针:
指针p3的地址:00BEFC68
指针p3指向的值为:100

说明:

  1. const修饰指针时,指针指向的地址发生改变,那么指针指向的值也将发生改变
  2. const修饰常量时,指针指向的地址没有改变,而指针指向的值发生改变,那么分配内存的那个变量值也将发生改变,例如a本来等于10,后来改成了100

5.7 指针与数组

作用: 利用指针访问数组中的元素。

代码示例:

int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
//创建int型指针
int* p = arr;//arr就是数组首地址,即将arr首地址赋给指针p
for (int i = 0; i < 10; i++) {
    cout << *(p++) << " ";
}

运行结果:

1 2 3 4 5 6 7 8 9 10

注意: 这里的*(p++)亦可换成p[i]

5.8 指针与函数

作用: 利用指针作函数参数,可以修改实参的值。

5.8.1值传递

(之前讲过,没看过的同学可以看一看我之前发的传送门

代码示例:

//实现两个数字进行交换
void swap1(int a, int b) {
	int temp = a;
	a = b;
	b = temp;
	cout << "swap1 a=" << a << endl;
	cout << "swap1 b=" << b << endl;
}
int main() {
	//1、值传递
	int a = 10;
	int b = 20;
	swap1(a, b);
	cout << "a=" << a << endl;
	cout << "b=" << b << endl;

}

运行结果:
swap1 a=20
swap1 b=10
a=10
b=20

注意: 值传递时,如果形参发生改变,不会影响实参。

5.8.2 地址传递

代码示例:

//实现两个数字进行交换
void swap2(int *p, int *q) {
	int temp = *p;
	*p = *q;
	*q = temp;
}
int main() {
	//2、地址传递(可以修饰实参)
	int a = 10;
	int b = 20;
	swap2(&a, &b);//传入a、b的地址
	cout << "a=" << a << endl;
	cout << "b=" << b << endl;
}

运行结果:

swap2 a=20
swap2 b=10
a=20
b=10

注意: 地址传递中,形参改变,实参也会跟着改变。

思考: 看到这里,不难想到指针函数函数指针这两个玩意,那么这俩有啥区别呢?

5.8.3 指针函数

含义: 一个返回类型为指针的函数。

代码示例:

//计算两个数的和
int* add(int a, int b) {
	int sum = 0;
	sum = a + b;
	int *p = &sum;
	return p;
}
int main() {
	int a = 0;
	int b = 0;
	cout << "请输入a和b的值:" << endl;
	cin >> a >> b;
	int *ptr = add(a, b);
	cout <<"a+b="<< *ptr << endl;
}

运行结果:

请输入a和b的值:
2 3
a+b=5

如果在int * ptr = add(a, b);后添上一句话,即不立刻执行* ptr的结果:

代码实现:

//计算两个数的和
int* add(int a, int b) {
	int sum = 0;
	sum = a + b;
	int *p = &sum;
	return p;
}
int main() {
	int a = 0;
	int b = 0;
	cout << "请输入a和b的值:" << endl;
	cin >> a >> b;
	int *ptr = add(a, b);
	cout << "请稍等..." << endl;
	cout <<"a+b="<< *ptr << endl;
}

运行结果:

请输入a和b的值:
2 3
请稍等…
a+b=2040314848

发现,*ptr的值已经错误,说明指针ptr访问到不可访问的内容。

原因:

一般的局部变量是存放于栈区的,当函数结束,栈区的变量就会释放掉,如果我们在函数内部定义一个变量,在使用一个指针去指向这个变量,当函数调用结束时,这个变量的空间就已经被释放,这时就算返回了该地址的指针,也不一定会得到正确的值
因此,在使用指针函数的时候,一定要避免出现返回局部变量指针的情况。

解决方法:

将局部变量改为静态变量(推荐)或全局变量。这样,指针就能一直访问到那个变量的值。

修改成功代码:

#include <iostream>
using namespace std;

//计算两个数的和
int* add(int a, int b) {
	static int sum = 0;
	sum = a + b;
	int *p = &sum;
	return p;
}
int main() {
	int a = 0;
	int b = 0;
	cout << "请输入a和b的值:" << endl;
	cin >> a >> b;
	int *ptr = add(a, b);
	cout << "请稍等..." << endl;
	cout <<"a+b="<< *ptr << endl;
}

运行结果:

请输入a和b的值:
2 3
请稍等…
a+b=5

上述问题的发现与解决是我参考别人的传送门

5.8.4 函数指针

含义: 函数指针的本质是一个指针,该指针的地址指向了一个函数,所以它是指向函数的指针。

步骤:

  • 函数指针的定义
  • 函数指针初始化
  • 使用函数指针来调用函数

代码示例:

//计算两个数的和
int add(int a, int b) {
	int sum = 0;
	sum = a + b;
	return sum;
}
int main() {
	int a = 0;
	int b = 0;
	int (*p)(int,int);//函数指针的定义
	p = add;//将add函数地址赋给指针p
	cout << "请输入a和b的值:" << endl;
	cin >> a >> b;
	int sum = (*p)(a, b);//用指针调用函数
	cout <<"a+b="<< sum << endl;
}

运行结果:

请输入a和b的值:
3 5
a+b=8

注意: 在函数调用时,(* p)与p等价,不过为了突出函数指针,建议写(*p)。

Logo

旨在为数千万中国开发者提供一个无缝且高效的云端环境,以支持学习、使用和贡献开源项目。

更多推荐