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

文章目录


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

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

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

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

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

在这里插入图片描述

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

示例

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

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

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

在这里插入图片描述

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

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

  1. 不初始化:
#include <stdio.h>
#define SIZE 4
int main()
{
    int a[SIZE];
    for(int i=0;i<SIZE;i++)
        printf("%d: %d\n", i, a[i]);
    return 0;
}

得到了内存里原本的值

0: 4200827
1: 4200720
2: 0
3: 3411968
  1. 初始化但是不写值
int a[SIZE] = {};

默认全部初始化为0

0: 0
1: 0
2: 0
3: 0
  1. 部分初始化
int a[SIZE] = {1, 2};

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

0: 1
1: 2
2: 0
3: 0
  1. 花括号的值的数目多于数组size
int a[SIZE] = {1, 2, 3, 4, 5};

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

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

在这里插入图片描述
5. 省略方括号的数字,让编译器自动匹配数组大小和初始化列表的项数

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

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

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

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

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

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

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

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

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

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

0: 1
1: 2
3: 6422300

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

在这里插入图片描述

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

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

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

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

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

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

#include <stdio.h>
#define YEAR 3
#define MONTH 4
int main()
{
    float sum_year[YEAR] = {}, sum_month[MONTH]={};
    float total=0;
    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}};
    printf("Year  Rainfall(inches)\n");
    for(int i=0;i<YEAR;i++)
    {
        for(int j=0;j<MONTH;j++)
            sum_year[i] += rainfall[i][j];

        printf("%d: %.2f\n", 2011+i, sum_year[i]);
        total += sum_year[i];
    }
    printf("The yearly average is %.2f inches.\n\n", total/YEAR);
    printf("Monthly averages:\n");
    printf("Jan Feb Mar Apr\n");
    for(int i=0;i<MONTH;i++)
    {
        for(int j=0;j<YEAR;j++)
           sum_month[i] += rainfall[j][i];
        printf("%.2f ", sum_month[i]/YEAR);
    }
    return 0;
}
Year  Rainfall(inches)
2011: 17.30
2012: 23.10
2013: 18.60
The yearly average is 19.67 inches.

Monthly averages:
Jan Feb Mar Apr
3.37 4.67 6.57 5.07

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

在这里插入图片描述

在这里插入图片描述

当然这种方式是不推荐的

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

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

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

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

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

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

在这里插入图片描述

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

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

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

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

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

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

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

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

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

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

在这里插入图片描述

#include <stdio.h>
int sum_array(int *arr, int n);
int sum_array1(int *arr, int n);
int sum_arr(int arr[], int n);
int sum_arr1(int arr[], int n);
int main()
{
    int beer[4] ={1, 2, 3, 5};
    int sum, sum1, sum2, sum3;
    sum = sum_array(beer, sizeof beer / sizeof beer[0]);
    sum1 = sum_array1(beer, sizeof beer / sizeof beer[0]);
    sum2 = sum_arr(beer, sizeof beer / sizeof beer[0]);
    sum3 = sum_arr1(beer, sizeof beer / sizeof beer[0]);
    printf("sum of beer is %d, %d, %d, %d.\n", sum, sum1, sum2, sum3);
    return 0;
}
int sum_array(int *arr, int n)
{
    int sum=0;
    for(int i=0;i<n;i++)
        sum += *(arr+i);
    return sum;
}
int sum_array1(int *arr, int n)
{
    int sum=0;
    for(int i=0;i<n;i++)
        sum += arr[i];
    return sum;
}
int sum_arr(int arr[], int n)//这样可以使得处理数组的意图更加明显
{
    int sum=0;
    for(int i=0;i<n;i++)
        sum += *(arr+i);
    return sum;
}
int sum_arr1(int arr[], int n)
{
    int sum=0;
    for(int i=0;i<n;i++)
        sum += arr[i];
    return sum;
}

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

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

在这里插入图片描述

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

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

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

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

while(start<end)
        sum += *start++;

在这里插入图片描述

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

在这里插入图片描述

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

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

示例程序

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

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

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

指针-指针=整数

指针-整数=指针

