没看过我之前的文章,可以看看哦 1 C++数据类型 2 C++之程序流程结构 3 C++之数组 4 C++之排序算法 5 C++之初识函数 6 C++之字符串 持续更新ing
5 指针
5.1 指针的基本概念
指针的作用:可以通过指针间接访问内存
- 内存编号是从0开始记录的,一般用十六进制数字表示
- 可以利用指针变量保存地址
5.2 指针变量的定义与使用
语法:
数据类型 * 变量名
代码示例:
int a = 10;
int *p;
p = &a;
cout << "a的地址为:" << p << endl;
*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 指针所占内存空间
有两点需要说明:
代码示例:
int a = 10;
int *p = &a;
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的空间
用途: 初始化指针变量
注意: 空指针指向的内存是不可以访问的
代码示例:
int *p = NULL;
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];
...
delete[] p;
new和delete要一一对应
代码示例:
int *p = new int[3];
p[0] = 1;
p[1] = 2;
p[2] = 3;
p++;
cout << p[0] << endl;
delete[] p;
但是上述情况会报错,vs编译会出现未加载wntdll.pdb的异常。为什么?
解答: 因为对指针 p 的使用时改变了指针指向的位置,直接delete时不是从开辟内存的开始位置开始释放内存,我们只要将p指向的位置指向最初就可以解决该问题。
修改代码如下:
int *p = new int[3];
p[0] = 1;
p[1] = 2;
p[2] = 3;
p++;
cout << p[0] << endl;
p--;
delete[] p;
运行结果:
2
思考:
如果我们想要遍历这个数组怎么弄?有如下两个方法:
-
普通遍历 int *p = new int[3];
p[0] = 1;
p[1] = 2;
p[2] = 3;
for (int i = 0; i < 3; i++) {
cout << *(p + i) << endl;
}
delete[] p;
-
指针遍历 int *p = new int[3];
p[0] = 1;
p[1] = 2;
p[2] = 3;
for (int *q = p; q != p + 3; q++) {
cout << *q << endl;
}
delete[] p;
指针加法的具体图示:
注意: 将指针变量加1后,其增加的值等于指向的类型占用的字节数。
5.6 const修饰指针
const修饰指针有三种情况:
- const修饰指针 — 常量指针
- const修饰常量 — 指针常量
- const既修饰指针,又修饰常量
const修饰指针 — 常量指针
const修饰常量 — 指针常量
const既修饰指针,又修饰常量
代码示例:
int a = 10;
int b = 20;
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;
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;
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
说明:
- const修饰指针时,指针指向的地址发生改变,那么指针指向的值也将发生改变
- const修饰常量时,指针指向的地址没有改变,而指针指向的值发生改变,那么分配内存的那个变量值也将发生改变,例如a本来等于10,后来改成了100
5.7 指针与数组
作用: 利用指针访问数组中的元素。
代码示例:
int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
int* p = arr;
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() {
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() {
int a = 10;
int b = 20;
swap2(&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 = ∑
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 = ∑
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 = ∑
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;
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)。
|