java、C#及C++数组及其创建和初始化
来源:长沙it培训|发布时间:2017-05-02|浏览量:
关于三种C++ 、Java 和C#语言数组及其创建和初始化,本文要讲的是内置的数组类型,不包含任何扩展代码或者附加库。数组就是一个列表,是一个受到某些限制的列表,是一片连续的内存空间,存放着大量相同类型的对象。
使用C++、java和C#语言,在对于同样的类型来说,我们实际是在谈论一个对象列表,这些对象拥有共同的祖先。如果有一个基类 Base 和一个派生类 Derived,然后我们定义了一个保存 Base 实例的数组,那么我们可以填充任何 Base 类型的实现或者从 Base 派生的类型的实例。
在数组中放了什么就能取回什么。数组元素在内存中是一个接一个的保存着。换句话说,这是数组语法带来的好处,因为它们之间没有间隔,你可以快速的在相邻元素之间进行移动,而不会像在列表中那样,从一个元素的地址很难推断出下一个元素保存在内存中的哪个位置。
创建数组
创建和准备好使用数组,有三部分内容。
首先是申明,然后分配内存,最后初始化数组。如果你创建的是类实例的数组,你还得确保这个类有不带参数的构造函数。
先来看看 Java 中怎样创建和初始化数组:
package com.roboticsfordreamers;
import java.io.IOException;
class Base
{
public Base()
{
System.out.println("Base constructor");
}
}
public class Main {
public static void main(String[] args) {
int[] MyArray; //Declare an array - the type is an array it doesn't need a size of the array for declaring it
int MyOtheArray[]; //Declare it with the brackets after the name
MyArray = new int[20]; //Allocate memory for an array with new, if you don't the compiler will warn you its not initialized
//MyArray = {1,2,3,4,5,6}; //You can not initialize the array after defining it
MyArray[0] = 20; //Assign to the first item in the array
//MyArray[0] = 2231122212000022020; //overflow - compiler will not let you do this
try {
MyArray[20] = 33; //over bounds
}
catch (Exception e)
{
System.out.println("Over the bounds of the array - there is no 21st element." + e.getMessage());
//java.lang.IndexOutOfBoundsException : Invalid array range: 20 to 20
}
int[] MySecondArray = new int[40]; //declare and define on the same line
//int CantDoThis[20];
//int CantDoThis = new int [20];
//All on one line declare, define, initialize
int[] MyThirdArray = {
2,3,4,5,6,7,7,7,7,7
};
//we use length to count the elements in the array
System.out.println("The third array was automatically initialized, and it contains " + MyThirdArray.length + " integers");
Base MyArrayOfBase[]; //No constructors called - not initialized
System.out.println("About to define array of Base classes");
MyArrayOfBase = new Base[10]; //No constructors called - only the array has been allocated
System.out.println("About to init array of Base classes");
for (int i=0; i!=10; i++)
{
MyArrayOfBase[i] = new Base(); //Instantiate the element in the array
}
System.out.println("About to declare, define and init array of Base classes on single line");
//Or what about all on one line ? Here we define the array and insert 3 instances into it
Base[] MyAllInOneArrayOfVase = new Base[] {new Base(), new Base(), new Base()};
try {
System.in.read();
} catch (IOException e) {
e.printStackTrace();
}
}
}
你可以看到三个阶段。首先我们有申明,即申明变量名称、实际的数组类型以及数组要存储什么。然后是为数组对象本身(而不是其内容)的大小和需要战用的内存。最后是内容战用的内存。占用的内存分为两个部分,第1部分是数组第1个元素在内存中的地址,本质上这就是变量名(就像指针或者引用),它的大小和在内存中的地址通常是32位或64位。第2部分是数组内容所占据的空间,它是1个元素的大小乘以在定义数组时指定的元素个数,比如 int[10] 是 10 倍 int 的大小。
在创建数和初始化数组的时候要小心,数组中的元素这时候还是 null,直到我们后面为它们分配内存(new)。所以是先为数组分配地址的空间,再为数组的每个元素分配空间。
下面是上面代码的输出:
Over the bounds of the array - there is no 21st element.20
The third array was automatically initialized, and it contains 10 integers
About to define array of Base classes
About to init array of Base classes
Base constructor
Base constructor
Base constructor
Base constructor
Base constructor
Base constructor
Base constructor
Base constructor
Base constructor
Base constructor
About to declare, define and init array of Base classes on single line
Base constructor
Base constructor
Base constructor
C# 中的数组:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace ArraysInCSharp
{
public class Base
{
public Base()
{
System.Console.WriteLine("Base constructor");
}
}
public class Program
{
static void Main(string[] args)
{
int[] MyArray; //Declare an array - the type is an array it doesn't need a size of the array for declaring it
//int MyOtheArray[]; Can not declare it with the brackets after the name in C#
MyArray = new int[20]; //Allocate memory for an array with new, if you don't the compiler will warn you its not initialized
//MyArray = {1,2,3,4,5,6}; //You can not initialize the array after defining it
MyArray[0] = 20; //Assign to the first item in the array
//MyArray[0] = 2231122212000022020; //overflow - compiler will not let you do this
try
{
MyArray[20] = 33; //over bounds
}
catch (System.Exception e)
{
System.Console.WriteLine("Over the bounds of the array - there is no 21st element." + e.Message);
//Exception - Index was outside the bounds of the array
}
int[] MySecondArray = new int[40]; //declare and define on the same line
//int CantDoThis[20];
//int CantDoThis = new int [20];
//All on one line declare, define, initialize
int[] MyThirdArray = {
2,3,4,5,6,7,7,7,7,7
};
//we use length to count the elements in the array
System.Console.WriteLine("The third array was automatically initialized, and it contains " + MyThirdArray.Length + " integers");
Base[] MyArrayOfBase; //No constructors called - not initialized
System.Console.WriteLine("About to define array of Base classes");
MyArrayOfBase = new Base[10]; //No constructors called - only the array has been allocated
System.Console.WriteLine("About to init array of Base classes");
for (int i = 0; i < 10; i++)
{
MyArrayOfBase[i] = new Base(); //Instantiate the element in the array
}
System.Console.WriteLine("About to declare, define and init array of Base classes on single line");
//Or what about all on one line ? Here we define the array and insert 3 instances into it
Base[] MyAllInOneArrayOfVase = new Base[] { new Base(), new Base(), new Base() };
System.Console.ReadKey();
}
}
}
主要的区别在于 [] 括号不能放在数组变量名后!除此之外它几乎都是相同的。
输出看起来和 Java 的输出几乎一样:
Over the bounds of the array - there is no 21st element.Index was outside the bounds of the array.
The third array was automatically initialized, and it contains 10 integers
About to define array of Base classes
About to init array of Base classes
Base constructor
Base constructor
Base constructor
Base constructor
Base constructor
Base constructor
Base constructor
Base constructor
Base constructor
Base constructor
About to declare, define and init array of Base classes on single line
Base constructor
Base constructor
Base constructor
不要忘了,创建数组之后可以给它添加任意实例,只要它们来自声明的类型(声明类型或其子孙类型)。看看下面的 C# 示例,这里申明了一个 Base 类型的数组,但我们可以将 Derived 实例放入其中:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace roboticsfordreamers
{
public class Base
{
private String name;
public void print()
{
System.Console.Out.WriteLine("Derived Name : " + name);
}
public Base(string na)
{
name = na;
System.Console.Out.WriteLine("Base constructor with name : " + name);
}
}
public class Derived : Base
{
public Derived(String na) :base (na)
{
System.Console.Out.WriteLine("Derived constructor with name : " + na);
}
}
class Program
{
private static void printAll(Base[] myArray)
{
foreach (var item in myArray)
{
item.print();
}
}
static void Main(string[] args)
{
Base[] MyArray = new Base[10];
Derived instance1 = new Derived("instance1 1");
Derived instance2 = new Derived("instance1 2");
MyArray[0] = instance1;
MyArray[1] = instance2;
printAll(MyArray);
}
}
}
最后我们在 C++ 中定义一个数组:
// ArraysConsoleApp.cpp : Defines the entry point for the console application.
//
#include "stdafx.h"
using namespace std;
namespace MyArrayStuff
{
class Base
{
public:
Base()
{
cout << "Base constructor" << endl;
}
};
}
int main()
{
//int[] MyArray; //can not do this in C++ must declare the array with the brackets after the variable name
//int MyOtherArray[]; //Can not declare it with the brackets after the name in C# - unknown size
int MyArray[20]; //Allocate memory for an array with new
//MyArray = {1,2,3,4,5,6}; //You can not initialize the array after defining it
MyArray[0] = 20; //Assign to the first item in the array
MyArray[0] = 2231122212000022020; //overflow - C++ compiler will let you do this - warning ONLY
try
{
MyArray[20] = 33; //over bounds
}
catch (...)
{
//Does not catch this in an exception - its a run time error !
//cout << "Over the bounds of the array - there is no 21st element." << endl;
//Exception - Index was outside the bounds of the array
}
//Can not on one line do all three declare, define, initialize
//int[] MyThirdArray = { 2,3,4,5,6,7,7,7,7,7 };
//we use sizeof and divide it by an item to count the elements in the array
int size = sizeof(MyArray) / sizeof(int);
cout<<"MyArray contains "<<size<<" integers"<<endl;
cout << "About to declare an array of 10 Base classes" << endl;
MyArrayStuff::Base MyArrayOfBase[10]; //No constructors of Base called - not initialized
cout<<"About to init array of 10 Base classes"<<endl;
for (int i = 0; i < 10; i++)
{
MyArrayOfBase[i] = MyArrayStuff::Base(); //Instantiate the element in the array
}
cout<<"About to declare, define and init array of Base classes on single line"<<endl;
//Or what about all on one line ? Here we define the array and insert 3 instances into it
MyArrayStuff::Base MyAllInOneArrayOfVase[3] = { MyArrayStuff::Base(), MyArrayStuff::Base(), MyArrayStuff::Base() };
char a;
cin>>a;
return 0;
}
我们可以看到在超越数组范围(尝试访问第21个元素,它我们只分配了20个元素的空间)时会发生未知行为。我们只能在变量名后使用括号。在 C++ 中初始化器不需要再使用 new 创建数组对象,数组保存的也不是对象指针而是实例。
下面是 C++ 的输出:
MyArray contains 20 integers
About to declare an array of 10 Base classes
Base constructor
Base constructor
Base constructor
Base constructor
Base constructor
Base constructor
Base constructor
Base constructor
Base constructor
Base constructor
About to init array of 10 Base classes
Base constructor
Base constructor
Base constructor
Base constructor
Base constructor
Base constructor
Base constructor
Base constructor
Base constructor
Base constructor
About to declare, define and init array of Base classes on single line
Base constructor
Base constructor
Base constructor
如果你想创建实例的数组,只需要使用普通的数组语法,比如
MyClass instances[10];
你需要提供无参数构造函数,或者在类申明时不提供任何构造函数,这样它会有一个默认的构造函数。上面那行代码会构造 10 个对象,所以我们需要无参数的构造函数。当然只要我们没定义其它带参数的构造函数就会有一个默认的构造函数。记住,如果你添加了带参数的构造函数,又想在能在数组中自动构造实例,你就得添加一个无参数的构造函数。
要不然,使用带参数的构造函数是无法创建实例数组的,想像一下:
MyClass instances[10] = new MyClass { MyClass("AllParamsTheSame"); }
这样做会一次创建10个具有相同状态的实例,这似乎没什么意义。就算你确实需要这样也是不可能的,你只能通过循环用参数构造它们。
当然你可以使用初始化列表,不过你得像这样显式构造它们:
MyClass ans[3] = { MyClass("Dog"), MyClass("Snake"), MyClass("Lion")};
如果确实需要指针数组,这样可以使用 new 来创建而不是自动构造每一个实例。注意在 C++ 中使用实例数组的好处是不需要使用指针/引用数组去寻址,直接使用实例会少一次间接寻址,因而更快。
使用实例意味着你仍然可以利用基类型的优势。因此,如果你希望数组保存 Animal 类的子类实例,你不必使用引用或指针的数组。即使你定义的数组是保存 Animal 类型实例而不是指针或引用的,也可以用于保存其子类实例。
C# 的写法:
#include "stdafx.h"
#include <iostream>
#include <string>
using namespace std;
namespace roboticsfordreamers
{
class Base
{
private:
string name;
public:
Base()
{
cout << "Base class constructor with NO name" << endl;
}
Base(string na)
{
name = na;
cout << "Base class constructor with name : " << name << endl;
}
virtual void print()
{
cout << "Printing the base class with name : " << name << endl;
}
};
class Derived : public Base
{
public:
void derivedFunction()
{
cout << "Function on derived class" << endl;
}
Derived(string na) : Base(na)
{
cout << "Derived class constructor with name : " << na << endl;
}
};
}
int main()
{
using namespace roboticsfordreamers;
Base MyArray[10]; //Array of 10 Base instances - need a default constructor for this
Base *MyArrayPtrs[10]; //Array of 10 pointers to instances - DO NOT need a default constructor for this, we have not constructed the instances of Base, we have just created the pointers to the instances !
//Lets create 2 instances
Base myBase;
Derived myDerived("Derived");
Base *myBasePtr = new Base();
Derived *myDerivedPtr = new Derived("Derived Ptr");
MyArray[0] = myBase;
MyArray[1] = myDerived;
MyArrayPtrs[0] = myBasePtr;
MyArrayPtrs[1] = myDerivedPtr;
for (int index = 0; index < 2; index++)
{
MyArray[index].print();
MyArrayPtrs[index]->print();
}
//Now lets try and get our Derived Type back - we know index 1 is really a derived
//Cant do this ! There is no way to cast an instance of Derived from Base type
//Derived tempDerived = dynamic_cast<Derived>(MyArray[1]);
//can not do this below unless Base is a polymorphic type - ie it has at least one virtual function
Derived *tempDerivedPtr = dynamic_cast<Derived*>(MyArrayPtrs[1]);
tempDerivedPtr->derivedFunction();
return 0;
}
我想说,指针或者引用数组比实例数组更好。如果定义一个指针或引用数组,一开始的时候就不需要无参数构造函数。接下来,如果你想从数组中取回派生类型(在上面的例子中,我们希望指针是派生类型,这样就可以调用派生类的方法),确保数组保存的是指针或者引用。这样即使在另一个层面间接使用数组中的指针或引用也会更清晰,尤其是你不想复制数组的时候。只不过还有唯一需要注意的一点,就是删除数组的时候,其中引用的对象并未销毁。千万不要忘了手工去删除数组中引用的每一个实例,它们在离开作用域的时候不会被自动销毁,但数组会!
多维数组
数组可以有多个维度。目前存在两种类型的非一维数组,锯齿形和多维。它们的区别在于形式上,前一种允许有不同长度和子数组,而后者不允许。可以简单地想像个2D数组的画面,锯齿状的拥有不同的长度,而多维的是统一的长度。
Java 中的多维数组是数组的数组,因此它天生就支持锯齿状的数组(想像一下列表,它的每一项都是长度不同的数组)。
Java 中锯齿状的数组语法如下:
//Misleading this multi-dim array
int[][] MyMulti = { {2,2},{1,2},{4,4},{4,4},{4,4},
{2,2},{1,2},{4,4},{4,4},{4,4} };
System.out.println("Each row is " + MyMulti[0].length + " elements long");
//Or is it ?
MyMulti[0] = new int[] {1,1,1,1,1,1};
System.out.println("The first row is " + MyMulti[0].length + " elements long");
System.out.println("The second row is " + MyMulti[1].length + " elements long");
这里数组中每一行的长度并不相同,并且可以在初始化之后进行更改。
Java 运行结果:
Each row is 2 elements long
The first row is 6 elements long
The second row is 2 elements long
在 C# 的多维数组中的例子:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace ArraysInCSharp
{
public class Base
{
public Base()
{
System.Console.WriteLine("Base constructor");
}
}
public class Program
{
static void Main(string[] args)
{
//C# is also Jagged arrays
Base[][] MyMultiBase = new Base[3][];
MyMultiBase[0] = new Base[5];
MyMultiBase[0] = new Base[2];
System.Console.WriteLine("Multi Array first row length :" + MyMultiBase[0].Length);
System.Console.WriteLine("Multi Array second row length :" + MyMultiBase[1].Length);
//Or what about all on one line ? Here we define the jagged multi array and insert 3 instances into it, then into the next row, then 1
Base[][] MyAllInOneArrayOfBase = new Base[][]
{
new Base[] { new Base(), new Base(), new Base() },
new Base[] {new Base(), new Base() },
new Base[] {new Base() }
};
//Now here is a matrix - not jagged - same number of instances per row
Base[,] My2dMatrix = new Base[2, 2];
Base[,] My2dMatrixSingleLine = new Base[,] {
{ new Base(), new Base() },
{ new Base(), new Base() }
};
System.Console.ReadKey();
}
}
}
C# 中我们可以使用每一行长度相同的矩阵风格,也可以使用锯齿状的数组。我们可以把申明、定义和初始化写在同一行。当我看到上面的语法时,我觉得现在语言有很严重的缺点。我很难记住如何使用那些括号来初始化数组,而且到处都是 new 语句……。幸好我需要用到多维数组的时候并不多。实事上,数组并不是非常容易看到。不过至少本文中有一个参考,你可以参考着如何使用数组!
最后让我们把来看看 C++ 的多维数组。然后我们将讨论数组是在内存的什么地方被创建的,以及当进行参数传递时类型的变化。
在 C++ 中,多维数组是这样创建的:
MyArrayStuff::Base MyMulti[5][4][2][2];
int a[3][4] = {
{ 0, 1, 2, 3 } , /* initializers for row indexed by 0 */
{ 4, 5, 6, 7 } , /* initializers for row indexed by 1 */
{ 8, 9, 10, 11 } /* initializers for row indexed by 2 */
};
数组的维度是没有限制的,或许对你来说多于 4 个维度有存在的意义,可能你已经有这么一个像《2001太空漫游》般的庞然大物。
按值或按引用传递数组
C# 和 Java 都有数组类的类型,它们都从 Object 这个基类派生出来。因此数组被认为是引用类型(并在堆上分配空间 - 我刚才说的?)。无论如何,这表示 C# 中的参数是数组引用的拷贝,你可以改变数组引用其它对象,但不能改变数组本身(只能改变它的内容)。
Java 中一切都是拷贝(它没有 ref 或 out),所以上述内容也适用于 Java。C++ 中可以使用数组的引用,那么我们来讨论与 C++ 相关的类型,以及数组是如何作为参数传递的。
在 Java 中传递数组的方式:
package com.roboticsfordreamers;
import java.io.IOException;
class Base
{
private String animal;
public void print()
{
System.out.println("Base Animal is : " + animal);
}
public Base(String an)
{
animal = an;
System.out.println("Base Constructor called with Animal : " + animal);
}
}
public class Main
{
public static void FillMyArray(Base[] MyArray, int count)
{
if (MyArray.length != count)
return;
MyArray[0] = new Base("Dog");
MyArray[1] = new Base("Horse");
MyArray[2] = new Base("Lizard");
}
public static void main(String[] args)
{
final int NumberOfAnimals = 3;
Base[] MyArray = new Base[NumberOfAnimals];
FillMyArray(MyArray, NumberOfAnimals);
for (Base b : MyArray)
{
b.print();
}
}
}
Java 代码的输出是这样的:
Base Constructor called with Animal : Dog
Base Constructor called with Animal : Horse
Base Constructor called with Animal : Lizard
Base Animal is : Dog
Base Animal is : Horse
Base Animal is : Lizard
Process finished with exit code 0
所以在 Java 和 C# 中很容易使用数组参数。
C++ 中则有更多选择。首先,简单地传递数据可以这样定义:
void myFunc(MyClass myArray[])
不过我像下面这样传递一个数组进去的时候,看看会发生什么事情:
#include "stdafx.h"
using namespace std;
class MyClass
{
private:
string animal;
public:
friend std::ostream& operator<<(std::ostream& os,const MyClass& obj)
{
os << obj.animal;
return os;
}
MyClass(string an)
{
animal = an;
}
};
void printThemAll(MyClass ans[], int numberOfItems)
{
int szMyClass = sizeof(ans[0]);
int szand = sizeof(ans);
int count = szand / szMyClass;
cout << "The size of the first element AFTER I pass it into a function is " << szMyClass << endl;
cout << "The size of the an instance of MyClass AFTER I pass it into a function is " << szand << endl;
cout << "The array length AFTER I pass it into a function is " << count << endl;
cout<<"Print all together now ";
for (int index=0; index<numberOfItems-1; index++)
cout<<ans[index]<<", ";
cout<<ans[numberOfItems-1]<<endl;
}
int main()
{
cout<<"Hello - create an MyClass with Dog"<<endl;
MyClass f("Dog");
cout<<f<<endl;
MyClass ans[3] = { MyClass("Dog"), MyClass("Snake"), MyClass("Lion")};
int szMyClass = sizeof(ans[0]);
int szand = sizeof(ans);
int count = szand / szMyClass;
cout<<"The array length BEFORE I pass it into a function is "<<count<<endl;
cout<<"The size of the first element BEFORE I pass it into a function is "<<szMyClass<<endl;
cout<<"The size of the an instance of MyClass BEFORE I pass it into a function is "<<szand<<endl;
printThemAll(ans, count);
char g;
cin >> g;
return 0;
}
输出是这样的:
Hello - create an MyClass with Dog
Dog
The array length BEFORE I pass it into a function is 3
The size of the first element BEFORE I pass it into a function is 28
The size of the an instance of MyClass BEFORE I pass it into a function is 84
The size of the first element AFTER I pass it into a function is 28
The size of the an instance of MyClass AFTER I pass it into a function is 4
The array length AFTER I pass it into a function is 0
Print all together now Dog, Snake, Lion
sizeof 的返回值是 4,而不是 28。这是因为 C++ 会在把数组传入函数之前把 [] 转换成指针。本质上 [] 就是指针,它就是第一个元素的地址。所以小心这个数组退变成指针。这只会在按值传递数组的时候发生,你要考虑这是不是你想要的?传入的数组的拷贝中,是否每个元素也是原数组内容的拷贝?这很难想明白(比如,深/浅拷贝)。
你可以使用数组引用来传递数组以避免退变。但是你必须告诉编译器你的数组大小。把数组作为引用参数会像这样写:
#include "stdafx.h"
using namespace std;
class MyClass
{
private:
string animal;
public:
friend std::ostream& operator<<(std::ostream& os,const MyClass& obj)
{
os << obj.animal;
return os;
}
MyClass(string an)
{
animal = an;
}
};
void printThemAllV2(MyClass (&ans)[3])
{
int szMyClass = sizeof(ans[0]);
int szand = sizeof(ans);
int count = szand / szMyClass;
cout << "The size of the first element AFTER I pass it into a function as a pointer is " << szMyClass << endl;
cout << "The size of the an instance of MyClass AFTER I pass it into a function as a pointer is " << szand << endl;
cout << "The array length AFTER I pass it into a function as a pointer is " << count << endl;
}
void printThemAll(MyClass ans[], int numberOfItems)
{
int szMyClass = sizeof(ans[0]);
int szand = sizeof(ans);
int count = szand / szMyClass;
cout << "The size of the first element AFTER I pass it into a function is " << szMyClass << endl;
cout << "The size of the an instance of MyClass AFTER I pass it into a function is " << szand << endl;
cout << "The array length AFTER I pass it into a function is " << count << endl;
cout<<"Print all together now ";
for (int index=0; index<numberOfItems-1; index++)
cout<<ans[index]<<", ";
cout<<ans[numberOfItems-1]<<endl;
}
int main()
{
cout<<"Hello - create an MyClass with Dog"<<endl;
MyClass f("Dog");
cout<<f<<endl;
MyClass ans[3] = { MyClass("Dog"), MyClass("Snake"), MyClass("Lion")};
int szMyClass = sizeof(ans[0]);
int szand = sizeof(ans);
int count = szand / szMyClass;
cout<<"The array length BEFORE I pass it into a function is "<<count<<endl;
cout<<"The size of the first element BEFORE I pass it into a function is "<<szMyClass<<endl;
cout<<"The size of the an instance of MyClass BEFORE I pass it into a function is "<<szand<<endl;
printThemAll(ans, count);
printThemAllV2(ans);
char g;
cin >> g;
return 0;
}
输出结果:
Hello - create an MyClass with Dog
Dog
The array length BEFORE I pass it into a function is 3
The size of the first element BEFORE I pass it into a function is 28
The size of the an instance of MyClass BEFORE I pass it into a function is 84
The size of the first element AFTER I pass it into a function is 28
The size of the an instance of MyClass AFTER I pass it into a function is 4
The array length AFTER I pass it into a function is 0
Print all together now Dog, Snake, Lion
The size of the first element AFTER I pass it into a function as a pointer is 28
The size of the an instance of MyClass AFTER I pass it into a function as a pointer is 84
The array length AFTER I pass it into a function as a pointer is 3
删除数组及其内容
在 C# 和 Java 中,实例或者对象如果不再使用,就会被标记然后由垃圾回收器删除掉。所以删除数组就有可能会删除数组中保存的实例,当然是在只有数组引用了它们的情况下。这对你来说不是问题,问题在于垃圾回收是个重量级的魔法。
现在来说 C++,它没有垃圾回收,所以你必须自己删除那些实例,这恐怕比你想像的要直接一些。
首先,这里有一些在栈上自动生成的对象,它会自己创建也会自然释放,这不是问题,数组离开作用域的时候,它内部的实例也也如此:
这是输出:
Hello - create an MyClass with Dog
Dog
Thankful this Lion instance is now gone !
Thankful this Snake instance is now gone !
Thankful this Dog instance is now gone !
Thankful this Dog instance is now gone !
不过如果在数组中使用指针,你就有麻烦了。这里有一个典型的内存泄漏问题(如果失去对实例的引用,就意味着我们永远无法再释放这些实例所战用的内存,它们离开作用域的时候就是如此):
// ConsoleApplication4.cpp : Defines the entry point for the console application.
//
#include "stdafx.h"
using namespace std;
class MyClass
{
private:
string animal;
public:
friend std::ostream& operator<<(std::ostream& os,const MyClass& obj)
{
os << obj.animal;
return os;
}
~MyClass()
{
cout<<"Thankful this " << animal <<" instance is now gone ! "<<endl;
}
MyClass(string an)
{
animal = an;
}
};
int main()
{
{
cout<<"Hello - create an MyClass with Dog"<<endl;
MyClass *f = new MyClass("Dog");
cout<<*f<<endl;
MyClass *ans[3] = { new MyClass("Dog"), new MyClass("Snake"), new MyClass("Lion")};
delete f;
}
char g;
cin >> g;
return 0;
}
输出结果:
Hello - create an MyClass with Dog
Dog
Thankful this Dog instance is now gone !
你会注意到我们并没有释放第二只狗、蛇以及狮子占用的内存。因此我们需要显式的删除数组的内容,但没什么效果:
delete ans;
必须这样才行 :
delete []ans;
下面是完整例子:
// ConsoleApplication4.cpp : Defines the entry point for the console application.
//
#include "stdafx.h"
using namespace std;
class MyClass
{
private:
string animal;
public:
friend std::ostream& operator<<(std::ostream& os,const MyClass& obj)
{
os << obj.animal;
return os;
}
~MyClass()
{
cout<<"Thankful this " << animal <<" instance is now gone ! "<<endl;
}
void setAnimal(string an)
{
animal = an;
}
MyClass()
{
}
};
int main()
{
{
MyClass *ans = new MyClass[3];
ans[0].setAnimal("Dog");
ans[1].setAnimal("Snake");
ans[2].setAnimal("Lion");
delete []ans;
}
char g;
cin >> g;
return 0;
}
输出:
Thankful this Lion instance is now gone !
Thankful this Snake instance is now gone !
Thankful this Dog instance is now gone !
现在需要考虑另外一个问题,如果你在某个函数中填充数组,但数组并不是在这个函数中实例化的,会怎么样。
看看下面的 C++ 示例,它创建一个数组,然后以传递给另一个函数来进行填充。先来看看数组中使用的实例:
//This program shows the fact we can not populate the array with instances
//that we can access in the calling function in this case main, the
//instances go out of scope !
#include "stdafx.h"
#include <iostream>
#include <string>
using namespace std;
namespace roboticsfordreamers
{
class Base
{
private:
static int counter;
public:
Base()
{
counter++;
cout << "Base class constructor, there are now " << counter << " instances of Base." << endl;
}
Base(const Base&)
{
counter++;
cout << "Base class COPY constructor, there are now " << counter << " instances of Base." << endl;
}
~Base()
{
counter--;
cout << "Base class memory freed, there are now " << counter << " instances of Base." << endl;
}
};
int Base::counter = 0;
static void fillArray(Base myArray[])
{
cout << "We are inside the function now" << endl;
for (int i = 0; i < 10; i++)
myArray[i] = Base();
cout << "We are inside the function now and have finished adding instances" << endl;
}
}
int main()
{
using namespace roboticsfordreamers;
{
//Array of 10 Base instances
//need a default constructor for this
cout << "We are inside main and about to create our array" << endl;
Base MyArray[10];
//we can not populate the array with instances in another function as they go out of scope !
cout << "We are inside the main function and are about to call the populate function now" << endl;
fillArray(MyArray);
}
cout << "We are inside the main function and are about to exit" << endl;
char a;
cin >> a;
return 0;
}
输出结果:
We are inside main and about to create our array
Base class constructor, there are now 1 instances of Base.
Base class constructor, there are now 2 instances of Base.
Base class constructor, there are now 3 instances of Base.
Base class constructor, there are now 4 instances of Base.
Base class constructor, there are now 5 instances of Base.
Base class constructor, there are now 6 instances of Base.
Base class constructor, there are now 7 instances of Base.
Base class constructor, there are now 8 instances of Base.
Base class constructor, there are now 9 instances of Base.
Base class constructor, there are now 10 instances of Base.
We are inside the main function and are about to call the populate function now
We are inside the function now
Base class constructor, there are now 11 instances of Base.
Base class memory freed, there are now 10 instances of Base.
Base class constructor, there are now 11 instances of Base.
Base class memory freed, there are now 10 instances of Base.
Base class constructor, there are now 11 instances of Base.
Base class memory freed, there are now 10 instances of Base.
Base class constructor, there are now 11 instances of Base.
Base class memory freed, there are now 10 instances of Base.
Base class constructor, there are now 11 instances of Base.
Base class memory freed, there are now 10 instances of Base.
Base class constructor, there are now 11 instances of Base.
Base class memory freed, there are now 10 instances of Base.
Base class constructor, there are now 11 instances of Base.
Base class memory freed, there are now 10 instances of Base.
Base class constructor, there are now 11 instances of Base.
Base class memory freed, there are now 10 instances of Base.
Base class constructor, there are now 11 instances of Base.
Base class memory freed, there are now 10 instances of Base.
Base class constructor, there are now 11 instances of Base.
Base class memory freed, there are now 10 instances of Base.
We are inside the function now and have finished adding instances
Base class memory freed, there are now 9 instances of Base.
Base class memory freed, there are now 8 instances of Base.
Base class memory freed, there are now 7 instances of Base.
Base class memory freed, there are now 6 instances of Base.
Base class memory freed, there are now 5 instances of Base.
Base class memory freed, there are now 4 instances of Base.
Base class memory freed, there are now 3 instances of Base.
Base class memory freed, there are now 2 instances of Base.
Base class memory freed, there are now 1 instances of Base.
Base class memory freed, there are now 0 instances of Base.
We are inside the main function and are about to exit
当我们创建一个数组时,首先会填充 10 个空实例。然后我们将这个数组传递给函数进行数据填充。我们在数组中存放一个新的实例,紧接着我们返回数组进行下一次迭代时,这个数据就会被破坏掉。 当我们返回调用函数时,这些实例就会丢失。而 main 函数结束时,原先的 10 个实例就超出了范围,这样至少我们清理过了。
因此我们在数组中改用指针的方式,并传递给函数:
//这个问题展示了当我们使用指针进行填充时由于没有清理数组导致的内存泄漏
#include "stdafx.h"
#include <iostream>
#include <string>
using namespace std;
namespace roboticsfordreamers
{
class Base
{
private:
static int counter;
public:
Base()
{
counter++;
cout << "Base class constructor, there are now " << counter << " instances of Base." << endl;
}
Base(const Base&)
{
counter++;
cout << "Base class COPY constructor, there are now " << counter << " instances of Base." << endl;
}
~Base()
{
counter--;
cout << "Base class memory freed, there are now " << counter << " instances of Base." << endl;
}
};
int Base::counter = 0;
static void fillArray(Base* myArray[])
{
cout << "We are inside the function now" << endl;
for (int i = 0; i < 10; i++)
myArray[i] = new Base();
cout << "We are inside the function now and have finished adding instances" << endl;
}
}
int main()
{
using namespace roboticsfordreamers;
//Array of pointers to 10 Base instances - DO NOT need a default constructor for this
cout << "We are inside main and about to create our array" << endl;
Base* MyArray[10];
//we can not safely populate the array with instances in another function as they do NOT go out of scope !
cout << "We are inside the main function and are about to call the populate function now" << endl;
fillArray(MyArray);
cout << "We are inside the main function and are about to exit" << endl;
char a;
cin >> a;
return 0;
}
输出结果如下:
We are inside main and about to create our array
We are inside the main function and are about to call the populate function now
We are inside the function now
Base class constructor, there are now 1 instances of Base.
Base class constructor, there are now 2 instances of Base.
Base class constructor, there are now 3 instances of Base.
Base class constructor, there are now 4 instances of Base.
Base class constructor, there are now 5 instances of Base.
Base class constructor, there are now 6 instances of Base.
Base class constructor, there are now 7 instances of Base.
Base class constructor, there are now 8 instances of Base.
Base class constructor, there are now 9 instances of Base.
Base class constructor, there are now 10 instances of Base.
We are inside the function now and have finished adding instances
We are inside the main function and are about to exit
现在我们就会看到了内存泄露问题,所以我们增加了 delete[] ,如下所示:
int main()
{
using namespace roboticsfordreamers;
//Array of pointers to 10 Base instances - DO NOT need a default constructor for this
cout << "We are inside main and about to create our array" << endl;
Base* MyArray[10];
//we can not safely populate the array with instances in another function as they do NOT go out of scope !
cout << "We are inside the main function and are about to call the populate function now" << endl;
fillArray(MyArray);
cout << "We are inside the main function and are about to use the delete[]" << endl;
delete[] MyArray;
cout << "We are inside the main function and are about to exit" << endl;
char a;
cin >> a;
return 0;
}
当我们使用 delete[] 时输出如下:
We are inside main and about to create our array
We are inside the main function and are about to call the populate function now
We are inside the function now
Base class constructor, there are now 1 instances of Base.
Base class constructor, there are now 2 instances of Base.
Base class constructor, there are now 3 instances of Base.
Base class constructor, there are now 4 instances of Base.
Base class constructor, there are now 5 instances of Base.
Base class constructor, there are now 6 instances of Base.
Base class constructor, there are now 7 instances of Base.
Base class constructor, there are now 8 instances of Base.
Base class constructor, there are now 9 instances of Base.
Base class constructor, there are now 10 instances of Base.
We are inside the function now and have finished adding instances
We are inside the main function and are about to use the delete[]
delete[] 导致程序崩溃或者一直在做删除操作 —— 未定义行为!
正确的方式是传递数组,然后进行填充,然后手工删除实例,如下所示:
//The correct way to delete the array contents when we are using pointers
#include "stdafx.h"
#include <iostream>
#include <string>
using namespace std;
namespace roboticsfordreamers
{
class Base
{
private:
static int counter;
public:
Base()
{
counter++;
cout << "Base class constructor, there are now " << counter << " instances of Base." << endl;
}
Base(const Base&)
{
counter++;
cout << "Base class COPY constructor, there are now " << counter << " instances of Base." << endl;
}
~Base()
{
counter--;
cout << "Base class memory freed, there are now " << counter << " instances of Base." << endl;
}
};
int Base::counter = 0;
static void fillArray(Base* myArray[])
{
cout << "We are inside the function now" << endl;
for (int i = 0; i < 10; i++)
myArray[i] = new Base();
cout << "We are inside the function now and have finished adding instances" << endl;
}
}
int main()
{
using namespace roboticsfordreamers;
Base* MyArray[10]; //Array of 10 Base instances - need a default constructor for this
fillArray(MyArray); //we can not populate the array with instances in another function as they go out of scope !
//Safe way to delete contents of array
for (int i = 0; i < 10; i++)
delete MyArray[i];
char a;
cin >> a;
return 0;
}
好了,输出的结果如下:
We are inside the function now
Base class constructor, there are now 1 instances of Base.
Base class constructor, there are now 2 instances of Base.
Base class constructor, there are now 3 instances of Base.
Base class constructor, there are now 4 instances of Base.
Base class constructor, there are now 5 instances of Base.
Base class constructor, there are now 6 instances of Base.
Base class constructor, there are now 7 instances of Base.
Base class constructor, there are now 8 instances of Base.
Base class constructor, there are now 9 instances of Base.
Base class constructor, there are now 10 instances of Base.
We are inside the function now and have finished adding instances
Base class memory freed, there are now 9 instances of Base.
Base class memory freed, there are now 8 instances of Base.
Base class memory freed, there are now 7 instances of Base.
Base class memory freed, there are now 6 instances of Base.
Base class memory freed, there are now 5 instances of Base.
Base class memory freed, there are now 4 instances of Base.
Base class memory freed, there are now 3 instances of Base.
Base class memory freed, there are now 2 instances of Base.
Base class memory freed, there are now 1 instances of Base.
Base class memory freed, there are now 0 instances of Base.
所以要记住当你使用 delete[] 操作符来删除一个实例时,当你使用数组的指针或者引用时,请确认你从内存堆中删除每一个实例。每一个 new 操作都应该有一个相匹配或者对应的 delete 操作。
另外一个关于 C++ 数组的评论是,当你创建一个数组,但它初始化时创建的实例并不是你真正想要的,那会非常浪费时间。别忘了,创建数组的时候它会同时创建所有实例。所以如果你这样定义数组:
MyClass MyArray[10,00,000]; //THE Commas are there so you can read the number - not valid syntax !
稍后你将这个数组传入某个函数来进行设置(比如从磁盘读取),来看看会发生什么事情。首先会调用 10,000,000 次 MyClass 的无参数构造函数,然后填充数组的时候又会再调用 10,000,000 次。在拷贝拥有数组实例作为属性的类对象时,也会发生同样的事情。所以,另一个使用指针数组或引用数组的原因就是避免这种事情发生。
对于 C++ 数组还有一点需要注意的就是它的大小。数组可以变得非常巨大,如果在局部作用域内定义,它会占用很大的栈内存,而更令人担忧的是,如果 new 出来的对象乱放的会会占用大量堆空间。如果你不需要它们,就删除它们,不要放在代码的最后来做这个事情。另外,记住 10,000,000 大小的指针数组与 10,000,000 大小的实例相比,小得可怜,所以如果你不需要所以这些实例,就不要为它们分配空间,应该使用指针数组!
下一篇:JavaScript:React中setState的同步更新方法
扫码关注微信公众号了解更多详情
跟技术大咖,专业导师一起交流学习
- 推荐阅读