#include <stdio.h>
int main()
{
    int apple[5] = {100, 200, 300, 400, 500};
    int *ptr1, *ptr2, *ptr3;
    ptr1 = apple;//赋值,把地址赋给指针
    ptr2 = &apple[2];//把地址赋给指针
    printf("pointer value, dereferenced pointer, pointer address:\n");
    printf("ptr1=%p, *ptr1=%d, &ptr1=%p\n", ptr1, *ptr1, &ptr1);
    printf("ptr2=%p, *ptr2=%d, &ptr2=%p\n", ptr2, *ptr2, &ptr2);
    //指针加上一个整数
    ptr3 = ptr1+3;
    printf("\nadding an integer to a pointer:\n");
    printf("ptr1+3=%p, *(ptr1+3)=%d\n", ptr1+3, *(ptr1+3));
    //递增指针,使得指针指向数组的下一个元素,递增指针改变了指针变量里存储的地址,它本身被存储的地址不变哦
    ptr1++;
    printf("\nvalues after ptr1++:\n");
    printf("ptr1=%p, *ptr1=%d, &ptr1=%p\n", ptr1, *ptr1, &ptr1);
    //递减指针
    ptr2--;
    printf("\nvalues after ptr2--:\n");
    printf("ptr2=%p, *ptr2=%d, &ptr2=%p\n", ptr2, *ptr2, &ptr2);
    //恢复为初始值
    --ptr1;
    ++ptr2;
    printf("\npointers reset to original values:\n");
    printf("ptr1=%p, ptr2=%p\n", ptr1, ptr2);
    //一个指针减去另一个指针
    printf("\nsubtracting one pointer from another:\n");
    printf("ptr2=%p, ptr1=%p, ptr2-ptr1=%td\n", ptr2, ptr1, ptr2-ptr1);//%td转换说明用于打印地址差值
    //一个指针减去一个整数
    printf("\nsubtracting one integer from a pointer:\n");
    printf("ptr3=%p, ptr3-2=%p\n", ptr3, ptr3-2);
    return 0;
}

一个指针变量涉及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

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

#include <stdio.h>
int main()
{
    int *ptr1;
    printf("&ptr1=%p", &ptr1);
    return 0;
}
&ptr1=0061ff2c

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

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

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

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

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

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

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

#include <stdio.h>
int main()
{
    int *ptr1, *ptr2;
    printf("&ptr1=%p\n", &ptr1);
    ptr2 = &ptr1;
    printf("*ptr2=%p, *ptr2=%d", *ptr2, *ptr2);
    return 0;
}
&ptr1=0061ff28
*ptr2=0026c000, *ptr2=2539520

数组名和指针变量的区别

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

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

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

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

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

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

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

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

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

#include <stdio.h>
void add_to(int *arr, int n, int value);
int main()
{
    int arr[3] = {1, 3, 5};
    printf("original array: %d, %d, %d\n", arr[0], arr[1], arr[2]);
    add_to(arr, 3, 2);
    printf("new array: %d, %d, %d\n", *arr, *(arr+1), *(arr+2));
    return 0;
}
void add_to(int *arr, int n, int value)
{
    for(int i=0;i<n;i++)
        *(arr+i) += value;
}
original array: 1, 3, 5
new array: 3, 5, 7

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

不小心改原数据的示例

#include <stdio.h>
int sum( int *arr, int n);
int main()
{
    int total;
    int arr[3] = {1, 3, 5};
    printf("array: %d, %d, %d\n", arr[0], arr[1], arr[2]);
    total = sum(arr, 3);
    printf("sum of the array: %d\n", total);
    printf("array: %d, %d, %d\n", arr[0], arr[1], arr[2]);
    return 0;
}
int sum( int *arr, int n)
{
    int sum=0;
    for(int i=0;i<n;i++)
        sum += arr[i]++;//错误代码,正确应为sum += arr[i];
    return sum;
}

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

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

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

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

在这里插入图片描述

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

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

在这里插入图片描述

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

#include <stdio.h>
#define SIZE 4
void show_array(const double *arr, int n);
void mult_array(double *arr, int n, double mul);
int main()
{
    double dip[SIZE] = {2.34, 4.56, 7.23, 5.89};
    printf("array: %.2f, %.2f, %.2f, %.2f\n", *(dip), *(dip+1), dip[2], dip[3]);
    show_array(dip, SIZE);
    printf("array: %.2f, %.2f, %.2f, %.2f\n", *(dip), *(dip+1), dip[2], dip[3]);
    mult_array(dip, SIZE, 2.00);
    printf("array: %.2f, %.2f, %.2f, %.2f\n", *(dip), *(dip+1), dip[2], dip[3]);
    return 0;
}

