一.传递数组给函数

如果您想要在函数中传递一个一维数组作为参数,您必须以下面三种方式来声明函数形式参数,这三种声明方式的结果是一样的,因为每种方式都会告诉编译器将要接收一个整型指针。同样地,您也可以传递一个多维数组作为形式参数。

1.传递数组名

数组名在大多数语境下会隐式转换为指向首元素的指针,因此函数参数可声明为指针类型,接收数组的首地址。

  • 函数内部无法直接获取数组长度,需额外传递长度参数。
  • 函数内对数组的修改会影响原数组(传递的是地址)。
    #include <iostream>
    using namespace std;

    // 函数参数为指针(接收数组首地址)和数组长度
    void printArray(int* arr, int length) {
    for (int i = 0; i < length; i++) {
    cout << arr[i] << " ";
    }
    cout << endl;
    }

    int main() {
    int nums[] = {1, 2, 3, 4, 5};
    int len = sizeof(nums) / sizeof(nums[0]); // 计算数组长度
    printArray(nums, len); // 传递数组名和长度
    return 0;
    }

2.指定数组长度的参数形式

函数参数可声明为「数组形式」并指定长度,但编译器会忽略长度,本质还是传递指针。

  • 形式上更直观,但长度声明无实际意义(不会检查数组实际长度)。
  • 仍需手动传递实际长度以避免越界。
    // 形式上声明为数组,实际等同于 int* arr
    void printArray(int arr[5], int length) {
    for (int i = 0; i < length; i++) {
    cout << arr[i] << " ";
    }
    }

    int main() {
    int nums[] = {1, 2, 3};
    int len = 3;
    printArray(nums, len); // 即使数组实际长度≠5,也能正常传递
    return 0;
    }

3. 传递引用

通过数组引用作为参数,可保留数组的长度信息,避免指针转换。
特点

  • 必须显式指定数组长度,且实参的长度必须与形参一致。
  • 函数内可通过 sizeof 获取数组长度(无需额外传递)。
  • 修改会影响原数组。
    // 引用参数必须指定长度(如[5]),实参长度需匹配
    void printArray(int (&arr)[5]) {
    int len = sizeof(arr) / sizeof(arr[0]); // 可直接获取长度(5)
    for (int i = 0; i < len; i++) {
    cout << arr[i] << " ";
    }
    }

    int main() {
    int nums[5] = {1, 2, 3, 4, 5}; // 长度必须为5,否则编译报错
    printArray(nums);
    return 0;
    }

二.从函数返回数组

1. 返回指向数组的指针(最常用)

函数可以返回指向数组首元素的指针,本质是返回数组的地址。
注意

  • 不能返回函数内部局部数组的指针(局部数组在函数结束后会被销毁,指针会变为野指针)。
  • 需确保返回的数组在函数外部仍有效(如全局数组、动态分配的数组、静态局部数组)。

示例(返回动态分配的数组)

#include <iostream>
using namespace std;

// 返回 int 类型指针(指向动态分配的数组)
int* createArray(int size) {
int* arr = new int[size]; // 动态分配数组(堆内存,函数结束后不释放)
for (int i = 0; i < size; i++) {
arr[i] = i * 10;
}
return arr; // 返回指针
}

int main() {
int* ptr = createArray(5); // 接收指针
for (int i = 0; i < 5; i++) {
cout << ptr[i] << " "; // 输出:0 10 20 30 40
}
delete[] ptr; // 必须手动释放,避免内存泄漏
return 0;
}

2. 返回数组的引用(需指定长度)

通过返回数组的引用,可以避免指针的间接性,且保留数组的长度信息(需显式指定)。
特点

  • 必须指定数组长度(如 int (&)[5]),且返回的数组必须是全局/静态/动态分配的(确保生命周期)。

示例

#include <iostream>
using namespace std;

// 全局数组(生命周期与程序一致)
int globalArr[5] = {1, 2, 3, 4, 5};

// 返回数组的引用(必须指定长度5)
int (&getArrayRef())[5] {
return globalArr; // 返回全局数组的引用
}

int main() {
int (&arrRef)[5] = getArrayRef(); // 接收数组引用
for (int num : arrRef) {
cout << num << " "; // 输出:1 2 3 4 5
}
return 0;
}

三. 传递二维数组给函数

