初步了解指针(二)

数组指针
1、理解两种概念:
数组指针与指针数组

int arr[5];//数组
int *p[5];//指针数组
int (*p[5];//数组指针
int (*p[10][5];//存放指针数组的数组

数组指针(也称行指针):定义 int (*p)[n];

  • ()优先级高,首先说明p是一个指针,指向一个整型的一维数组,这个一维数组的长度是n,也可以说是p的步长。也就是说执行p+1时,p要跨过n个整型数据的长度。
  • 如要将二维数组赋给一指针,应这样赋值:
    指针+1,加上其所指向类型的大小
int a[3][4] ={ {1,2,3,4},{4,5,6,7},{8,9,0,1} };
    int(*pi)[4] = a;
    int arr[4] = {1,2,0,8 };
    int(*p)[4] = arr;
    printf("%p\n", pi); //0115FBDC
    printf("%p\n", pi+1);//0115FBEC 二维指针发生降维,指针是指向一维数组的指针;
    printf("%d\n", **(pi + 1));//4
    printf("%p\n", p);//0115FBB8
    printf("%p\n", p+1);//0115FBC8  一维数组的数组指针;
    printf("%d\n",*(*(p+1)-1));//8

指针数组:定义 int *p[n];

  • []优先级高,先与p结合成为一个数组,再由int*说明这是一个整型指针数组,它有n个指针类型的数组元素。

  • 执行p+1时,则p指向下一个数组元素,这样赋值是错误的:p=a;因为p是个不可知的表示,只存在p[0]、p[1]、p[2]…p[n-1],而且它们分别是指针变量可以用来存放变量地址。

  • *p=a; 这里*p表示指针数组第一个元素的值,a的首地址的值。

  • 将二维数组赋给一指针数组:

    int b[3][4] = { {1},{2},{3} };
    int *p1[3];
    int i = 0;
    for (; i < 3; i++) {
        p1[i] = b[i];
        printf("%d ", *p1[i]);//1 2 3
        printf("\n");
        printf("%p ", p1[i]);//0136F958(&b)  0136F968  0136F978
        printf("\n");
    }

2、储存数组地址

    int arr[10] = {1,2, 0 };
    int(*p)[10] = &arr;//数组指针
    printf("%p\n", p);   //001AFBEC(指向数组地址的指针)
    printf("%p\n", &arr);//001AFBEC(数组的地址)
    printf("%p\n", *p);  //001AFBEC(解引用之后的内容为&arr)
    printf("%d\n", **p);     //1(数组第一个元素的内容)

3、指针与数组的定义与声明
- 定义:(开辟空间)让不存在的存在;
- 声明:(不开辟空间)让不知道的知道;
- 声明其实就是定义的变量本身,因此,声明与定义必须保持一致。
数组参数与指针参数
1、一维数组传参:

int a[10] = {0};    //int a   int a[10]    int *a
int *b[20] = { 0 };// int *b  int *b[10]  int **b

2、二维数组传参:

int a[1][5] = { 0 };//int a[][5] int a[1][5] int (*a)[5]

3、一级指针传参:
参数为一级指针的情况:一维数组(类型区别),一维指针,

int main(){
int a[5]={2,3,4,5,1};
int *p=a;
print(p,2);//(int *p,int n)
}

4、二级指针传参:
参数为二级指针的情况:二级指针,指针数组,一级指针地址

int n=10;
int *p=&n;
int **pp=&p;
test(pp);// int **ppa

函数指针
1、函数指针就是函数的指针;(入口地址)
定义:void (*pfun)()

 char * (*fun1)(char * p1,char * p2);
 //fun1不是什么函数名,而是一个指针变量,它指向一个函数。这个函数有两个指针类型的参数,函数的返回值也是一个指针。
 //(改写之后为 char * (*)(char * p1,char * p2) fun1;)
 char * *fun2(char * p1,char * p2);
 //函数的返回值类型为char**,是个二级指针。
 char * fun3(char * p1,char * p2);
 //fun3是函数名,p1,p2是参数,其类型为char *型,函数的返回值为char *类型。
  • 在Visual C++6.0里,给函数指针赋值时,可以用&fun或直接用函数名fun。这是因为函数名被编译之后其实就是一个地址,所以这里两种用法没有本质的差别。
    两段有趣的代码
1(*(void(*)())0()//将0强转成返回值为void的函数指针,并且调用这个指针指向的函数。
 -  float (*h)()//h是返回值为float的函数指针。
 -  (float(*)())//类型强转符
 -  (*p)()//若p为函数指针,调用p所指向的函数
2、
void (*signal(int ,void(*)(int)))(int)
//返回值为void(*)(int)的函数指针

2、函数指针数组、函数指针的数组的指针

  • 函数指针数组: 存放函数地址的数组;

    • int (*p[10]) ()
    • 函数指针的用途:转移表;
  • 函数指针数组的指针:指向指针数组元素都是函数指针的指针

回调函数
一个通过函数指针调用的函数。其不是由该函数的实现方法直接调用,而是通过特定的事件或条件发生时由另外的一方调用的,用于对该事件或条件的响应。
实例:模拟实现qsort(冒泡法)链接如下:(https://blog.youkuaiyun.com/fayfayfaydyt/article/details/80465345))

计算器实例

#pragma warning (disable:4996)
int add(int a,int b) {
    return a + b;
}
int mul(int a, int b) {
    return a * b;
}
int sub(int a, int b) {
    return a - b;
}
int dv(int a, int b) {
    if (b == 0) {
        printf("div zero!error!\n");
        return 0;        //分母为0;
    }
    return a / b;
}
void memu( ) {
    printf("******************************\n");
    printf("***** 1、add     3、sub  *****\n");
    printf("***** 2、mul     4、div  *****\n");
    printf("*****       0、quit      *****\n");
    printf("******************************\n");

}
int main() 
{
    int x, y;
    int input = 0;
    int ret = 0;
    int (*p[5])(int ,int )= { 0,add,sub,mul,dv };//函数指针数组,p[0],p[1],p[2]...p[5];
    do {
        memu();
        printf("请选择\n");
        scanf("%d", &input);
        if ((input <= 4 && input>0))//判断输入是否满足循环
        {
                printf("输入操作数:");
                scanf("%d%d", &x, &y);
                ret = p[input](x, y);//调用函数p[input]
                printf("%d\n", ret);  
        }
            else if (input==0)
            {
                break;
            }
            else {
                printf("输入有误\n");
            }
    } while (1);    
    system("pause");
    return 0;
}

综上,指针就是地址,地址的地址叫二级指针,地址的数组叫指针数组,指向数组的指针叫数组指针,指向函数的指针叫函数指针。

IAR Embedded Workbench 是一款广泛应用于嵌入式系统开发的集成开发环境(IDE),支持多种微控制器架构,如 ARM Cortex-M 系列、STM8、AVR、MSP430 等。它提供了一整套开发工具链,包括编辑器、编译器、链接器、调试器以及静态代码分析工具,适用于从项目创建到调试、优化的全流程开发需求[^1]。 ### IAR Embedded Workbench 的主要功能模块包括: - **编辑器**:支持语法高亮、代码折叠、自动补全等功能,提升代码编写效率。 - **编译器**:基于 C/C++ 标准,支持 ANSI C、C99 和 C++11 等标准,具备高效的优化能力。 - **链接器(ILINK)**:用于将多个目标文件和库文件链接为可执行文件,支持分散加载(scatter loading)功能,适用于复杂内存布局的嵌入式系统[^1]。 - **调试器(C-SPY)**:支持硬件仿真器和软件仿真,提供断点、单步执行、变量观察、寄存器查看等功能。 - **静态分析工具(C-RUN)**:在运行时检测运行时错误,如数组越界、空指针解引用等。 - **插件支持**:例如 AStyle 插件可用于代码格式化,支持多种风格配置,如 Allman 缩进风格、操作符填充、最大代码行长度限制等。 ### IAR 的启动流程与初始化 在基于 Cortex-M3 的项目中,IAR 会在 `main()` 函数执行之前完成一系列初始化操作。这些操作通常包括: - **设置堆栈指针**:从复位向量开始,初始化主堆栈指针(MSP)。 - **执行复位处理函数**:进入 `Reset_Handler`,该函数通常由启动文件(startup 文件)定义。 - **初始化 .data 和 .bss 段**:将初始化数据从 Flash 拷贝到 RAM,并将未初始化数据清零。 - **调用系统初始化函数**:例如 `SystemInit()`,用于配置系统时钟。 - **跳转到 `main()` 函数**:完成上述初始化后,进入用户主函数 `main()`。 ### 工程结构与文件组织 在 IAR 中组织工程时,通常会创建多个文件组(Group),用于分类管理源代码文件。例如: - **User Group**:存放用户自定义的 `.c` 和 `.h` 文件。 - **Lib Group**:存放第三方库文件或官方提供的驱动库。 - **Startup Group**:存放启动文件(如 `startup_stm32f10x_md.s`),用于定义中断向量表和复位处理程序[^2]。 通过合理组织文件结构,可以提升工程的可维护性和可读性。 ### 分散加载(Scatter Loading) 分散加载是一种将代码和数据分配到不同内存区域的技术。在 IAR 中,通过链接器脚本(`.icf` 文件)定义内存布局和段分配规则。例如: ```c /* Linker script example for IAR */ define memory with size = 4G; define region ROM = mem:[from 0x08000000 to 0x0807FFFF]; define region RAM = mem:[from 0x20000000 to 0x2000FFFF]; place at address mem: 0x08000000 { readonly section .intvec }; place in ROM { readonly }; place in RAM { readwrite }; ``` 该脚本定义了 Flash(ROM)和 RAM 的地址范围,并定 `.intvec` 段位于 Flash 起始地址,其余只读段也放在 ROM,而可读写段则放在 RAM 中[^1]。 ### 入门南 1. **安装与配置**:下载并安装 IAR Embedded Workbench,选择对应的目标芯片型号。 2. **创建新工程**:选择“File > New > Project”,选择目标设备,创建空白工程或模板工程。 3. **添加文件组与文件**:右键点击工程,选择“Add Group”,并为不同类型的文件建立组。 4. **配置编译选项**:包括优化等级、调试信息、预处理器宏定义等。 5. **配置链接器脚本**:根据硬件内存布局修改 `.icf` 文件。 6. **编译与下载**:点击“Project > Rebuild All”进行编译,使用调试器将程序下载到目标设备。 7. **调试与优化**:使用 C-SPY 调试器进行单步调试、变量观察等操作。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值