1 字符指针
字符指针就是指向字符的指针,指针的类型为 char* ;
来看看一种用法:指向一个字符
int main()
{
char ch = 'a';
char *pc = &ch;
return 0;
}
分析一下:pc是一个指针,指向类型为char的变量 char;
还有一种用法是用字符指针,指向字符串常量。
int main()
{
char* pstr = "hello world.";
printf("%s\n", pstr);
return 0;
}
上面有个问题,是否将“ hello world.” 放入指针变量 pstr 中?
答:不是,即使你想放也放不下,pstr 是指针,32为机器是四个字节,而 hello world. 这个字符串早就超过四个字节啦。 其实是把 hello world. 的字符串首地址放入到指针变量 pstr 中,然后指针变量pstr 就可以找到该字符串的首地址,打印时候,就会顺着首地址一直打印,直到结束。
但是上面的指针写法并不完美,我们知道“hello world.” 是常量字符串,常量字符串存放在常量区,这个常量字符串是不可以修改的,一旦你通过*pstr 去修改字符串的字符时候,编译器就会报错。 调试看看如下:
所以说,我们有一种写法就是
char* pstr = "hello world.";
修改为:
const char* pstr = "hello world.";
加个const 假如你要修改,就可以在编译阶段报错,不至于运行时候报错了。
来看看一道面试题:帮助你理解字符指针
int main()
{
char str1[] = "hello world.";
char str2[] = "hello world.";
char *str3 = "hello world.";
char *str4 = "hello world.";
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 指向同一块内存,所以 str3 == str4;才会成立; 而 数组名 str1 和 str2 指向不同的内存,虽然他们的值相同,但是地址空间不同,所以 str1 和 str2不一样。 原因很简单:使用字符指针 会指向字符串所在常量区的地址,而使用数组名,只会将字符串拷贝一份给数组去存储。很明显拷贝时候要开辟栈空间,很明显对数组名来说地址是不一样滴。
2 指针数组和数组指针
很多人都搞混这两个名词,其实有个记忆方式 指针数组:数组在后面,所以说指针数组是个数组,数组里存放的是指针; 数组指针:指针在后面,所以说数组指针是个指针,指向数组的指针;
1)指针数组
指针数组
int a = 10;
int b = 20;
int c = 30;
int *arr[3] = {&a,&b,&c};
指针数组就是数组,数组里面的元素是指针,即地址;
那我要访问 arr数组的元素就是 arr[ i ];比如得到arr[0]就是 &a;也就是a 的地址,我想得到 a的值,则需要解引用*arr[i];比如:*arr[0] ,这就是a 的值; 所以,有一种打印方式,得到指针数组里边变量地址的值
int main()
{
int a = 10;
int b = 20;
int c = 30;
int *arr[3] = { &a, &b, &c };
for (int i = 0; i < 3; i++)
{
printf("%d ", *(arr[i]));
}
return 0;
}
还有另一种用法,look:
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 };
return 0;
}
分析一下:*这个parr;首先parr是一个数组,该数组的每个元素是 int,每个元素是其他数组的首地址;**画出内存布局图看看,大概如下。
假如我想通过数组指针 parr访问 里面数组的值,该如何访问? 首先,我们直到解引用指针,得到该指针指向的值,所以*parr== arr1 ; 但是 arr1 还是地址,我们再解引用 即 *(*parr) == arr1[0] ;得到了arr1[0] 的元素,那如何获得 arr1[1]的元素呢?我们可以通过* (arr1 +1)得到 arr[1] ,那么也就是说,还可以通过 *(*parr+ 1)得到 arr1[1] ; 以此类推就有:
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 };
for (int i = 0; i < 3; i++)
{
for (int j = 0; j < 5; j++)
{
printf("%d ", *(*(parr + i) + j));
}
printf("\n");
}
return 0;
}
这就类似二维数组了。
2)数组指针
数组指针,就是指针,指向的是数组; 数组指针的初始化:
int arr[3] = {0};
int (*p)[3] = &arr;
这是怎么定义出来的呢?或者说怎么书写的? 我上面(指针第一篇文章中有概述)是不是说过一种方法呀: 我是这么想的:
- 首先你要书写数组指针,就要知道*等号左边需要写 p,即
*p = 的模样; - 其次,你数组的类型(去掉数组名剩下的就是数组的类型): int [3];
- 然后在
*p = ;星号的左边补上 int[3]; 即 int [3] *p = ; - 最后,在等号右边写上 &arr,即
int [3] *p = &arr ; - 最后的最后还没完,需要调整位置 即把
int [3] *p = &arr 调整为 int (*p)[3] = &arr ; - 这就是书写数组指针的过程。
当我们阅读这句代码,即 int (*p)[3] = &arr 时候,应该这么阅读: 首先 p是一个指针,指针指向数组,数组有3个元素,每个元素的类型为int 类型
举个例子:请写出指向该数组的数组指针 char* arr[5]; 分析:首先数组指针是个指针,所以等号左边= 先写 (*p) ,其次,看数组的类型,是 char* [5] ,等号=的左边 再写上 char*(*p)[5] ;最后书写格式:char*(*p)[5] = &arr ;
3)数组指针的常见用法
- 和二维数组一起用;
普通二维数组的遍历
# include<stdio.h>
int main()
{
int arr[3][4] = { { 1, 2, 3, 4 }, { 2, 3, 4, 5 }, { 3, 4, 5, 6 } };
for (int i = 0; i < 3; i++)
{
for (int j = 0; j < 4; j++)
{
printf("%d ", arr[i][j]);
}
printf("\n");
}
return 0;
}
结果: 用数组指针去遍历二维数组
void print(int (*p)[4], int x, int y)
{
for (int i = 0; i < x; i++)
{
for (int j = 0; j < y; j++)
{
printf("%d ", *(*(p + i) + j) );
}
printf("\n");
}
}
# include<stdio.h>
int main()
{
int arr[3][4] = { { 1, 2, 3, 4 }, { 2, 3, 4, 5 }, { 3, 4, 5, 6 } };
print(arr, 3, 4);
return 0;
}
首先我们知道数组名就是首元素的地址,在二维数组中,我们把它看成为一维数组;怎么理解呢?
那么arr 表示 &arr[0][0] ,那么arr + 1;表示 arr[1][0] 的地址,那么*(arr + 1) 表示 arr[1][0] 的元素值,那么*(*(arr + 1)+1) 表示 arr[1][1] 的元素的值 那么数组指针,指向数组的指针,int (*p)[4] = arr ; 就可以这么理解:*表示p是个数组指针 ,p指向的数组有4个元素 ,每个元素的类型为 int ;
3 指针和数组结合的复杂类型的阅读例题
int arr[5];
分析:arr是一个数组,数组有5个元素,每个元素的类型为int;
可以这么说:arr是一个包含5个int类型的数组。
int *parr1[10];
分析:parr1是一个数组,数组有10个元素,每个元素的类型为 int*;
可以这么说:parr是一个包含10个int*类型元素的数组。
int (*parr2)[10];
分析:parr2是一个指针,指向了一个数组,该数组有10个元素,每个元素的类型为int;
可以这么说: parr2指向了含有10个int类型元素的数组,parr2是数组指针。
int (*parr3[10])[5];
分析:parr3是一个数组,该数组有10个元素,每个元素的类型为 int(*)[5],即每个元素的类型为数组指针,该数组指针指向了含有5个int类型元素的数组。
可以这么说:parr3是一个包含了 10个数组指针类型的数组,该数组指针指向的数组含有5个int类型的数组。
4 关于指针传参,数组传参的设计思考方式
许多朋友,从设计者的角度来说不知道怎么设计一个函数,参数类型是 一级指针,还是二级指针,不知道一级指针可以接受什么参数,二级指针可以接受什么参数。 从调用者的角度,当你传参的时候,也不知道传的参数,是否能改变外界实参,也不知道,传地址过去还是传变量过去。
这里我将带你扫一扫这个问题的疑惑,看看能不能帮助你们解决这个问题。
首先我们得有个认知,当我是调用者时候,即我要调用函数时候,传入实参,其实就是给形参初始化,我打个比方,看下面函数。
int Add (int x, int y)
{
return x+y;
}
int main()
{
int a = 10;
int b = 10;
int ret = 0;
ret = Add(a,b);
return 0;
}
_______________________________________________________
Add(a,b);对于这个函数调用,当你在传实参时候,就相当于给形参初始化,
从形参得角度来看:就是这种效果:
int x = a,int y = b;对于形参来说,等价于这种方式。
_______________________________________________________
为什么说这个呢?因为,这有帮助我们在设计实参,形参时候有理解帮助,还记得我们在平时普通初始化一个变量是怎么初始化得嘛?是不是也是通过 = 等号初始化一个变量。 那这么说, = 等号两边得类型是需要相同的,那也就是说,当我看 = 等号,左边时候,也就是从函数设计者的角度区思考,这个类型必须是需要与 等号 = 右边的类型相同,那么等号 = 右边的类型从哪里来呢?就是从我们的调用者角度去看,就在调用函数里面的实参,可以得到实参类型。
这个思想很重要,你需要有这个思想才可以知道,函数形参,实参到底如何设计合理。
1)从设计的函数角度,思考形参的设计
所以说,当我是一个函数设计人员,当我在设计形参 为 int* x 时候,我是在想什么?
我是想,我这个形参到底能够接受什么参数?
1. 接收比形参 x 低一个等级级变量的地址,即 int a,
a是int类型,比x 的类型 int*低一个等级,接受后相当于:int* x = &a;
你发现等号 = 左右两边的变量类型是一致的。
2. 接收同等级的变量,即 int* a;
a 是int* 类型,和 x 的类型 int* 同等级,接受后相当于: int* x = a;
你发现等号 = 左右两边的变量类型是一致的。
3. 接收一位数组名,由于一位数组名本质是首元素地址,对于 int arr[2] = {1,2};
一位数组名的类型为int*,接收后相当于 int * x = arr;
你发现等号=左右两边的变量类型相等。
其次我又想我到底需不需要改变外界传入的实参呢?
假如需要改变外部传入的实参,那么我需要 接收的是比形参 x 的类型低一个等级变量的地址;
如 int a = 10; 传入 &a;
假如我不需要改变外部传入的实参,那么我就接收一个和形参 x 的类型一致的变量;
如: int *p = &a; 传入 p;
这里有个重点:无论你是什么类型的变量,如一级指针变量还是二级指针变量还是多级指针变量,只要你传的不是该指针变量的地址,就无法改变该指针的内容;无论你是什么类型的变量,只要你传入一个地址,前提这个变量是需要比形参低一个等级,那么你就可以修改该变量的内容。
2)从调用者角度思考实参形参设计
自然而然的假如你是调用者,你要是传变量的地址,那么就需要设计函数为比变量高一个等级(就是多一个*星号)的形参类型,你要是传变量,那么设计函数就需要和实参同类型的形参类型; 那你是想不想通过形参修改实参的数据呢?想的话,你就传实参的地址,不想的话,你就传实参的变量。
假如我想设计一个交换数的函数,首先你就得清楚,你需不需要修改实参,需要就传地址,不需要就传变量。
很明显,交换数的函数,需要修改实参。所以传实参时候,你就传地址。
如:
int a = 10;
int b = 20;
swap(&a,&b);
形参设计时候,由于需要修改实参,所以用一个比实参高一级的形参变量去接收实参,
int swap(int*x,int*y);这个时候 形参 x 和 y 是int*类型,都是 比实参 a和b 都是 int 类型,高一个等级类型的。
假如你不需要修改外部实参,那么你就不需要传地址,
就传同等级的变量过去,无论你的实参变量是否为指针,形参接收时候都是相同的类型。
3)一些参数传递的练习
问:对于main函数中的四条语句是否传参成功。
#include <stdio.h>
void test(int arr[])
{}
void test(int arr[10])
{}
void test(int *arr)
{}
void test2(int *arr[20])
{}
void test2(int **arr)
{}
int main()
{
int arr[10] = {0};
int *arr2[20] = {0};
test(arr);
test2(arr2);
答:对于 test(arr); 从调用者的角度思考,这是一个数组名,数组首元素地址&a[0],该类型为 int* ,既然传的是地址,那么说明,调用者希望在函数里面是可以修改实参的。 并且由于 需要实参和形参的类型对应,那么我的设计函数的形参,就必须类型为 int*或者int [ ](对于数组来说,这是一种特别的形参);
所以 前面4个函数都是可以传参成功,唯独第五个函数void test2(int **arr){ } ,不行,因为 它的形参类型为 int ,从设计函数的角度来说,它希望接收的参数**
- 接收比形参 arr 低一个等级级变量的地址,即 int* arr,
- 接收一个同等级的实参 即,int** arr;
- 接收一个二维数组的数组名,int arr[][3] = {{0},{0},{0};
至于要不要改变实参,还是那句话,看你传入的是否为 取地址变量,还是同等级的变量。
对于 test2(arr2),从调用者的角度思考,数组名是首元素的地址,该首元素地址的类型 为 int**,所以设计时候,对于上面的函数,只有第五个函数可以接收这个参数啦。
这次分享到这里,下一篇我们继续指针,讲的是函数指针,指针函数,指向函数指针的数组,回调函数等。
|