十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

C语言语法详解之指针(四)从入门到入土

C语言语法详解之指针(四)从入门到入土 1.0数组的本质1.1数组名的理解我们先来看这样一串代码int arr[10] {1,2,3,4,5,6,7,8,9,10}; int *p arr[0]我们这里使用了arr[0]的方式取出了数组中首个元素的地址但是实际上数组名本身就是地址只不过这里使用了取地址的方式方便理解。那么我们不禁要思考一个问题一个数组内部有那么多的元素如果只写出一个数组名不标注序号出现的地址是哪一个元素的呢我们来做一个测试。//测试环境:X86 #include stdio.h int main() { int arr[10] { 1,2,3,4,5,6,7,8,9,10 }; printf(arr[0] %p\n, arr[0]); printf(arr %p\n, arr); return 0; }输出结果如下这时候我们会发现单独一个数组名竟然和首元素地址的输出结果是一样的那么显然当我们单独打出一个数组名的时候得出的结果就只会与其首元素地址相同但是这时候我们就会有疑惑了使用sizeof的时候为什么显示的不是首元素的大小呢#include stdio.h int main() { int arr[10] { 1,2,3,4,5,6,7,8,9,10 }; printf(%d\n, sizeof(arr)); return 0; }在这里面输出的结果是40和我们原先的猜想怎么不一样呢。但是其实这是属于极少数的个例在C语言的编程中只有两种情况会发生不一样的结果在其他情况下单独数组名的出现指向的都会是它首元素的地址。特殊情况如下所示a. sizeof数组名在sizeof中存放数组名计算的是整个数组的大小而不是首元素的大小。b. 数组名这里取出的是整个数组的地址在实现数组加减的时候跨越的字节数量是不一样的下面会用代码进行解释。这两个例外只有在它们单独出现的时候才会生效如果是处于叠加状态比如说sizeofarr1的情况下那么arr数组名就仍然只会等于首元素的地址。为了更深入理解数组的性质我们不妨再尝试一下这一串代码。#include stdio.h int main() { int arr[10] { 1,2,3,4,5,6,7,8,9,10 }; printf(arr[0] %p\n, arr[0]); printf(arr %p\n, arr); printf(arr %p\n, arr); return 0; }尝试后我们不难发现三个打印结果一模一样那么arr到底与arr有什么区别呢//测试环境:X86 #include stdio.h int main() { int arr[10] { 1,2,3,4,5,6,7,8,9,10 }; printf(arr[0] %p\n, arr[0]); printf(arr[0]1 %p\n, arr[0]1); printf(arr %p\n, arr); printf(arr1 %p\n, arr1); printf(arr %p\n, arr); printf(arr1 %p\n, arr1); return 0; }输出结果arr[0] 0077F820 arr[0]1 0077F824 arr 0077F820 arr1 0077F824 arr 0077F820 arr1 0077F848我们不难发现取出首元素地址与单个数组名的情况下都是仅仅向后跳过了4个字节而取出arr则是向后跳了整个数组也就是40个字节。证明了arr确实是使用的取出整个数组的形式来存储与内存当中的只是碍于表达形式只能打印出首元素的地址。2.0使用指针访问数组2.1arr[]实际上是*arr的语法糖我们不妨使用这么一个指针来访问数组#include stdio.h int main() { int arr[10] {0}; //输⼊ int i 0; int sz sizeof(arr)/sizeof(arr[0]); //输⼊ int* p arr; for(i 0; i sz; i) { scanf(%d, pi); //scanf(%d, arri);//也可以这样写 } //输出 for(i 0; i sz; i) { printf(%d , *(pi)); } return 0; }通过这一个代码我们可以很清楚的发现通过指针不断相加的存储然后再进行解引用就能输出其中的内容对于数组而言在没有进行[]处理的时候使用解引用对应指针的方式也能够打印出其中的数值。但我们此时肯定不禁会有一个疑惑为什么在使用scanf进行输入的时候并不需要解引用也能存储改变数组空间当中的内容呢这是因为在scanf的输入之时就会自动的解引用指向对应的空间所以在使用scanf的时候我们都会在变量之前加上符号而对于数组名就直接相当于首元素地址的数组而言就不需要了这也是数组与普通变量之间的直接区别。这个代码搞明白后我们再试一下如果我们再分析一下数组名arr是数组首元素的地址可以赋值给p其实数组名arr和p在这里是等价的。那我们可以使用arr[i]可以访问数组的元素那p[i]是否也可以访问数组呢#include stdio.h int main() { int arr[10] {0}; //输入 int i 0; int sz sizeof(arr)/sizeof(arr[0]); //输入 int* p arr; for(i 0; i sz; i) { scanf(%d, pi); //scanf(%d, arri);//也可以这样写 } //输出 for(i 0; i sz; i) { printf(%d , p[i]); } return 0; }我们运行这个代码不难发现p[i]的结果与上面*pi的结果是相同的那么实际上这两个代码在运行的角度中就正是一样的这样的情况正如标题所说称之为arr[]实际上是*arr的语法糖在指针的情况中如此面对数组本体也能起到同样的效果3.0一维数组传参的本质3.1数组传参的实验数组我们之前学过了也讲过了。我们不妨来做一个实验来测试数组在函数当中是怎么传递的。到底是不是如同我们之前所说的那样传递的为首元素的大小。为了达成实验效果我们将在函数中与函数外分别计算数组中的元素个数#include stdio.h //测试环境是x86 void test(int arr[]) { int sz2 sizeof(arr)/sizeof(arr[0]); printf(sz2 %d\n, sz2); } int main() { int arr[10] {1,2,3,4,5,6,7,8,9,10}; int sz1 sizeof(arr)/sizeof(arr[0]); printf(sz1 %d\n, sz1); test(arr); return 0; }得出的结果是意料之中的在函数内部并没有正确获得数组中的元素个数。在上个小节中我们学到了数组的本质实际上是指针而单独一个数组名存储的实际上是数组首元素的地址而在此处函数的传参中传递上去的实际上就不过是一个地址而已并没有将整个数组完整的传递上去。于此同时sizeof计算的大小就是一个地址的大小而不是整体数组的大小正是因为函数的参数部分是本质是指针所以在函数内部是没办法求的数组元素个数的。void test(int arr[])//参数写成数组形式本质上还是指针 { printf(%d\n, sizeof(arr)); } void test(int* arr)//参数写成指针形式 { printf(%d\n, sizeof(arr));//计算⼀个指针变量的⼤⼩ } int main() { int arr[10] {1,2,3,4,5,6,7,8,9,10}; test(arr); return 0; }总结一维数组传参无论是写成数组形式还是指针形式都行两者在代码中代表的含义是相同的这就是数组的本质。4.0二级指针与指针数组4.1二级数组的本质变量有各种各样的类型从总的方面来说指针变量也是变量那么既然是变量就会有储存的空间也就产生了指向这个空间的地址那么储存这一个地址的指针就被称之为二级数组。这个概念对于数据中的链表是十分的重要的它代表了一个串联的线往后可以由此组成一张大网方便我们快速寻找到应有的数据。它的基础代码是这样的#include stdio.h int main() { int a 10; int *pa a; int** ppa pa; return 0; }相比于一级指针来说看起来好像不过只是多加了一个*而在计算机的系统中它却是这样表示在实际上的意义中pa代表着一维指针指向的那个地点而*pa则代表其指向的空间也就是a那么*ppa实际上就是等于pa而**ppa则代表着a往后以此类推哪怕是三维指针或者四维指针它们所代表的结果都是这样的了。4.2指针数组指针数组到底是数组还是指针呢我相信大部分人在看见这个标题的时候都会产生这个疑惑。但是正如同整形数组于字符数组是存放对应数据类型的数组指针数组存放的就都是指针这与后面我们会讲到的数组指针有很大的区别。我们不妨先来看看这样一串代码好让我们充分了解指针数组的初始化和创建方式#include stdio.h int main() { int a 10; int b 20; int c 30; int* arr[3] {a, b, c};//指针数组存放的是三个int*类型的指针 int i 0; for(i 0; i 3; i) { printf(%d , *(arr[i]));//通过指针解引用取出对应变量的值 } return 0; }对于指针数组来说必须要填充其中的具体数字当初始化为0时实际上是默认在里面创建了NULL指针当你不规定具体的元素个数时这里的指针数组如果有具体指向就会很容易导致程序崩溃所以在使用的时候最好还是提前规定一下4.3指针数组模拟二维数组我们都知道二维数组来表示编号的时候是以X Y来为具体的元素空间填充序号的那么在使用指针数组的过程中我们就可以模拟这种效果指针数组里面的序号变为实际上二维数组中的列号内部蕴含着的数组成为行号。代码实现如下#include stdio.h int main() { int arr1[] {1,2,3,4,5}; int arr2[] {2,3,4,5,6}; int arr3[] {3,4,5,6,7}; //数组名是数组⾸元素的地址类型是int*的就可以存放在parr数组中 int* parr[3] {arr1, arr2, arr3}; int i 0; int j 0; for(i 0; i 3; i) { for(j 0; j 5; j) { printf(%d , parr[i][j]); } printf(\n); } return 0; }parr[i] 是访问 parr 数组的元素parr[i] 找到的数组元素指向了整型一维数组parr[i][j] 就是整型一维数组中的元素。上述的代码模拟出二维数组的效果实际上并非完全是二维数组因为每一行并非是连续的。从上面的代码和运行结果可以看出指针数组模拟二维数组的核心思路是用一个指针数组来存放多个一维数组的首元素地址再通过双重下标访问的方式把原本分散在内存中的多个一维数组在逻辑上组织成一个类似二维数组的结构。其中 parr 是外层指针数组parr[i] 取出第 i 个一维数组的首地址parr[i][j] 则进一步访问该一维数组中的第 j 个元素。这种写法在逻辑上非常直观也便于理解指针与数组之间的紧密联系。但需要特别注意的是它和真正的二维数组在内存布局上有着本质区别真正的二维数组在内存中是连续存放的所有元素按行紧密排列而指针数组模拟出的二维数组每一行实际上是独立的一维数组它们在内存中的地址并不连续行与行之间可能存在空隙。因此这种模拟方式更适合用于逻辑上的行列访问如果对内存连续性有严格要求例如需要整体拷贝或按连续内存块操作仍然应当使用真正的二维数组。总结指针数组模拟二维数组本质上是用指针数组管理多个一维数组它借助数组名即首元素地址的特性把多个一维数组的首地址统一存放在一个指针数组中再通过双重下标完成行列访问。这种方式代码简洁、逻辑清晰是理解指针数组应用场景的经典示例同时也要牢记它与真正二维数组在内存连续性上的差异在实际开发中根据需求选择合适的数据组织方式。
返回列表