嵌入式八股C语言---指针与数组篇

devtools/2025/3/11 11:19:32/

数组

  1. 数组是什么
    数组算是定义了一块连续的空间,数组名就是这块连续空间首地址的名字
    这块空间多大?—数组的长度乘以元素的类型得到 或者使用sizeof也行
    如何访问?—数组的起始地址 + 对应的偏移量
    数组的起始地址可以用数组名得到
  2. 一维数组和二维数组
  • 2.1 一维数组
    int a[2] = {1,2,3,4};//不会报错但是会warning 越界了
    从汇编也能看出来 确实只分配了8个字节 所以数组越界行为的后果都是未定义的
    .global a
    .data
    .align 2
    .type a, %object
    .size a, 8
  • 2.2二维数组
    int a[4][2] = {1,2,3}; //明确了大小 就是 4 * 2 * 4个字节,虽然部分没填满
    int b[][2] = {1,2}; // 告诉编译器自己推导大小是多大 根据给定的数据
    int c[][] = {5,6}; // 错误的初始化
        .global	a.data.align	2.type	a, %object.size	a, 32.global	b`	.align	2.type	b, %object.size	b, 8
  1. []运算符 与 *运算符
    a[x]运算符本质上就是
    (a + x)运算符
    不过这里注意 指针的++可不是单纯的递增1 而是和数据类型有关
  • 3.1 []里面的数可以是负数吗?
    当然可以 只要这个[]里面的表达式最后能算出来数 是啥都行
  • 3.2二维数组的运算
    数组名可以看做是这块连续内存的起始地址的别名
    • 对于一维数组
        int main(){int array[4] = {1,2,3};int count = 2;printf("%d %d %d\r\n",array[count],*(array + count),(array - count)[count]);  // 结果是 3 3 0printf("%d\r\n",a[3][0]);return 0;}
  • 二维数组呢?
    int a[4][2] = {1,2,3};
    a++相当于移动了多少个字节呢
    printf("%p %p\r\n",a,a + 1); // 0x55eae2a01020 0x55eae2a01028
    相当于移动了8个字节 此时a可以看做是这样类型 int (* a)[2];
  1. 数组指针与指针数组
  • 数组指针
    本质上是个指针 指向了一个数组
    int (*a)[2]; // 这是一个指针 指向一个数组 数组的大小是2 元素类型是int
  • 指针数组
    本质是个数组 只不过里面元素类型是指针
    int a[2]; // 这是一个长度为2的数组 数组里元素的类型是 int *
    因为指针的操作 每次++都是按照指针的类型自增地址的
    int p[8] = {1,2,3,4,5,6,7};
    int a[2];
    a = p;
    printf("%d %d %d",
    (a),
    (++a),a[2]); // []等于*
    所以我们可以通过指针数组实现跳着访问一维数组
        void test1(){int p[8] = {1,2,3,4,5,6,7};int (*a)[2] = (int (*) [2])p;int (*b)[2] = (int (*) [2])p;printf("%d %d %d\r\n",*(a)[0],*(++b)[0],a[2][0]);   // 答案为 1 3 5}
  1. 字符、字符串与字符数组
  • 字符串与字符数组
    实际上我们是用字符数组存储字符串 如果字符串是用""定义的 会自动往后面补上’\0’
    但是如果是 char a[]= {‘a’, ‘b’, ‘c’} 不手动添加’\0’的话是不会有的
    但是,字符数组会自动补0,so有时候就会发蒙就是因为这个
        #include <stdio.h>void test1(){char a[] = {'a','b','c'};   // 编译器认为字符数组长度为3char b[6] = {'a','b','c'};  // 长度为6 不够的全补0了char * c = "abc";printf("%d %d %d\r\n",strlen(a), strlen(b), strlen(c)); // 6 3 3printf("%d %d %d\r\n",sizeof(a), sizeof(b), sizeof(c)); // 3 6 8}int main(){test1();}// 为什么stelen(b)算对了呢 ,**后面没初始化的自动补0**了 你看a就没算对movb	$97, -33(%rbp)movb	$98, -32(%rbp)movb	$99, -31(%rbp)movl	$0, -30(%rbp)       // 补为0了movw	$0, -26(%rbp)movb	$97, -30(%rbp)movb	$98, -29(%rbp)movb	$99, -28(%rbp)
  • 转义字符
    我们最常用的就是 \r\n 和 \
    • \x转义字符–表示16进制
      char a = ‘\x15’; // 表示值为 0x15 = 21
      所以’\x’有范围 为’\x00 ---- \xff’
      '\xffff’就是不对的
      char sub1 = ‘\x15’;
      char sub2 = ‘\x1555’;
      printf("%d %d\r\n",sub1, sub2); // suib2 = 85 还是得看编译器怎么做
    • 八进制 \105 反斜杠后面的就默认是8进制的
      printf("\150\n"); //答案就是105
  1. 数组与指针的区别
  • 数组是连续的区间 指针指向的区间大小可不一定,也不一定连续(链表)
  • 同种类型指针之间可以直接赋值,数组只能一个个元素赋值
  • 概念不同
    数组:是同种类型的集合
    指针:里面保存的地址的值
  • 所占用的字节不同
  • 修改内容的方式不同
        char * c = "abc";char d[] = "abc";void test2(){c[0] = '5';d[0] = '5';printf("修改已完成");}// 编译不报错 但是直接段错误 为啥会段错误呢// 因为 char * c = "abc";是在只读段 而 char d[]是在数据段.globl	c.section	.rodata..................globl	d.data.type	d, @object.size	d, 4
  1. 数组作为参数传递----直接当做指针处理
    void test1(int array[]) {int n = sizeof(array) / sizeof(array[0]);  // 大错特错 因为这样传参相当于传入了一个指针 sizeof(array)会是4(32位)}int array[10] = {1,2}; // 可以把 array的类型看做是 int[10] 所以sizeof(array)就能算出来大小是 4 * 10// &array 就变成了 int (*)[0] 此时array + 1 就相当于走过了40个字节
  1. 实现排序
    什么冒泡啊 快排啊之类的

指针

  1. 指针是什么,指针的类型
    指针其实也是个变量,只不过这个变量里面存储的是内存地址
    这个内存地址指向了一片内存 通过指针 + */[] 就可以操作这块内存
    • 指针的类型
      int * char; // 指针类型为 int *
      函数指针 指针类型就是它
  2. 指针的运算
    一定注意只有同类型的指针才可以运算
    比如一个指针指向float 一个指针指向int 是不可以进行运算的
  • 2.1 指针的自增/减
    int *p;
    p ++;
    此时不是单纯的数值 + 1 而是 + siezeof(type),这里是int 所以打印++p的结果%p会发现地址增加了4
  • 2.2 两个指针相减–表示它们在内存中的距离
    相减的结果以数据类型的长度sizeof(type)为单位,而
        int main (void){int a[10];int *p1,*p2;p1 = &a[1];p2 = &a[2];printf("%d\n", p2 - p1);return 0;  // 结果是1 而不是4 }#define mysizeof2(name) ((size_t)(&(name)+ 1) - (size_t)(&name))     // 自己定义的sizeof
  • 2.3 比较运算符
    p < q:指针p所指的数在q所指数据的前面。
  1. &运算符和运算符,[]运算符
    只要了解[]运算符和
    运算符的本质木有区别就行
  2. 强制类型转换
    int num = 0x12345679;char * p = (char*)&num;printf("%02x %02x %02x %02x\r\n",p[0],p[1],p[2],p[3]);
  1. 大端与小端
    大端:是高字节序的存放在低地址
    小端:是高字节序的存放在高地址
    什么是高字节序 int a = 0x1234;// 34是低字节序 12是高字节序
    注意:栈是从高地址向低地址生长的
    // 打印 p[0] = 0x79 说明低地址存放低字节序 所以是小端
  2. 空指针与野指针与void*
    野指针:是指指针指向的地址是不确定的;
    原因:释放内存之后,指针没有及时置空
    避免:
    • 初始化置 NULL
    • 申请内存后判空
    • 指针释放后置 NULL
      空指针就是指向了地址为0的地方
    • void*表示一种通用的指针 但注意不能直接解引用
        void *my_memcpy(void *dest, const void *src, size_t n) {void * res = dest;while (n--) {*dest++ = *src++;  // 直接报错了 因为对void*的使用不合法}return res;}
  1. 二级指针
  • 7.1二级指针与指针数组–传入参数
    char *a[4] = {“123”,“456”,“789”,“101”};
    void test(char *a[],int len);
    void test2(char **a,int len); //使用起来没有区别
  • 7.2二级指针与二维数组
        void test3()  // 你可以以任何灵活的方式访问这块连续的空间{int array[5][10] = {1,2,3,4,5,6,7,8,9,10,11,123};  // 对于二维数组 编译器认为的一块连续的 5 * 10 * 4的空间int * p = array;        // 所以你还用int *操作没问题 毕竟每次地址偏移4就行了printf("%d \r\n",p[6]);  // 非常神奇 竟然正确的访问了7?// 编译器就warning了  此时int **的话 你可以认为每次 + 1是地址偏移了8个(64位操作系统) // 编译器只是认为地址需要偏移8之后取出来值 不一定是错误的呀int ** p2 = array;printf("%d  %d %d\r\n",p2[0],p2[1], p2[2]);int (*p3)[10] = array;      // 这样看起来标准多了 但是没人规定非得如此标准printf("%p %p %d %d %d\r\n",p3, p3 + 1, *(p3)[0], *(p3 + 1)[0],p3[1][1]);int (*p4)[2] = array;      // 也warning 但是没错printf("%p %p %d %d %d\r\n",p4, p4 + 1, *(p4)[0], *(p4 + 1)[0],p4[1][1]);}
  1. 指针和引用的区别
    1. 指针是实体,而引用是别名。
    2. 指针和引用的自增(++)运算符意义不同,指针是对内存地址自增,引用是对值的自增。
    3. 引用使用时无需解引用(*),指针需要解引用;
    4. 引用只能在定义时被初始化一次,之后不可变;指针可变。
    5. 引用不能为空,指针可以为空。
    6. sizeof的结果不同

http://www.ppmy.cn/devtools/166268.html

相关文章

AI代理的高效助手:Composio工具集详解

摘要 Composio 是一个专为AI代理设计的工具集&#xff0c;适用于生产环境。它提供了超过250种类别的多样化工具&#xff0c;支持AI代理在不同工作场景中的高效运作。这些工具覆盖了GitHub、Notion、Linear、Gmail、Slack、Hubspot、Salesforce等多个平台&#xff0c;旨在促进AI…

批量删除 Excel 中的空白行、空白列以及空白表格

我们经常会碰到需要删除 Excel 文档表格中的空白行及空白列的场景&#xff0c;有一些空白行或空白列可能我们人工不好识别&#xff0c;因此删除空白行空白列对我们来讲就非常的繁琐&#xff0c;因为我们需要先识别哪些 Excel 文档中包含空白行或者空白列&#xff0c;我们才能够…

Greenplum部署(docker)

参考&#xff1a;sheng的学习笔记-docker部署Greenplum-CSDN博客 打开终端&#xff0c;输入代码&#xff0c;查看版本 docker search greenplum docker pull projectairws/greenplum docker run -itd -p 5432:5432 --name greenplum projectairws/greenplum 连接数据库 创建…

计算机考研408数据结构大题高频考点与真题解析

一、线性表&#xff08;顺序表与链表&#xff09; 1.1 顺序表操作与算法设计 高频考点&#xff1a; 插入/删除操作的边界处理&#xff1a;检查下标越界与存储空间溢出 子数组操作&#xff1a;合并、拆分、逆置等 多数组综合问题&#xff1a;如寻找三元组最小距离 真题示例…

git忽略特定文件或者文件夹

如果想让 Git 忽略指定目录&#xff0c;不进行更新或提交&#xff0c;可以使用 .gitignore 文件进行配置。 &#x1f6e0; 方法&#xff1a;使用 .gitignore 忽略目录 1️⃣ 在仓库根目录创建 .gitignore 文件 如果你的项目目录下还没有 .gitignore 文件&#xff0c;可以新建…

python总结(2)

面向 对象 在面向对象编程中&#xff0c;术语对象大致意味着一系列数据(属性)以及一套访问和操作这些数据的方法。使用对象而非全局变量和函数的原因有多个&#xff0c;下面列出了使用对象的最重要的好处。 口 多态:可对不同类型的对象执行相同的操作&#xff0c;而这些操作就…

Java多线程与高并发专题——阻塞队列常用方法与区别

引入 上一篇文章我们了解了阻塞队列&#xff0c;在阻塞队列中有很多方法&#xff0c;而且它们都非常相似&#xff0c;所以非常有必要对这些类似的方法进行辨析&#xff0c;所以借鉴其源码注释里面的分类方式&#xff0c;把阻塞队列中常见的方法进行梳理和讲解。 其源码注释中…

穿梭车与机器人协同作业:构建高效仓储物流系统的关键

在仓储物流领域&#xff0c;效率和准确性至关重要。穿梭车和机器人作为自动化技术的代表&#xff0c;通过协同作业能够显著提升仓储效率&#xff0c;降低成本&#xff0c;构建高效智能的物流系统。 一、穿梭车与机器人的优势 穿梭车: 高效存储与检索: 穿梭车在密集存储系统中…