指针与数组
- 指针与数组
- 指针数组
- 数组指针
- 多维数组
- 数组名的保存
- 结构体
- 定义结构体
- 定义结构体变量
- 使用typedef简化结构体声明
- 访问结构体成员
- 结构体内存分配
- 字节对齐
- 位域
- 定义位域
- 位域的限制
- 示例
指针与数组
指针数组和数组指针是两个不同的概念,它们涉及到指针和数组的组合使用。
指针数组
- 定义: 指针数组是一个数组,其中的每个元素都是一个指针。
- 示例:
int *ptrArray[5];
声明了一个包含5个元素的指针数组,每个元素都是一个指向整数的指针。 - 用途: 指针数组常用于存储一组指针,每个指针可以指向不同类型的数据或不同位置的数组。
#include <stdio.h>
int main()
{
char a[5] = {'a', 'b', 'c', 'd', 'e'};
char *p[5];
printf("sizeof(a) = %d\n", sizeof(a));
printf("sizeof(p) = %d\n", sizeof(p));
return 0;
}
输出
cd 'e:\CProject\output'
PS E:\CProject\output> & .\'指针数组.exe'
sizeof(a) = 5
sizeof(p) = 40
可以看到指针数组的大小是个数*对应类型的指针的大小
举例2
#include <stdio.h>
int main() {
int a = 1, b = 2, c = 3;
int *ptrArray[3] = {&a, &b, &c};
// 打印指针数组中每个元素指向的值
for (int i = 0; i < 3; i++) {
printf("%d ", *ptrArray[i]);
}
return 0;
}
输出
PS E:\CProject\output> cd 'e:\CProject\output'
PS E:\CProject\output> & .\'指针数组.exe'
1 2 3
在这个例子中,ptrArray
是一个包含3个指针的数组,每个指针分别指向整数变量 a
、b
和 c
。
数组指针
- 定义: 数组指针是一个指针,它指向一个数组。
- 示例:
int (*ptr)[5];
声明了一个指针,指向包含5个元素的整数数组。 - 用途: 数组指针常用于处理二维数组或作为指向动态分配数组的指针。
#include <stdio.h>
int main() {
int arr[3][5] = {{1, 2, 3, 4, 5}, {6, 7, 8, 9, 10}, {11, 12, 13, 14, 15}};
int (*ptr)[5] = arr;
// 打印数组指针指向的数组中的元素
for (int i = 0; i < 3; i++) {
for (int j = 0; j < 5; j++) {
printf("%d ", ptr[i][j]);
}
printf("\n");
}
return 0;
}
在这个例子中,ptr
是一个指针,指向包含5个元素的整数数组,它被初始化为指向二维数组 arr
的第一行。
总体而言,指针数组和数组指针提供了在数组和指针之间灵活切换的方式,依赖于实际需求选择使用哪一种形式。
多维数组
数组名的保存
- 定义一个指针,指向
int a[10];
的首地址 - 定义一个指针,指向
int b[5][6];
的首地址
#include <stdio.h>
int main()
{
int a[10] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
int b[3][4];
int *p1 = a;
int **p2 = b; // 这样是错误的,二维数组和二维指针没有关系
return 0;
}
这样是错误的,二维数组和二维指针没有关系
二维数组是一行一行读取的,一次就读取4列的数据
二维指针是存储线性的地址
正确的读取方式
#include <stdio.h>
int main()
{
int a[10] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
int b[3][4] = {{1, 2, 3, 4}, {5, 6, 7, 8}, {9, 10, 11, 12}};
int *p1 = a;
int(*p2)[4] = b; // 每次读4个
printf("%d\n\n", *p1);
printf("%d\n", *p2[0]);
printf("%d\n", *p2[1]);
printf("%d\n", *p2[2]);
return 0;
}
举例2
int c[2][3][4];
int (*p)[3][4]; //T
结构体
C语言中的结构体(struct)是一种用户自定义的数据类型,它允许你将不同类型的数据组合在一起。这使得你可以创建更复杂的数据结构来表示现实世界中的实体。一个结构体可以包含多个成员(member),每个成员都有自己的数据类型和名称。
下面是一些关于C语言结构体的基本概念和用法:
定义结构体
要定义一个结构体,你需要使用struct
关键字,后跟结构体的标签(可选),接着是花括号包围的一系列成员声明,最后以分号结束。
struct Point {
int x;
int y;
};
定义结构体变量
一旦定义了结构体,就可以声明该结构体类型的变量。有两种方式来做这件事:
- 在定义结构体的同时声明变量:
struct Point pt1, pt2;
- 先定义结构体,然后在其他地方声明变量:
struct Point;
// ... 之后某个地方
struct Point pt1, pt2;
如果你给结构体指定了标签(如上面例子中的Point
),那么可以在后续代码中仅使用struct Point
来声明新的变量。
使用typedef简化结构体声明
为了简化结构体变量的声明,通常会与typedef
一起使用:
typedef struct {
int x;
int y;
} Point;
这样,以后可以直接使用Point
作为类型名来声明结构体变量,而不需要再写struct Point
。
访问结构体成员
结构体成员通过点运算符.
来访问:
pt1.x = 10;
pt1.y = 20;
如果结构体是指针,则使用箭头运算符->
来访问成员:
Point *p = &pt1;
p->x = 30; // 等价于 (*p).x = 30;
结构体内存分配
结构体变量的内存是在声明时分配的。如果你需要动态分配结构体的内存,可以使用malloc()
或calloc()
函数。
Point *p = (Point *)malloc(sizeof(Point));
if (p != NULL) {
p->x = 40;
p->y = 50;
}
// 使用完后记得释放内存
free(p);
字节对齐
#include <stdio.h>
struct student
{
char x; // 4 字节对齐
int roll; // 4
float marks; // 4
};
int main()
{
struct student s1;
printf("struct size: %d\n", sizeof(s1));
return 0;
}
字节对齐:为了效率,希望牺牲一点空间换取时间的效率
分别看一下定义的顺序不同,占用的空间大小!
#include <stdio.h>
struct student
{
char x; // 2
short y; // 2
int roll; // 4
};
struct stu
{
char x; // 4
int roll; // 4
short y; // 4
};
int main()
{
struct student s1;
printf("struct size: %d\n", sizeof(s1));
struct stu s2;
printf("struct size: %d\n", sizeof(s2));
return 0;
}
由于定义的顺序不同,占用的空间大小也不同!
第一种定义方式里,char剩余3bytes,可以给short类型的变量使用,如下图所示:
第二种定义的顺序,在内存中的分布如下
位域
位域(bit-fields)是C语言中的一种特殊结构体成员,它允许你定义一个特定宽度的字段,以位为单位。位域主要用于需要紧凑存储或与硬件通信的场合,比如嵌入式系统编程。
在位域中,你可以指定每个成员占用的位数,这可以减少内存的使用量。然而,使用位域也可能会导致代码的可移植性问题,因为不同的编译器可能对位域的实现有所不同,包括它们如何打包和对齐数据。
定义位域
位域通过在结构体成员声明后添加冒号和位宽来定义。以下是位域的一个简单例子:
struct packed_struct {
unsigned int f1 : 1; // 1 bit
unsigned int f2 : 3; // 3 bits
unsigned int f3 : 4; // 4 bits
};
在这个例子中,packed_struct
包含三个位域成员:f1
、f2
和 f3
,分别占据1位、3位和4位。请注意,位域成员必须是整数类型(如int
、unsigned int
等),并且不能是数组或指针。
位域的限制
- 位域的大小:位域的最大宽度取决于底层整型的大小。例如,在大多数系统上,
unsigned int
通常是32位,所以你不能创建超过32位的位域。 - 匿名位域:有时会看到不带名称但带有宽度的位域,这样的位域用于填充或者对齐。例如,
unsigned int : 2;
将占用2位,但没有关联的变量名。 - 不可寻址性:由于位域是由多个位组成的,因此不能取位域成员的地址。
- 端序依赖:不同体系结构上的字节顺序(大端或小端)会影响位域的实际布局。
示例
这里有一个更完整的示例,展示了如何使用位域:
#include <stdio.h>
struct Flags {
unsigned int flag1 : 1;
unsigned int flag2 : 1;
unsigned int : 6; // 匿名位域,用于填充或对齐
unsigned int flag3 : 1;
};
int main() {
struct Flags flags;
flags.flag1 = 1;
flags.flag2 = 0;
flags.flag3 = 1;
printf("flag1: %d\n", flags.flag1);
printf("flag2: %d\n", flags.flag2);
printf("flag3: %d\n", flags.flag3);
return 0;
}
这个程序定义了一个Flags
结构体,其中包含了三个标志位,每个都只占用了1位。注意,我们还插入了一个6位的匿名位域,用于确保后续的位域从一个新的字节开始(假设编译器按照8位边界对齐)。
请记住,尽管位域提供了一种节省空间的方法,但在编写代码时应该考虑到它的可移植性和潜在的复杂性。如果你不需要精确控制位级别的数据,通常最好使用普通的结构体成员。
【嵌入式C语言】 https://www.bilibili.com/video/BV1RW411G7cr/?p=44&share_source=copy_web&vd_source=8af85e60c2df9af1f0fd23935753a933