C语言回顾 day10 指针和数组的亲密关系

比眉伴天荒 2023-07-04 08:38 125阅读 0赞

文章目录

  • 数组的初始化(用花括号括起来的逗号分隔的值列表)
    • 声明数组的同时最好初始化
    • 给数组元素赋值(C不允许直接把一个数组赋给另一个数组)
    • 数组下标越界(编译器不会查这个错误!用安全换速度)
    • 声明数组的方括号中的数字必须是大于0的整数,也可以是值为大于0的整数的变量(变长数组!!)
  • 多维数组(即:数组的数组)
  • 指针效率高是因为计算机的硬件指令很依赖地址,而指针以符号形式使用地址
  • 数组表示法实际是变相地使用指针(数组名是数组首元素的地址)
    • 示例1 指针加法(指针变量值加1,地址不止加1哦)
    • 示例2 指针表示法等效于数组表示法
    • 对数组元素求和的函数(用指针或数组名作为形参)
      • 数组求和函数的两个形参都用指针
    • 指针的8种操作(赋值 取址 解引用 递增 递减 加上整数 减去整数 比较)
    • 严重错误:引用未初始化(赋值)的指针!
    • 数组名和指针变量的区别
  • 指针的两个基本用法(函数 数组)
      1. 在被调函数中改变主调函数的变量,必须使用指针
      1. 用在处理数组的函数中
      • 用const限定形式参数,以保护数组元素的内容不被不小心修改
  • const深究(一把保护伞,一种安全机制)
    • const指针(常用于函数形参以避免函数通过指针修改该参数的值)
    • 另一种更严格的const指针(连重新指向别处都不允许了)
    • 更更严格的const指针(没想到C这么狠!)
  • 指针和多维数组(指针真正变难的地方)
    • chess,&chess[0], &chess[0][0]是一样的

之前已经接触过很多次数组了,而且我感觉我经常用数组,很多问题都是用一维数组解决的,只是matlab和python里也会常常用到2,3,4维的数组,很清楚数组占用连续的内存,存储同类型的数据,也略微了解数组名实际是一个指针变量,但是更深入的并不懂,渴望被知识洗礼,让新知识来的更猛烈些吧!

注意声明,不管是声明普通变量还是数组,都是为了告诉编译器要分配多大内存,以正确的创建变量或者数组。

就像之前说编译器看到圆括号就知道前面的标识符是个函数名一样,编译器看到方括号就知道前面的标识符是个数组。

数组的初始化(用花括号括起来的逗号分隔的值列表)

除了声明,当然最首要的就是初始化啦
在这里插入图片描述
又是宏,之前说getchar(), putchar()也是宏,不明白

在这里插入图片描述

static也是一直不了解,渴望解密

示例

  1. #include <stdio.h>
  2. #define MONTH 12
  3. int main()
  4. {
  5. int days[MONTH] = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
  6. for(int i=1;i<=MONTH;i++)
  7. printf("Month %d has %d days.\n", i, days[i-1]);
  8. return 0;
  9. }
  10. Month 1 has 31 days.
  11. Month 2 has 28 days.
  12. Month 3 has 31 days.
  13. Month 4 has 30 days.
  14. Month 5 has 31 days.
  15. Month 6 has 30 days.
  16. Month 7 has 31 days.
  17. Month 8 has 31 days.
  18. Month 9 has 30 days.
  19. Month 10 has 31 days.
  20. Month 11 has 30 days.
  21. Month 12 has 31 days.

在这里插入图片描述
所以把初始化数组那句代码加个const

  1. const int days[MONTH] = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};

在这里插入图片描述

又来一个不懂的东西····

声明数组的同时最好初始化

  1. 不初始化:

    include

    define SIZE 4

    int main()
    {

    1. int a[SIZE];
    2. for(int i=0;i<SIZE;i++)
    3. printf("%d: %d\n", i, a[i]);
    4. return 0;

    }

得到了内存里原本的值

  1. 0: 4200827
  2. 1: 4200720
  3. 2: 0
  4. 3: 3411968
  1. 初始化但是不写值

    int a[SIZE] = { };

默认全部初始化为0

  1. 0: 0
  2. 1: 0
  3. 2: 0
  4. 3: 0
  1. 部分初始化

    int a[SIZE] = { 1, 2};

只有前两个元素被初始化为想要的值,其他的被初始化为0

  1. 0: 1
  2. 1: 2
  3. 2: 0
  4. 3: 0
  1. 花括号的值的数目多于数组size

    int a[SIZE] = { 1, 2, 3, 4, 5};

只有前size个元素被使用,编译器还是不错的,只是给了警告

  1. 0: 1
  2. 1: 2
  3. 2: 3
  4. 3: 4