传递二维数组时,它同样会退化为指针。但由于二维数组的内存布局特性,除了第一维的大小外,其他所有维的大小都必须在函数参数中指定。这是因为编译器需要知道每行有多少列,才能正确计算内存地址。
一个 的二维数组 arr[M][N] 在退化时,退化为指向包含 个元素的数组的指针,即 int (*arr)[N]

1. 传递指向数组的指针(最常用且推荐)

函数参数声明为指向包含固定列数的数组的指针。

  • 特点: 必须显式指定列数(即第二维大小),行数可以省略。
    #include <iostream>

    // 函数参数:arr 是一个指向包含 3 个 int 元素的数组的指针
    void printMatrix(int (*arr)[3], int rows) {
    // arr[i][j] 的地址计算依赖于列数 3:地址 = arr + i * 3 + j
    for (int i = 0; i < rows; ++i) {
    for (int j = 0; j < 3; ++j) {
    std::cout << arr[i][j] << " ";
    }
    std::cout << std::endl;
    }
    }

    int main() {
    // 2 行 x 3 列
    int matrix[2][3] = {{1, 2, 3}, {4, 5, 6}};

    // 传递数组名(隐式退化为 int (*)[3] 类型的指针)和行数
    printMatrix(matrix, 2);
    return 0;
    }

2. 指定数组形式(必须指定列数)

函数参数可声明为二维数组形式,但必须指定列数。

  • 特点: 形式上更直观,但本质上与 int (*arr)[N] 相同。行数仍会被编译器忽略。
// 形式上声明为 2 行 3 列的数组,但行数 2 实际上被忽略
void printMatrix(int arr[][3], int rows) {
// 访问方式与上例相同
for (int i = 0; i < rows; ++i) {
for (int j = 0; j < 3; ++j) {
std::cout << arr[i][j] << " ";
}
std::cout << std::endl;
}
}

// 调用方式与上例完全相同
// printMatrix(matrix, 2);

3. 传递引用(必须指定所有维度)

通过数组引用传递,保留数组的所有维度信息。

  • 特点: 必须显式指定所有维度的大小。实参的行列数必须与形参完全一致。
    #include <iostream>

    // 引用参数必须指定所有维度,这里要求是 2 行 3 列
    void printMatrixRef(int (&arr)[2][3]) {
    // 可以通过 sizeof 准确获取总大小,但通常仍通过已知的行列数循环
    int rows = sizeof(arr) / sizeof(arr[0]); // 2
    int cols = sizeof(arr[0]) / sizeof(arr[0][0]); // 3

    for (int i = 0; i < rows; ++i) {
    for (int j = 0; j < cols; ++j) {
    std::cout << arr[i][j] << " ";
    }
    std::cout << std::endl;
    }
    }

    int main() {
    int matrix[2][3] = {{10, 20, 30}, {40, 50, 60}};
    // 传递引用
    printMatrixRef(matrix);

    // int wrong_matrix[3][3] = {...}; // 如果传递这个,会引发编译错误,因为维度不匹配
    return 0;
    }

四. 从函数返回二维数组

与一维数组类似,函数不能直接返回整个二维数组,只能返回指向数组的指针引用

1. 返回指向二维数组的指针(必须指定列数)

返回类型是一个指向 列整型数组的指针:int (*)(int N)

  • 注意: 返回的数组必须具有静态或动态分配的生命周期。
    #include <iostream>

    // 传统形式:返回类型 int (*)[3] 直接写在函数名前
    // int (*): 这是一个指针
    // [3]: 这个指针指向一个包含 3 个 int 元素的数组
    int (*createMatrix(int rows))[3] {
    // 动态分配 rows 个包含 3 个 int 元素的数组
    int (*ptr)[3] = new int[rows][3];

    for (int i = 0; i < rows; ++i) {
    for (int j = 0; j < 3; ++j) {
    ptr[i][j] = i * 10 + j;
    }
    }
    return ptr;
    }

    int main() {
    int rows = 3;
    // 接收指针
    int (*matrix_ptr)[3] = createMatrix(rows);

    // 访问
    for (int i = 0; i < rows; ++i) {
    for (int j = 0; j < 3; ++j) {
    std::cout << matrix_ptr[i][j] << " ";
    }
    std::cout << std::endl;
    }

    // 释放动态分配的内存
    delete[] matrix_ptr;
    return 0;
    }

2. 返回二维数组的引用(必须指定所有维度)

返回类型是一个具有特定行列数的数组的引用:int (&)[M][N]

  • 注意: 返回的数组必须是全局、静态或动态分配的。
// 静态全局二维数组
static int static_matrix[2][3] = {{100, 200, 300}, {400, 500, 600}};

