1. 字符指针
在指针的类型中我们知道有一种指针类型为字符指针 char* ;
- 一般使用:
int main() { char ch = 'w'; char *pc = &ch; printf("%c\n", ch); // w printf("%c\n", *pc); // w //所以*pc的内容 = ch的内容 = 'w'; return 0; }
- 再如:
int main() { char arr[] = "abcdef"; char* pc = arr; printf("%s\n", arr); //abcdef printf("%s\n", pc); //abcdef return 0; }
还有一种使用方式如下:
#include<stdio.h> int main() { const char* pstr = "hello bit"; //这里是把一个字符串放到pstr指针变量里了吗? printf("%c\n", *pstr); // h printf("%s\n", pstr); // hello bit char* p = "abcdef"; *p = 'w'; // err 编译器报错,最好在开始加上const修饰,这样若再修改*p编译器会直接报警,像上述的const char* pstr = "hello bit";一样 return 0; }
代码 const char* pstr = "hello bit.";
特别容易让大家以为是把字符串 hello bit 放到字符指针 pstr 里了,但是/本质是把字符串 hello bit. 首字符的地址放到了pstr中。
上面代码的意思是把一个常量字符串的首字符 h 的地址存放到指针变量 pstr 中。
- 那就有可这样的面试题:
#include<stdio.h> int main() { char arr1[] = "abcdef"; char arr2[] = "abcdef"; const char* p1 = "abcdef"; const char* p2 = "abcdef"; if (arr1 == arr2) { printf("hehe\n"); } else { printf("haha\n"); } if (p1 == p2) { printf("hehe\n"); } else { printf("haha\n"); } return 0;
- 运行结果如下:
- 出现haha的原因:
这里我们为了存储abcdef创建了arr1和arr2两个数组,那么这个字符串在内存中一定是有两块空间的,arr1和arr2两块不同数组都创建了abcdef,两个不同数组的数组名当然是不同首元素的地址,当这两个地址在不同空间上的时候,那么这两个值自然不一样,所以haha。
出现hehe的原因:
这里两个字符串abcdef一模一样,且都是常量字符串,各自又不能进行修改,没有必要在内存里存两份,为了在内存使用方便,能够节省空间,不管是p1还是p2都指向同一块空间的起始位置的地址,也就是第一个字符的地址。所以p1==p2,所以hehe。
2. 指针数组
- 概念:指针数组是一个存放指针的数组。
#include<stdio.h> int main() { int arr[10] = { 0 }; // 整形数组 char ch[5] = { 0 }; // 字符数组 int* arr1[5]; // 存放整形指针的数组 - 指针数组 char* arr2[5]; // 存放一级字符指针的数组 - 指针数组 char** arr3[5];// 存放二级字符指针的数组 - 指针数组 return 0; }
- 用途:
#include<stdio.h> int main() { int a = 10; int b = 20; int c = 30; int d = 40; int i = 0; int *arr[4] = { &a,&b,&c,&d }; for (i = 0; i < 4; i++) { printf("%d ", *(arr[i])); // 10 20 30 40 } return 0; }
上述代码是只是为了便于理解而写出来的,但真实情况下很少有类似上述代码的,因为有点小low。
正确用法如下:
- 字符指针数组:
#include<stdio.h> int main() { char* arr[] = { "abcdef","qwer","zhangsan" }; int i = 0; int sz = sizeof(arr) / sizeof(arr[0]); for (i = 0; i < sz; i++) { printf("%s\n", arr[i]); } return 0; }
- 画图解释 char* arr[ ] = { "abcdef","qwer","zhangsan" }; 整形指针数组
#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[] = { arr1,arr2,arr3 }; int i = 0; for (i = 0; i < 3; i++) { int j = 0; for (j = 0; j < 5; j++) { printf("%d ", *(parr[i] + j)); } printf("\n"); } return 0; }
- 画图解释 int* parr[ ] = { arr1,arr2,arr3 };3. 数组指针
3.1、 数组指针的定义
数组指针是指针?还是数组?
答案是:指针。
我们已经熟悉:
整形指针: int * pint; 能够指向整形数据的指针。
浮点型指针: float * pf; 能够指向浮点型数据的指针。
那数组指针应该是:能够指向数组的指针。
下面代码哪个是数组指针?
int *p1[10]; int (*p2)[10]; //p1, p2分别是什么?
- 解释:
int *p1[10]; // 指针数组 int (*p2)[10];// 数组指针 //解释p1 //p1先和[]结合,说明p1是个数组,数组有10个元素且每个元素类型是int*,所以p1是指针数组 //解释:p2 //p2先和*结合,说明p2是一个指针变量,然后指着指向的是一个大小为10个整型的数组。所以p2是一个指针,指向一个数组,叫数组指针。 //这里要注意:[]的优先级要高于*号的,所以必须加上()来保证p2先和*结合。以此确保p2是数组指针
3.2、 &数组名VS数组名
- 对于下面的数组:
int arr[10];
arr 和 &arr 分别是啥?
我们知道arr是数组名,数组名表示数组首元素的地址。
那&arr数组名到底是啥?
- 我们看一段代码:
#include <stdio.h> int main() { int arr[10] = { 0 }; printf(" arr= %p\n", arr); printf("&(arr[0])=%p\n", &(arr[0])); printf(" &arr= %p\n", &arr); return 0; }
- 运行结果如下:可见数组名和&数组名打印的地址是一样的
难道两个是一样的吗?
- 我们再看一段代码:
#include <stdio.h> int main() { int arr[10] = { 0 }; printf(" arr= %p\n", arr); printf(" arr+1= %p\n", arr + 1); printf("\n"); printf(" &(arr[0])= %p\n", &(arr[0])); printf("&(arr[0])+1= %p\n", &(arr[0]) + 1); printf("\n"); printf(" &arr= %p\n", &arr); printf(" &arr + 1= %p\n", &arr + 1); return 0; }
- 运行如下:根据上面的代码我们发现,我们都知道数组名arr就是首元素地址,所以arr和&arr[0]是一样的,所以后续即使分别+1得到的地址也是对应相等的。其实&arr和arr,虽然值是一样的,但是意义应该不一样的。
实际上: &arr 表示的是数组的地址,而不是数组首元素的地址。(细细体会一下)
本例中 &arr 的类型是: int(*)[10] ,是一种数组指针类型
数组的地址+1,跳过整个数组的大小,所以 &arr+1 相对于 &arr 的差值是40.
再来一组代码:
int arr[10]; int* p = arr; int(*p2)[10] = &arr; //取出的是数组的地址,既然是数组的地址,就应该放到数组指针变量中 //p2就是一个数组指针
p存放的是第一个元素的地址,p+1跳过1个int,*p访问一个int数据4个字节
p2存放的是arr数组的地址,p2+1跳过整个数组
*p2访问到的是arr数组,*p2等价于arr,那么*p2就是数组名,数组名又相当于首元素的地址,所以*p2本质上是arr数组第一个元素的地址。
即p<--->*p2<---->arr
p+1<--->*p2+1
3.3、 数组指针的使用
既然数组指针指向的是数组,那数组指针中存放的应该是数组的地址。 看代码:
#include <stdio.h> int main() { int arr[10] = {1,2,3,4,5,6,7,8,9,0}; int (*p)[10] = &arr;//把数组arr的地址赋值给数组指针变量p //但是我们一般很少这样写代码,我们先用数组指针打印出10个元素看看 int i = 0; for (i = 0; i < 10; i++) { printf("%d ", *((*p) + i)); // 1 2 3 4 5 6 7 8 9 10 } return 0; }
char* arr[5]; char* (*pa)[5] = &arr; //pa是指针变量的名字,(*pa)中*说明pa是指针,[5]说明pa指向的数组是5个元素,char*说明pa指向的数组的元素类型是char*
- 上述用数组指针打印一维数组的方法多少有些多此一举,明明可以用for循环打印,却偏偏用数组指针,无疑是脱裤子放屁!!!(狗头保命)
- 数组指针至少用到二维数组以上才方便些,如下:
#include<stdio.h> // 参数是数组的形式 void print1(int arr[3][5], int x, int y) { int i = 0; int j = 0; for (i = 0; i < x; i++) { for (j = 0; j < y; j++) { printf("%d ", arr[i][j]); } printf("\n"); } } // 参数是指针的形式 void print2(int(*p)[5], int x, int y) { int i = 0; for (i = 0; i < x; i++) { int j = 0; for (j = 0; j < y; j++) { printf("%d ", *(*(p + i) + j)); //printf("%d ", p[i][j]); //printf("%d ", *(p[i] + j)); //printf("%d ", (*(p + i))[j]); } printf("\n"); } } int main() { int arr[3][5] = { {1,2,3,4,5},{2,3,4,5,6},{3,4,5,6,7} }; print1(arr, 3, 5); // arr - 数组名 - 数组名就是首元素地址 //数组名arr,表示首元素的地址 //但是二维数组的首元素是二维数组的第一行 //所以这里传递的arr,其实相当于第一行的地址,是一维数组的地址 //可以数组指针来接收 print2(arr, 3, 5); return 0; }
注意:
print2函数形参那用指针的过程:
首先,print2函数实参的数组名是首元素第一行的地址,第一行是个一维数组,那么传给形参的就是一维数组的地址,所以应该放到数组指针里头去。该指针指向的是个数组,数组有5个元素,每个元素是int。所以就是void print2(int(*p)[5], int x, int y)
print2函数4个不同打印输出的方式等价:
当要打印数组的内容时,需要遍历数组,先创建变量i,用for循环。p是指向一行的,p+i是表示跳过i行,此时就表示下标为i的这一行,此时再解引用*(p+i)就找到了这一行,就相当于拿到了这一行的数组名,然后再创建变量j,同样for循环,我们找到了这一行,需要再找到这一行的某个元素,此时再加上j,就找到了下标为j的元素地址*(p + i) + j,然后括号再括起来,再*解引用,就找到了i行j列的元素。*(*(p + i) + j),而下面三种的打印方式与刚才这种是等效的,就像下述代码一样。
int arr[10] = { 1,2,3,4,5,6,7,8,9,10 }; int* p = arr; int i = 0; for (i = 0; i < 10; i++) { printf("%d ", *(p + i)); printf("%d ", *(arr + i)); printf("%d ", arr[i]); printf("%d ", p[i]); // arr[i] == *(arr + i) == *(p + i) == p[i] 等价 }
- 学了指针数组和数组指针我们来一起回顾并看看下面代码的意思:
int arr[5]; // arr是一个5个元素的整形数组 int* parr1[10];// parr1是一个数组,数组有10个元素,每个元素的类型都是int*,parr1是指针数组 int(*parr2)[10];//parr2是一个指针,该指针指向了一个数组,数组有10个元素,每个元素的类型都是int,parr2是数组指针 int(*parr3[10])[5];//parr3是一个数组,该数组的有10个元素,每个元素是一个数组指针,该数组指针指向的数组有5个元素,每个元素是int
4. 数组参数、指针参数
4.1、 一维数组传参
#include <stdio.h> void test(int arr[])//ok? 可 {} void test(int arr[10])//ok? 可 {} void test(int* arr)//ok? 可 {} void test2(int* arr[20])//ok?可 {} void test2(int** arr)//ok? 可 {} int main() { int arr[10] = { 0 }; int* arr2[20] = { 0 }; test(arr); test2(arr2); }
4.2、 二维数组传参
#include<stdio.h> void test(int arr[3][5])//ok? 可 {} void test(int arr[][5])//ok? 可 {} void test(int arr[5][])//ok? 不可 {} void test(int arr[][])//ok? 不可 {} //总结:二维数组传参,函数形参的设计只能省略第一个[]的数字。行可以省略,但列不可省略 //因为对一个二维数组,可以不知道有多少行,但是必须知道一行多少元素。 //这样才方便运算。 int main() { int arr[3][5] = { 0 }; test(arr); // 二维数组传参 return 0; }
- 有指针:
#include<stdio.h> void test(int* arr)//ok? 不可 arr是个二维数组,二维数组的数组名表示第一行的地址,第一行是个一维数组,而int* arr是存放整形地址的 {} void test(int** arr)//ok? 不可 数组名是首元素的地址,是第一行的地址,是数组的地址,数组的地址不能存到二级指针里,二级指针是存放一级指针变量的地址 {} void test(int* arr[5])//ok? 不可 {} void test(int(*arr)[5])//ok? 可 第一行的地址是5个整形的指针 {} int main() { int arr[3][5] = { 0 }; test(arr); // 二维数组传参 return 0; }
4.3、 一级指针传参
#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; }
- 当一个函数的参数部分为一级指针的时候,函数能接收什么参数?
#include<stdio.h> void test1(int* p) {} void test2(char* p) {} int main() { int a = 10; int* p1 = &a; test1(&a); //可 test1(p1); //可 char ch = 'w'; char* pc = &ch; test2(&ch);//可 test2(pc); //可 return 0; }
4.4、 二级指针传参
#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; }
- 当函数的参数为二级指针的时候,可以接收什么参数?
void test(char** p) {} int main() { char c = 'b'; char* pc = &c; char** ppc = &pc; char* arr[10]; test(&pc); // 可 test(ppc); // 可 test(arr);//Ok? 可 return 0; }