在这里插入图片描述

  1. 省略方括号的数字,让编译器自动匹配数组大小和初始化列表的项数

这里数组的size用sizeof运算符巧妙的计算,sizeof a / sizeof a[0],很棒哦

  1. #include <stdio.h>
  2. int main()
  3. {
  4. int a[] = { 1, 2, 3, 4, 5};
  5. for(int i=0;i<sizeof a / sizeof a[0];i++)
  6. printf("%d: %d\n", i, a[i]);
  7. return 0;
  8. }
  9. 0: 1
  10. 1: 2
  11. 2: 3
  12. 3: 4
  13. 4: 5
  1. 指定初始化器(C99新加的)
    即可以指定初始化数组某一个或某几个元素的值

    include

    int main()
    {

    1. int a[] = { 1, [2]=6, 2, [4]=5};
    2. for(int i=0;i<sizeof a / sizeof a[0];i++)
    3. printf("%d: %d\n", i, a[i]);
    4. return 0;

    }

    0: 1
    1: 0
    2: 6
    3: 2
    4: 5

如果不指定数组大小,则编译器会自动设置数组的大小以装下指定初始化的项

  1. #include <stdio.h>
  2. int main()
  3. {
  4. int a[] = { 1, [7]=5};
  5. for(int i=0;i<sizeof a / sizeof a[0];i++)
  6. printf("%d: %d\n", i, a[i]);
  7. return 0;
  8. }
  9. 0: 1
  10. 1: 0
  11. 2: 0
  12. 3: 0
  13. 4: 0
  14. 5: 0
  15. 6: 0
  16. 7: 5
  17. int a[] = { 1, [7]=5, 12, 2};
  18. 0: 1
  19. 1: 0
  20. 2: 0
  21. 3: 0
  22. 4: 0
  23. 5: 0
  24. 6: 0
  25. 7: 5
  26. 8: 12
  27. 9: 2

给数组元素赋值(C不允许直接把一个数组赋给另一个数组)

而且除了初始化以外, 也不支持用初始化花括号列表给整个数组赋值

数组下标越界(编译器不会查这个错误!用安全换速度)

数组的边界非常重要,要保证数组下标是有效值,因为编译器不会报错,是一个陷阱

  1. #include <stdio.h>
  2. int main()
  3. {
  4. int a[2]= { 1, 2};
  5. for(int i=0;i<sizeof a / sizeof a[0];i++)
  6. printf("%d: %d\n", i, a[i]);
  7. printf("%d: %d\n", 3, a[3]);
  8. return 0;
  9. }

没报错,没终止编译,我的编译器连警告都没报····可能是有的选项可以设置吧

  1. 0: 1
  2. 1: 2
  3. 3: 6422300

至于编译器为什么不检查,只能说C信任使用它的程序员的实力,认为大家不会犯这种错误,因为编译器检查的东西越少,程序运行的就越快,所以C牺牲了安全换取一点速度

在这里插入图片描述

声明数组的方括号中的数字必须是大于0的整数,也可以是值为大于0的整数的变量(变长数组!!)

我的编译器int a2[0];竟然没报错···

  1. int n=4;
  2. int a1[2];
  3. int a2[0];
  4. //int a3[-2];//必须大于0
  5. //int a4[2.3];//必须是整数
  6. int a5[(int)2.3];
  7. int a6[n];//变长数组

变长数组,听起来很牛逼,可是好像最后用的并不多,在C11已经成为可选的了
在这里插入图片描述在这里插入图片描述在这里插入图片描述

多维数组(即:数组的数组)

在这里插入图片描述在这里插入图片描述
虽然我们习惯于用二维表格的感觉去理解二维数组,但是要明确内存里还是线性存储的哦,matlab里面也提到过,所以多维数组也还是占用着一块连续的线性的内存
在这里插入图片描述

  1. #include <stdio.h>
  2. #define YEAR 3
  3. #define MONTH 4
  4. int main()
  5. {
  6. float sum_year[YEAR] = { }, sum_month[MONTH]={ };
  7. float total=0;
  8. float rainfall[YEAR][MONTH] = { { 3.4, 4.5, 4.2, 5.2},{ 4.6, 6.4, 7.3, 4.8}, { 2.1, 3.1, 8.2, 5.2}};
  9. printf("Year Rainfall(inches)\n");
  10. for(int i=0;i<YEAR;i++)
  11. {
  12. for(int j=0;j<MONTH;j++)
  13. sum_year[i] += rainfall[i][j];
  14. printf("%d: %.2f\n", 2011+i, sum_year[i]);
  15. total += sum_year[i];
  16. }
  17. printf("The yearly average is %.2f inches.\n\n", total/YEAR);
  18. printf("Monthly averages:\n");
  19. printf("Jan Feb Mar Apr\n");
  20. for(int i=0;i<MONTH;i++)
  21. {
  22. for(int j=0;j<YEAR;j++)
  23. sum_month[i] += rainfall[j][i];
  24. printf("%.2f ", sum_month[i]/YEAR);
  25. }
  26. return 0;
  27. }
  28. Year Rainfall(inches)
  29. 2011: 17.30
  30. 2012: 23.10
  31. 2013: 18.60
  32. The yearly average is 19.67 inches.
  33. Monthly averages:
  34. Jan Feb Mar Apr
  35. 3.37 4.67 6.57 5.07

