前几天在学习密码学的时候,再看 C++源码的时候,发现 C++在函数调用数组的时候并不需要调用 & 来指向数组地址 基础还是不行,留此随记提醒自己

数组与指针的关系

C++中数组和指针是两个十分常用且关系密切的数据结构,“数组即指针,指针即数组”的言论一直层出不穷。从本质上讲,数组和指针是不同的,数组是具有确定数量的元素,而指针只是一个标量值。但是,在某些情况下数组和指针又能相互转换。下面,将从多个角度分析数组和指针。

1. 数组作为函数参数

1.1 数组元素作为函数实参

这里并没有引用数组地址,而是仅仅作为实参传入数值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
int work(int n)
{
n = 1;
return n;
}

int main() {
int arr[5] = {1,2,3,4,5};
int n = -0x3f3f3f3f; // 十六进制,就是很小很小的负数
cout << max(arr[1], n) << endl; // 此处会输出2
cout << "仅作为实参下的arr[1]:" << work(arr[1])<< endl; //仅作为实参下的arr[1]:1

cout << "真实的arr[1]:" << arr[1];//真实的arr[1]:2
return 0;

}

1.2 数组名作为函数实参

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include <iostream>

using namespace std;

void work(int n[])
{
n[1] = 1;
}

int main() {
int arr[5] = {1,2,3,4,5};
cout << "开始的arr[1]:" << arr[1] << endl; // 开始的arr[1]:2
work(arr);
cout << "最后的arr[1]:" << arr[1] << endl;//最后的arr[1]:1
return 0;

}

可以发现这里的引用arr(尽管名字不一样),函数引用的不仅仅是数组里面的数值,而是地址,这边牵扯到下面有管数组和指针的区别

2. 数组和指针互相转换

数组能在指定情况下转换为指针,当数组在表达式中使用时,编译器将数组名转换为一个指针常量,指向数组第一个元素的地址。

1
2
3
4
5
6
7
8
9
10
11
12
int main()
{
int arr[] = {2,4,1,5,6};
int *p1 = arr;
int *p2 = &arr[0];
cout << "p1:" << p1 << ", value:" << *p1 << endl; // p1:0xffffcbd0, value:2
cout << "p2:" << p2 << ", value:" << *p2 << endl; // p2:0xffffcbd0, value:2
cout << "arr:" << arr << ", value:" << arr[0] << endl; // arr:0xffffcbd0, value:2

cout << "p1+2:" << p1+2 << ", value:" << *(p1+2) << endl; // p1+2:0xffffcbd8, value:1
cout << "arr+2:" << &arr[2] << ", value:" << arr[2] << endl;// arr+2:0xffffcbd8, value:1
}

结论:如果令int * p = arr,则p = arr = &arr[0], p+2 = arr+2 = &arr[2];

但是,有两种情况下,数组名与指针不能混为一谈。

  • 第一种,数组作为sizeof操作符的参数时:

    • sizeof是一个操作符(operator),其作用是返回一个对象或类型所占的内存字节数。

    • sizeof(数组): 大小是数组的元素个数*元素类型所占字节数,与数组的类型信息相关,与地址信息无关;

    • sizeof(指针): 大小固定,32 位机器全是 4 个字节,64 位机都是 8 个字节。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
int main() {
int arr[] = {2, 4, 1, 5, 6};
int *p = arr;
cout << sizeof(arr) << endl; // 20 : 5*sizeof(int)
cout << sizeof(*arr) << endl; // 4 : 第一个元素值的大小
cout << sizeof(&arr) << endl; // 8 : 第一个元素地址的大小
cout << sizeof(arr[0]) << endl; // 4 : 第一个元素值的大小
cout << sizeof(&arr[0]) << endl; // 8 : 第一个元素地址的大小

cout << sizeof(p) << endl; // 8 : 指针的大小
cout << sizeof(*p) << endl; // 4 : 指针所指元素值的大小
cout << sizeof(&p) << endl; // 8 : 指针的地址的大小
cout << sizeof(p[0]) << endl; // 4 : 数组第一个元素值的大小
cout << sizeof(&p[0]) << endl; // 8 : 数组第一个元素值的地址的大小
}
  • 第二种,数组作为单目操作符&的操作数时:
    • &数组: 表示取数组的地址,即数组的指针;
    • &指针: 表示取指针的地址,即指针的指针。

