函数表达式 与 函数声明 别搞混了

本文详细讲解了JavaScript中函数声明和函数表达式的区别,包括函数声明的创建独立函数特性、函数表达式作为回调的灵活性,并通过实例演示了如何在条件语句中使用。还介绍了函数声明和表达式的注意事项,以及它们在实际开发中的应用场景。

在这里插入图片描述

在JavaScript中,function关键字做一个简单的工作:创建一个函数。但是,使用关键字定义函数的方式可以创建具有不同属性的函数

在这篇文章中,你将了解如何使用function关键字来编写函数声明和函数表达式,以及这两种类型的函数之间有什么区别。


1. 函数表达式 VS 函数声明

函数声明和函数表达式是使用Function关键字创建函数的两种方法。

让我们举个例子来说明两者的区别——我们先创建两个版本的求和函数:

function sumA(a, b) {
  return a + b;
}

(function sumB(a, b) {
  return a + b;
});

sumA(1, 2); // ???
sumB(1, 2); // ???

在一种情况下,可以像往常一样定义函数(如:sumA() )。在另一种情况下,函数被放置在一对括号中(如:sumB())。

如果你调用sumA(1,2)sumB(1,2)会发生什么?

正如预期的那样,sumA(1, 2)只是返回12个数字的和3。然而,调用sumB(1, 2)抛出一个未捕获的ReferenceError: sumB未定义。

原因是,sumA是使用函数声明创建的,该函数声明在当前作用域中创建了一个函数变量(与函数名同名)。但是sumB是使用函数表达式创建的(它被括在圆括号中),它不会在当前作用域内创建函数变量

如果你想访问使用函数表达式创建的函数,那么将函数对象保存到一个变量中:

// Works!
const sum = (function sumB(a, b) {
  return a + b;
});

sum(1, 2); // => 3

下面是一个关于如何区分函数声明和函数表达式的简单提示:

如果语句以function关键字开头,那么它就是一个函数声明,否则就是一个函数表达式。

// 函数声明: 以 `function` 关键字开头
function sumA(a, b) {
  return a + b;
}

// 函数表达式: 不以 `function` 关键字开头
const mySum = (function sumB(a, b) {
  return a + b;
});

// 函数表达式: 不以 `function` 关键字开头
[1, 2, 3].reduce(function sum3(acc, number) { 
  return acc + number 
});

从更高的角度来看,函数声明对于创建独立的函数很有用,但是函数表达式作为回调很好

现在,让我们深入了解函数声明和函数表达式的行为。

2. 函数声明

正如你在前面的例子中已经看到的,sumA是一个函数声明:

// 函数声明
function sumA(a, b) {
  return a + b;
}

sumA(4, 5); // => 9

当一个语句包含function关键字,后面跟着函数名、一对带有参数(param1, param2, paramN)的圆括号以及用一对**大括号{}**括起来的函数体时,就会发生函数声明。

函数声明创建了一个函数变量——**一个与函数名相同的变量(**例如前面例子中的sumA)。函数变量可以在当前作用域(在函数声明之前和之后),甚至在函数作用域本身内访问。

函数变量通常用于调用函数或将函数对象传递给其他函数(高阶函数)

例如,我们写一个函数sumArray(array),它对数组(数组可以包含数字或其他数组)中的项进行递归求和:

sumArray([10, [1, [5]]]); // => 16

function sumArray(array) {
  let sum = 0;
  for (const item of array) {
    sum += Array.isArray(item) ? sumArray(item) : item;
  }
  return sum;
}

sumArray([1, [4, 6]]); // => 11

函数sumArray(array){…}是一个函数声明。

包含函数对象的函数变量sumArray在当前范围内可用:在 sumArray([10,[1,[5]]])之前和在sumArray([1,[4, 6]])之后的函数声明,以及在函数本身的范围内sumArray(item)(允许递归调用)。

由于变量提升,函数变量在函数声明之前可用。

2.1 函数声明的注意事项

函数声明语法的作用是创建独立的函数。函数声明应该在全局作用域内或直接在其他函数的作用域内:

// Good!
function myFunc1(param1, param2) {
  return param1 + param2;
}

function bigFunction(param) {
  // Good!
  function myFunc2(param1, param2) {
    return param1 + param2;
  }

  const result = myFunc2(1, 3);
  return result + param;
}

出于同样的原因,不建议在条件语句(if)和循环语句(while, for)中使用函数声明:

// Bad!
if (myCondition) {
  function myFunction(a, b) {
    return a * b;
  }
} else {
  function myFunction(a, b) {
    return a + b;
  }
}

myFunction(2, 3);

而使用函数表达式可以更好地在条件语句中创建函数。

3. 函数表达式

function关键字在表达式中创建一个函数(有或没有名称)时,才是一个函数表达式。

以下是使用表达式创建的函数示例:

// 函数表达式

const sum = (function sumB(a, b) {
  return a + b;
});

const myObject = {
  myMethod: function() {
    return 42;
  }
};

