C++学习笔记:一维数组与地址、内存、指针的关系

专栏其他文章

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++“一维数组与地址、内存、指针的关系”知识点的复习笔记。

你可能感兴趣的:(C++学习笔记,c++,学习,笔记)