注意,计算年平均降水量和月平均降水量时的外层内层循环是相反的哦,内层循环结束才会走外层循环

在这里插入图片描述

在这里插入图片描述

当然这种方式是不推荐的

指针效率高是因为计算机的硬件指令很依赖地址,而指针以符号形式使用地址

数组表示法实际是变相地使用指针(数组名是数组首元素的地址)

在上面的程序末尾加了几句:

  1. _Bool equal = rainfall==&rainfall[0][0];
  2. printf("\n\nrainfall==&rainfall[0][0]? ");
  3. equal?printf("true"):printf("false");

注意,rainfall二元数组的首元素是一个数组rainfall[0]哈,不是rainfall[0][0],但是&rainfall[0]==&rainfall[0][0]是true,即第一个数组的地址就是第一个数的地址

  1. rainfall==&rainfall[0][0]? true

在这里插入图片描述

示例1 指针加法(指针变量值加1,地址不止加1哦)

  1. #include <stdio.h>
  2. int main()
  3. {
  4. int beer[4];
  5. double coke[4];
  6. int *bottle;
  7. double *cup;
  8. bottle = beer;
  9. cup = coke;
  10. printf("%18s %15s\n", "int", "double");
  11. for(int i=0;i<4;i++)
  12. printf("pointer + %d: %#10p %#10p\n", i, bottle++, cup++);
  13. return 0;
  14. }
  15. int double
  16. pointer + 0: 0x61ff14 0x61fef0
  17. pointer + 1: 0x61ff18 0x61fef8
  18. pointer + 2: 0x61ff1c 0x61ff00
  19. pointer + 3: 0x61ff20 0x61ff08

在这里插入图片描述在这里插入图片描述在这里插入图片描述

示例2 指针表示法等效于数组表示法

可以用指针表示数组,也可以用数组表示指针,生成的代码都一样

  1. #include <stdio.h>
  2. int main()
  3. {
  4. int beer[4] ={ 1, 2, 3, 5};
  5. double coke[4] = { 5, 6, 8, 2};
  6. int *bottle;
  7. double *cup;
  8. bottle = beer;
  9. cup = coke;
  10. printf("%22s %12s\n", "int value", "double value");
  11. for(int i=0;i<4;i++)
  12. printf("pointer + %d: %d %d %.2f %.2f\n", i, beer[i], *(beer+i), coke[i], *(coke+i));
  13. return 0;
  14. }

所以beer[i]就等效于*(beer+i)

  1. int value double value
  2. pointer + 0: 1 1 5.00 5.00
  3. pointer + 1: 2 2 6.00 6.00
  4. pointer + 2: 3 3 8.00 8.00
  5. pointer + 3: 5 5 2.00 2.00

对数组元素求和的函数(用指针或数组名作为形参)

处理数组的函数实际上以指针为参数

在这里插入图片描述

  1. #include <stdio.h>
  2. int sum_array(int *arr, int n);
  3. int sum_array1(int *arr, int n);
  4. int sum_arr(int arr[], int n);
  5. int sum_arr1(int arr[], int n);
  6. int main()
  7. {
  8. int beer[4] ={ 1, 2, 3, 5};
  9. int sum, sum1, sum2, sum3;
  10. sum = sum_array(beer, sizeof beer / sizeof beer[0]);
  11. sum1 = sum_array1(beer, sizeof beer / sizeof beer[0]);
  12. sum2 = sum_arr(beer, sizeof beer / sizeof beer[0]);
  13. sum3 = sum_arr1(beer, sizeof beer / sizeof beer[0]);
  14. printf("sum of beer is %d, %d, %d, %d.\n", sum, sum1, sum2, sum3);
  15. return 0;
  16. }
  17. int sum_array(int *arr, int n)
  18. {
  19. int sum=0;
  20. for(int i=0;i<n;i++)
  21. sum += *(arr+i);
  22. return sum;
  23. }
  24. int sum_array1(int *arr, int n)
  25. {
  26. int sum=0;
  27. for(int i=0;i<n;i++)
  28. sum += arr[i];
  29. return sum;
  30. }
  31. int sum_arr(int arr[], int n)//这样可以使得处理数组的意图更加明显
  32. {
  33. int sum=0;
  34. for(int i=0;i<n;i++)
  35. sum += *(arr+i);
  36. return sum;
  37. }
  38. int sum_arr1(int arr[], int n)
  39. {
  40. int sum=0;
  41. for(int i=0;i<n;i++)
  42. sum += arr[i];
  43. return sum;
  44. }

