从零到一学习c++(基础篇--筑基期五-数组、指针)

 从零到一学习C++(基础篇) 作者:羡鱼肘子

 温馨提示1:本篇是记录我的学习经历,会有不少片面的认知,万分期待您的指正。

 温馨提示2:本篇会尽量避免一些术语,尽量用更加通俗的语言介绍c++的基础,但术语也是很重要的。

 温馨提示3:看本篇前可以先了解前篇的内容,知识体系会更加完整哦。

从零到一学习c++(基础篇--筑基期四-auto、decltype)-优快云博客

数组 

数组(Array)是编程中最基础的数据结构之一,用于存储一组相同类型的元素。它在内存中是连续存储的,通过索引快速访问。

一、 数组的基本概念

  • 定义:数组是连续内存空间中存储的相同数据类型元素的集合。

  • 特点

    • 元素类型相同,内存连续。

    • 大小在声明时确定,不可动态改变(静态数组)。

    • 通过下标(索引)访问元素,下标从 0 开始。

二、数组的声明与初始化

1. 声明语法
数据类型 数组名[常量表达式]; // 常量表达式指定数组长度

示例

int arr[5];      // 声明一个包含5个int元素的数组
float scores[10]; // 声明10个float元素的数组
2. 初始化方式
  • 完全初始化:显式指定所有元素。

    int arr[3] = {1, 2, 3}; // arr[0]=1, arr[1]=2, arr[2]=3
  • 部分初始化:未显式初始化的元素为默认值(如 int 默认0)。

    int arr[5] = {1, 2}; // arr[0]=1, arr[1]=2, 其余为0
  • 自动推断长度:省略大小,编译器根据初始化列表确定长度。

    int arr[] = {4, 5, 6}; // 数组长度自动推断为3
3.字符数组 
  • 大小:数组能存储的字符数量(包括末尾的 \0)。

  • 示例

    char name[10]; // 声明一个可以存储9个字符+1个'\0'的数组
  •  直接赋值

    char name[] = "Tom"; // 编译器会自动加上'\0'

    等价于:

    char name[] = {'T', 'o', 'm', '\0'};
  • 逐个字符赋值

    char name[4];
    name[0] = 'T';
    name[1] = 'o';
    name[2] = 'm';
    name[3] = '\0'; // 必须手动添加'\0'
  • 温馨提示:数组不能拷贝和赋值

  •  
4.如何实现数组的拷贝

 虽然数组不能直接拷贝或赋值,但可以通过以下方式实现数组内容的复制:

1. 使用循环逐个复制
int arr1[5] = {1, 2, 3, 4, 5};
int arr2[5];

for (int i = 0; i < 5; i++) {
    arr2[i] = arr1[i]; // 逐个复制
}
2. 使用 memcpy 函数
  • memcpy 是C标准库中的函数,用于复制内存块。

  • 需要包含头文件 <cstring>

#include <cstring>

int arr1[5] = {1, 2, 3, 4, 5};
int arr2[5];

memcpy(arr2, arr1, sizeof(arr1)); // 复制arr1的内容到arr2
3. 使用 std::copy 函数
  • C++标准库提供了 std::copy,可以更安全地复制数组。

  • 需要包含头文件 <algorithm>

#include <algorithm>

int arr1[5] = {1, 2, 3, 4, 5};
int arr2[5];

std::copy(arr1, arr1 + 5, arr2); // 复制arr1的内容到arr2
5.为什么 std::array 可以拷贝和赋值?

C++11 引入了 std::array,它是一个封装了固定大小数组的类模板。与普通数组不同,std::array 支持拷贝和赋值,因为它是一个对象,而不是一个指针。

示例:

#include <array>

std::array<int, 5> arr1 = {1, 2, 3, 4, 5};
std::array<int, 5> arr2;

arr2 = arr1; // 正确!std::array 支持赋值
6.如何去理解复杂的数组声明

在C++中,复杂的数组声明可能会让人感到困惑,尤其是当数组与指针、函数、多维数组等结合时。我们可以使用一种系统化的方法来解析它们。以下是一些技巧和步骤,帮助理解复杂的数组声明。

基本原则
  1. 从内到外:从变量名开始,逐步向外解析。

  2. 优先级规则

    • 括号 () 优先级最高。

    • 数组 [] 和函数 () 的优先级高于指针 *

  3. 结合性:数组和函数是左结合的(从左到右解析),指针是右结合的(从右到左解析)。

