前言
通过前面学习初识指针,我们知道指针变量是用来存放地址的,了解二级指针变量是用来存放一级指针变量的地址的,野指针的成因等,下面我们将继续深入的学习指针。
一 字符指针
在指针的类型中我们知道有一种指针类型为字符指针 char*
#include<stdio.h> int main() { char* p = "abcdefg"; return 0; }
对于这段代码,我相信会有不少人会问,指针变量不是存放的是地址吗?怎么字符指针就可以存放一个字符串。其实char*p里面存放的是字符串的首地址(a字符的地址)。
下面我们来看道面试题
#include <stdio.h> int main() { char str1[] = "hello bit."; char str2[] = "hello bit."; const char* str3 = "hello bit."; const char* str4 = "hello bit."; if (str1 == str2) printf("str1 and str2 are same\n"); else printf("str1 and str2 are not same\n"); if (str3 == str4) printf("str3 and str4 are same\n"); else printf("str3 and str4 are not same\n"); return 0; }
从这道面试题可以看出,不同数组存放相同的元素,元素首地址是不同的。
字符指针存放相同元素,首地址是相同的,这种字符串我们又称为常量字符串。
二指针数组
指针数组故名思意本质是数组,是一个存放指针的数组。
#define _CRT_SECURE_NO_WARNINGS #include<stdio.h> int main() { int arr1[] = { 1,2,3,4,5,6 }; int arr2[] = { 2,4,5,6,8,7 }; int arr3[] = { 3,4,5,6,8,9 }; int* ppr[3] = { arr1,arr2,arr3 }; return 0; }
其中的ppr便是指针数组。
三&数组和数组名
arr 和 &arr 分别是啥? 我们知道arr是数组名,数组名表示数组首元素的地址。 那&arr数组名到底是啥
可见数组名和&数组名打印的地址是一样的。 难道两个是一样的吗?
我们在看到下面这段代码
根据上面的代码我们发现,其实&arr和arr,虽然值是一样的,但是意义是不一样的。 实际上: &arr 表示的是数组的地址,而不是数组首元素的地址。(细细体会一下) 本例中 &arr 的类型是: int(*)[10] ,是一种数组指针类型 数组的地址+1,跳过整个数组的大小,所以 &arr+1 相对于 &arr 的差值是40。
数组名是首元素地址是通常来说。
但存在二个特例:
1sizeof(数组名)这里的数组名表示整个数组,计算的是整个数组的大小。
2&数组名,这里的数组名表示的依然是整个数组。
上面我们提到数组指针,大家可能不怎么了解,下面我们将会从什么是数组指针,数组指针怎么用来说明。
四 数组指针
数组指针的本质指针,数组指针是能够指向数组的指针。
下面我们可以看下面一段代码
#include<stdio.h> int main() { int* p1[10]; int(*p2)[10]; return 0; }
对于p1来是由于[]的优先级大于*所以p1先和[]结合,所以p1是一个数组。
对于p2来说*p2被()所以*和p2先结合所以p2是指针。
我们知道了数组指针是指针,他是指向数组的指针。那么我们到底又改如何使用他呢?
我们又知道二维数组的首元素是他的第一行,也就是说是第一行的地址,那么我们就可以用数组指针来接收。代码如下:
#include<stdio.h> void print(int(*arr)[5], int row, int col) { int i = 0; for (i = 0; i < row; i++) { int j = 0; for (j = 0; j < col; j++) { printf("%d ", arr[i][j]);//打印每行的元素 } printf("\n"); } } int main() { int arr[3][5] = { 1,2,3,4,5,6,7,8,9,10,11,12,13,14,15 }; print(arr, 3, 5); //arr传过去的是第行的地址 return 0; }
所以说对与数组指针可以用来接收二维数组第一行的地址。
五 数组参数、指针参数
我们在写代码的时候都会将数组或者指针的参数传给函数,那么函数的参数又将如何去设计呢?
我们可以看到下面这段代码:
一维数组的传参
#include <stdio.h> void test(int arr[])//方式1 {} void test(int arr[10])//方式2 {} void test(int* arr)//方式3 {} void test2(int* arr[20])//方式1 {} void test2(int** arr)//方式2 {} int main() { int arr[10] = { 0 }; int* arr2[20] = { 0 }; test(arr); test2(arr2); }
对于test1()函数来说,我们很容易理解方式1和方式2的书写,但对于方式3,由于我们知道arr是首元素的地址,我们用指针来接收,这样也是很好理解的。
对于test2()函数来说,是一个指针数组,方式1用数组指针的方式来接收也是没有问题的,但对于方式2同样也是可以的,那我们又将要如何去理解呢?方式2是用了二级指针方式来接收,二级指针又是存放一级指针的地址的,test2函数传了arr2过来(数组首元素的地址),这个数组有点特殊,他的每个元素是int*(相当于一个一级指针),所以说用二级指针来接收参数arr2是可以的。
我们知道了一维数组的传参可以用数组的形式接收,也可以用指针的形式接收,那么二维数组的传参又可以用什么来接收呢?
二维数组传参
void test(int arr[3][5])//方式1 {} void test(int arr[][])//方式2 {} void test(int arr[][5])//方式3 {} //总结:二维数组传参,函数形参的设计只能省略第一个[]的数字。 //因为对一个二维数组,可以不知道有多少行,但是必须知道一行多少元素。 //这样才方便运算。 void test(int* arr)//方式4 {} void test(int* arr[5])//方式5 {} void test(int(*arr)[5])//方式6 {} void test(int** arr)//方式7 {} int main() { int arr[3][5] = { 0 }; test(arr); }
对于上面这个二维数组来说,只有方式1,方式3,方式6参数设计是可以行的,对test的函数我传了arr(第1行的地址),方式1和方式3都是用数组的方式设计参数(只要组列必须写出来就可以了),对于方式6来说,是数组指针的方式接收(前面已经说过)。
一级指针传参
对于一级指针传参,我们传个指针给函数,函数就要用指针去接收,如下面代码:
#include <stdio.h> void print(int *p, int sz) { int i = 0; for(i=0; i<sz; i++) { printf("%d\n", *(p+i)); } } int main() { int arr[10] = {1,2,3,4,5,6,7,8,9}; int *p = arr; int sz = sizeof(arr)/sizeof(arr[0]); //一级指针p,传给函数 print(p, sz); return 0; }
那么我们又如何去用指针去实现我们想要的功能呢?我们可以解引用指针便能找到我们想要的元素,通过加减指针指向另个元素。
下面我们可以继续思考一个问题当一个函数的参数部分为一级指针的时候,函数能接收什么参数?
void test1(int *p) {} //test1函数能接收什么参数?
其中test1可以接收指针,数组首元素的地址。
二级指针传参
二级指针的传参有上面区别吗?其实我用一级指针来传参就要用一级指针来接收,那么我用二级指针来传参也就要用二级指针来接收。
#include <stdio.h> void test(int** ptr) { printf("num = %d\n", **ptr); } int main() { int n = 10; int* p = &n; int** pp = &p; test(pp); test(&p); return 0; }
那么当函数的参数为二级指针的时候,可以接收什么参数?
我们可以传二级指针,要可以传指针数组。
总结
在这里我们知道了什么是字符指针,指针数组,数组指针,我们还了解一维和二维数组和指针是怎么传参的,在下篇博客我会为大家继续分享指针进阶的知识。