C语言中为了表示某个数据对象的地址,引出了指针这种概念,通过取地址&运算符来创建指针。为了表示出所指向对象的类型,指针也具有相对应的类型,而不是单纯的地址。
譬如指向一个int类型的对象的指针,那么其类型是int*,
int a=1;
int* ap=&a;
上面的例子中我们声明了一个int*类型的指针变量ap,&a是a的指针,作为ap的初始值。
当然有时候为了方便,很多情况下我们也直接称ap是指向对象a的指针,不特意区分指针变量和指针概念。
指针变量ap类型是int*,表示其指向的是一个int类型对象。
如果希望获得指针指向的那个对象,可以用*运算符,如下
int b=*ap;
int c=*&a;
我们再看一个例子:
short b=2;
int* ap=(int*)&b;
上面我们创建对象b的指针,并且强制转换成int*类型指针,赋值给变量ap,那么ap此时是指向b吗?
答案不是的。
b是short类型占2个字节,而ap是int*类型,它所指向的对象必须是int类型的对象,占4个字节。就算我们用b的指针强制转换赋值给ap以后,也只是初始地址一样,对于ap来说,根本不知道b,它指向的对象包含了b以及b后面2个字节加一起,整个构成一个4字节的int对象。
数组是一系列特定类型对象组合成的数据结构,数组的概念在其他高级语言中都有出现,基本都差不多
int array[5]={1,2,3,4,5};
声明一个名为array的数组,并且初始化,该数组有5个int类型的对象构成,分别为1,2,3,4,5
array是该数组的标识符,是一个不可修改变量,只可以初始化,无法重新赋值。
很多人说array是指针常量,所以无法修改,这其实是错误的,按照C标准里解释:数组名是不可修改左值。这才是原因。
array={2,3,4,5,6} 错误,因是不可修改左值,无法重新赋值。
通过下标运算符[ ]可以访问数组中的元素
int a=array[0];获取数组的第0个元素1,并初始化变量a
array[0]=2;修改数组的第0个元素,改为2
很多情况下,好像下标运算符[ ]是用于数组的,但是其实关于 [ ]的定义是用于两个操作数,一个操作数是指针,另一个是整数,顺序无关, 所以[ ]运算符并不是用于数组的。也就是a[b] 时 a为指针b整数,或者a是整数b是指针都可以,所以上面array[0]=2 也可以写0[array]=2
int a=1;
int* ap=&a;
a=ap[0];//ap是指针变量,用[ ] 运算符,虽然看着怪怪的,但这是完全正解的方式
这时候你疑问:不对啊?不是说[ ] 运算符用于指针吗,你又说array是数组啊,也可以了?
其实这里是array隐适转换成了数组首元素的指针,也就是array相当于&array[0]。
array[0]等同于*(array+0)
这也是为什么很多人说数组名是首元素的指针或者地址,当然这是错误的说法,只是看着像,实际是因为发生了隐适转换,而且大多数表达式中都发生了这种转换,少数用于&和sizeof运算符的时候不会发生转换,以及当字符串字面量用于初始化char []数组的时候,不要问为什么,这是C语言ISO标准规定的,对于这种规定我们只需要记住。
这里特意提一下字符串字面量,也就是这种“hello”,它在C语言中是字符数组类型。是数组类型,那么在大多数表达式中会隐适转换成首元素指针,初始化字符数组char []类型的时候不会转换。
char arrstr[]={"hello"} 这是标准方式,因为是初始化数组,所以需要花括号,但是这里我们可以去掉花括号,也算语法糖,为了方便而已。所以我们通常见到的是 char arrstr[]="hello"。
char* arrstrp [ ]={“hello”} 这也算字面串用于初始化吧? 但是因为它初始化的不是 char[] 这种类型,
所以"hello"会转为首元素的指针,相当于char *arrstrp [] = { &("hello"[0]) }
在C语言的最新标准ISO/IEC 9899:2023 中 有一个新的运算符 typeof 可以获取数据类型
可以这样用:
int arr[2]={1,2};//声明数组arr
typeof(arr) newarr={3,4};//typeof(arr) 获得了数组名arr的类型,是一个int [2]类型 声明新的数组newarr
所以再说一遍,不要再把数组名当成指针了!
int a = array[0];//隐适转换成了首元素指针,然后进行*(array+0)运算,获取首元素
int *p=array; //隐适转换成了首元素指针
printf("%p",array); //隐适转换成了首元素指针
int (*ap)[5] = &array ;//未转换成指针,这里&array是返回数组array的指针
int size = sizeof array; //未转换成指针,这里返回数组的大小,如果int占用4字节,那么返回20
array++;//错误,因为array是不可修改左值,当然也不可能转换成指针
array是一维数组,其中的每一个元素都是int类型的对象,那么假如每一个元素又是一个数组呢?
这就是二维数组,也就是数组的每一个元素还是数组。
下面我们声明一个二维数组,其中有3个元素,每个元素本身又是一个含有5个int元素的数组
int array[3][5]={{1,2,3,4,5},{6,7,8,9,10},{11,12,13,14,15}}
访问第0个子元素就是array[0], 它本身也是一个{1,2,3,4,5}一维数组。
那么如果我们希望访问数组array[0]的第0个子元素,只要array[0][0]就可以了
有人也会称呼array[0]为元素1的指针, 这是不对的,array[0]返回的是子元素,是一个一维数组。当然有人会这样解释
int *a=array[0] ;//a是元素1的指针变量
int b=*array[0];//获得元素1
然后说,你看着分明就是指针啊,但这里其实也是因为数组array[0]在表达式中,已经转换成了首元素指针,怎么证明呢,很简单 sizeof array[0]输出的是20,如果是指针,不可能得到数组的大小。我们对一个指针 sizeof的时候 获取的是指针的大小,不可能因为是数组的指针,就特例。但是很多书中或者文章就会奇葩说数组指针是特例。其实C语言的标注规范中从没有说过数组名是指针,为什么传到后面很多书都直接说是指针。
&array[0]的类型是 int (*)[5] 这是一个数组的指针类型
上面我们说了通过array[0][0]就可以获取二维数组中的元素1,也就是
int c=array[0][0];//获取元素1
下面我们看下完整的过程
1、array是数组变量,作为表达式首先转换成首元素的指针p1,也就是转换成指向数组{1,2,3,4,5}的指针p1
2、对指针p1执行[ ]操作,等同于*(p1+0),返回子数组{1,2,3,4,5}
3、子数组{1,2,3,4,5}作为表达式再次转换成首元素指针p2,也就是转换成指向元素1的指针p2
4、对指针p2执行[ ]操作,等同于*(p2+0),返回子元素1
当然如果实际你查看编译后的汇编代码,不会真的这样一步步下来,那还不慢死了,现在的编译器都是经过优化的,这边讲的只是原理
网上有很多人从一开始就错误的理解了指针和数组,认为数组名就是指针,然后又说二维数组名就是二级指针,希望通过这篇讲解,让大家认识这完全不是一回事,只是其中发生了隐适转换而已。
类似下面的转换
short a=1;
long b=a;//这边short类型的 a隐适转换成long类型的b
我们并不因为a在下面这个表达式中发生了隐适类型转换,就说a是long类型,a还是short类型