3. 二维数组与指针

指针的概念其实很简单,指针难就难在与其他结构之间的牵扯,比如指针与二维数组。

  • 第一点,定义指向二维数组的指针:

    定义二维数组 int aa[2][5];
    由于数组名为数组的第一个元素的地址,而二维数组 aa 的第一个元素为长度为 5 的一维数组;
    因此,如果定义一个指针指向二维数组的话,该指针的长度也必须为 5;
    即,int (*p)[5] = aa 或者 int (*p)[5] = &aa[0], 表示长度为 5 的指针数组。

  • 第二点,指针访问二维数组第一个元素中的值:

    • 首先,*p 表示二维数组中第一个元素对应的值,即长度为 5 的一维数组,假设为 a[5];
    • 其次,*p 可看成一维数组 a[5]的名,即 a[5]的第一个元素的地址;
    • 最后,如果想取 aa[0][2],则可用*(*p+2) 表示。
1
2
3
4
5
int main() {
int aa[2][5] = {{2, 4, 1, 5, 6},{1,2,3,4,5}};
int (*p)[5] = aa;
cout << *(*p+2)<< endl; // 1
}
  • 第三点,指针访问二维数组任意一个元素的值:

    • 使用列指针:定义一个列指针 p,让它指向二维数组的第 1 个元素。
      • 首先,aa[0] 相当于 int a[5], 则 p=&aa[0][0]相当于 p=a[0];
      • 其次,C 语言中数组是按行优先顺序存储,而 aa[i][j]前面共有 i5+j 个元素,所以该二维数组的任意 i 行 j 列元素可表示为(p+i*5+j)
1
2
3
4
5
int main() {
int aa[2][5] = {{2, 4, 1, 5, 6},{1,2,3,4,5}};
int *p = &aa[0][0];
cout << *(p+1*5+2)<< endl;
}
  • 使用行指针:定义一个行指针 p,让它指向二维数组的第 1 行。
    • 其中* ( *(p+i)+j)表示任意一个 i 行 j 列的元素值, (p+i)可理解为取二维数组中第 i 个元素的值,即 a=int[5],而(a+j)表示一维数组 a 的第 j 个元素的值。
1
2
3
4
5
int main() {
int aa[2][5] = {{2, 4, 1, 5, 6},{1,2,3,4,5}};
int (*p)[5] = aa; //也可以为p=&aa[0];
cout << *(*(p+1)+2)<< endl;
}

char 类型的数组

char类型的数组通常用于存储字符数据。由于一个字符通常占用一个字节(即 8 位),因此char数组可以用于存储较小的数据集,例如字符串、文本文件中的数据等。使用char数组可以有效地利用内存空间,特别是在处理大量文本数据时。

关于使用unsigned。在C++中,unsigned是一种整数类型修饰符,用于表示只能存储非负整数的数据类型。它可以将数据类型的取值范围限制为非负范围,即从02^n-1,其中 n 是该类型的位数。

以下是一些需要使用 unsigned 的情况:

  • 当需要表示的数据范围为非负时,例如计数器、循环索引等。
  • 当需要与无符号整数类型进行算术运算时,例如位运算、移位运算等。
  • 当需要确保变量不会存储负值时,例如在某些算法或数据结构中,负值没有意义或导致错误。
  • 需要注意的是,如果使用 unsigned 修饰符,那么变量只能存储非负整数。如果尝试将负值存储到无符号整数类型中,编译器可能会发出警告或错误。因此,在使用 unsigned 时,需要确保程序中不会出现负值或确保在合适的地方进行了正确的处理。

C++中,char类型通常被视为有符号类型,其取值范围为-128127,无符号整数的取值范围为0255
比如说

1
2
3
4
5
6
7
8
9
10
11
12
// 如果不用unsigned 初始化char s数组的话,s[i] > 127 就会有溢出错误!!!
void init() // KSA初始化S盒
{
unsigned char s[256]; // S盒子
unsigned key_len = secret_key.size();
unsigned char T[256] = {0}; // 临时数组向量
for(unsigned int i = 0; i < 256; i ++ )
{
s[i] = i;
T[i] = secret_key[i % key_len];
}
}