一次学透C指针C进阶
Posted 林慢慢i
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了一次学透C指针C进阶相关的知识,希望对你有一定的参考价值。
前言:本章主要内容是C指针进阶部分,旨在一次性讲透指针,主要以程序代码块形式讲解。有些难度,坚持住兄弟们!
文章目录
不知道为啥直接引入目录显示不全,只能插个目录图片了。
数组与指针
实例1
char* ps = "hello";
char arr[] = "hello";
完成以上赋值后,进行如下输出,思考下输出结果?
printf("%c\\n", *ps);
printf("%s\\n", ps);
printf("%s\\n", arr);
答案:分别输出h、hello、hello,第一个h是因为*ps指针指向的就是hello的首字符h,故输出h;后俩个输出本质上一样的,数组名arr和ps一样都是hello的首字符地址,故均输出hello。
实例2
思考下列程序输出结果是两个都相同呢?还是存在不同?(题选自剑指offer)
char str1[] = "hello";
char str2[] = "hello";
const char* str3 = "hello";
const char* str4 = "hello";
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");
答案:str1和str2不同,str3和str4相同。因为str1[]、str2[]两个数组分别创建了一块空间,分别放了hello,而数组名又代表首元素地址,str1和str2分别指向两个hello的首地址,这俩地址不同,故str1和str2不同;而str3和str4指向的hello是常量字符串的首元素地址,该字符串不能被修改,内存中只存一份,故str3和str4相同。
指针数组
定义:本质上是数组,但数组中存放的是指针(地址)
int* arr[3];//存放整形指针的数组
实例1
int a = 10;
int b = 20;
int c = 30;
int* arr[3] = {&a, &b, &c};
int i = 0;
for (i = 0; i < 3; i++)
{
printf("%d ", *(arr[i]));
}
解析:arr[i]内存放的分别是a、b、c的地址,想要打印出具体数字就需要对arr[i]使用解引用操作符*
实例2
int a[5] = { 1,2,3,4,5 };
int b[] = { 2,3,4,5,6 };
int c[] = { 3,4,5,6,7 };
int* arr[3] = {a,b,c};
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]);
}
printf("\\n");
}
解析:int* arr[3] = {a,b,c};相当于int* arr[3] 里边三个指针类型的元素分别指向数组a、数组b、数组c的首地址。输出结果将分别输出数组a、b、c所有元素。
数组指针
定义:是一种指针,是指向数组的指针
实例1
怎么取一个数组的地址,直接用数组名行不行?
int arr[10]={1,2,3};
arr;
&arr;
int (*parr)[10] = &arr;
注释:“&arr”取出的是数组的地址,“arr”是数组首元素的地址,二者本质上不同,但是打印出来显示的值是一样的。parr就是一个数组指针,parr中存放的就是数组的地址。
思考下对于存放以下指针数组应该用什么样子的数组指针?
double* d[5];
应该用:
double* (*pd)[5] = &d;//ok pd就是一个数组指针
实例2
对于数组arr[]而言,arr与&arr意义完全是不一样的,以下代码感受以下:
int arr[10] = {0};
int* p1 = arr;
int (*p2)[10] = &arr;
printf("%p\\n", p1);
printf("%p\\n", p1+1);
printf("%p\\n", p2);
printf("%p\\n", p2+1);
注释:p1是整形指针,+1会跳过四个字节,p2是一个数组指针,+1会跳过一个数组。
再提一次,数组名是数组首元素的地址,但是有2个例外:
- sizeof(数组名) - 数组名表示整个数组,计算的是整个数组大小,单位是字节
- &数组名 - 数组名表示整个数组,取出的是整个数组的地址
实例3
数组指针的数组遍历:
int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
int (*pa)[10] = &arr;
int i = 0;
for (i = 0; i < 10; i++)
{
printf("%d ", *((*pa) + i));
}
但是多多少少有点麻烦,没必要用到数组指针,单纯使用指针int *pa=&arr就足够了。
实例4
二维数组通过数组指针传参:
void print2(int(*p)[5], int r, int c)
{
int i = 0;
int j = 0;
for (i = 0; i < r; i++)
{
for (j = 0; j < c; 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 } };
print2(arr, 3, 5);//arr数组名,表示数组首元素的地址
return 0;
}
解析:二维数组的数组名表示首元素的地址,而二维数组的首元素是:第一行。二维数组把每一行看成一个元素。int(*p)[5]是一个指向一维数组的指针,每个数组有5个元素,每个元素都是int类型。
区分下以下数据类型:
int arr[5] 整型数组
int * parr1[10] 整型指针的数组
int ( * parr2)[10] 数组指针,该指针能够指向一个数组,数组有10个元素,每个元素的类型是int
int ( * parr3[10])[5] 一个存放数组指针的数组,能存放10个数组指针,每个数组指针指向一个数组,数组有5个元素,每个元素是int类型,图片拆解如下:
一维数组参数
判断哪些传参类型可以?哪些不行?
答案:都正确,这里分析下最后一个二级指针来传递指针函数
二维数组传参
判断哪些传参类型可以?哪些不行?
答案见下图(直接手写分析了):
插播内容(非指针内容),让大家大脑休息下,如下代码思考输出是>还是<呢?
i--;
if (i > sizeof(i))//
{
printf(">\\n");
}
else
{
printf("<\\n");
}
第一眼看是不是觉得一点难度都没有,i没有初始化默认是0,i–执行后i的数值是-1,sizeof(i)得到4,-1<4所以认为输出是<,但很遗憾,你成功被坑了。
解析:因为sizeof()得到的数据类型是无符号整型unsigned int,所以-1直接转换成无符号整形的形式,变成一个巨大的数字,所以结果是>。
一级指针传参
void test(char* p)
{
}
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]);
//p是一级指针
print(p, sz);
char ch = 'w';
char* p1 = &ch;
test(&ch);//char*
test(p1);
return 0;
}
解析:遇见函数形式参数类型为一级指针(比如char*)时要知道实际参数传递过来的是地址。
二级指针传参
二级指针的3种传参方式,如下程序所示。
void test(int** p2)
{
**p2 = 20;
}
int main()
{
int a = 10;
int* pa = &a;
int** ppa = &pa;
//怎么把二级指针进行传参呢?
test(ppa);
printf("%d\\n", a);//
test(&pa);
printf("%d\\n", a);//
int* arr[10] = {0};
test(arr);
printf("%d\\n", a);//
return 0;
}
解析:pa是一级指针,ppa是二级指针,test(ppa)把二级指针ppa进行传参;test(&pa)传一级指针变量的地址;test(arr)传存放一级指针的数组,传递了arr数组名,数组名代表首元素的地址,而arr数组每个元素都是int *类型,int *的地址相当于int * *,这样一来,符合咱们 void test(int ** p2)中形参的要求,也可以传递。
函数指针
定义:存放函数地址的指针
程序1
int Add(int x, int y)
{
return x + y;
}
int main()
{
//&函数名 - 取到的就是函数的地址
//pf就是一个函数指针变量
int (*pf)(int, int) = &Add;
printf("%p\\n", &Add);
printf("%p\\n", Add);
return 0;
}
解释:函数名Add和&Add的效果是一样的。&函数名 - 取到的就是函数的地址;函数名-取到的也是函数的地址。
程序2
int main()
{
//pf就是一个函数指针变量
//int (*pf)(int, int) = &Add;
int (*pf)(int, int) = Add;//Add === pf
int ret1 = (*pf)(3, 5);//方式1
int ret2 = pf(3, 5);//方式2
int ret3 = Add(3, 5);//方式3
//int ret4 = * pf(3, 5);//err
printf("%d %d %d \\n", ret1, ret2, ret3);
return 0;
}
解释:int ret1 = (*pf)(3, 5); int ret2 = pf(3, 5);int ret3 = Add(3, 5);三种调用方式效果一样,int ( *pf )(int, int) = Add;Add能把地址放进pf,说明两者一样的,Add==pf。方式1和方式2一模一样,那个解引用操作符 * 没有任何作用,就是一个摆设之所以放着 * 就是为了便于理解。
OK,fine,到这里咱们进行一个小总结!
阅读两段有趣的代码(程序选自<c陷阱与缺陷>):
程序1
(*(void(*)())0)();
调用0地址处的函数
该函数无参,返回类型是void
- void(*)( ) - 函数指针类型
- (void(*)( ))0 - 对0进行强制类型转换,被解释为一个函数地址
- *(void( * )( ))0 - 对0地址进行了解引用操作
- ( *(void( * )( ) )0)( ) - 调用0地址处的函数
程序2
void (* signal(int, void(*)(int) ) )(int);
- signal 和()先结合,说明signal是函数名
- signal函数的第一个参数的类型是int,第二个参数的类型是函数指针
该函数指针,指向一个参数为int,返回类型是void的函数- signal函数的返回类型也是一个函数指针
该函数指针,指向一个参数为int,返回类型是void的函数
signal是一个函数的声明
函数指针数组
int Add(int x, int y)
{
return x + y;
}
int Sub(int x, int y)
{
return x - y;
}
int main()
{
int (*pf1)(int, int) = Add;
int (*pf2)(int, int) = Sub;
int (*pfArr[2])(int, int) = {Add, Sub};//pfArr就是函数指针数组
return 0;
}
解析:int (*pfArr[2])(int, int) = {Add, Sub};函数指针数组,用于存放同类型的函数指针。
设计一个计算器,计算整型变量的加减乘除。
计算器解法1
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. add 2. sub ****\\n");
printf("**** 3. mul 4. div ****\\n");
printf("**** 0. exit ****\\n");
printf("**************************\\n");
}
int main()
{
int input = 0;
//计算器-计算整型变量的加、减、乘、除
//a&b a^b a|b a>>b a<<b a>b
do {
menu();
//pfArr就是函数指针数组
//转移表 - 《C和指针》
int (*pfArr[5])(int, int) = { NULL, Add, Sub, Mul, Div };
int x = 0;
int y = 0;
int ret = 0;
printf("请选择:>");
scanf("%d", &input);//2
if (input >= 1 && input <= 4)
{
printf("请输入2个操作数>:");
scanf("%d %d", &x, &y);
ret = (pfArr[input])(x, y);
printf("ret = %d\\n", ret);
}
else if(input == 0)
{
printf("退出程序\\n");
break;
}
else
{
printf("选择错误\\n");
}
} while (input);
return 0;
}
回调函数
定义:回调函数就是一个通过函数指针调用的函数。 如果你把函数的指针(地址)作为参数传递给另一个函数,当这个指针被用来调用其所指向的函数时, 我们就说这是回调函数
以上是关于一次学透C指针C进阶的主要内容,如果未能解决你的问题,请参考以下文章