Structure Array 结构体数组

#前置知识

#指针常量和常量指针

谁对谁的名字总是记不住

1
2
int* const b;   /* 指针常量 */
const int* a;   /* 常量指针 */

观察一下形式,发现*号在左,const在右,从左往右读,指针常量。
反之const在左,*在右,从左往右读,常量指针。

#数组名和指针

之前一直都觉得:数组名就是数组开始地址的指针,包括我面试也是这么回答,现在学习到这是错误的或者说不严谨的。

在C/C++中。数组就是数组,指针就是指针,这是两个不同的类型。

  • 数组在内存空间中有确定的空间

    1
    2
    3
    4
    5
    
    int arr[4];
    cout << sizeof(arr) << endl;  /* 内存空间占用:16 */
    
    int* b;
    cout << sizeof(b) << endl;    /* 内存占用:8 (64位系统) */
    
  • 只不过有时候数组名会临时的表示数组中第一个元素的指针常量

    1
    2
    3
    
    int a[5] = {1, 2, 3, 4, 5};
    printf("a = %p \n", a);
    printf("&a = %p \n", &a);
    

    第一个 printf 中,a临时的代表指向第一个元素的常量指针。
    第二个 printf 中,a 就表示一个数组,与指针没有半毛钱的关系,前面加上取地址符 &,就表示获取这个数组所在的地址,这个地址与第一个元素的地址是重合的。

#为什么数组之间不能直接赋值

#左值理解

数组就是数组,指针就是指针
在llvm中间代码中,也是会分为数组类型和指针类型的:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
/// ElementType:数组元素的类型
/// NumElements:数组元素的个数
/// static ArrayType *llvm::ArrayType::get(Type *ElementType, uint64_t NumElements);
/// 例如:int [10];
llvm::ArrayType *arrType1D = llvm::ArrayType::get(Type::getInt32Ty(TheContext), 10);

/// ElementType: 指针指向的元素类型
/// AddressSpace:地址空间,0表示默认地址空间
/// static PointerType *llvm::PointerType::get(Type *ElementType, unsigned AddressSpace);
/// 例如:int *
llvm::Type *pointer = llvm::PointerType::get(Type::getInt32Ty(TheContext), 0);
1
2
3
int a[3] = {1, 2, 3};
int b[3];
b = a;  /* 表达式必须是可以修改的左值 */

在第三条赋值语句中,左侧的b是一个数组类型,不是一个左值,C/C++规定的左值只有标量和结构体,而数组名不可以作为左值放在赋值表达式左侧。

同样既然不是左值,那就无法对其进行运算:

1
2
int a[3] = {1, 2, 3};
a++;    /* "++"需要左值 */

#指针常量理解

在表达式中,数组名被临时的当作指针常量,那么

1
b = a;

肯定是不允许的,因为指针常量不允许改变自己的指向。同理:

1
2
a++;
a--;

也是不被允许的。

#结构体数组

结构体数组的好处是:

  • 可以直接进行赋值
  • 可以作为表达式返回
  • 可以作为参数传递
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
/* 定义报文结构:128字节的报文 */
typedef struct Packt {
	char packet[128];	/* 结构体数组 */
}Packet;

/* 出参: p_ptr */
void GetPacket1(Packet* p_ptr) {
	Packet res = { "123" };
	*p_ptr = res;
}

/* 返回值类型:Packet */
Packet GetPacket2() {
	Packet res = { "456" };
	return res;
}

int main() {
	Packet a = { "this is a packet" };
	/* 直接赋值 */
	Packet b = a;
	cout << b.packet << endl;	/* this is a packet */

	/* 使用参数赋值 */
	GetPacket1(&b);
	cout << b.packet << endl;	/* 123 */

	/* 使用返回值赋值 */
	b = GetPacket2();
	cout << b.packet << endl;	/* 456 */
	return 0;
}

但是这并没有直接操作结构体中的数组,操作的是结构体实例,而实例是一个左值。
直接操作里面的数组依然是不被允许的:

1
2
3
4
5
6
7
8
9
int main() {
	Packet a = { "this is a packet" };

	/* 直接操作结构体里的数组 */
	Packet b;
	b.packet = a.packet;	/* 表达式必须是可修改的左值 */
	b.packet = { 1, 2, 3 };	/* 表达式必须是可修改的左值 */
	return 0;
}

#结构体的初始化

只要声明结构体的时候使用了初始化,即使没有为所有成员都指定初始化,结构体的所有成员都会被初始化。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
typedef struct MyStruct {
	int len;
	int pkt[10];
	short a;
	long long int b;
}MyStruct;

int main() {
	MyStruct a;					/* 无初始化,所有成员都不会被初始化 */
	PrintStruct(&a);
	MyStruct b = { 0 };			/* 部分,但所有成员都会被初始化 */
	PrintStruct(&b);
	MyStruct c = { 1,{1,2,3} };	/* 同上 */
	PrintStruct(&c);
	return 0;
}

结构体初始化

声明在global或者用static的结构体,属于BSS段,就像其他变量一样,自动初始化为0,除非手动初始化。

updatedupdated2024-10-072024-10-07