// 返回 2 行 3 列二维数组的引用
int (&getStaticMatrixRef())[2][3] {
return static_matrix;
}

// 调用示例:
// int (&ref)[2][3] = getStaticMatrixRef();
// std::cout << ref[1][1] << std::endl; // 输出 500

五.数组初始化

1)一维数组的初始化

1. 完全初始化

提供与数组大小相等或更多的元素。

// 声明大小为 5 的数组,并为每个元素赋值
int a1[5] = {10, 20, 30, 40, 50};
// a1[0]=10, a1[1]=20, ..., a1[4]=50

2. 部分初始化

提供的初始化元素少于数组的大小。

  • 规则: 初始化列表中未指定的元素,会被自动初始化为零值(Zero Initialization)。
    int a2[5] = {10, 20}; 
    // a2[0]=10, a2[1]=20
    // a2[2]=0, a2[3]=0, a2[4]=0 (自动初始化为 0)

3. 隐式大小初始化

声明时不指定大小,让编译器根据初始化列表中的元素数量来确定数组的大小。

int a3[] = {10, 20, 30}; 
// 编译器自动计算:a3 的大小为 3

4. C++11 统一初始化(全部初始化为 0)

使用空的花括号 {} 是将整个数组元素全部初始化为零值的最简洁方式。

int a4[5] = {};   // a4 中的所有 5 个元素都是 0 (C++11/14 标准)
int a5[5] {}; // a5 中的所有 5 个元素都是 0 (C++11 统一初始化语法)

2)二维数组的初始化

二维数组可以被视为“数组的数组”。它的初始化列表是嵌套{} 结构。

1. 完全初始化(嵌套列表)

为每一行(即每个内层数组)提供一个初始化列表。

// 2 行 x 3 列的二维数组
int matrix[2][3] = {
{1, 2, 3}, // 第一行
{4, 5, 6} // 第二行
};
// matrix[0][0]=1, matrix[1][2]=6

2. 简洁的完全初始化(扁平列表)

可以省略内部的花括号,以行优先(Row-major order)的方式顺序提供所有元素。

// 效果与上面完全相同
int matrix2[2][3] = {1, 2, 3, 4, 5, 6};

3. 部分初始化(零值填充)

与一维数组类似,未明确赋值的元素将自动初始化为零值。这在二维数组中尤其有用。

  • 规则: 按照行优先的顺序进行填充。
    int matrix3[2][3] = {
    {1, 2}, // 第一行只初始化了前两个元素,matrix3[0][2] 为 0
    {4} // 第二行只初始化了第一个元素,matrix3[1][1] 和 matrix3[1][2] 为 0
    };
    /* 结果:
    1, 2, 0
    4, 0, 0
    */

4. 隐式行数初始化(必须指定列数)

在定义二维数组时,必须指定列数(第二维大小),但可以省略行数(第一维大小),让编译器根据初始化列表推导。

// 省略行数,列数必须是 3
int matrix4[][3] = {
{1, 2, 3},
{4, 5, 6}
};
// 编译器推导 matrix4 是 2 行 3 列的数组

六.字符数组

1)字符数组的基础概念

1. 定义与初始化

字符数组和普通数组一样,可以显式指定大小或让编译器推导。它的元素类型是 char

方式一:作为普通数组初始化
你可以像初始化任何其他类型的数组一样,初始化字符数组。

char char_array[5] = {'H', 'e', 'l', 'l', 'o'};
// char_array 的长度是 5。
// 注意:这个数组不是一个 C 风格字符串,因为它缺少空终止符。

方式二:使用字符串字面量初始化(最常用)
这是最常见且最简洁的初始化方式。当你使用双引号 " " 时,编译器会自动在数组末尾添加一个特殊的空终止符(Null Terminator),即 \0

char c_string_array[] = "Hello";
// 编译器自动推导大小为 6:H, e, l, l, o, \0
// c_string_array 是一个有效的 C 风格字符串。

方式三:部分初始化
如果数组大小足够大,未被字符字面量填充的部分会被自动初始化为 \0

char large_array[10] = "Hi";
// 元素为:'H', 'i', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0'

2)字符数组的特殊性:C 风格字符串

字符数组的特殊性在于它与 C 风格字符串的紧密关系。

1. 空终止符 (\0)