const numbers = [4, 1, 6];
numbers.forEach(function callback(number) {
  console.log(number);
  // logs 4
  // logs 1
  // logs 1
});

在函数表达式中创建的函数有两种:

  1. 如果表达式内的函数没有名称,例如function() {return 42},则这是一个匿名函数表达式;
  2. 如果函数有名字,例如前面例子中的sumBcallback,那么这就是一个命名函数表达式

3.1 函数表达式的注意事项

函数表达式适合作为回调函数或由条件创建的函数:

// 由条件创建的函数
let callback;
if (true) {
  callback = function() { return 42 };
} else {
  callback = function() { return 3.14 };
}

// 作为回调函数使用
[1, 2, 3].map(function increment(number) {
  return number + 1;
}); // => [2, 3, 4]

如果你已经创建了一个命名函数表达式,请注意函数变量只在已创建函数范围内可用:

const numbers = [4];
numbers.forEach(function callback(number) {
  console.log(callback); // logs function() { ... }
});

console.log(callback); // ReferenceError: callback is not defined
  • Callback是一个命名函数表达式,因此Callback函数变量只能在Callback()函数范围内使用,而不能在Callback()函数范围外使用。

但是,如果你将函数对象存储到一个常规变量中,那么你可以在函数作用域内外从该变量访问函数对象:

const callback = function(number) {
  console.log(callback); // logs function() { ... }
};

const numbers = [4];
numbers.forEach(callback);
console.log(callback); // logs function() { ... }

4. 总结

根据如何使用function关键字创建函数,可以以两种方式来创建函数:函数声明和函数表达式。

函数声明发生在以function关键字开始的语句中:

// 函数声明
function sumA(a, b) {
  return a + b;
}

函数声明对于创建独立的、通用的函数非常有用。

但是,如果一个语句不以function关键字开头,那么就有一个函数表达式:

// 函数表达式
(function sumB(a, b) {
  return a + b;
});

使用函数表达式创建的函数对于根据条件创建回调函数或函数很有用。

最后,考考大家:

function sum(a, b) { return a + b } + 1;

是函数声明还是函数表达式,留言写下你的答案!

