chess[0]和&chess[0]一样的哦,都是地址
大概是因为编译器把chess[0]当做二维数组的第一个元素(一个一维数组)的名字吧
大概编译器看到&chess[0]并不会真的把chess[0]当做一个变量,然后去看它被存在哪里,而是知道我实际是想要chess[0]地址,所以不管我用这两个的哪一个,都给了我正确的地址
#include <stdio.h>
#define SIZE 3
int main()
{
int chess[SIZE][SIZE-1] = {{1, 2},{4, 6}, {9, 3}};
printf("chess=%#p, chess[0]=%#p, &chess[0]=%#p, &chess[0][0]=%#p\n", chess, chess[0], &chess[0], &chess[0][0]);
printf("chess+1=%#p, chess[0]+1=%#p", chess+1, chess[0]+1);
}
但是chess+1是加了8个字节,即2个int类型的值的大小;而chess[0]+1只加了4字节。这是因为chess它指向的是二维数组里面的每个子数组的首地址,递增1就会递增整个子数组的大小。而chess[0]递增只会递增二维数组的子数组的值元素,只有一个,所以是4字节。说的有点不清楚,总之应该能理解
chess=0x61ff18, chess[0]=0x61ff18, &chess[0]=0x61ff18, &chess[0][0]=0x61ff18
chess+1=0x61ff20, chess[0]+1=0x61ff1c
二维数组的双重间接(二维数组名是地址的地址)
下面这段话就有点绕了·····
来强行梳理一波,得从里层到外层来
chess[0]是二维数组的首元素数组的名字,存储着它的首元素的值,所以 ∗ * ∗chess[0] = chess[0][0]
然后,chess是二维数组的数组名,是首元素的地址,首元素是chess[0], 所以 ∗ * ∗chess本来应该是chess[0]的值,但是 chess[0]是数组啊,chess[0]自己都仍然是个地址, ∗ * ∗chess=&chess[0][0]
要得到值而非地址,只能再用一次解引用运算符
∗
∗
**
∗∗chess= chess[0][0]
即 ∗ ∗ ** ∗∗chess等价于 ∗ * ∗&chess[0][0],···有点晕,但是我们应该早就知道解引用运算符和取址运算符有点相反运算符的意思
对chess要用两次解引用才可以得到具体值,所以说chess是地址(指chess[0])的地址,所以说是双重间接。
如果是三维数组,那chess就需要用三次解引用才可以得到值,三重间接,chess是地址(chess[0][0])的地址(chess[0])的地址。
下面是一个示例
#include <stdio.h>
#define SIZE 3
int main()
{
int chess[SIZE][SIZE-1] = {{1, 2},{4, 6}, {9, 3}};
printf("chess = %p, chess + 1 = %p\n", chess, chess+1);
printf("chess[0] = %p, chess[0] + 1 = %p\n", chess[0], chess[0]+1);
printf("*chess = %p, *chess + 1 = %p\n", *chess, *chess+1);
printf("chess[0][0] = %d\n", chess[0][0]);
printf("*chess[0] = %d\n", *chess[0]);
printf("**chess = %d\n", **chess);
printf("chess[2][1] = %d\n", chess[2][1]);
printf("*(*(chess+2)+1) = %d\n", *(*(chess+2)+1));
}
所以:
∗
*
∗chess就是chess[0],
∗
*
∗chess[0]就是chess[0][0],就是
∗
∗
**
∗∗chess
chess
[
2
]
[
1
]
[2][1]
[2][1](数组表示法)就是*(*(chess+2)+1) (指针表示法)
chess = 0061ff18, chess + 1 = 0061ff20
chess[0] = 0061ff18, chess[0] + 1 = 0061ff1c
*chess = 0061ff18, *chess + 1 = 0061ff1c
chess[0][0] = 1
*chess[0] = 1
**chess = 1
chess[2][1] = 3
*(*(chess+2)+1) = 3
指向二维数组的指针
声明
用程序说明
#include <stdio.h>
#define SIZE 3
int main()
{
int chess[SIZE][SIZE-1] = {{1, 2},{4, 6}, {9, 3}};
int *ptr[2];
//ptr是一个存了两个指向int的指针的数组
ptr[0] = chess[0];
ptr[1] = &chess[2][1];
printf("ptr[0]=%p, ptr[1]=%p\n", ptr[0], ptr[1]);
printf("ptr[0]+1=%p, ptr[1]+1=%p\n", ptr[0]+1, ptr[1]+1);
int (*ptr1)[2];//ptr1是一个指向数组的指针
ptr1 = chess[0];
printf("ptr1=%p\n", ptr1);
printf("ptr1+1=%p\n", ptr1+1);
return 0;
}
通过给指针加1就能证明上面的论述,ptr[0]和ptr[1]加1内存加了4字节,说明他俩是指向一个int值;而ptr1加1,内存加了8字节,说明人家确实是指向了一个数组,因为我的二维数组的元素数组只有2个int元素所以内存变了8字节哈
ptr[0]=0061ff14, ptr[1]=0061ff28
ptr[0]+1=0061ff18, ptr[1]+1=0061ff2c
ptr1=0061ff14
ptr1+1=0061ff1c
示例 (对指针使用方括号相当于使用解引用运算符)
#include <stdio.h>
#define SIZE 3
int main()
{
int chess[SIZE][SIZE-1] = {{1, 2},{4, 6}, {9, 3}};
int (*ptr)[2];
ptr = chess;
printf("ptr=%p, ptr+1=%p, ptr+2=%p\n", ptr, ptr+1, ptr+2);
printf("ptr[0]=%p, ptr[0]+1=%p, ptr[1]=%p, ptr[2]=%p\n", ptr[0], ptr[0]+1, ptr[1], ptr[2]);
printf("*ptr=%p, *ptr+1=%p\n", *ptr, *ptr+1);
printf("ptr[0][0]=%d\n", ptr[0][0]);
printf("*ptr[0]=%d\n", *ptr[0]);
printf("**ptr=%d\n", **ptr);
printf("ptr[2][1]=%d\n", ptr[2][1]);
printf("*(*(ptr+2)+1)=%d\n",*(*(ptr+2)+1));
return 0;
}
可以看到,
- ptr是指向数组的指针,所以对他加1会加8字节(因为我这里的二维数组的元素数组只有2个int值)
- 对指针使用方括号就相当于使用解引用运算符!!ptr等于ptr[0], ptr+1等于ptr[1], ptr+2 等于ptr[2]但他们并不等效!!ptr是指向第一个数组元素的指针,ptr+1是指向第二个数组元素的指针,ptr[0]是指向第一个数组的第一个元素的指针。ptr[2]是指向第三个数组元素的首元素的指针。所以 ∗ * ∗ptr和ptr[0]是完全等效的; ∗ * ∗ptr+1完全等效于ptr[1]; ∗ * ∗ptr+2等效于ptr[2];而ptr[0][0]等效于 ∗ ∗ ** ∗∗ptr,也等效于 ∗ * ∗ptr[0];
- 虽然ptr本身是一个指针,它并不是数组,却可以用类似于数组的写法来使用,ptr[2][1]
ptr=0061ff14, ptr+1=0061ff1c, ptr+2=0061ff24
ptr[0]=0061ff14, ptr[0]+1=0061ff18, ptr[1]=0061ff1c, ptr[2]=0061ff24
*ptr=0061ff14, *ptr+1=0061ff18
ptr[0][0]=1
*ptr[0]=1
**ptr=1
ptr[2][1]=3
*(*(ptr+2)+1)=3
所以访问多维数组的元素可以用我们最熟悉的也比较简单的数组表示法,也可以用指针表示法
指针的兼容性(不可以把不同类型的指针相互赋值)
我的编译器对于这类不兼容的赋值全部给警告,所以更加说明必须处理警告啦,并不是说警告比错误更不重要,编译器把一些很重要的错误忽视了,只发送警告,如果你不处理警告,你可能得到了错误结果还不知道为啥····
C++在指针赋值方面的类型兼容性检查的严格的多,不会只给警告的
这一节的示例都有点绕哦,不过对于理解多维数组的指针也蛮重要的,要有耐心
#include <stdio.h>
int main()
{
int n=6;
double x;
x=n;//数值可以跨类型赋值,编译器会自动隐式转换类型
int *p1 = &n;
double *p2;
p2 = p1;
return 0;
}
编译器警告,把不兼容的int指针赋给double指针,有的编译器直接报错的
更多示例
#include <stdio.h>
int main()
{
int *pt;
int (*pa)[3];//指向有3个int元素的数组的指针
int ar1[2][3];//二维数组
int ar2[3][2];
int **p2;//p2是指向指针的指针,它指向的指针指向int变量
pt = &ar1[0][0];//正确,都是指向int的指针
pt = ar1[0];//正确,都是指向int的指针
pt = ar1; //错误,pt是指向一个int值的指针,而ar1是指向数组的指针
pa = ar1;//正确,ar1也是指向有3个int元素的数组的指针
pa = ar2;//错误,因为ar2是指向有2个int元素的数组的指针,不兼容
p2 = &pt;//正确,这里可以看p2指向的指针和pt是否是一个类型,p2指向的指针指向int,而pt也是指向int,所以正确
p2 = ar2[0];//错误,p2是指向指针的指针,但是ar2[0]是指向int的指针
*p2 = ar2[0];//正确,都是指向int的指针
p2 = ar2;//错误,ar2是指向数组的指针,p2是指向int的指针
return 0;
}
四个错误的地方都给了警告
不要把const指针赋给非const指针,不安全(C允许,C++禁止!!)
因为这样就可以通过新的非const指针修改原来用const指针指向的数据了!!
#include <stdio.h>
int main()
{
int x = 20;
const int y = 23;
int *p1 = &x;
const int *p2 = &y;
p1 = p2;//不安全,const指针赋值给非const指针,编译器警告会去掉const
p2 = p1;//非const指针赋给const指针,没问题
return 0;
}
把非const指针赋给const指针也可能出错,在有多级解引用时
#include <stdio.h>
int main()
{
const int x = 20;
int *p1;
const int **pp2;
pp2 = &p1;//警告,把int**赋给不兼容的const int**
*pp2 = &x;//正确,完全兼容
printf("x is %d\n", x);
*p1 = 10;//兼容
printf("x is %d\n", x);
return 0;
}
把受const保护的x改变了值
x is 20
x is 10
示例 处理二维数组的函数(注意形参的声明)
只需要把行数作为形参, 列数是内置在函数体内
注意void sum_cols(int [][COLS], int);中COLS的位置不可以写变量,只可以使常量,如果写变量就成了变长数组,看后面
#include <stdio.h>
#define ROWS 3
#define COLS 4
void sum_rows(int a[][COLS], int rows);//空的方括号表明形参是一个指针
void sum_cols(int [][COLS], int);
int sum2d(int (*ar)[COLS], int rows);//注意这样声明只需要用到列数哦
int main()
{
int junk[ROWS][COLS]={{2, 4, 6, 8}, {1, 3, 5, 7}, {10, 20, 30, 40}};
sum_rows(junk, ROWS);//junk是一个指向数组的指针
sum_cols(junk, ROWS);
printf("sum of all elements=%d\n", sum2d(junk, ROWS));
return 0;
}
void sum_rows(int ar[][COLS], int rows)
{
int r, c, tot;
for(r=0;r<rows;r++)
{
tot = 0;
for(c=0;c<COLS;c++)
tot += ar[r][c];
printf("row %d: sum = %d\n", r, tot);
}
}
void sum_cols(int ar[][COLS], int rows)
{
int r, c, tot;
for(c=0;c<COLS;c++)
{
tot = 0;
for(r=0;r<rows;r++)
tot += ar[r][c];
printf("col %d: sum = %d\n", c, tot);
}
}
int sum2d(int ar[][COLS],int rows)
{
int r, c, tot=0;
for(r=0;r<rows;r++)
for(c=0;c<COLS;c++)
tot += ar[r][c];
return tot;
}
row 0: sum = 20
row 1: sum = 16
row 2: sum = 100
col 0: sum = 13
col 1: sum = 27
col 2: sum = 41
col 3: sum = 55
sum of all elements=136
变长数组 VLA
千万不要顾名思义!!!没有那么柔韧
不知道这个程序为啥不对???难道是我的编译器不支持,我想去找支持变长数组编译选项,没找到,换成c99也不行
#include <stdio.h>
int sum2d(int row, int col, int ar[row][col]);
int main()
{
int quarters = 2;
int regions = 4;
int tot1, tot2;
int sales[regions][quarters] = {{1, 3},{4, 6},{12, 13},{34, 1}};//变长数组
int buy[3][3] = {{1,2,3},{4,5,2},{8,9,0}};
tot1 = sum2d(regions, quarters, sales);
tot2 = sum2d(3, 3, buy);
printf("sum of sales is %d\n", tot1);
printf("sum of buy is %d\n", tot2);
return 0;
}
int sum2d(int row, int col, int ar[row][col])//方括号中的是变量而不是常量
{
int tot=0;
for(int r=0;r<row;r++)
for(int c=0;c<col;c++)
tot += ar[r][c];
return tot;
}
这是因为我的赋值不对,变长数组的值必须一个一个地赋值,不知道为什么
#include <stdio.h>
int sum2d(int row, int col, int ar[row][col]);
int main()
{
int quarters = 2;
int regions = 4;
int tot1, tot2;
int i, j;
int sales[regions][quarters];//变长数组,aka动态数组
int buy[3][3] = {{1,2,3},{4,5,2},{8,9,0}};
for(i=0;i<regions;i++)
{
for(j=0;j<quarters;j++)
{
sales[i][j] = i+j;
}
}
tot1 = sum2d(regions, quarters, sales);
tot2 = sum2d(3, 3, buy);
printf("sum of sales is %d\n", tot1);
printf("sum of buy is %d\n", tot2);
return 0;
}
int sum2d(int row, int col, int ar[row][col])//方括号中的是变量而不是常量
{
int tot=0;
for(int r=0;r<row;r++)
for(int c=0;c<col;c++)
tot += ar[r][c];
return tot;
}
sum of sales is 16
sum of buy is 34
动态内存分配是指在程序运行时才分配内存,静态内存分配是指编译时分配
只要在编译时编译器知道的信息就是静态的吗?
在程序运行时才操作则很像面向对象
复合字面量
下图中是一个匿名数组
示例
#include <stdio.h>
#define COLS 4
int sum2d(const int ar[][COLS], int rows);
int sum(const int ar[], int n);
int main()
{
int tot1, tot2, tot3;
int *pt1;//指向int的指针
int (*pt2)[COLS];//指向有COLS个元素的数组的指针
pt1 = (int[2]){10, 20};//匿名数组,pt1指向数组首元素
//二维匿名数组,pt2指向第一个子数组
pt2 = (int[2][COLS]){{1, 2, 3, -9}, {4, 5, 6, 7}};
tot1 = sum(pt1, 2);//数组为参数,传入首地址就好了,因为声明虽然是数组表示,但会转换为指针
tot2 = sum2d(pt2, 2);
tot3 = sum((int []){4, 4, 4, 5, 5, 5}, 6);//传入复合字面量(数组)作为参数,实际上也是传的首地址啦
printf("total1=%d\n", tot1);
printf("total2=%d\n", tot2);
printf("total3=%d\n", tot3);
return 0;
}
int sum(const int ar[], int n)
{
int sum=0;
for(int i=0;i<n;i++)
sum += ar[i];
return sum;
}
//二维数组第一个方括号是空的,第二个要写列数
int sum2d(const int ar [][COLS], int rows)
{
int sum=0;
for(int r=0;r<rows;r++)
for(int c=0;c<COLS;c++)
sum += ar[r][c];
return sum;
}
total1=30
total2=19
total3=27
对数组和指针的总结
在C中,数组是一种派生类型,所以声明的时候必须说明数组元素的类型,比如int类型的数组,float类型的数组,但是也可以创建数组类型的数组哦,即多维数组
处理数组的函数的形式参数都是数组的首元素的地址哈,即数组名,即指针,而不是数组本身,当然了,为了正确处理,还要把数组的元素个数作为单独的形式参数(字符数组除外,因为有空字符表示字符串结束,厉害哦)。重要的事情说三遍:处理数组的函数的形参是指针;处理数组的函数的形参是指针;处理数组的函数的形参是指针。