可以看到,两种声明都是一样的效果

  1. sum of beer is 11, 11, 11, 11.

在这里插入图片描述

数组求和函数的两个形参都用指针

注意:对于C, arr[i]和*(arr+i)是等价的,因为arr数组名就是一个指针变量

  1. #include <stdio.h>
  2. #define SIZE 4
  3. int sum_array(int *, int *);
  4. int main()
  5. {
  6. int beer[SIZE] ={ 1, 2, 3, 5};
  7. int sum;
  8. sum = sum_array(beer, beer+SIZE);//注意,beer[SIZE]是访问了非法内存哦,但是这么写是对的
  9. printf("sum is %d.\n", sum);
  10. return 0;
  11. }
  12. int sum_array(int *start, int *end)
  13. {
  14. int sum=0;
  15. while(start<end)
  16. {
  17. sum += *start;//千万别忘记写*
  18. start++;
  19. }
  20. return sum;
  21. }
  22. sum is 11.

上面的while循环还可以写为一句代码:因为*解除引用运算符和自增运算符的优先级一样,但是结合律是从右向左,所以还是start先自增,然后解除引用

  1. while(start<end)
  2. sum += *start++;

在这里插入图片描述

  1. #include <stdio.h>
  2. #define SIZE 4
  3. int main()
  4. {
  5. int beer[SIZE] = { 1, 2, 3, 5};
  6. int coke[SIZE] = { 34, 23, 67, 90};
  7. int *p1, *p2, *p3;
  8. p1 = p2 = beer;//很棒,要多用
  9. p3 = coke;
  10. printf("*p1 is %d, *p2 is %d, *p3 is %d.\n", *p1, *p2, *p3);
  11. printf("*p1++ is %d, *++p2 is %d, (*p3)++ is %d.\n", *p1++, *++p2, (*p3)++);
  12. printf("*p1 is %d, *p2 is %d, *p3 is %d.\n", *p1, *p2, *p3);
  13. return 0;
  14. }
  15. *p1 is 1, *p2 is 1, *p3 is 34.
  16. *p1++ is 1, *++p2 is 2, (*p3)++ is 34.
  17. *p1 is 2, *p2 is 2, *p3 is 35.

在这里插入图片描述

看来还是应该多用指针,值得注意的是,++ —两个运算符是很接近机器代码的,之前看到过类似说法没有引起重视,虽然没见过机器语言是啥样子,但是见过汇编语言,以后可以再深入看看

指针的8种操作(赋值 取址 解引用 递增 递减 加上整数 减去整数 比较)

示例程序

注意指针减去整数的时候,指针变量必须是第一个运算对象,整数是第二个运算对象

递增递减中前后缀的都可以用 ,注意递增递减指针时,编译器并不会检查指针是否仍然指向数组元素,C只会保证指向数组所有元素的指针,以及指向数组最后一个元素后面的第一个位置的指针有效。 所以这些有效指针是可以解引用的,但是如果解引用数组最后一个元素后面的第一个位置的指针,则发生了指针越界。

对两个指针求差可以求出两个数组元素的距离,ptr2-ptr1=2,则两个数组元素相差2个int,注意不是2个字节,但是求差的2个指针必须指向同一个数组!否则出错。

指针-指针=整数

