C语言指针详解

  • 字符指针
    • 1.如何定义
    • 2.类型和指向的内容
    • 3.代码例子
  • 指针数组
    • 1.如何定义
    • 2.类型和内容
  • 数组指针
    • 1.如何定义
    • 2.类型和指向类型
    • 3.数组名vs&数组名
    • 数组指针运用
  • 数组参数&指针参数
    • 一维数组传参
    • 二维数组传参
    • 一级指针传参
    • 二级指针传参
  • 函数指针
    • 1.如何定义
    • 2.类型和指向内容
    • 3.函数名vs&函数名
    • 4.两个有趣的代码
  • 函数指针数组
    • 1.如何定义
    • 2.类型和内容
    • 3.1代码例子(switch语句实现计算器)
    • 3.2代码例子(函数指针数组实现计算器)
  • 指向函数指针数组的指针
    • 1.如何定义
    • 2.类型和指向内容
  • 回调函数
  • 一个小知识点如何找到指针和数组的类型和(指向)存储内容

博主主页zoro-1
祝大家有个好心情,给大家分享一下我拍的彩虹

字符指针

1.如何定义

int main() {char ch='w';char *pc=&ch;*pc='w';return 0;}

2.类型和指向的内容

譬如char pc=&ch;
将=左边pc去掉就是指针类型char

将*和pc去掉就是指针指向的内容char

3.代码例子

例1

