C语言中对指针的小结(2)

C语言中对指针的小结(2)

在上一篇小结(1)中对指针进行了浅谈,本文将进一步分析其他类型的指针
一、字符指针:
1.

char* p;//定义一个字符指针,类型为char* ,该指针指向一个字符型的变量

使用如下:

int main()
{
char ch = 'a';
char *pc = &ch;//pc存放ch的地址
*pc = 'w';//对pc解引用将访问到ch这个变量,并给其重新赋值‘w’,此时ch的值被改为字符‘w’.
return 0;
}

若要输出ch的值,会得到’w’
2.

int main()
{
char* pstr = "hello bit.";//这里是把一个字符串首字符的地址放到pstr指针变量
printf("%s\n", pstr);//通过pstr找到这个字符串的起始位置,以%s进行打印即可得到该字符串
return 0;

3.下面是一道笔试题

#include <stdio.h>
int main()
{
char str1[] = "hello bit.";
char str2[] = "hello bit.";
char *str3 = "hello bit.";
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;
}

  • 定义两个数组str1[]和str2[]:在内存中分别给这两个数组分配了两块独立的内存空间,因此这两个数组虽然存放了一样的元素,但是地址是不相同的
  • 该字符串在内存中是一个常量,str2和str3是指向同一个字符串的指针,因此这两个指针存放的地址相同,是同一个字符串的首字符的地址

其输出结果为

str1 and str2 are not same
str3 and str4 are same

二、指针数组:是一个数组

int* arr1[10]; //整形指针的数组,每个元素均为指向整型变量的指针
char *arr2[4]; //一级字符指针的数组,每个元素均为指向字符型变量的指针
char **arr3[5];//二级字符指针的数组,每个元素均为二级指针

三、数组指针:指向数组的指针
如何定义?
p先和* 结合,说明p是一个指针变量,然后指向的是一个大小为10个整型的数组。所以p是一个指针,指
向一个数组,叫数组指针
这里要注意 [] 的优先级要高于* 号的,所以必须加上()来保证p先和*结合

int (*p2)[10];//p2是指向一个存放了10个整形元素的数组,即p2的值是数组的地址

char arr[10];
注意:数组的地址和数组名的区别
数组名arr是数组首元素的地址
数组的地址&arr是整个数组的地址
虽然arr和&arr的值一样,但含义不同
arr+1是该数组第二个元素的地址,&arr+1是该数组最后一个元素后面的地址

  • 数组指针的使用
    二维数组的数组名是第一行数组的地址,将其当作实参传给一个函数,此函数应该用一个数组指针来接收(如print_arr2)或者用一个二维数组来接收(如print_arr1)
#include <stdio.h>
void print_arr1(int arr[3][5], int row, int col)
{
int i = 0;
for(i=0; i<row; i++)
{
for(j=0; j<col; j++)
{
printf("%d ", arr[i][j]);
}
printf("\n");
}
}
void print_arr2(int (*arr)[5], int row, int col)
{
int i = 0;
for(i=0; i<row; i++)
{
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};
print_arr1(arr, 3, 5);
//数组名arr,表示首元素的地址
//但是二维数组的首元素是二维数组的第一行
//所以这里传递的arr,其实相当于第一行的地址,是一维数组的地址
//可以数组指针来接收
print_arr2(arr, 3, 5);
return 0;
}

四、关于传参
1.一维数组传参
给以下函数传一个数组名,哪些函数的形参可以接收?

#include <stdio.h>
void test(int arr[])//ok可以用一维数组接收arr,数组大小可以不必有
{}
void test(int arr[10])//ok
{}
void test(int *arr)//ok可以用指针接收,因为传过来的数组名arr是一个地址,应该用指针变量存放
{}
void test2(int *arr[20])//ok如果传过来的数组名是一个指针数组的数组名arr2,则用一个指针数组来接收
{}
void test2(int **arr)
{}
int main()
{
int arr[10] = {0};
int *arr2[20] = {0};
test(arr);
test2(arr2);
return 0;
}

2.二维数组传参

void test(int arr[3][5])//ok
{}
void test(int arr[][])//错
{}
void test(int arr[][5])//ok
{}
//总结:二维数组传参,函数形参的设计只能省略第一个[]的数字。
//因为对一个二维数组,可以不知道有多少行,但是必须知道一行多少元素。
//这样才方便运算。
void test(int *arr)//错,arr是一个二维数组的数组名,表示第一行数组的地址,应该用数组指针接收
{}
void test(int* arr[5])//错,这里的形参是一个指针数组
{}
void test(int (*arr)[5])//ok,此处是一个数组指针
{}
void test(int **arr)//错,这是一个二级指针
{}
int main()
{
int arr[3][5] = {0};
test(arr);
}

3.一级指针传参

#include <stdio.h>
void print(int *p, int sz)
{
int i = 0;
for(i=0; i<sz; i++)
{
printf("%d\n", *(p+i));//*(p+i)等价于p[i],等价于arr[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,传给函数,p存放的是一维数组的数组名
print(p, sz);
return 0;
}

4、二级指针传参

#include<stdio.h>
void test(int** ptr)
{
printf("num = %d\n", **ptr);//对二级指针进行两次解引用操作可直接访问到n
}
int main()
{
int n = 10;
int*p = &n;
int **pp = &p;
test(pp);//pp是一个二级指针,存放一级指针的地址
test(&p);//p存放n的地址
return 0;
}

五、函数指针

#include <stdio.h>
void test()
{
printf("hehe\n");
}
int main()
{
printf("%p\n", test);
printf("%p\n", &test);
return 0;
}

可以得到输出结果是同一个地址,因此函数名test和&test都是表示该函数的地址
**1.定义一个函数指针:**这是一个指针,应先于*结合
该指针指向的函数类型要有,而函数类型包括函数的参数类型和返回值,因此对函数指针的定义如下所示

int (*p)(int,int);//p是指向一个函数,该函数的两个参数为int,函数的返回值为int

2.这里有两段有趣的代码

//代码1
(*(void (*)())0)();

void(* )()是对一个函数指针类型,加上()则对一个值或变量产生强制类型转换的作用,此处是 (void (* )())0,即把0强制类型转换为一个函数指针,函数指针是一个地址0,又对该地址进行解引用操作得到一个地址为0的值,在该值后加(),因此这段代码整体的效果是一次函数调用,函数名为0处地址的值

//代码2
void (*signal(int , void(*)(int)))(int);

signal(int , void(* )(int))是一个函数,此函数的两个参数:第一个是整型,第二个是函数指针类型
返回值类型:void (* )(int)是一个函数指针类型,若将函数signal(int , void(* )(int))看作一个x,则有void (x )(int);即函数x的返回值是一个函数指针类型
因此该代码整体效果是对一个signal(int , void(
)(int))函数的声明
注:推荐《c陷阱和缺陷》
将代码二简化为:

typedef void(*pfun_t)(int);
pfun_t signal(int, pfun_t);

void()(int)表示一个函数指针的类型,用typedef对这个类型进行重命名:typedef void( * )(int) pfun_t;这是我们可以想到的写法,即pfun_t就代表一个函数指针类型,但是不能这样写,正确写法应该将pfun_t写入括号里,即typedef void(pfun_t)(int);这就对函数指针的类型进行了重命名,接下来就可以用pfun_t来定义一个函数指针了
pfun_t signal(int, pfun_t);由此可以看到函数signal(int, pfun_t)的两个参数类型为:int和函数指针,返回值类型为函数指针,因此,这和代码二的效果相同,都是一次函数声明
五、函数指针数组:这是一个数组
该数组存放的所有元素都是函数指针
1.如何定义?
这是一个数组,因此要先和[ ]结合,又因为这个数组的每个元素的类型为函数指针,则定义如下

int(* parr[20])(int,int);

parr先和[20]结合表示这是一个有20个元素的数组,而该数组每个元素的类型是函数指针,因此得到上面的定义方式,上面的例子中,该数组每个元素指向的函数有两个整型参数且返回值也是整型。
2.函数指针数组的应用——转移表
例子:编写一个简单计算器的程序

#include <stdio.h>
int add(int a, int b)
{
return a + b;
}
int sub(int a, int b)
{
return a - b;
}
int mul(int a, int b)
{
return a*b;
}
int div(int a, int b)
{
return a / b;
}
int main()
{
int x, y;
int input = 1;
int ret = 0;
while (input)
{
printf( "*************************\n" );
printf( " 1:add 2:sub \n" );
printf( " 3:mul 4:div \n" );
printf( "*************************\n" );
printf( "请选择:" );
scanf( "%d", &input);
switch (input)
{
case 1:
printf( "输入操作数:" );
scanf( "%d %d", &x, &y);
ret = add(x, y);
break;
case 2:
printf( "输入操作数:" );
scanf( "%d %d", &x, &y);
ret = sub(x, y);
break;
case 3:
printf( "输入操作数:" );
scanf( "%d %d", &x, &y);
ret = mul(x, y);
break;
case 4:
printf( "输入操作数:" );
scanf( "%d %d", &x, &y);
ret = div(x, y);
break;
default:
printf( "选择错误\n" );
break;
}
printf( "ret = %d\n", ret);
}
return 0;
}

使用函数指针数组的实现:

#include <stdio.h>
int add(int a, int b)
{
return a + b;
}
int sub(int a, int b)
{
return a - b;
}
int mul(int a, int b)
{
return a*b;
}
int div(int a, int b)
{
return a / b;
}
int main()
{
int x, y;
int input = 1;
int ret = 0;
int(*p[5])(int x, int y) = { 0, add, sub, mul, div }; //转移表
while (input)
{
printf( "*************************\n" );
printf( " 1:add 2:sub \n" );
printf( " 3:mul 4:div \n" );
printf( "*************************\n" );
printf( "请选择:" );
scanf( "%d", &input);
if ((input <= 4 && input >= 1))
{
printf( "输入操作数:" );
scanf( "%d %d", &x, &y);
ret = (*p[input])(x, y);
}
else
printf( "输入有误\n" );
printf( "ret = %d\n", ret);
}
return 0;
}

六、指向函数指针数组的指针
这是一个指针,该指针指向一个数组,而这个数组里的每一个元素都是函数指针。
首先,这是一个指针,所定义的变量应该先和*结合,表示它是一个指针,其次它指向的是一个数组,又和[ ]结合,该数组每个元素都是函数指针,又将所得的整体写成定义一个函数指针数组的形式,因此,有如下定义

int (*(*p)[10])(int,int);

再来分析:p先和*结合表示p是一个指针变量,后与[10]结合表示这是一个数组,而这个数组有10个元素,且每个元素都是函数指针,每个函数指针都分别指向一个函数,而这种函数的两个参数和返回值均为整型
七、回调函数
回调函数就是一个通过函数指针调用的函数。如果你把函数的指针(地址)作为参数传递给另一个函数,当这个指针被用来调用其所指向的函数时,我们就说这是回调数。回调函数不是由该函数的实现方直接调用,而是在特定的事件或条件发生时由另外的一方调用的,用于对该事件或条件进行响应。
**注:**回调函数在“用C语言实现通用冒泡排序函数”一文中有所应用,具体见这篇里的My_qsort()函数

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值