指针进阶
- 一、字符指针
- 二、指针数组
- 三、数组指针
- 3.1:数组指针的使用
- 四、数组参数、指针参数
- 4.1:一维数组传参
- 4.2:二维数组传参
- 4.3:一级指针传参:
- 4.4:二级指针传参:
- 五:函数指针
一、字符指针
一般使用:
int main()
{
char ch = 'w';
char* pc = &ch;
return 0;
}
进阶使用:
int main()
{
const char* p = "abcd";//字符串常量的值是首字符的地址,
//由于p指向的是常量字符串,所以*p解引用的时候不能改变这个字符串,所以前面用const来修饰
printf("%s\n", p);
//打印字符串只需要知道字符串首字符的地址,然后就会一直往后打印直到遇到'\0'为止
//如果这里没有const修饰,通过解引用修改字符串常量程序会崩溃
/*char* p = "abcd";
*p='wcvb';
printf("%s\n", p);*/
//这三段代码执行后,虽然程序不会报错,但是会崩溃
//加了const修饰后,再通过解引p用去改变字符串常量程序就会报错
//*p = "dvcb";此时程序报错
return 0;
}
经典例题:
//一个例题:
int main()
{
char str1[] = "hello word.";
char str2[] = "hello word.";
//这两段代码执行后会在内存中创建两个数组,这两个数组有各自的空间
const char* str3 = "hello word.";//字符常量一般是放在内存中的只读常量区
const char* str4 = "hello word.";
//"hello word."是一个常量字符串,永远不可能被修改掉,所以就没有必要创建两份,大家都共用这一份
//str3 和 str4里面存的都是 'h'的地址
if (str1 == str2)//数组名表示的是首元素的地址,由于两个数组开辟了各自的空间,所以它们首元素的地址一定不相同
printf("str1 and str2 are same\n");
else
printf("str1 and str2 are not same\n");
if (str3 == str4)//str3 和 str4里面存的都是 'h'的地址,所以它们两个的值是相等的
printf("str3 and str4 are same\n");
else
printf("str3 and str4 are not same\n");
//因为str3 和 str4里面存的都是 'h'的地址,我们可以把这个地址打印出来
//结果显示这两个地址是相同的
printf("%p\n", str3);
printf("%p\n", str4);
return 0;
}
//结果:
str1 and str2 are not same
str3 and str4 are same
00C27BD8
00C27BD8
二、指针数组
关于指针数组到底是指针还是数组?交给大家一个简单的方法:看后缀!这里的后缀是数组,就可以十分确定,指针数组其实就是数字。这里的指针我们可以看成是形容词,用来修饰数组的,就像整型数组:表示这个数组是用来存放整形数据的,字符型数组:表示这个数组是用来存放字符型数据的。同理:指针数组就表示这个数组是用来存放指针(地址)的。
字符型指针数组:
//指针数组
int main()
{
const char* arr[4] = {"abcd","bcde","nmjh","lkju"};
//这就是字符型指针数组,这有一个叫arr的数组,它可以存放4个元素,每个元素都是 字符指针(存放的就是字符的地址)
//数组中的4个元素都存的是字符串常量的首字符地址,由于指向的是字符串常量,不可被修改,所以加上const修饰
//打印出首元素的地址,发现字符串常量在内存中不是连续存放的
/*printf("%p\n", "abcd");
printf("%p\n", "bcde");
printf("%p\n", "nmjk");
printf("%p\n", "lkju");
printf("\n");*/
int i = 0;
for (i = 0; i < 4; i++)
{
printf("%s\n", arr[i]);//调用数组中的元素
}
return 0;
}
int* arr1[10]; //整形指针的数组
char *arr2[4]; //一级字符指针的数组
char **arr3[5];//二级字符指针的数组
整型指针数组:
//整型指针数组
//这里有一个数组,里面存的都是整型指针(整形的地址)
int main()
{
int arr1[5] = { 1,2,3,4,5 };
int arr2[5] = { 6,5,4,3,2 };
int arr3[5] = { 10,9,1,2,5 };
//创建三个整型指针
int* arr[3] = { arr1,arr2,arr3 };//数组名表示首元素地址
//这里有一个数组arr,它能够存储三个元素,每一个元素的类型都是整型指针(int*)(整形的地址)
//arr1 - 1的地址
//arr2 - 6的地址
//arr3 - 10的地址
int i = 0;
for (i = 0; i < 3; i++)
{
int j = 0;
for (j = 0; j < 5; j++)
{
//printf("%d ", arr[i][j]);
printf("%d ", *(arr[i] + j));//arr[i]拿到的是数组首元素的地址,那再加j,就是跳过j个整型,就可以访问数组中的其他元素了。
}
printf("\n");
}
return 0;
}
三、数组指针
数组指针的后缀是指针,那就说明数组指针本质上是指针,比如整型指针(int*):表示这个指针变量里面存放的是一个整型变量的地址,它指向一个整型变量。字符指针(char*):表示这个指针变量里面存放的是一个字符型变量的地址,它指向一个字符变量。同理:那数组指针一定是一个指向数组的指针,换言之,这个指针变量里面存放的是数组的地址。
//数组指针
int main()
{
char ch = 'w';
char* pc = &ch;//pc是字符指针,类型是:char*
//这里的*交代pc是一个指针变量,char表示它指向指针
int num = 10;
int* pi = #//pi是整型指针,类型是int*
//这里的*交代pi是一个指针变量,int表示它指向指针
int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };//arr的类型是int [10],如果有一个指针变量指向它,那指针变量的类型应该也是这样
int(*pa)[10]=&arr;//&arr取到的是整个数组的地址,pa是数组指针,类型是:int(*)[10]
//这里的*交代pa是一个指针变量,int [10]表示它指向一个类型为int [10]的数组
//int(*pa)[10] = arr;//这种写法是错误的,arr是数组首元素的地址,也就是1的地址,1是int型,int型的地址需要用int*来接收才对
return 0;
}
3.1:数组指针的使用
在一维数组中的使用:
//数组指针的使用
int main()
{
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));//(p + i)就是偏移过i个元素,第i个元素的地址,然后解引用
// printf("%d ", p[i]);//通过首元素地址+下标的方式来打印
//}
//利用数组指针来打印
int(*p)[10] = &arr;//拿到整个数组的地址
printf("%p\n", p);
printf("%p\n", (*p));
printf("%p\n", &arr[0]);
//三者打印出来的数据完全相同,都是首元素的地址
printf("\n");
printf("%p\n", p+1);//p+1跳过了整个数组,也就是40个字节
printf("%p\n", (*p)+1);//(*p)+1只跳过了首元素,也就是4个字节
printf("%p\n", &arr[0]+1);//&arr[0]+1也只跳过了首元素,也就是4个字节
//这说明对数组指针解引用得到的是该指针所指向数组的首元素的地址
//这一点就和以前有所不同
//整型指针解引用得到的是一个整型变量
//字符指针解引用得到的是一个字符变量
//这里的数组指针解引用得到的是一个地址
//有了上面的分析,我们就可以利用数组指针来打印数组了
int i = 0;
for (i = 0; i < 10; i++)
{
printf("%d ", *(*p + i));//*p得到的是首元素的地址,+i表示第i个元素的地址,然后再解引用
printf("%d ", (*p)[i]);//既然(*p)得到的是数组首元素的地址,那就还可以通过:数组首元素地址+下标 的方式去访问数组中的每一个元素
}
return 0;
}
通过上面的代码不难发现:数组指针解引用得到的该指针所指向数组的首元素的地址,这一点和其他类型的指针有所不同。通过:*p = *(&arr) = arr
也不难发现,*p最终的结果是数组名,数组名又表示首元素地址,所以数组指针解引用得到的是首元素地址
在二维数组中的使用:
这里主要得知道:二维数组的数组名也表示首元素地址,二维数组arr
的首元素是二维数组中第一行那个一维数组arr[0]
,所以二维数组名就表示&arr[0]
,得到一个数组的地址
//数组指针在二维数组中的使用
void print1(int arr[3][4], int r, int l)
{
int i = 0;
for (i = 0; i < r; i++)
{
int j = 0;
for (j = 0; j < l; j++)
{
printf("%d ", arr[i][j]);//普通打印方式
}
printf("\n");
}
}
void print2(int(*p)[4],int r,int l)
{
int i = 0;
for (i = 0; i < r; i++)
{
int j = 0;
for (j = 0; j < l; j++)
{
//printf("%d ",(*(p+i))[j]);//这里的p存放的第一行这个一位数组的地址,是一个数组指针,所以:p+i 会跳过i个数组得到第i个数组的地址,然后再对数组地址解引用得到数组首元素的地址,然后通过 数组首元素的地址+下标的方式进行访问
printf("%d ", p[i][j]);//既然p是二维数组首元素第一行一维数组的地址,那就可以通过 首元素地址+下标i 的方式去访问二维数组第i个元素,也就是第i行一位数组的首地址
}
printf("\n");
}
}
int main()
{
int arr[3][4] = { {1,2,3,4},{5,4,3,2},{8,9,6,7} };//创建一个二维数组
//print1(arr, 3, 4);
print2(arr, 3, 4);
//printf("%p\n", arr);
//printf("%p\n", &arr[0][0]);
//printf("%p\n", &(arr[0]));//arr[0]是二维数组中第一行那个一维数组的数组名,&(arr[0])就是取那个一维数组的地址
三者打印结果一样,都是arr[0][0]的地址
//printf("\n");
//printf("%p\n", arr+1);//跳过16个字节,也就是二维数组的一行元素
//printf("%p\n", &arr[0][0]+1);//跳过4个字节,也就是一个整型
//printf("%p\n", &(arr[0])+1);//跳过16个字节,也就是二维数组的一行元素
结果表明二维数组的数组名确实表示首元素的地址,但是二维数组中的首元素是第一行那个一维数组
return 0;
}
看看下面代码分别是什么意思:
int arr[5];//arr是一个整型数组,可以存储5个元素
int *parr1[10];//首先parr1先和[10]结合,说明parr1是一个数组,可以存储10个元素,数组里面存储的元素类型是int*,所以parr1是一个整型指针数组
int (*parr2)[10];//首先parr2先和*结合,说明parr2是一个指针,指向的数据类型是 int [10],这是一个可以存储10个元素的整型数组,所以parr2是一个数组指针
int (*parr3[10])[5];//首先parr3先和[10]结合,说明parr3是一个可以存储10个元素的数组,这个数组里面存的元素类型是:int(*)[5],也就是数组指针类型的数据,所以parr3是一个数组指针数组
int (*parr3[10])[5];
详解:
四、数组参数、指针参数
4.1:一维数组传参
普通一维数组传参:
//普通一维数组传参
void test(int arr[])//形参用数组接收,可以不写数组大小
{}
void test(int arr[10])//形参用数组接收,理所当然
{}
void test(int* arr)//数组名是首元素地址,首元素是一个整型,所以用一个整型指针接收也可以
{}
int main()
{
int arr[10] = {0};
text(arr);//参数是一维数组名,表示首元素地址
return 0;
}
一维指针数组:
//一维指针数组
void test2(int* arr[20])//形参用对应类型的数组来接收理所应当
{}
void test2(int* arr[])//形参用对应的数组接收,可以省略数组元素个数
{}
void test2(int** arr)//数组名表示首元素地址,整型指针数组的首元素是一个整型指针,那首元素的地址就是一个指针的地址,指针的地址就是二级指针,所以形参用一个二级指针来接收
{}
int main()
{
int *arr[20] = { 0 };
text(arr);//参数是一维数组名,表示首元素地址
return 0;
}
4.2:二维数组传参
//二维数组传参
void text(int arr[3][5])//形参用对应类型的二维数组来接收,可行
{}
void text(int arr[][])//形参用对应类型的二维数组来接收,行数可以省略但是列数不可以省略
{}
void text(int arr[][5])形参用对应类型的二维数组来接收,行数可以省略但是列数不可以省略
{}
void text(int(*arr)[5])//数组名是首元素地址,二维数组的首元素是第一行那个一维数组arr[0],所以首元素的地址就是一个一维数组的地址,所以形参应该用一个数组指针来接收,这个数组类型是 int [5]
{}
//一些错误写法
void text(int *arr)//这里的形参本质上是一个一级整型指针,指向一个整型,而我们需要的是一个可以存储一维数组地址的数组指针,所以这样写肯定不行
{}
void text(int *arr[5])//这里的形参本质上是一个可以存放5个元素的一维数组,形参如果是数组的话应该是对应的二维数组,这里肯能也不对
{}
void text(int **arr)//这里的形参是一个二级指针,也就是用来存储指针的地址(一级指针的地址),而我们需要的形参是一个可以存放数组地址的变量,仅仅是一个一维指针,所以这里肯定也是错误的
{}
int main()
{
int arr[3][5] = { 0 };
test(arr);
return 0;
}
4.3:一级指针传参:
//一级指针传参
void print(int* p, int sz)//一级指针作为函数的实参,形参也用一级指针来接收
{
int i = 0;
for(i = 0; i < sz; i++)
{
printf("%d ", *(p + i));//指针变量加上一个偏移量,因为这是一个整形指针,加i就跳过4*i个字节,这里也就是取第i个元素的地址,然后解引用来访问第i个元素
printf("%d ", p[i]);//也可以通过首元素地址+下标的形式来访问数组元素
}
}
int main()
{
int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
int* p = arr;//数组名是首元素的地址
int sz = sizeof(arr) / sizeof(arr[0]);
print(p, sz);//一级指针p作为函数参数,传给函数
return 0;
}
思考: 当一个函数的参数部分为一级指针的时候,函数能接收什么参数?
- 一级指针变量
- 一维数组的数组名
- 变量的地址
4.4:二级指针传参:
//二级指针传参
void test(int **p)//形参直接用一个二级指针来接收
{
printf("num=%d\n", **p);
}
int main()
{
int n = 10;
int* p = &n;
int** pp = &p;
test(pp);
test(&p);
return 0;
}
思考: 当一个函数的参数部分为二级指针的时候,函数能接收什么参数?
- 二级指针变量
- 一级指针的数组
- 一维指针数组的数组名
五:函数指针
函数指针:就是指向函数的指针
int Add(int x, int y)
{
return x + y;
}
int main()
{
printf("%p\n", &Add);
//打印出来一个地址,说明&函数名,确实可以取出函数的地址
//既然是地址那一定可以被存起来
int(*p)(int, int) = &Add;//首先,&ADD得到的是一个地址,说明需要一个指针变量来存储,所以p先和*结合,说明p是指针变量,接着需要知道p这个指针所指向内容的类型,也就是这个函数的类型,函数的类型可以通过函数的声明看出来,去掉函数名剩下的就是函数类型,这里Add的函数类型为:int (int,int)
int(*p)(int, int) = Add;
//&函数名和函数名都是函数的地址
//p 是一个存放函数地址的指针变量 - 函数指针
//函数指针的使用
int ret = (*p)(3, 2);//对p指针解引用得到对应的函数,然后调用它,进行传参
int ret = p(3, 2);//这样写也是可以的,因为可以直接把Add传给p,说明Add和p是等价的,一般的函数调用就是直接通过Add函数名来调用,所以这里可以不用对p解引用,无论p前面有多少个*都无济于事
printf("%d\n", ret);
return 0;
}
注意:&函数名和函数名都是函数的地址
经典例题:
//练习1
int main()
{
( *(void (*)())0 )();//这里:void (*)()是一个函数指针类型,指向一个没有参数,返回值为void类型的函数,这个类型被放在一个括号里,说明要进行强制类型转换,这里把0强制类型转换成(void (*)())型,此时0就成了一个地址,指向一个void ()型函数,然后再通过解引用找到这个函数,进行传参,当然这个函数没有参数,所以最后一个括号是空的
//该代码是一次函数调用
return 0;
}
//练习2
int main()
{
void (*signal(int, void(*)(int))) (int);
//signal是函数名,他有两个参数,一个是int型,一个是void(*)(int)函数指针类型,该函数指针指向一个参数为int,返回类型为void的函数
//函数名和参数都有了,去掉函数名和参数部分剩下的就是函数的返回值类型了
//这里signal函数的返回值类型是void(*)(int)
//该代码是一次函数声明
return 0;
}
//简化
typedef void(*pf_t)(int);//把void(*)(int)型重命名成pf_t,注意pf_t的位置
int main()
{
pf_t signal(int, pf_t);
return 0;
}