数组

定义

在Java中,通过int[] a这样的写法定义数组类型就非常简单明了,但是在C++中偏偏不能这么写,还必须写成int a[],将括号放在变量后。建议是,按照Java的数组写法理解数组的逻辑,但是书写的时候转换成C++的写法。

按照Java的方式理解数组,很多问题迎刃而解。比如说,在函数传参时传入数组,一定要在参数列表中指定数组除了第一维之外的维数,例如可以写成void f(int a[][2])但是不能写成void f(int a[3][]) 。这是因为int a[][2]实际上是(int[2])[] a,即a是一个数组,数组的每个单元是一个长度为2的整数数组。编译器必须知道数组内每个元素的空间大小,以便能够正确计算偏移量;编译器不需要知道数组自身的长度,因此如果函数中需要用到数组自身长度,还需要再传入一个int型的参数进行告知。

另外,在定义数组的时候可以缺省第一维,但是不能不指定其他维度的具体长度(如果有更高的维度),也是这个道理。(但是如果没有用大括号初始化也没有用new,还是要指定数组总长度的,以便在栈中分配空间)

本质

在C++中,数组的本质是指向第一个元素的、类型为“数组中每个单元”类型的指针。除了在栈中定义数组,在定义它的环境中会保留一部分数组的属性,这时如果使用类似sizeof()这样的函数可以获得整个数组所占用的总空间。但是,一旦数组经过类似传参这样的操作,会马上原形毕露,实实在在就是指针,丢失除了所指地址之外的任何属性。

请看下面的例子:

#include <iostream>
using namespace std;

void f(int a[]){
    cout<<"f: "<<sizeof(a)<<endl;
}

void g(char c[]){
    cout<<"g: "<<sizeof(c)<<endl;
    cout<<"g: "<<c<<endl; 
    //幸运的是,cout对字符数组(字符指针)的输出进行了特殊处理,能够正确打印其中储存的字符串
}

int main(){
    int a[]={0,1,2,3,4,5};
    char c[]="abcde";
    int* b=new int[10];
    char *chars=c;
    cout<<sizeof(a)<<endl; // 输出24,每个整数4字节,一共6个数
    cout<<sizeof(c)<<endl; // 输出6,每个字符1字节,一共6字符(别忘了\0)
    f(a); // 输出8(或者4或者别的),这个数事实上是int*类型占用空间大小
    g(c); // 和上面输出一致,事实上是char*占用空间大小
    g(chars); //和上面完全一致。这里证明在传参时,数组完全被当做指针处理。
    // 传参之后丢失除了地址之外的一切性质。
}

因此,这也是多维数组能够升降维的关键(这部分放在指针中)。

结构体

不展开,和类基本一致,可以包含若干成员变量和方法。

唯一不同的是,结构体中的成员变量和方法不声明的情况下默认public,而类中默认private。

联合体 Union

联合体中可以定义多个成员变量,但是,里面的所有成员变量都共用同一段空间。一个联合体所占的空间取决于其成员变量中占用最大空间的那个。而且所有变量的开始地址都是对齐到联合体的开始地址的。

尽管联合体中的变量使用同一个空间,但是访问其中变量值的时候还是要用.的方式来访问,这样编译器才知道要把这段空间中的内容作为什么类型来解读。

举例:

#include <iostream>

using namespace std;

void checkCPU()
{
    union MyUnion{
        int a;
        char c;
    }test{};
    test.a = 1;
    if (test.c == 1)
        cout << "little endian" <<endl;
    else cout << "big endian" <<endl;
}

int main()
{
    checkCPU();
    return 0;
}

很经典的测试大小端的一段代码。放入一个整数1,结构体的内容为31个0后接1个1。这时尝试读取其中储存的字符(一个字节)。如果读到的字符还是1,说明内存中从低地址到高地址存的内容为01 00 00 00,字符为01,读整数时低地址在前高地址在后,读整数时低字节内容读到低位中,高字节内容读到高位中,是小端;如果读到的字符是0,说明内存中从低到高地址存的内容是00 00 00 01,字符为00,读整数时高地址在前低地址在后,低字节内容读入但是表示高位,高字节内容后读入但是表示低位,为大端。请注意不管是字符还是整数,开头地址都是联合体最低的那个字节地址。

Cpp内存中存储都是按照低地址到高地址的顺序存储的。联合体里面所有可能的类型会按低地址的开始位置对齐。

最后要补充的是,在栈中定义的数组变量、结构体变量或者联合体变量都能通过大括号的方式初始化。例如:

#include <iostream>
using namespace std;

struct test{
    int a;
    char b;
};

union gest{
    int a;
    char b;
};

int main()
{
    char chars[]={'a','b','c'};//数组初始化
    int nums[][2]={{1,2},{3,4}};//多维数组初始化
    test t={1,'h'};//结构体初始化
    gest g={4};//联合体初始化
    cout<<t.a<<' '<<t.b<<endl; //1 h
    cout<<g.a<<' '<<int(g.b)<<endl;//1 1
}