指针-整数=指针

  1. #include <stdio.h>
  2. int main()
  3. {
  4. int apple[5] = { 100, 200, 300, 400, 500};
  5. int *ptr1, *ptr2, *ptr3;
  6. ptr1 = apple;//赋值,把地址赋给指针
  7. ptr2 = &apple[2];//把地址赋给指针
  8. printf("pointer value, dereferenced pointer, pointer address:\n");
  9. printf("ptr1=%p, *ptr1=%d, &ptr1=%p\n", ptr1, *ptr1, &ptr1);
  10. printf("ptr2=%p, *ptr2=%d, &ptr2=%p\n", ptr2, *ptr2, &ptr2);
  11. //指针加上一个整数
  12. ptr3 = ptr1+3;
  13. printf("\nadding an integer to a pointer:\n");
  14. printf("ptr1+3=%p, *(ptr1+3)=%d\n", ptr1+3, *(ptr1+3));
  15. //递增指针,使得指针指向数组的下一个元素,递增指针改变了指针变量里存储的地址,它本身被存储的地址不变哦
  16. ptr1++;
  17. printf("\nvalues after ptr1++:\n");
  18. printf("ptr1=%p, *ptr1=%d, &ptr1=%p\n", ptr1, *ptr1, &ptr1);
  19. //递减指针
  20. ptr2--;
  21. printf("\nvalues after ptr2--:\n");
  22. printf("ptr2=%p, *ptr2=%d, &ptr2=%p\n", ptr2, *ptr2, &ptr2);
  23. //恢复为初始值
  24. --ptr1;
  25. ++ptr2;
  26. printf("\npointers reset to original values:\n");
  27. printf("ptr1=%p, ptr2=%p\n", ptr1, ptr2);
  28. //一个指针减去另一个指针
  29. printf("\nsubtracting one pointer from another:\n");
  30. printf("ptr2=%p, ptr1=%p, ptr2-ptr1=%td\n", ptr2, ptr1, ptr2-ptr1);//%td转换说明用于打印地址差值
  31. //一个指针减去一个整数
  32. printf("\nsubtracting one integer from a pointer:\n");
  33. printf("ptr3=%p, ptr3-2=%p\n", ptr3, ptr3-2);
  34. return 0;
  35. }

一个指针变量涉及3种值,一是它里面存储的地址,二是它存的这个地址里面存的值(即解引用的值),三是它被存在哪个地址。

可以看到:

  • 我的电脑是用的32位地址(大概因为我装的code::blocks是32位的?),因为&ptr1和&ptr2之间差了4个字节而已
  • ptr1和ptr2隔了8个字节(0061ff18,19,1a,1b,1c,1d,1e,1f,0061ff20),因为是int类型的数组

    pointer value, dereferenced pointer, pointer address:
    ptr1=0061ff18, ptr1=100, &ptr1=0061ff14
    ptr2=0061ff20,
    ptr2=300, &ptr2=0061ff10

    adding an integer to a pointer:
    ptr1+3=0061ff24, *(ptr1+3)=400

    values after ptr1++:
    ptr1=0061ff1c, *ptr1=200, &ptr1=0061ff14

    values after ptr2—:
    ptr2=0061ff1c, *ptr2=200, &ptr2=0061ff10

    pointers reset to original values:
    ptr1=0061ff18, ptr2=0061ff20

    subtracting one pointer from another:
    ptr2=0061ff20, ptr1=0061ff18, ptr2-ptr1=2

    subtracting one integer from a pointer:
    ptr3=0061ff24, ptr3-2=0061ff1c

严重错误:引用未初始化(赋值)的指针!

  1. #include <stdio.h>
  2. int main()
  3. {
  4. int *ptr1;
  5. printf("&ptr1=%p", &ptr1);
  6. return 0;
  7. }
  8. &ptr1=0061ff2c

没有给指针变量ptr1赋值,可以看到编译器把他存在了0061ff2c处。

但是如果直接解引用ptr1,则可以通过编译,但是程序错误退出,且打印不出被注释的那句代码需要打印的内容

  1. #include <stdio.h>
  2. int main()
  3. {
  4. int *ptr1;
  5. printf("&ptr1=%p", &ptr1);
  6. *ptr1 = 2;
  7. //printf("ptr1=%p", ptr1);//没打印ptr1存储的地址,导致程序错误退出
  8. //printf("*ptr1=%d",*ptr1);//没打印ptr1存储的地址中存储的值,导致程序错误退出
  9. return 0;
  10. }

&ptr1=0061ff2c打印出来后程序大概等了10s才输出了后面的错误退出代码。

原因分析起来也很简单:因为你没有给ptr1赋值,赋一个地址,那么存储ptr1的内存里原来是什么,现在ptr1的值就是什么,但是一般内存里都不是存储的地址呀!!!!!所以你硬把它当做地址,去输出这个地址处存的值,咋可能行嘛

  1. &ptr1=0061ff2c
  2. Process returned -1073741819 (0xC0000005) execution time : 11.529 s
  3. Press any key to continue.

这个程序展示了不给ptr1赋值的情况下,ptr1里面到底存了什么,我把ptr1声明后就直接把它的地址赋给了指针变量ptr2,所以ptr2指向指针变量ptr1,(当然这么设计本身就有问题的哈,毕竟不能真的弄一个指向指针变量的指针,没有这个类型),那么对ptr2解引用,就可以看到ptr1里面存的是多少,但是还有一个问题就是你不知道用啥转换说明,我强行用%p把它输出为地址,或者用%d输出为数字,但是总之这是不对的,····

  1. #include <stdio.h>
  2. int main()
  3. {
  4. int *ptr1, *ptr2;
  5. printf("&ptr1=%p\n", &ptr1);
  6. ptr2 = &ptr1;
  7. printf("*ptr2=%p, *ptr2=%d", *ptr2, *ptr2);
  8. return 0;
  9. }
  10. &ptr1=0061ff28
  11. *ptr2=0026c000, *ptr2=2539520

