C的实用笔记25——通过指针引用数组

1.定义一个指针变量指向数组首地址

1、知识点:

  1. 数组的首地址是其下标为0的元素的地址。
  2. 在C语言中,数组的名字(不包括函数形参中的数组名,它是一个假数组,实际上是一个地址,形参数组并不占据实际内存单元)代表数组的首地址。

2、两种方式:

  1. 将数组第0个元素的地址赋值给指针变量
    int a[10] = {1,2,7,9,11,6,3,5,9,10};
    int *p = &a[0];

  2. 将数组的名字赋值给指针变量
    int a[10] = {1,2,7,9,11,6,3,5,9,10};
    int *p = a;

2.通过指针偏移间接访问数组的每一个元素

1、知识点:

  1. 回顾前一节课程中的“为啥要定义指针变量的类型?”,指针变量+1并不是说指针的值(地址编号)加1,而是地址偏移了它所指向的类型所占据的内存字节数。
  2. 定义指针时,要保证数组的类型和指针的类型一致,比如int arr[3]; 必须用int *p; 匹配    

2、标准写法:指针偏移后取星花

#include <stdio.h>
int main(int argc, char const *argv[]) 
{
    int arr[3] = {1,2,3};
    int *p;
    p = arr;
    /* 笨方法 */
    printf("数组的首元素是:%d\n", *p);
    printf("1元素是:%d\n", *(p+1));
    printf("2元素是:%d\n", *(p+2));
    /* 用指针遍历 */
    for(int i=0; i<3; i++){
        printf("address:0x%p, %d元素是:%d\n", p+i, i, *(p+i));
    }
    return 0;
}

3、下标法和指针法访问的效率对比:说法不明确,二者效率差距可能微乎其微

  1. 数组下标对数组元素进行访问时,开销较大,占用的硬件资源会更多一些。但优点是可读性好。
  2. 指针法对数组元素进行访问时,更有效率。但缺点是容易出错。

