C++数组
一.传递数组给函数
如果您想要在函数中传递一个一维数组作为参数,您必须以下面三种方式来声明函数形式参数,这三种声明方式的结果是一样的,因为每种方式都会告诉编译器将要接收一个整型指针。同样地,您也可以传递一个多维数组作为形式参数。
1.传递数组名
数组名在大多数语境下会隐式转换为指向首元素的指针,因此函数参数可声明为指针类型,接收数组的首地址。
- 函数内部无法直接获取数组长度,需额外传递长度参数。
- 函数内对数组的修改会影响原数组(传递的是地址)。
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. 返回指向数组的指针(最常用)
函数可以返回指向数组首元素的指针,本质是返回数组的地址。
注意:
- 不能返回函数内部局部数组的指针(局部数组在函数结束后会被销毁,指针会变为野指针)。
- 需确保返回的数组在函数外部仍有效(如全局数组、动态分配的数组、静态局部数组)。
示例(返回动态分配的数组):
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]),且返回的数组必须是全局/静态/动态分配的(确保生命周期)。
示例:
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. 传递指向数组的指针(最常用且推荐)
函数参数声明为指向包含固定列数的数组的指针。
- 特点: 必须显式指定列数(即第二维大小),行数可以省略。
// 函数参数: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 实际上被忽略 |
3. 传递引用(必须指定所有维度)
通过数组引用传递,保留数组的所有维度信息。
- 特点: 必须显式指定所有维度的大小。实参的行列数必须与形参完全一致。
// 引用参数必须指定所有维度,这里要求是 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)。
- 注意: 返回的数组必须具有静态或动态分配的生命周期。
// 传统形式:返回类型 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]。
- 注意: 返回的数组必须是全局、静态或动态分配的。
// 静态全局二维数组 |
五.数组初始化
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 为止。
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] = { |
在这个例子中,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] 的数组。 |
| 作用 | 存储多个地址。 | 存储一个完整数组的首地址。 |
简单记忆:
- 指针数组:主体是数组,它装着指针。
- 数组指针:主体是指针,它指向数组。