数据、指令和内存
- 程序的数据和指令存放在同一内存空间
- 虚拟存储空间的两个关键要素:内存地址+类型
- 指针是对内存区域的抽象:指针变量存放目标对象的内存地址
定义和使用指针
指针的定义
- 使用* 标记指针,定义方式与定义变量一样
在同一个变量定义语句中,基本数据类型只能有一个,但是可以有多个形式相同或不同的声明符。这也就是说,同一个语句可以定义出不同类型的变量。
1
2int *ip1, *ip2; // ip1 和 ip2 都是指向 int 类型变量的指针变量
double d, *dp; // d 是 double 类型变量,dp 是指向 double 类型变量的指针变量可以定义一个指向这一指针的指针
1
2
3int val = 1024
int *p = &val
int **pp = &p
pp 是一个指向指向 int 类型变量的指针的指针。
获取对象的地址
- 用&取地址符号来获取对象地址
- 指针的类型和对象的类型需要严格匹配
1
2int val = 42;
int *p = &val; // &val 返回变量 val 的地址,记录在指向 int 类型变量的指针里
访问指针指向的对象
- 通过解引用指针 p 来访问变量 val
1
2
3int val = 1004
int *p = &val
cout << *p << endl
空指针和空类型的指针
- 空指针是不指向任何对象的指针,字面值是 NULL,它定义在 stdlib 当中
- 空类型的指针,指的是形如 void *pv 的指针。这是一类特殊的指针;这里的空类型,不是说没有类型,而是说空类型的指针,可以用于存储任意类型对象的地址。
- 由于空类型的指针可以接受任意类型对象的地址,所以,当编译器拿到一个空类型的指针的时候,它无法知道应该按照何种方式解释和使用指针中记录地址中的内容。因此,空类型指针能够做的事情非常有限:做指针之间的比较、作为函数的输入或输出、赋值给另外一个空类型指针。
1 | int *p1 = NULL; // C 风格的空指针初始化 |
1 | double pi = 3.14; |
const与指针
- 定义常量,只需要在基本类型前,加上 const 关键字即可
- 常量的值在生存期内不允许改变
- const与指针连用有4种情况:
1 | int val = 0; // int 型变量 |
- 变量可以是常量,而指针本身也可以是常量。因此在变量和指针两个维度,都可以选择是否为常量
- 为了区分这两个维度,我们引入顶层 const 和底层 const 的概念:
顶层 const:指针本身是常量。此时,指针在定义初始化之外,不能被赋值修改。称指针为指针常量。
层 const:指针指向的变量是常量。此时,不能通过解引用指针的方式,修改变量的值。称指针为常量的指针。
指针与数组
1 | int nums[] = {1, 2, 3}; |
数组指针可自增,可加减运算
1
2
3
4
5
6int nums[] = {0,1,2,3,4,5};
size_t len = sizeof(nums) / sizeof(nums[0]);
int *iter, end = nums[len]; // end 是尾后指针
for (iter = nums; iter != end; ++iter) {
printf("%d\n", *iter);
}两个指针如果指向同一个数组中的元素,那么它们可以做差
- 数组指针与整数的加减,实际是将指针沿着数组进行移动,得到的结果还是一个指针。既然结果是指针,那么就可以解引用,访问数组中的元素
函数与指针
让函数返回一个数组的指针
- 函数在返回时会对返回值拷贝
- 数组不能被拷贝,函数无法直接返回数组,但可返回数组的指针
- 如何定义一个返回数组指针的函数:element_type
(*func(param_list))[dimension]
1 | int arr[10] |
1 | int (*func(param_list))[10]; |
函数的指针
- 一个函数的类型,取决于它的输入和输出。这也就是说,一个函数的类型,应当包含它的返回值类型和参数列表
定义一个指向某类型的函数指针
1
2
3bool isEqual(int, int);
bool (*pfunc)(int, int) = &isEqual; // 定义了一个函数指针,指向 isEqual
bool (*pfunc)(int, int) = isEqual; // 一个等价定义pfunc 就是一个函数指针,它指向一个 bool (int, int) 类型的函数。也就是说,这类函数接收两个 int 型的参数,并返回一个 bool 类型的值。
- 当函数名字作为值使用时,它会自动地转换成指针(有点像数组名字,不是吗)。因此,在函数指针的初始化或者复制的过程中,取值运算符是可选的。于是,上述两个定义语句是等价的。另一方面,函数指针作为函数调用使用时,它会自动转换成函数名(有点像数组指针,不是吗)
1 | bool isEqual(int, int); |
将函数指针作为参数传入另一个函数
- 函数不能拷贝,但函数指针可以
1
2
3
4
5
6void addIfEqual(int lhs, int rhs, bool pfunc(int, int));
// addIfEqual 的第三个参数是一个函数定义
// 它会自动地转换成一个函数指针的参数
void addIfEqual(int lhs, int rhs, bool (*pfunc)(int, int));
// 一个等价定义:显式地注明第三个参数是函数指针
>>addIfEqual(1, 1, isEqual);
让函数返回一个函数的指针
- outer_return_type (*func(param_list))(outer_param_list)
- func(param_list) 是当前需要定义的函数;outer_return_type 和 outer_param_list 分别是当前定义的函数返回的函数指针对应函数的返回值类型和参数列表。
其他
- ((void()())0)():访问内存地址 0,将它作为一个参数列表和返回类型均为空的函数,并执行函数调用
- void(*pfunc)(); 定义了一个函数指针 pfunc,它指向的函数参数列表为空、返回值类型也为空
- 类型强制转换符:
1
2(double) c; // 将变量 c 强制转换为 double 类型
(double *) d; // 将变量 d 强制转换为 double * 类型