4、见怪不怪的做法:

  1. *p++:涉及到运算符的优先级,但是存在缺点(因为对p进行了修改,下次再用容易造成数组越界
    #include <stdio.h>
    int main(int argc, char const *argv[]) 
    {
        int arr[3] = {1,2,3};
        int *p;
        p = arr;
        for(int i=0; i<3; i++){
            printf("%d\n", *p++);		//见怪不怪的写法
        }
        return 0;
    }
    /*	或者这么写
    	for(int i=0; i<3; i++){
        	printf("%d\n", *p);
            p++;
        }
    */

  2. *p++:强调初始化条件,每次都让p回到数组头部。
    #include <stdio.h>
    int main(int argc, char const *argv[]) 
    {
        int arr[3] = {1,2,3};
        int *p;
        int i;
        for(i=0, p=arr; i<3; i++){			//每次让p回到头部
            printf("%d\n", *p++);
        }
        return 0;
    }

  3. 指针下标法:把指针当做数组名,然后下标法访问,这种方法比较直观
    #include <stdio.h>
    int main(int argc, char const *argv[]) 
    {
        int arr[3] = {1,2,3};
        int *p = arr;
        int i;
        for(i=0; i<3; i++){
            printf("%d\n", p[i]);       //见怪不怪的写法
        }
        return 0;
    }

  4. 数组名当成指针来用 *(arr+i):
    #include <stdio.h>
    int main(int argc, char const *argv[]) 
    {
        int arr[3] = {1,2,3};
        int i;
        for(i=0; i<3; i++){
            printf("%d ", *(arr+i));   	//见怪不怪的写法
        }
        return 0;
    }

5、数组名是指针吗?可以用arr++吗?arr有地址吗?:

  1. 知识点1:数组名是地址常量。我们早就知道不能修改常量的值,所以用*arr++来访问地址元素的做法是错的。
        int arr[3] = {1,2,3};
        int i;
        for(i=0; i<3; i++){
            printf("%d ", *arr++);   	//编译不通过,指针常量
        }

  2. 知识点2:对数组名用sizeof运算符计算出来的是数组占用的内存空间,而对指针变量p用sizeof运算符计算出来的是指针占据的空间,且在64位操作系统中,任何类型的指针大小都是8个字节,也就是统一用8个字节表示1个地址。所以数组名不是指针,只是一个地址常量。
    #include <stdio.h>
    int main(int argc, char const *argv[]) 
    {
        int arr[3] = {1,2,3};
        int *p = arr;
        printf("sizeOfArr is %d\n", sizeof(arr));             //3*4=12
        printf("sizeOfP is %d\n", sizeof(p));                 //os用8个字节表示一个地址
        printf("sizeOf int* is %d\n", sizeof(int *));         //os用8个字节表示一个地址
        printf("sizeOf char* is %d\n", sizeof(char *));       //os用8个字节表示一个地址
        return 0;
    }

  3. 知识点3:说出来可能有点奇怪,由于数组名不是变量,所以数组名arr没有地址,对它取地址是无效操作,&arr 的值 和 arr的值是一样的,数值上都是&arr[0] 。
    #include <stdio.h>
    int main(int argc, char const *argv[])
    {
        int arr[3] = {1,2,3};
        printf("add:%p\n", arr);		//等同于&arr[0]
        printf("add:%p\n", &arr);		//数值上等于&arr[0],没人这么用
        printf("add:%p\n", *(&arr));	//数值上等于&arr[0],没人这么用
        return 0;
    }

3.习题(函数形参接收数组名)

1、代码心得:

  1. 有了指针之后,我们在用数组作为函数的参数的时候,既然已经知道了数组的名字就是一个地址常量,那么不要再用假数组的方式定义形参了,要用指针来保存这个地址常量。
  2. 函数的形参如果是指针,我们要养成一个命名习惯:int *parr,前面加个p代表指针变量parr保存的是arr的地址。
  3. 函数中,为了符合人的阅读习惯,我们一般用指针下标法来间接访问数组元素。
  4. 题外话:当我们写scanf语句时,忘记加&来取地址了,然后程序运行时卡死,又不报错,这时候傻傻的我们不知道问题出在哪里,怎么办?按照以下步骤:用gbd软件调试程序,发现那些不报错的死机程序。(关于gdb,来自有道:你会用到它做很多事.GNU调试器(GDB)是Linux程序员使用的最多的调试工具.你可以用GDB来单步调试你的代码,设置断点和查看变量的值)
    1. 在编译时,后面加个-g选项,也就是在命令提示窗口中写“gcc test.c -g”,它的功能是让程序变得可调试。
    1. 然后用在运行可执行程序a.exe时,前面加个gdb选项,也就是在命令提示窗口写“gdb a.exe”,按下enter后程序进入待运行状态。
    2. 输入r,是run的意思,让程序跑起来。
    3. 遇到问题,会显示,比方说:“Segmentation fault”,段错误的意思。
    4. 输入q,是quit的意思,退出程序。

习题1:封装数组初始化的API、数组遍历的API

  1. 思路:
    f1. 封装初始化数组的API:void initArry(int *parr, int len); 形参分别是保存数组arr地址的指针变量parr、数组arr的长度
    	f1.1 for循环,代表数组下标的循环变量i从0开始,<len 时,进入循环
        	f1.1.1 输入数组元素的值,用指针下标法来间接访问数组元素
    f2. 封装打印数组的API:void printArry(int *parr, int len); 形参分别是保存数组arr地址的指针变量parr、数组arr的长度
    	f2.1 for循环,代表数组下标的循环变量i从0开始,<len 时,进入循环
        	f2.1.1 打印数组元素的值,用指针下标法来间接访问数组元素
        f2.2 换行
    1. 定义一个数组arr
    2. 用sizeof运算符获取数组arr的长度len
    3. 用API1. 初始化数组arr
    4. 用API2. 打印数组arr

  2. 代码:
    #include <stdio.h>
    void initArry(int *parr, int len);
    void printArry(int *parr, int len);
    int main(int argc, char const *argv[]) 
    {
        int arr[5];
        int len = sizeof(arr) / sizeof(arr[0]);
        initArry(arr, len);         //指针名是一个地址常量
        printArry(arr, len);
        return 0;
    }
    void initArry(int *parr, int len)
    {
        int i;
        for(i=0; i<len; i++){
            printf("请输入第%i个元素的数值:\n", i+1);
            scanf("%d", &parr[i]);   //指针下标法来间接访问数组元素
        }
    }
    void printArry(int *parr, int len) 
    {
        int i;
        for(i=0; i<len; i++){
            printf("%d ", *(parr+i));  //指针偏移取星花的方法来间接访问数组元素
        }
        putchar('\n');
    }

习题2:封装选择排序法的函数

  1. 思路:回顾选择排序法
  2. 代码:
    void selectionSort_SmallToLarge(int *parr, int len)
    {
        int i, j;   //分别是外、内层循环变量
        int temp;   //用于交换的临时变量
        int sword;  //尚方宝剑
        for (i=0; i<len-1; i++){
            sword = i;  //暂时把尚方宝剑交给第i个人
            for (j=i+1; j<len; j++){    //j每次都从i+1开始,因为一轮开始时尚方宝剑在第i个人手中
                if (parr[sword] < parr[j]){   //尚方宝剑在parr[sword]位置,让parr[j]轮流去挑战,大的夺走sword
                    sword = j;
                }
            }
            if (sword != i){    //判断尚方宝剑是否还在第i个人手中
                temp = parr[i];
                parr[i] = parr[sword];
                parr[sword] = temp;  
            }
        }
    }

习题3:封装逆序存放数组元素的函数

  1. 思路:for循环的双循环变量法、数组长度不管是奇数还是偶数,都是相同思路。
    f1. 封装翻转数组的API:void reverseArry(int *parr, int len); 形参分别是保存数组arr地址的指针变量parr、数组arr的长度
    	f1.1 for循环,代表数组头部的循环变量head从0开始,代表数组尾部的循环变量tail从len-1开始,当 head<tail 时,进入循环
        	f1.1 用临时变量head交换第head个数组元素和第tail个数组元素,用指针下标法来实现间接访问数组元素
            f1.2 改变循环变量head和tail: head++;  tail--;
    1. 定义一个数组arr并初始化
    2. 用sizeof运算符获取arr的长度
    3. 用API1. 翻转数组arr
    4. 打印数组元素,验证

  2. 代码:
    #include <stdio.h>
    void reverseArry(int *parr, int len);
    int main(int argc, char const *argv[])
    {
        int i;
        int arr[5] = {66,88,33,44,22};
        int len;
        len = sizeof(arr) / sizeof(arr[0]);
        reverseArry(arr, len);
        for(i=0; i<len; i++){
            printf("%d ", arr[i]);
        }
        return 0;
    }
    void reverseArry(int *parr, int len) 
    {
        int head, tail;
        int temp;
        for(head=0,tail=len-1; head<tail; head++,tail--){
            temp = parr[head];
            parr[head] = parr[tail];
            parr[tail] = temp;
        }
    }

  • 5
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值