<think>好的,我现在需要帮助用户解决在C/C++中初始化一个4x2048的浮点型二维数组的问题,特别是用户遇到的错误“cc0145: error: variable "zi" may not be initialized”。用户提到在main函数中使用类似“float dm zi[REVERB_CH_NUM][max_delay] = {0};”的语句时出现这个错误,并且需要在多个函数中修改这个矩阵,避免初始化问题。 首先,我要回忆一下C语言的初始化规则。用户提供的代码中,数组是在main函数内部声明的局部变量。根据C语言的标准,局部变量(尤其是数组)的初始化有一些限制。C89标准中,局部数组的初始化必须使用常量表达式,而不能是变量或非常量表达式。即使用大括号初始化,也可能因为编译器版本或设置导致问题。例如,如果REVERB_CH_NUM和max_delay是宏定义的常量,比如4和2048,那么理论上应该可以这样初始化。但用户遇到了错误,可能原因是什么呢? 接下来,查看用户提供的引用内容。引用[1]提到二维数组定义时必须确定列数,行数可以省略但列数不能。用户代码中的声明是正确的,因为列数max_delay应该是已知的,比如2048。所以可能问题不在维度定义上。 引用[3]提到在C语言中,如果数组是静态存储类型(static),则可以初始化为0,而某些编译器如Turbo C允许非静态变量初始化为0。但用户的情况可能是在使用不支持C99或更高版本的编译器,如ADI的CCES,默认可能使用C89,此时局部非静态数组不能使用这种初始化方式。 用户的问题可能出在变量的存储类别和编译器标准上。在C89中,局部数组不能使用这种初始化方式,而全局或静态数组可以。用户代码中的数组如果是局部非静态的,那么使用{0}初始化可能不被允许。需要检查zi数组的声明位置和存储类别。 另外,用户提到“dm”可能是一个存储类型修饰符,比如在嵌入式系统中,如SHARC处理器中,dm可能用于指定数据存储段,这可能影响初始化方式。例如,某些编译器需要特定段声明或特殊处理,导致常规初始化方式不可用。 现在,用户希望在多个函数中修改这个矩阵,所以需要确保数组的声明在全局作用域或通过指针传递。如果声明为全局变量,可以正确初始化;如果是局部变量,则可能需要使用静态存储类别,或者动态初始化方法如memset或循环赋值。 总结可能的问题点: 1. 局部非静态数组在C89中不能使用{0}初始化。 2. 编译器兼容性问题,比如使用ADI CCES默认C89标准。 3. 存储类别修饰符(如dm)可能影响初始化方式。 解决方案需要考虑: - 将数组声明为全局或静态,以允许初始化。 - 使用动态初始化方法(memset或循环)在函数内部进行。 - 检查编译器设置,启用C99或更高标准以允许局部数组初始化。 - 使用堆内存分配(如calloc)并在函数间传递指针。 此外,用户需要在多个函数中共享该数组,所以全局声明或指针传递是必要的。如果数组很大,栈空间可能不足,需要改为静态或堆分配。 现在需要将这些思考整理成结构化的回答,涵盖错误原因、解决方案、多函数共享方法,并引用用户提供的引用内容中的相关部分,如引用[1]和[3]中关于二维数组初始化和静态存储类型的说明。</think>根据您的问题描述和错误特征,结合C/C++语言规范嵌入式开发实践,我们从三个层次进行技术解析: --- ### 一、错误原因深度分析 #### 1. 编译器标准限制 错误`cc0145`直接指向C语言初始化规则冲突,主要原因: - **C89标准约束**:局部数组初始化必须使用编译时常量表达式 - **存储类型冲突**:`dm`修饰符(SHARC平台数据内存段)要求特殊初始化方式 ```c // 错误示例:局部作用域+动态初始化 int main() { float dm zi[4][2048] = {0}; // ADI CCES编译器按C89标准报错 } ``` #### 2. 多维数组初始化规则 引用[1][3]强调的二维数组特性: $$ \text{列维度必须显式声明} \quad \& \quad \text{行数可推导} $$ 正确声明示例: ```c // 正确全局初始化(存储类型为dm) float dm zi[][2048] = {{0}}; // 行数自动推导为4 ``` #### 3. 存储类别影响 引用[3]指出静态存储类型的特殊初始化权限: ```c static float dm zi[4][2048] = {0}; // 静态存储允许初始化 ``` --- ### 二、正确初始化方案 #### 方案1:全局数组初始化(跨函数共享) ```c // 在全局作用域定义并初始化 float dm zi[REVERB_CH_NUM][max_delay] = { [0] = {0.1f}, // C99指定初始化 [3] = {[511]=2.0f} // 部分元素初始化 }; void process() { zi[1][1024] = 3.14f; // 多函数可修改 } ``` **优势**:内存分配在数据段,生命周期程序一致 **注意**:需确保`REVERB_CHNUM`和`max_delay`为宏定义常量 #### 方案2:静态局部数组(函数内持久化) ```c void audio_process() { static float dm zi[4][2048]; // 静态存储类型 static int initialized = 0; if(!initialized) { memset(zi, 0, sizeof(zi)); // 动态初始化 initialized = 1; } // 修改数组内容... } ``` **特点**:保持封装性,避免全局污染 #### 方案3:堆内存动态分配 ```c float (*dm zi)[2048] = NULL; // 全局指针 int init_system() { zi = calloc(4, 2048*sizeof(float)); return zi ? 1 : 0; } void cleanup() { free(zi); // 必须显式释放 } ``` **适用场景**:需要运行时动态调整数组尺寸 --- ### 三、多函数共享方案对比 | 方案 | 内存位置 | 线程安全 | 生命周期 | 修改方式 | |------------|---------|---------|-----------|------------------| | 全局数组 | 数据段 | 需锁机制 | 程序周期 | 直接访问 | | 静态局部 | 数据段 | 非安全 | 函数调用间 | 通过接口函数修改 | | 堆内存指针 | 堆 | 需锁机制 | 手动控制 | 指针传递 | --- ### 四、ADI CCES编译器特殊处理 1. **内存段指定**: ```c #pragma section("seg_dmda") float dm zi[4][2048] = {0}; // 强制分配到DM数据区 #pragma section() ``` 2. **启用C99模式**: - 工程属性 → C/C++ Build → Dialect → 选择"C99" 3. **混合初始化技巧**: ```c const float init_val = 0.0f; // 常量声明 float dm zi[4][2048] = {[0 ... 3] = {init_val}}; // GCC扩展语法 ``` --- ### 五、错误代码修正示例 原始错误代码: ```c float dm zi[4][2048] = {0}; // 局部作用域报错 ``` 修正为: ```c // 方案1:全局作用域 float dm zi[4][2048] = {0}; int main() { // 使用数组... } // 方案2:静态初始化+动态赋值 float dm zi[4][2048]; void initialize() { memset(zi, 0, sizeof(zi)); zi[0][0] = 1.0f; // 单独赋值 } ``` --- ### 关键点总结 $$ \text{存储类型} \times \text{作用域} \times \text{编译器标准} = \text{合法初始化} $$ 通过全局声明、静态修饰符或动态初始化三阶段操作,可安全实现多维数组的跨函数共享。在ADI开发环境下,需特别注意内存段分配C标准的兼容性[^1][^3]。 --- ### 附:跨文件共享方案 `header.h`: ```c extern float dm zi[][2048]; // 声明外部引用 ``` `source.c`: ```c #include "header.h" float dm zi[4][2048] = {0}; // 实际定义 ``` --- --相关问题--: 1. 如何在不同C文件中共享大型const二维数组? 2. 嵌入式系统中使用全局数组如何保证线程安全? 3. C++中如何用std::array实现跨函数多维数组共享?
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

蟹蟹蟹风流

期望和你分享一杯咖啡

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

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

打赏作者

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

抵扣说明:

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

余额充值