C++学习笔记:函数指针和回调函数
前言
一、思考一个问题
二、一维数组与地址、内存的关系
三、数组的本质
四、数组名不一定会被解释为地址
总结
临近秋招,正在准备海投智驾开发的相关岗位。智驾岗位普遍要求能够熟练使用C++,因此决定对C++的知识点进行一次系统的回顾和复习,满足岗位的面试和工作需求。
新开一个专栏,将复习过程中遇到的重要知识点记录下来,做个备忘,方便回顾。
这一部分的内容相对抽象,但非常重要。如果能真正搞懂,会对数组与内存、地址、指针之间的关系有更深的认识。
在开始记录具体的知识点之前,先看一段代码,并思考一个问题:对一个地址或指针变量加1,其值将如何变化?
#include
using namespace std;
int main()
{
char a; cout<< "sizeof(char)=" << sizeof(char) << endl;
short b; cout<< "sizeof(short)=" << sizeof(short) << endl;
int c; cout<< "sizeof(char)=" << sizeof(char) << endl;
double d; cout<< "sizeof(char)=" << sizeof(char) << endl;
cout << "a的地址是:" << (void*)&a << endl;
cout << "a的地址+1是:" << (void*)(&a + 1) << endl;
//注意要对&a+1整体加括号,并把地址转换为(void*),避免char类型使用cout时的显示bug
cout << "b的地址是:" << (void*)&b << endl;
cout << "b的地址+1是:" << (void*)(&b + 1) << endl;
cout << "c的地址是:" << (void*)&c << endl;
cout << "c的地址+1是:" << (void*)(&c + 1) << endl;
cout << "d的地址是:" << (void*)&d << endl;
cout << "d的地址+1是:" << (void*)(&d + 1) << endl;
}
这段代码的运行结果如下:
sizeof(char)=1
sizeof(short)=2
sizeof(int)=4
sizeof(double)=8
a的地址是:000000035B2FFA04
a的地址+1是:000000035B2FFA05
b的地址是:000000035B2FFA24
b的地址+1是:000000035B2FFA26
c的地址是:000000035B2FFA44
c的地址+1是:000000035B2FFA48
d的地址是:000000035B2FFA68
d的地址+1是:000000035B2FFA70
通过上面的测试,不难看出,char、short、int、double所占用的内存空间大小分别是1、2、4、8个字节。
注意看输出的各个地址的尾数,char类型的a的地址+1比a地址增加1,short类型的b的地址+1比b地址增加2,int类型的c的地址+1比c地址增加4,double类型的d的地址+1比d地址增加8。
是否发现了规律?
结论是:将指针变量(地址的值)加1后,增加的量等于它指向的数据类型的字节数。
这个结论就是指针的算术规律,与普通变量(如整型变量)的算术存在明显差异。明白了这一点之后,再来看这一部分的知识点。
仍然先用一段代码来演示数组的地址。
#include
using namespace std;
int main()
{
int a[5]; //定义一个包含5个元素的数组
// 将a的值显示出来
cout << "a的值是:" << (long long) a << endl;
// 将数组a每个元素的地址显示出来
cout << "a[0]的地址是:" << (long long) & a[0] << endl;
cout << "a[1]的地址是:" << (long long) & a[1] << endl;
cout << "a[2]的地址是:" << (long long) & a[2] << endl;
cout << "a[3]的地址是:" << (long long) & a[3] << endl;
cout << "a[4]的地址是:" << (long long) & a[4] << endl;
}
这段代码的运行结果如下:
a的值是:410453538792
a[0]的地址是:410453538792
a[1]的地址是:410453538796
a[2]的地址是:410453538800
a[3]的地址是:410453538804
a[4]的地址是:410453538808
观察a的值和每个元素地址的尾数,发现也很有规律:
a的值与数组第0个元素a[0]的地址相同;
数组中每个元素的地址间隔都是4个字节,与整型变量占用内存空间的大小相同;
数组中相邻两个元素在内存长占用的空间是连续的。
可以尝试将代码中的数据类型改为double或其他类型,也能得到同样的规律。
因此,我们可以得出下面几条数组、地址和内存之间的关系:
1. C++将数组名解释为数组第0个元素的地址;
2. 数组在内存中占用的空间是连续的;
3. 数组第0个元素的地址和数组首地址的取值是相同的;
4. 数组第n个元素的地址是:数组首地址+n;
5. C++编译器把“数组名[下标]”解释为“*(数组首地址+下标)”,即:对数组中该元素的地址解引用。
基于上面的演示,可以得出结论:
数组在内存中占用了一块连续的空间,数组名被解释为数组第0个元素的地址。
而针对这块内存,C++有两种操作方法:数组解释法和指针表示法,二者是等价的。
“数组解释法”是指:直接通过数组中的元素对数组进行操作。
“指针表示法”是指:通过指针的算术运算,也可以得到数组中每个元素的地址。对每个元素的地址解引用,就可以访问每个元素的值。
用下面的代码演示数组解释法和指针表示法:
#include
using namespace std;
int main()
{
int a[5] = { 3 , 6 , 5 , 8 , 9 };
// 用数组表示法操作数组。
cout << "a[0]的值是:" << a[0] << endl;
cout << "a[1]的值是:" << a[1] << endl;
cout << "a[2]的值是:" << a[2] << endl;
cout << "a[3]的值是:" << a[3] << endl;
cout << "a[4]的值是:" << a[4] << endl;
// 用指针表示法操作数组。
int* p = a;
cout << "*(p+0)的值是:" << *(p + 0) << endl;
cout << "*(p+1)的值是:" << *(p + 1) << endl;
cout << "*(p+2)的值是:" << *(p + 2) << endl;
cout << "*(p+3)的值是:" << *(p + 3) << endl;
cout << "*(p+4)的值是:" << *(p + 4) << endl;
}
代码运行结果如下:
a[0]的值是:3
a[0]的值是:6
a[0]的值是:5
a[0]的值是:8
a[0]的值是:9
*(p+0)的值是:3
*(p+1)的值是:6
*(p+2)的值是:5
*(p+3)的值是:8
*(p+4)的值是:9
不难看出,C++操作数组的数组解释法和指针表示法确实是等价的。
既然数组、地址、内存之间具有上面的关系,那么数组本质上是什么呢?
针对这个问题,我是这样理解的:
我们知道,指针用于存放变量在内存中的起始地址,而数组名恰好被C++解释为数组起始元素的地址。
因此,当数组名被解释为地址时,数组本质上其实就是指针,指向的是被数组占用连续空间的那块内存。我们看到的数组,不过是C++在语法上的表示而已。
在之前的笔记中,考虑的都是“将数组名解释为数组第0个元素的地址”的情况。并且,我多次将“解释为”这三个字标红,因为这个说法是严谨且专业的。没有“数组名就是地址”这种说法,这非常不专业。
然而,数组名并不等价于地址!只是在多数情况下被C++解释为数组第0个元素的地址。
并且,只存在下面两种情况,数组名不会被解释为地址。
(1)将sizeof运算符用于数组名:这将返回数组长度(整个数组占用内存空间的字节数),而不是某个指针的长度,此时的数组名显然没有被解释为一个地址;
(2)&数组名:产生一个指向数组的指针,而不是指向某个地址或指针常量的指针。
另外,还有一点需要注意,我们可以对指针的值进行修改,但数组名是常量,不可修改。
本文是针对C++“一维数组与地址、内存、指针的关系”知识点的复习笔记。