数组名和指针变量的区别

我之前一直把二者划等号,现在才知道数组名是一个特殊的指针变量,特殊之处在于数组名是一个值不可以改变的指针变量,不可以使用递增递减运算符,以及其他任何加减操作去改变数组名的值,否则数组位置变了,内容也就错了,幸好编译器这回要检查,不再放手信任C程序员了
在这里插入图片描述

刚开始我很疑惑为啥不能对数组名使用递增运算符,于是自己试了试,编译器报错,如下。想了一下,这是因为数组名虽然是一个存储着地址的变量,很像指针,但是毕竟还是有点区别,区别就在于,数组名必须始终指向数组首元素,存储数组首元素的地址,你一旦去递增递减,数组的存储位置就变了,内容自然也就错了,编译器是不允许这样的错误的发生的
在这里插入图片描述在这里插入图片描述

指针的两个基本用法(函数 数组)

假设被调函数有一个int类型参数a,如果主调函数希望被调函数改变a的值,则必须传递a的地址才行?因为如果只传递值,那么被调函数会复制主调函数传来的值,然后使用复制的值,主调函数的原本值并不会改变。其中怎么使用的栈的细节我还不知全貌。
在这里插入图片描述

1. 在被调函数中改变主调函数的变量,必须使用指针

因为函数通过指针,直接使用原始数据,而不是原数据的副本。

2. 用在处理数组的函数中

以前我用python的时候,都是直接把数组当做参数传的,也许底层C会帮我把它转换为数组的地址和size吧。

示例
用指针修改数组元素的值,根本不需要返回值,不使用return机制

  1. #include <stdio.h>
  2. void add_to(int *arr, int n, int value);
  3. int main()
  4. {
  5. int arr[3] = { 1, 3, 5};
  6. printf("original array: %d, %d, %d\n", arr[0], arr[1], arr[2]);
  7. add_to(arr, 3, 2);
  8. printf("new array: %d, %d, %d\n", *arr, *(arr+1), *(arr+2));
  9. return 0;
  10. }
  11. void add_to(int *arr, int n, int value)
  12. {
  13. for(int i=0;i<n;i++)
  14. *(arr+i) += value;
  15. }
  16. original array: 1, 3, 5
  17. new array: 3, 5, 7

但是要明白,传递了地址带来方便的同时也带来的麻烦,那就是如果你不想改变原数据,却因为编程不慎不小心改了原数据,那就呵呵了,越灵活的语言越危险,要小心,但是幸好我们不是只能靠提高警惕来应对这个问题,我们可以把形式参数设置为只读的数组的地址!!

不小心改原数据的示例

  1. #include <stdio.h>
  2. int sum( int *arr, int n);
  3. int main()
  4. {
  5. int total;
  6. int arr[3] = { 1, 3, 5};
  7. printf("array: %d, %d, %d\n", arr[0], arr[1], arr[2]);
  8. total = sum(arr, 3);
  9. printf("sum of the array: %d\n", total);
  10. printf("array: %d, %d, %d\n", arr[0], arr[1], arr[2]);
  11. return 0;
  12. }
  13. int sum( int *arr, int n)
  14. {
  15. int sum=0;
  16. for(int i=0;i<n;i++)
  17. sum += arr[i]++;//错误代码,正确应为sum += arr[i];
  18. return sum;
  19. }

虽然和计算正确,但是数组元素已经不小心递增了1

  1. array: 1, 3, 5
  2. sum of the array: 9
  3. array: 2, 4, 6

用const限定形式参数,以保护数组元素的内容不被不小心修改

对上面的示例程序,我们给求和函数加个const, 编译器就发现错误啦

在这里插入图片描述

再把错误代码的递增运算符去掉,程序就正确了

  1. array: 1, 3, 5
  2. sum of the array: 9
  3. array: 1, 3, 5

在这里插入图片描述

下面还有一个很简单的小小示例,一个函数改,一个不改数组元素的值

  1. #include <stdio.h>
  2. #define SIZE 4
  3. void show_array(const double *arr, int n);
  4. void mult_array(double *arr, int n, double mul);
  5. int main()
  6. {
  7. double dip[SIZE] = { 2.34, 4.56, 7.23, 5.89};
  8. printf("array: %.2f, %.2f, %.2f, %.2f\n", *(dip), *(dip+1), dip[2], dip[3]);
  9. show_array(dip, SIZE);
  10. printf("array: %.2f, %.2f, %.2f, %.2f\n", *(dip), *(dip+1), dip[2], dip[3]);
  11. mult_array(dip, SIZE, 2.00);
  12. printf("array: %.2f, %.2f, %.2f, %.2f\n", *(dip), *(dip+1), dip[2], dip[3]);
  13. return 0;
  14. }
  15. void show_array(const double *arr, int n)
  16. {
  17. for(int i=0;i<n;i++)
  18. printf("%.2f ", *(arr+i));
  19. putchar('\n');
  20. }
  21. void mult_array(double *arr, int n, double mul)
  22. {
  23. for(int i=0;i<n;i++)
  24. *(arr+i) *= mul;
  25. }

