文章目录
之前已经接触过很多次数组了,而且我感觉我经常用数组,很多问题都是用一维数组解决的,只是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};
又来一个不懂的东西····
声明数组的同时最好初始化
- 不初始化:
#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
- 初始化但是不写值
int a[SIZE] = {};
默认全部初始化为0
0: 0
1: 0
2: 0
3: 0
- 部分初始化
int a[SIZE] = {1, 2};
只有前两个元素被初始化为想要的值,其他的被初始化为0
0: 1
1: 2
2: 0
3: 0
- 花括号的值的数目多于数组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
- 指定初始化器(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;
}
- 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;
}
- 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;
}
- 普通指针指向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
- 普通指针指向普通数组(非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