void show_array(const double *arr, int n)
{
    for(int i=0;i<n;i++)
        printf("%.2f ", *(arr+i));
    putchar('\n');
}

void mult_array(double *arr, int n, double mul)
{
    for(int i=0;i<n;i++)
        *(arr+i) *= mul;
}

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

array: 2.34, 4.56, 7.23, 5.89
2.34 4.56 7.23 5.89
array: 2.34, 4.56, 7.23, 5.89
array: 4.68, 9.12, 14.46, 11.78

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

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

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

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

#include <stdio.h>
#define SIZE 4
int main()
{
    double dip[SIZE] = {2.34, 4.56, 7.23, 5.89};
    const double *ptr = dip; //ptr是一个指向const的指针
    const double rate[SIZE] = {1.22, 3.56, 7.21, 9.34};
   // *ptr=2.56;//试图修改const指针指向的变量,错误
    //ptr[2]=4.59;//试图修改const指针指向的变量,错误
    dip[1]=3.33;//正确,因为dip没有被const限制
    ptr++;//正确,虽然被const限制,不可以通过ptr改变他所指向的值,但是他想指向别处还是允许的
    ptr = rate; //const指针允许指向一个受const保护的数组,实际上const指针可以指向任何数组,
    //和数组自己有没有被const限制没有一毛钱关系
    ptr = &dip[2];//指向别处,可以
    return 0;
}
  1. const指针指向const数组
#include <stdio.h>
#define SIZE 4
int main()
{
    double dip[SIZE] = {2.34, 4.56, 7.23, 5.89};//普通数组
    const double rate[SIZE] = {1.22, 3.56, 7.21, 9.34};//const数组
    double *ptr1 = dip;//普通指针
    const double *ptr = dip; //ptr是一个指向const的指针
    ptr=rate;
    //*ptr=2.44;//const指针指向const数组,错误,无法修改
    return 0;
}
  1. const指针指向非const数组
#include <stdio.h>
#define SIZE 4
int main()
{
    double dip[SIZE] = {2.34, 4.56, 7.23, 5.89};//普通数组
    const double rate[SIZE] = {1.22, 3.56, 7.21, 9.34};//const数组
    double *ptr1 = dip;//普通指针
    const double *ptr = dip; //ptr是一个指向const的指针
    ptr=dip;
    //*ptr=2.44;//const指针指向非const数组,错误,无法修改
    return 0;
}
  1. 普通指针指向const数组(陷阱!!会修改数组元素的值)
#include <stdio.h>
#define SIZE 4
int main()
{
    const double rate[SIZE] = {1.22, 3.56, 7.21, 9.34};
    printf("rate array:%.2f %.2f %.2f %.2f\n", rate[0], rate[1], rate[2], rate[3]);
    double *ptr1 = rate;//非const指针指向const数组,仍然必须保证不能通过指针修改这个数组
    *ptr1 = 2.34;//可以!!!会修改const数组,说明非const指针指向const数组是一个陷阱!!!
    printf("rate array:%.2f %.2f %.2f %.2f\n", rate[0], rate[1], rate[2], rate[3]);
    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限制形参)

#include <stdio.h>
#define SIZE 4
void show_array(const double *arr, int n);
void mult_array(double *arr, int n, double mul);
int main()
{
    const double dip[SIZE] = {2.34, 4.56, 7.23, 5.89};
    printf("array: %.2f, %.2f, %.2f, %.2f\n", *(dip), *(dip+1), dip[2], dip[3]);
    show_array(dip, SIZE);
    printf("array: %.2f, %.2f, %.2f, %.2f\n", *(dip), *(dip+1), dip[2], dip[3]);
    mult_array(dip, SIZE, 2.00);
    printf("array: %.2f, %.2f, %.2f, %.2f\n", *(dip), *(dip+1), dip[2], dip[3]);
    return 0;
}

void show_array(const double *arr, int n)
{
    for(int i=0;i<n;i++)
        printf("%.2f ", *(arr+i));
    putchar('\n');
}

void mult_array(double *arr, int n, double mul)
{
    for(int i=0;i<n;i++)
        *(arr+i) *= mul;
}

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

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

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

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

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

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

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

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

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

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

用两次const

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

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

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

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

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

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

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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值