Be Grateful for JavaScript Arrays: A Comparison with C
啊,javascript数组。对于我们许多人来说,它们是我们介绍的第一个数据结构,这是有充分理由的!我们可以通过多种方式使用较小的(或有时是庞大的)类似列表的结构。但是,您是否曾经躺在床上,对它们在引擎盖下的工作方式感到好奇?不,您可能是一个普通人!但是对于其他人来说,坚持下去,您也可能会对JavaScript产生更强烈的爱。
Learning how arrays and simple methods like.push()
低级语言的工作确实可以加深您对JavaScript阵列奇观的理解和欣赏。还有什么比实际的语言更好的语言经营我们的世界, C?
In this post, we’ll take a look at how you could go about implementing something similar to.push()
在C. We'll discuss some of the major differences between C and JavaScript, memory management and heap vs. stack memory in C, array types in C, and variables and pointers. And at the end, hopefully you'll have a newfound appreciation for JavaScript's humble array.push()
方法!
Background
这是JavaScript和C之间的一些关键区别:
- JavaScript is considered a high-level language, meaning there are a lot of abstractions between you (the developer) and the machine. Which is a fancy way of saying that high-level languages do a lot of work for us. Here’s a greatarticlethat highlights these differences.
- C需要手动内存管理,而JavaScript提供自动内存管理。稍后再详细介绍。
- C must be compiled in advance, whereas JavaScript is compiled right before being executed, often referred to as恰到好处compilation.
- C中的数组可以容纳单个数据类型(char,int,float等),而在JavaScript中,数组可以将混合的数据类型放在框外,例如字符串,数字和布尔值。(但是,C中的数组也可以使用一些工作)
The ease of developing in high-level languages often comes at the cost of performance. C is generally used for system programming and embedded systems where speed is more of a concern, while JavaScript is generally used in the browser and (more recently) on servers with Node.
Everything you can do in JS you can do in C as well, with a lot of work! The main高级语言的优势是开箱即用的工具,供我们开发人员使用,以及跨平台部署的易用性。
推它!
让我们看一下我们将如何实施pushingan element into an array in both languages.
JavaScript code:
const myArr = [1,2,3,4,5];
myarr.push(6);console.log(myarr);
// [1,2,3,4,5,6]
这里不足为奇。在C中怎么样?
#include
#include
int main(void){
char sizeofintegerpointer = sizeof(int);
int * myarr = malloc(sizeofintegerpointer * 5);
if (myArr == NULL) {
printf(“为数组分配内存有错误”);
退出(exit_failure);
}
for(int i = 0; i <5; i ++){
myarr [i] = i + 1;
}
在t *myArrExpanded = realloc(myArr, sizeOfIntegerPointer * 6);
if(myarrexpanded == null){
printf("There was an error reallocating memory for the array");
退出(exit_failure);
}
myarrexpanded [5] = 6;
for(int i = 0; i <6; i ++){
printf(“%d”,myarrexpanded [i]);
}
免费(myarrexpanded);
return 0;
}
Quite a lot more code! So what exactly is our good friend JavaScript abstracting away for us? To understand what’s happening here, we need to dive into a few concepts. Let’s start with how memory is laid out in C and JavaScript programs.
Memory Layout
Stack
When C and JavaScript programs are running, the memory they have access to is split up into a few areas. One of these areas is known as the call stack, which holds function invocations and variables in their scope.
您可以将呼叫堆栈视为一堆煎饼。为了获得更多美味,可以说每个煎饼上都有糖浆和巧克力片。很容易将我们的flapjack堆叠在彼此之上,但是当您想取出堆栈中的第三个煎饼时,这会变得更加棘手。当然,除非您一次举起两个煎饼,然后用另一只手抓住第三只煎饼。但是随后您冒着将所有这些煎饼掉下来的风险,到处都洒了糖浆!
This is why stacks follow the last in, first out method. Meaning, if I place 4 pancakes on the stack, in order to get to the second one, I’ll first need to remove the fourth and third.
当您的程序中调用函数时,将函数内的本地变量和呼叫函数的返回地址(统称为堆栈帧)推到呼叫堆栈。功能完成后,将从堆栈中删除堆栈框架,并删除该功能中声明的任何变量。
这是一个在-depth example diagramof the call stack in action in JavaScript.
通常,在编译程序时计算为调用堆栈和每个堆栈帧分配的内存量,并且在运行时无法更改。(虽然您可以在运行时间内使用堆栈中的更多内存alloca在C中,通常会劝阻这一点。)这里的主要思想是,如果您需要数据在函数调用和/或运行时所需的内存量之间存在,那么您需要依靠另一个,更持久的记忆区域。
Heap
在C中,当我们希望数据在函数调用之间持续存在时,我们可以从操作系统(OS)请求内存。如果操作系统能够为我们提供我们要求的内存,我们将以名为“树状的数据结构”的位置在存储器中占有一席之地堆.
分配在堆上的变量在任何功能调用的末尾都不会像堆栈一样被破坏。取而代之的是,这些变量一直持续到我们释放它们为止。在低级语言中,我们必须手动将这些对象释放到使用它们后。但是在诸如JavaScript之类的高级语言中,我们有一个内置的卫生人员称为垃圾收集器当我们的程序使用它们完成后,进来将这些变量自动将这些变量扔到垃圾箱中。
Here’s a really fantastic, short video describing thedifferences between the heap and stack.
注意:C程序内存还有其他领域。您可以在这里阅读有关这些信息.
Let’s now go over some of the different types of arrays in C.
Arrays
我们并没有真正谈论JavaScript中不同类型的阵列,因为JavaScript数组可以完成所有操作!它们是动态的,意味着它们可以缩小,成长并成为混合类型。它们是阵列世界的杂食动物。(许多高级语言也是如此。)C中的简单阵列是另一种野兽。
这是C中简单整数阵列的示例。这些数字是指该元素在内存中的位置。实际上,我们可以使用指针来利用这些内存位置,我们将在下面介绍。
C中的固定长度阵列
Fixed-length arrays are determined at compile time. Their sizes cannot change at run time and must be known prior to compiling your program. Soint arr [5] = {1,2,3,4,5}
will stay as a 5 element integer array for the entire duration of the program.
Variable-Length Arrays in C
Variable-length arrays were introduced in C99 (released in 1999, 27 years after C was created!). Their sizes can be updated at run time. For example:
在t array_len;printf(“输入所需的数组长度:”);
// C中的“&”操作员是指“地址”。我们将在下面介绍更多
scanf(“%d”,&array_len);int arr [array_len];
该C代码从用户获取输入,并根据用户的输入创建数组的大小。可变长度阵列的长度在运行时间之前不需要知道,但是一旦其大小初始化,就无法更改。因此,我们可以将用户输入一个数字,以确定C中这种数组的长度,但是之后,大小无法更改。这更接近我们要实现的目标,但是它仍然不允许我们在数组中添加一个元素。
C中的动态阵列
您可能已经猜到了,程序正在运行时可以调整动态数组。这是可能的,因为他们使用的内存分配在堆上,这意味着我们可以在需要时在运行时要求更多内存。但是,重要的是要注意,从操作系统请求内存是一个昂贵的操作,因为此运行时间分配需要进行许多不同的步骤。因此,两种类型的阵列都有其权衡和用例。
现在,让我们从一开始返回C代码示例:
#include
#include
int main(void){
// Request memory from the OS
// Hello OS! I'm requesting memory for 5 times the size of an integer
char sizeofintegerpointer = sizeof(int);
int * myarr = malloc(sizeofintegerpointer * 5);
//验证分配成功
if (myArr == NULL) {
printf(“为数组分配内存有错误”);
退出(exit_failure);
}
//初始化数组值
for(int i = 0; i <5; i ++){
myarr [i] = i + 1;
}
//将数组值移至新的,扩展的内存位置通过Realloc
在t *myArrExpanded = realloc(myArr, sizeOfIntegerPointer * 6);
if(myarrexpanded == null){
printf("There was an error reallocating memory for the array");
退出(exit_failure);
}
myarrexpanded [5] = 6;
//打印值
for(int i = 0; i <6; i ++){
printf(“%d”,myarrexpanded [i]);
}
//释放内存
免费(myarrexpanded);
return 0;
}
I’ve left comments on each section of code I felt was important and wrote further explanations below.
从操作系统请求内存
功能调用malloc
(内存分配)可能是此代码中最令人不安的作品,但实际上还不错!malloc
takes one parameter: how much memory to request from the OS in bytes.
不同的数据类型占用不同的大小(例如,整数的4个字节,1个字节用于字符等)。因此我们可以利用大小
函数以确定数据类型需要多少个字节,并将其乘以我们想要的数组的长度。如果我们想要一个5个元素数组,我们要做的就是将5乘以整数的大小,以获取5个整数数组的空间!
您可能正在考虑malloc
returns an array. It definitely seems like it given its usage in the for loops afterwards! But what it actually returns is a pointer. Apointeris simply a variable that stores a memory location.
考虑一个更简单的示例:
Here we have an array of 5 integers, and each element has a specific address in memory. It’s like their home address. You can see here that the value ofarr [0]
是1,因为这是我们分配给第一个元素的内容。该1个寿命的地址存储在mypointer
.
So in our original example, the result ofmalloc
将把指针返回到我们数组的第一个元素。
为什么要使用指针?当它已经意味着乘法时,为什么他们会重复使用星号呢?好问题!不幸的是,我无法回答后者,但是有很多使用指针的原因。一个示例是,在JavaScript中,当我们将对象传递给函数时,我们将被称为一个参考到那个对象。C没有这个参考的概念。这意味着我们说“我想将此整数数组传递给此功能并在不制作副本的情况下对其进行修改”的唯一方法是使用指针。
Check That Allocation Worked
When we request memory from the OS, there is a chance the OS doesn’t have enough memory to spare. So we should always check to make sure the request was successful, which we do in the if statement here. If the allocation fails, we exit the program with an error code.
Move Array Values to a Larger Memory Location
Therealloc
函数重新分配了以前从malloc
by the new size given in the second parameter. It will copy all the data to the new expanded memory location. This is how we can achieve dynamic arrays and, more broadly, dynamically allocated memory.
Assign and Free Up Memory
然后,我们可以安全地为我们的第六个数组元素分配一个值,最后释放重新分配的内存以防止内存泄漏,因为我们不再使用它。回想一下,在功能完成后,呼叫堆栈内存将被划分,但是堆并非如此。在C中,堆的对象可以并且会坚持下去,直到您使用free
.
与JavaScript进行比较
我们对C中的阵列进行了新的了解,让我们再看一下JS代码:
const myArr = [1,2,3,4,5];
myarr.push(6);console.log(myarr);
// [1,2,3,4,5,6]
In that first line alone, we’re requesting memory from the OS and with no fixed size. No need to manually allocate memory or check if the allocation was successful.
一旦将6推入数组,我们就不必担心重新定位以前分配的内存。变量在堆栈上声明并在JavaScript中堆积,但是您作为程序员不必担心它 - 语言为您提供了。
You can see how this process could get onerous in C when repeated over and over in a complex application. So thank goodness for JavaScript!
结论
我们只浏览了每个主题的表面,但是我认为这些知识使我们对整个高级语言表示赞赏。现在,当你看.push()
您可以欣赏JS为您所做的多少。:)