注意:虽然show_array函数的形参用const限制了,void show_array(const double *arr, int n),但是调用它的时候使用的实参数组名可以是const 的,也可以不是const的哦,形参的const限制并没有要求实参必须是一个const数组。

  1. array: 2.34, 4.56, 7.23, 5.89
  2. 2.34 4.56 7.23 5.89
  3. array: 2.34, 4.56, 7.23, 5.89
  4. array: 4.68, 9.12, 14.46, 11.78

const深究(一把保护伞,一种安全机制)

之前在C++里面也看到过类似观点,认为const优于define,毕竟const还可以指定类型,而define则完全靠编译器根据默认去自己决定,最多依赖后缀比如L去告知编译器我是long整型,而且const可以活跃在函数里面,保护数组呀,保护变量啦,而define由于是预处理器指令只能写在函数外面,只能起到一个复制和替换的作用

在这里插入图片描述下面深入分析一下const可以带来哪些保护,为程序的安全提供哪些保障

const指针(常用于函数形参以避免函数通过指针修改该参数的值)

  1. #include <stdio.h>
  2. #define SIZE 4
  3. int main()
  4. {
  5. double dip[SIZE] = { 2.34, 4.56, 7.23, 5.89};
  6. const double *ptr = dip; //ptr是一个指向const的指针
  7. const double rate[SIZE] = { 1.22, 3.56, 7.21, 9.34};
  8. // *ptr=2.56;//试图修改const指针指向的变量,错误
  9. //ptr[2]=4.59;//试图修改const指针指向的变量,错误
  10. dip[1]=3.33;//正确,因为dip没有被const限制
  11. ptr++;//正确,虽然被const限制,不可以通过ptr改变他所指向的值,但是他想指向别处还是允许的
  12. ptr = rate; //const指针允许指向一个受const保护的数组,实际上const指针可以指向任何数组,
  13. //和数组自己有没有被const限制没有一毛钱关系
  14. ptr = &dip[2];//指向别处,可以
  15. return 0;
  16. }
  1. const指针指向const数组

    include

    define SIZE 4

    int main()
    {

    1. double dip[SIZE] = { 2.34, 4.56, 7.23, 5.89};//普通数组
    2. const double rate[SIZE] = { 1.22, 3.56, 7.21, 9.34};//const数组
    3. double *ptr1 = dip;//普通指针
    4. const double *ptr = dip; //ptr是一个指向const的指针
    5. ptr=rate;
    6. //*ptr=2.44;//const指针指向const数组,错误,无法修改
    7. return 0;

    }

  2. const指针指向非const数组

    include

    define SIZE 4

    int main()
    {

    1. double dip[SIZE] = { 2.34, 4.56, 7.23, 5.89};//普通数组
    2. const double rate[SIZE] = { 1.22, 3.56, 7.21, 9.34};//const数组
    3. double *ptr1 = dip;//普通指针
    4. const double *ptr = dip; //ptr是一个指向const的指针
    5. ptr=dip;
    6. //*ptr=2.44;//const指针指向非const数组,错误,无法修改
    7. return 0;

    }

  3. 普通指针指向const数组(陷阱!!会修改数组元素的值)

    include

    define SIZE 4

    int main()
    {

    1. const double rate[SIZE] = { 1.22, 3.56, 7.21, 9.34};
    2. printf("rate array:%.2f %.2f %.2f %.2f\n", rate[0], rate[1], rate[2], rate[3]);
    3. double *ptr1 = rate;//非const指针指向const数组,仍然必须保证不能通过指针修改这个数组
    4. *ptr1 = 2.34;//可以!!!会修改const数组,说明非const指针指向const数组是一个陷阱!!!
    5. printf("rate array:%.2f %.2f %.2f %.2f\n", rate[0], rate[1], rate[2], rate[3]);
    6. return 0;

    }

    rate array:1.22 3.56 7.21 9.34
    rate array:2.34 3.56 7.21 9.34

