指针
指针
一、指针的基本概念
变量的地址
获取 变量在内存中的 起始地址,使用 &变量名
。
1 | int a; |
指针变量
简称 指针,存放 变量在内存中的起始地址,使用 数据类型* 变量名
,数据类型*
也是一种数据类型,即 x 型指针。
对指针赋值
使用 指针 = &地址
,称为指向某变量,被指向的类型称为基类型。
1 | int a; |
二、使用指针
注意:声明指针变量后必须初始化。
*
运算符(即 解引用)用于获得该地址的内存中存储的值。
变量和指向变量的指针就像是同一枚硬币的两面,即 &a
与 pa
相同,表示的是地址;a
与 *pa
相同,表示的是变量的值。
1 | int a = 8; |
对于普通变量,系统在内部跟踪该内存单元;对于指针变量,系统直接访问该内存单元。
三、指针用于函数的参数
1 | void fun(int *a) { |
在函数中修改地址,将修改变量的值,称为 传址。除此之外,可以减少内存拷贝,提升性能。
四、用 const
修饰指针
常量指针
const 数据类型* 变量名
,不能解引用以修改内存地址的值,但可以修改指向的变量。
1 | int a = 8, b = 6; |
应用:一般用于修饰函数的形参,表示不希望在函数里修改内存地址中的值。
指针常量
数据类型* const 变量名
,指向的变量不能改变,但可以解引用以修改内存地址的值。
应用:一般无用。
常指针常量
const 数据类型* const 变量名
,指向的变量不能改变,也不能解引用以修改内存地址的值。
应用:一般无用。
五、void 关键字
函数的形参用 void*
,表示接受数据类型的指针,如 memset()
中第一个参数。
注意:不能对 void*
指针直接解引用,需要先转换成其它类型的指针。
1 | void fun(void* p) { |
六、动态分配内存
内存空间
地址从低到高依次为:代码段(可执行代码、常量区)、数据段(全局变量和静态变量)、堆(动态开辟内存的变量,内存很大,需手动管理)、栈(很小,局部变量、函数参数和返回值)、内核空间。
动态分配内存
申请内存:int* p = new 数据类型(初始值)
;
释放内存:delete 地址
。
1 | int* p = new int(5); |
七、二级指针
指针用于存放普通变量的地址,二级指针用于存放指针变量的地址,使用 数据类型** 指针名
。
1 | int a = 8; |
在函数中,如果传递普通变量的地址,形参用指针;传递指针的地址,形参用二级指针。
1 | void fun(int** pp) { |
八、空指针
用 0
或 NULL
表示空指针(在 C++11 中有更安全的 nullptr
),它指向空,不能解引用。
1 | int* p = nullptr; |
为避免程序崩溃,应当判断函数形参是否为空。
1 | void fun(int *a) { |
九、无效指针
访问无效的指针可能造成程序崩溃,可能原因有:
手动赋值;
未初始化;
释放内存后指针失效:
1
2
3
4
5int* p = new int(8);
cout << *p << '\n';
delete p;
cout << *p << '\n';
// return ugly变量的内存空间已被回收(让指针指向局部变量,将局部变量的地址作为返回值赋给指针):
1
2
3
4
5
6
7
8
9int* fun() {
int a = 8;
return &a;
}
int main() {
int p = fun();
cout << *p << '\n';
}
// return ugly
规避方法:
- 定义时初始化为空;
- 释放内存后,置为空;
- 函数不返回局部变量的地址。
十、一维数组和指针
指针的算术
指针变量加 1
,增加量等于 sizeof 数据类型
。
数组的地址
数组在内存中占用的空间是连续的,数组的地址即数组的第 0 个元素的地址:
1 | int a[5] = { 1, 2, 3, 5, 8 }; |
数组的第 个元素的地址是 数组首地址 + n
,编译器将 a[1]
解释为 *(a + 1)
。
使用 sizeof 数组名
,得到的是整个数组占用内存空间的字节数。
十一、一维数组用于函数的参数
指针的数组表示
编译器将 数组名[下标]
解释为 *(数组名 + 下标)
,将 指针[下标]
解释为 *(指针 + 下标)
。
1 | int a[5] = { 1, 2, 3, 5, 8 }; |
于是:
1 | int a[5] = { 1, 2, 3, 5, 8 }; |
又:
1 | char a[20]; |
表明,内存指定了操作方法,但可通过修改指针类型改变操作内存的方法。
一维数组用于函数的参数
1 | void fun(int* p, int len); |
传参时,必须指定数组的长度。因为在函数内,使用 sizeof a
获取的是指针占用内存空间的字节数(8 字节),而不是数组的。
十二、动态创建一维数组
在堆上分配内存。
申请内存:int* a = new 数据类型[数组长度]
;
释放内存:delete[] 指针
。
1 | int* a = new int[8]; |
注意:
动态创建的数组没有数组名,使用
sizeof
运算符得到的是指针占用内存空间的字节数。为避免申请失败造成程序崩溃,使用
1
2
3
4
5
6
7int* a = new(std::nothrow) int[8];
if (a == nullptr) {
cout << "Ugly";
return;
}
a[5] = 8;
delete[] a;
十三、二维数组用于函数的参数
1 | int* p; // 整型指针 |
行指针(数组指针)
数据类型 (*行指针名)[行的大小];
用于指向数组长度为 行的大小
的 数据类型
型数组。
一维数组名被解释为数组第 0 个元素的地址,对一维数组名取地址得到的是数组的地址,即行地址。
1 | int a[10]; |
二维数组名是行地址
1 | int a[2][3] = {{ 2,3,5 }, { 3,5,8 }}; |
二维数组 a
有 2 个元素,每个元素是一个数组长度为 3 的整型数组。
a
被解释为数组长度为 3 的整型数组类型的行地址。
(*p)[3]
是数组长度为 3 的整型数组类型的行指针。
二维数组用于函数的参数
1 | void fun(int (*p)[3], int len); |
十四、多维数组
多维数组
1 | int a[2][3][4]; |
三维数组 a
有 2 个元素,每个元素是一个 3 行 4 列的整型二维数组。
a
被解释为 3 行 4 列的整型二维数组类型的行地址。
(*p)[3][4]
是 3 行 4 列的整型二维数组类型的行指针。
多维数组用于函数的参数
1 | void fun(int (*p)[3][4], int len); |
十五、函数指针与函数回调
把函数的地址作为参数传递给函数,可以在函数中灵活调用其它函数。
1 | void fun(int n) { |
应用:
1 | void fun1(int a) { |