拓冰建站拓冰建站
首页 / 资讯中心 / 正文

数组底层逻辑与高频操作:从内存模型到跨语言实践

1. 数组的内存模型指针、多维与动态数组背后的同一套逻辑如果你打开搜索热词列表会发现数组初始化指针数组C多维数组指针动态数组数组长度计算这些词常年挂在榜单上。看起来是五个问题实际上它们全部指向同一件事——数组在内存里到底是什么。把这个底层逻辑想透上述问题至少能解决八成。先看最核心的概念数组是一段连续内存上的一组同类型元素。这句话里连续二字是大道至简的根源。它决定了数组名能当指针用、决定了多维数组在内存里其实是线性排列的、也决定了为什么动态数组需要手动管理。我用储物柜来类比数组就是一排编号连续的储物柜柜号是索引柜子里的东西是元素。你要找第3个柜子不需要从第1个开始数直接按地址偏移就能跳过去这就是O(1)随机访问的由来。1.1 数组名的双重身份是地址但不完全是指针C语言里数组名最让人迷惑的一点它像指针又不是指针。看这个经典例子int arr[4] {1, 2, 3, 4}; printf(%zu\n, sizeof(arr)); // 16整个数组占用的字节数但如果把arr传进函数void print_array(int a[]) { printf(%zu\n, sizeof(a)); // 864位系统上指针的大小 }同样一个arrsizeof结果完全不同。原因是函数参数声明int a[]时编译器把它当作int *a处理数组退化成指针——它只保留首元素地址丢失了长度信息。这就是数组长度计算这个热词的根本痛点你以为传过去的是一个完整的数组实际收到的只是一个地址。解决办法只有一个把长度作为单独参数传进去或者用C的std::array、std::vector它们把长度封进了类型里。数组名本身还存在三种特殊语义arr是首元素地址arr是整个数组的地址类型是int (*)[4]而arr1跳4个字节arr1跳16个字节。不理解这三者的区别后续学指针就永远是浆糊状态。1.2 指针数组与数组指针名字只差两个字含义完全不同指针数组和数组指针是C/C初学者必踩的坑我每次都要画图才讲得清。先看声明int *iptrs[3]; // 指针数组这是一个数组里面装了3个 int* int (*ptr)[4]; // 数组指针这是一个指针它指向长度为4的int数组判断方法是看变量名先和谁结合。int *iptrs[3]中iptrs先和[3]结合所以它是一个数组数组元素类型是int *常用于保存多段字符串的起始地址(*ptr)先和*结合说明ptr首先是指针然后它指向的类型是int[4]这在处理二维数组的函数参数时非常有用。实践中最常见的场景是把二维数组传给函数。假设有int matrix[3][4]函数形参应该这样声明void process(int (*p)[4], int rows); // p是一个指向每行有4个int的指针调用时直接传process(matrix, 3)。如果不这样写写成void process(int **p)编译器会直接报错——二维数组名并不等价于int**因为它指向的元素类型是长度为4的int数组而不是int指针。1.3 多维数组在内存中的真实布局很多人以为二维数组在内存里是棋盘实际它是一长条。int matrix[3][4]在内存里就是12个int首尾相连按行优先存放第一行4个int第二行4个int依次排开。这也解释了一个常见问题matrix[i][j]为什么能直接定位因为它的地址就是base (i * 4 j) * sizeof(int)编译器帮你做了线性坐标换算。这个线性存储的特性在图像处理、矩阵乘法、神经网络权重存储里极其关键。比如OpenCV的Mat对象本质就是一块连续内存加宽高信息numpy数组的shape和strides也是在描述这块连续内存如何被切分和跳步。理解了多维数组的线性本质你就能明白为什么numpy三维数组相乘需要谈reshape和轴对齐也能看懂为什么C里把三维数组传给函数时除了第一维以外的所有维度都必须写死void process_3d(int a[2][3][4]); // 只有第一维可以不写1.4 动态数组内存模型在运行时版动态数组热词的背后是运行前不知道要多大这个现实需求。C语言的做法是malloc配合realloc手动扩容这是最原始但也是理解内存模型最好的教科书int *arr NULL; size_t capacity 0; size_t len 0; void append(int val) { if (len capacity) { capacity capacity ? capacity * 2 : 4; arr realloc(arr, capacity * sizeof(int)); // 可能会搬移到新地址 } arr[len] val; }这里最关键的坑是realloc可能返回一块全新的内存地址会把原内容拷贝过去并释放旧内存。因此绝对不能让多个指针指向同一个realloc出来的地址后不做更新否则就会出现悬垂指针。C中的动态数组有两层裸的new[]配合delete[]以及我们真正该用的std::vector。vector内部就是上面这段逻辑的高度封装push_back触发的扩大会按约为2倍的因子增加容量均摊下来每次插入的代价是O(1)但这个增长多少倍在不同标准库实现里有细微差别不用背数字知道它是成倍扩张即可。在代码里优先使用容器而不是裸指针管理动态数组是现代C的基本素养。2. 初始化这门新手课规则、字符数组与宏定义暗藏的老手分水岭数组初始化规则搜索量这么高我完全理解。数组初始化是一个看着简单、实际细节极多的话题部分初始化、默认零初始化、字符串数组的多种形态、宏定引用数组……任何一步没处理好编译过了运行却莫名崩溃。2.1 各语言数组初始化的默认值对比先说最基础的问题不完整初始化时剩余位置是什么值不同语言给出的答案完全不同这是跨语言开发最容易踩的暗坑语言写法默认值C/C局部数组int a[5] {1,2}未指定部分为0必须有大括号初始化列表C/C全局数组int a[5];0Javaint[] a new int[5];0Python[0] * 5需要显式写5个0JSconst a new Array(5);空槽位map会跳过不是undefinedPHParray_fill(0, 5, 0)需要显式指定VBADim a(1 To 5) As Long0这里有个非常经典的JavaScript陷阱new Array(5)得到的是一个有5个空槽位的数组你用map遍历会得到[empty × 5]而不是[undefined, undefined, ...]。正确写法是Array(5).fill(0)或[...Array(5)].map(...)。类似这种细节查数组初始化规则时很难一次搜全我建议你做一个跨语言对照表存入自己的笔记遇到就在表里查比每次都搜搜索引擎高效得多。C语言里还有一个容易被忽略的点{1,2}部分初始化时剩余元素自动补0这个行为在C和C中都是保证的。但只有局部数组不初始化时才是未知值。一句话总结有初始化列表补齐为0无初始化列表局部数组是垃圾值全局数组是0。2.2 C字符串数组的三种形态与选择c字符串数组初始化这个搜索词说明很多人被字符串数组绕晕了。C里存在三种常见写法对应三种不同的语义// 形式一指针数组保存的是字符串字面量的地址 const char *fruits[] {apple, banana, orange}; // 形式二二维字符数组逐字符拷贝到固定大小的块里 char grid[][8] {apple, banana, orange}; // 形式三std::string 数组最现代、最省心的方式 std::string modern[] {apple, banana, orange};形式一本质是三个const char*指针每个指向一块只读内存优点是省空间缺点是不能修改内容形式二是在数组里复制了一份可修改的副本但第二维必须足够长——banana需要至少7列如果写char grid[][6]直接编译失败这是C新手特别容易踩的编译错误形式三则完全由标准库处理内存分配长度任意、可增删改是现代C默认选择。实际项目里的经验法则是除非做嵌入式开发或底层接口对接否则一律用std::string数组。需要和C风格API交互时用c_str()拿临时指针用完就好。2.3 CString转char数组MFC世界里的常见需求cstring转char数组函数是Windows开发尤其MFC Legacy项目的高频问题。核心点是CString是宽字符还是窄字符取决于工程配置的字符集不能简单强转。我的惯用方案是#include atlbase.h #include atlconv.h CString str _T(hello array); char buf[64] {0}; strcpy_s(buf, sizeof(buf), CT2A(str)); // 自动处理 Unicode 到 ANSI 的转换这里有两个关键点。第一strcpy_s是安全版本必须显式传入缓冲区大小防止溢出第二CT2A这个宏在Unicode环境下做宽字符转多字节字符用完后不需要手动释放它内部持有临时栈缓存和堆内存。如果是其他CString版本也可以用CW2A更明确地表达宽字符转ANSI。这个需求虽然老但在维护Windows旧工程时天天碰得到。2.4 宏定义数组和类型转换中的经验税热词里出现了宏定义数组典型写法是这样#define ARR {1, 2, 3, 4} int data[] ARR;表面看挺方便但这属于典型的用宏节省几秒、用调试时间偿还的馊主意。宏展开就是纯文本替换没有类型检查如果有人在头文件里把宏值改了整个项目的数组长度和内容都会变而且扫码根本看不出问题。更糟糕的是宏不遵守作用域容易被意外覆盖。现代C的替代品非常成熟constexpr std::arrayint, 4 data {1, 2, 3, 4};constexpr在编译期求值有类型检查有作用域还能进调试器。能用语言特性解决的不要用预处理器。c语言数组变量的类型转换也不是冷知识。数组名隐式转换成指针是C语言的默认行为但强制转换通常在图像缓冲区、序列化协议解析等底层代码里出现比如把unsigned char[]转成uint32_t*再做内存映射。这里必须额外注意对齐问题如果数组首地址不是4字节对齐强行转成uint32_t*并解引用在x86上可能没问题但在ARM上会直接触发总线错误。跨平台代码里宁可memcpy出来也不要强行转换指针。3. JS数组加工流水线删、排、去重、合并一次讲透前端方向的热词几乎被数组操作霸榜js数组删除指定元素js数组排序的几种方法数组去重js数组合并去重数组转字符串数组增加。这也不奇怪JavaScript数组在后端的接口数据处理、前端的列表渲染里都是绝对主力。我把这些高频操作整理成一条加工流水线每个操作给最优解和替代方案。3.1 删除指定元素splice与filter的分工js数组删除指定元素有完全不同的两种诉求很多人没分清只删除第一个匹配到的元素用splice配合indexOf删除所有匹配到的元素用filterlet arr [1, 2, 3, 2, 4]; let elm 2; // 删除第一个 2 let idx arr.indexOf(elm); if (idx ! -1) arr.splice(idx, 1); // 删除所有 2 let arr2 arr.filter(v v ! elm);注意splice会修改原数组filter会返回新数组。按引用对象的删除也一样但indexOf对对象用的是引用相等如果是按某个属性匹配改写成findIndexlet userIdx arr.findIndex(u u.id targetId); if (userIdx ! -1) arr.splice(userIdx, 1);3.2 sort排序的几种方法默认行为是个陷阱js数组排序的几种方法里第一个要讲的就是arr.sort()不传排序函数时的反直觉行为const nums [3, 30, 10]; nums.sort(); // 结果是 [10, 3, 30]不是bug是因为默认按字符串的UTF-16字典序排序。数字要排序必须传比较器nums.sort((a, b) a - b); // 升序 [3, 10, 30] nums.sort((a, b) b - a); // 降序 [30, 10, 3]比较器理解成返回负数就让a排在b前面非常简单。对象数组按属性排序则是比较器里取属性const items [{name: 张三, age: 30}, {name: 李四, age: 20}]; items.sort((a, b) a.age - b.age);如果是中文排序直接localeCompare更符合语言习惯items.sort((a, b) a.name.localeCompare(b.name, zh-Hans-CN));还有就是sort也是原地修改原数组并返回同一个数组引用如果想保留原数组先[...arr].sort(...)。3.3 去重Set、filter和Map各管一摊基础去重最简单const arr [1, 2, 2, 3, 3, 4]; const uniq [...new Set(arr)];一行搞定Set自带元素唯一性这个方案对数字、字符串等原始类型完全够用。但对象数组去重就得上Mapconst users [ {id: 1, name: a}, {id: 1, name: b}, {id: 2, name: c} ]; const seen new Map(); const uniqUsers users.filter(u !seen.has(u.id) seen.set(u.id, 1));为什么不用Set去重对象因为对象是按引用比较的两个内容相同但引用不同的对象在Set里是两个元素。用Map按某个不影响结果的属性比如id做键才能实现按业务语义去重。这里的技巧是seen.set(u.id, 1)的返回值是Map本身真值所以filter的右侧条件能实现未见过则标记并保留的组合逻辑。3.4 合并去重、转字符串与数组增加js数组合并去重就是上面两者的结合const merged [...new Set([...arr1, ...arr2])];展开运算符把两个数组合并成新数组Set去重再展开回来逻辑清晰且不可变。数组转字符串常用的有两种arr.toString(); // 相当于 arr.join(,)默认逗号连接 arr.join( | ); // 自定义连接符更可控有一个细节toString对嵌套数组会递归展平[1, [2, 3]].toString()得到1,2,3做接口报文时这可能是意外结果用join可以避免。数组增加的姿势就更多了按需求选择arr.push(4); // 尾部加一个返回新长度 arr.unshift(0); // 头部加一个O(n) 慎用 arr.splice(2, 0, x); // 在索引2处插入 let newArr [...arr, 5]; // 不可变追加一个核心提醒高频头部插入不要用unshift因为每次都要搬动所有元素。需要栈语义就用push/pop需要队列语义用push/shift会让头部操作变成O(n)性能敏感场景考虑维护一个头指针或直接用链表结构。4. 切片式取数Python、NumPy、Matlab与Excel里的高效姿势python数组切片numpy三维数组相乘matlab数组取出多列excel提取前两列匹配的数据成一个数组vba数组对比最快……这些热词背后是同一个需求在大量数据里按规则快速抽出目标部分。这一节我按工具逐个拆解每条都是实际可复制的代码。4.1 Python列表切片左闭右开、负索引与步长Python切片的基本格式是arr[start:stop:step]核心记忆点是左闭右开[2:5]取索引2、3、4不包含5。配合负索引和步长能写出极其精炼的操作items [0, 1, 2, 3, 4, 5, 6, 7] items[2:5] # [2, 3, 4] items[-3:] # [5, 6, 7]取最后三个 items[::-1] # 反转整个列表 items[::2] # 每隔一个取一个[0, 2, 4, 6] items[1:7:2] # [1, 3, 5]每次切片都会创建新列表大列表频繁切片内存开销不小。如果你只需要顺序遍历用itertools.islice可以惰性处理不生成中间副本。还有一个容易忽略的点切片是浅拷贝切片结果里的元素若是可变对象修改它们原列表对应元素也会变。4.2 NumPy三维数组相乘轴的概念必须过一遍numpy三维数组相乘是科学计算方向的高频词。三维数组乘法不是给你一个单纯答案而是要学会读要求和选对操作。先造两个典型三维数组import numpy as np A np.arange(24).reshape(2, 3, 4) # 2×3×4 B np.arange(40).reshape(2, 4, 5) # 2×4×5 C A B # 2×3×5用运算符或np.matmul做的是沿最后一对轴做矩阵乘法前面的轴一一对应广播。A的前两维是(2,3)B的前两维是(2,4)最后一个轴维度对齐之后得到(2,3,5)。可以想象成有2个批次每个批次里做一次3×4乘4×5的普通矩阵乘法人话版就是同一批的矩阵各自相乘。np.dot和在二维情况下行为一致在三维情况下规则不同dot会把A拉平参与点积初学者强烈建议不用dot直接用。还有个极常用但容易记混的是np.multiply或*它做的是逐元素乘法要求两数组形状完全一致或满足广播条件D A * A # 逐元素平方形状不变如果需要更高维度的复杂轴对齐np.einsum是终极武器E np.einsum(bij,bjk-bik, A, B) # 等效 A B字符串文法总结成一句逗号左边是输入轴标签箭头右边是输出轴标签重复标签就是要求和的轴。练习几遍三维乘法的轴问题基本就通了。4.3 Matlab数组取出多列索引和冒号的艺术Matlab里取出多列非常直观A randi(10, 5, 6); % 5行6列随机矩阵 B A(:, [1 3]); % 取第1列和第3列 C A(:, 2:4); % 取第2到第4列连续 D A(2:4, [1 2 5]); % 取第2~4行、第1、2、5列冒号是全部的意思:放在行位置表示取所有行。Matlab的索引从1开始这是和很多其他语言不一样的地方。这个特性在处理表格化数据、特征矩阵抽取时极其高效整个列复制和重排都是矢量化的不需要循环。4.4 Excel提取匹配数据成数组与VBA性能优化excel提取前两列匹配的数据成一个数组是个非常典型的办公自动化问题。如果用的Office 365直接用动态数组函数FILTER一条公式搞定FILTER(A:B, C:C目标值)返回的就是一个两列数组会自动溢出填充到右侧单元格。老版本Excel没有FILTER只能用INDEXSMALLIF的数组公式写起来长且必须三键回车CtrlShiftEnter这里不推荐新手尝试。如果数据在VBA里操作最常用的是把Range直接读成数组Dim dataArr As Variant dataArr Range(A1:B100).Value 读成一个二维数组一行占一个下标然后可以用Application.Index一次性抽取指定列Dim result As Variant result Application.Index(Range(A1:B100).Value, 0, Array(1, 2))VBA里还有个大坑值得单独拿出来说vba数组对比最快。很多人写两层循环对比两个数组数据量上了十万级就卡死。正确的优化路线是借助字典Dim dict As Object Set dict CreateObject(Scripting.Dictionary) For i LBound(arrA) To UBound(arrA) dict(arrA(i)) 1 Next For j LBound(arrB) To UBound(arrB) If dict.Exists(arrB(j)) Then 找到交集 End If Next字典底层是哈希表查找近乎O(1)双层循环是O(n×m)差距是指数级的。这条优化思路和Excel大面积公式卡顿时启用手动重算是办公数据处理必会的两手。5. 数组背后的算法题区间最值、树状数组与组合求和热搜词里混进了一批算法向关键词数组求区间最大值的算法题树状数组模板三个数组最大的乘积如何确定数组中的哪些数据和等于固定值。这些词放在数组笔记里不违和它们正是数组数据结构向算法能力延伸的登高台阶。我讲两个最典型的代表代码可直接当模板收藏。5.1 从暴力遍历到RMQ区间最值问题到底在考什么数组求区间最值的算法题看起来简单遍历区间不就行了吗但面试和竞赛里它通常伴随一个前提——有m次查询数组长度n是10的5次方级。每次遍历O(n)总复杂度O(n×m)直接超时。优化的方向是预处理静态区间最大/最小值用稀疏表ST表预处理O(nlogn)每次查询O(1)动态单点更新区间查询用线段树或树状数组操作复杂度都是O(logn)以ST表为例核心思想是倍增。st[i][j]表示从i开始、长度2^j的区间最大值转移方程是st[i][j] max(st[i][j-1], st[i2^(j-1)][j-1])。查询[l,r]时令len r-l1k floor(log2(len))答案就是max(st[l][k], st[r-2^k1][k])——两个区间覆盖并集重叠覆盖了整个[l,r]。竞赛中最常考到的是树状数组因为代码短、常数小。“树状数组模板”这个热词说明很多人搜的就是能直接抄的那份代码。我这里给一个C模板class BIT { private: vectorint tree; int n; public: BIT(int _n) : n(_n), tree(_n 1, 0) {} int lowbit(int x) { return x -x; } void add(int i, int delta) { for (; i n; i lowbit(i)) tree[i] delta; } int prefix_sum(int i) { int res 0; for (; i 0; i - lowbit(i)) res tree[i]; return res; } int range_sum(int l, int r) { return prefix_sum(r) - prefix_sum(l - 1); } };lowbit(x)取的是x二进制最低位的1所代表的数值比如lowbit(6)2。add操作沿索引链向上更新query操作沿索引链向下累加两条链的跳跃都依赖lowbit。不用死背代码画一棵BIT下标的二进制树就能看懂但模板本身建议存一份笔试时直接默写能省很多时间。树状数组原生产应用于单点更新前缀和查询。如果想用它维护区间最大值、区间最值查询也可以但初始化时要逆序更新——这是另外的内容了。先掌握上面的二维模板覆盖范围已经很大。5.2 找出哪些数组元素之和等于固定值DFS回溯与背包思路如何确定数组中的哪些数据和等于固定值是典型的组合求和问题。比如从[2, 3, 5, 7, 8]中找出所有和为10的组合每个数只能用一次。最直觉的解法是DFS回溯模板如下def find_combinations(nums, target): res [] path [] nums.sort() # 先排序方便剪枝 def dfs(start, cur_sum): if cur_sum target: res.append(path[:]) return for i in range(start, len(nums)): if cur_sum nums[i] target: break # 已排序后面的更大直接剪掉 path.append(nums[i]) dfs(i 1, cur_sum nums[i]) path.pop() dfs(0, 0) return res关键点有三个排序列保证剪枝正确性i1保证每个元素只用一次path.pop()实现恢复现场以便继续搜索别的分支。如果允许同一个数字在组合中重复使用就把dfs(i1, ...)改成dfs(i, ...)代表还可以继续选当前元素。如果只问有多少种方案而不要求列出组合用动态规划的完全背包思路更快dp[sum] dp[sum - nums[i]]注意内层循环的方向决定了是0-1背包还是完全背包。这类题的变体在LeetCode上非常常见核心就是一个DFS剪枝和一个背包递推掌握这两条思路可以应对九成变体。5.3 三个数组乘积最大先排序再列边界情况“三个数组最大的乘积”有两类理解从同一个数组里取三个数或者从三个数组里各取一个数。以“一个数组里选三个数乘积最大”为例最容易漏掉的是负数情况[-100, -98, 2, 3, 4]中三个数乘积最大的是(-100)*(-98)*439200而不是4*3*224。所以排序后只需要比较两种候选最大的三个正数nums[-1] * nums[-2] * nums[-3]两个最小负数×最大正数nums[0] * nums[1] * nums[-1]取两者最大值即可。如果是从三个数组各取一个数思路是把每个数组排序然后枚举各数组的最小值和最大值这几组候选值因为最后乘积要么是三个最大值之一要么是把负数带给结果的影响考虑进去最稳的做法是各数组取前一小值和前三大值做组合对比。这类题的灵魂其实是不要凭直觉取最大先想边界条件。6. 跨语言细节点Java数组API、PHP接口数组与Qt引用传参最后这一节我挑几个热搜词里比较零散但非常实际的点集中处理Java数组的所有方法、PHP接口数组对象、JSON数组、Qt窗体间引用数组的接收赋值。这些都是日常开发中几乎一定会碰到但官方文档不会花大篇幅讲的内容。6.1 Java数组的所有常用方法Arrays工具类一次说清“java数组的所有方法”是个常见搜索词其实Java的数组本身没有方法所有工具方法都在java.util.Arrays里最常用的有这几个import java.util.Arrays; int[] arr {3, 1, 2}; Arrays.sort(arr); // 排序 int idx Arrays.binarySearch(arr, 2); // 二分查找必须先排序 Arrays.fill(arr, 0); // 全部填充 int[] copy Arrays.copyOf(arr, 5); // 扩容多的位置补0 String s Arrays.toString(arr); // [0, 0, 0]这里面有两个高频坑。第一binarySearch返回的是负数不代表没找到正数它返回的是-(插入点)-1要正确解读。第二Arrays.asList对基本类型数组有陷阱int[] arr {1, 2, 3}; Listint[] list Arrays.asList(arr); // 得到一个只有一个元素的Listint[]不是ListInteger!正确做基本类型转列表要么用Arrays.stream(arr).boxed().collect(...)要么循环添加。Java 8之后的流式处理也要顺手记一下int sum Arrays.stream(arr).sum(); double avg Arrays.stream(arr).average().orElse(0);6.2 PHP接口数组与JSON数组的处理要点PHP的接口数据解析在热词里占了两个位置php接口数组对象和json数组。它们的核心都集中在json_decode的第二个参数上$json [{id:1,name:a},{id:2,name:b}]; $array json_decode($json, true); // 第二个参数true得到关联数组 $object json_decode($json); // 不传true得到stdClass对象数组如果用对象方式访问记得判空处理因为任意一层数据可能是nullforeach ($object as $item) { $name $item-name ?? ; }而array_column是从二维数组里抽一列的利器接口数据处理里几乎天天用$ids array_column($array, id); // [1, 2] $pairs array_column($array, name, id); // 以id为键、name为值array_map做批量转换也很常见$names array_map(function ($u) { return $u[name]; }, $array);“json数组”的另一个常见场景是嵌套数组的遍历、过滤与重建。PHP没有JavaScript的filter和map链式风格对应的组合是array_filter配合array_values重排下标$filtered array_values(array_filter($array, fn($u) $u[age] 18));6.3 Qt窗体间引用数组const (double [10])接收赋值的正确姿势热词里最冷门但也最真实现场感的是qt窗体间引用数组const (double [10])如何接收赋值。这是C数组引用作函数参数的典型案例。在Qt的信号槽或普通函数中如果你声明了一个形如const double (data)[10]的参数它的含义是data是一个引用绑定到长度为10的double数组上。这比const double *data有一个额外优势——数组长度是类型的一部分传错长度的数组在编译期就会报错。// 窗口B接收并保存数据 class ReceiverWindow : public QWidget { Q_OBJECT public: void setData(const double (data)[10]) { for (int i 0; i 10; i) { m_data[i] data[i]; // 逐元素拷贝保存到成员数组 } } private: double m_data[10] {0.0}; }; // 窗口A调用方 double values[10] {1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0}; receiver-setData(values); // 直接传数组名自动绑定引用接收赋值的核心是不能直接把一个数组引用赋值给另一个数组C里数组不支持整体赋值必须逐元素拷贝C11以后可以用std::copy或std::array。如果只是需要借用数据而不保存函数内部直接用data[i]读即可不用拷贝这是const引用的价值不复制、不可修改、安全。到这里数组相关的这条线就串完了。我在实际写代码时越来越觉得数组的新手和老手差别不在背了多少API而在遇到奇怪bug时有没有第一时间想到我是不是越界了我是不是在数组拷贝上动了别名我的负数和边界条件是不是漏了。数组这种基础又无处不在的东西多花时间做梳理和对照性价比极高。建议你也给常用语言做一份快速参考表遇到处理数组的场景先查自己的笔记再查搜索引擎效率会明显不一样。
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门