1.字符指针:
1.1字符指针的使用:
形式一:
int main() { char ch = 'w'; char* pc = &ch; *pc = 'w'; printf("%c\n", *pc); }
形式二:
int main() { const char* pstr = "hello bit."; printf("%s\n", *pstr); return 0; }
第二种用法的本质是把常量字符串 ’ hello bit. ’ 的首字符 ’ h ’ 的地址放到了pstr中,不是把一个字符串放到pstr指针变量里。
1.2.常量字符串:
注意,常量字符串作为常量,它们不可被修改
int main() { char* p = "abcdef"; //使指针指向常量区的常量字符串abcdef *p = 'A'; //按照我们的理解,*p中存放的是首元素a的地址,我们尝试改变它 //我们运行起来发现无法完成编译运行,程序会直接卡住 //于是我们可以得出,当使用常量字符串时,常量字符串abcdef不可修改 return 0; }
这些常量字符串中的常量字符,是原本就储存在常量区(只读数据区)的一些常量,不同于需要我们输入的字符变量。所以当使用字符指针直接指向常量字符串时,可以直接进行调用但无法对其进行修改。
当出现这种错误时,程序往往是可以正常编译运行但会卡住而得不出结果,会给我们的代码书写造成很大的困扰。为了尽可能的避免这种情况的发生,我们通常在指向常量字符串时,使用 const 对指针变量进行修饰,进行这样的书写操作之后,当我们试图对常量字符串进行改动时,将会直接报错而无法进行编译运行, 便于我们找到问题的所在
1.3.相关面试题:
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; }
这道面试题中定义了两个字符型数组和两个字符型指针变量,将两个字符型数组进行对比,又将两个字符型指针变量尽行了对比,目的是为了验证它们在存储时内存的开辟。
这里 str3 和 str4 指向的是一个同一个常量字符串。C/C++会把常量字符串存储到单独的一个内存区域(在我们的内存中存在着常量区用于存放一些常量),当几个指针指向同一个字符串的时候,他们实际会指向同一块内存。但是用相同的常量字符串去初始化不同的数组的时候就会开辟出不同的内存块。所以str1和str2不同,str3和str4相同
2.指针数组:
指针数组的本质是数组,是用于存放指针变量的数组:
int main() { //创建变量: int a = 1; int b = 2; int c = 3; //创建指针数组,用于存放指针(即变量的地址): int* arr[3] = { &a,&b,&c }; //打印验证内容: int i = 0; for (i = 0; i < 3; i++) { printf("%p\n", arr[i]); } //也可以通过指针数组(存放的是各个变量的地址)来访问变量内容: for (i = 0; i < 3; i++) { printf("%d\n", *(arr[i])); } return 0; }
更常用的却是另外一种情况:
int main() { //创建数组: int arr1[3] = { 1,2,3 }; int arr2[3] = { 4,5,6 }; int arr3[3] = { 7,8,9 }; int* parr[3] = { arr1,arr2,arr3 }; //我们都知道,数组名即为数组内首元素地址 //即此处的数组名为地址,故可以存放在指针变量内 //验证指针数组内存放数据: int i = 0; for (i = 0; i < 3; i++) { printf("整型数组arr%d[3]首元素地址为:%p\n", i + 1, parr[i]); } printf("\n"); //通过指针数组访问整型数组内的数据: for (i = 0; i < 3; i++) { printf("arr%d[3]中存储的数据为:", i + 1); int j = 0; for (j = 0; j < 3; j++) { printf("%d ", *(parr[i] + j)); } printf("\n"); } return 0; }
3.数组指针:
3.1 数组指针的定义:
数组指针的本质是指针。不同于其他指针,数组指针指向的不是地址而是数组
:
int main() { int* p1[10]; //p1是数组,指针数组 int(*p2)[10]; //p2是指针。数组指针 return 0; }
上面的示例中,p2先和*进行结合,说明p2是一个指针变量,再指向大小为10的整型数组。即p2是一个指向数组的指针,叫做数组指针。简单来说,数组指针就是专门用来存放一个数组的地址的指针。
3.2.&数组名VS数组名:
我们都知道,除一些特殊情况外,大多数的数组名都表示数组首元素的地址,那么既然数组名已经是地址了,&+数组名的组合又代表着什么呢?
int main() { int arr[5] = { 0 }; printf("arr :%p\n", arr); printf("&arr:%p\n", &arr); return 0; }
我们如果通过打印,会发现 arr 的打印结果与 &arr 相同:
那它们真的是一回事吗?
我们将上面这段代码进行改写:
int main() { int arr[5] = { 0 }; printf("arr :%p\n", arr); printf("&arr:%p\n", &arr); printf("\n"); printf("arr+1 = %p\n", arr + 1); printf("&arr+1= %p\n", &arr + 1); return 0; }
我们使 arr 与 &arr 同时向后走一步,这时我们看到了不一样的结果:
实际上,&arr 表示的是整个数组的地址,而 arr 仅表示其中首元素的地址,这就导致了我们在使它们在向后走的时候,它们向后走的步幅不同,&arr 跨过了整个数组的长度而 arr 仅仅只跳过了一个数据元素。
3.3.数组指针的使用:
在上面的介绍中,我们看到了数组指针的最基础用法:
int arr[10] = { 0 }; int (*p2)[10] = &arr;
但我们却很少会这样去使用它。更多的是在我们进行函数调用,进行数组传参时,可以通过数组指针来进行接收:
void print_arr1(int arr[3][5], int row, int col) { int i = 0; for (i = 0; i < row; i++) { int j = 0; for (j = 0; j < col; j++) { printf("%02d ", arr[i][j]); } printf("\n"); } } //使用数组指针来进行接收: void print_arr2(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("%02d ", arr[i][j]); } printf("\n"); } } int main() { int arr[3][5] = { 1,2,3,4,5,6,7,8,9,10 }; print_arr1(arr, 3, 5); //数组名arr,表示首元素的地址 //但是二维数组的首元素是二维数组的第一行,所以这里传递的arr,其实相当于第一行的地址,是一维数组的地址 printf("\n"); //可以使用数组指针来进行接收: print_arr2(arr, 3, 5); return 0; }
4.数组参数:
4.1一维数组传参:
我们都知道,在对数组进行传参时并不会真实的在内存中创建临时数组,数组名的意义是作为数组内首元素的地址,因此当函数参数为数组名时,实际上传递的是数组中首元素的地址,于是我们可以发现下面三种传参方式都是可行的:
//方式1:标准传参方式 //完整的传递数组内容 void test1(int arr[3]) { int i = 0; for (i = 0; i < 3; i++) { printf("%d ", arr[i]); } printf("\n"); } //方式2:省略数组大小 //形参部分的数组大小可以省略 void test2(int arr[]) { int i = 0; for (i = 0; i < 3; i++) { printf("%d ", arr[i]); } printf("\n"); } //方式3:扩大形参数组大小 //实际仍为传递首元素地址,故可行,但不建议 void test3(int arr[100]) { int i = 0; for (i = 0; i < 3; i++) { printf("%d ", arr[i]); } printf("\n"); } int main() { int arr[3] = { 1,2,3 }; //调用函数: test1(arr); test2(arr); test3(arr); return 0; }
这三种将形参写作数组形式的方式都是可行的,但是为了避免出现错误,推荐大家尽可能的使用第一种方式,其次是第二种方式。第三种方式虽然也可以运行,但有可能会出现难以预料的错误,不建议使用。
同时,以上三种将形式参数写成数组形式的写法,也可以改写为使用指针做形式参数的形式
//使用指针作为形式参数: //一级指针: void test1(int* p) { int i; for (i = 0; i < 3; i++) { printf("%d ", *(p + i)); } printf("\n"); } //二级指针_形式1: void test2(int** p) { int i; for (i = 0; i < 3; i++) { printf("%d ", *(p + i)); } printf("\n"); } //二级指针_形式2: void test3(int** p) { int i = 0; for (i = 0; i < 3; i++) { printf("%d ", **(p + i)); } } int main() { int arr[3] = { 1,2,3 }; int* parr = &arr; int arrA[3] = { 1,2,3 }; int arrB[3] = { 4,5,6 }; int arrC[3] = { 7,8,9 }; int* arrD[3] = { &arrA,&arrA,&arrC }; //调用函数: test1(arr); test2(parr); test3(arrD); return 0; }
并且同理,使用指针作为形式参数的另外两种形式也是可以(但最后一种方式同样不建议)的:
void test(int* p[3]) { ... } void test(int* p[100]) //可以但不建议使用 { ... }
4.2 二维数组传参:
我们在前面初阶的部分学习二维数组传参时就知道了,二维数组在进行传参时可以不知道有多好行,但必须知道有多少列,这样计算机才知道应该在何时进行换行。如此只要知道了什么时候进行换行,对于行数就不需要再进行强制要求了,所以在数组进行传参时,允许写成以下三种形式:
//二维数组传参: //方式1:标准传参 void test1(int arr[3][3]) { int i = 0; for (i = 0; i < 3; i++) { int j = 0; for (j = 0; j < 3; j++) { printf("arr[%d][%d] = %d ", i, j, arr[i][j]); } printf("\n"); } } //方式2:行数可以省略 void test2(int arr[][3]) { int i = 0; for (i = 0; i < 3; i++) { int j = 0; for (j = 0; j < 3; j++) { printf("arr[%d][%d] = %d ", i, j, arr[i][j]); } printf("\n"); } } //方式3:行数可以超出原数组上限 void test3(int arr[100][3]) { int i = 0; for (i = 0; i < 3; i++) { int j = 0; for (j = 0; j < 3; j++) { printf("arr[%d][%d] = %d ", i, j, arr[i][j]); } printf("\n"); } } int main() { int arr[3][3] = { {1,2,3},{4,5,6},{7,8,9} }; //函数调用: test1(arr); test2(arr); test3(arr); return 0; }
同样的,二维数组除了可以使用数组作为函数的参数以外,也可以使用指针作为函数的参数进行传参,区别于一维数组,二维数组在传参时传递的不是首元素的地址而是首行元素的地址:
void test(int(*p)[3]) //此处的数组大小3为列数 { int i = 0; for (i = 0; i < 3; i++) { int j = 0; for (j = 0; j < 3; j++) { printf("%d ", (*p + i * 3)[j]); //这里注意p+(i*3)是因为i为行号,跳过行时需要跳过i*3个数据元素 } printf("\n"); } } int main() { int arr[3][3] = { {1,2,3},{4,5,6},{7,8,9} }; test(arr); return 0; }
但是要格外注意,二维数组与一维数组不同,不可以使用二级指针进行传参
。其原理是,二级指针的作用是用于存储一级指针的的地址,而传递过来的参数是二维数组第一行(这里可以简单理解为一个一维数组,但本质上不是)的地址,无法使用二级指针进行存储。