int main(){const char* pstr = "hello";//这里是把一个字符串放到pstr指针变量里了吗?printf("%s\n", pstr);return 0;}

这时我就要问一个问题了,pstr存储的是什么?
没错pstr存储的是hello的首元素地址

例2

#include <stdio.h>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");elseprintf("str1 and str2 are not same\n");if(str3==str4)printf("str3 and str4 are same\n");elseprintf("str3 and str4 are not same\n");return0;

大家猜猜这段代码会输出什么?

代码解释:str1,str2是存放char的数组的数组名,那么他们存放的就是数组首元素的地址,即使他们的内容相同,但地址是随机的,而str3,str4是被const修饰的指针变量,存放的也是字符的地址只不过这里的hello bit.在这里是存放在常量池的下一个str4不需要开辟新的空间,所以str3,str4他们指向的是同一个hello bit.所以相同

一个小知识点:
如果 const 用于修饰字符串常量,那么该字符串常量将存储在常量存储区(Constant Storage Area)。
常量存储区是用于存储常量字符串和全局常量的特殊内存区域,其中的数据在程序运行期间保持不变。

指针数组

1.如何定义

int main(){int arr[5]={1,2,3,4,5};int arr1[5]={2.3.4.5.6};int arr2[5]={3,4,5,6,7};int*p[3]={arr,arr1,arr};}

2.类型和内容

去掉名字p就是类型
去掉p【3】就是存储的内容int*

数组指针

1.如何定义

int arr[5]={1,2,3,4,5};int (*p)[10]=&arr;//解释:p先和*结合,说明p是一个指针变量,然后指着指向的是一个大小为10个整型的数组。所以p是一个指针,指向一个数组,叫数组指针。//这里要注意:[]的优先级要高于*号的,所以必须加上()来保证p先和*结合。

2.类型和指向类型

去掉名字p就是类型
去掉*p就是指向内容

3.数组名vs&数组名

数组名是数组的首元素地址
但有两个例外:
1.&arr得arr表示整个数组的地址
2.sizeof(arr)表示整个数组的大小

#include int main(){int arr[10] = {0};printf("%p\n", arr);printf("%p\n", &arr);return 0;}

大家猜猜这段代码执行结果是什么,如果看过我之前的指针初阶就应该知道他们的输出内容一样

代码解释:arr是数组首元素地址,&arr是整个数组的地址,虽然他们输出的内容一样,但是他们的权重不一样,
arr+1会跳过4字节,&arr+1会跳过整个数组也就是10*4,一共40个字节

#include int main(){int arr[10] = { 0 };printf("arr = %p\n", arr);printf("&arr= %p\n", &arr);printf("arr+1 = %p\n", arr+1);printf("&arr+1= %p\n", &arr+1);return 0;}


这段代码就能很好解释&arr和arr的区别了

数组指针运用

我们很少用在一维数组,多数用在二维数组,接下来我用代码让大家感受一下数组指针
一维数组:

#include int main(){int arr[10] = {1,2,3,4,5,6,7,8,9,0};int (*p)[10] = &arr;//把数组arr的地址赋值给数组指针变量p//但是我们一般很少这样写代码return 0;}

二维数组:

一个数组指针的使用:学了指针数组和数组指针我们来一起回顾并看看下面代码的意思:#include 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]);//相当于*(*(arr+i)+j)}printf("\n");}}#include int main(){int arr[3][5] = {1,2,3,4,5,6,7,8,9,10};print_arr1(arr, 3, 5);//数组名arr,表示首元素的地址//但是二维数组的首元素是二维数组的第一行//所以这里传递的arr,其实相当于第一行的地址,是一维数组的地址//可以数组指针来接收我们发现这两个遍历数组的1方法只有接收数组第一个形参不一样这里写成int arr[3][5]是为了大家好理解,编译器会将int arr[3][5]转化成int (*arr)[5]print_arr2(arr, 3, 5);return 0;}

数组参数&指针参数

在写代码时候难免要把【数组】或者【指针】传给函数,那函数的参数该如何设计呢?

一维数组传参

#include void test(int arr[])//ok" />{}void test(int arr[10])//ok?显然是可以的只是加上了长度{}void test(int *arr)//ok?这种可以int*arr是整型指针,一维数组传过来是第一个元素地址,第一个元素也是整型{}void test2(int *arr[20])//ok?这种显然可以的传的是指针数组,也是用指针数组接收{}void test2(int **arr)//ok?传的是指针数组的首元素地址,而元素又是指针所以用二级指针接收,二级指针解引用一次得到首元素(首元素就是指针int*{}int main(){int arr[10] = {0};int *arr2[20] = {0};test(arr);test2(arr2);}

二维数组传参

void test(int arr[3][5])//ok?显然是可以的传二维数组用二维数组接收{}void test(int arr[][])//ok?虽然形参是二维数组,但是不能省略列{}void test(int arr[][5])//ok?行可以省略列不能省略所以可以{}//总结:二维数组传参,函数形参的设计只能省略第一个[]的数字。//因为对一个二维数组,可以不知道有多少行,但是必须知道一行多少元素。//这样才方便运算。void test(int *arr)//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);}

总结:
一维整形数组传参数,可以用一维整形数组接收,也可以用整形指针接收
一维指针数组,可以用一维指针数组接收,也可以用二维指针接收
二维整形数组传参数,可以用二维整形数组接收,也可以用数组指针接收

一级指针传参

#include 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);//也可以直接写成printf(p,sz);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;}

函数指针

上面介绍了整形指针,数组指针,那么函数有没有指针呢?
答案是有

1.如何定义

#include void test(){printf("hehe\n");}int main(){void (*p)()=&test;return 0;}

2.类型和指向内容

去掉名字p就是类型
去掉*p就是指向内容

3.函数名vs&函数名

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

注:函数名是函数地址,&函数名也是函数地址,没有区别

4.两个有趣的代码

:推荐《C陷阱和缺陷》这本书中提及这两个代码。//代码1(*(void (*)())0)();//代码2void (*signal(int , void(*)(int)))(int);

*代码1解释是将0看做成地址强制转化成函数指针然后解引用,调用函数
代码2解释signal()是一个函数有两个参数int,viod(*)(int),返回值是一个函数指针
代码2简化
typedef void(pfun_t)(int);
pfun_t signal(int, pfun_t);

函数指针数组

有指针数组,那么有没有函数指针数组呢
答案是有

1.如何定义

#include #include int Add(int x, int y){return x + y;}int Sub(int x, int y){return x - y;}int Mul(int x, int y){return x * y;}int Div(int x, int y){return x / y;}int main(){//int (*pf1)(int, int) = Add;//int (*pf2)(int, int) = Sub;//int (*pf3)(int, int) = Mul;//int (*pf4)(int, int) = Div;//函数指针数组//int (*pfArr[4])(int, int) = {Add, Sub, Mul, Div};//return 0;}

2.类型和内容

去掉pfArr就是类型
去掉pfArr【4】就是存储的内容函数指针

3.1代码例子(switch语句实现计算器)

如果让你们实现自算器的简易功能,你们会怎么实现

int Add(int x, int y){return x + y;}int Sub(int x, int y){return x - y;}int Mul(int x, int y){return x * y;}int Div(int x, int y){return x / y;}//目录void menu(){printf("***************************\n");printf("*****1.add2.sub******\n");printf("*****3.mul4.div******\n");printf("*****0.exit******\n");printf("***************************\n");}int main(){int input = 0;int x = 0;int y = 0;int ret = 0;do{menu();printf("请选择:>");scanf("%d", &input);switch (input){case 1:printf("请输入两个操作数:");scanf("%d %d", &x, &y);ret = Add(x, y);printf("ret = %d\n", ret);break;case 2:printf("请输入两个操作数:");scanf("%d %d", &x, &y);ret = Sub(x, y);printf("ret = %d\n", ret);break;case 3:printf("请输入两个操作数:");scanf("%d %d", &x, &y);ret = Mul(x, y);printf("ret = %d\n", ret);break;case 4:printf("请输入两个操作数:");scanf("%d %d", &x, &y);ret = Div(x, y);printf("ret = %d\n", ret);break;case 0:printf("退出计算器\n");break;default:printf("选择错误,重新选择\n");break;}} while (input);return 0;}

有没有感觉上面这段代码很冗杂需要这么多次case
接下来我用函数指针数组再来实现一下

3.2代码例子(函数指针数组实现计算器)

int Add(int x, int y){return x + y;}int Sub(int x, int y){return x - y;}int Mul(int x, int y){return x * y;}int Div(int x, int y){return x / y;}//...void menu(){printf("***************************\n");printf("*****1.add2.sub******\n");printf("*****3.mul4.div******\n");printf("*****0.exit******\n");printf("***************************\n");}int main(){int input = 0;int x = 0;int y = 0;int ret = 0;//函数指针数组的使用 - 转移表int (* pfArr[5])(int, int) = {NULL, Add, Sub, Mul, Div};//0 1234do{menu();printf("请选择:>");scanf("%d", &input);if (input >= 1 && input <= 4){printf("请输入两个操作数:");scanf("%d %d", &x, &y);ret = pfArr[input](x, y);printf("ret = %d\n", ret);}else if(input == 0){printf("退出计算器\n");}else{printf("选择错误,重新选择\n");}} while (input);return 0;}

这里我们将函数储存在数组里面,直接通过下标调用是不是感觉很方便

指向函数指针数组的指针

这里给大家表演一个套娃,是不是感觉很绕口,这里我们不过多解释只讲定义(用处不多);

1.如何定义

void test(const char* str){printf("%s\n", str);}int main(){void (*pf)(const char*) = test;//pf是函数指针变量void (*pfArr[10])(const char*);//pfArr是存放函数指针的数组void (* (*p) [10])(const char*) = &pfArr;//p指向函数指针数组的指针return 0;}

2.类型和指向内容

去掉p就是类型
去掉*p就是指向内容

回调函数

这个意思就是有函数A,B,main方法调用函数A将函数B地址作为参数传到函数A,A中利用函数地址调用函数B
这个我会在下一篇博客讲解qsort时举例讲解,

一个小知识点如何找到指针和数组的类型和(指向)存储内容

*类型都是去掉名字
指针指向的内容是去掉(指针名字)
数组存储内容是去掉名字和【】(中括号)

更多指针相关内容请听下回讲解,看到这里了,不妨给博主给个三连,要是想持续收听,也可以关注博主, 让我们一起变得更强吧,大家加油!!!!!