C++(和 C 语言)中的字符串是通过一系列字符后跟一个值为 0 的字符(即空终止符 \0)来表示的。

  • 所有处理 C 风格字符串的标准库函数(如 strlen, strcpy, cout << 等)都依赖于这个 \0 来确定字符串的结束位置

2. 打印行为

当你将一个字符数组名传递给 std::cout 时,std::cout 会将其视为一个 C 风格字符串的首地址,并从该地址开始,连续打印字符,直到遇到第一个 \0 为止

#include <iostream>

int main() {
char s1[] = "C++"; // 'C', '+', '+', '\0'
char s2[4] = {'A', 'B', 'C', 'D'}; // 没有 \0!

std::cout << "S1: " << s1 << std::endl; // 输出: C++

// 危险操作:cout 找不到 \0,它会继续打印内存中的数据,直到偶然遇到 \0 或程序崩溃。
// std::cout << "S2: " << s2 << std::endl;

return 0;
}

3. C 字符串处理函数(需要 <cstring> 头文件)

C++ 继承了 C 语言中用于处理字符数组的函数,它们都依赖于 \0

函数 描述 示例
strlen() 计算字符串的有效长度(不包括 \0)。 strlen("Hello") 返回 5。
strcpy() 复制字符串。 strcpy(dest, source)
strcat() 连接字符串。 strcat(dest, source)
strcmp() 比较两个字符串。 strcmp(s1, s2) 返回 0 表示相等。

3)现代 C++ 推荐:std::string

由于字符数组(C 风格字符串)存在上述诸多安全隐患和操作不便,现代 C++ 强烈推荐使用 <string> 头文件中的 std::string来处理字符串。

  • std::string 能够自动管理内存,无需手动处理 \0
  • 它支持安全的赋值、连接(使用 + 运算符)和大小查询(使用 .length().size())。

七.指针数组 详解

指针数组是一种复合数据类型,顾名思义,它是一个数组,而这个数组的每个元素都是一个指针

它与“数组指针”是两个完全不同的概念,在 C++ 中需要严格区分。

1. 指针数组的定义和结构

A. 定义格式

类型* 数组名[大小];

B. 结构解析

部分 描述
[大小] 数组定义,说明这是一个具有固定数量元素的集合。
* 星号,说明这个数组的元素是指针类型。
类型 指针所指向的数据类型。

示例:

int* arr[10]; // 这是一个包含 10 个元素的数组,
// 数组中的每个元素都是一个指向 int 类型的指针(int*)。

2. 指针数组的用途

指针数组最主要的用途是存储一组地址

用途一:存储多个字符串(字符串数组)

在 C/C++ 中,字符串实际上是字符数组。指针数组常用于存储多个 C 风格字符串(即 const char*)。

const char* names[4] = {
"Alice", // names[0] 存储了字符串 "Alice" 的首地址
"Bob", // names[1] 存储了字符串 "Bob" 的首地址
"Charlie",
"David"
};

// 访问第二个字符串:
std::cout << names[1] << std::endl; // 输出: Bob

// 访问第二个字符串的第一个字符:
std::cout << names[1][0] << std::endl; // 输出: B

在这个例子中,names 数组的每个元素都是一个指针,指向内存中不同位置的常量字符串字面量。

用途二:存储多个变量的地址

你可以用指针数组来组织和管理多个独立变量的地址。

int a = 10;
int b = 20;
int c = 30;

// 定义一个包含 3 个 int* 类型元素的数组
int* ptrs[3];

ptrs[0] = &a; // 存储变量 a 的地址
ptrs[1] = &b; // 存储变量 b 的地址
ptrs[2] = &c; // 存储变量 c 的地址

// 通过解引用指针来访问和修改变量值
*ptrs[1] = 200; // 相当于 b = 200;

std::cout << "a: " << *ptrs[0] << std::endl; // 输出: 10
std::cout << "b: " << b << std::endl; // 输出: 200

3. 与数组指针的区别

特性 指针数组 (Array of Pointers) 数组指针 (Pointer to an Array)
定义格式 int* arr[10]; int (*ptr_arr)[10];
本质 是一个数组,元素是指针。 是一个指针,指向一个数组。
运算符优先级 优先级:[] > *。先与 arr 结合成数组,再确定元素是 int* 优先级:() > [] > **ptr_arr 被括号包围,先与 * 结合,确定 ptr_arr 是指针,再确定它指向 [10] 的数组。
作用 存储多个地址。 存储一个完整数组的首地址。

简单记忆:

  • 指针数组:主体是数组,它装着指针
  • 数组指针:主体是指针,它指向数组