另一个示例,把const数组作为参数传给会修改数组元素值的函数mul_array(未用const限制形参)

  1. #include <stdio.h>
  2. #define SIZE 4
  3. void show_array(const double *arr, int n);
  4. void mult_array(double *arr, int n, double mul);
  5. int main()
  6. {
  7. const double dip[SIZE] = { 2.34, 4.56, 7.23, 5.89};
  8. printf("array: %.2f, %.2f, %.2f, %.2f\n", *(dip), *(dip+1), dip[2], dip[3]);
  9. show_array(dip, SIZE);
  10. printf("array: %.2f, %.2f, %.2f, %.2f\n", *(dip), *(dip+1), dip[2], dip[3]);
  11. mult_array(dip, SIZE, 2.00);
  12. printf("array: %.2f, %.2f, %.2f, %.2f\n", *(dip), *(dip+1), dip[2], dip[3]);
  13. return 0;
  14. }
  15. void show_array(const double *arr, int n)
  16. {
  17. for(int i=0;i<n;i++)
  18. printf("%.2f ", *(arr+i));
  19. putchar('\n');
  20. }
  21. void mult_array(double *arr, int n, double mul)
  22. {
  23. for(int i=0;i<n;i++)
  24. *(arr+i) *= mul;
  25. }

可以看到,const数组传入非const形参的函数,值仍然被修改了!!!

所以,必须const指针,光const数组是没有用的,指针果然牛逼,能钻空子,能直接操作地址的人就是不一样

  1. array: 2.34, 4.56, 7.23, 5.89
  2. 2.34 4.56 7.23 5.89
  3. array: 2.34, 4.56, 7.23, 5.89
  4. array: 4.68, 9.12, 14.46, 11.78
  1. 普通指针指向普通数组(非const)
    当然可以改了,不用试····

总结了上面四种情况,只要指针是const的,就不会通过指针修改数组的值,但是如果数组是const的,却用普通指针指向他,就可以通过这个普通指针去修改这个数组而不被编译器发现!!!!非常可怕的漏洞。

所以写程序要注意,const数组一定要用const指针去匹配!!!

如果实在不希望数组的值被修改,就应该双重保障,const数组配搭配const指针,双管齐下,哪种意外都防止了

另一种更严格的const指针(连重新指向别处都不允许了)

刚才说的const指针主要是防止我们通过指针去修改数组元素,但是仍然可以允许这个指针重新指向另一处内存,现在说一个更严格的,主要是const在声明时候的位置:

  1. #include <stdio.h>
  2. #define SIZE 4
  3. int main()
  4. {
  5. const double dip[SIZE] = { 2.34, 4.56, 7.23, 5.89};
  6. double * const ptr = dip;
  7. *ptr = 1.22;//可以修改该处的值
  8. //ptr = &dip[1];//错误,不可以再指向别处
  9. return 0;
  10. }

更更严格的const指针(没想到C这么狠!)

用两次const

  1. #include <stdio.h>
  2. #define SIZE 4
  3. int main()
  4. {
  5. const double dip[SIZE] = { 2.34, 4.56, 7.23, 5.89};
  6. const double * const ptr = dip;
  7. //*ptr = 1.22;//不可以修改该处的值
  8. //ptr = &dip[1];//错误,不可以再指向别处
  9. return 0;
  10. }

指针和多维数组(指针真正变难的地方)

当指针和多维数组纠缠在一起时,你就会明白为啥指针是C最难的部分了,数组维数越大,难度越高

chess,&chess[0], &chess[0][0]是一样的

首先明确这三个地址是一样的,chess是二维数组名,&chess[0]是这个二维数组的首元素(一个有两个int元素的数组)的地址,&chess[0][0]是二维数组首元素的首元素的地址。

  1. #include <stdio.h>
  2. #define SIZE 3
  3. int main()
  4. {
  5. int chess[SIZE][SIZE-1] = { { 1, 2},{ 4, 6}, { 9, 3}};
  6. printf("chess==&chess[0]? %s\n", chess==&chess[0]?"true":"false");
  7. printf("chess==&chess[0][0]? %s\n", chess==&chess[0][0]?"true":"false");
  8. printf("&chess[0]==&chess[0][0]? %s\n", &chess[0]==&chess[0][0]?"true":"false");
  9. return 0;
  10. }
  11. chess==&chess[0]? true
  12. chess==&chess[0][0]? true
  13. &chess[0]==&chess[0][0]? true

这很简单,因为数组名就是数组首元素的地址,chess[0]就是数组的首元素,所以第一行为true;而chess[0][0]又是chess[0]的首地址,第三行true;传递性,第二行true

发表评论

表情:
评论列表 (有 0 条评论,125人围观)

还没有评论,来说两句吧...

相关阅读

    相关 指针和数区别

    指针和数组的区别: 1.空间分配:数组是静态分配,且分配的空间是连续的;指针是动态分配,分配的空间不一定是连续的。 2.安全性:使用数组可能会造成数组越界;指针使用时可能会