九日集训day3数组_pt1 心猿意马优快云
数组知识快读盘点
以下是针对数组知识的Markdown文本的优化版本,旨在提高可读性和信息的清晰度:
基础知识:数组
顺序存储结构
数组是一种顺序存储结构,可用于存储相同类型的多个元素。数组可以完全初始化,也可以部分初始化,如下所示:
- 完全未初始化:
int a[8];
- 完全初始化:
int a[5] = {1, 2, 3, 4, 5};
- 部分初始化:
int a[10] = {1, 2, 3, 4, 5};
- 自动推断长度:
int a[] = {1, 2, 3, 4, 5};
(长度为5)
长度、容量和索引
- 长度:数组当前包含的元素数量。
- 容量:数组最大能存储的元素数量,定义时确定。
- 索引:数组元素的索引从0开始。
注意事项
- 数组溢出:数组索引超出其定义范围会导致运行时错误,可能引发不可预料的问题,因此必须避免。
- 数据类型:数组可以存储不同的数据类型,包括但不限于:
int
:32位整型float
:单精度浮点型double
:双精度浮点型char
:字符型long long
:64位整型short
:16位短整型
数组的函数传参
-
add(int num[20])
:此声明方式明确数组的大小为20,但在传递给函数时,这种大小信息并不强制要求数组必须有20个元素,它主要用于文档上的指示,说明期望的数组大小。 -
add(int num[])
:此声明没有指定数组大小,这在函数内部意味着num
仅是一个指向整型的指针。 -
add(int *num)
:此方式直接将数组作为指针传递,这是更明确地表明数组被视为一个指针。
本质分析
所有这三种方式的本质都是通过指针传递数组的首地址。C语言中数组名本质上是一个指向数组第一个元素的指针。因此,无论哪种形式,函数内部接收到的都是指向原始数组的指针,而非数组的复制。这意味着在函数内对数组元素的任何修改都会影响到原始数组。
能否改变原数组?
由于所有这三种传递方式本质上都是传递数组的地址,所以在函数内对数组的修改都会反映到原数组上。
示例代码
#include <stdio.h>
// 使用具体数组大小
void addSize(int num[20]) {
int sum = 0;
for (int i = 0; i < 10; i++) { // 只累加前10个元素
sum += num[i];
}
printf("通过指定大小累加的结果:%d\n", sum);
}
// 使用不确定大小的数组
void add(int num[]) {
int sum = 0;
for (int i = 0; i < 10; i++) { // 只累加前10个元素
sum += num[i];
}
printf("通过未指定大小累加的结果:%d\n", sum);
}
// 使用指针
void addPtr(int *num) {
int sum = 0;
for (int i = 0; i < 10; i++) { // 只累加前10个元素
sum += num[i];
}
printf("通过指针累加的结果:%d\n", sum);
}
int main() {
int numbers[20] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; // 只初始化前10个元素
addSize(numbers);
add(numbers);
addPtr(numbers);
return 0;
}
演示三种传参方式能否影响到原数组:
#include <stdio.h>
// 使用具体数组大小
void modifySize(int num[20]) {
for (int i = 0; i < 10; i++) { // 修改前10个元素
num[i] += 5; // 每个元素增加5
}
}
// 使用不确定大小的数组
void modify(int num[]) {
for (int i = 0; i < 10; i++) { // 修改前10个元素
num[i] += 10; // 每个元素增加10
}
}
// 使用指针
void modifyPtr(int *num) {
for (int i = 0; i < 10; i++) { // 修改前10个元素
num[i] += 15; // 每个元素增加15
}
}
void printArray(int num[], int size) {
for (int i = 0; i < size; i++) {
printf("%d ", num[i]);
}
printf("\n");
}
int main() {
int numbers[20] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; // 只初始化前10个元素
printf("原始数组: ");
printArray(numbers, 20);
modifySize(numbers);
printf("使用具体大小修改后: ");
printArray(numbers, 20);
modify(numbers);
printf("使用未指定大小修改后: ");
printArray(numbers, 20);
modifyPtr(numbers);
printf("使用指针修改后: ");
printArray(numbers, 20);
return 0;
}
演示结果:
tips:为什么后面都打印的是0?明明没有进行初始化啊?
在C语言中,当你声明一个局部数组而没有完全初始化它时,未初始化的元素的值是未定义的。这意味着它们可能包含任何值,通常是内存中已存在的值。然而,在大多数现代编译器中,当数组被部分初始化时,未明确初始化的元素通常会默认初始化为0。这是为了提供一定的安全性和可预测性,但这种行为并非C标准所保证的。
在你的示例代码中,你初始化了数组 numbers
的前10个元素,后10个元素没有被显式初始化。由于在很多实现中,局部数组的未初始化元素会默认为0,这就是为什么这些元素在打印时显示为0的原因。然而,依赖于此行为可能不是一个好的编程实践,因为它依赖于具体的编译器行为,可能在不同的编译器或设置中得到不同的结果。
在C语言中,确保一个函数不修改传入的数组可以通过两种主要方法实现:
使用 const
限定符
使用 const
关键字是确保函数不修改传入数组的标准方法。这个关键字可以应用于函数的参数,以表明该参数不应被修改。例如:
void processArray(const int *array, int size) {
// 这里尝试修改 array 的内容将会导致编译错误
for (int i = 0; i < size; i++) {
printf("%d ", array[i]);
}
printf("\n");
}
在这个例子中,const int *array
表示 array
是一个指向 int
的指针,其中的 int
值不能被修改。如果函数试图修改 array
指向的内容,比如通过 array[i] = value;
,编译器将报错,因为这违反了 const
限定。
复制数组
另一种方法是在函数内部创建数组的副本,然后对副本进行操作,这样原始数组的值不会被改变:
void processArray(int array[], int size) {
int localArray[20]; // 假设知道数组不会超过20个元素
for (int i = 0; i < size; i++) {
localArray[i] = array[i]; // 复制数组元素
}
// 在 localArray 上进行操作
}
在这种情况下,即使 processArray
函数修改了 localArray
,原始的 array
也不会受到影响,因为仅仅修改了它的副本。
注意
在C++中,你提到的“引用”也是一种传递参数的方式,它可以保证引用的对象不被修改(如果使用 const
)。例如:
void processArray(const int (&array)[20]) {
// 这里无法修改 array 的内容
}
这种方式在C++中非常有用,它通过引用传递数组,并通过 const
保证不修改数组。但请注意,C语言中没有引用的概念;引用是C++特有的。
如何自动确定数组的大小?
在C语言和C++中,数组一旦传递给函数,就会退化成指针,失去其长度信息。这意味着无法直接在函数中确定数组的长度。但是,我们可以使用几种技巧来解决这个问题,具体方法取决于你的编程环境是C还是C++。
C语言
在C语言中,通常需要手动传递数组的长度作为一个额外的参数给函数。这是最常见的做法,因为C语言本身没有内建的方式来检查数组的长度。
#include <stdio.h>
void printArrayLength(int *array, int length) {
printf("Length of the integer array is: %d\n", length);
}
void printCharArrayLength(char *array, int length) {
printf("Length of the char array is: %d\n", length);
}
int main() {
int intArray[] = {1, 2, 3, 4, 5};
char charArray[] = "Hello";
// 注意,对于charArray,strlen可以用来获取字符串长度,不包括null字符
printArrayLength(intArray, sizeof(intArray) / sizeof(intArray[0]));
printCharArrayLength(charArray, sizeof(charArray) - 1);
return 0;
}
C++
C++ 提供了一些更灵活的方式来处理这个问题,特别是模板和 std::array
。使用模板,你可以创建一个函数,它可以推导数组的长度。
使用模板
#include <iostream>
template<typename T, size_t N>
void printArrayLength(T(&array)[N]) {
std::cout << "Length of the array is: " << N << std::endl;
}
int main() {
int intArray[] = {1, 2, 3, 4, 5};
char charArray[] = "Hello";
printArrayLength(intArray);
printArrayLength(charArray); // 这里会包括null终止符
return 0;
}
使用 std::array
如果你使用C++11或更高版本,可以使用 std::array
,它保留了数组的长度信息。
#include <iostream>
#include <array>
int main() {
std::array<int, 5> intArray = {1, 2, 3, 4, 5};
std::array<char, 6> charArray = {'H', 'e', 'l', 'l', 'o', '\0'};
std::cout << "Length of the integer array is: " << intArray.size() << std::endl;
std::cout << "Length of the char array is: " << charArray.size() - 1 << std::endl;
return 0;
}
在这些示例中,使用C++的模板和 std::array
可以使代码更加安全和易于管理,同时还提供了数组长度的自动推导,这是在纯C语言中无法实现的。对于C++,优先选择使用这些现代特性,特别是当安全和易用性是关键考虑因素时。
sizeof的注意
这里我们稍微修改了代码让大家知道为什么不能这么写sizeof
#include <stdio.h>
void printArrayLength(int *array, int length) {
printf("Length of the integer array is: %d\n", sizeof array);
}
void printCharArrayLength(char *array, int length) {
printf("Length of the char array is: %d\n", sizeof array);
}
int main() {
int intArray[] = {1, 2, 3, 4, 5};
char charArray[] = "Hello";
// 注意,对于charArray,strlen可以用来获取字符串长度,不包括null字符
printArrayLength(intArray, sizeof(intArray) / sizeof(intArray[0]));
printCharArrayLength(charArray, sizeof(charArray) - 1);
return 0;
}
打印出来为什么是8?
因为这里打印的是array指针的大小,通常为8个字节。即使把int *array换成int *array[]都是一样的,这点要注意。
在主函数里面使用的sizeof
#include <stdio.h>
void printArrayLength(int *array, int length) {
printf("Length of the integer array is: %d\n", length);
}
void printCharArrayLength(char *array, int length) {
printf("Length of the char array is: %d\n", length);
}
int main() {
int intArray[] = {1, 2, 3, 4, 5};
char charArray[] = "Hello";
// 注意,对于charArray,strlen可以用来获取字符串长度,不包括null字符
printArrayLength(intArray, sizeof(intArray) / sizeof(intArray[0]));
printCharArrayLength(charArray, sizeof(charArray) - 1);
return 0;
}
为什么这里正确的打印方法是sizeof(intArray) / sizeof(intArray[0])?
在C语言中,sizeof
运算符用于获取内存中数据类型或数据结构所占的字节数。当 sizeof
用于数组时,它返回整个数组所占用的字节数。要获取数组中元素的数量,需要将数组的总字节大小除以单个元素的字节大小。
解释如下:
数组的总大小
sizeof(intArray)
返回整个intArray
的大小(以字节为单位)。因为intArray
是一个由5个整数组成的数组,而每个整数通常在C语言中占用4字节(这取决于编译器和平台),所以整个数组的大小通常是5 * 4 = 20
字节。
单个元素的大小
sizeof(intArray[0])
返回数组中单个元素的大小。在这个例子中,intArray[0]
是intArray
中的第一个整数,所以sizeof(intArray[0])
是4字节。
计算数组长度
- 通过
sizeof(intArray) / sizeof(intArray[0])
,你实际上是在计算20 / 4 = 5
,这正是数组中元素的数量。
这种方法可以用于任何类型的数组(不仅仅是整数)来准确地计算数组中的元素数量,只要你知道数组的总大小和单个元素的大小。这是在C语言中处理数组长度的常见技巧,特别是在数组作为参数传递给函数时,因为传递的数组会退化为指针,失去其原有的大小信息。
主函数 & 传参的数组名有什么区别让 sizeof 的运用都不同了?
在C语言中,当你在函数外部声明并初始化一个数组,如 int intArray[] = {1, 2, 3, 4, 5};
,intArray
在这个上下文中确实是一个数组,不是一个指针。因此,使用 sizeof
运算符于 intArray
时,它计算的是整个数组的大小,而不是指针的大小。
这里的关键在于 intArray
的上下文和用法。在声明时它是一个具有完整尺寸信息的数组。这意味着编译器知道这个数组包含多少个元素,以及每个元素的大小。因此,sizeof(intArray)
会返回所有这些整数元素占用的总字节数。
指针的情况
当数组作为参数传递给函数时,数组名称(如 intArray
)会退化为指向数组首元素的指针。在函数内部,这个“数组”实际上已经是一个指针了,因此如果在函数内部对同名参数使用 sizeof
,结果将是指针的大小,而不是原始数组的总大小。
例如,考虑以下函数定义:
void exampleFunction(int arr[]) {
// 这里 sizeof(arr) 将返回指针的大小,而非数组总大小。
}
如果在 main
函数或其它任何数组被定义为数组的地方使用 sizeof(intArray)
,你得到的是整个数组的大小。但在如上的函数内部使用 sizeof(arr)
,你会得到的是指向 int
的指针的大小(通常是4或8字节,依据平台和编译器的不同)。