解析复杂声明的步骤
  1. 找到变量名:从变量名开始,这是声明的核心。

  2. 解析右侧:先看变量名右侧的符号(如 [] 或 ())。

  3. 解析左侧:再看变量名左侧的符号(如 * 或类型)。

  4. 递归解析:如果遇到括号,递归解析括号内的内容。

看几个栗子

1. 简单数组

int arr[10];
  • 解析

    • arr 是一个数组,大小为10,元素类型是 int

  • 含义arr 是一个包含10个整数的数组。


2. 指针数组

int* arr[10];
  • 解析

    • arr 是一个数组,大小为10。

    • 数组的元素类型是 int*(指向整数的指针)。

  • 含义arr 是一个包含10个指向整数的指针的数组。


3. 数组指针

int (*arr)[10];

  • 解析

    • arr 是一个指针。

    • 指针指向的类型是 int[10](包含10个整数的数组)。

  • 含义arr 是一个指向包含10个整数的数组的指针。


4. 函数指针数组

int (*funcArr[10])(int, int);

  • 解析

    • funcArr 是一个数组,大小为10。

    • 数组的元素类型是 int (*)(int, int)(指向函数的指针)。

    • 函数指针指向的函数接受两个 int 参数,返回 int

  • 含义funcArr 是一个包含10个函数指针的数组,每个函数指针指向一个接受两个 int 参数并返回 int 的函数。


5. 多维数组

int arr[3][4];

  • 解析

    • arr 是一个数组,大小为3。

    • 数组的元素类型是 int[4](包含4个整数的数组)。

  • 含义arr 是一个3行4列的二维数组。


6. 指向多维数组的指针

int (*arr)[3][4];

  • 解析

    • arr 是一个指针。

    • 指针指向的类型是 int[3][4](3行4列的二维数组)。

  • 含义arr 是一个指向3行4列的二维数组的指针。


7. 返回数组指针的函数

int (*func(int))[10];

  • 解析

    • func 是一个函数,接受一个 int 参数。

    • 函数的返回类型是 int (*)[10](指向包含10个整数的数组的指针)。

  • 含义func 是一个函数,接受一个 int 参数,返回一个指向包含10个整数的数组的指针。


7.使用 typedef 简化复杂声明

为了简化复杂的声明,可以使用 typedef 定义类型别名。

示例:

typedef int (*FuncPtr)(int, int); // 定义函数指针类型
FuncPtr funcArr[10];              // 声明函数指针数组
  • 含义funcArr 是一个包含10个函数指针的数组,每个函数指针指向一个接受两个 int 参数并返回 int 的函数。

三、访问数组元素

在C++中,访问数组元素是通过下标(索引)来实现的。数组的下标从 0 开始,到

数组长度 - 1 结束。

1.访问数组元素的基本语法
数组名[下标];
  • 下标:必须是整数类型(如 int),表示要访问的元素位置。

  • 范围下标从 0 开始,最大为 数组长度 - 1


2.一维数组的访问
1. 示例代码
int arr[5] = {10, 20, 30, 40, 50};

// 访问数组元素
cout << arr[0] << endl; // 输出 10(第一个元素)
cout << arr[2] << endl; // 输出 30(第三个元素)
arr[3] = 100;           // 修改第四个元素为 100
cout << arr[3] << endl; // 输出 100
2. 遍历数组

使用循环遍历数组中的所有元素:

for (int i = 0; i < 5; i++) {
    cout << arr[i] << " "; // 依次输出 10 20 30 100 50
}

3.多维数组的访问

多维数组(如二维数组)的访问需要多个下标。

1. 二维数组示例
int matrix[2][3] = {
    {1, 2, 3},
    {4, 5, 6}
};

// 访问二维数组元素
cout << matrix[0][1] << endl; // 输出 2(第一行第二列)
matrix[1][2] = 10;            // 修改第二行第三列的元素为 10
cout << matrix[1][2] << endl; // 输出 10
2. 遍历二维数组

使用嵌套循环遍历二维数组:

for (int i = 0; i < 2; i++) {       // 遍历行
    for (int j = 0; j < 3; j++) {   // 遍历列
        cout << matrix[i][j] << " ";
    }
    cout << endl; // 换行
}

输出:

1 2 3
4 5 10

4.字符数组的访问

字符数组(C风格字符串)的访问方式与普通数组类似,但需要注意字符串以 \0 结尾。

1. 示例代码
char name[] = "Hello";

// 访问字符数组元素
cout << name[0] << endl; // 输出 'H'
name[1] = 'a';           // 修改第二个字符为 'a'
cout << name << endl;    // 输出 "Hallo"
2. 遍历字符数组

使用循环遍历字符数组:

for (int i = 0; name[i] != '\0'; i++) {
    cout << name[i] << " "; // 依次输出 H a l l o
}

5.动态数组的访问

动态数组(通过 new 分配内存)的访问方式与普通数组相同。

1. 示例代码
int* arr = new int[5]{10, 20, 30, 40, 50};

// 访问动态数组元素
cout << arr[2] << endl; // 输出 30
arr[3] = 100;           // 修改第四个元素为 100
cout << arr[3] << endl; // 输出 100

delete[] arr; // 释放内存
2. 遍历动态数组
for (int i = 0; i < 5; i++) {
    cout << arr[i] << " "; // 依次输出 10 20 30 100 50
}

6.注意事项
  1. 下标越界
    访问超出数组范围的下标会导致未定义行为(如程序崩溃或数据损坏)。
    • 错误示例

      int arr[3] = {1, 2, 3};
      cout << arr[3]; // 错误!下标越界
  2. 字符数组的 \0
    字符数组的末尾必须有 ‘\0',否则可能导致访问错误。
  3. 动态数组的内存管理
    动态数组使用后必须手动释放内存(delete[]),否则会导致内存泄漏。

7.小结
  • 访问数组元素的基本语法是 数组名[下标]

  • 一维数组、多维数组、字符数组和动态数组的访问方式类似。

  • 注意下标越界问题,确保访问的下标在合法范围内。

四、数组和指针的关系 

1.数组名是指针

在C++中,数组名实际上是一个指向数组第一个元素的指针。也就是说,数组名存储的是数组首元素的地址。

示例
int arr[5] = {10, 20, 30, 40, 50};
  • arr 是一个数组名,它的类型是 int*(指向 int 的指针)。

  • arr 的值是 &arr[0],即数组第一个元素的地址。

2.通过指针访问数组元素

既然数组名是指针,我们可以通过指针的方式来访问数组元素。

1. 使用数组下标访问
cout << arr[2]; // 输出30
  • arr[2] 等价于 *(arr + 2)

2. 使用指针算术访问
cout << *(arr + 2); // 输出30
  • arr + 2:指针向后移动2个位置,指向 arr[2]

  • *(arr + 2):访问指针指向的值。

3.指针和数组的等价性

在某些情况下,指针和数组可以互换使用。

1. 数组名作为指针
int* p = arr; // p 指向数组的第一个元素
cout << p[1]; // 输出20
cout << *(p + 1); // 输出20
2. 指针作为数组名
int* p = new int[5]{1, 2, 3, 4, 5}; // 动态数组
cout << p[2]; // 输出3
delete[] p; // 释放内存
4.数组名和指针的区别

虽然数组名和指针有很多相似之处,但它们也有一些关键区别。

1. 数组名是常量指针
  • 数组名是一个常量指针,不能修改它的值。

    int arr[5] = {1, 2, 3, 4, 5};
    arr = arr + 1; // 错误!数组名不能修改
2. 指针是变量
  • 指针是一个变量,可以修改它的值。

    int* p = arr;
    p = p + 1; // 正确!指针可以修改
3. sizeof 的行为不同
  • 对数组名使用 sizeof,返回整个数组的大小。

  • 对指针使用 sizeof,返回指针本身的大小。

    int arr[5] = {1, 2, 3, 4, 5};
    int* p = arr;
    
    cout << sizeof(arr); // 输出20(5个int,每个4字节)
    cout << sizeof(p);   // 输出8(指针的大小,64位系统)int arr[5] = {1, 2, 3, 4, 5};
    int* p = arr;
    
    cout << sizeof(arr); // 输出20(5个int,每个4字节)
    cout << sizeof(p);   // 输出8(指针的大小,64位系统)

5.指针和数组在函数中的使用
1. 传递数组给函数

数组名是指针,传递数组给函数时,实际传递的是指针。

void printArray(int* arr, int size) {
    for (int i = 0; i < size; i++) {
        cout << arr[i] << " ";
    }
}

int main() {
    int arr[5] = {1, 2, 3, 4, 5};
    printArray(arr, 5); // 传递数组名(指针)
}
2. 返回指针的函数

函数可以返回指针,但要注意指针指向的内存是否有效。

int* getPointer() {
    static int num = 42; // 静态变量,内存不会释放
    return &num;
}

int main() {
    int* p = getPointer();
    cout << *p; // 输出42
}

 下一篇:我会学习标准库类型:string,让我们一起加油!!!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

愚戏师

多谢道友

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值